// // AudioLimiter.cpp // libraries/audio/src // // Created by Ken Cooke on 2/11/15. // Copyright 2016 High Fidelity, Inc. // #include "AudioLimiter.h" #include #include "AudioDynamics.h" // // Limiter (common) // class LimiterImpl { protected: static const int NARC = 64; int32_t _holdTable[NARC]; int32_t _releaseTable[NARC]; int32_t _rmsAttack = 0x7fffffff; int32_t _rmsRelease = 0x7fffffff; int32_t _arcRelease = 0x7fffffff; int32_t _threshold = 0; int32_t _attn = 0; int32_t _rms = 0; int32_t _arc = 0; int _sampleRate; float _outGain = 0.0f; public: LimiterImpl(int sampleRate); virtual ~LimiterImpl() {} void setThreshold(float threshold); void setRelease(float release); int32_t envelope(int32_t attn); virtual void process(float* input, int16_t* output, int numFrames) = 0; }; LimiterImpl::LimiterImpl(int sampleRate) { sampleRate = MAX(sampleRate, 8000); sampleRate = MIN(sampleRate, 96000); _sampleRate = sampleRate; // defaults setThreshold(0.0f); setRelease(250.0f); } // // Set the limiter threshold (dB) // Brickwall limiting will begin when the signal exceeds the threshold. // Makeup gain is applied, to reach but never exceed the output ceiling. // void LimiterImpl::setThreshold(float threshold) { const double OUT_CEILING = -0.3; // cannot be 0.0, due to dither const double Q31_TO_Q15 = 32768 / 2147483648.0; // limiter threshold = -48dB to 0dB threshold = MAX(threshold, -48.0f); threshold = MIN(threshold, 0.0f); // limiter threshold in log2 domain _threshold = (int32_t)(-(double)threshold * DB_TO_LOG2 * (1 << LOG2_FRACBITS)); _threshold += LOG2_BIAS + EXP2_BIAS; _threshold += LOG2_HEADROOM << LOG2_FRACBITS; // makeup gain and conversion to 16-bit _outGain = (float)(dBToGain(OUT_CEILING - (double)threshold) * Q31_TO_Q15); } // // Set the limiter release time (milliseconds) // This is a base value that scales the adaptive hold and release algorithms. // void LimiterImpl::setRelease(float release) { const double MAXHOLD = 0.100; // max hold = 100ms const double MINREL = 0.025; // min release = 0.025 * release const int NHOLD = 16; // adaptive hold to adaptive release transition // limiter release = 50 to 5000ms release = MAX(release, 50.0f); release = MIN(release, 5000.0f); int32_t maxRelease = msToTc((double)release, _sampleRate); _rmsAttack = msToTc(0.1 * (double)release, _sampleRate); _rmsRelease = maxRelease; // Compute ARC tables, working from low peak/rms to high peak/rms. // // At low peak/rms, release = max and hold is progressive to max // At high peak/rms, hold = 0 and release is progressive to min double x = MAXHOLD * _sampleRate; double xstep = x / NHOLD; // 1.0 to 1.0/NHOLD int i = 0; for (; i < NHOLD; i++) { // max release _releaseTable[i] = maxRelease; // progressive hold _holdTable[i] = (int32_t)((maxRelease - 0x7fffffff) / x); _holdTable[i] = MIN(_holdTable[i], -1); // prevent 0 on long releases x -= xstep; x = MAX(x, 1.0); } x = release; xstep = x * (1.0-MINREL) / (NARC-NHOLD-1); // 1.0 to MINREL for (; i < NARC; i++) { // progressive release _releaseTable[i] = msToTc(x, _sampleRate); // min hold _holdTable[i] = (_releaseTable[i] - 0x7fffffff); // 1 sample x -= xstep; } } // // Limiter envelope processing // zero attack, adaptive hold and release // int32_t LimiterImpl::envelope(int32_t attn) { // table of (1/attn) for 1dB to 6dB, rounded to prevent overflow static const int16_t invTable[64] = { 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x6000, 0x5d17, 0x5555, 0x4ec4, 0x4924, 0x4444, 0x4000, 0x3c3c, 0x38e3, 0x35e5, 0x3333, 0x30c3, 0x2e8b, 0x2c85, 0x2aaa, 0x28f5, 0x2762, 0x25ed, 0x2492, 0x234f, 0x2222, 0x2108, 0x2000, 0x1f07, 0x1e1e, 0x1d41, 0x1c71, 0x1bac, 0x1af2, 0x1a41, 0x1999, 0x18f9, 0x1861, 0x17d0, 0x1745, 0x16c1, 0x1642, 0x15c9, 0x1555, 0x14e5, 0x147a, 0x1414, 0x13b1, 0x1352, 0x12f6, 0x129e, 0x1249, 0x11f7, 0x11a7, 0x115b, 0x1111, 0x10c9, 0x1084, 0x1041, 0x1000, }; if (attn < _attn) { // RELEASE // update release before use, to implement hold = 0 _arcRelease += _holdTable[_arc]; // update progressive hold _arcRelease = MAX(_arcRelease, _releaseTable[_arc]); // saturate at final value attn += MULQ31((_attn - attn), _arcRelease); // apply release } else { // ATTACK // update ARC with normalized peak/rms // // arc = (attn-rms)*6/1 for attn < 1dB // arc = (attn-rms)*6/attn for attn = 1dB to 6dB // arc = (attn-rms)*6/6 for attn > 6dB size_t bits = MIN(attn >> 20, 0x3f); // saturate 1/attn at 6dB _arc = MAX(attn - _rms, 0); // peak/rms = (attn-rms) _arc = MULHI(_arc, invTable[bits]); // normalized peak/rms = (attn-rms)/attn _arc = MIN(_arc, NARC - 1); // saturate at 6dB _arcRelease = 0x7fffffff; // reset release } _attn = attn; // Update the RMS estimate after release is applied. // The feedback loop with adaptive hold will damp any sustained modulation distortion. int32_t tc = (attn > _rms) ? _rmsAttack : _rmsRelease; _rms = attn + MULQ31((_rms - attn), tc); return attn; } // // Limiter (mono) // template class LimiterMono : public LimiterImpl { MinFilter _filter; MonoDelay _delay; public: LimiterMono(int sampleRate) : LimiterImpl(sampleRate) {} void process(float* input, int16_t* output, int numFrames) override; }; template void LimiterMono::process(float* input, int16_t* output, int numFrames) { for (int n = 0; n < numFrames; n++) { // peak detect and convert to log2 domain int32_t peak = peaklog2(&input[n]); // compute limiter attenuation int32_t attn = MAX(_threshold - peak, 0); // apply envelope attn = envelope(attn); // convert from log2 domain attn = fixexp2(attn); // lowpass filter attn = _filter.process(attn); float gain = attn * _outGain; // delay audio float x = input[n]; _delay.process(x); // apply gain x *= gain; // apply dither x += dither(); // store 16-bit output output[n] = (int16_t)floatToInt(x); } } // // Limiter (stereo) // template class LimiterStereo : public LimiterImpl { MinFilter _filter; StereoDelay _delay; public: LimiterStereo(int sampleRate) : LimiterImpl(sampleRate) {} // interleaved stereo input/output void process(float* input, int16_t* output, int numFrames) override; }; template void LimiterStereo::process(float* input, int16_t* output, int numFrames) { for (int n = 0; n < numFrames; n++) { // peak detect and convert to log2 domain int32_t peak = peaklog2(&input[2*n+0], &input[2*n+1]); // compute limiter attenuation int32_t attn = MAX(_threshold - peak, 0); // apply envelope attn = envelope(attn); // convert from log2 domain attn = fixexp2(attn); // lowpass filter attn = _filter.process(attn); float gain = attn * _outGain; // delay audio float x0 = input[2*n+0]; float x1 = input[2*n+1]; _delay.process(x0, x1); // apply gain x0 *= gain; x1 *= gain; // apply dither float d = dither(); x0 += d; x1 += d; // store 16-bit output output[2*n+0] = (int16_t)floatToInt(x0); output[2*n+1] = (int16_t)floatToInt(x1); } } // // Limiter (quad) // template class LimiterQuad : public LimiterImpl { MinFilter _filter; QuadDelay _delay; public: LimiterQuad(int sampleRate) : LimiterImpl(sampleRate) {} // interleaved quad input/output void process(float* input, int16_t* output, int numFrames) override; }; template void LimiterQuad::process(float* input, int16_t* output, int numFrames) { for (int n = 0; n < numFrames; n++) { // peak detect and convert to log2 domain int32_t peak = peaklog2(&input[4*n+0], &input[4*n+1], &input[4*n+2], &input[4*n+3]); // compute limiter attenuation int32_t attn = MAX(_threshold - peak, 0); // apply envelope attn = envelope(attn); // convert from log2 domain attn = fixexp2(attn); // lowpass filter attn = _filter.process(attn); float gain = attn * _outGain; // delay audio float x0 = input[4*n+0]; float x1 = input[4*n+1]; float x2 = input[4*n+2]; float x3 = input[4*n+3]; _delay.process(x0, x1, x2, x3); // apply gain x0 *= gain; x1 *= gain; x2 *= gain; x3 *= gain; // apply dither float d = dither(); x0 += d; x1 += d; x2 += d; x3 += d; // store 16-bit output output[4*n+0] = (int16_t)floatToInt(x0); output[4*n+1] = (int16_t)floatToInt(x1); output[4*n+2] = (int16_t)floatToInt(x2); output[4*n+3] = (int16_t)floatToInt(x3); } } // // Public API // AudioLimiter::AudioLimiter(int sampleRate, int numChannels) { if (numChannels == 1) { // ~1.5ms lookahead for all rates if (sampleRate < 16000) { _impl = new LimiterMono<16>(sampleRate); } else if (sampleRate < 32000) { _impl = new LimiterMono<32>(sampleRate); } else if (sampleRate < 64000) { _impl = new LimiterMono<64>(sampleRate); } else { _impl = new LimiterMono<128>(sampleRate); } } else if (numChannels == 2) { // ~1.5ms lookahead for all rates if (sampleRate < 16000) { _impl = new LimiterStereo<16>(sampleRate); } else if (sampleRate < 32000) { _impl = new LimiterStereo<32>(sampleRate); } else if (sampleRate < 64000) { _impl = new LimiterStereo<64>(sampleRate); } else { _impl = new LimiterStereo<128>(sampleRate); } } else if (numChannels == 4) { // ~1.5ms lookahead for all rates if (sampleRate < 16000) { _impl = new LimiterQuad<16>(sampleRate); } else if (sampleRate < 32000) { _impl = new LimiterQuad<32>(sampleRate); } else if (sampleRate < 64000) { _impl = new LimiterQuad<64>(sampleRate); } else { _impl = new LimiterQuad<128>(sampleRate); } } else { assert(0); // unsupported } } AudioLimiter::~AudioLimiter() { delete _impl; } void AudioLimiter::render(float* input, int16_t* output, int numFrames) { _impl->process(input, output, numFrames); } void AudioLimiter::setThreshold(float threshold) { _impl->setThreshold(threshold); } void AudioLimiter::setRelease(float release) { _impl->setRelease(release); }