//
//   ...o0* Formant Function *0o...
//
//         ("form and function")
//
// by Sault
//
// A highly customizable, sweepable, "singing" formant filter.
// (automate slider5 to see what I mean)
//
// many thanks to Stillwell for RBJ4EQ, Loser for Saturation, and
// schwa for soft clipper, parts of which were all used in the making
// of this pluging.
//

desc:sweepable formant filter

slider1:3<0,9,1{beat,bit,bet,bat,part,pot,boot,book,but,pert}>formant 1
slider2:7<0,9,1{beat,bit,bet,bat,part,pot,boot,book,but,pert}>formant 2
slider3:4000<2000,8000,1>f4 formant peak (Hz)
slider4:3<-15,30,0.1>f4 level (dB)
slider5:0<0,1,0.01>Sing! (Automate me!!!)
slider6:1<0,5,1{pow, root, frown, smile, line, notch}>transfer function
slider7:1.5<0.5,5,0.01>resonance (more = sharper Q)
slider8:0<0,100,1>saturation
slider9:0<-30,30,0.1>output gain
slider10:1<0,1,1{off,on}>limit output


//
// The overall concept is to define a range of frequencies by
// selecting two different formants with slider1 and slider2,
// picking a fourth formant with slider3, then using slider5
// to sweep between that range of frequencies.

// Slider6 defines the relationship between how that slider moves
// and what frequencies are selected. Slider7 gives us the ability
// to sharpen our Q's and make a more pronounced effect.
// Slider8 gives us some saturation, slider9 our master volume,
// and slider10 for a soft-knee brickwall limiter to -0.2 dB.

// Formant table derived from female vocals.

// Enjoy.


@init

  outgain = 2^(slider9/6);
  res = slider7;


// Set relative volumes for formants in dB
// Higher Q's lose a lot of signal, so we're
// boosting each band by a lot.

  f1gain = 18;
  f2gain = 14;
  f3gain = 16;
  f4gain = slider4;


// Set up our functions...


// 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;
);


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;
);

// Here is a table defining our formants -
// female vocals for 10 different vowel sounds.
// I have tweaked the Q's a bit.
//
//	ex.
// 	bet_F3_Hz = fm[ (3*6) + (3*2) + 0 ] = 3000;

  fm = 0;

// "beat"

  fm[0]  = 300;  fm[1]  = 8;
  fm[2]  = 2800; fm[3]  = 20;
  fm[4]  = 3300; fm[5]  = 19.8;

// "bit"

  fm[6]  = 430;  fm[7]  = 11;
  fm[8]  = 2500; fm[9]  = 16;
  fm[10] = 3300; fm[11] = 18.8;

// "bet"

  fm[12] = 600;  fm[13] = 14;
  fm[14] = 2350; fm[15] = 13;
  fm[16] = 3000; fm[17] = 18.2; 

// "bat"

  fm[18] = 860;  fm[19] = 20;
  fm[20] = 2050; fm[21] = 12.5;
  fm[22] = 2850; fm[23] = 17.4;

// "part"

  fm[24] = 850;  fm[25] = 20;
  fm[26] = 1200; fm[27] = 14;
  fm[28] = 2800; fm[29] = 17.1;

// "pot"

  fm[30] = 590;  fm[31] = 14;
  fm[32] = 900;  fm[33] = 11;
  fm[34] = 2700; fm[35] = 16.6;

// "boot"

  fm[36] = 470;  fm[37] = 11;
  fm[38] = 1150; fm[39] = 13.5;
  fm[40] = 2700; fm[41] = 16.6;

// "book"

  fm[42] = 370;  fm[43] = 9;
  fm[44] = 950;  fm[45] = 11.4;
  fm[46] = 2650; fm[47] = 16.3;

// "but"

  fm[48] = 760;  fm[49] = 17;
  fm[50] = 1400; fm[51] = 16;
  fm[52] = 2800; fm[53] = 17.1;

// "pert"

  fm[54] = 500;  fm[55] = 12;
  fm[56] = 1650; fm[57] = 18.5;
  fm[58] = 1950; fm[59] = 12.5;


// Here are the transfer functions
// [ pow, root, frown, smile, line, notch ]


function transfer(type, vala, valb, trans)
 instance (out, range)
(
  range = valb - vala;

  type == 0 ? trans ^= 1.5;
  type == 1 ? trans ^= 0.7;
  type == 2 ? trans = 1-sin($pi * trans);
  type == 3 ? trans = sin($pi * trans);
  type == 4 ? trans = trans;
  type == 5 ? trans = abs( cos($pi * trans));

 out = (trans*range) + vala;
 out;
);

// here we load our formant information
// both our "start" formant and our "end" formant

  index1 = slider1 * 6;
  index2 = slider2 * 6;

  f1Hza = index1[0]; f1Qa = index1[1];
  f2Hza = index1[2]; f2Qa = index1[3];
  f3Hza = index1[4]; f3Qa = index1[5];

  f1Hzb = index2[0]; f1Qb = index2[1];
  f2Hzb = index2[2]; f2Qb = index2[3];
  f3Hzb = index2[4]; f3Qb = index2[5];


// Our transfer function takes our Sing! slider
// and translates it to a value between Hza and Hzb, etc.
// Our choice of transfer function determines
// how the filter reacts to the slider.


  f1Hz = transfer(slider6, f1Hza, f1Hzb, slider5);
  f2Hz = transfer(slider6, f2Hza, f2Hzb, slider5);
  f3Hz = transfer(slider6, f3Hza, f3Hzb, slider5);

  f1Q = transfer(slider6, f1Qa, f1Qb, slider5);
  f2Q = transfer(slider6, f2Qa, f2Qb, slider5);
  f3Q = transfer(slider6, f3Qa, f3Qb, slider5);


// Each band has both a left and a right RBJ bandpass.
// The first three formants are taken from the table,
// the fourth formant is for presence and character.


  formant10.rbj_bp_init( f1Hz, f1Q, f1gain );
  formant20.rbj_bp_init( f2Hz, f2Q, f2gain );
  formant30.rbj_bp_init( f3Hz, f3Q, f3gain );

  formant11.rbj_bp_init( f1Hz, f1Q, f1gain );
  formant21.rbj_bp_init( f2Hz, f2Q, f2gain );
  formant31.rbj_bp_init( f3Hz, f3Q, f3gain );


// I've taken certain liberties here in order to
// cut down on the number of sliders. I'm going to
// just blanket assume that our presence formant is
// about 600 Hz wide, and that it has a relatively
// high Q. As it raises in frequency it lowers Q, though,
// to balance itself out somewhat.


  f4Hza = slider3 - 300; f4Hzb = slider3 + 300;
  f4Qa = 12 * res;       f4Qb = 10 * res;

  f4Hz = transfer(slider6, f4Hza, f4Hzb, slider5);
  f4Q = transfer(slider6, f4Qa, f4Qb, slider5);

  formant40.rbj_bp_init( f4Hz, f4Q, f4gain );
  formant41.rbj_bp_init( f4Hz, f4Q, f4gain );


// Yes, I'm lifting Loser's Saturation code verbatim,
// Because it's that awesome. Except I'm doubling it.
// Subtleness is not for this plugin.

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


// ...same thing with schwa's soft-clipper. It is the shiz.
// I tweaked it a little bit to focus more as a limiter and
// less as a clipper.

  amp_dB = 8.6562;
  threshold_dB = -6.0;
  limit_dB = -0.2;
  a = 1.017;
  b = -0.025;


@slider

  res = slider7;

  index1 = slider1 * 6;
  index2 = slider2 * 6;

  f1Hza = index1[0]; f1Qa = index1[1] * res;
  f2Hza = index1[2]; f2Qa = index1[3] * res;
  f3Hza = index1[4]; f3Qa = index1[5] * res;

  f1Hzb = index2[0]; f1Qb = index2[1] * res;
  f2Hzb = index2[2]; f2Qb = index2[3] * res;
  f3Hzb = index2[4]; f3Qb = index2[5] * res;

  f1Hz = transfer(slider6, f1Hza, f1Hzb, slider5);
  f2Hz = transfer(slider6, f2Hza, f2Hzb, slider5);
  f3Hz = transfer(slider6, f3Hza, f3Hzb, slider5);

  f1Q = transfer(slider6, f1Qa, f1Qb, slider5);
  f1Q = transfer(slider6, f1Qa, f1Qb, slider5);
  f1Q = transfer(slider6, f1Qa, f1Qb, slider5);

  formant10.rbj_bp_init( f1Hz, f1Q, f1gain );
  formant20.rbj_bp_init( f2Hz, f2Q, f2gain );
  formant30.rbj_bp_init( f3Hz, f3Q, f3gain );

  formant11.rbj_bp_init( f1Hz, f1Q, f1gain );
  formant21.rbj_bp_init( f2Hz, f2Q, f2gain );
  formant31.rbj_bp_init( f3Hz, f3Q, f3gain );


  f4gain = slider4;

  f4Hza = slider3 - 300; f4Hzb = slider3 + 300;
  f4Qa = 12 * res;       f4Qb = 8 * res;

  f4Hz = transfer(slider6, f4Hza, f4Hzb, slider5);
  f4Q = transfer(slider6, f4Qa, f4Qb, slider5);

  formant40.rbj_bp_init( f4Hz, f4Q, f4gain );
  formant41.rbj_bp_init( f4Hz, f4Q, f4gain );


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

  outgain = 2^(slider9/6);


@sample


// We run our bandpasses in parallel, then
// sum the outputs.


  band1L = formant10.rbj_bp(spl0);
  band2L = formant20.rbj_bp(spl0);
  band3L = formant30.rbj_bp(spl0);
  band4L = formant40.rbj_bp(spl0);


  band1R = formant11.rbj_bp(spl1);
  band2R = formant21.rbj_bp(spl1);
  band3R = formant31.rbj_bp(spl1);
  band4R = formant41.rbj_bp(spl1);

  spl0 = band1L + band2L + band3L + band4L;
  spl1 = band1R + band2R + band3R + band4R;


// Apply doubled up Loser saturation, if desired.

slider8 ? (

  spl0 = min(max( sin(max(min(spl0,1),-1)*foo)/bar ,-1) ,1);
  spl1 = min(max( sin(max(min(spl1,1),-1)*foo)/bar ,-1) ,1);

  spl0 = min(max( sin(max(min(spl0,1),-1)*foo)/bar ,-1) ,1);
  spl1 = min(max( sin(max(min(spl1,1),-1)*foo)/bar ,-1) ,1);
);


// Output gain

spl0 *= outgain;
spl1 *= outgain;


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

slider10 ? (

  dB0 = amp_dB * log(abs(spl0));
  dB1 = amp_dB * log(abs(spl1));

  (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);
  );

  spl0 = exp(dB0 / amp_dB) * sign(spl0);
  spl1 = exp(dB1 / amp_dB) * sign(spl1);
);

// EOF