parent
d9c788fc79
commit
5c6bd90446
@ -0,0 +1,165 @@ |
||||
/*
|
||||
AudioEffectCompressor |
||||
|
||||
Created: Chip Audette, December 2016 |
||||
Purpose; Apply dynamic range compression to the audio stream. |
||||
Assumes floating-point data. |
||||
|
||||
This processes a single stream fo audio data (ie, it is mono) |
||||
|
||||
MIT License. use at your own risk. |
||||
*/ |
||||
|
||||
#include <arm_math.h> //ARM DSP extensions. https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html |
||||
#include <AudioStream_F32.h> |
||||
|
||||
class AudioEffectCompressor_F32 : public AudioStream_F32 |
||||
{ |
||||
public: |
||||
//constructor
|
||||
AudioEffectCompressor_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { |
||||
setThresh_dBFS(-20.0f); //default to this threshold
|
||||
setAttack_sec(0.005f, AUDIO_SAMPLE_RATE); //default to this value
|
||||
setRelease_sec(0.200f, AUDIO_SAMPLE_RATE); //default to this value
|
||||
setCompressionRatio(5.0f); //default to this value
|
||||
setThresh_dBFS(-20.0f); //default to this value
|
||||
setHPFilterCoeff(); |
||||
resetStates(); |
||||
}; |
||||
|
||||
//here's the method that does all the work
|
||||
void update(void) { |
||||
//Serial.println("AudioEffectGain_F32: updating."); //for debugging.
|
||||
audio_block_f32_t *audio_block; |
||||
audio_block = AudioStream_F32::receiveWritable_f32(); |
||||
if (!audio_block) return; |
||||
|
||||
//apply a high-pass filter to get rid of the DC offset
|
||||
if (use_HP_prefilter) arm_biquad_cascade_df1_f32(&hp_filt_struct, audio_block->data, audio_block->data, audio_block->length); |
||||
|
||||
//apply the pre-gain...a negative gain value will disable
|
||||
if (pre_gain > 0.0f) arm_scale_f32(audio_block->data, pre_gain, audio_block->data, audio_block->length); //use ARM DSP for speed!
|
||||
|
||||
//compute the desired gain
|
||||
audio_block_f32_t *gain_block = AudioStream_F32::allocate_f32(); |
||||
calcGain(audio_block, gain_block); //returns through gain_block
|
||||
|
||||
//apply the gain...store it back into audio_block
|
||||
arm_mult_f32(audio_block->data, gain_block->data, audio_block->data, audio_block->length); |
||||
|
||||
///transmit the block and release memory
|
||||
AudioStream_F32::transmit(audio_block); |
||||
AudioStream_F32::release(audio_block); |
||||
AudioStream_F32::release(gain_block); |
||||
} |
||||
|
||||
void calcGain(audio_block_f32_t *wav_block, audio_block_f32_t *gain_block) {
|
||||
|
||||
//calculate the signal power...ie, square the signal: wav_pow = wav.^2
|
||||
audio_block_f32_t *wav_pow_block = AudioStream_F32::allocate_f32(); |
||||
arm_mult_f32(wav_block->data, wav_block->data, wav_pow_block->data, wav_block->length); |
||||
|
||||
//loop over each sample
|
||||
float32_t gain_pow; |
||||
for (int i = 0; i < wav_pow_block->length; i++) { |
||||
|
||||
//compute target gain (well, we're actualy calculating gain^2) assuming we want to copress
|
||||
gain_pow = thresh_pow_FS_wCR / powf(wav_pow_block->data[i], comp_ratio_const); |
||||
|
||||
//if our signal level is below the threshold, don't compress (set target gain to 0dB, which is 1.0)
|
||||
if (wav_pow_block->data[i] < thresh_pow_FS) gain_pow = 1.0f; |
||||
|
||||
//are we in the attack mode or release mode?
|
||||
float32_t c = attack_const; //at first, assume that we're in the attack phase
|
||||
if (gain_pow > prev_gain_pow) c = release_const; //here, we decide if we're really in the release phase
|
||||
|
||||
//smooth the gain using the attack or release constants
|
||||
gain_pow = c*prev_gain_pow + (1.0f-c)*gain_pow; |
||||
|
||||
//take he sqrt of gain^2 so that we simply get the gain
|
||||
//arm_sqrt_f32(gain_pow, &(gain_block->data[i])); //should use the DSP acceleration, if the right CMSIS library is used
|
||||
//gain_block->data[i] = __builtin_sqrtf(gain_pow); //seems to give the same speed as the arm_sqrt_f32
|
||||
gain_block->data[i] = sqrtf(gain_pow); //also give the same speed and is more portable
|
||||
|
||||
//save value for the next time through this loop
|
||||
prev_gain_pow = gain_pow; |
||||
} |
||||
|
||||
//free up the memory and return
|
||||
release(wav_pow_block); |
||||
return; //the output here is gain_block
|
||||
} |
||||
|
||||
//methods to set parameters of this module
|
||||
void resetStates(void) { |
||||
prev_gain_pow = 1.0f; |
||||
|
||||
//initialize the HP filter (it also resets the filter states)
|
||||
arm_biquad_cascade_df1_init_f32(&hp_filt_struct, hp_nstages, hp_coeff, hp_state); |
||||
} |
||||
void setPreGain(float g) { pre_gain = g; } |
||||
void setPreGain_dB(float gain_dB) { setPreGain(pow(10.0, gain_dB / 20.0)); } |
||||
void setCompressionRatio(float cr) { |
||||
comp_ratio = max(0.001, cr); //limit to positive values
|
||||
updateThresholdAndCompRatioConstants(); |
||||
} |
||||
void setAttack_sec(float a, float fs_Hz) { |
||||
attack_sec = a; |
||||
attack_const = expf(-1.0f / (attack_sec * fs_Hz)); //expf() is much faster than exp()
|
||||
}
|
||||
void setRelease_sec(float r, float fs_Hz) { |
||||
release_sec = r; |
||||
release_const = expf(-1.0f / (release_sec * fs_Hz)); //expf() is much faster than exp()
|
||||
}
|
||||
void setThresh_dBFS(float thresh_dBFS) { setThreshPow(pow(10.0, thresh_dBFS / 10.0)); } |
||||
void setThreshPow(float t_pow) {
|
||||
thresh_pow_FS = t_pow; |
||||
updateThresholdAndCompRatioConstants(); |
||||
} |
||||
void enableHPFilter(boolean flag) { use_HP_prefilter = flag; }; |
||||
|
||||
//methods to return information about this module
|
||||
float32_t getPreGain_dB(void) { return 20.0 * log10(pre_gain); } |
||||
float32_t getAttack_sec(void) { return attack_sec; } |
||||
float32_t getRelease_sec(void) { return release_sec; } |
||||
float32_t getThresh_dBFS(void) { return 10.0 * log10(thresh_pow_FS); } |
||||
float32_t getCompressionRatio(void) { return comp_ratio; } |
||||
float32_t getCurrentGain_dB(void) { return 10.0 * log10(prev_gain_pow); } |
||||
|
||||
private: |
||||
//state-related variables
|
||||
audio_block_f32_t *inputQueueArray_f32[1]; //memory pointer for the input to this module
|
||||
float32_t prev_gain_pow = 1.0; //last gain^2 used
|
||||
|
||||
//HP filter state-related variables
|
||||
arm_biquad_casd_df1_inst_f32 hp_filt_struct; |
||||
static const uint8_t hp_nstages = 1; |
||||
float32_t hp_coeff[5 * hp_nstages] = {1.0, 0.0, 0.0, 0.0, 0.0}; //no filtering. actual filter coeff set later
|
||||
float32_t hp_state[4 * hp_nstages]; |
||||
void setHPFilterCoeff(void) { |
||||
//https://www.keil.com/pack/doc/CMSIS/DSP/html/group__BiquadCascadeDF1.html#ga8e73b69a788e681a61bccc8959d823c5
|
||||
//Use matlab to compute the coeff for HP at 20Hz: [b,a]=butter(2,20/(44100/2),'high'); %assumes fs_Hz = 44100
|
||||
float32_t b[] = {9.979871156751189e-01, -1.995974231350238e+00, 9.979871156751189e-01}; //from Matlab
|
||||
float32_t a[] = { 1.000000000000000e+00, -1.995970179642828e+00, 9.959782830576472e-01}; //from Matlab
|
||||
hp_coeff[0] = b[0]; hp_coeff[1] = b[1]; hp_coeff[2] = b[2]; //here are the matlab "b" coefficients
|
||||
hp_coeff[3] = -a[1]; hp_coeff[4] = -a[2]; //the DSP needs the "a" terms to have opposite sign vs Matlab
|
||||
} |
||||
|
||||
//private parameters related to gain calculation
|
||||
float32_t attack_const, release_const; //used in calcGain(). set by setAttack_sec() and setRelease_sec();
|
||||
float32_t comp_ratio_const, thresh_pow_FS_wCR; //used in calcGain(); set in updateThresholdAndCompRatioConstants()
|
||||
void updateThresholdAndCompRatioConstants(void) { |
||||
comp_ratio_const = 1.0f-(1.0f / comp_ratio); |
||||
thresh_pow_FS_wCR = powf(thresh_pow_FS, comp_ratio_const); //powf() is much faster than pow()
|
||||
} |
||||
|
||||
//settings
|
||||
float32_t attack_sec, release_sec;
|
||||
float32_t thresh_pow_FS = 1.0f; //threshold for compression, relative to digital full scale
|
||||
float32_t comp_ratio = 1.0; //compression ratio
|
||||
float32_t pre_gain = -1.0; //gain to apply before the compression. negative value disables
|
||||
boolean use_HP_prefilter = false; |
||||
|
||||
}; |
||||
|
||||
|
@ -0,0 +1,165 @@ |
||||
/*
|
||||
BasicCompressor |
||||
|
||||
Created: Chip Audette, Dec 2016 |
||||
Purpose: Process audio by applying a single-band compressor |
||||
Demonstrates audio processing using floating point data type. |
||||
|
||||
Uses Teensy Audio Adapter. |
||||
Assumes microphones (or whatever) are attached to the LINE IN (stereo) |
||||
|
||||
MIT License. use at your own risk. |
||||
*/ |
||||
|
||||
//These are the includes from the Teensy Audio Library
|
||||
#include <Audio.h> //Teensy Audio Librarya |
||||
#include <Wire.h> |
||||
#include <SPI.h> |
||||
#include <SD.h> |
||||
#include <SerialFlash.h> |
||||
|
||||
#include <OpenAudio_ArduinoLibrary.h> //for AudioConvert_I16toF32, AudioConvert_F32toI16, and AudioEffectGain_F32 |
||||
|
||||
//create audio library objects for handling the audio
|
||||
AudioControlSGTL5000_Extended sgtl5000; //controller for the Teensy Audio Board
|
||||
AudioInputI2S i2s_in; //Digital audio *from* the Teensy Audio Board ADC. Sends Int16. Stereo.
|
||||
AudioOutputI2S i2s_out; //Digital audio *to* the Teensy Audio Board DAC. Expects Int16. Stereo
|
||||
AudioConvert_I16toF32 int2Float1, int2Float2; //Converts Int16 to Float. See class in AudioStream_F32.h
|
||||
AudioConvert_F32toI16 float2Int1, float2Int2; //Converts Float to Int16. See class in AudioStream_F32.h
|
||||
AudioEffectCompressor_F32 comp1, comp2;
|
||||
|
||||
//Make all of the audio connections
|
||||
AudioConnection patchCord1(i2s_in, 0, int2Float1, 0); //connect the Left input to the Left Int->Float converter
|
||||
AudioConnection patchCord2(i2s_in, 1, int2Float2, 0); //connect the Right input to the Right Int->Float converter
|
||||
AudioConnection_F32 patchCord10(int2Float1, 0, comp1, 0); //Left. makes Float connections between objects
|
||||
AudioConnection_F32 patchCord11(int2Float2, 0, comp2, 0); //Right. makes Float connections between objects
|
||||
AudioConnection_F32 patchCord12(comp1, 0, float2Int1, 0); //Left. makes Float connections between objects
|
||||
AudioConnection_F32 patchCord13(comp2, 0, float2Int2, 0); //Right. makes Float connections between objects
|
||||
AudioConnection patchCord20(float2Int1, 0, i2s_out, 0); //connect the Left float processor to the Left output
|
||||
AudioConnection patchCord21(float2Int2, 0, i2s_out, 1); //connect the Right float processor to the Right output
|
||||
|
||||
// which input on the audio shield will be used?
|
||||
const int myInput = AUDIO_INPUT_LINEIN; |
||||
//const int myInput = AUDIO_INPUT_MIC;
|
||||
|
||||
//I have a potentiometer on the Teensy Audio Board
|
||||
#define POT_PIN A1 //potentiometer is tied to this pin
|
||||
|
||||
//define a function to setup the Teensy Audio Board how I like it
|
||||
void setupMyAudioBoard(void) { |
||||
sgtl5000.enable(); //start the audio board
|
||||
sgtl5000.inputSelect(myInput); //choose line-in or mic-in
|
||||
sgtl5000.volume(0.8); //volume can be 0.0 to 1.0. 0.5 seems to be the usual default.
|
||||
sgtl5000.lineInLevel(10,10); //level can be 0 to 15. 5 is the Teensy Audio Library's default
|
||||
sgtl5000.adcHighPassFilterDisable(); //reduces noise. https://forum.pjrc.com/threads/27215-24-bit-audio-boards?p=78831&viewfull=1#post78831
|
||||
sgtl5000.micBiasEnable(3.0); //enable the mic bias voltage...only in AudioControlSGTL5000_Extended
|
||||
} |
||||
|
||||
//define a function to configure the left and right compressors
|
||||
void setupMyCompressors(boolean use_HP_filter, float knee_dBFS, float comp_ratio, float attack_sec, float release_sec) { |
||||
comp1.enableHPFilter(use_HP_filter); comp2.enableHPFilter(use_HP_filter); |
||||
comp1.setThresh_dBFS(knee_dBFS); comp2.setThresh_dBFS(knee_dBFS); |
||||
comp1.setCompressionRatio(comp_ratio); comp2.setCompressionRatio(comp_ratio);
|
||||
|
||||
float fs_Hz = AUDIO_SAMPLE_RATE; |
||||
comp1.setAttack_sec(attack_sec, fs_Hz); comp2.setAttack_sec(attack_sec, fs_Hz); |
||||
comp1.setRelease_sec(release_sec, fs_Hz); comp2.setRelease_sec(release_sec, fs_Hz); |
||||
} |
||||
|
||||
// define the overall setup() function, the function that is called once when the device is booting
|
||||
void setup() { |
||||
Serial.begin(115200); //open the USB serial link to enable debugging messages
|
||||
delay(500); //give the computer's USB serial system a moment to catch up.
|
||||
Serial.println("Teensy Hearing Aid: BasicCompressor_Float..."); //identify myself over the USB serial
|
||||
|
||||
// Audio connections require memory, and the record queue
|
||||
// uses this memory to buffer incoming audio.
|
||||
AudioMemory(12); //allocate Int16 audio data blocks
|
||||
AudioMemory_F32(10); //allocate Float32 audio data blocks
|
||||
|
||||
// Enable the audio shield, select input, and enable output
|
||||
setupMyAudioBoard(); |
||||
|
||||
//choose the compressor parameters...note that preGain is set by the potentiometer in the main loop()
|
||||
boolean use_HP_filter = true; //enable the software HP filter to get rid of DC?
|
||||
float knee_dBFS, comp_ratio, attack_sec, release_sec; |
||||
if (false) { |
||||
Serial.println("Configuring Compressor for fast response for use as a limitter."); |
||||
knee_dBFS = -15.0; comp_ratio = 8.0; attack_sec = 0.005; release_sec = 0.200;
|
||||
} else { |
||||
Serial.println("Configuring Compressor for slow response more like an automatic volume control."); |
||||
knee_dBFS = -50.0; comp_ratio = 5.0; attack_sec = 1.0; release_sec = 2.0; |
||||
} |
||||
|
||||
//configure the left and right compressors with the desired settings
|
||||
setupMyCompressors(use_HP_filter, knee_dBFS, comp_ratio, attack_sec, release_sec); |
||||
|
||||
// setup any other other features
|
||||
pinMode(POT_PIN, INPUT); //set the potentiometer's input pin as an INPUT
|
||||
|
||||
} //end setup()
|
||||
|
||||
|
||||
// define the loop() function, the function that is repeated over and over for the life of the device
|
||||
unsigned long updatePeriod_millis = 100; //how many milliseconds between updating gain reading?
|
||||
unsigned long lastUpdate_millis = 0; |
||||
unsigned long curTime_millis = 0; |
||||
int prev_gain_dB = 0; |
||||
unsigned long lastMemUpdate_millis=0; |
||||
void loop() { |
||||
//choose to sleep ("wait for interrupt") instead of spinning our wheels doing nothing but consuming power
|
||||
asm(" WFI"); //ARM-specific. Will wake on next interrupt. The audio library issues tons of interrupts, so we wake up often.
|
||||
|
||||
//has enough time passed to try updating the GUI?
|
||||
curTime_millis = millis(); //what time is it right now
|
||||
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
|
||||
float32_t val = float(analogRead(POT_PIN)) / 1024.0f; //0.0 to 1.0
|
||||
val = 0.1*(float)((int)(10.0*val + 0.5)); //quantize so that it doesn't chatter
|
||||
|
||||
//compute desired digital gain
|
||||
const float min_gain_dB = -20.0, max_gain_dB = 40.0; //set desired gain range
|
||||
float gain_dB = min_gain_dB + (max_gain_dB - min_gain_dB)*val; //computed desired gain value in dB
|
||||
|
||||
//if the gain is different than before, set the new gain value
|
||||
if (abs(gain_dB - prev_gain_dB) > 1.0) { //is it different than before
|
||||
comp1.setPreGain_dB(gain_dB); //set the gain of the Left-channel gain processor
|
||||
comp2.setPreGain_dB(gain_dB); //set the gain of the Right-channel gain processor
|
||||
Serial.print("Setting Digital Pre-Gain dB = "); Serial.println(gain_dB); //print text to Serial port for debugging
|
||||
prev_gain_dB = gain_dB; //we will use this value the next time around
|
||||
} |
||||
|
||||
lastUpdate_millis = curTime_millis; //we will use this value the next time around.
|
||||
} // end if
|
||||
|
||||
|
||||
//print status information to the Serial port
|
||||
if ((curTime_millis - lastMemUpdate_millis) > 2000) { // print a summary of the current & maximum usage
|
||||
//printCompressorState(&Serial);
|
||||
printCPUandMemoryUsage(&Serial); |
||||
lastMemUpdate_millis = curTime_millis; //we will use this value the next time around.
|
||||
} |
||||
|
||||
} //end loop();
|
||||
|
||||
void printCompressorState(Stream *s) { |
||||
s->print("Current Compressor: Pre-Gain (dB) = "); |
||||
s->print(comp1.getPreGain_dB()); |
||||
s->print(", Dynamic Gain L/R (dB) = ");
|
||||
s->print(comp1.getCurrentGain_dB()); |
||||
s->print(", "); |
||||
s->print(comp2.getCurrentGain_dB()); |
||||
s->println(); |
||||
}; |
||||
|
||||
void printCPUandMemoryUsage(Stream *s) { |
||||
s->print("Usage/Max: "); |
||||
s->print("comp1 CPU = "); s->print(comp1.processorUsage()); s->print("/"); s->print(comp1.processorUsageMax());s->print(", "); |
||||
s->print("all CPU = " ); s->print(AudioProcessorUsage()); s->print("/"); s->print(AudioProcessorUsageMax());s->print(", "); |
||||
s->print("Int16 Mem = ");s->print(AudioMemoryUsage()); s->print("/"); s->print(AudioMemoryUsageMax());s->print(", "); |
||||
s->print("Float Mem = ");s->print(AudioMemoryUsage_F32());s->print("/"); s->print(AudioMemoryUsageMax_F32()); s->print(", "); |
||||
s->println(); |
||||
}; |
||||
|
Loading…
Reference in new issue