desc:sault :: 4x oversampled peak limiter

// by Sault
// 2017-01-31


slider1:0<-15,15,0.1>Boost (dB)
slider2:-0.3<-6,0,0.1>Ceiling (dB)
slider3:-3<-15,0,0.1>Threshold (dB)
slider4:0<-15,15,0.1>Volume (dB)
slider7:0<0,1,0.01>Saturation
slider11:0<0,6>Gain Reduction (dB)

import st-oversampler.jsfx-inc

@init

pdc_bot_ch = 0; pdc_top_ch = 2;
pdc_delay = 31;

function int(x) ( x|0 );
dbc = 20/log(10);
function db2ratio(d) ( 10^(d/20); );
function ratio2db(r) ( log(abs(r))*dbc; );


function saturate(in) ( (in*foo - ((in*foo)^4)*0.25)*div; );

function spline2(mu,dv1,dv2) ( mu*dv1 + mu*mu*0.5*(dv2-dv1); );

max_ratio = 0.01; // 1:100

display_coeff = exp(-3/(srate * 0.4 * 8)); // 400 ms, called 8x per sample

function update_diff(val) (
    slider11 *= display_coeff;
    slider11 = max(slider11,val);
);

function process(in)
 local(spl,out)
(
  slider7 ? ( in = saturate(abs(in))*sign(in) );
  spl = ratio2db(in);
  spl >= ceil ? (
    out = (thresh+knee*0.5) + (spl - (thresh+knee*0.5)) * max_ratio;
    ) :
  spl <= thresh ? (
    out = spl;
    ) : ( 		// (spl > thresh && spl < ceil)
    out = thresh + spline2((spl - thresh)/knee,1,max_ratio)*knee;
    );
  update_diff(abs(out - spl));
  db2ratio(out)*sign(in);
);



@slider

ceil = slider2-0.05;
slider3 >= ceil ? ( slider3 = slider2 - 0.05; sliderchange(); );

boost = db2ratio(slider1);

thresh = slider3;
volume = db2ratio(slider4);
knee = ceil - thresh;

foo = slider7*0.99 + 0.01;
div = 1/(foo - (foo^4)*0.25);

@sample

s0 = spl0 * boost;
s1 = spl1 * boost;

os0.os_up4(s0);
os1.os_up4(s1);

os0.y3 = process(os0.y3);
os0.y2 = process(os0.y2);
os0.y1 = process(os0.y1);
os0.y0 = process(os0.y0);
s0 = os0.os_down4();

os1.y3 = process(os1.y3);
os1.y2 = process(os1.y2);
os1.y1 = process(os1.y1);
os1.y0 = process(os1.y0);
s1 = os1.os_down4();

spl0 = s0 * volume;
spl1 = s1 * volume;