Move FFT examples from Tympan

pull/11/head
boblark 4 years ago
parent fb807bbec6
commit 13eb4a3545
  1. 219
      examples/FormantShifter_FD/FormantShifter_FD.ino
  2. 24
      examples/FormantShifter_FD_OA/AudioEffectFormantShiftFD_OA_F32.h
  3. 196
      examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino
  4. 16
      examples/FormantShifter_FD_OA/SerialManagerFormant_OA.h
  5. 0
      examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino
  6. 0
      examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h
  7. 0
      examples/LowpassFilter_FD_OA/AudioEffectLowpassFD_OA_F32.h
  8. 3
      examples/LowpassFilter_FD_OA/LowpassFilter_FD_OA.ino
  9. 2
      readme.md

@ -1,219 +0,0 @@
// 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);
}

@ -1,27 +1,27 @@
#ifndef _AudioEffectFormantShiftFD_F32_h
#define _AudioEffectFormantShiftFD_F32_h
#ifndef _AudioEffectFormantShiftFD_OA_F32_h
#define _AudioEffectFormantShiftFD_OA_F32_h
#include "AudioStream_F32.h"
#include <arm_math.h>
#include "FFT_Overlapped_F32.h"
class AudioEffectFormantShiftFD_F32 : public AudioStream_F32
class AudioEffectFormantShiftFD_OA_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) :
//constructors...a few different options. The usual one should be: AudioEffectFormantShiftFD_OA_F32(const AudioSettings_F32 &settings, const int _N_FFT)
AudioEffectFormantShiftFD_OA_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {};
AudioEffectFormantShiftFD_OA_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) :
AudioEffectFormantShiftFD_OA_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) {
~AudioEffectFormantShiftFD_OA_F32(void) {
if (complex_2N_buffer != NULL) delete complex_2N_buffer;
}
@ -36,17 +36,17 @@ class AudioEffectFormantShiftFD_F32 : public AudioStream_F32
if (N_FFT < 1) return N_FFT;
//decide windowing
Serial.println("AudioEffectFormantShiftFD_F32: setting myFFT to use hanning...");
Serial.println("AudioEffectFormantShiftFD_OA_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...");
Serial.println("AudioEffectFormantShiftFD_OA_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.println("AudioEffectFormantShiftFD_OA_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());
@ -87,7 +87,7 @@ class AudioEffectFormantShiftFD_F32 : public AudioStream_F32
};
void AudioEffectFormantShiftFD_F32::update(void)
void AudioEffectFormantShiftFD_OA_F32::update(void)
{
//get a pointer to the latest data
audio_block_f32_t *in_audio_block = AudioStream_F32::receiveReadOnly_f32();

@ -0,0 +1,196 @@
/* FormantShifter_FD.ino
*
* 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
*
* Adapt to OpenAudio Library - Bob Larkin June 2020
* Ref: http://iris.elf.stuba.sk/JEEEC/data/pdf/1_110-08.pdf
*
* MIT License. Use at your own risk.
*
*/
#include "AudioStream_F32.h"
#include "OpenAudio_ArduinoLibrary.h"
#include "AudioEffectFormantShiftFD_OA_F32.h" //the local file holding your custom function
#include "SerialManager_OA.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
AudioInputI2S i2sIn;
AudioConvert_I16toF32 cnvrt1;
AudioEffectFormantShiftFD_F32 formantShift(audio_settings); //create the frequency-domain processing block
AudioEffectGain_F32 gain1; //Applies digital gain to audio data.
AudioConvert_F32toI16 cnvrt2;
AudioOutputI2S i2sOut;
AudioControlSGTL5000 codec;
//Make all of the audio connections
AudioConnection patchCord1(i2sIn, 0, cnvrt1, 0); // connect to Left codec, 16-bit
AudioConnection_F32 patchCord2(cnvrt1, 0, formantShift, 0);
AudioConnection_F32 patchCord2(formantShift, 0, gain1, 0); //connect to gain
AudioConnection_F32 patchCord3(gain1, 0, cnvrt2, 0); //connect to the left output
AudioConnection patchCord6(cnvrt2, 0, i2sOut, 0);
//control display and serial interaction
bool enable_printCPUandMemory = false;
void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; };
SerialManager_OA SerialManager_OA(audioHardware);
//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
// define the setup() function, the function that is called once when the device is booting
void setup() {
Serial.begin(1); 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(3); // I16 type
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;
}
codec.enable(); // activate AIC
//setup DC-blocking highpass filter running in the ADC hardware itself
//Choose the desired input
//Set the desired volume levels
audioHardware.volume_dB(0); // headphone amplifier. -63.6 to +24 dB in 0.5dB steps.
//finish the setup by printing the help menu to the serial connections
Serial.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_OA.respondToByte((char)Serial.read()); //USB Serial
//while (Serial1.available()) SerialManager_OA.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
#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?
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 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.
}
}
void printGainSettings(void) {
Serial.print("Gain (dB): ");
Serial.print("Vol Knob = "); Serial.print(vol_knob_gain_dB,1);
//Serial.print(", Input PGA = "); Serial.print(input_gain_dB,1);
Serial.println();
}
void incrementKnobGain(float increment_dB) { //"extern" to make it available to other files, such as SerialManager_OA.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);
}

@ -1,19 +1,19 @@
#ifndef _SerialManager_h
#define _SerialManager_h
#ifndef _SerialManager_OA_h
#define _SerialManager_OA_h
#include <Tympan_Library.h>
//now, define the Serial Manager class
class SerialManager {
class SerialManager_OA {
public:
public:
SerialManager(Tympan &_audioHardware)
SerialManager_OA(Tympan &_audioHardware)
: audioHardware(_audioHardware)
{ };
//SerialManager(void)
//SerialManager_OA(void)
//{ };
void respondToByte(char c);
@ -27,9 +27,9 @@ class SerialManager {
};
#define thisSerial audioHardware
void SerialManager::printHelp(void) {
void SerialManager_OA::printHelp(void) {
thisSerial.println();
thisSerial.println("SerialManager Help: Available Commands:");
thisSerial.println("SerialManager_OA 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");
@ -52,7 +52,7 @@ extern void switchToMicInOnMicJack(void);
extern void switchToLineInOnMicJack(void);
//switch yard to determine the desired action
void SerialManager::respondToByte(char c) {
void SerialManager_OA::respondToByte(char c) {
//float old_val = 0.0, new_val = 0.0;
switch (c) {
case 'h': case '?':

@ -26,10 +26,9 @@
* 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
#include "AudioEffectLowpassFD_OA_F32.h" // the local file holding your custom function
//set the sample rate and block size
const float sample_rate_Hz = 44117.f;

@ -15,6 +15,8 @@ OpenAudio Library for Teensy
10-Added new analyze_rms_f32.h and .cpp that parallel similar classes in the Teensy I16 library.
11-Moved AudioSwitch_F32 from Tympan, added 8-channel version.
12-Added /examples/Switches_float.ino for 4 and 8 channel switches.
13-Moved FFT_Overlapped_F32 files from Tympan, revised for T4.0, added _OA name isolation.
14-Moved Overlapped FFT LPF .INO from Tympan to OA. Working T3.6 and T4.0
**Purpose**: The purpose of this library is to build upon the [Teensy Audio Library](http://www.pjrc.com/teensy/td_libs_Audio.html) to enable new functionality for real-time audio processing.

Loading…
Cancel
Save