|
| 1 | +/* |
| 2 | + * AudioCalcGainWDRC_F32 |
| 3 | + * |
| 4 | + * Created: Chip Audette, Feb 2017 |
| 5 | + * Purpose: This module calculates the gain needed for wide dynamic range compression. |
| 6 | + * Derived From: Core algorithm is from "WDRC_circuit" |
| 7 | + * WDRC_circuit from CHAPRO from BTNRC: https://github.com/BTNRH/chapro |
| 8 | + * As of Feb 2017, CHAPRO license is listed as "Creative Commons?" |
| 9 | + * |
| 10 | + * This processes a single stream fo audio data (ie, it is mono) |
| 11 | + * |
| 12 | + * MIT License. use at your own risk. |
| 13 | +*/ |
| 14 | + |
| 15 | +#ifndef _AudioCalcGainWDRC_F32_h |
| 16 | +#define _AudioCalcGainWDRC_F32_h |
| 17 | + |
| 18 | +#include <arm_math.h> //ARM DSP extensions. for speed! |
| 19 | +#include <AudioStream_F32.h> |
| 20 | + |
| 21 | +typedef struct { |
| 22 | + float attack; // attack time (ms), unused in this class |
| 23 | + float release; // release time (ms), unused in this class |
| 24 | + float fs; // sampling rate (Hz), set through other means in this class |
| 25 | + float maxdB; // maximum signal (dB SPL)...I think this is the SPL corresponding to signal with rms of 1.0 |
| 26 | + float tkgain; // compression-start gain |
| 27 | + float tk; // compression-start kneepoint |
| 28 | + float cr; // compression ratio |
| 29 | + float bolt; // broadband output limiting threshold |
| 30 | +} CHA_WDRC; |
| 31 | + |
| 32 | + |
| 33 | +class AudioCalcGainWDRC_F32 : public AudioStream_F32 |
| 34 | +{ |
| 35 | + //GUI: inputs:1, outputs:1 //this line used for automatic generation of GUI node |
| 36 | + //GUI: shortName:calc_WDRCGain |
| 37 | + public: |
| 38 | + //default constructor |
| 39 | + AudioCalcGainWDRC_F32(void) : AudioStream_F32(1, inputQueueArray_f32) { setDefaultValues(); }; |
| 40 | + |
| 41 | + //here's the method that does all the work |
| 42 | + void update(void) { |
| 43 | + |
| 44 | + //get the input audio data block |
| 45 | + audio_block_f32_t *in_block = AudioStream_F32::receiveReadOnly_f32(); // must be the envelope! |
| 46 | + if (!in_block) return; |
| 47 | + |
| 48 | + //prepare an output data block |
| 49 | + audio_block_f32_t *out_block = AudioStream_F32::allocate_f32(); |
| 50 | + if (!out_block) return; |
| 51 | + |
| 52 | + // //////////////////////add your processing here! |
| 53 | + calcGainFromEnvelope(in_block->data, out_block->data, in_block->length); |
| 54 | + out_block->length = in_block->length; out_block->fs_Hz = in_block->fs_Hz; |
| 55 | + |
| 56 | + //transmit the block and be done |
| 57 | + AudioStream_F32::transmit(out_block); |
| 58 | + AudioStream_F32::release(out_block); |
| 59 | + AudioStream_F32::release(in_block); |
| 60 | + |
| 61 | + } |
| 62 | + |
| 63 | + void calcGainFromEnvelope(float *env, float *gain_out, const int n) { |
| 64 | + //env = input, signal envelope (not the envelope of the power, but the envelope of the signal itslef) |
| 65 | + //gain = output, the gain in natural units (not power, not dB) |
| 66 | + //n = input, number of samples to process in each vector |
| 67 | + |
| 68 | + //prepare intermediate data block |
| 69 | + audio_block_f32_t *env_dB_block = AudioStream_F32::allocate_f32(); |
| 70 | + if (!env_dB_block) return; |
| 71 | + |
| 72 | + //convert to dB |
| 73 | + for (int k=0; k < n; k++) env_dB_block->data[k] = maxdB + db2(env[k]); //maxdb in the private section |
| 74 | + |
| 75 | + // apply wide-dynamic range compression |
| 76 | + WDRC_circuit_gain(env_dB_block->data, gain_out, n, tkgn, tk, cr, bolt); |
| 77 | + AudioStream_F32::release(env_dB_block); |
| 78 | + } |
| 79 | + |
| 80 | + //original call to WDRC_circuit |
| 81 | + //void WDRC_circuit(float *x, float *y, float *pdb, int n, float tkgn, float tk, float cr, float bolt) |
| 82 | + //void WDRC_circuit(float *orig_signal, float *signal_out, float *env_dB, int n, float tkgn, float tk, float cr, float bolt) |
| 83 | + //modified to output the gain instead of the fully processed signal |
| 84 | + void WDRC_circuit_gain(float *env_dB, float *gain_out, const int n, |
| 85 | + const float tkgn, const float tk, const float cr, const float bolt) { |
| 86 | + |
| 87 | + float gdb, tkgo, pblt; |
| 88 | + int k; |
| 89 | + float *pdb = env_dB; //just rename it to keep the code below unchanged |
| 90 | + float tk_tmp = tk; |
| 91 | + |
| 92 | + if ((tk_tmp + tkgn) > bolt) { |
| 93 | + tk_tmp = bolt - tkgn; |
| 94 | + } |
| 95 | + tkgo = tkgn + tk_tmp * (1.0f - 1.0f / cr); |
| 96 | + pblt = cr * (bolt - tkgo); |
| 97 | + const float cr_const = ((1.0f / cr) - 1.0f); |
| 98 | + for (k = 0; k < n; k++) { |
| 99 | + if ((pdb[k] < tk_tmp) && (cr >= 1.0f)) { |
| 100 | + gdb = tkgn; |
| 101 | + } else if (pdb[k] > pblt) { |
| 102 | + gdb = bolt + ((pdb[k] - pblt) / 10.0f) - pdb[k]; |
| 103 | + } else { |
| 104 | + gdb = cr_const * pdb[k] + tkgo; |
| 105 | + } |
| 106 | + gain_out[k] = undb2(gdb); |
| 107 | + //y[k] = x[k] * undb2(gdb); //apply the gain |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + void setDefaultValues(void) { |
| 112 | + CHA_WDRC gha = {1.0f, // attack time (ms), IGNORED HERE |
| 113 | + 50.0f, // release time (ms), IGNORED HERE |
| 114 | + 24000.0f, // fs, sampling rate (Hz), IGNORED HERE |
| 115 | + 119.0f, // maxdB, maximum signal (dB SPL) |
| 116 | + 0.0f, // tkgain, compression-start gain |
| 117 | + 105.0f, // tk, compression-start kneepoint |
| 118 | + 10.0f, // cr, compression ratio |
| 119 | + 105.0f // bolt, broadband output limiting threshold |
| 120 | + }; |
| 121 | + //setParams(gha.maxdB, gha.tkgain, gha.cr, gha.tk, gha.bolt); //also sets calcEnvelope |
| 122 | + setParams_from_CHA_WDRC(&gha); |
| 123 | + } |
| 124 | + void setParams_from_CHA_WDRC(CHA_WDRC *gha) { |
| 125 | + setParams(gha->maxdB, gha->tkgain, gha->cr, gha->tk, gha->bolt); //also sets calcEnvelope |
| 126 | + } |
| 127 | + void setParams(float _maxdB, float _tkgain, float _cr, float _tk, float _bolt) { |
| 128 | + maxdB = _maxdB; |
| 129 | + tkgn = _tkgain; |
| 130 | + tk = _tk; |
| 131 | + cr = _cr; |
| 132 | + bolt = _bolt; |
| 133 | + } |
| 134 | + |
| 135 | + static float undb2(const float &x) { return expf(0.11512925464970228420089957273422f*x); } //faster: exp(log(10.0f)*x/20); this is exact |
| 136 | + static float db2(const float &x) { return 6.020599913279623f*log2f_approx(x); } //faster: 20*log2_approx(x)/log2(10); this is approximate |
| 137 | + |
| 138 | + /* ---------------------------------------------------------------------- |
| 139 | + ** Fast approximation to the log2() function. It uses a two step |
| 140 | + ** process. First, it decomposes the floating-point number into |
| 141 | + ** a fractional component F and an exponent E. The fraction component |
| 142 | + ** is used in a polynomial approximation and then the exponent added |
| 143 | + ** to the result. A 3rd order polynomial is used and the result |
| 144 | + ** when computing db20() is accurate to 7.984884e-003 dB. |
| 145 | + ** ------------------------------------------------------------------- */ |
| 146 | + //https://community.arm.com/tools/f/discussions/4292/cmsis-dsp-new-functionality-proposal/22621#22621 |
| 147 | + static float log2f_approx(float X) { |
| 148 | + //float *C = &log2f_approx_coeff[0]; |
| 149 | + float Y; |
| 150 | + float F; |
| 151 | + int E; |
| 152 | + |
| 153 | + // This is the approximation to log2() |
| 154 | + F = frexpf(fabsf(X), &E); |
| 155 | + // Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E; |
| 156 | + Y = 1.23149591368684f; //C[0] |
| 157 | + Y *= F; |
| 158 | + Y += -4.11852516267426f; //C[1] |
| 159 | + Y *= F; |
| 160 | + Y += 6.02197014179219f; //C[2] |
| 161 | + Y *= F; |
| 162 | + Y += -3.13396450166353f; //C[3] |
| 163 | + Y += E; |
| 164 | + |
| 165 | + return(Y); |
| 166 | + } |
| 167 | + |
| 168 | + private: |
| 169 | + audio_block_f32_t *inputQueueArray_f32[1]; //memory pointer for the input to this module |
| 170 | + float maxdB, tkgn, tk, cr, bolt; |
| 171 | +}; |
| 172 | + |
| 173 | +#endif |
0 commit comments