From 7ec40c3429f57d650eaa3d21e624d79751a7b799 Mon Sep 17 00:00:00 2001 From: boblark Date: Wed, 24 Jun 2020 20:49:45 -0700 Subject: [PATCH] Added Frequency Shift Classes andINO; added id to Block_F32 --- AudioEffectFreqShiftFD_OA_F32.cpp | 143 +++++++++++ AudioEffectFreqShiftFD_OA_F32.h | 134 +++++++++++ AudioStream_F32.h | 6 +- OpenAudio_ArduinoLibrary.h | 1 + .../FormantShifter_FD_OA.ino | 36 +-- .../FrequencyShifter_FD_OA.ino | 179 ++++++++++++++ .../FrequencyShifter_OA_FD.ino | 226 ------------------ .../SerialManager_FreqShift_OA.h | 73 ++++++ .../SerialManager_Shift_OA.h | 87 ------- 9 files changed, 557 insertions(+), 328 deletions(-) create mode 100644 AudioEffectFreqShiftFD_OA_F32.cpp create mode 100644 AudioEffectFreqShiftFD_OA_F32.h create mode 100644 examples/FrequencyShifter_FD_OA/FrequencyShifter_FD_OA.ino delete mode 100644 examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino create mode 100644 examples/FrequencyShifter_FD_OA/SerialManager_FreqShift_OA.h delete mode 100644 examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h diff --git a/AudioEffectFreqShiftFD_OA_F32.cpp b/AudioEffectFreqShiftFD_OA_F32.cpp new file mode 100644 index 0000000..024d205 --- /dev/null +++ b/AudioEffectFreqShiftFD_OA_F32.cpp @@ -0,0 +1,143 @@ +/* SerialManager_FreqShift_OA.h + * Demonstrate frequency shifting via frequency domain processing. + * + * Created: Chip Audette (OpenAudio) Aug 2019 + * Built for the Tympan library for Teensy 3.6-based hardware + * + * Convert to Open Audio Bob Larkin June 2020 + * + * MIT License. Use at your own risk. + */ + +#include "AudioEffectFreqShiftFD_OA_F32.h" + +void AudioEffectFreqShiftFD_OA_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 + //FFT is in complex_2N_buffer, interleaved real, imaginary, real, imaginary, etc + myFFT.execute(in_audio_block, complex_2N_buffer); + unsigned long incoming_id = in_audio_block->id; + // We just passed ownership of in_audio_block to myFFT, so we can + // release it here as we won't use it here again. + AudioStream_F32::release(in_audio_block); + + // ////////////// Do your processing here!!! + + //define some variables + int fftSize = myFFT.getNFFT(); + int N_2 = fftSize / 2 + 1; + int source_ind; // neg_dest_ind; + + //zero out DC and Nyquist + //complex_2N_buffer[0] = 0.0; complex_2N_buffer[1] = 0.0; + //complex_2N_buffer[N_2] = 0.0; complex_2N_buffer[N_2] = 0.0; + + //do the shifting + if (shift_bins < 0) { + for (int dest_ind=0; dest_ind < N_2; dest_ind++) { + source_ind = dest_ind - shift_bins; + if (source_ind < N_2) { + complex_2N_buffer[2 * dest_ind] = complex_2N_buffer[2 * source_ind]; //real + complex_2N_buffer[(2 * dest_ind) + 1] = complex_2N_buffer[(2 * source_ind) + 1]; //imaginary + } else { + complex_2N_buffer[2 * dest_ind] = 0.0; + complex_2N_buffer[(2 * dest_ind) + 1] = 0.0; + } + } + } else if (shift_bins > 0) { + //do reverse order because, otherwise, we'd overwrite our source indices with zeros! + for (int dest_ind=N_2-1; dest_ind >= 0; dest_ind--) { + source_ind = dest_ind - shift_bins; + if (source_ind >= 0) { + complex_2N_buffer[2 * dest_ind] = complex_2N_buffer[2 * source_ind]; //real + complex_2N_buffer[(2 * dest_ind) + 1] = complex_2N_buffer[(2 * source_ind) +1]; //imaginary + } else { + complex_2N_buffer[2 * dest_ind] = 0.0; + complex_2N_buffer[(2 * dest_ind) + 1] = 0.0; + } + } + } + + //here's the tricky bit! If the phase shift is an odd number of bins, we must manually evolve the phase through time + if ((abs(shift_bins) % 2) == 1) { + switch (overlap_amount) { + case NONE: + //no phase change needed + break; + case HALF: + //alternate adding 180 deg...which is flipping the sign + overlap_block_counter++; + if (overlap_block_counter == 2){ + overlap_block_counter = 0; + for (int i=0; i < N_2; i++) { + complex_2N_buffer[2*i] = -complex_2N_buffer[2*i]; + complex_2N_buffer[2*i+1] = -complex_2N_buffer[2*i+1]; + } + } + break; + case THREE_QUARTERS: + overlap_block_counter++; //will be 1 to 4 + float foo; + switch (overlap_block_counter) { + case 1: + //no rotation + break; + case 2: + //90 deg + for (int i=0; i < N_2; i++) { + foo = complex_2N_buffer[2*i+1]; + complex_2N_buffer[2*i+1] = complex_2N_buffer[2*i]; + complex_2N_buffer[2*i] = -foo; + } + break; + case 3: + //180 deg + for (int i=0; i < N_2; i++) { + complex_2N_buffer[2*i] = -complex_2N_buffer[2*i]; + complex_2N_buffer[2*i+1] = -complex_2N_buffer[2*i+1]; + } + break; + case 4: + //270 deg + for (int i=0; i < N_2; i++) { + foo = complex_2N_buffer[2*i+1]; + complex_2N_buffer[2*i+1] = -complex_2N_buffer[2*i]; + complex_2N_buffer[2*i] = foo; + } + overlap_block_counter = 0; + break; + } + } + + } + + //zero out the new DC and new nyquist + //complex_2N_buffer[0] = 0.0; complex_2N_buffer[1] = 0.0; + //complex_2N_buffer[N_2] = 0.0; complex_2N_buffer[N_2] = 0.0; + + //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. + + //update the block number to match the incoming one + out_audio_block->id = incoming_id; + + //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; +}; diff --git a/AudioEffectFreqShiftFD_OA_F32.h b/AudioEffectFreqShiftFD_OA_F32.h new file mode 100644 index 0000000..4bad1b5 --- /dev/null +++ b/AudioEffectFreqShiftFD_OA_F32.h @@ -0,0 +1,134 @@ +/* + * AudioEffectFreqShiftFD_F32 + * + * Created: Chip Audette, Aug 2019 + * Purpose: Shift the frequency content of the audio up or down. Performed in the frequency domain + * + * This processes a single stream of audio data (ie, it is mono) + * + * MIT License. use at your own risk. +*/ + +#ifndef _AudioEffectFreqShiftFD_OA_F32_h +#define _AudioEffectFreqShiftFD_OA_F32_h + +#include "AudioStream_F32.h" +#include +#include "FFT_Overlapped_OA_F32.h" +#include + + +class AudioEffectFreqShiftFD_OA_F32 : public AudioStream_F32 +{ +//GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node +//GUI: shortName:freq_shift + public: + //constructors...a few different options. The usual one should be: AudioEffectFreqShiftFD_F32(const AudioSettings_F32 &settings, const int _N_FFT) + AudioEffectFreqShiftFD_OA_F32(void) : AudioStream_F32(1, inputQueueArray_f32) {}; + AudioEffectFreqShiftFD_OA_F32(const AudioSettings_F32 &settings) : + AudioStream_F32(1, inputQueueArray_f32) { + sample_rate_Hz = settings.sample_rate_Hz; + } + AudioEffectFreqShiftFD_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 + ~AudioEffectFreqShiftFD_OA_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; + + //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("AudioEffectFreqShiftFD_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 + + //decide how much overlap is happening + switch (myIFFT.getNBuffBlocks()) { + case 0: + //should never happen + break; + case 1: + overlap_amount = NONE; + break; + case 2: + overlap_amount = HALF; + break; + case 3: + //to do...need to add phase shifting logic to the update() function to support this case + break; + case 4: + overlap_amount = THREE_QUARTERS; + //to do...need to add phase shifting logic to the update() function to support this case + break; + } + + + #if 0 + //print info about setup + Serial.println("AudioEffectFreqShiftFD_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()); + #endif + + //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; + } + + int setShift_bins(int _shift_bins) { + return shift_bins = _shift_bins; + } + int getShift_bins(void) { + return shift_bins; + } + float getShift_Hz(void) { + return getFrequencyOfBin(shift_bins); + } + float getFrequencyOfBin(int bin) { //"bin" should be zero to (N_FFT-1) + return sample_rate_Hz * ((float)bin) / ((float) N_FFT); + } + + virtual void update(void); + bool enable(bool state = true) { enabled = state; return enabled;} + + 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 sample_rate_Hz = AUDIO_SAMPLE_RATE; + int N_FFT = -1; + enum OVERLAP_OPTIONS {NONE, HALF, THREE_QUARTERS}; //evenutally extend to THREE_QUARTERS + int overlap_amount = NONE; + int overlap_block_counter = 0; + + int shift_bins = 0; //how much to shift the frequency +}; + + +#endif diff --git a/AudioStream_F32.h b/AudioStream_F32.h index 4894187..6b9f431 100644 --- a/AudioStream_F32.h +++ b/AudioStream_F32.h @@ -7,6 +7,8 @@ * I modeled it directly on the Teensy code in "AudioStream.h" and "AudioStream.cpp", which are * available here: https://github.com/PaulStoffregen/cores/tree/master/teensy3 * + * Added id to audio_block_f32_t class, per Tympan. Bob Larkin June 2020 + * * MIT License. use at your own risk. */ @@ -28,6 +30,7 @@ class AudioConnection_F32; //create a new structure to hold audio as floating point values. //modeled on the existing teensy audio block struct, which uses Int16 //https://github.com/PaulStoffregen/cores/blob/268848cdb0121f26b7ef6b82b4fb54abbe465427/teensy3/AudioStream.h +// Added id, per Tympan. Should not disturb existing programs. Bob Larkin June 2020 class audio_block_f32_t { public: audio_block_f32_t(void) {}; @@ -44,6 +47,7 @@ class audio_block_f32_t { const int full_length = AUDIO_BLOCK_SAMPLES; int length = AUDIO_BLOCK_SAMPLES; // AUDIO_BLOCK_SAMPLES is 128, from AudioStream.h float fs_Hz = AUDIO_SAMPLE_RATE; // AUDIO_SAMPLE_RATE is 44117.64706 from AudioStream.h + unsigned long id; }; class AudioConnection_F32 @@ -122,4 +126,4 @@ void AudioMemory_F32(const int num, const AudioSettings_F32 &settings); #define AudioMemoryUsageMaxReset_F32() (AudioStream_F32::f32_memory_used_max = AudioStream_F32::f32_memory_used) -#endif \ No newline at end of file +#endif diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 560fb3c..d589444 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -31,3 +31,4 @@ // #include "control_tlv320aic3206.h" collides much with Teensy Audio #include "AudioSwitch_OA_F32.h" #include "FFT_Overlapped_OA_F32.h" +#include "AudioEffectFreqShiftFD_OA_F32.h" diff --git a/examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino b/examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino index 947417d..57adffb 100644 --- a/examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino +++ b/examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino @@ -11,18 +11,19 @@ * * 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 + * The amount of formant shifting is controLled 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 * This example supports only audio Line In, left channel, of SGTL5000 - * Codec (Teensy Audio Adaptor) and Line Out on left channel. Simplified - * control by using only serial commands. This is an interesting block, - * but it doesn't produce the most pleasant result. Have fun and see what you - * can do with the algorithm in the update() function in - * AudioEffectFormantShiftFD_OA_F32.h. + * Codec (Teensy Audio Adaptor) and Line Out on left and right channel. + * Simplified control by using only serial commands. + * + * This is an interesting block. Try it! Ask the internet about + * formant shifting, and check out Chip's implementation in update() + * of AudioEffectFormantShiftFD_OA_F32.h * * This is tested to run on Teensy 3.6 and Teensy 4.0 using the PJRC * Teensy Audio Adaptor. @@ -47,20 +48,23 @@ AudioEffectGain_F32 gain1; //Applies digital gain to audio data. AudioConvert_F32toI16 cnvrt2; AudioOutputI2S i2sOut; AudioControlSGTL5000 codec; + +AudioAnalyzePeak_F32 peak1; //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 patchCord3(formantShift, 0, gain1, 0); //connect to gain AudioConnection_F32 patchCord4(gain1, 0, cnvrt2, 0); //connect to the left output AudioConnection patchCord6(cnvrt2, 0, i2sOut, 0); - +AudioConnection patchCord7(cnvrt2, 0, i2sOut, 1); +AudioConnection_F32 connx(gain1, 0, peak1, 0); //control display and serial interaction bool enable_printCPUandMemory = false; void togglePrintMemoryAndCPU(void) { enable_printCPUandMemory = !enable_printCPUandMemory; }; SerialManagerFormant_OA SerMgr; //inputs and levels -float input_gain_dB = 20.0f; //gain on the microphone +float input_gain_dB = 0.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 @@ -73,11 +77,11 @@ void setup() { // Audio connections require memory to work. For more // detailed information, see the MemoryAndCpuUsage example - AudioMemory(3); // I16 type + AudioMemory(10); // I16 type AudioMemory_F32(40, audio_settings); codec.enable(); - codec.adcHighPassFilterEnable(); + codec.adcHighPassFilterDisable(); codec.inputSelect(AUDIO_INPUT_LINEIN); // Configure the frequency-domain algorithm @@ -120,12 +124,16 @@ void printCPUandMemory(unsigned long curTime_millis, unsigned long updatePeriod_ Serial.print(audio_settings.processorUsageMax()); //Serial.print(AudioProcessorUsageMax()); //if not using AudioSettings_F32 Serial.print("%, "); - Serial.print("Dyn MEM Float32 Cur/Peak: "); + Serial.print("Audio MEM Float32 Cur/Peak: "); Serial.print(AudioMemoryUsage_F32()); Serial.print("/"); - Serial.print(AudioMemoryUsageMax_F32()); + Serial.println(AudioMemoryUsageMax_F32()); + Serial.print("Audio MEM Int16 Cur/Peak: "); + Serial.print(AudioMemoryUsage()); + Serial.print("/"); + Serial.println(AudioMemoryUsageMax()); Serial.println(); - +//if(peak1.available()) Serial.println(peak1.read(), 6); lastUpdate_millis = curTime_millis; //we will use this value the next time around. } } diff --git a/examples/FrequencyShifter_FD_OA/FrequencyShifter_FD_OA.ino b/examples/FrequencyShifter_FD_OA/FrequencyShifter_FD_OA.ino new file mode 100644 index 0000000..0136a98 --- /dev/null +++ b/examples/FrequencyShifter_FD_OA/FrequencyShifter_FD_OA.ino @@ -0,0 +1,179 @@ +/* FreqShifter_FD_OA.ino + * + * 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 + * + * Convert to Open Audio Bob Larkin June 2020 + * Tested OK for Teensy 3.6 and 4.0. + * For settings: + * sample rate (Hz) = 44117.00 + * block size (samples) = 128 + * N_FFT = 512 + * the following resources were used for Teensy 3.6 + * CPU Cur/Peak: 50.02%/50.24% + * MEM Float32 Cur/Peak: 8/9 + * MEM Int16 Cur/Peak: 3/5 + * For Teensy 4.0: + * CPU Cur/Peak: 6.53%/6.84% + * MEM Float32 Cur/Peak: 8/9 + * MEM Int16 Cur/Peak: 3/4 + * + * MIT License. Use at your own risk. + */ + +#include "Audio.h" +#include "AudioStream_F32.h" +#include "OpenAudio_ArduinoLibrary.h" +#include "SerialManager_FreqShift_OA.h" + +//set the sample rate and block size +const float sample_rate_Hz = 44117.f; ; // other frequencies in the table in AudioOutputI2S_F32 for T3.x only +const int audio_block_samples = 128; //for freq domain processing, a power of 2: 16, 32, 64, 128 +AudioSettings_F32 audio_settings(sample_rate_Hz, audio_block_samples); + +//create audio library objects for handling the audio +AudioInputI2S i2sIn; // This I16 input/output is T4.x compatible +AudioConvert_I16toF32 cnvrt1; // Convert to float +AudioEffectFreqShiftFD_OA_F32 freqShift(audio_settings); // 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, freqShift, 0); +AudioConnection_F32 patchCord3(freqShift, 0, gain1, 0); //connect to gain +AudioConnection_F32 patchCord4(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; }; +SerialManagerFreqShift_OA serialMgr; + +//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 + +// *************** SETUP ********************************** +void setup() { + Serial.begin(1); delay(1000); + Serial.println("freqShifter: 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); // I16 type + 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! + codec.enable(); + codec.adcHighPassFilterEnable(); + codec.inputSelect(AUDIO_INPUT_LINEIN); + + //finish the setup by printing the help menu to the serial connections + serialMgr.printHelp(); +} + +// ************************* LOOP **************************** +void loop() { + + //respond to Serial commands + while (Serial.available()) serialMgr.respondToByte((char)Serial.read()); //USB Serial + + //check to see whether to print the CPU and Memory Usage + if (enable_printCPUandMemory) printCPUandMemory(millis(), 3000); //print every 3000 msec + +} //end 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("CPU Cur/Peak: "); + Serial.print(audio_settings.processorUsage()); + Serial.print("%/"); + Serial.print(audio_settings.processorUsageMax()); + Serial.print("%, "); + Serial.print(" Dyn MEM Float32 Cur/Peak: "); + Serial.print(AudioMemoryUsage_F32()); + Serial.print("/"); + Serial.print(AudioMemoryUsageMax_F32()); + Serial.print(" MEM Int16 Cur/Peak: "); + Serial.print(AudioMemoryUsage()); + Serial.print("/"); + Serial.print(AudioMemoryUsageMax()); + 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) { + 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); +} diff --git a/examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino b/examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino deleted file mode 100644 index 4cf1746..0000000 --- a/examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino +++ /dev/null @@ -1,226 +0,0 @@ -// 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 -#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); -} diff --git a/examples/FrequencyShifter_FD_OA/SerialManager_FreqShift_OA.h b/examples/FrequencyShifter_FD_OA/SerialManager_FreqShift_OA.h new file mode 100644 index 0000000..7305e0f --- /dev/null +++ b/examples/FrequencyShifter_FD_OA/SerialManager_FreqShift_OA.h @@ -0,0 +1,73 @@ +// SerialManager_FreqShift_OA.h +// Demonstrate frequency shifting via frequency domain processing. +// +// Created: Chip Audette (OpenAudio) Aug 2019 +// Built for the Tympan library for Teensy 3.6-based hardware +// +// Convert to Open Audio Bob Larkin June 2020 +// +// MIT License. Use at your own risk. +// + +#ifndef _SerialManagerFreqShift_OA_h +#define _SerialManagerFreqShift_OA_h +#include "AudioStream_F32.h" +#include "OpenAudio_ArduinoLibrary.h" +//now, define the Serial Manager class +class SerialManagerFreqShift_OA { + public: + SerialManagerFreqShift_OA(void) { }; + + void respondToByte(char c); + void printHelp(void); + int N_CHAN; + float channelGainIncrement_dB = 2.5f; + int freq_shift_increment = 1; + }; + +void SerialManagerFreqShift_OA::printHelp(void) { + Serial.println(); + Serial.println("SerialManager Help: Available Commands:"); + Serial.println(" h: Print this help"); + Serial.println(" g: Print the gain settings of the device."); + Serial.println(" C: Toggle printing of CPU and Memory usage"); + Serial.print( " k: Increase the gain of all channels (ie, knob gain) by "); + Serial.print(channelGainIncrement_dB); Serial.println(" dB"); + Serial.print( " K: Decrease the gain of all channels (ie, knob gain) by "); + Serial.print(-channelGainIncrement_dB); Serial.println(" dB"); + Serial.print( " f: Raise freq shifting (change by "); Serial.print(freq_shift_increment); Serial.println(" bins)"); + Serial.print( " F: Lower freq shifting (change by "); Serial.print(-freq_shift_increment); Serial.println(" bins)"); Serial.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); + +//switch yard to determine the desired action +void SerialManagerFreqShift_OA::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': + Serial.println("Received: toggle printing of memory and CPU usage."); + togglePrintMemoryAndCPU(); break; + case 'f': + { int new_val = incrementFreqShift(freq_shift_increment); + Serial.print("Recieved: new freq shift = "); Serial.println(new_val);} + break; + case 'F': + { int new_val = incrementFreqShift(-freq_shift_increment); + Serial.print("Recieved: new freq shift = "); Serial.println(new_val);} + break; + } +} +#endif diff --git a/examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h b/examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h deleted file mode 100644 index c2eaf6a..0000000 --- a/examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h +++ /dev/null @@ -1,87 +0,0 @@ - - -#ifndef _SerialManager_h -#define _SerialManager_h - -#include - - -//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