From 3b0f7c37ddde19b91715f873e76ac319a15fc15e Mon Sep 17 00:00:00 2001 From: Chip Audette Date: Tue, 21 Feb 2017 09:51:59 -0500 Subject: [PATCH] Add WDRC gain and WDRC compression to library --- AudioCalcGainWDRC_F32.h | 173 +++++++++++++++++++++++++++++++++++++ AudioEffectCompWDRC_F32.h | 170 ++++++++++++++++++++++++++++++++++++ OpenAudio_ArduinoLibrary.h | 2 + keywords.txt | 4 + 4 files changed, 349 insertions(+) create mode 100644 AudioCalcGainWDRC_F32.h create mode 100644 AudioEffectCompWDRC_F32.h diff --git a/AudioCalcGainWDRC_F32.h b/AudioCalcGainWDRC_F32.h new file mode 100644 index 0000000..e08a798 --- /dev/null +++ b/AudioCalcGainWDRC_F32.h @@ -0,0 +1,173 @@ +/* + * AudioCalcGainWDRC_F32 + * + * Created: Chip Audette, Feb 2017 + * Purpose: This module calculates the gain needed for wide dynamic range compression. + * Derived From: Core algorithm is from "WDRC_circuit" + * WDRC_circuit from CHAPRO from BTNRC: https://github.com/BTNRH/chapro + * As of Feb 2017, CHAPRO license is listed as "Creative Commons?" + * + * This processes a single stream fo audio data (ie, it is mono) + * + * MIT License. use at your own risk. +*/ + +#ifndef _AudioCalcGainWDRC_F32_h +#define _AudioCalcGainWDRC_F32_h + +#include //ARM DSP extensions. for speed! +#include + +typedef struct { + float attack; // attack time (ms), unused in this class + float release; // release time (ms), unused in this class + float fs; // sampling rate (Hz), set through other means in this class + float maxdB; // maximum signal (dB SPL)...I think this is the SPL corresponding to signal with rms of 1.0 + float tkgain; // compression-start gain + float tk; // compression-start kneepoint + float cr; // compression ratio + float bolt; // broadband output limiting threshold +} CHA_WDRC; + + +class AudioCalcGainWDRC_F32 : public AudioStream_F32 +{ + //GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node + //GUI: shortName:calc_WDRCGain + public: + //default constructor + AudioCalcGainWDRC_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { setDefaultValues(); }; + + //here's the method that does all the work + void update(void) { + + //get the input audio data block + audio_block_f32_t *in_block = AudioStream_F32::receiveReadOnly_f32(); // must be the envelope! + if (!in_block) return; + + //prepare an output data block + audio_block_f32_t *out_block = AudioStream_F32::allocate_f32(); + if (!out_block) return; + + // //////////////////////add your processing here! + calcGainFromEnvelope(in_block->data, out_block->data, in_block->length); + out_block->length = in_block->length; out_block->fs_Hz = in_block->fs_Hz; + + //transmit the block and be done + AudioStream_F32::transmit(out_block); + AudioStream_F32::release(out_block); + AudioStream_F32::release(in_block); + + } + + void calcGainFromEnvelope(float *env, float *gain_out, const int n) { + //env = input, signal envelope (not the envelope of the power, but the envelope of the signal itslef) + //gain = output, the gain in natural units (not power, not dB) + //n = input, number of samples to process in each vector + + //prepare intermediate data block + audio_block_f32_t *env_dB_block = AudioStream_F32::allocate_f32(); + if (!env_dB_block) return; + + //convert to dB + for (int k=0; k < n; k++) env_dB_block->data[k] = maxdB + db2(env[k]); //maxdb in the private section + + // apply wide-dynamic range compression + WDRC_circuit_gain(env_dB_block->data, gain_out, n, tkgn, tk, cr, bolt); + AudioStream_F32::release(env_dB_block); + } + + //original call to WDRC_circuit + //void WDRC_circuit(float *x, float *y, float *pdb, int n, float tkgn, float tk, float cr, float bolt) + //void WDRC_circuit(float *orig_signal, float *signal_out, float *env_dB, int n, float tkgn, float tk, float cr, float bolt) + //modified to output the gain instead of the fully processed signal + void WDRC_circuit_gain(float *env_dB, float *gain_out, const int n, + const float tkgn, const float tk, const float cr, const float bolt) { + + float gdb, tkgo, pblt; + int k; + float *pdb = env_dB; //just rename it to keep the code below unchanged + float tk_tmp = tk; + + if ((tk_tmp + tkgn) > bolt) { + tk_tmp = bolt - tkgn; + } + tkgo = tkgn + tk_tmp * (1.0f - 1.0f / cr); + pblt = cr * (bolt - tkgo); + const float cr_const = ((1.0f / cr) - 1.0f); + for (k = 0; k < n; k++) { + if ((pdb[k] < tk_tmp) && (cr >= 1.0f)) { + gdb = tkgn; + } else if (pdb[k] > pblt) { + gdb = bolt + ((pdb[k] - pblt) / 10.0f) - pdb[k]; + } else { + gdb = cr_const * pdb[k] + tkgo; + } + gain_out[k] = undb2(gdb); + //y[k] = x[k] * undb2(gdb); //apply the gain + } + } + + void setDefaultValues(void) { + CHA_WDRC gha = {1.0f, // attack time (ms), IGNORED HERE + 50.0f, // release time (ms), IGNORED HERE + 24000.0f, // fs, sampling rate (Hz), IGNORED HERE + 119.0f, // maxdB, maximum signal (dB SPL) + 0.0f, // tkgain, compression-start gain + 105.0f, // tk, compression-start kneepoint + 10.0f, // cr, compression ratio + 105.0f // bolt, broadband output limiting threshold + }; + //setParams(gha.maxdB, gha.tkgain, gha.cr, gha.tk, gha.bolt); //also sets calcEnvelope + setParams_from_CHA_WDRC(&gha); + } + void setParams_from_CHA_WDRC(CHA_WDRC *gha) { + setParams(gha->maxdB, gha->tkgain, gha->cr, gha->tk, gha->bolt); //also sets calcEnvelope + } + void setParams(float _maxdB, float _tkgain, float _cr, float _tk, float _bolt) { + maxdB = _maxdB; + tkgn = _tkgain; + tk = _tk; + cr = _cr; + bolt = _bolt; + } + + static float undb2(const float &x) { return expf(0.11512925464970228420089957273422f*x); } //faster: exp(log(10.0f)*x/20); this is exact + static float db2(const float &x) { return 6.020599913279623f*log2f_approx(x); } //faster: 20*log2_approx(x)/log2(10); this is approximate + + /* ---------------------------------------------------------------------- + ** Fast approximation to the log2() function. It uses a two step + ** process. First, it decomposes the floating-point number into + ** a fractional component F and an exponent E. The fraction component + ** is used in a polynomial approximation and then the exponent added + ** to the result. A 3rd order polynomial is used and the result + ** when computing db20() is accurate to 7.984884e-003 dB. + ** ------------------------------------------------------------------- */ + //https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 + static float log2f_approx(float X) { + //float *C = &log2f_approx_coeff[0]; + float Y; + float F; + int E; + + // This is the approximation to log2() + F = frexpf(fabsf(X), &E); + // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; + Y = 1.23149591368684f; //C[0] + Y *= F; + Y += -4.11852516267426f; //C[1] + Y *= F; + Y += 6.02197014179219f; //C[2] + Y *= F; + Y += -3.13396450166353f; //C[3] + Y += E; + + return(Y); + } + + private: + audio_block_f32_t *inputQueueArray_f32[1]; //memory pointer for the input to this module + float maxdB, tkgn, tk, cr, bolt; +}; + +#endif diff --git a/AudioEffectCompWDRC_F32.h b/AudioEffectCompWDRC_F32.h new file mode 100644 index 0000000..9dcdd38 --- /dev/null +++ b/AudioEffectCompWDRC_F32.h @@ -0,0 +1,170 @@ +/* + * AudioEffectCompWDR_F32: Wide Dynamic Rnage Compressor + * + * Created: Chip Audette (OpenAudio) Feb 2017 + * Derived From: WDRC_circuit from CHAPRO from BTNRC: https://github.com/BTNRH/chapro + * As of Feb 2017, CHAPRO license is listed as "Creative Commons?" + * + * MIT License. Use at your own risk. + * + */ + +#ifndef _AudioEffectCompWDRC_F32 +#define _AudioEffectCompWDRC_F32 + +#include +#include +#include +#include +#include "AudioCalcGainWDRC_F32.h" //has definition of CHA_WDRC + + +// from CHAPRO cha_ff.h +#define DSL_MXCH 32 // maximum number of channels +typedef struct { + float attack; // attack time (ms) + float release; // release time (ms) + float maxdB; // maximum signal (dB SPL) + int ear; // 0=left, 1=right + int nchannel; // number of channels + float cross_freq[DSL_MXCH]; // cross frequencies (Hz) + float tkgain[DSL_MXCH]; // compression-start gain + float cr[DSL_MXCH]; // compression ratio + float tk[DSL_MXCH]; // compression-start kneepoint + float bolt[DSL_MXCH]; // broadband output limiting threshold +} CHA_DSL; + +typedef struct { + float alfa; // attack constant (not time) + float beta; // release constant (not time + float fs; // sampling rate (Hz) + float maxdB; // maximum signal (dB SPL) + float tkgain; // compression-start gain + float tk; // compression-start kneepoint + float cr; // compression ratio + float bolt; // broadband output limiting threshold +} CHA_DVAR_t; + + +class AudioEffectCompWDRC_F32 : public AudioStream_F32 +{ + public: + AudioEffectCompWDRC_F32(void): AudioStream_F32(1,inputQueueArray) { //need to modify this for user to set sample rate + setSampleRate_Hz(AUDIO_SAMPLE_RATE); + setDefaultValues(); + } + + AudioEffectCompWDRC_F32(AudioSettings_F32 settings): AudioStream_F32(1,inputQueueArray) { //need to modify this for user to set sample rate + setSampleRate_Hz(settings.sample_rate_Hz); + setDefaultValues(); + } + + //here is the method called automatically by the audio library + void update(void) { + //receive the input audio data + audio_block_f32_t *block = AudioStream_F32::receiveReadOnly_f32(); + if (!block) return; + + //allocate memory for the output of our algorithm + audio_block_f32_t *out_block = AudioStream_F32::allocate_f32(); + if (!out_block) return; + + //do the algorithm + cha_agc_channel(block->data, out_block->data, block->length); + + // transmit the block and release memory + AudioStream_F32::transmit(out_block); // send the FIR output + AudioStream_F32::release(out_block); + AudioStream_F32::release(block); + } + + + //here is the function that does all the work + void cha_agc_channel(float *input, float *output, int cs) { + //compress(input, output, cs, &prev_env, + // CHA_DVAR.alfa, CHA_DVAR.beta, CHA_DVAR.tkgain, CHA_DVAR.tk, CHA_DVAR.cr, CHA_DVAR.bolt, CHA_DVAR.maxdB); + compress(input, output, cs); + } + + //void compress(float *x, float *y, int n, float *prev_env, + // float &alfa, float &beta, float &tkgn, float &tk, float &cr, float &bolt, float &mxdB) + void compress(float *x, float *y, int n) + //x, input, audio waveform data + //y, output, audio waveform data after compression + //n, input, number of samples in this audio block + { + // find smoothed envelope + audio_block_f32_t *envelope_block = AudioStream_F32::allocate_f32(); + if (!envelope_block) return; + calcEnvelope.smooth_env(x, envelope_block->data, n); + //float *xpk = envelope_block->data; //get pointer to the array of (empty) data values + + //calculate gain + audio_block_f32_t *gain_block = AudioStream_F32::allocate_f32(); + if (!gain_block) return; + calcGain.calcGainFromEnvelope(envelope_block->data, gain_block->data, n); + + //apply gain + arm_mult_f32(x, gain_block->data, y, n); + + // release memory + AudioStream_F32::release(envelope_block); + AudioStream_F32::release(gain_block); + } + + + void setDefaultValues(void) { + //set default values...taken from CHAPRO, GHA_Demo.c from "amplify()"...ignores given sample rate + //assumes that the sample rate has already been set!!!! + CHA_WDRC gha = {1.0f, // attack time (ms) + 50.0f, // release time (ms) + 24000.0f, // fs, sampling rate (Hz), THIS IS IGNORED! + 119.0f, // maxdB, maximum signal (dB SPL) + 0.0f, // tkgain, compression-start gain + 105.0f, // tk, compression-start kneepoint + 10.0f, // cr, compression ratio + 105.0f // bolt, broadband output limiting threshold + }; + setParams_from_CHA_WDRC(&gha); + } + + //set all of the parameters for the compressor using the CHA_WDRC structure + //assumes that the sample rate has already been set!!! + void setParams_from_CHA_WDRC(CHA_WDRC *gha) { + //configure the envelope calculator...assumes that the sample rate has already been set! + calcEnvelope.setAttackRelease_msec(gha->attack,gha->release); //these are in milliseconds + + //configure the compressor + calcGain.setParams_from_CHA_WDRC(gha); + } + + //set all of the user parameters for the compressor + //assumes that the sample rate has already been set!!! + void setParams(float attack_ms, float release_ms, float maxdB, float tkgain, float comp_ratio, float tk, float bolt) { + + //configure the envelope calculator...assumes that the sample rate has already been set! + calcEnvelope.setAttackRelease_msec(attack_ms,release_ms); + + //configure the WDRC gains + calcGain.setParams(maxdB, tkgain, comp_ratio, tk, bolt); + } + + void setSampleRate_Hz(const float _fs_Hz) { + //pass this data on to its components that care + given_sample_rate_Hz = _fs_Hz; + calcEnvelope.setSampleRate_Hz(_fs_Hz); + } + + float getCurrentLevel_dB(void) { return AudioCalcGainWDRC_F32::db2(calcEnvelope.getCurrentLevel()); } //this is 20*log10(abs(signal)) after the envelope smoothing + + AudioCalcEnvelope_F32 calcEnvelope; + AudioCalcGainWDRC_F32 calcGain; + + private: + audio_block_f32_t *inputQueueArray[1]; + float given_sample_rate_Hz; +}; + + +#endif + diff --git a/OpenAudio_ArduinoLibrary.h b/OpenAudio_ArduinoLibrary.h index 4799ceb..f9a5522 100644 --- a/OpenAudio_ArduinoLibrary.h +++ b/OpenAudio_ArduinoLibrary.h @@ -3,7 +3,9 @@ //include #include #include "AudioCalcEnvelope_F32.h" +#include "AudioCalcGainWDRC_F32.h" #include +#include "AudioEffectCompWDRC_F32.h" #include "AudioEffectEmpty_F32.h" #include #include diff --git a/keywords.txt b/keywords.txt index d04c4b0..33b1cbd 100644 --- a/keywords.txt +++ b/keywords.txt @@ -7,6 +7,8 @@ audio_block_f32_t KEYWORD1 AudioStream_F32 KEYWORD1 AudioConnection_F32 KEYWORD1 +AudioCalcGainWDRC_F32 KEYWORD1 + AudioControlTLV320AIC3206 KEYWORD1 inputSelect KEYWORD2 setMicBias KEYWORD2 @@ -20,6 +22,8 @@ micBiasEnable KEYWORD2 AudioConvert_I16toF32 KEYWORD1 AudioConvert_F32toI16 KEYWORD1 +AudioEffectCompWDRC_F32 KEYWORD1 + AudioEffectGain_F32 KEYWORD1 setGain_dB KEYWORD2