parent
6e9aa1b10c
commit
7ec40c3429
@ -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 |
@ -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…
Reference in new issue