/*
BasicCompressor_Float
Created: Chip Audette, Dec 2016 - Jan 2017
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.
*/
//Chip Audette blog is: https://openaudio.blogspot.com/search/label/Compression
//This code has been modified to have 2 functioning MODES 1. MODE_Compressor(for TV movies or programs) or 2. MODE_AGC(for music)
//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 <Bounce.h>
#include <OpenAudio_ArduinoLibrary.h> //for AudioConvert_I16toF32, AudioConvert_F32toI16, and AudioEffectGain_F32
#define CompressorInput 0 //used for mixers
#define AGCInput 1
//create audio library objects for handling the audio
//------------- compressor objects ---------------------
AudioControlSGTL5000_Extended sgtl5000; //controller for the Teensy Audio Board
AudioInputI2S i2s_in; //Digital audio *from* the Teensy Audio Board ADC. Sends Int16. Stereo.
AudioConvert_I16toF32 int2Float1, int2Float2; //Converts Int16 to Float. See class in AudioStream_F32.h
AudioEffectCompressor_F32 comp1, comp2;
AudioConvert_F32toI16 float2Int1, float2Int2; //Converts Float to Int16. See class in AudioStream_F32.h
AudioOutputI2S i2s_out; //Digital audio *to* the Teensy Audio Board DAC. Expects Int16. Stereo
//-------------- agc objects ------------
AudioRecordQueue queue_inLeft;
AudioRecordQueue queue_inRight;
AudioPlayQueue queue_outLeft;
AudioPlayQueue queue_outRight;
AudioMixer4 mixerLeft;
AudioMixer4 mixerRight;
//---------------- compressor 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, mixerLeft, CompressorInput);
AudioConnection patchCord21(float2Int2, 0, mixerRight, CompressorInput);
AudioConnection patchCord30(mixerLeft, 0, i2s_out, 0);
AudioConnection patchCord31(mixerRight, 0, i2s_out, 1);
//-------------- agc connections ------------------
AudioConnection patchCord1a(i2s_in, 0, queue_inLeft, 0);
AudioConnection patchCord2a(i2s_in, 1, queue_inRight, 0);
AudioConnection patchCord10a(queue_outLeft, 0, mixerLeft, AGCInput);
AudioConnection patchCord11a(queue_outRight, 0, mixerRight, AGCInput);
Bounce button = Bounce(14, 15); //pin14 15msec debounce time
// which input on the audio shield will be used?
const int myInput = AUDIO_INPUT_LINEIN;
//const int myInput = AUDIO_INPUT_MIC;
//Global AGC Variables
int32_t agcGain1=0x08000000;//.5 in Q3.29 format
int32_t agcGain2=0x08000000;//.5 in Q3.29 format
int32_t ref=146752;//.035*AUDIO_BLOCK_SAMPLES in Q15 format(1146*128)
int SelectCompressor=0; //starting mode is AGC
//define a function to setup the Teensy Audio Board how I like it
void setupMyAudioBoard(void) {
sgtl5000.enable(); //start the audio board
sgtl5000.volume(0.8); //volume can be 0.0 to 1.0. 0.5 seems to be the usual default.
sgtl5000.inputSelect(myInput); //choose line-in or mic-in
// 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);
}
//Select processed Compressor or AGC audio for the output
void updateMode(int SelCompressor) //select Compressor or AGC outputs
{
if (SelCompressor)
{
mixerLeft.gain(CompressorInput,1.0); //connect Compressor to output
mixerRight.gain(CompressorInput,1.0);
mixerLeft.gain(AGCInput,0.0);
mixerRight.gain(AGCInput,0.0);
}
else
{
mixerLeft.gain(CompressorInput,0.0); //connect AGC to output
mixerRight.gain(CompressorInput,0.0);
mixerLeft.gain(AGCInput,1.0);
mixerRight.gain(AGCInput,1.0);
}
}
// 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
Serial4.begin(9600); //serial interface to UNO OLED controller
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(32); //allocate Int16 audio data blocks
AudioMemory_F32(16); //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 (true) {
Serial.println("Configuring Compressor for fast response for use as a limitter.");
knee_dBFS = -15.0f; comp_ratio = 5.0f; attack_sec = 0.005f; release_sec = 0.200f;
} 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);
//configure compressor pregains
float gain_dB = 20.0;
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
pinMode(14, INPUT_PULLUP); //pushbutton to change AGC/Compressor modes
updateMode(SelectCompressor); //Initially configure to AGC mode (SelectCompressor=0)
delay(100);
// Start the record queue
queue_inLeft.begin();
queue_inRight.begin();
} //end setup()
// define the loop() function, the function that is repeated over and over for the life of the device
unsigned long curTime_millis = 0;
unsigned long lastMemUpdate_millis = 0;
unsigned long last_time1 = millis();
void loop() {
//Left channel AGC
if (queue_inLeft.available() >= 1) //Left Channel
{
agcCalc(queue_inLeft.readBuffer(),queue_outLeft.getBuffer(),&agcGain1);
// Free the input audio buffer
queue_inLeft.freeBuffer();
// and play it back into the audio queue
queue_outLeft.playBuffer();
} //if (queue_inLeft.available() >= 1)
//Right channel AGC
if (queue_inRight.available() >= 1)
{
agcCalc(queue_inRight.readBuffer(),queue_outRight.getBuffer(),&agcGain2);
// Free the input audio buffer
queue_inRight.freeBuffer();
// and play it back into the audio queue
queue_outRight.playBuffer();
} //if (queue_inRight.available() >= 1)
//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.
}
if (millis() - last_time1 >= 2500) //Send info to OLED display via Arduino UNO
{
if (SelectCompressor)
{
Serial4.print("CompressorActive");
}
else
{
Serial4.print("agcGains: ");
Serial4.print((float)agcGain1/(float)0x10000000);
Serial4.print(",");
Serial4.println((float)agcGain2/(float)0x10000000);
}
last_time1=millis();
}
button.update(); //push button to change mode AGC/Compressor
if (button.fallingEdge())
{
if (SelectCompressor==0)
SelectCompressor=1;
else
SelectCompressor=0;
updateMode(SelectCompressor);
}
} //end loop();
void printCompressorState(Stream *s) {
s->print("Current Compressor: Pre-Gain (dB) = ");
s->print(comp1.getPreGain_dB());
s->print(", Level (dBFS) = ");
s->print(comp1.getCurrentLevel_dBFS());
s->print(", ");
s->print(comp2.getCurrentLevel_dBFS());
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();
};
void agcCalc(int16_t *inbuf,int16_t *outbuf,int32_t *agcGain)
{
int16_t *bp1=inbuf,*bp2=outbuf;
// Apply agcGain on input with saturation to output buffer
int32_t sum=0,sum1=0,sum2=0,sum3=0,res,res1,res2,res3,err;
for(int i = 0;i < AUDIO_BLOCK_SAMPLES>>2;i++)
{
//ref. dspinst.h in Teensy audio library
res=signed_multiply_32x16b(*agcGain>>12,*bp1++);
res1=signed_multiply_32x16b(*agcGain>>12,*bp1++);
res2=signed_multiply_32x16b(*agcGain>>12,*bp1++);
res3=signed_multiply_32x16b(*agcGain>>12,*bp1++);
*bp2++=saturate16(res);
*bp2++=saturate16(res1);
*bp2++=saturate16(res2);
*bp2++=saturate16(res3);
sum+=abs(res);
sum1+=abs(res1);
sum2+=abs(res2);
sum3+=abs(res3);
}
sum+=(sum1+sum2+sum3);
// Calculate new agcGain for next input buffer
err=ref-sum;
*agcGain+=err>>3; //update gain
*agcGain=min(*agcGain,0x40000000); //keep agcGain < 4.0
*agcGain=max(*agcGain,0); //keep agcGain >= 0
}