Added Frequency Shift Classes andINO; added id to Block_F32

pull/11/head
boblark 4 years ago
parent 6e9aa1b10c
commit 7ec40c3429
  1. 143
      AudioEffectFreqShiftFD_OA_F32.cpp
  2. 134
      AudioEffectFreqShiftFD_OA_F32.h
  3. 6
      AudioStream_F32.h
  4. 1
      OpenAudio_ArduinoLibrary.h
  5. 36
      examples/FormantShifter_FD_OA/FormantShifter_FD_OA.ino
  6. 179
      examples/FrequencyShifter_FD_OA/FrequencyShifter_FD_OA.ino
  7. 226
      examples/FrequencyShifter_FD_OA/FrequencyShifter_OA_FD.ino
  8. 73
      examples/FrequencyShifter_FD_OA/SerialManager_FreqShift_OA.h
  9. 87
      examples/FrequencyShifter_FD_OA/SerialManager_Shift_OA.h

@ -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;
};

@ -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 <arm_math.h>
#include "FFT_Overlapped_OA_F32.h"
#include <Arduino.h>
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

@ -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
#endif

@ -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"

@ -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.
}
}

@ -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);
}

@ -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 <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,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

@ -1,87 +0,0 @@
#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
Loading…
Cancel
Save