Skip to content

Commit fb807bb

Browse files
committed
Import Overlapping FFT from Tympan
1 parent 0232e19 commit fb807bb

File tree

7 files changed

+1035
-0
lines changed

7 files changed

+1035
-0
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
2+
#ifndef _AudioEffectFormantShiftFD_F32_h
3+
#define _AudioEffectFormantShiftFD_F32_h
4+
5+
#include "AudioStream_F32.h"
6+
#include <arm_math.h>
7+
#include "FFT_Overlapped_F32.h"
8+
9+
class AudioEffectFormantShiftFD_F32 : public AudioStream_F32
10+
{
11+
public:
12+
//constructors...a few different options. The usual one should be: AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings, const int _N_FFT)
13+
AudioEffectFormantShiftFD_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {};
14+
AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings) :
15+
AudioStream_F32(1, inputQueueArray_f32) {
16+
sample_rate_Hz = settings.sample_rate_Hz;
17+
}
18+
AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings, const int _N_FFT) :
19+
AudioStream_F32(1, inputQueueArray_f32) {
20+
setup(settings, _N_FFT);
21+
}
22+
23+
//destructor...release all of the memory that has been allocated
24+
~AudioEffectFormantShiftFD_F32(void) {
25+
if (complex_2N_buffer != NULL) delete complex_2N_buffer;
26+
}
27+
28+
int setup(const AudioSettings_F32 &settings, const int _N_FFT) {
29+
sample_rate_Hz = settings.sample_rate_Hz;
30+
int N_FFT;
31+
32+
//setup the FFT and IFFT. If they return a negative FFT, it wasn't an allowed FFT size.
33+
N_FFT = myFFT.setup(settings, _N_FFT); //hopefully, we got the same N_FFT that we asked for
34+
if (N_FFT < 1) return N_FFT;
35+
N_FFT = myIFFT.setup(settings, _N_FFT); //hopefully, we got the same N_FFT that we asked for
36+
if (N_FFT < 1) return N_FFT;
37+
38+
//decide windowing
39+
Serial.println("AudioEffectFormantShiftFD_F32: setting myFFT to use hanning...");
40+
(myFFT.getFFTObject())->useHanningWindow(); //applied prior to FFT
41+
#if 1
42+
if (myIFFT.getNBuffBlocks() > 3) {
43+
Serial.println("AudioEffectFormantShiftFD_F32: setting myIFFT to use hanning...");
44+
(myIFFT.getIFFTObject())->useHanningWindow(); //window again after IFFT
45+
}
46+
#endif
47+
48+
//print info about setup
49+
Serial.println("AudioEffectFormantShiftFD_F32: FFT parameters...");
50+
Serial.print(" : N_FFT = "); Serial.println(N_FFT);
51+
Serial.print(" : audio_block_samples = "); Serial.println(settings.audio_block_samples);
52+
Serial.print(" : FFT N_BUFF_BLOCKS = "); Serial.println(myFFT.getNBuffBlocks());
53+
Serial.print(" : IFFT N_BUFF_BLOCKS = "); Serial.println(myIFFT.getNBuffBlocks());
54+
Serial.print(" : FFT use window = "); Serial.println(myFFT.getFFTObject()->get_flagUseWindow());
55+
Serial.print(" : IFFT use window = "); Serial.println((myIFFT.getIFFTObject())->get_flagUseWindow());
56+
57+
//allocate memory to hold frequency domain data
58+
complex_2N_buffer = new float32_t[2 * N_FFT];
59+
60+
//we're done. return!
61+
enabled = 1;
62+
return N_FFT;
63+
}
64+
65+
//void setLowpassFreq_Hz(float freq_Hz) { lowpass_freq_Hz = freq_Hz; }
66+
//float getLowpassFreq_Hz(void) { return lowpass_freq_Hz; }
67+
float setScaleFactor(float scale_fac) {
68+
if (scale_fac < 0.00001) scale_fac = 0.00001;
69+
return shift_scale_fac = scale_fac;
70+
}
71+
float getScaleFactor(void) {
72+
return shift_scale_fac;
73+
}
74+
75+
virtual void update(void);
76+
77+
private:
78+
int enabled = 0;
79+
float32_t *complex_2N_buffer;
80+
audio_block_f32_t *inputQueueArray_f32[1];
81+
FFT_Overlapped_F32 myFFT;
82+
IFFT_Overlapped_F32 myIFFT;
83+
float lowpass_freq_Hz = 1000.f;
84+
float sample_rate_Hz = AUDIO_SAMPLE_RATE;
85+
86+
float shift_scale_fac = 1.0; //how much to shift formants (frequency multiplier). 1.0 is no shift
87+
};
88+
89+
90+
void AudioEffectFormantShiftFD_F32::update(void)
91+
{
92+
//get a pointer to the latest data
93+
audio_block_f32_t *in_audio_block = AudioStream_F32::receiveReadOnly_f32();
94+
if (!in_audio_block) return;
95+
96+
//simply return the audio if this class hasn't been enabled
97+
if (!enabled) {
98+
AudioStream_F32::transmit(in_audio_block);
99+
AudioStream_F32::release(in_audio_block);
100+
return;
101+
}
102+
103+
//convert to frequency domain
104+
myFFT.execute(in_audio_block, complex_2N_buffer);
105+
AudioStream_F32::release(in_audio_block); //We just passed ownership to myFFT, so release it here.
106+
107+
// ////////////// Do your processing here!!!
108+
109+
//define some variables
110+
int fftSize = myFFT.getNFFT();
111+
int N_2 = fftSize / 2 + 1;
112+
int source_ind; // neg_dest_ind;
113+
float source_ind_float, interp_fac;
114+
float new_mag, scale;
115+
float orig_mag[N_2];
116+
//int max_source_ind = (int)(((float)N_2) * (10000.0 / (48000.0 / 2.0))); //highest frequency bin to grab from (Assuming 48kHz sample rate)
117+
118+
#if 1
119+
float max_source_Hz = 10000.0; //highest frequency to use as source data
120+
int max_source_ind = min(int(max_source_Hz / sample_rate_Hz * fftSize + 0.5),N_2);
121+
#else
122+
int max_source_ind = N_2; //this line causes this feature to be defeated
123+
#endif
124+
125+
//get the magnitude for each FFT bin and store somewhere safes
126+
arm_cmplx_mag_f32(complex_2N_buffer, orig_mag, N_2);
127+
128+
//now, loop over each bin and compute the new magnitude based on shifting the formants
129+
for (int dest_ind = 1; dest_ind < N_2; dest_ind++) { //don't start at zero bin, keep it at its original
130+
131+
//what is the source bin for the new magnitude for this current destination bin
132+
source_ind_float = (((float)dest_ind) / shift_scale_fac) + 0.5;
133+
//source_ind = (int)(source_ind_float+0.5); //no interpolation but round to the neariest index
134+
//source_ind = min(max(source_ind,1),N_2-1);
135+
source_ind = min(max(1, (int)source_ind_float), N_2 - 2); //Chip: why -2 and not -1? Because later, for for the interpolation, we do a +1 and we want to stay within nyquist
136+
interp_fac = source_ind_float - (float)source_ind;
137+
interp_fac = max(0.0, interp_fac); //this will be used in the interpolation in a few lines
138+
139+
//what is the new magnitude
140+
new_mag = 0.0; scale = 0.0;
141+
if (source_ind < max_source_ind) {
142+
143+
//interpolate in the original magnitude vector to find the new magnitude that we want
144+
//new_mag=orig_mag[source_ind]; //the magnitude that we desire
145+
//scale = new_mag / orig_mag[dest_ind];//compute the scale factor
146+
new_mag = orig_mag[source_ind];
147+
new_mag += interp_fac * (orig_mag[source_ind] - orig_mag[source_ind + 1]);
148+
scale = new_mag / orig_mag[dest_ind];
149+
150+
//apply scale factor
151+
complex_2N_buffer[2 * dest_ind] *= scale; //real
152+
complex_2N_buffer[2 * dest_ind + 1] *= scale; //imaginary
153+
} else {
154+
complex_2N_buffer[2 * dest_ind] = 0.0; //real
155+
complex_2N_buffer[2 * dest_ind + 1] = 0.0; //imaginary
156+
}
157+
158+
//zero out the lowest bin
159+
complex_2N_buffer[0] = 0.0; //real
160+
complex_2N_buffer[1] = 0.0; //imaginary
161+
}
162+
163+
//rebuild the negative frequency space
164+
myFFT.rebuildNegativeFrequencySpace(complex_2N_buffer); //set the negative frequency space based on the positive
165+
166+
167+
// ///////////// End do your processing here
168+
169+
//call the IFFT
170+
audio_block_f32_t *out_audio_block = myIFFT.execute(complex_2N_buffer); //out_block is pre-allocated in here.
171+
172+
173+
//send the returned audio block. Don't issue the release command here because myIFFT will re-use it
174+
AudioStream_F32::transmit(out_audio_block); //don't release this buffer because myIFFT re-uses it within its own code
175+
return;
176+
};
177+
#endif
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// FormantShifter_FD
2+
//
3+
// Demonstrate formant shifting via frequency domain processin.
4+
//
5+
// Created: Chip Audette (OpenAudio) March 2019
6+
//
7+
// Approach:
8+
// * Take samples in the time domain
9+
// * Take FFT to convert to frequency domain
10+
// * Manipulate the frequency bins to do the formant shifting
11+
// * Take IFFT to convert back to time domain
12+
// * Send samples back to the audio interface
13+
//
14+
// The amount of formant shifting is controled via the Serial link.
15+
// It defaults to a modest upward shifting of the formants
16+
//
17+
// Built for the Tympan library for Teensy 3.6-based hardware
18+
//
19+
// MIT License. Use at your own risk.
20+
//
21+
22+
#include <Tympan_Library.h>
23+
#include "AudioEffectFormantShiftFD_F32.h" //the local file holding your custom function
24+
#include "SerialManager.h"
25+
26+
//set the sample rate and block size
27+
const float sample_rate_Hz = 44117.f; ; //24000 or 44117 (or other frequencies in the table in AudioOutputI2S_F32)
28+
const int audio_block_samples = 128; //for freq domain processing choose a power of 2 (16, 32, 64, 128) but no higher than 128
29+
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples);
30+
31+
//create audio library objects for handling the audio
32+
Tympan audioHardware(TympanRev::D); //do TympanRev::C or TympanRev::D
33+
AudioInputI2S_F32 i2s_in(audio_settings); //Digital audio *from* the Tympan AIC.
34+
AudioEffectFormantShiftFD_F32 formantShift(audio_settings); //create the frequency-domain processing block
35+
AudioEffectGain_F32 gain1; //Applies digital gain to audio data.
36+
AudioOutputI2S_F32 i2s_out(audio_settings); //Digital audio out *to* the Tympan AIC.
37+
38+
//Make all of the audio connections
39+
AudioConnection_F32 patchCord1(i2s_in, 0, formantShift, 0); //use the Left input
40+
AudioConnection_F32 patchCord2(formantShift, 0, gain1, 0); //connect to gain
41+
AudioConnection_F32 patchCord3(gain1, 0, i2s_out, 0); //connect to the left output
42+
AudioConnection_F32 patchCord4(gain1, 0, i2s_out, 1); //connect to the right output
43+
44+
45+
//control display and serial interaction
46+
bool enable_printCPUandMemory = false;
47+
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; };
48+
SerialManager serialManager(audioHardware);
49+
#define mySerial audioHardware //audioHardware is a printable stream!
50+
51+
//inputs and levels
52+
float input_gain_dB = 20.0f; //gain on the microphone
53+
float formant_shift_gain_correction_dB = 0.0; //will be used to adjust for gain in formant shifter
54+
float vol_knob_gain_dB = 0.0; //will be overridden by volume knob
55+
void switchToPCBMics(void) {
56+
mySerial.println("Switching to PCB Mics.");
57+
audioHardware.inputSelect(TYMPAN_INPUT_ON_BOARD_MIC); // use the microphone jack - defaults to mic bias OFF
58+
audioHardware.setInputGain_dB(input_gain_dB);
59+
}
60+
void switchToLineInOnMicJack(void) {
61+
mySerial.println("Switching to Line-in on Mic Jack.");
62+
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_LINEIN); // use the microphone jack - defaults to mic bias OFF
63+
audioHardware.setInputGain_dB(0.0);
64+
}
65+
void switchToMicInOnMicJack(void) {
66+
mySerial.println("Switching to Mic-In on Mic Jack.");
67+
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_MIC); // use the microphone jack - defaults to mic bias OFF
68+
audioHardware.setInputGain_dB(input_gain_dB);
69+
}
70+
71+
// define the setup() function, the function that is called once when the device is booting
72+
void setup() {
73+
audioHardware.beginBothSerial(); delay(1000);
74+
mySerial.println("FormantShifter: starting setup()...");
75+
mySerial.print(" : sample rate (Hz) = "); mySerial.println(audio_settings.sample_rate_Hz);
76+
mySerial.print(" : block size (samples) = "); mySerial.println(audio_settings.audio_block_samples);
77+
78+
// Audio connections require memory to work. For more
79+
// detailed information, see the MemoryAndCpuUsage example
80+
AudioMemory_F32(40, audio_settings);
81+
82+
// Configure the frequency-domain algorithm
83+
int overlap_factor = 4; //set to 4 or 8 or either 75% overlap (4x) or 87.5% overlap (8x)
84+
int N_FFT = audio_block_samples * overlap_factor;
85+
formantShift.setup(audio_settings, N_FFT); //do after AudioMemory_F32();
86+
formantShift.setScaleFactor(1.5); //1.0 is no formant shifting.
87+
if (overlap_factor == 4) {
88+
formant_shift_gain_correction_dB = -3.0;
89+
} else if (overlap_factor == 8) {
90+
formant_shift_gain_correction_dB = -9.0;
91+
}
92+
93+
//Enable the Tympan to start the audio flowing!
94+
audioHardware.enable(); // activate AIC
95+
96+
//setup DC-blocking highpass filter running in the ADC hardware itself
97+
float cutoff_Hz = 60.0; //set the default cutoff frequency for the highpass filter
98+
audioHardware.setHPFonADC(true,cutoff_Hz,audio_settings.sample_rate_Hz); //set to false to disble
99+
100+
//Choose the desired input
101+
switchToPCBMics(); //use PCB mics as input
102+
//switchToMicInOnMicJack(); //use Mic jack as mic input (ie, with mic bias)
103+
//switchToLineInOnMicJack(); //use Mic jack as line input (ie, no mic bias)
104+
105+
//Set the desired volume levels
106+
audioHardware.volume_dB(0); // headphone amplifier. -63.6 to +24 dB in 0.5dB steps.
107+
108+
// configure the blue potentiometer
109+
servicePotentiometer(millis(),0); //update based on the knob setting the "0" is not relevant here.
110+
111+
//finish the setup by printing the help menu to the serial connections
112+
serialManager.printHelp();
113+
}
114+
115+
116+
// define the loop() function, the function that is repeated over and over for the life of the device
117+
void loop() {
118+
119+
//respond to Serial commands
120+
while (Serial.available()) serialManager.respondToByte((char)Serial.read()); //USB Serial
121+
//while (Serial1.available()) serialManager.respondToByte((char)Serial1.read()); //BT Serial
122+
123+
//check the potentiometer
124+
servicePotentiometer(millis(), 100); //service the potentiometer every 100 msec
125+
126+
//check to see whether to print the CPU and Memory Usage
127+
if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec
128+
129+
} //end loop();
130+
131+
132+
// ///////////////// Servicing routines
133+
134+
//servicePotentiometer: listens to the blue potentiometer and sends the new pot value
135+
// to the audio processing algorithm as a control parameter
136+
void servicePotentiometer(unsigned long curTime_millis, const unsigned long updatePeriod_millis) {
137+
//static unsigned long updatePeriod_millis = 100; //how many milliseconds between updating the potentiometer reading?
138+
static unsigned long lastUpdate_millis = 0;
139+
static float prev_val = -1.0;
140+
141+
//has enough time passed to update everything?
142+
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
143+
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
144+
145+
//read potentiometer
146+
float val = float(audioHardware.readPotentiometer()) / 1023.0; //0.0 to 1.0
147+
val = (1.0/9.0) * (float)((int)(9.0 * val + 0.5)); //quantize so that it doesn't chatter...0 to 1.0
148+
149+
//use the potentiometer value to control something interesting
150+
if (abs(val - prev_val) > 0.05) { //is it different than befor?
151+
prev_val = val; //save the value for comparison for the next time around
152+
153+
#if 0
154+
//set the volume of the system
155+
setVolKnobGain_dB(val*45.0f - 10.0f - input_gain_dB);
156+
#else
157+
//set the amount of formant shifting
158+
float new_scale_fac = powf(2.0,(val-0.5)*2.0);
159+
formantShift.setScaleFactor(new_scale_fac);
160+
#endif
161+
}
162+
163+
164+
lastUpdate_millis = curTime_millis;
165+
} // end if
166+
} //end servicePotentiometer();
167+
168+
169+
170+
//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
171+
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) {
172+
//static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
173+
static unsigned long lastUpdate_millis = 0;
174+
175+
//has enough time passed to update everything?
176+
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
177+
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
178+
mySerial.print("printCPUandMemory: ");
179+
mySerial.print("CPU Cur/Peak: ");
180+
mySerial.print(audio_settings.processorUsage());
181+
//mySerial.print(AudioProcessorUsage()); //if not using AudioSettings_F32
182+
mySerial.print("%/");
183+
mySerial.print(audio_settings.processorUsageMax());
184+
//mySerial.print(AudioProcessorUsageMax()); //if not using AudioSettings_F32
185+
mySerial.print("%, ");
186+
mySerial.print("Dyn MEM Float32 Cur/Peak: ");
187+
mySerial.print(AudioMemoryUsage_F32());
188+
mySerial.print("/");
189+
mySerial.print(AudioMemoryUsageMax_F32());
190+
mySerial.println();
191+
192+
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
193+
}
194+
}
195+
196+
void printGainSettings(void) {
197+
mySerial.print("Gain (dB): ");
198+
mySerial.print("Vol Knob = "); mySerial.print(vol_knob_gain_dB,1);
199+
//mySerial.print(", Input PGA = "); mySerial.print(input_gain_dB,1);
200+
mySerial.println();
201+
}
202+
203+
204+
void incrementKnobGain(float increment_dB) { //"extern" to make it available to other files, such as SerialManager.h
205+
setVolKnobGain_dB(vol_knob_gain_dB+increment_dB);
206+
}
207+
208+
void setVolKnobGain_dB(float gain_dB) {
209+
vol_knob_gain_dB = gain_dB;
210+
gain1.setGain_dB(vol_knob_gain_dB+formant_shift_gain_correction_dB);
211+
printGainSettings();
212+
}
213+
214+
float incrementFormantShift(float incr_factor) {
215+
float cur_scale_factor = formantShift.getScaleFactor();
216+
return formantShift.setScaleFactor(cur_scale_factor*incr_factor);
217+
}
218+
219+

0 commit comments

Comments
 (0)