desc:Oversampled TPT Moog Ladder Filter v 1.1

/*

  Oversampled Topology Preserving Transform of Moog Ladder Filter

    from 'App Note 4: Virtual Analog (VA) Filter Impementation and Comparisons'
    by Will Pirkle 2013

    based on concepts covered in 'The Art of VA Filter Design' by Vadim Zavalishin

    customized with up to 6x oversampling and a drive control

    by Sault 6-22-14


  Changelog

      7-8-14

    * Oversampling library tweaked, oversampling factor raised to 6x.
    * Now clamps the signal in the input/feedback to a maximum value. Blow-ups should be more controllable.
    * There are times when the signal will "stick" to 0 dB and no amount of knob-tweaking seems to stop it.
	The only solution is to recompile the plugin.

    * Note - with higher oversampling, a lower Q factor will cause it to blow up. This is because
	higher Q values cause an overall loss of signal in the Moog filter. Don't reduce the signal
	as much, and the normalizing factor in the oversampling FIR will blow it up. I suggest keeping
	the Q above 1 once you've raised oversampling above 3x.

*/

slider1:0.9<0.05,0.99,0.01>Fc
slider2:1<0,3.95,0.01>Resonance
slider3:0<0,15,0.1>Drive (dB)
slider4:0<-15,15,0.1>Level (dB)
slider5:1<1,6,1>Oversample

import oversample-1.1.jsfx-inc

@init

function moog_lpf(in)
 instance(z1,v,out,gg)
(
  v = (in - z1) * gg;
  out = v + z1;
  z1 = out + v;
  out;
);

// limit Q [0.5 to 20]
// limit k [0 ... 4]

function moog_update(fc,q)
 instance(g,gg,wa,k)
(
  g = tan($pi*fc/(srate*oversample));	// uses global value
  wa = 2/srate * g;
  gg = g/(1+g);
  k = 4 * (q - 0.5)/(20);
  this.f0.gg = gg;
  this.f1.gg = gg;
  this.f2.gg = gg;
  this.f3.gg = gg;
);


function moog(in)
 instance(fc,g,gg,wa,gamma,sigma,k,s1,s2,s3,s4)
(
  gamma = gg * gg * gg * gg;

  s1 = this.f0.z1 / (1 + g);
  s2 = this.f1.z1 / (1 + g);
  s3 = this.f2.z1 / (1 + g);
  s4 = this.f3.z1 / (1 + g);

  sigma  = gg * gg * gg * s1;
  sigma += gg * gg * s2;
  sigma += gg * s3;
  sigma += s4;

  in = (in - k*sigma)/(1 + k*gamma);

  in *= drive;				// uses global value

  in = min(max(in,-1.414),1.414);	// approx where max value of +/1 0.9428 is reached

  in = in - in * in * in/6;

  in = this.f0.moog_lpf(in);
  in = this.f1.moog_lpf(in);
  in = this.f2.moog_lpf(in);
  in = this.f3.moog_lpf(in);

  in;
);

  samples0 = 1000;
  samples1 = 2000;

  oversampleo = oversample = slider5;

  s0.oversample_init(samples0,oversample);
  s1.oversample_init(samples1,oversample);

  moog0.moog_update(cutoff,slider2);
  moog1.moog_update(cutoff,slider2);
  cutoff = (-0.1*slider1)/(slider1-1.1);
  cutoff *= 0.495 * srate;
  drive = 2^(slider3/6);
  level = 2^(slider4/6);


@slider

  oversample = slider5;
  oversample != oversampleo ? (
    s0.oversample_init(samples0,oversample);
    s1.oversample_init(samples1,oversample);
    oversampleo = oversample;
    );

  moog0.moog_update(cutoff,slider2);
  moog1.moog_update(cutoff,slider2);
  cutoff = (-0.1*slider1)/(slider1-1.1);
  cutoff *= 0.495 * srate;
  drive = 2^(slider3/6);
  level = 2^(slider4/6);


@sample

  s0.upsample(spl0);
  s1.upsample(spl1);

  n = 0;
  loop(oversample,
    samples0[n] = moog0.moog(samples0[n]);
    samples1[n] = moog1.moog(samples1[n]);
    n += 1;
    );

  spl0 = s0.downsample() * level;
  spl1 = s1.downsample() * level;

  spl0 = max(min(spl0,1),-1);
  spl1 = max(min(spl1,1),-1);