View Single Post
Old 10-01-2019, 01:24 PM   #57
ErBird
Human being with feelings
 
Join Date: Jan 2017
Location: Los Angeles
Posts: 1,161
Default Update

That 354 comparison and clamping never sat well with me so I recently sought to eliminate it. Above about 60dB the output quickly degraded into aliasing just as bad as a straight tanh.

Stripping down F0, it's clear the loss of precision occurs in e^(-2x) for extreme -x values. For x -> +infinity there is no problem since e^(-2x) -> 0.



F0 is an even function so F0(x) can be changed to F0(abs(x)). This allows you to eliminate the comparison and maintains anti-aliasing up to infinite gain.

Here's the demonstration. Regular tanh is first, then the previous code, then the new one.



The only remaining problem is dealing with the 1/2 sample delay and inherent low-pass effect.

Code:
desc:Tanh AA

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,1,1>Antialias?

@slider

  gain        = 10^(slider1/20);
  ceiling     = 10^(-slider2/20);
  inv_ceiling = 10^(slider2/20);

@sample

  function F0(x)
  (
    abs(x) - log(2/(1 + exp(-2*abs(x))));
  );

  function tanh(x)
  (
    2/(1 + exp(-2*x)) - 1;
  );
  
  function antialiased_tanh_rect(x)
  local(eps, F0_xn)
  instance(xnm1, F0_xnm1, out)
  (
    F0_xn   = F0(x);
    eps     = 0.0000000001;
    out     = eps < abs(x - xnm1) ? (F0_xn - F0_xnm1)/(x - xnm1) : tanh(0.5*(x+xnm1));
    F0_xnm1 = F0_xn;
    xnm1    = x;
    out;
  );

  spl0 *= gain;
  spl1 *= gain;
  
  spl0 *= ceiling;
  spl1 *= ceiling;
  
  slider3 ? (
    spl0 = ch0.antialiased_tanh_rect(spl0);
    spl1 = ch1.antialiased_tanh_rect(spl1);
  ) : (
    spl0 = tanh(spl0);
    spl1 = tanh(spl1);
  );
  
  spl0 *= inv_ceiling;
  spl1 *= inv_ceiling;

Last edited by ErBird; 10-01-2019 at 01:32 PM.
ErBird is offline   Reply With Quote