parent
0232e199a8
commit
fb807bbec6
@ -0,0 +1,177 @@ |
|||||||
|
|
||||||
|
#ifndef _AudioEffectFormantShiftFD_F32_h |
||||||
|
#define _AudioEffectFormantShiftFD_F32_h |
||||||
|
|
||||||
|
#include "AudioStream_F32.h" |
||||||
|
#include <arm_math.h> |
||||||
|
#include "FFT_Overlapped_F32.h" |
||||||
|
|
||||||
|
class AudioEffectFormantShiftFD_F32 : public AudioStream_F32 |
||||||
|
{ |
||||||
|
public: |
||||||
|
//constructors...a few different options. The usual one should be: AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings, const int _N_FFT)
|
||||||
|
AudioEffectFormantShiftFD_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {}; |
||||||
|
AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings) : |
||||||
|
AudioStream_F32(1, inputQueueArray_f32) { |
||||||
|
sample_rate_Hz = settings.sample_rate_Hz; |
||||||
|
} |
||||||
|
AudioEffectFormantShiftFD_F32(const AudioSettings_F32 &settings, const int _N_FFT) : |
||||||
|
AudioStream_F32(1, inputQueueArray_f32) { |
||||||
|
setup(settings, _N_FFT); |
||||||
|
} |
||||||
|
|
||||||
|
//destructor...release all of the memory that has been allocated
|
||||||
|
~AudioEffectFormantShiftFD_F32(void) { |
||||||
|
if (complex_2N_buffer != NULL) delete complex_2N_buffer; |
||||||
|
} |
||||||
|
|
||||||
|
int setup(const AudioSettings_F32 &settings, const int _N_FFT) { |
||||||
|
sample_rate_Hz = settings.sample_rate_Hz; |
||||||
|
int N_FFT; |
||||||
|
|
||||||
|
//setup the FFT and IFFT. If they return a negative FFT, it wasn't an allowed FFT size.
|
||||||
|
N_FFT = myFFT.setup(settings, _N_FFT); //hopefully, we got the same N_FFT that we asked for
|
||||||
|
if (N_FFT < 1) return N_FFT; |
||||||
|
N_FFT = myIFFT.setup(settings, _N_FFT); //hopefully, we got the same N_FFT that we asked for
|
||||||
|
if (N_FFT < 1) return N_FFT; |
||||||
|
|
||||||
|
//decide windowing
|
||||||
|
Serial.println("AudioEffectFormantShiftFD_F32: setting myFFT to use hanning..."); |
||||||
|
(myFFT.getFFTObject())->useHanningWindow(); //applied prior to FFT
|
||||||
|
#if 1 |
||||||
|
if (myIFFT.getNBuffBlocks() > 3) { |
||||||
|
Serial.println("AudioEffectFormantShiftFD_F32: setting myIFFT to use hanning..."); |
||||||
|
(myIFFT.getIFFTObject())->useHanningWindow(); //window again after IFFT
|
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
//print info about setup
|
||||||
|
Serial.println("AudioEffectFormantShiftFD_F32: FFT parameters..."); |
||||||
|
Serial.print(" : N_FFT = "); Serial.println(N_FFT); |
||||||
|
Serial.print(" : audio_block_samples = "); Serial.println(settings.audio_block_samples); |
||||||
|
Serial.print(" : FFT N_BUFF_BLOCKS = "); Serial.println(myFFT.getNBuffBlocks()); |
||||||
|
Serial.print(" : IFFT N_BUFF_BLOCKS = "); Serial.println(myIFFT.getNBuffBlocks()); |
||||||
|
Serial.print(" : FFT use window = "); Serial.println(myFFT.getFFTObject()->get_flagUseWindow()); |
||||||
|
Serial.print(" : IFFT use window = "); Serial.println((myIFFT.getIFFTObject())->get_flagUseWindow()); |
||||||
|
|
||||||
|
//allocate memory to hold frequency domain data
|
||||||
|
complex_2N_buffer = new float32_t[2 * N_FFT]; |
||||||
|
|
||||||
|
//we're done. return!
|
||||||
|
enabled = 1; |
||||||
|
return N_FFT; |
||||||
|
} |
||||||
|
|
||||||
|
//void setLowpassFreq_Hz(float freq_Hz) { lowpass_freq_Hz = freq_Hz; }
|
||||||
|
//float getLowpassFreq_Hz(void) { return lowpass_freq_Hz; }
|
||||||
|
float setScaleFactor(float scale_fac) { |
||||||
|
if (scale_fac < 0.00001) scale_fac = 0.00001; |
||||||
|
return shift_scale_fac = scale_fac; |
||||||
|
} |
||||||
|
float getScaleFactor(void) { |
||||||
|
return shift_scale_fac; |
||||||
|
} |
||||||
|
|
||||||
|
virtual void update(void); |
||||||
|
|
||||||
|
private: |
||||||
|
int enabled = 0; |
||||||
|
float32_t *complex_2N_buffer; |
||||||
|
audio_block_f32_t *inputQueueArray_f32[1]; |
||||||
|
FFT_Overlapped_F32 myFFT; |
||||||
|
IFFT_Overlapped_F32 myIFFT; |
||||||
|
float lowpass_freq_Hz = 1000.f; |
||||||
|
float sample_rate_Hz = AUDIO_SAMPLE_RATE; |
||||||
|
|
||||||
|
float shift_scale_fac = 1.0; //how much to shift formants (frequency multiplier). 1.0 is no shift
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
void AudioEffectFormantShiftFD_F32::update(void) |
||||||
|
{ |
||||||
|
//get a pointer to the latest data
|
||||||
|
audio_block_f32_t *in_audio_block = AudioStream_F32::receiveReadOnly_f32(); |
||||||
|
if (!in_audio_block) return; |
||||||
|
|
||||||
|
//simply return the audio if this class hasn't been enabled
|
||||||
|
if (!enabled) { |
||||||
|
AudioStream_F32::transmit(in_audio_block); |
||||||
|
AudioStream_F32::release(in_audio_block); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
//convert to frequency domain
|
||||||
|
myFFT.execute(in_audio_block, complex_2N_buffer); |
||||||
|
AudioStream_F32::release(in_audio_block); //We just passed ownership to myFFT, so release it here.
|
||||||
|
|
||||||
|
// ////////////// Do your processing here!!!
|
||||||
|
|
||||||
|
//define some variables
|
||||||
|
int fftSize = myFFT.getNFFT(); |
||||||
|
int N_2 = fftSize / 2 + 1; |
||||||
|
int source_ind; // neg_dest_ind;
|
||||||
|
float source_ind_float, interp_fac; |
||||||
|
float new_mag, scale; |
||||||
|
float orig_mag[N_2]; |
||||||
|
//int max_source_ind = (int)(((float)N_2) * (10000.0 / (48000.0 / 2.0))); //highest frequency bin to grab from (Assuming 48kHz sample rate)
|
||||||
|
|
||||||
|
#if 1 |
||||||
|
float max_source_Hz = 10000.0; //highest frequency to use as source data
|
||||||
|
int max_source_ind = min(int(max_source_Hz / sample_rate_Hz * fftSize + 0.5),N_2); |
||||||
|
#else |
||||||
|
int max_source_ind = N_2; //this line causes this feature to be defeated
|
||||||
|
#endif |
||||||
|
|
||||||
|
//get the magnitude for each FFT bin and store somewhere safes
|
||||||
|
arm_cmplx_mag_f32(complex_2N_buffer, orig_mag, N_2); |
||||||
|
|
||||||
|
//now, loop over each bin and compute the new magnitude based on shifting the formants
|
||||||
|
for (int dest_ind = 1; dest_ind < N_2; dest_ind++) { //don't start at zero bin, keep it at its original
|
||||||
|
|
||||||
|
//what is the source bin for the new magnitude for this current destination bin
|
||||||
|
source_ind_float = (((float)dest_ind) / shift_scale_fac) + 0.5; |
||||||
|
//source_ind = (int)(source_ind_float+0.5); //no interpolation but round to the neariest index
|
||||||
|
//source_ind = min(max(source_ind,1),N_2-1);
|
||||||
|
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
|
||||||
|
interp_fac = source_ind_float - (float)source_ind; |
||||||
|
interp_fac = max(0.0, interp_fac); //this will be used in the interpolation in a few lines
|
||||||
|
|
||||||
|
//what is the new magnitude
|
||||||
|
new_mag = 0.0; scale = 0.0; |
||||||
|
if (source_ind < max_source_ind) { |
||||||
|
|
||||||
|
//interpolate in the original magnitude vector to find the new magnitude that we want
|
||||||
|
//new_mag=orig_mag[source_ind]; //the magnitude that we desire
|
||||||
|
//scale = new_mag / orig_mag[dest_ind];//compute the scale factor
|
||||||
|
new_mag = orig_mag[source_ind]; |
||||||
|
new_mag += interp_fac * (orig_mag[source_ind] - orig_mag[source_ind + 1]); |
||||||
|
scale = new_mag / orig_mag[dest_ind]; |
||||||
|
|
||||||
|
//apply scale factor
|
||||||
|
complex_2N_buffer[2 * dest_ind] *= scale; //real
|
||||||
|
complex_2N_buffer[2 * dest_ind + 1] *= scale; //imaginary
|
||||||
|
} else { |
||||||
|
complex_2N_buffer[2 * dest_ind] = 0.0; //real
|
||||||
|
complex_2N_buffer[2 * dest_ind + 1] = 0.0; //imaginary
|
||||||
|
} |
||||||
|
|
||||||
|
//zero out the lowest bin
|
||||||
|
complex_2N_buffer[0] = 0.0; //real
|
||||||
|
complex_2N_buffer[1] = 0.0; //imaginary
|
||||||
|
} |
||||||
|
|
||||||
|
//rebuild the negative frequency space
|
||||||
|
myFFT.rebuildNegativeFrequencySpace(complex_2N_buffer); //set the negative frequency space based on the positive
|
||||||
|
|
||||||
|
|
||||||
|
// ///////////// End do your processing here
|
||||||
|
|
||||||
|
//call the IFFT
|
||||||
|
audio_block_f32_t *out_audio_block = myIFFT.execute(complex_2N_buffer); //out_block is pre-allocated in here.
|
||||||
|
|
||||||
|
|
||||||
|
//send the returned audio block. Don't issue the release command here because myIFFT will re-use it
|
||||||
|
AudioStream_F32::transmit(out_audio_block); //don't release this buffer because myIFFT re-uses it within its own code
|
||||||
|
return; |
||||||
|
}; |
||||||
|
#endif |
@ -0,0 +1,219 @@ |
|||||||
|
// FormantShifter_FD
|
||||||
|
//
|
||||||
|
// Demonstrate formant shifting via frequency domain processin.
|
||||||
|
//
|
||||||
|
// Created: Chip Audette (OpenAudio) March 2019
|
||||||
|
//
|
||||||
|
// Approach:
|
||||||
|
// * Take samples in the time domain
|
||||||
|
// * Take FFT to convert to frequency domain
|
||||||
|
// * Manipulate the frequency bins to do the formant shifting
|
||||||
|
// * Take IFFT to convert back to time domain
|
||||||
|
// * Send samples back to the audio interface
|
||||||
|
//
|
||||||
|
// The amount of formant shifting is controled via the Serial link.
|
||||||
|
// It defaults to a modest upward shifting of the formants
|
||||||
|
//
|
||||||
|
// Built for the Tympan library for Teensy 3.6-based hardware
|
||||||
|
//
|
||||||
|
// MIT License. Use at your own risk.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Tympan_Library.h> |
||||||
|
#include "AudioEffectFormantShiftFD_F32.h" //the local file holding your custom function |
||||||
|
#include "SerialManager.h" |
||||||
|
|
||||||
|
//set the sample rate and block size
|
||||||
|
const float sample_rate_Hz = 44117.f; ; //24000 or 44117 (or other frequencies in the table in AudioOutputI2S_F32)
|
||||||
|
const int audio_block_samples = 128; //for freq domain processing choose a power of 2 (16, 32, 64, 128) but no higher than 128
|
||||||
|
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); |
||||||
|
|
||||||
|
//create audio library objects for handling the audio
|
||||||
|
Tympan audioHardware(TympanRev::D); //do TympanRev::C or TympanRev::D
|
||||||
|
AudioInputI2S_F32 i2s_in(audio_settings); //Digital audio *from* the Tympan AIC.
|
||||||
|
AudioEffectFormantShiftFD_F32 formantShift(audio_settings); //create the frequency-domain processing block
|
||||||
|
AudioEffectGain_F32 gain1; //Applies digital gain to audio data.
|
||||||
|
AudioOutputI2S_F32 i2s_out(audio_settings); //Digital audio out *to* the Tympan AIC.
|
||||||
|
|
||||||
|
//Make all of the audio connections
|
||||||
|
AudioConnection_F32 patchCord1(i2s_in, 0, formantShift, 0); //use the Left input
|
||||||
|
AudioConnection_F32 patchCord2(formantShift, 0, gain1, 0); //connect to gain
|
||||||
|
AudioConnection_F32 patchCord3(gain1, 0, i2s_out, 0); //connect to the left output
|
||||||
|
AudioConnection_F32 patchCord4(gain1, 0, i2s_out, 1); //connect to the right output
|
||||||
|
|
||||||
|
|
||||||
|
//control display and serial interaction
|
||||||
|
bool enable_printCPUandMemory = false; |
||||||
|
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; }; |
||||||
|
SerialManager serialManager(audioHardware); |
||||||
|
#define mySerial audioHardware //audioHardware is a printable stream!
|
||||||
|
|
||||||
|
//inputs and levels
|
||||||
|
float input_gain_dB = 20.0f; //gain on the microphone
|
||||||
|
float formant_shift_gain_correction_dB = 0.0; //will be used to adjust for gain in formant shifter
|
||||||
|
float vol_knob_gain_dB = 0.0; //will be overridden by volume knob
|
||||||
|
void switchToPCBMics(void) { |
||||||
|
mySerial.println("Switching to PCB Mics."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_ON_BOARD_MIC); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setInputGain_dB(input_gain_dB); |
||||||
|
} |
||||||
|
void switchToLineInOnMicJack(void) { |
||||||
|
mySerial.println("Switching to Line-in on Mic Jack."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_LINEIN); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setInputGain_dB(0.0); |
||||||
|
} |
||||||
|
void switchToMicInOnMicJack(void) { |
||||||
|
mySerial.println("Switching to Mic-In on Mic Jack."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_MIC); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setInputGain_dB(input_gain_dB); |
||||||
|
} |
||||||
|
|
||||||
|
// define the setup() function, the function that is called once when the device is booting
|
||||||
|
void setup() { |
||||||
|
audioHardware.beginBothSerial(); delay(1000); |
||||||
|
mySerial.println("FormantShifter: starting setup()..."); |
||||||
|
mySerial.print(" : sample rate (Hz) = "); mySerial.println(audio_settings.sample_rate_Hz); |
||||||
|
mySerial.print(" : block size (samples) = "); mySerial.println(audio_settings.audio_block_samples); |
||||||
|
|
||||||
|
// Audio connections require memory to work. For more
|
||||||
|
// detailed information, see the MemoryAndCpuUsage example
|
||||||
|
AudioMemory_F32(40, audio_settings); |
||||||
|
|
||||||
|
// Configure the frequency-domain algorithm
|
||||||
|
int overlap_factor = 4; //set to 4 or 8 or either 75% overlap (4x) or 87.5% overlap (8x)
|
||||||
|
int N_FFT = audio_block_samples * overlap_factor;
|
||||||
|
formantShift.setup(audio_settings, N_FFT); //do after AudioMemory_F32();
|
||||||
|
formantShift.setScaleFactor(1.5); //1.0 is no formant shifting.
|
||||||
|
if (overlap_factor == 4) { |
||||||
|
formant_shift_gain_correction_dB = -3.0; |
||||||
|
} else if (overlap_factor == 8) { |
||||||
|
formant_shift_gain_correction_dB = -9.0; |
||||||
|
} |
||||||
|
|
||||||
|
//Enable the Tympan to start the audio flowing!
|
||||||
|
audioHardware.enable(); // activate AIC
|
||||||
|
|
||||||
|
//setup DC-blocking highpass filter running in the ADC hardware itself
|
||||||
|
float cutoff_Hz = 60.0; //set the default cutoff frequency for the highpass filter
|
||||||
|
audioHardware.setHPFonADC(true,cutoff_Hz,audio_settings.sample_rate_Hz); //set to false to disble
|
||||||
|
|
||||||
|
//Choose the desired input
|
||||||
|
switchToPCBMics(); //use PCB mics as input
|
||||||
|
//switchToMicInOnMicJack(); //use Mic jack as mic input (ie, with mic bias)
|
||||||
|
//switchToLineInOnMicJack(); //use Mic jack as line input (ie, no mic bias)
|
||||||
|
|
||||||
|
//Set the desired volume levels
|
||||||
|
audioHardware.volume_dB(0); // headphone amplifier. -63.6 to +24 dB in 0.5dB steps.
|
||||||
|
|
||||||
|
// configure the blue potentiometer
|
||||||
|
servicePotentiometer(millis(),0); //update based on the knob setting the "0" is not relevant here.
|
||||||
|
|
||||||
|
//finish the setup by printing the help menu to the serial connections
|
||||||
|
serialManager.printHelp(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// define the loop() function, the function that is repeated over and over for the life of the device
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
//respond to Serial commands
|
||||||
|
while (Serial.available()) serialManager.respondToByte((char)Serial.read()); //USB Serial
|
||||||
|
//while (Serial1.available()) serialManager.respondToByte((char)Serial1.read()); //BT Serial
|
||||||
|
|
||||||
|
//check the potentiometer
|
||||||
|
servicePotentiometer(millis(), 100); //service the potentiometer every 100 msec
|
||||||
|
|
||||||
|
//check to see whether to print the CPU and Memory Usage
|
||||||
|
if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec
|
||||||
|
|
||||||
|
} //end loop();
|
||||||
|
|
||||||
|
|
||||||
|
// ///////////////// Servicing routines
|
||||||
|
|
||||||
|
//servicePotentiometer: listens to the blue potentiometer and sends the new pot value
|
||||||
|
// to the audio processing algorithm as a control parameter
|
||||||
|
void servicePotentiometer(unsigned long curTime_millis, const unsigned long updatePeriod_millis) { |
||||||
|
//static unsigned long updatePeriod_millis = 100; //how many milliseconds between updating the potentiometer reading?
|
||||||
|
static unsigned long lastUpdate_millis = 0; |
||||||
|
static float prev_val = -1.0; |
||||||
|
|
||||||
|
//has enough time passed to update everything?
|
||||||
|
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
|
||||||
|
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
|
||||||
|
|
||||||
|
//read potentiometer
|
||||||
|
float val = float(audioHardware.readPotentiometer()) / 1023.0; //0.0 to 1.0
|
||||||
|
val = (1.0/9.0) * (float)((int)(9.0 * val + 0.5)); //quantize so that it doesn't chatter...0 to 1.0
|
||||||
|
|
||||||
|
//use the potentiometer value to control something interesting
|
||||||
|
if (abs(val - prev_val) > 0.05) { //is it different than befor?
|
||||||
|
prev_val = val; //save the value for comparison for the next time around
|
||||||
|
|
||||||
|
#if 0 |
||||||
|
//set the volume of the system
|
||||||
|
setVolKnobGain_dB(val*45.0f - 10.0f - input_gain_dB); |
||||||
|
#else |
||||||
|
//set the amount of formant shifting
|
||||||
|
float new_scale_fac = powf(2.0,(val-0.5)*2.0); |
||||||
|
formantShift.setScaleFactor(new_scale_fac); |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
lastUpdate_millis = curTime_millis; |
||||||
|
} // end if
|
||||||
|
} //end servicePotentiometer();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
|
||||||
|
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) { |
||||||
|
//static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
|
||||||
|
static unsigned long lastUpdate_millis = 0; |
||||||
|
|
||||||
|
//has enough time passed to update everything?
|
||||||
|
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
|
||||||
|
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
|
||||||
|
mySerial.print("printCPUandMemory: "); |
||||||
|
mySerial.print("CPU Cur/Peak: "); |
||||||
|
mySerial.print(audio_settings.processorUsage()); |
||||||
|
//mySerial.print(AudioProcessorUsage()); //if not using AudioSettings_F32
|
||||||
|
mySerial.print("%/"); |
||||||
|
mySerial.print(audio_settings.processorUsageMax()); |
||||||
|
//mySerial.print(AudioProcessorUsageMax()); //if not using AudioSettings_F32
|
||||||
|
mySerial.print("%, "); |
||||||
|
mySerial.print("Dyn MEM Float32 Cur/Peak: "); |
||||||
|
mySerial.print(AudioMemoryUsage_F32()); |
||||||
|
mySerial.print("/"); |
||||||
|
mySerial.print(AudioMemoryUsageMax_F32()); |
||||||
|
mySerial.println(); |
||||||
|
|
||||||
|
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void printGainSettings(void) { |
||||||
|
mySerial.print("Gain (dB): "); |
||||||
|
mySerial.print("Vol Knob = "); mySerial.print(vol_knob_gain_dB,1); |
||||||
|
//mySerial.print(", Input PGA = "); mySerial.print(input_gain_dB,1);
|
||||||
|
mySerial.println(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void incrementKnobGain(float increment_dB) { //"extern" to make it available to other files, such as SerialManager.h
|
||||||
|
setVolKnobGain_dB(vol_knob_gain_dB+increment_dB); |
||||||
|
} |
||||||
|
|
||||||
|
void setVolKnobGain_dB(float gain_dB) { |
||||||
|
vol_knob_gain_dB = gain_dB; |
||||||
|
gain1.setGain_dB(vol_knob_gain_dB+formant_shift_gain_correction_dB); |
||||||
|
printGainSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
float incrementFormantShift(float incr_factor) { |
||||||
|
float cur_scale_factor = formantShift.getScaleFactor(); |
||||||
|
return formantShift.setScaleFactor(cur_scale_factor*incr_factor); |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,87 @@ |
|||||||
|
|
||||||
|
|
||||||
|
#ifndef _SerialManager_h |
||||||
|
#define _SerialManager_h |
||||||
|
|
||||||
|
#include <Tympan_Library.h> |
||||||
|
|
||||||
|
|
||||||
|
//now, define the Serial Manager class
|
||||||
|
class SerialManager { |
||||||
|
public: |
||||||
|
public: |
||||||
|
SerialManager(Tympan &_audioHardware) |
||||||
|
: audioHardware(_audioHardware) |
||||||
|
{ }; |
||||||
|
//SerialManager(void)
|
||||||
|
//{ };
|
||||||
|
|
||||||
|
void respondToByte(char c); |
||||||
|
void printHelp(void); |
||||||
|
int N_CHAN; |
||||||
|
float channelGainIncrement_dB = 2.5f;
|
||||||
|
float formantScaleIncrement = powf(2.0,1.0/6.0); |
||||||
|
|
||||||
|
private: |
||||||
|
Tympan &audioHardware; |
||||||
|
}; |
||||||
|
#define thisSerial audioHardware |
||||||
|
|
||||||
|
void SerialManager::printHelp(void) {
|
||||||
|
thisSerial.println(); |
||||||
|
thisSerial.println("SerialManager Help: Available Commands:"); |
||||||
|
thisSerial.println(" h: Print this help"); |
||||||
|
thisSerial.println(" g: Print the gain settings of the device."); |
||||||
|
thisSerial.println(" C: Toggle printing of CPU and Memory usage"); |
||||||
|
thisSerial.println(" p: Switch to built-in PCB microphones"); |
||||||
|
thisSerial.println(" m: switch to external mic via mic jack"); |
||||||
|
thisSerial.println(" l: switch to line-in via mic jack"); |
||||||
|
thisSerial.print(" k: Increase the gain of all channels (ie, knob gain) by "); thisSerial.print(channelGainIncrement_dB); thisSerial.println(" dB"); |
||||||
|
thisSerial.print(" K: Decrease the gain of all channels (ie, knob gain) by ");thisSerial.print(-channelGainIncrement_dB); thisSerial.println(" dB"); |
||||||
|
thisSerial.print(" f: Raise formant shifting (change by "); thisSerial.print(formantScaleIncrement); thisSerial.println("x)"); |
||||||
|
thisSerial.print(" F: Lower formant shifting (change by "); thisSerial.print(1.0/formantScaleIncrement); thisSerial.println("x)"); thisSerial.println(); |
||||||
|
} |
||||||
|
|
||||||
|
//functions in the main sketch that I want to call from here
|
||||||
|
extern void incrementKnobGain(float); |
||||||
|
extern void printGainSettings(void); |
||||||
|
extern void togglePrintMemoryAndCPU(void); |
||||||
|
extern float incrementFormantShift(float); |
||||||
|
extern void switchToPCBMics(void); |
||||||
|
extern void switchToMicInOnMicJack(void); |
||||||
|
extern void switchToLineInOnMicJack(void); |
||||||
|
|
||||||
|
//switch yard to determine the desired action
|
||||||
|
void SerialManager::respondToByte(char c) { |
||||||
|
//float old_val = 0.0, new_val = 0.0;
|
||||||
|
switch (c) { |
||||||
|
case 'h': case '?': |
||||||
|
printHelp(); break; |
||||||
|
case 'g': case 'G': |
||||||
|
printGainSettings(); break; |
||||||
|
case 'k': |
||||||
|
incrementKnobGain(channelGainIncrement_dB); break; |
||||||
|
case 'K': //which is "shift k"
|
||||||
|
incrementKnobGain(-channelGainIncrement_dB); break; |
||||||
|
case 'C': case 'c': |
||||||
|
thisSerial.println("Received: toggle printing of memory and CPU usage."); |
||||||
|
togglePrintMemoryAndCPU(); break; |
||||||
|
case 'p': |
||||||
|
switchToPCBMics(); break; |
||||||
|
case 'm': |
||||||
|
switchToMicInOnMicJack(); break; |
||||||
|
case 'l': |
||||||
|
switchToLineInOnMicJack();break; |
||||||
|
case 'f': |
||||||
|
{ float new_val = incrementFormantShift(formantScaleIncrement); |
||||||
|
thisSerial.print("Recieved: new format scale = "); thisSerial.println(new_val);} |
||||||
|
break; |
||||||
|
case 'F': |
||||||
|
{ float new_val = incrementFormantShift(1./formantScaleIncrement); |
||||||
|
thisSerial.print("Recieved: new format scale = "); thisSerial.println(new_val);} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,226 @@ |
|||||||
|
// FreqShifter_FD
|
||||||
|
//
|
||||||
|
// Demonstrate frequency shifting via frequency domain processin.
|
||||||
|
//
|
||||||
|
// Created: Chip Audette (OpenAudio) Aug 2019
|
||||||
|
//
|
||||||
|
// Approach: This processing is performed in the frequency domain.
|
||||||
|
// Frequencies can only be shifted by an integer number of bins,
|
||||||
|
// so small frequency shifts are not possible. For example, for
|
||||||
|
// a sample rate of 44.1kHz, and when using N=256, one can only
|
||||||
|
// shift frequencies in multiples of 44.1/256 = 172.3 Hz.
|
||||||
|
//
|
||||||
|
// This processing is performed in the frequency domain where
|
||||||
|
// we take the FFT, shift the bins upward or downward, take
|
||||||
|
// the IFFT, and listen to the results. In effect, this is
|
||||||
|
// single sideband modulation, which will sound very unnatural
|
||||||
|
// (like robot voices!). Maybe you'll like it, or maybe not.
|
||||||
|
// Probably not, unless you like weird. ;)
|
||||||
|
//
|
||||||
|
// You can shift frequencies upward or downward with this algorithm.
|
||||||
|
//
|
||||||
|
// Frequency Domain Processing:
|
||||||
|
// * Take samples in the time domain
|
||||||
|
// * Take FFT to convert to frequency domain
|
||||||
|
// * Manipulate the frequency bins to do the freqyebct shifting
|
||||||
|
// * Take IFFT to convert back to time domain
|
||||||
|
// * Send samples back to the audio interface
|
||||||
|
//
|
||||||
|
// Built for the Tympan library for Teensy 3.6-based hardware
|
||||||
|
//
|
||||||
|
// MIT License. Use at your own risk.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Tympan_Library.h> |
||||||
|
#include "SerialManager.h" |
||||||
|
|
||||||
|
//set the sample rate and block size
|
||||||
|
const float sample_rate_Hz = 44117.f; ; //24000 or 44117 (or other frequencies in the table in AudioOutputI2S_F32)
|
||||||
|
const int audio_block_samples = 64; //for freq domain processing choose a power of 2 (16, 32, 64, 128) but no higher than 128
|
||||||
|
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); |
||||||
|
|
||||||
|
//create audio library objects for handling the audio
|
||||||
|
Tympan audioHardware(TympanRev::D); //do TympanRev::C or TympanRev::D
|
||||||
|
AudioInputI2S_F32 i2s_in(audio_settings); //Digital audio *from* the Tympan AIC.
|
||||||
|
AudioEffectFreqShiftFD_F32 freqShift(audio_settings); //create the frequency-domain processing block
|
||||||
|
AudioEffectGain_F32 gain1; //Applies digital gain to audio data.
|
||||||
|
AudioOutputI2S_F32 i2s_out(audio_settings); //Digital audio out *to* the Tympan AIC.
|
||||||
|
|
||||||
|
//Make all of the audio connections
|
||||||
|
AudioConnection_F32 patchCord10(i2s_in, 0, freqShift, 0); //use the Left input
|
||||||
|
AudioConnection_F32 patchCord20(freqShift, 0, gain1, 0); //connect to gain
|
||||||
|
AudioConnection_F32 patchCord30(gain1, 0, i2s_out, 0); //connect to the left output
|
||||||
|
AudioConnection_F32 patchCord40(gain1, 0, i2s_out, 1); //connect to the right output
|
||||||
|
|
||||||
|
|
||||||
|
//control display and serial interaction
|
||||||
|
bool enable_printCPUandMemory = false; |
||||||
|
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; }; |
||||||
|
SerialManager serialManager(audioHardware); |
||||||
|
#define mySerial audioHardware //audioHardware is a printable stream!
|
||||||
|
|
||||||
|
//inputs and levels
|
||||||
|
float input_gain_dB = 15.0f; //gain on the microphone
|
||||||
|
float vol_knob_gain_dB = 0.0; //will be overridden by volume knob
|
||||||
|
void switchToPCBMics(void) { |
||||||
|
mySerial.println("Switching to PCB Mics."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_ON_BOARD_MIC); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setInputGain_dB(input_gain_dB); |
||||||
|
} |
||||||
|
void switchToLineInOnMicJack(void) { |
||||||
|
mySerial.println("Switching to Line-in on Mic Jack."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_LINEIN); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setInputGain_dB(0.0); |
||||||
|
} |
||||||
|
void switchToMicInOnMicJack(void) { |
||||||
|
mySerial.println("Switching to Mic-In on Mic Jack."); |
||||||
|
audioHardware.inputSelect(TYMPAN_INPUT_JACK_AS_MIC); // use the microphone jack - defaults to mic bias OFF
|
||||||
|
audioHardware.setEnableStereoExtMicBias(true); //put the mic bias on both channels
|
||||||
|
audioHardware.setInputGain_dB(input_gain_dB); |
||||||
|
} |
||||||
|
|
||||||
|
// define the setup() function, the function that is called once when the device is booting
|
||||||
|
void setup() { |
||||||
|
audioHardware.beginBothSerial(); delay(1000); |
||||||
|
mySerial.println("freqShifter: starting setup()..."); |
||||||
|
mySerial.print(" : sample rate (Hz) = "); mySerial.println(audio_settings.sample_rate_Hz); |
||||||
|
mySerial.print(" : block size (samples) = "); mySerial.println(audio_settings.audio_block_samples); |
||||||
|
|
||||||
|
// Audio connections require memory to work. For more
|
||||||
|
// detailed information, see the MemoryAndCpuUsage example
|
||||||
|
AudioMemory_F32(40, audio_settings); |
||||||
|
|
||||||
|
// Configure the FFT parameters algorithm
|
||||||
|
int overlap_factor = 4; //set to 2, 4 or 8...which yields 50%, 75%, or 87.5% overlap (8x)
|
||||||
|
int N_FFT = audio_block_samples * overlap_factor;
|
||||||
|
Serial.print(" : N_FFT = "); Serial.println(N_FFT); |
||||||
|
freqShift.setup(audio_settings, N_FFT); //do after AudioMemory_F32();
|
||||||
|
|
||||||
|
//configure the frequency shifting
|
||||||
|
float shiftFreq_Hz = 750.0; //shift audio upward a bit
|
||||||
|
float Hz_per_bin = audio_settings.sample_rate_Hz / ((float)N_FFT); |
||||||
|
int shift_bins = (int)(shiftFreq_Hz / Hz_per_bin + 0.5); //round to nearest bin
|
||||||
|
shiftFreq_Hz = shift_bins * Hz_per_bin; |
||||||
|
Serial.print("Setting shift to "); Serial.print(shiftFreq_Hz); Serial.print(" Hz, which is "); Serial.print(shift_bins); Serial.println(" bins"); |
||||||
|
freqShift.setShift_bins(shift_bins); //0 is no ffreq shifting.
|
||||||
|
|
||||||
|
//Enable the Tympan to start the audio flowing!
|
||||||
|
audioHardware.enable(); // activate AIC
|
||||||
|
|
||||||
|
//setup DC-blocking highpass filter running in the ADC hardware itself
|
||||||
|
float cutoff_Hz = 60.0; //set the default cutoff frequency for the highpass filter
|
||||||
|
audioHardware.setHPFonADC(true,cutoff_Hz,audio_settings.sample_rate_Hz); //set to false to disble
|
||||||
|
|
||||||
|
//Choose the desired input
|
||||||
|
switchToPCBMics(); //use PCB mics as input
|
||||||
|
//switchToMicInOnMicJack(); //use Mic jack as mic input (ie, with mic bias)
|
||||||
|
//switchToLineInOnMicJack(); //use Mic jack as line input (ie, no mic bias)
|
||||||
|
|
||||||
|
//Set the desired volume levels
|
||||||
|
audioHardware.volume_dB(0); // headphone amplifier. -63.6 to +24 dB in 0.5dB steps.
|
||||||
|
|
||||||
|
// configure the blue potentiometer
|
||||||
|
servicePotentiometer(millis(),0); //update based on the knob setting the "0" is not relevant here.
|
||||||
|
|
||||||
|
//finish the setup by printing the help menu to the serial connections
|
||||||
|
serialManager.printHelp(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// define the loop() function, the function that is repeated over and over for the life of the device
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
//respond to Serial commands
|
||||||
|
while (Serial.available()) serialManager.respondToByte((char)Serial.read()); //USB Serial
|
||||||
|
//while (Serial1.available()) serialManager.respondToByte((char)Serial1.read()); //BT Serial
|
||||||
|
|
||||||
|
//check the potentiometer
|
||||||
|
servicePotentiometer(millis(), 100); //service the potentiometer every 100 msec
|
||||||
|
|
||||||
|
//check to see whether to print the CPU and Memory Usage
|
||||||
|
if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec
|
||||||
|
|
||||||
|
} //end loop();
|
||||||
|
|
||||||
|
|
||||||
|
// ///////////////// Servicing routines
|
||||||
|
|
||||||
|
//servicePotentiometer: listens to the blue potentiometer and sends the new pot value
|
||||||
|
// to the audio processing algorithm as a control parameter
|
||||||
|
void servicePotentiometer(unsigned long curTime_millis, const unsigned long updatePeriod_millis) { |
||||||
|
//static unsigned long updatePeriod_millis = 100; //how many milliseconds between updating the potentiometer reading?
|
||||||
|
static unsigned long lastUpdate_millis = 0; |
||||||
|
static float prev_val = -1.0; |
||||||
|
|
||||||
|
//has enough time passed to update everything?
|
||||||
|
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
|
||||||
|
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
|
||||||
|
|
||||||
|
//read potentiometer
|
||||||
|
float val = float(audioHardware.readPotentiometer()) / 1023.0; //0.0 to 1.0
|
||||||
|
val = (1.0/9.0) * (float)((int)(9.0 * val + 0.5)); //quantize so that it doesn't chatter...0 to 1.0
|
||||||
|
|
||||||
|
//use the potentiometer value to control something interesting
|
||||||
|
if (abs(val - prev_val) > 0.05) { //is it different than befor?
|
||||||
|
prev_val = val; //save the value for comparison for the next time around
|
||||||
|
|
||||||
|
//change the volume
|
||||||
|
float vol_dB = 0.f + 30.0f * ((val - 0.5) * 2.0); //set volume as 0dB +/- 30 dB
|
||||||
|
audioHardware.print("Changing output volume to = "); audioHardware.print(vol_dB); audioHardware.println(" dB"); |
||||||
|
audioHardware.volume_dB(vol_dB); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
lastUpdate_millis = curTime_millis; |
||||||
|
} // end if
|
||||||
|
} //end servicePotentiometer();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
|
||||||
|
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) { |
||||||
|
//static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
|
||||||
|
static unsigned long lastUpdate_millis = 0; |
||||||
|
|
||||||
|
//has enough time passed to update everything?
|
||||||
|
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
|
||||||
|
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
|
||||||
|
mySerial.print("printCPUandMemory: "); |
||||||
|
mySerial.print("CPU Cur/Peak: "); |
||||||
|
mySerial.print(audio_settings.processorUsage()); |
||||||
|
mySerial.print("%/"); |
||||||
|
mySerial.print(audio_settings.processorUsageMax()); |
||||||
|
mySerial.print("%, "); |
||||||
|
mySerial.print("Dyn MEM Float32 Cur/Peak: "); |
||||||
|
mySerial.print(AudioMemoryUsage_F32()); |
||||||
|
mySerial.print("/"); |
||||||
|
mySerial.print(AudioMemoryUsageMax_F32()); |
||||||
|
mySerial.println(); |
||||||
|
|
||||||
|
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void printGainSettings(void) { |
||||||
|
mySerial.print("Gain (dB): "); |
||||||
|
mySerial.print("Vol Knob = "); mySerial.print(vol_knob_gain_dB,1); |
||||||
|
//mySerial.print(", Input PGA = "); mySerial.print(input_gain_dB,1);
|
||||||
|
mySerial.println(); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void incrementKnobGain(float increment_dB) { //"extern" to make it available to other files, such as SerialManager.h
|
||||||
|
setVolKnobGain_dB(vol_knob_gain_dB+increment_dB); |
||||||
|
} |
||||||
|
|
||||||
|
void setVolKnobGain_dB(float gain_dB) { |
||||||
|
vol_knob_gain_dB = gain_dB; |
||||||
|
gain1.setGain_dB(vol_knob_gain_dB); |
||||||
|
printGainSettings(); |
||||||
|
} |
||||||
|
|
||||||
|
int incrementFreqShift(int incr_factor) { |
||||||
|
int cur_shift_bins = freqShift.getShift_bins(); |
||||||
|
return freqShift.setShift_bins(cur_shift_bins + incr_factor); |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
|
||||||
|
|
||||||
|
#ifndef _SerialManager_h |
||||||
|
#define _SerialManager_h |
||||||
|
|
||||||
|
#include <Tympan_Library.h> |
||||||
|
|
||||||
|
|
||||||
|
//now, define the Serial Manager class
|
||||||
|
class SerialManager { |
||||||
|
public: |
||||||
|
public: |
||||||
|
SerialManager(Tympan &_audioHardware) |
||||||
|
: audioHardware(_audioHardware) |
||||||
|
{ }; |
||||||
|
//SerialManager(void)
|
||||||
|
//{ };
|
||||||
|
|
||||||
|
void respondToByte(char c); |
||||||
|
void printHelp(void); |
||||||
|
int N_CHAN; |
||||||
|
float channelGainIncrement_dB = 2.5f;
|
||||||
|
int freq_shift_increment = 1; |
||||||
|
|
||||||
|
private: |
||||||
|
Tympan &audioHardware; |
||||||
|
}; |
||||||
|
#define thisSerial audioHardware |
||||||
|
|
||||||
|
void SerialManager::printHelp(void) {
|
||||||
|
thisSerial.println(); |
||||||
|
thisSerial.println("SerialManager Help: Available Commands:"); |
||||||
|
thisSerial.println(" h: Print this help"); |
||||||
|
thisSerial.println(" g: Print the gain settings of the device."); |
||||||
|
thisSerial.println(" C: Toggle printing of CPU and Memory usage"); |
||||||
|
thisSerial.println(" p: Switch to built-in PCB microphones"); |
||||||
|
thisSerial.println(" m: switch to external mic via mic jack"); |
||||||
|
thisSerial.println(" l: switch to line-in via mic jack"); |
||||||
|
thisSerial.print(" k: Increase the gain of all channels (ie, knob gain) by "); thisSerial.print(channelGainIncrement_dB); thisSerial.println(" dB"); |
||||||
|
thisSerial.print(" K: Decrease the gain of all channels (ie, knob gain) by ");thisSerial.print(-channelGainIncrement_dB); thisSerial.println(" dB"); |
||||||
|
thisSerial.print(" f: Raise freq shifting (change by "); thisSerial.print(freq_shift_increment); thisSerial.println(" bins)"); |
||||||
|
thisSerial.print(" F: Lower freq shifting (change by "); thisSerial.print(-freq_shift_increment); thisSerial.println(" bins)"); thisSerial.println(); |
||||||
|
} |
||||||
|
|
||||||
|
//functions in the main sketch that I want to call from here
|
||||||
|
extern void incrementKnobGain(float); |
||||||
|
extern void printGainSettings(void); |
||||||
|
extern void togglePrintMemoryAndCPU(void); |
||||||
|
extern int incrementFreqShift(int); |
||||||
|
extern void switchToPCBMics(void); |
||||||
|
extern void switchToMicInOnMicJack(void); |
||||||
|
extern void switchToLineInOnMicJack(void); |
||||||
|
|
||||||
|
//switch yard to determine the desired action
|
||||||
|
void SerialManager::respondToByte(char c) { |
||||||
|
//float old_val = 0.0, new_val = 0.0;
|
||||||
|
switch (c) { |
||||||
|
case 'h': case '?': |
||||||
|
printHelp(); break; |
||||||
|
case 'g': case 'G': |
||||||
|
printGainSettings(); break; |
||||||
|
case 'k': |
||||||
|
incrementKnobGain(channelGainIncrement_dB); break; |
||||||
|
case 'K': //which is "shift k"
|
||||||
|
incrementKnobGain(-channelGainIncrement_dB); break; |
||||||
|
case 'C': case 'c': |
||||||
|
thisSerial.println("Received: toggle printing of memory and CPU usage."); |
||||||
|
togglePrintMemoryAndCPU(); break; |
||||||
|
case 'p': |
||||||
|
switchToPCBMics(); break; |
||||||
|
case 'm': |
||||||
|
switchToMicInOnMicJack(); break; |
||||||
|
case 'l': |
||||||
|
switchToLineInOnMicJack();break; |
||||||
|
case 'f': |
||||||
|
{ int new_val = incrementFreqShift(freq_shift_increment); |
||||||
|
thisSerial.print("Recieved: new freq shift = "); thisSerial.println(new_val);} |
||||||
|
break; |
||||||
|
case 'F': |
||||||
|
{ int new_val = incrementFreqShift(-freq_shift_increment); |
||||||
|
thisSerial.print("Recieved: new freq shift = "); thisSerial.println(new_val);} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,125 @@ |
|||||||
|
/*
|
||||||
|
* This include is NOT global to the OpenAudio library, but supports |
||||||
|
* LowpassFilter_FD.ino |
||||||
|
*/ |
||||||
|
#ifndef _AudioEffectLowpassFD_F32_h |
||||||
|
#define _AudioEffectLowpassFD_F32_h |
||||||
|
|
||||||
|
#include "AudioStream_F32.h" |
||||||
|
#include <arm_math.h> |
||||||
|
#include "FFT_Overlapped_OA_F32.h" |
||||||
|
|
||||||
|
class AudioEffectLowpassFD_F32 : public AudioStream_F32 |
||||||
|
{ |
||||||
|
public: |
||||||
|
// constructors...a few different options. The usual one should be:
|
||||||
|
// AudioEffectLowpassFD_F32(const AudioSettings_F32 &settings, const int _N_FFT)
|
||||||
|
AudioEffectLowpassFD_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {}; |
||||||
|
AudioEffectLowpassFD_F32(const AudioSettings_F32 &settings) : |
||||||
|
AudioStream_F32(1, inputQueueArray_f32) { sample_rate_Hz = settings.sample_rate_Hz; } |
||||||
|
AudioEffectLowpassFD_F32(const AudioSettings_F32 &settings, const int _N_FFT) :
|
||||||
|
AudioStream_F32(1, inputQueueArray_f32) { setup(settings,_N_FFT); } |
||||||
|
|
||||||
|
//destructor...release all of the memory that has been allocated
|
||||||
|
~AudioEffectLowpassFD_F32(void) { |
||||||
|
if (complex_2N_buffer != NULL) delete complex_2N_buffer; |
||||||
|
} |
||||||
|
|
||||||
|
int setup(const AudioSettings_F32 &settings, const int _N_FFT) { |
||||||
|
sample_rate_Hz = settings.sample_rate_Hz; |
||||||
|
int N_FFT; |
||||||
|
|
||||||
|
//setup the FFT and IFFT. If they return a negative FFT, it wasn't an allowed FFT size.
|
||||||
|
N_FFT = myFFT.setup(settings,_N_FFT); //hopefully, we got the same N_FFT that we asked for
|
||||||
|
if (N_FFT < 1) return N_FFT; |
||||||
|
N_FFT = myIFFT.setup(settings,_N_FFT); //hopefully, we got the same N_FFT that we asked for
|
||||||
|
if (N_FFT < 1) return N_FFT; |
||||||
|
|
||||||
|
//decide windowing
|
||||||
|
Serial.println("AudioEffectLowpassFD_F32: setting myFFT to use hanning..."); |
||||||
|
(myFFT.getFFTObject())->useHanningWindow(); //applied prior to FFT
|
||||||
|
//if (myIFFT.getNBuffBlocks() > 3) {
|
||||||
|
// Serial.println("AudioEffectLowpassFD_F32: setting myIFFT to use hanning...");
|
||||||
|
// (myIFFT.getIFFTObject())->useHanningWindow(); //window again after IFFT
|
||||||
|
//}
|
||||||
|
|
||||||
|
//print info about setup
|
||||||
|
Serial.println("AudioEffectLowpassFD_F32: FFT parameters..."); |
||||||
|
Serial.print(" : N_FFT = "); Serial.println(N_FFT); |
||||||
|
Serial.print(" : audio_block_samples = "); Serial.println(settings.audio_block_samples); |
||||||
|
Serial.print(" : FFT N_BUFF_BLOCKS = "); Serial.println(myFFT.getNBuffBlocks()); |
||||||
|
Serial.print(" : IFFT N_BUFF_BLOCKS = "); Serial.println(myIFFT.getNBuffBlocks()); |
||||||
|
Serial.print(" : FFT use window = "); Serial.println(myFFT.getFFTObject()->get_flagUseWindow()); |
||||||
|
Serial.print(" : IFFT use window = "); Serial.println((myIFFT.getIFFTObject())->get_flagUseWindow()); |
||||||
|
|
||||||
|
//allocate memory to hold frequency domain data
|
||||||
|
complex_2N_buffer = new float32_t[2*N_FFT]; |
||||||
|
|
||||||
|
//we're done. return!
|
||||||
|
enabled=1; |
||||||
|
return N_FFT; |
||||||
|
} |
||||||
|
|
||||||
|
void setLowpassFreq_Hz(float freq_Hz) { lowpass_freq_Hz = freq_Hz; } |
||||||
|
|
||||||
|
float getLowpassFreq_Hz(void) { return lowpass_freq_Hz; } |
||||||
|
|
||||||
|
virtual void update(void); |
||||||
|
|
||||||
|
private: |
||||||
|
int enabled=0; |
||||||
|
float32_t *complex_2N_buffer; |
||||||
|
audio_block_f32_t *inputQueueArray_f32[1]; |
||||||
|
FFT_Overlapped_OA_F32 myFFT; |
||||||
|
IFFT_Overlapped_OA_F32 myIFFT; |
||||||
|
float lowpass_freq_Hz = 1000.f; |
||||||
|
float sample_rate_Hz = AUDIO_SAMPLE_RATE;
|
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
void AudioEffectLowpassFD_F32::update(void) |
||||||
|
{ |
||||||
|
//get a pointer to the latest data
|
||||||
|
audio_block_f32_t *in_audio_block = AudioStream_F32::receiveReadOnly_f32(); |
||||||
|
if (!in_audio_block) return; |
||||||
|
|
||||||
|
//simply return the audio if this class hasn't been enabled
|
||||||
|
if (!enabled) { AudioStream_F32::transmit(in_audio_block); AudioStream_F32::release(in_audio_block); return; } |
||||||
|
|
||||||
|
//convert to frequency domain
|
||||||
|
myFFT.execute(in_audio_block, complex_2N_buffer); |
||||||
|
AudioStream_F32::release(in_audio_block); //We just passed ownership to myFFT, so release it here.
|
||||||
|
|
||||||
|
// ////////////// Do your processing here!!!
|
||||||
|
|
||||||
|
//this is lowpass, so zero the bins above the cutoff
|
||||||
|
int NFFT = myFFT.getNFFT(); |
||||||
|
int nyquist_bin = NFFT/2 + 1; |
||||||
|
float bin_width_Hz = sample_rate_Hz / ((float)NFFT); |
||||||
|
int cutoff_bin = (int)(lowpass_freq_Hz / bin_width_Hz + 0.5); //the 0.5 is so that it rounds instead of truncates
|
||||||
|
if (cutoff_bin < nyquist_bin) { |
||||||
|
for (int i=cutoff_bin; i < nyquist_bin; i++) { //only do positive frequency space...will rebuild the neg freq space later
|
||||||
|
#if 0 |
||||||
|
//zero out the bins (silence
|
||||||
|
complex_2N_buffer[2*i] = 0.0f; //real
|
||||||
|
complex_2N_buffer[2*i+1]= 0.0f; //imaginary
|
||||||
|
#else |
||||||
|
//attenuate by 30 dB
|
||||||
|
complex_2N_buffer[2*i] *= 0.03f; //real
|
||||||
|
complex_2N_buffer[2*i+1] *= 0.03f; //imaginary
|
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
myFFT.rebuildNegativeFrequencySpace(complex_2N_buffer); //set the negative frequency space based on the positive
|
||||||
|
|
||||||
|
// ///////////// End do your processing here
|
||||||
|
|
||||||
|
//call the IFFT
|
||||||
|
audio_block_f32_t *out_audio_block = myIFFT.execute(complex_2N_buffer); //out_block is pre-allocated in here.
|
||||||
|
|
||||||
|
|
||||||
|
//send the returned audio block. Don't issue the release command here because myIFFT will re-use it
|
||||||
|
AudioStream_F32::transmit(out_audio_block); //don't release this buffer because myIFFT re-uses it within its own code
|
||||||
|
return; |
||||||
|
}; |
||||||
|
#endif |
@ -0,0 +1,114 @@ |
|||||||
|
/* LowpassFilter_FD.ino
|
||||||
|
* |
||||||
|
* Demonstrate audio procesing in the frequency domain. |
||||||
|
* |
||||||
|
* Created: Chip Audette Sept-Oct 2016 for Tympan Library |
||||||
|
* Approach: |
||||||
|
* Take samples in the time domain |
||||||
|
* Take FFT to convert to frequency domain |
||||||
|
* Manipulate the frequency bins as desired (LP filter? BP filter? Formant shift?) |
||||||
|
* Take IFFT to convert back to time domain |
||||||
|
* Send samples back to the audio interface |
||||||
|
* |
||||||
|
* Assumes the use of the Audio library from PJRC |
||||||
|
* |
||||||
|
* Adapted to OpenAudio, "_OA". June 2020 Bob Larkin. |
||||||
|
* This changed from direct F32 AudioInputI2S to I16 Teensy Audio Library |
||||||
|
* versions with Chip Audette's AudioConvert objects. Also removed volume |
||||||
|
* control. Class and file names are isolated from other libraries by "_OA". |
||||||
|
* Tested T3.6 and T4.0 with PJRC Teensy Audio Adaptor. |
||||||
|
*
|
||||||
|
* Ref: https://github.com/Tympan/Tympan_Library
|
||||||
|
* https://github.com/Tympan/Tympan_Library/tree/master/examples/04-FrequencyDomain/LowpassFilter_FD
|
||||||
|
* https://forum.pjrc.com/threads/40188-Fast-Convolution-filtering-in-floating-point-with-Teensy-3-6
|
||||||
|
* https://forum.pjrc.com/threads/40590-Teensy-Convolution-SDR-(Software-Defined-Radio)
|
||||||
|
*
|
||||||
|
* This example code is in the public domain (MIT License) |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "SD.h" |
||||||
|
#include "AudioStream_F32.h" |
||||||
|
#include <OpenAudio_ArduinoLibrary.h> |
||||||
|
#include "AudioEffectLowpassFD_F32.h" // the local file holding your custom function |
||||||
|
|
||||||
|
//set the sample rate and block size
|
||||||
|
const float sample_rate_Hz = 44117.f; |
||||||
|
const int audio_block_samples = 128; // for freq domain processing, choose 16, 32, 64 or 128
|
||||||
|
AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); |
||||||
|
|
||||||
|
//create audio library objects for handling the audio
|
||||||
|
AudioInputI2S i2sIn; |
||||||
|
AudioConvert_I16toF32 cnvrt1; |
||||||
|
AudioSynthWaveformSine_F32 sinewave(audio_settings); |
||||||
|
AudioEffectLowpassFD_F32 audioEffectLowpassFD(audio_settings); //create the frequency-domain processing block
|
||||||
|
AudioConvert_F32toI16 cnvrt2;
|
||||||
|
AudioOutputI2S i2sOut; |
||||||
|
AudioControlSGTL5000 codec; |
||||||
|
//Make all of the audio connections if 1 for Codec input, 0 for internally generated sine wave
|
||||||
|
#if 1 |
||||||
|
AudioConnection patchCord1(i2sIn, 0, cnvrt1, 0); // connect to Left codec, 16-bit
|
||||||
|
AudioConnection_F32 patchCord2(cnvrt1, 0, audioEffectLowpassFD, 0); // Now converted to float
|
||||||
|
#else |
||||||
|
AudioConnection_F32 patchCord1(sinewave, 0, audioEffectLowpassFD, 0); // connect sine to filter
|
||||||
|
#endif |
||||||
|
AudioConnection_F32 patchCord5(audioEffectLowpassFD, 0, cnvrt2, 0); // filtered output
|
||||||
|
AudioConnection patchCord6(cnvrt2, 0, i2sOut, 0); // Left channel
|
||||||
|
|
||||||
|
int is_windowing_active = 0; |
||||||
|
void setup() { |
||||||
|
//begin the serial comms (for debugging), any baud rate
|
||||||
|
Serial.begin(1); delay(500); |
||||||
|
Serial.println("FrequencyDomainDemo2: starting setup()..."); |
||||||
|
Serial.print(" : sample rate (Hz) = "); Serial.println(audio_settings.sample_rate_Hz); |
||||||
|
Serial.print(" : block size (samples) = "); Serial.println(audio_settings.audio_block_samples); |
||||||
|
|
||||||
|
// Audio connections require memory to work. For more
|
||||||
|
// detailed information, see the MemoryAndCpuUsage example
|
||||||
|
AudioMemory(10); AudioMemory_F32(20, audio_settings); |
||||||
|
|
||||||
|
codec.enable(); |
||||||
|
|
||||||
|
// Configure the frequency-domain algorithm
|
||||||
|
int N_FFT = 1024; |
||||||
|
audioEffectLowpassFD.setup(audio_settings,N_FFT); //do after AudioMemory_F32();
|
||||||
|
|
||||||
|
sinewave.frequency(1000.0f); |
||||||
|
sinewave.amplitude(0.025f); |
||||||
|
|
||||||
|
Serial.println("Setup complete."); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//This routine prints the current and maximum CPU usage and the current usage of the AudioMemory that has been allocated
|
||||||
|
void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_millis) { |
||||||
|
//static unsigned long updatePeriod_millis = 3000; //how many milliseconds between updating gain reading?
|
||||||
|
static unsigned long lastUpdate_millis = 0; |
||||||
|
|
||||||
|
//has enough time passed to update everything?
|
||||||
|
if (curTime_millis < lastUpdate_millis) lastUpdate_millis = 0; //handle wrap-around of the clock
|
||||||
|
if ((curTime_millis - lastUpdate_millis) > updatePeriod_millis) { //is it time to update the user interface?
|
||||||
|
Serial.print("printCPUandMemory: "); |
||||||
|
Serial.print("CPU Cur/Peak: "); |
||||||
|
Serial.print(audio_settings.processorUsage()); |
||||||
|
//Serial.print(AudioProcessorUsage()); //if not using AudioSettings_F32
|
||||||
|
Serial.print("%/"); |
||||||
|
Serial.print(audio_settings.processorUsageMax()); |
||||||
|
//Serial.print(AudioProcessorUsageMax()); //if not using AudioSettings_F32
|
||||||
|
Serial.print("%, "); |
||||||
|
Serial.print("Dyn MEM Int16 Cur/Peak: "); |
||||||
|
Serial.print(AudioMemoryUsage()); |
||||||
|
Serial.print("/"); |
||||||
|
Serial.print(AudioMemoryUsageMax()); |
||||||
|
Serial.print(", "); |
||||||
|
Serial.print("Dyn MEM Float32 Cur/Peak: "); |
||||||
|
Serial.print(AudioMemoryUsage_F32()); |
||||||
|
Serial.print("/"); |
||||||
|
Serial.print(AudioMemoryUsageMax_F32()); |
||||||
|
Serial.println(); |
||||||
|
|
||||||
|
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
|
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue