parent
d96047b80a
commit
3b0f7c37dd
@ -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_math.h> //ARM DSP extensions. for speed! |
||||
#include <AudioStream_F32.h> |
||||
|
||||
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 |
@ -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 <Arduino.h> |
||||
#include <AudioStream_F32.h> |
||||
#include <arm_math.h> |
||||
#include <AudioCalcEnvelope_F32.h> |
||||
#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 |
||||
|
Loading…
Reference in new issue