desc:MIDI triggered formant filter

/*
 MIDI-triggered formant filter

 v 1.3
 02-01-14

 by Sault


Want a different formant for each note? Well, now you can have one.
Has another two bands, one for presence and one for bass. The bass
formant is one octave below F1, and presence is user-selectable. Each
formant has an option to have a fade-in, if desired. A bit of this
gives it some vocal-ish qualities, I suppose.

It's 28 sliders. I know, I know. I'm sorry. I hope you can enjoy it
anyways.


*/

slider1:0<-15,15,0.1>Preamp (dB)
slider2:0<0,1,1{1&2,3&4}>Audio Channel In
slider3:0<0,15,1>MIDI channel (0 = all)
slider4:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>C
slider5:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>C#/Db
slider6:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>D
slider7:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>D#/Eb
slider8:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>E
slider9:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>F
slider10:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>F#/Gb
slider11:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>G
slider12:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>G#/Ab
slider13:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>A
slider14:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>A#/Bb
slider15:0<0,15,1{m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}>B
slider16:4000<2000,8000,1>Presence (Hz)
slider17:0<0,30,0.1>Presence (dB)
slider18:0<0,1000,1>Presence attack (ms)
slider19:0<0,30,0.1>Bass (dB)
slider20:0<0,1000,1>Bass attack (ms)
slider21:0<0,1000,1>F1 attack (ms)
slider22:0<0,1000,1>F2 attack (ms)
slider23:0<0,1000,1>F3 attack (ms)
slider24:0<0,100,1>Saturation (dB)
slider25:0<-60,30,0.1>Wet (dB)
slider26:-60<-60,30,0.1>Dry (dB)
slider27:1<0,1,1{off,on}>Limit
slider28:0<0,1,1{1&2,3&4}>Audio Channel Out


@init

// F1, F2, F3 are normal formant bands
// F4 is "presence", F5 is bass formant (octave down from F1)

  initial_filter_off = 0;

  f1.gain = 13;
  f1.Q = 8;
  f2.gain = 11;
  f2.Q = 10;
  f3.gain = 13;
  f3.Q = 12;

  amp_dB = 8.6562;
  threshold_dB = -3.0;
  limit_dB = -0.2;
  a = 1.017;
  b = -0.025;
  boost_dB = 0;

  state_decay = exp(-1/(srate*0.001*20));


// Setting up an RBJ bandpass as a function
// so I can have it as multiple instances, one
// per band. Call rbj_bp_init to set or change
// its characteristics, call rbj_bp to do
// the biquad.


function rbj_bp_init(freq, Q, gain)
 instance(w0, cosw0, sinw0, alpha, a1)
(
  a1 = (2^(gain/6));
  w0 = 2 * $pi * freq/srate;
  cosw0 = cos(w0);
  sinw0 = sin(w0);
  alpha = sinw0 / (2 * Q);

  this.b01 = alpha;
  this.b11 = 0;
  this.b21 = -alpha;
  this.a01 = 1 + alpha;
  this.a11 = -2 * cosw0;
  this.a21 = 1 - alpha;
  this.b01 /= this.a01;
  this.b11 /= this.a01;
  this.b21 /= this.a01;
  this.a11 /= this.a01;
  this.a21 /= this.a01;

  this.xr11 = this.xr21 = 0;
  this.yr11 = this.yr21 = 0;
  this.oin = this.in = 0;

);


function rbj_bp(in)
 instance(oin, out)
(
  oin = in;
  out = this.b01 * in + this.b11 * this.xr11 + this.b21 * this.xr21 - this.a11 * this.yr11 - this.a21 * this.yr21;
  this.xr21 = this.xr11;
  this.xr11 = this.oin;
  this.yr21 = this.yr11;
  this.yr11 = out;
  out*this.a1;
);


function push(ptr,a,b,c,d)
(
 ptr[0] = a;
 ptr[1] = b;
 ptr[2] = c;
 ptr[3] = d;
 ptr += 4;
 ptr;
);

// new formant table
// 
// added a few more
// adding a 5th, "F-1", one oct below F1
// (for more bass)
// taken from https://ccrma.stanford.edu/~kglee/m220c/formant.html
// 
// {m-ee,m-eh,m-ae,m-ah,m-oo,f-ee,f-eh,f-ae,f-ah,f-oo,c-ee,c-eh,c-ae,c-ah,c-oo}
//

fm = 0;
fm = push(fm,135,270,2290,3010); // m-ee	male formants
fm = push(fm,265,530,1840,2480); // m-eh
fm = push(fm,330,660,1720,2410); // m-ae
fm = push(fm,365,730,1090,2440); // m-ah
fm = push(fm,150,300,870,2240);  // m-oo
fm = push(fm,155,310,2790,3310); // f-ee	female formants
fm = push(fm,355,610,2330,2990); // f-eh
fm = push(fm,425,850,2050,2850); // f-ae
fm = push(fm,295,590,1220,2810); // f-ah
fm = push(fm,185,370,950,2670);  // f-oo
fm = push(fm,185,370,3200,3310); // c-ee	child formants
fm = push(fm,345,690,2610,3570); // c-eh
fm = push(fm,515,1030,2320,3320);// c-ae
fm = push(fm,340,680,1370,3170); // c-ah
fm = push(fm,215,430,1170,3260); // c-oo
fm = 0;


function load_formant(ptr)
(
  f5.hz = ptr[0];
  f1.hz = ptr[1];
  f2.hz = ptr[2];
  f3.hz = ptr[3];

  f1.gainAdj = f1.gain + slider21/150;
  f2.gainAdj = f2.gain + slider22/150;
  f3.gainAdj = f3.gain + slider23/150;
  f4.gainAdj = f4.gain + slider18/150 - 1;
  f5.gainAdj = f5.gain + slider20/150 - 1;

  formant10.rbj_bp_init( f1.hz, f1.Q, f1.gainAdj );
  formant20.rbj_bp_init( f2.hz, f2.Q, f2.gainAdj );
  formant30.rbj_bp_init( f3.hz, f3.Q, f3.gainAdj );
  formant40.rbj_bp_init( f4.hz, f4.Q, f4.gainAdj );
  formant50.rbj_bp_init( f5.hz, f5.Q, f5.gainAdj );

  formant11.rbj_bp_init( f1.hz, f1.Q, f1.gainAdj );
  formant21.rbj_bp_init( f2.hz, f2.Q, f2.gainAdj );
  formant31.rbj_bp_init( f3.hz, f3.Q, f3.gainAdj );
  formant41.rbj_bp_init( f4.hz, f4.Q, f4.gainAdj );
  formant51.rbj_bp_init( f5.hz, f5.Q, f5.gainAdj );
);


function RMS(input, ms)
  instance(out, rms_s )
(
  coeff = exp(-1/(ms / 1000 * srate)); // usually 0.999..
  rms_s = (rms_s * coeff) + ((1 - coeff) * input * input);
  out = sqrt(rms_s);
  out;
);

  f1.env = f2.env = f3.env = f4.env = f5.env = 0;



// these variables are updated at slider, also


  f4.hz = slider16;
  f4.gain = slider17;
  f4.Q = 6;
  f5.gain = slider19;
  f5.Q = 8;

  f1.attack = exp(-1/(srate*0.001*slider21));
  f2.attack = exp(-1/(srate*0.001*slider22));
  f3.attack = exp(-1/(srate*0.001*slider23));
  f4.attack = exp(-1/(srate*0.001*slider18));
  f5.attack = exp(-1/(srate*0.001*slider20));

  f1.delay = exp(-1/(srate*0.001*slider21*0.5));
  f2.delay = exp(-1/(srate*0.001*slider22*0.5));
  f3.delay = exp(-1/(srate*0.001*slider23*0.5));
  f4.delay = exp(-1/(srate*0.001*slider18*0.5));
  f5.delay = exp(-1/(srate*0.001*slider20*0.5));

  preamp = 2^(slider1/6);
  wet = 2^(slider25/6);
  dry = 2^(slider26/6);

  foo = slider24/200*$pi;
  bar = sin(slider24/200*$pi);

  channelIN = slider2 * 2;
  channelOUT = slider28 * 2;

  load_formant(0);


@slider

  f4.hz = slider16;
  f4.gain = slider17;
  f4.Q = 6;
  f5.gain = slider19;
  f5.Q = 8;

  f1.attack = exp(-1/(srate*0.001*slider21*0.7));
  f2.attack = exp(-1/(srate*0.001*slider22*0.7));
  f3.attack = exp(-1/(srate*0.001*slider23*0.7));
  f4.attack = exp(-1/(srate*0.001*slider18*0.7));
  f5.attack = exp(-1/(srate*0.001*slider20*0.7));

  f1.delay = exp(-1/(srate*0.001*slider21*0.5));
  f2.delay = exp(-1/(srate*0.001*slider22*0.5));
  f3.delay = exp(-1/(srate*0.001*slider23*0.5));
  f4.delay = exp(-1/(srate*0.001*slider18*0.5));
  f5.delay = exp(-1/(srate*0.001*slider20*0.5));

  preamp = 2^(slider1/6);
  wet = 2^(slider25/6);
  dry = 2^(slider26/6);

  foo = slider24/200*$pi;
  bar = sin(slider24/200*$pi);

  channelIN = slider2 * 2;
  channelOUT = slider28 * 2;


@block
while (

 midirecv(ts,msg1,msg23) ? (

  m = msg1 & 240;
  channel = msg1 & $x0F;
  vel = (msg23/256) | 0;
  vol = vel/127;

  ((slider3 == 0) || (channel == slider3)) ? (

   ((m == 9*16) && (vol > 0)) ? (
     note = msg23 & 127;

     formant_number = (note % 12);

     formant_number == 0  ? fm_index = slider4;
     formant_number == 1  ? fm_index = slider5;
     formant_number == 2  ? fm_index = slider6;
     formant_number == 3  ? fm_index = slider7;
     formant_number == 4  ? fm_index = slider8;
     formant_number == 5  ? fm_index = slider9;
     formant_number == 6  ? fm_index = slider10;
     formant_number == 7  ? fm_index = slider11;
     formant_number == 8  ? fm_index = slider12;
     formant_number == 9  ? fm_index = slider13;
     formant_number == 10 ? fm_index = slider14;
     formant_number == 11 ? fm_index = slider15;

     load_formant(fm + fm_index * 4);

	f1.state = 1;
	f2.state = 1;
	f3.state = 1;
	f4.state = 1;
	f5.state = 1;

	f1.dstate = 1;
	f2.dstate = 1;
	f3.dstate = 1;
	f4.dstate = 1;
	f5.dstate = 1;

	initial_filter_off = 1;

     ); // noteon?
   ); // checking against MIDI channel or allow all


  midisend(ts,msg1,msg23);
  ); // midirecv

); // while



@sample

  f1.dstate *= f1.delay;
  f2.dstate *= f2.delay;
  f3.dstate *= f3.delay;
  f4.dstate *= f4.delay;
  f5.dstate *= f5.delay;

  f1.dstate < 0.6 ? (
    f1.state *= f1.attack;
    f1.env = 1 - f1.state;
	) : (
	f1.env *= state_decay;
	);
 
  f2.dstate < 0.6 ? (
    f2.state *= f2.attack;
    f2.env = 1 - f2.state;
	) : (
	f2.env *= state_decay;
	);

  f3.dstate < 0.6 ? (
    f3.state *= f3.attack;
    f3.env = 1 - f3.state;
	) : (
	f3.env *= state_decay;
	);

  f4.dstate < 0.6 ? (
    f4.state *= f4.attack;
    f4.env = 1 - f4.state;
	) : (
	f4.env *= state_decay;
	);

  f5.dstate < 0.6 ? (
    f5.state *= f5.attack;
    f5.env = 1 - f5.state;
	) : (
	f5.env *= state_decay;
	);

  f1.env = f1env.RMS(f1.env,5);
  f2.env = f2env.RMS(f2.env,5);
  f3.env = f3env.RMS(f3.env,5);
  f4.env = f4env.RMS(f4.env,5);
  f5.env = f5env.RMS(f5.env,5);

  dryL = inL = spl(0+channelIN) * preamp;
  dryR = inR = spl(1+channelIN) * preamp;

  band1L = formant10.rbj_bp(inL) * f1.env;
  band2L = formant20.rbj_bp(inL) * f2.env;
  band3L = formant30.rbj_bp(inL) * f3.env;
  band4L = formant40.rbj_bp(inL) * f4.env;
  band5L = formant50.rbj_bp(inL) * f5.env;

  band1R = formant11.rbj_bp(inR) * f1.env;
  band2R = formant21.rbj_bp(inR) * f2.env;
  band3R = formant31.rbj_bp(inR) * f3.env;
  band4R = formant41.rbj_bp(inR) * f4.env;
  band5R = formant51.rbj_bp(inR) * f5.env;

  inL = band1L + band2L + band3L + band4L + band5L;
  inR = band1R + band2R + band3R + band4R + band5R;


// Apply doubled up Loser saturation, if desired.

slider24 ? (
  inL = min(max(sin(max(min(inL,1),-1)*foo)/bar,-1),1);
  inR = min(max(sin(max(min(inR,1),-1)*foo)/bar,-1),1);

  inL = min(max(sin(max(min(inL,1),-1)*foo)/bar,-1),1);
  inR = min(max(sin(max(min(inR,1),-1)*foo)/bar,-1),1);
);


// Output gain

  inL = inL * wet * initial_filter_off + dryL * dry;
  inR = inR * wet * initial_filter_off + dryR * dry;


// Schwa's soft clipper, functioning as a limiter.

slider27 ? (

  dB0 = amp_dB * log(abs(inL)) + boost_dB;
  dB1 = amp_dB * log(abs(inR)) + boost_dB;

  (dB0 > threshold_dB) ? (
    over_dB = dB0 - threshold_dB;
    over_dB = a * over_dB + b * over_dB * over_dB;
    dB0 = min(threshold_dB + over_dB, limit_dB);
  );

  (dB1 > threshold_dB) ? (
    over_dB = dB1 - threshold_dB;
    over_dB = a * over_dB + b * over_dB * over_dB;
    dB1 = min(threshold_dB + over_dB, limit_dB);
  );

  inL = exp(dB0 / amp_dB) * sign(inL);
  inR = exp(dB1 / amp_dB) * sign(inR);
);


  spl(0+channelOUT) = inL;
  spl(1+channelOUT) = inR;



// EOF