/**************************
          VOLA-EX
Copyright (C) 2015 Stige T.
**************************/

desc:VOLA-EX [Build 151103]

slider1:-26<-40,-3,1>RMS Target [dBfs]
slider2:-0.3<-40,0,0.1>Peak Target [dBfs]
slider3:0<0,1,1{Off,On}>Output Auto-Gain

slider10:2<1,10,0.1>Max Reaction Time [Sec]
slider11:20<10,100,1>Min Reaction Time [%]
slider12:-45<-60,-30,1>Noise Floor [RMS dBfs]
slider13:10<0,20,1>Pre-Leveller Headroom [dB]

slider20:1<0,2,1{No,Yes,Yes (compensate)}>Allow Latency

@init

t001 = exp(-1/(srate*0.0001));
t005 = exp(-1/(srate*0.0005));
t01 = exp(-1/(srate*0.001));
t02 = exp(-1/(srate*0.002));
t05 = exp(-1/(srate*0.005));
t10 = exp(-1/(srate*0.01));
t20 = exp(-1/(srate*0.02));
t30 = exp(-1/(srate*0.03));
t50 = exp(-1/(srate*0.05));
t100 = exp(-1/(srate*0.1));
t200 = exp(-1/(srate*0.2));
t500 = exp(-1/(srate*0.5));
t1000 = exp(-1/(srate*1));

HPfreq = 1-exp(-2*$pi*20/srate);
LPfreq = 1-exp(-2*$pi*4000/srate);
mediumweight = 1-exp(-1/(srate*0.5));
fastweight = 1-exp(-1/(srate*0.1));
slowweight = 1-exp(-1/(srate*5));

frms = 1;
orms = 1;

start_time = time();

function delay64spl(input) (
  this.x0 = input;
  this.x65=this.x64; this.x64=this.x63; this.x63=this.x62; this.x62=this.x61; this.x61=this.x60; this.x60=this.x59; this.x59=this.x58;
  this.x58=this.x57; this.x57=this.x56; this.x56=this.x55; this.x55=this.x54; this.x54=this.x53; this.x53=this.x52; this.x52=this.x51;
  this.x51=this.x50; this.x50=this.x49; this.x49=this.x48; this.x48=this.x47; this.x47=this.x46; this.x46=this.x45; this.x45=this.x44;  
  this.x44=this.x43; this.x43=this.x42; this.x42=this.x41; this.x41=this.x40; this.x40=this.x39; this.x39=this.x38; this.x38=this.x37;
  this.x37=this.x36; this.x36=this.x35; this.x35=this.x34; this.x34=this.x33; this.x33=this.x32; this.x32=this.x31; this.x31=this.x30;
  this.x30=this.x29; this.x29=this.x28; this.x28=this.x27; this.x27=this.x26; this.x26=this.x25; this.x25=this.x24; this.x24=this.x23;
  this.x23=this.x22; this.x22=this.x21; this.x21=this.x20; this.x20=this.x19; this.x19=this.x18; this.x18=this.x17; this.x17=this.x16;
  this.x16=this.x15; this.x15=this.x14; this.x14=this.x13; this.x13=this.x12; this.x12=this.x11; this.x11=this.x10; this.x10=this.x9;
  this.x9=this.x8; this.x8=this.x7; this.x7=this.x6; this.x6=this.x5; this.x5=this.x4; this.x4=this.x3; this.x3=this.x2; this.x2=this.x1; this.x1=this.x0;
  this.x65;
);

function delay_init(samples,index)
instance (len,sloop,splay)
(
  len = samples > srate ? srate : samples;
  sloop = splay = srate * index;
);

function delay(input)
instance(sindex,splay,sloop,len,sloop,splay)
(
  sloop[sindex] = input;
  sindex += 1;
  sindex >= len ? sindex = 0;
  splay[sindex];
);

function interpol(in1,in2,mu) (
   (in1*(1-mu)+in2*mu);
);

function fastLP(input)
instance(s) (
  input = s += LPfreq * ( input - s );
);

function fastHP(input)
instance(s) (
  input = input - ( s += HPfreq * ( input - s ) );
);

function follower(input,att,rel)
instance (env,tmp) (
  tmp = input;
  (tmp > env) ? (
      env = att * (env - tmp) + tmp;
  ) : (
      env = rel * (env - tmp) + tmp;
  );
);

function follower2(input,att,rel,inertia)
instance (env,tmp) (
  tmp = input >= tmp ? input : input + inertia * (tmp-input);
  (tmp > env) ? (
      env = att * (env - tmp) + tmp;
  ) : (
      env = rel * (env - tmp) + tmp;
  );
);

function signaldir(input)
instance (m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,dir,e)
(
  m1 = input;
  m10 = m9;
  m9 = m8;
  m8 = m7;
  m7 = m6;
  m6 = m5;
  m5 = m4;
  m4 = m3;
  m3 = m2;
  m2 = m1;
  dir = m1 >= m10*1.001 ? 1 : 0;
  dir = e.follower2(dir,t200,t200,t200);
);

function rms_display(input)
instance (rms,s) (
  rms = sqrt(s += 0.01 * ( input^2 - s ));
);

function rms(input)
instance (rms,s) (
  rms = sqrt(s += weight * ( input^2 - s ));
);

function rms_medium(input)
instance (rms,s) (
  rms = sqrt(s += mediumweight * ( input^2 - s ));
);

function rms_fast(input)
instance (rms,s) (
  rms = sqrt(s += fastweight * ( input^2 - s ));
);

function rms_slow(input)
instance (rms,s) (
  rms = sqrt(s += slowweight * ( input^2 - s ));
);

function releasemod(input)
instance(e0,peak,out) (
  peak = e0.follower(abs(input),0,t1000);
  out = recovery - (peak*0.00003);
);

function rmsleveller(input)
instance(rms,target,r,f,over,d) (
  gating == 1 ? (
    rms = r.rms( f.fastHP(input) );
  ) : (
    rms = min(rms * (1+maxweight),normalize);
  );
  rms = max(rms,threshold);
  target = normalize/rms;
  allow_latency ? (
    input = d.delay(input);
  );
  input = input * target;
);

function gate(key,input)
instance(over,r,rms,f,d) (
  rms = r.rms_fast(key);
  over = min(rms/(threshold),1);
  gating = over;
  input = input * over;
);

function limiter(input,threshold)
instance(env0,env1,env,e0,e1,th,rmod,rlmod,d)
(
  th = max(abs(input),threshold);
  th = th / threshold;
  
  rlmod = rmod.releasemod(th);
  
  env0 = e0.follower2(th,0,t10,t10);
  env1 = e1.follower(env0,t10,rlmod);
  env = max(env0,env1);
  allow_latency ? (
    input = d.delay64spl(input);
  );
  input = input / env;
);

function limiter2(input,threshold)
instance(env0,env1,env,e0,e1,th,rmod,rlmod,d)
(
  th = max(abs(input),threshold);
  th = th / threshold;
  
  rlmod = rmod.releasemod(th);
  
  env0 = e0.follower2(th,t01,t10,t10);
  env1 = e1.follower(env0,t10,rlmod);
  env = max(env0,env1);
  allow_latency ? (
    input = d.delay64spl(input);
  );
  input = input / env;
);

function sum(L,R)
instance(sum,asum,aL,aR,out) (
  sum = (L+R) * 0.501;
  asum = abs(sum);
  aL = abs(L);
  aR = abs(R);
  asum > aL && asum > aR ? (
    out = sum;
  ) : (
    aL > aR ? out = L : out = R;
  );
  out;
);

maxbcount = 32;
function blocksniffer(inL,inR) (
  inL ? (
    // Silence is treated as stereo to avoid switching issues.
    inL == inR ? (this.mspl = min(this.mspl += 1,maxbcount);) : (this.mspl = max(this.mspl -= 1,0););
    this.mspl == 0 ? sniffer.isMono = 0;
    this.mspl == maxbcount ? sniffer.isMono = 1;
  );
);
  
@slider

normalize = 10^(slider1/20);
limit = 10^(slider2/20);
nout = slider3;
recovery = exp(-1/(srate*slider10));
maxweight = 1-exp(-1/(srate*slider10));
minweight = 1-exp(-1/(srate*slider10*(slider11/100)));
threshold = 10^(slider12/20);
headroom = 10^(slider13/20);
allow_latency = slider20;

rmsdel = (srate * slider10*(slider11/100))/10;

rl1.d.delay_init(rmsdel,0);
rl2.d.delay_init(rmsdel,1);

total_lat_spl = 128 + rmsdel - 1;
total_lat_ms = total_lat_spl / srate * 1000;

allow_latency == 2 ? (
  pdc_delay = total_lat_spl;
  pdc_bot_ch = 0;
  pdc_top_ch = 2;
);

@block

drift_rms = frms/normalize;
difference_rms = frms/orms;
mtr1 = max(rl1.target,rl2.target);
mtr2 = max(lm1.env,lm2.env);
mtr3 = max(lm3.env,lm4.env);
mtr4 = 1-min(g1.over,g2.over);

blocksniffer(SnL,SnR);

@sample

InL = SnL = spl0;
InR = SnR = spl1;

!sniffer.isMono ? (
  inSum = s1.sum(InL,InR);
) : (
  inSum = InL;
);

wmul = sd.signaldir(r2.rms_slow(inSum));
weight = ip1.interpol(minweight,maxweight,wmul);
orms = r1.rms_medium(inSum);

InL = lm3.limiter2(InL,orms*headroom);
InL = rl1.rmsleveller(InL);
InL = g1.gate(inSum,InL);
InL = lm1.limiter(InL,limit);

!sniffer.isMono ? (
  InR = lm4.limiter2(InR,orms*headroom);
  InR = rl2.rmsleveller(InR);
  InR = g2.gate(inSum,InR);
  InR = lm2.limiter(InR,limit);
) : (
  InR = InL;
  lm4.env = lm3.env;
  lm2.env = lm1.env;
  rl2.target = rl1.target;
  g2.over = g1.over;
);

nout ? (
  InL *= (1/limit*0.989);
  InR *= (1/limit*0.989);
);

outSum = s2.sum(InL,InR);
frms = r2.rms_medium(outSum);

!run ? (
  splcount += 1;
  splcount <= srate ? (
    run = 0;
    InL = InL * (splcount/srate);
    InR = InR * (splcount/srate);
  ) : (
    run = 1;
  );
);

spl0 = InL;
spl1 = InR;

@gfx 390 120

function dynmeter(in1,in2,in3,x_pos,y_pos,showval)
instance(redux,col1,col2,in1_gfx,in2_gfx,in3_gfx)
(
  gfx_r = 1; gfx_g = 1; gfx_b = 1; gfx_a = 1;
  gfx_x = x_pos; gfx_y = y_pos;
  gfx_rectto(200+x_pos,10+y_pos);
  gfx_r = 0; gfx_g = 0; gfx_b = 0; gfx_a = 1;
  gfx_x = x_pos+1; gfx_y = y_pos+1;
  gfx_rectto(199+x_pos,9+y_pos);
  gfx_r = 1; gfx_g = 1; gfx_b = 1; gfx_a = 1;
  showval ? (
    gfx_x = 199+x_pos; gfx_y = 10+y_pos;gfx_lineto(199+x_pos,20+y_pos,0);gfx_drawnumber(20,0);
    gfx_x = 161+x_pos; gfx_y = 10+y_pos;gfx_lineto(161+x_pos,20+y_pos,0);gfx_drawnumber(12,0);
    gfx_x = 130+x_pos; gfx_y = 10+y_pos;gfx_lineto(130+x_pos,20+y_pos,0);gfx_drawnumber(6,0);
    gfx_x = 100+x_pos; gfx_y = 10+y_pos;gfx_lineto(100+x_pos,20+y_pos,0);gfx_drawnumber(0,0);
    gfx_x = 69+x_pos; gfx_y = 10+y_pos;gfx_lineto(69+x_pos,20+y_pos,0);gfx_drawnumber(-6,0);
    gfx_x = 38+x_pos; gfx_y = 10+y_pos;gfx_lineto(38+x_pos,20+y_pos,0);gfx_drawnumber(-12,0);
    gfx_x = 0+x_pos; gfx_y = 10+y_pos;gfx_lineto(0+x_pos,20+y_pos,0);gfx_drawnumber(-20,0);
  );
  
  gfx_x = 101+x_pos; gfx_y = y_pos+2;
  in1 = max(in1,0.1);
  in1 = min(in1,9.8);
  gfx_r = 0; gfx_g = 0.9; gfx_b = 0.9; gfx_a = 1;
  in1_gfx = ceil(100 - log(1/in1)*43.3);
  gfx_rectto( in1_gfx + x_pos, 8 + y_pos);
  
  gfx_x = 101+x_pos; gfx_y = y_pos+3;
  in2 = max(in2,0.1);
  in2 = min(in2,9.8);
  gfx_r = 0.8; gfx_g = 0.4; gfx_b = 0.4; gfx_a = 1;
  in2_gfx = ceil(100 - log(1/in2)*43.3);
  gfx_rectto( in2_gfx + x_pos, 7 + y_pos);
  
  gfx_x = in2_gfx + x_pos; gfx_y = y_pos+3;
  in3 = max(in3,0.1);
  in3 = min(in3,9.8);
  gfx_r = 0.9; gfx_g = 0.9; gfx_b = 0; gfx_a = 1;
  in3_gfx = ceil(100 - log(1/in3)*43.3);
  gfx_rectto( in3_gfx + x_pos, 7 + y_pos);
  
  gfx_r = 1; gfx_g = 1; gfx_b = 1; gfx_a = 1;
  gfx_x = 100+x_pos; gfx_y = y_pos;
  gfx_rectto(101+x_pos,10+y_pos);
);

gfx_a = 1;
gfx_x = 0; gfx_y = 0;
gfx_r = 0.1; gfx_g = 0.1; gfx_b = 0.1;
gfx_rectto(1000,1000);
gfx_setfont(1,"Arial",16);

gfx_r = 1; gfx_g = 1; gfx_b = 1;
gfx_x = 10; gfx_y = 10;
db = dbenv.follower(drift_rms,0,t01);
db = 20 * log10(db);
gfx_drawstr("RMS Target Drift: ");
gfx_r = 0; gfx_g = 1; gfx_b = 0;
db > 1 ? (gfx_r = 1; gfx_g = 0.3; gfx_b = 0.3;);
db < -1 ? (gfx_r = 0.4; gfx_g = 0.4; gfx_b = 1;);
db >= 0 ? gfx_drawstr("+");
gfx_drawstr(strcat(sprintf(#,"%.1f",db)," dB"););

gfx_r = 1; gfx_g = 1; gfx_b = 1;
gfx_x = 10; gfx_y = 30;
db2 = dbenv2.follower(difference_rms,0,t01);
db2 = 20 * log10(db2);
gfx_drawstr("RMS I/O Difference: ");
db2 >= 0 ? gfx_drawstr("+");
gfx_drawstr(strcat(sprintf(#,"%.1f",db2)," dB"););

gfx_r = 1; gfx_g = 1; gfx_b = 1;
gfx_x = 10; gfx_y = 50;
db3 = 20 * log10(orms);
gfx_drawstr("RMS Intput: ");
gfx_drawstr(strcat(sprintf(#,"%.1f",dbenv3.follower(db3,0,t01))," dBfs"););

gfx_r = 1; gfx_g = 1; gfx_b = 1;
gfx_x = 10; gfx_y = 70;
db4 = 20 * log10(frms);
gfx_drawstr("RMS Output: ");
gfx_drawstr(strcat(sprintf(#,"%.1f",dbenv4.follower(db4,0,t01))," dBfs"););

gfx_x = 280; gfx_y = 10;
gfx_drawstr("Processing");

gfx_setfont(1,"Arial",14,'b');
gfx_r = 1; gfx_g = 1; gfx_b = 1; gfx_a = 0.05+mtr4*3;
gfx_x = 280; gfx_y = 70;
gfx_drawstr("GATING ");
gfx_drawstr(strcat(sprintf(#,"%.0f",mtr4*100),"%"););

gfx_setfont(1,"Arial",16);
mtr2 = mtr2env.follower(mtr2,0,t001);
mtr3 = mtr3env.follower(mtr3,0,t001);
x.dynmeter(mtr1,1/mtr2,1/(mtr2*mtr3),210,30,1);

mouse_cap & 4 ? (
  slider_automate(slider1 = -26);
  slider_automate(slider2 = -0.3);
  slider_automate(slider3 = 0);
  slider_automate(slider10 = 2);
  slider_automate(slider11 = 20);
  slider_automate(slider13 = 10);
  normalize = 10^(slider1/20);
  limit = 10^(slider2/20);
  nout = slider3;
  recovery = exp(-1/(srate*slider10));
  maxweight = 1-exp(-1/(srate*slider10));
  minweight = 1-exp(-1/(srate*slider10*(slider11/100)));
  threshold = 10^(slider12/20);
  headroom = 10^(slider13/20); 
);

allow_latency ? (
  gfx_setfont(1,"Arial",12,'i');
  gfx_r = 1; gfx_g = 1; gfx_b = 1; gfx_a = 0.5;  
  #lStr.a = "Current latency: ";
  lStr.b = strcat(sprintf(#,"%.0f",total_lat_spl),"spl / ");
  lStr.c = strcat(sprintf(#,"%.1f",total_lat_ms),"ms");
  lStr = strcat(strcat(#lStr.a,lStr.b),lStr.c);
  gfx_x = 10; gfx_y = 100;
  gfx_drawstr(lStr);
);
