├── LICENSE ├── README.md └── limiterStereo.dsp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dario Sanfilippo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yet another look-ahead limiter. 2 | 3 | The novel aspect of this limiter is that it 4 | uses N cascaded one-pole filters for amplitude profiling, which improves 5 | smoothness in the first to N-1 order derivatives and reduces total 6 | harmonic distortion. This design uses four cascaded one-pole lowpass filters, 7 | following the cut-off correction formula (8.9) found in [Zavalishin 2012]. 8 | 9 | IIR filters produce exponential curves, which are perceptually more natural. 10 | However, an IIR system, unlike a FIR moving average, for example, will never 11 | completely reach the target value and is thus not suitable for perfect 12 | brick-wall limiting. With reasonable settings, though, the limiter 13 | produces an overshooting of only .002 dB with signals boosted by 120 dBs, 14 | which I find negligible for most musical applications. 15 | 16 | The limiter introduces a delay that is equal to the attack time times 17 | the samplerate samples. 18 | 19 | For better peak detection, N peak-hold sections with 1/N hold time can be 20 | cascaded, so that secondary peaks that are at least 1/N of the hold time 21 | plus one sample apart from the primary peak can still be detected. 22 | The secondary peaks that are closer to the primary peaks can be taken care 23 | of by the release section. Thanks to Geraint "Signalsmith" Luff and our 24 | discussions on the topic, which inspired this solution. 25 | 26 | The other parameters are the hold time and the release time, for the 27 | amplitude profiling characteristics, as well as a bypass button, a pre-gain, 28 | and a ceiling threshold. 29 | 30 | The limiter is stereo-linked, hence the relative left-right amplitude 31 | difference is preserved. 32 | 33 | Future developements on this work may include an adaptive mechanism for 34 | self-adjusting release times to reduce the 'pumping' effect, and a deployment 35 | in multi-band processing for loudness maximisation. 36 | 37 | Note on loudness: while I am aware of the loudness war in the pop music 38 | context, which is today a rather nostalgic thought, loudness itself has been 39 | explored as a creative means beautifully, for example, in the work of 40 | Phil Niblock, which is a reason why I am interested in exploring these 41 | techniques. 42 | 43 | ![Figure_0](https://user-images.githubusercontent.com/30258280/153643622-e3e698c0-cd32-4c5a-96f5-74e3ef4928ca.png) 44 | 45 | ![Figure_1](https://user-images.githubusercontent.com/30258280/153643639-85be6520-9a9b-4788-b1be-6ee7819313fd.png) 46 | 47 | ![desmos-graph](https://user-images.githubusercontent.com/30258280/154347305-ec464f50-65b3-44f4-976f-823baba92054.png) 48 | 49 | ![peakdetectionedgecase0](https://user-images.githubusercontent.com/30258280/153750561-a6b7ce92-aaf1-4927-86d5-9d130abbacbe.png) 50 | 51 | ![peakdetectionedgecase1](https://user-images.githubusercontent.com/30258280/153750565-11f07762-309d-4ea3-9593-a1a2fd97ec58.png) 52 | 53 | image 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /limiterStereo.dsp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | ********** Look-ahead IIR stereo limiter **************************** 3 | ******************************************************************************** 4 | * 5 | * Yet another look-ahead limiter. 6 | * 7 | * The novel aspect of this limiter is that it 8 | * uses N cascaded one-pole filters for amplitude profiling, which improves 9 | * smoothness in the first to N-1 order derivatives and reduces total 10 | * harmonic distortion. This design uses four cascaded one-pole lowpass filters, 11 | * following the cut-off correction formula (8.9) found in [Zavalishin 2012]. 12 | * 13 | * IIR filters produce exponential curves, which are perceptually more natural. 14 | * However, an IIR system, unlike a FIR moving average, for example, will never 15 | * completely reach the target value and is thus not suitable for perfect 16 | * brick-wall limiting. With reasonable settings, though, the limiter 17 | * produces an overshooting of only .002 dB with signals boosted by 120 dBs, 18 | * which I find negligible for most musical applications. 19 | * 20 | * The limiter introduces a delay that is equal to the attack time times 21 | * the samplerate samples. 22 | * 23 | * For better peak detection, N peak-hold sections with 1/N hold time can be 24 | * cascaded, so that secondary peaks that are at least 1/N of the hold time 25 | * plus one sample apart from the primary peak can still be detected. 26 | * The secondary peaks that are closer to the primary peaks can be taken care 27 | * of by the release section. Thanks to Geraint "Signalsmith" Luff and our 28 | * discussions on the topic, which inspired this solution. 29 | * 30 | * The other parameters are the hold time and the release time, for the 31 | * amplitude profiling characteristics, as well as a bypass button, a pre-gain, 32 | * and a ceiling threshold. 33 | * 34 | * The limiter is stereo-linked, hence the relative left-right amplitude 35 | * difference is preserved. 36 | * 37 | * Future developements on this work may include an adaptive mechanism for 38 | * self-adjusting release times to reduce the 'pumping' effect, and a deployment 39 | * in multi-band processing for loudness maximisation. 40 | * 41 | * Note on loudness: while I am aware of the loudness war in the pop music 42 | * context, which is today a rather nostalgic thought, loudness itself has been 43 | * explored as a creative means beautifully, for example, in the work of 44 | * Phil Niblock, which is a reason why I am interested in exploring these 45 | * techniques. 46 | * 47 | *******************************************************************************/ 48 | 49 | import("stdfaust.lib"); 50 | declare limiterStereo author "Dario Sanfilippo"; 51 | declare limiterStereo copyright 52 | "Copyright (C) 2022 Dario Sanfilippo "; 53 | declare version "0.4.0"; 54 | declare limiterStereo license "MIT-style STK-4.3 license"; 55 | sdelay(maxDelay, interpolationLen, delayLen, x) = 56 | loop ~ si.bus(4) : (! , ! , ! , ! , _) 57 | with { 58 | loop(lineState, incrState, lowerDelayState, upperDelayState) = 59 | line , incr , lowerDelay , upperDelay , output 60 | with { 61 | lowerReach = lineState == 0; 62 | upperReach = lineState == 1; 63 | lowerDelayChange = delayLen != lowerDelayState; 64 | upperDelayChange = delayLen != upperDelayState; 65 | incr = ba.if( upperReach & upperDelayChange, 66 | -1.0 / interpolationLen, 67 | ba.if( lowerReach & lowerDelayChange), 68 | 1.0 / interpolationLen, 69 | incrState); 70 | line = max(.0, min(1.0, lineState + incr)); 71 | lowerDelay = ba.if(upperReach, delayLen, lowerDelayState); 72 | upperDelay = ba.if(lowerReach, delayLen, upperDelayState); 73 | lowerDelayline = de.delay(maxDelay, lowerDelay, x) * (1.0 - line); 74 | upperDelayline = de.delay(maxDelay, upperDelay, x) * line; 75 | output = lowerDelayline + upperDelayline; 76 | }; 77 | }; 78 | peakHold(t, x) = loop ~ si.bus(2) : ! , _ 79 | with { 80 | loop(timerState, outState) = timer , output 81 | with { 82 | isNewPeak = abs(x) >= outState; 83 | isTimeOut = timerState >= rint(t * ma.SR); 84 | bypass = isNewPeak | isTimeOut; 85 | timer = (1 - bypass) * (timerState + 1); 86 | output = bypass * (abs(x) - outState) + outState; 87 | }; 88 | }; 89 | peakHoldCascade(N, holdTime, x) = x : seq(i, N, peakHold(holdTime / N)); 90 | smoother(N, att, rel, x) = loop ~ _ 91 | with { 92 | loop(fb) = coeff * fb + (1.0 - coeff) * x 93 | with { 94 | coeff = ba.if(x > fb, attCoeff, relCoeff); 95 | twoPiCT = 2.0 * ma.PI * cutoffCorrection * ma.T; 96 | attCoeff = exp(-twoPiCT / att); 97 | relCoeff = exp(-twoPiCT / rel); 98 | cutoffCorrection = 1.0 / sqrt(pow(2.0, 1.0 / N) - 1.0); 99 | }; 100 | }; 101 | smootherCascade(N, att, rel, x) = x : seq(i, N, smoother(N, att, rel)); 102 | gainAttenuation(th, att, hold, rel, x) = 103 | th / (max(th, peakHoldCascade(8, att + hold, x)) : 104 | smootherCascade(4, att, rel)); 105 | limiterStereo(xL_, xR_) = 106 | (xL_ * (bypass) + (1 - bypass) * xLDelayed * stereoAttenuationGain : 107 | peakDisplayL), 108 | (xR_ * (bypass) + (1 - bypass) * xRDelayed * stereoAttenuationGain : 109 | peakDisplayR) 110 | with { 111 | xL = xL_ * preGain; 112 | xR = xR_ * preGain; 113 | delay = rint((attack / 8) * ma.SR) * 8; 114 | xLDelayed = sdelay(.1 * ma.SR, delay, delay, xL); 115 | xRDelayed = sdelay(.1 * ma.SR, delay, delay, xR); 116 | stereoAttenuationGain = 117 | gainAttenuation(threshold, 118 | attack, 119 | hold, 120 | release, 121 | max(abs(xL), abs(xR))) : attenuationDisplay; 122 | horizontalGroup(group) = hgroup("Look-ahead IIR Stereo Limiter", group); 123 | peakGroup(group) = hgroup("Peaks", group); 124 | displayGroup(display) = horizontalGroup(vgroup("Display", display)); 125 | controlGroup(param) = horizontalGroup(vgroup("Control", param)); 126 | peakDisplayL(peak) = 127 | displayGroup(peakGroup(attach(peak, (peakHold(3, peak) : 128 | ba.linear2db : 129 | vbargraph( "[06]Left Peak (dB)[style:numerical]", 130 | -60, 131 | 60))))); 132 | peakDisplayR(peak) = 133 | displayGroup(peakGroup(attach(peak, (peakHold(3, peak) : 134 | ba.linear2db : 135 | vbargraph( "[07]Right Peak (dB)[style:numerical]", 136 | -60, 137 | 60))))); 138 | attenuationDisplay(attenuation) = 139 | displayGroup(attach(attenuation, attenuation : 140 | ba.linear2db : vbargraph("[09]Attenuation (dB)", -120, 0))); 141 | bypass = controlGroup(checkbox("[00]Bypass")) : si.smoo; 142 | preGain = controlGroup(ba.db2linear(hslider("[01]Pre Gain (dB)", 143 | 0, 144 | 0, 145 | 120, 146 | .001))) : si.smoo; 147 | threshold = controlGroup(ba.db2linear( hslider("[02]Threshold (dB)", 148 | 0, 149 | -60, 150 | 0, 151 | .001))) : si.smoo; 152 | attack = controlGroup(hslider( "[03]Attack (s)", 153 | .01, 154 | .001, 155 | .05, 156 | .001)) : si.smoo; 157 | hold = controlGroup(hslider("[04]Hold (s)", 158 | .05, 159 | .000, 160 | 1, 161 | .001)) : si.smoo; 162 | release = controlGroup(hslider( "[05]Release (s)", 163 | .15, 164 | .05, 165 | 1, 166 | .001)) : si.smoo; 167 | }; 168 | process = limiterStereo; 169 | --------------------------------------------------------------------------------