Quote:
Originally Posted by sai'ke
Thanks for sharing it by the way!

No problem. You started the AA ball rolling after all. I wanted to make sure you had the fix.
Anyway, I tried for ages to no avail to get the linear kernel working for tanh. I didn't resort to a lookup table, but tried, with no concern for CPU, to use the power series for Li2 with some abs() and sign() applied accordingly. Alas, it doesn't work.
I did, however, get it working for x/sqrt(1+x^2). Not as nice for audio as tanh, but a good proof that the linear kernel can work. In the end, I think the improvement is not enough to warrant the extra complexity over the rect version. Also it seems more finicky and needs a much larger epsilon and sometimes clicks and pops when fed an already saturated signal. Because of that, I think the latest tanh with AA is the best possible implementation right now.
Code:
desc:Algebraic Saturation with AntiAliasing
Author: Erich M. Burg
Adapted from "Tanh Saturation with anti aliasing" JS by Joep Vanlier (Sai'ke)
Uses technique from: "REDUCING THE ALIASING OF NONLINEAR WAVESHAPING USING CONTINUOUSTIME CONVOLUTION", Parker et al,
Proceedings of the 19th International Conference on Digital Audio Effects (DAFx16), Brno, Czech Republic, September 5–9, 2016
in_pin:left input
in_pin:right input
out_pin:left output
out_pin:right output
slider1:0<6,24,1>Gain (dB)
slider2:0<18,0,1>Ceiling (dB)
slider3:1<0,2,1{No AA,Rectangular Kernel,Linear Kernel}>Mode
@slider
gain = 10^(slider1/20);
ceiling = 10^(slider2/20);
inv_ceiling = 10^(slider2/20);
@sample
function f(x)
(
x/sqrt(1 + x^2);
);
function F0(x)
(
sqrt(1 + x^2);
);
function F1(x)
(
0.5*(x*sqrt(1 + x^2)  log(sqrt(1 + x^2) + x));
);
function antialiased_algebraic_sat_rect(x)
local(eps, F0_xn, out)
instance(xnm1, F0_xnm1)
(
F0_xn = F0(x);
eps = 0.0000000001;
out = eps < abs(x  xnm1) ? (F0_xn  F0_xnm1)/(x  xnm1) : f(0.5*(x+xnm1));
F0_xnm1 = F0_xn;
xnm1 = x;
out;
);
function antialiased_algebraic_sat_linear(x)
local(eps, F0_xn, F1_xn, out1, out2, out)
instance(xnm1, xnm2, F0_xnm1, F0_xnm2, F1_xnm1, F1_xnm2)
(
F0_xn = F0(x);
F1_xn = sign(x)*F1(abs(x));
eps = 0.0001;
out1 = eps < abs(x  xnm1) ?
(
(x*(F0_xn  F0_xnm1)  (F1_xn  F1_xnm1))/(x  xnm1)^2;
):(
0.5*f((x + 2*xnm1)/3);
);
out2 = eps < abs(xnm1  xnm2) ?
(
(xnm2*(F0_xnm2  F0_xnm1)  (F1_xnm2  F1_xnm1))/(xnm2  xnm1)^2;
):(
0.5*f((xnm2 + 2*xnm1)/3);
);
out = (out1 + out2);
F0_xnm2 = F0_xnm1;
F0_xnm1 = F0_xn;
F1_xnm2 = F1_xnm1;
F1_xnm1 = F1_xn;
xnm2 = xnm1;
xnm1 = x;
out;
);
spl0 *= gain;
spl1 *= gain;
spl0 *= ceiling;
spl1 *= ceiling;
slider3 == 0 ?
(
spl0 = spl0/sqrt(1+spl0^2);
spl1 = spl1/sqrt(1+spl1^2);
):
slider3 == 1 ?
(
spl0 = ch0.antialiased_algebraic_sat_rect(spl0);
spl1 = ch1.antialiased_algebraic_sat_rect(spl1);
):
slider3 == 2 ?
(
spl0 = ch0.antialiased_algebraic_sat_linear(spl0);
spl1 = ch1.antialiased_algebraic_sat_linear(spl1);
);
spl0 *= inv_ceiling;
spl1 *= inv_ceiling;