├── 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 | 
44 |
45 | 
46 |
47 | 
48 |
49 | 
50 |
51 | 
52 |
53 |
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 |
--------------------------------------------------------------------------------