desc:simple three-op mono pm sine-only synthesizer

slider1:60<0,1000,1>Mod 1 Attack (ms)
slider2:300<1,1000,1>Mod 1 Release (ms)
slider3:2<1,9,1>Mod 1 Ratio
slider4:-50<-90,-30,0.1>Mod 1 Index (dB)
slider5:0.1<0,1,0.001>Mod 1 Feedback (hundredths of %)
slider6:150<0,1000,1>Mod 2 Attack (ms)
slider7:300<1,1000,1>Mod 2 Release (ms)
slider8:1<1,9,1>Mod 2 Ratio
slider9:-35<-90,-30,0.1>Mod 2 Index (dB)
slider10:20<0,500,1>Carrier Attack (ms)
slider11:1000<1,3000,1>Carrier Release (ms)
slider12:-3<-99,0,1>Detune (cents)
slider13:0<-90,15,0.1>Output Level (dB)

@init
global.ph_inc = 2*$pi/srate;

modA1.attack = exp(-1/(srate*0.001*slider1));
modA1.release = exp(-1/(srate*0.001*slider2));
modA1.ratio = slider3;
modA1.index = 2^(slider4/6);
modA1.feedback = slider5/100;
modB1.attack = exp(-1/(srate*0.001*slider6));
modB1.release = exp(-1/(srate*0.001*slider7));
modB1.ratio = slider8;
modB1.index = 2^(slider9/6);

modA2.attack = modA1.attack;
modA2.release = modA1.attack;
modA2.ratio = modA1.ratio;
modA2.index = modA1.index;
modA2.feedback = modA1.feedback;
modB2.attack = modB1.attack;
modB2.release = modB1.release;
modB2.ratio = modB1.ratio;
modB2.index = modB1.index;

carrier1.attack = exp(-1/(srate*0.001*slider10));
carrier1.release = exp(-1/(srate*0.001*slider11));
carrier1.gain = 2^(slider13/6);

carrier2.attack = carrier1.attack;
carrier2.release = carrier1.release;
carrier2.gain = carrier1.gain; 
carrier2.detune = slider12/4;
carrier2.blend = 1;

modA1.attack_vol = modA1.release_vol = 0;
modB1.attack_vol = modB1.release_vol = 0;
carrier1.attack_vol = carrier1.release_vol = 0;

modA2.attack_vol = modA2.release_vol = 0;
modB2.attack_vol = modB2.release_vol = 0;
carrier2.attack_vol = carrier2.release_vol = 0;

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


function HP_init(hcutoff)
 instance (hp_cut, hp_n, hp_b1, hp_a0, hoff)
(
  hoff = max(20,hcutoff);
  hp_cut = 2*$pi*hoff;
  hp_n = 1/(hp_cut+3*srate);
  hp_b1 = (3*srate-hp_cut)*hp_n;
  hp_a0 = hp_cut*hp_n;
);

function HP(input)
 instance(hin,hout)
(
 hin = input;
 hout = 2*hin*this.hp_a0+hout*this.hp_b1; 
 hout = (hin-hout);
 hout;
);

function LP_init(lcutoff)
 instance (lp_cut, lp_n, lp_b1, lp_a0, loff)
(
  loff=min(lcutoff,20000);
  lp_cut = 2*$pi*loff;
  lp_n = 1/(lp_cut+3*srate);
  lp_b1 = (3*srate-lp_cut)*lp_n;
  lp_a0 = lp_cut*lp_n;
);

function LP(input)
 instance(lin,lout)
(
  lin  = input;
  lout = 2*lin*this.lp_a0 + lout*this.lp_b1;
  lout;
);

//--- DC Blocker ---//

itm1=itm2=otm1=otm2=0;
itm3=itm3=otm4=otm4=0;

dcblock0.HP_init(250);
dcblock1.HP_init(250);

//--- end ---//

@slider
modA1.attack = exp(-1/(srate*0.001*slider1));
modA1.release = exp(-1/(srate*0.001*slider2));
modA1.ratio = slider3;
modA1.index = 2^(slider4/6);
modA1.feedback = slider5/100;
modB1.attack = exp(-1/(srate*0.001*slider6));
modB1.release = exp(-1/(srate*0.001*slider7));
modB1.ratio = slider8;
modB1.index = 2^(slider9/6);

modA2.attack = modA1.attack;
modA2.release = modA1.attack;
modA2.ratio = modA1.ratio;
modA2.index = modA1.index;
modA2.feedback = modA1.feedback;
modB2.attack = modB1.attack;
modB2.release = modB1.release;
modB2.ratio = modB1.ratio;
modB2.index = modB1.index;

carrier1.attack = exp(-1/(srate*0.001*slider10));
carrier1.release = exp(-1/(srate*0.001*slider11));
carrier1.gain = 2^(slider13/6);

carrier2.attack = carrier1.attack;
carrier2.release = carrier1.release;
carrier2.gain = carrier1.gain; 
carrier2.detune = slider12/4;
carrier2.blend = 1;

@block
while (
midirecv(ts,msg1,msg23) ? (
m = msg1 & 240;
(m == 9*16) ? (
note = msg23 & 127;
vel = (msg23/256) | 0;

vel > 0 ? (

vol = vel/127;

modA1.attack_vol = modA1.release_vol = 1;
modB1.attack_vol = modB1.release_vol = 1;

carrier1.attack_vol = vol;
carrier1.release_vol = 1;

carrier2.attack_vol = vol;
carrier2.release_vol = 1;

modA2.attack_vol = modA2.release_vol = 1;
modB2.attack_vol = modB2.release_vol = 1;

carrier1.freq = 440 * pow(2.0,(note-69.0)/12.0);
modA1.freq = carrier1.freq * modA1.ratio;
modB1.freq = carrier1.freq * modB1.ratio;

carrier2.freq = (440 * pow(2.0,(note-69.0)/12.0))-carrier2.detune;
modA2.freq = carrier2.freq * modA2.ratio;
modB2.freq = carrier2.freq * modB2.ratio;

global.ph_ctr = 0;

filter1.HP_init(carrier1.freq*2);
filter1.LP_init(carrier1.freq*10);

filter2.HP_init(carrier2.freq*2);
filter2.LP_init(carrier2.freq*10);
);

); // noteon?
midisend(ts,msg1,msg23);
); // midirecv
); // whle

@sample


modA1.attack_vol *= modA1.attack;
modB1.attack_vol *= modB1.attack;
carrier1.attack_vol *= carrier1.attack;

modA2.attack_vol *= modA2.attack;
modB2.attack_vol *= modB2.attack;
carrier2.attack_vol *= carrier2.attack;

modA1.release_vol *= modA1.release;
modB1.release_vol *= modB1.release;
carrier1.release_vol *= carrier1.release;

modA2.release_vol *= modA2.release;
modB2.release_vol *= modB2.release;
carrier2.release_vol *= carrier2.release;

modA1.vol = (1-modA1.attack_vol) * modA1.release_vol;
modB1.vol = (1-modB1.attack_vol) * modB1.release_vol;
carrier1.vol = (1-carrier1.attack_vol) * carrier1.release_vol;

modA2.vol = (1-modA2.attack_vol) * modA2.release_vol;
modB2.vol = (1-modB2.attack_vol) * modB2.release_vol;
carrier2.vol = (1-carrier2.attack_vol) * carrier2.release_vol;

carrier1.vol = c1rmsvol.RMS(carrier1.vol,5);
carrier2.vol = c2rmsvol.RMS(carrier2.vol,5);

modA1.fdbk = sin(global.ph_ctr * modA1.freq) * modA1.feedback;
modA1.val = sin((global.ph_ctr + modA1.fdbk) * modA1.freq);
modA1.val = modA1.vol * modA1.val * modA1.index;
modB1.val = sin(global.ph_ctr * modB1.freq);
modB1.val = modB1.vol * modB1.val * modB1.index;

modA2.fdbk = sin(global.ph_ctr * modA2.freq) * modA2.feedback;
modA2.val = sin((global.ph_ctr + modA2.fdbk) * modA2.freq);
modA2.val = modA2.vol * modA2.val * modA2.index;
modB2.val = sin(global.ph_ctr * modB2.freq);
modB2.val = modB2.vol * modB2.val * modB2.index;

carrier1.val = cos(carrier1.freq * (global.ph_ctr+modA1.val+modB1.val));
carrier1.out = carrier1.val * carrier1.gain * carrier1.vol;

carrier2.val = cos(carrier2.freq * (global.ph_ctr+modA2.val+modB2.val));
carrier2.out = carrier2.val * carrier2.gain * carrier2.vol * carrier2.blend;

//--- 6dB filters ---//


carrier1.out = filter1.HP(carrier1.out);
carrier1.out = filter1.LP(carrier1.out);

carrier2.out = filter2.HP(carrier2.out);
carrier2.out = filter2.LP(carrier2.out);


//--- ----//


carrier1.out = c1rmsout.RMS(carrier1.out,5);
carrier2.out = c2rmsout.RMS(carrier2.out,5);

//--- DC Blocker ---//

otm1=0.99*otm1 + carrier1.out - itm1;
  itm1=carrier1.out;
  carrier1.out=otm1;
otm2=0.99*otm2 + carrier2.out - itm2;
  itm2=carrier2.out;
  carrier2.out=otm2;

//--- ---//

makeupgain = 2^(12/6);

carrier1.out *= makeupgain;
carrier2.out *= makeupgain;

spl0 += (carrier1.out + carrier2.out*0.707);
spl1 += (carrier2.out + carrier1.out*0.707);

spl0 = dcblock0.HP(spl0);
spl1 = dcblock1.HP(spl1);

//--- DC Blocker ---//

otm3=0.99*otm3 + spl0 - itm3;
  itm3=spl0;
  spl0=otm3;
otm4=0.99*otm4 + spl1 - itm4;
  itm4=spl1;
  spl1=otm4;

//--- ---//


global.ph_ctr += global.ph_inc;
