Add AudioEffectCompressor plus example

pull/1/head
Chip Audette 7 years ago
parent d9c788fc79
commit 5c6bd90446
  1. 165
      AudioEffectCompressor_F32.h
  2. 1
      OpenAudio_ArduinoLibrary.h
  3. 165
      examples/BasicCompressor_Float/BasicCompressor_Float.ino
  4. 4
      keywords.txt

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

@ -2,5 +2,6 @@
#include <AudioStream_F32.h>
#include <AudioConvert_F32.h>
#include <AudioEffectGain_F32.h>
#include <AudioEffectCompressor_F32.h>
#include <AudioControlSGTL5000_Extended.h>

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

@ -2,14 +2,18 @@
OpenAudio_ArduinoLibrary KEYWORD1
#data type / class / static function names
float32_t KEYWORD1
audio_block_f32_t KEYWORD1
AudioStream_F32 KEYWORD1
AudioConnection_F32 KEYWORD1
AudioConvert_I16toF32 KEYWORD1
AudioConvert_F32toI16 KEYWORD1
AudioEffectGain_F32 KEYWORD1
AudioEffectCompressor_F32 KEYWORD1
AudioMemory_F32 KEYWORD1
AudioMemoryUsage_F32 KEYWORD1
AudioMemoryUsageMax_F32 KEYWORD1
AudioMemoryUsageMaxReset_F32 KEYWORD1
AudioControlSGTL5000_Extended KEYWORD1
micBiasEnable KEYWORD2
Loading…
Cancel
Save