desc: Partially transient-preserving limiter

slider1:0<-30,0,0.1>Soft threshold (dB)
slider2:5<0,30,0.1>Extra transient threshold (dB)
slider3:200<5,500,1>Release time (ms)
slider4:1<0,1,1{no,yes}>Make-up gain
slider5:1<0,1,1{no,yes}>Hard-limit
slider6:0<-18,0,0.1>Hard-Limit to... (dB)
slider7:0<-24,24,0.1>Output (dB)

slider11:350<50,1000,50>--RMS window ms (user input)
slider12:-60<-63,-3,3>--RMS meter min dB (user input)
slider13:0<-60,3,0.01>PEAK in
slider14:0<-60,3,0.01>PEAK out
slider15:0<-60,3,0.01>--CURRENT dB in
slider16:0<-60,3,0.01>--CURRENT dB out
slider17:0<-60,3,0.01>RMS in
slider18:0<-60,3,0.01>RMS out
slider19:0<-60,3,0.01>--min dB in
slider20:0<-60,3,0.01>--min dB out
slider21:0<-60,3,0.01>--max dB in
slider22:0<-60,3,0.01>--max dB out
slider23:0<0,60,0.01>--range dB in
slider24:0<0,60,0.01>--range dB out

in_pin:L in
in_pin:R in
out_pin:L out
out_pin:R out

@init
lookahead = floor(srate/1000*5);
dBFac = 20/log(10);
curfac = 1;
cfpeak = 1;
w = 0;
base = 1;

log2dB = 8.6858896380650365530225783783321;
DB_MAX = 0.0;

GFX_RANGE_L = 0.05;
GFX_RANGE_R = 0.95;
GFX_RANGE_H = 0.23;
GFX_RANGE_Y = 0.15;

GFX_PEAK_W = 0.02;
GFX_PEAK_H = 0.03;
GFX_PEAK_Y = 0.07;
GFX_CUR_Y = 0.08;

GFX_SUM_W = 0.02;
GFX_SUM_H = 0.03;
GFX_SUM_Y = GFX_RANGE_Y+GFX_RANGE_H+0.04;

GFX_METER_MAJ_Y = 0.47;
GFX_METER_MIN_Y = 0.49;

prevGfxW = 0;
prevGfxH = 0;

densityBufN = 2048;
densityBufL = 0;
densityBufR = densityBufL + densityBufN;
bufL = densityBufR + densityBufN;
bufR = bufL + 131072;

winMS = slider11;
holdIdx = bufN = (0.001*winMS*srate)|0;
nSamples = playing = 0;
bufOK = bufIdx = initBuf = 0;
peakL = peakR = ampEnvL = ampEnvR = 0.0;
sumL = sumR = sum2L = sum2R = win2L = win2R = 0.0;
win2LMin = win2LMax = win2RMin = win2RMax = 0.0;
memset(densityBufL, 0, densityBufN);
memset(densityBufR, 0, densityBufN);
maxDensityL = maxDensityR = 0.0;

@slider
limitVal = 10^(slider1/20);
maxLimitVal = 10^(slider1/20 + slider2/20);
release = srate/1000*slider3;
peak = limitVal;
renorm = slider4;
renFac = 10^-(slider1/20 + 1.6/20);
hard = slider5;
hardval = 10^(slider6/20);
output = 10^(slider7/20);

winMS = slider11;
dbMin = slider12;

dbRange = DB_MAX - dbMin;

holdIdx = bufN = (0.001*winMS*srate)|0;
bufOK = bufIdx = initBuf = 0;
ampEnvCoeff = exp(-1.0/bufN);

nSamples = playing = 0;
bufOK = bufIdx = initBuf = 0;
peakL = peakR = ampEnvL = ampEnvR = 0.0;
sumL = sumR = sum2L = sum2R = win2L = win2R = 0.0;
win2LMin = win2LMax = win2RMin = win2RMax = 0.0;
memset(densityBufL, 0, densityBufN);
memset(densityBufR, 0, densityBufN);
maxDensityL = maxDensityR = 0.0;

@block
(playing && bufOK) ? (
slider13 = floor(log2dB*log(peakL)*100.0)/100.0;
slider14 = floor(log2dB*log(peakR)*100.0)/100.0;  
slider15 = floor(dbRMSL*100.0)/100.0;
slider16 = floor(dbRMSR*100.0)/100.0;
slider17 = floor(log2dB*log(sqrt(sum2L/nSamples))*100.0)/100.0;
slider18 = floor(log2dB*log(sqrt(sum2R/nSamples))*100.0)/100.0;
slider19 = floor(log2dB*log(sqrt(win2LMin/bufN))*100.0)/100.0;
slider20 = floor(log2dB*log(sqrt(win2RMin/bufN))*100.0)/100.0;
slider21 = floor(log2dB*log(sqrt(win2LMax/bufN))*100.0)/100.0;
slider22 = floor(log2dB*log(sqrt(win2RMax/bufN))*100.0)/100.0;
slider23 = slider21-slider19;
slider24 = slider22-slider20;
);

(play_state == 1) ? (
playing = 1;
) : (
playing = 0;
);

@sample
inL=spl0;
inR=spl1;

sspl = max(abs(inL), abs(inR));

sspl > peak ? (
peak = sspl;
base = min(1, curfac);

sspl > maxLimitVal ? (
base = min(curfac, maxLimitVal/peak);
curfac = base;
);

cfpeak = limitVal/peak;
att = 1;
w = lookahead;

) : att ? (
w -= 1;
relcurve = w/lookahead;
curfac = relcurve*base + (1-relcurve)*cfpeak; // lin
w == 0 ? att = 0;

) : w == 0 ? (
(peak > limitVal) ? (
base = curfac;
w = release;
);

) : (
w -= 1;
relcurve = w/release;
curfac = relcurve*base + (1-relcurve); // lin
peak = limitVal/curfac;
curfac >= 1 ? w = 0;
);

spl0 = spl0 * curfac * (renorm ? renFac : 1);
spl1 = spl1 * curfac * (renorm ? renFac : 1);
hard ? (
spl0 = min(max(spl0, -hardval), hardval);
spl1 = min(max(spl1, -hardval), hardval)
);

spl0*=output;
spl1*=output;

out0=spl0;
out1=spl1;

(playing) ? (
(holdIdx) ? (
holdIdx -= 1;
) : (
nSamples += 1;

sumL += (inL+inR)*0.5;
sumR += (out0+out1)*0.5;

ampL = abs((inL+inR)*0.5);
ampR = abs((out0+out1)*0.5);

peakL = max(peakL, ampL);
peakR = max(peakR, ampR);

(ampL > ampEnvL) ? (
ampEnvL = ampL;
) : (
ampEnvL = ampL + ampEnvCoeff * (ampEnvL - ampL);
);
(ampR > ampEnvR) ? (
ampEnvR = ampR;
) : (
ampEnvR = ampR + ampEnvCoeff * (ampEnvR - ampR);
); 

amp2L = ampL*ampL;
amp2R = ampR*ampR;
sum2L += amp2L;
sum2R += amp2R;
win2L += amp2L;
win2R += amp2R;

prev2L = bufL[bufIdx];
prev2R = bufR[bufIdx];
bufL[bufIdx] = amp2L;
bufR[bufIdx] = amp2R;

bufIdx += 1;
(bufIdx == bufN) ? (
bufIdx = 0;
(!bufOK) ? (
win2LMin = win2LMax = win2L;
win2RMin = win2RMax = win2R;
bufOK = 1;
);
);

(bufOK) ? (
win2LMin = min(win2LMin, win2L);
win2LMax = max(win2LMax, win2L);
win2RMin = min(win2RMin, win2R);
win2RMax = max(win2RMax, win2R);
win2L -= prev2L;
win2R -= prev2R;      

dbRMSL = log2dB*log(sqrt(win2L/bufN));
dbRMSLX = min(max((dbRMSL-dbMin)/dbRange, 0.0), 1.0);
densityIdx = floor(dbRMSLX*(densityBufN-1));
densityBufL[densityIdx] += 1;
maxDensityL = max(maxDensityL, densityBufL[densityIdx]);

dbRMSR = log2dB*log(sqrt(win2R/bufN));
dbRMSRX = min(max((dbRMSR-dbMin)/dbRange, 0.0), 1.0);
densityIdx = floor(dbRMSRX*(densityBufN-1));
densityBufR[densityIdx] += 1;
maxDensityR = max(maxDensityR, densityBufR[densityIdx]);    
);
);
);

@gfx 0 150
(gfx_w != prevGfxW) || (gfx_h != prevGfxH) ? (
prevGfxW = gfx_w;
prevGfxH = gfx_h;
rangeL = floor(gfx_w*GFX_RANGE_L);
rangeR = floor(gfx_w*GFX_RANGE_R);
rangeW = rangeR-rangeL;
rangeH = floor(gfx_h*GFX_RANGE_H);
rangeY = floor(gfx_h*GFX_RANGE_Y);
peakW = floor(gfx_w*GFX_PEAK_W);
peakH = floor(gfx_h*GFX_PEAK_H);
peakY = floor(gfx_h*GFX_PEAK_Y);
curY = floor(gfx_h*GFX_CUR_Y);
sumW = floor(gfx_w*GFX_SUM_W);
sumH = floor(gfx_h*GFX_SUM_H);
sumY = floor(gfx_h*GFX_SUM_Y);
meterMajY = floor(gfx_h*GFX_METER_MAJ_Y);
meterMinY = floor(gfx_h*GFX_METER_MIN_Y);

// Would be nice to preserve the existing data.
densityBufN = rangeW;
memset(densityBufL, 0, densityBufN);
memset(densityBufR, 0, densityBufN);
maxDensityL = maxDensityR = 0.0;  
);  

// Frame elements.

gfx_r = gfx_g = gfx_b = gfx_a = 1.0;

gfx_x = rangeL-1;
gfx_y = rangeY-1;
gfx_lineto(gfx_x+rangeW+2, gfx_y, 0);
gfx_lineto(gfx_x, gfx_y+rangeH+2, 0);
gfx_lineto(gfx_x-rangeW-2, gfx_y, 0);
gfx_lineto(gfx_x, gfx_y-rangeH-2, 0);

gfx_x = rangeL-1;
gfx_y = gfx_h-rangeY-rangeH-1;
gfx_lineto(gfx_x+rangeW+2, gfx_y, 0);
gfx_lineto(gfx_x, gfx_y+rangeH+2, 0);
gfx_lineto(gfx_x-rangeW-2, gfx_y, 0);
gfx_lineto(gfx_x, gfx_y-rangeH-2, 0);

// Meter.

dbMeter = ceil(dbMin);
while (gfx_x = rangeL+(dbMeter-dbMin)/dbRange*rangeW;
(dbMeter/3.0 == floor(dbMeter/3.0)) ? (
gfx_y = meterMajY;
) : (
gfx_y = meterMinY;
);
gfx_lineto(gfx_x, gfx_h-gfx_y, 0);
dbMeter += 1.0;
dbMeter <= dbMax;
);

// Current amp lines.

gfx_r = gfx_g = gfx_b = 0.5;

ampDB = log2dB*log(ampEnvL);
ampX = min(max((ampDB-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL;
gfx_y = curY;
gfx_rectto(gfx_x+ampX*rangeW, gfx_y+3);

ampDB = log2dB*log(ampEnvR);
ampX = min(max((ampDB-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL;
gfx_y = gfx_h-curY;
gfx_rectto(gfx_x+ampX*rangeW, gfx_y-3);

// RMS markers.

gfx_r = gfx_g = gfx_b = 0.75;

peakX = min(max((slider21-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL+peakX*rangeW;
gfx_y = peakY;
gfx_lineto(gfx_x-peakW/2, gfx_y-peakH, 1);
gfx_lineto(gfx_x+peakW, gfx_y, 0);
gfx_lineto(gfx_x-peakW/2, gfx_y+peakH, 1);

peakX = min(max((slider22-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL+peakX*rangeW;
gfx_y = gfx_h-peakY;
gfx_lineto(gfx_x-peakW/2, gfx_y+peakH, 1);
gfx_lineto(gfx_x+peakW, gfx_y, 0);
gfx_lineto(gfx_x-peakW/2, gfx_y-peakH, 1);

(bufOK) ? (

// Peak markers.

gfx_r = gfx_g = gfx_b = 1.0;

sumX = min(max((slider13-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL+sumX*rangeW;
gfx_y = sumY;
gfx_lineto(gfx_x-sumW/2, gfx_y+sumH, 1);
gfx_lineto(gfx_x+sumW, gfx_y, 0);
gfx_lineto(gfx_x-sumW/2, gfx_y-sumH, 1);

sumX = min(max((slider14-dbMin)/dbRange, 0.0), 1.0);
gfx_x = rangeL+sumX*rangeW;
gfx_y = gfx_h-sumY;
gfx_lineto(gfx_x-sumW/2, gfx_y-sumH, 1);
gfx_lineto(gfx_x+sumW, gfx_y, 0);
gfx_lineto(gfx_x-sumW/2, gfx_y+sumH, 1);

// Density.

gfx_b = 0.0;
gfx_x = rangeL;
idx = 0;
loop(rangeW,
densityAtPxL = sqrt(densityBufL[idx]/maxDensityL);
densityAtPxR = sqrt(densityBufR[idx]/maxDensityR);

gfx_r = max(2.0*(densityAtPxL-0.5), 0.0);
gfx_g = 1.0-abs(2.0*densityAtPxL-1.0);
gfx_y = rangeY;
gfx_lineto(gfx_x, gfx_y+rangeH, 0);

gfx_r = max(2.0*(densityAtPxR-0.5), 0.0);
gfx_g = 1.0-abs(2.0*densityAtPxR-1.0);
gfx_y = gfx_h-rangeY;
gfx_lineto(gfx_x, gfx_y-rangeH, 0);

gfx_x += 1;
idx += 1;
);

// Current RMS lines.

gfx_r = gfx_g = gfx_b = 1.0;

gfx_x = rangeL+dbRMSLX*rangeW;
gfx_y = rangeY-3;
gfx_lineto(gfx_x, gfx_y+rangeH+6, 0);

gfx_x = rangeL+dbRMSRX*rangeW;
gfx_y = gfx_h-rangeY+3;
gfx_lineto(gfx_x, gfx_y-rangeH-6, 0); 
);
