class FMFS

    { // two uses of ADSR envelope...

        Step unity => ADSR envM => blackhole; //...as a separate signal

        SinOsc mod => blackhole;

        SinOsc car => ADSR envC => JCRev rev => Gain out ; //..as a modifier of a signal

       

       

        car.gain(0.2);

        float freq, index, ratio, m;

        0 => m;

        rev.mix(m);

        fun void fm()

        {

            now => time start;

            

            while (true)

            {

                if (start + 30::second <= now) {

                m+.000001 => m;  

                rev.mix(m);

                envM.last() * index => float indexEnv;

                mod.gain(freq); 

                mod.freq( (freq * ratio / pi) );

                car.freq( freq + mod.last()/2 );

                1::samp => now;

                }

                else {

                envM.last() * index => float indexEnv;

                mod.gain( freq * pi * indexEnv); 

                mod.freq( freq * ratio * indexEnv );

                car.freq( freq + mod.last()*2 );

                1::samp => now;

            }

            }

            

        }

        spork ~fm();

        

        //function to play a note on our FM instrument

        //this function 

        fun void playFM( dur length, float cFreq, float cADSR[], float mRatio, float mGain, float mADSR[] ) 

        {

            //set frequency values

            cFreq => freq;

            mRatio => ratio;

            mGain => index;

            //open and close envelopes

            spork ~ playEnv( envC, length, cADSR );

            spork ~ playEnv( envM, length, mADSR );

            

            length => now;

        }

        

        fun void playEnv( ADSR env, dur length, float adsr[] )

        {

            //get values for carrier ADSR envelope

            length * adsr[0] => dur A;

            length * adsr[1] => dur D;

            adsr[2] => float S;

            length * adsr[3] => dur R;

            

            //set ADSR envelope for envc

            env.set( A, D, S, R );

            // open envelope (start attack)

            env.keyOn();

            // wait through A+D+S, before R

            length-env.releaseTime() => now;

            // close envelope (start release)

            env.keyOff();

            // wait for release

            env.releaseTime() => now;

        }

        

    }

   

   

   

    // define the "clip" as a function

    fun void clip(dur myDur)

    {

        [60, 62, 64, 65] @=> int keyn[];

        

        // how many pitches are in the array

        keyn.cap() => int nP;

        

        // -------------------------------------------------------------

        // against a cycle of a different length which we'll use to vary 

        // instrument parameters

        nP - 1 => int nI;

        

        FMFS fm[nI];

        float cAmp[nI];

        float cADSR[nI][0];

        float mRatio[nI];

        float mGain[nI];

        float mADSR[nI][0];

        for (int i; i < nI; ++i) 

        {

            [.01,.4,.5,.1] @=> cADSR[i];

            2 => mRatio[i];

            mRatio[i] + 2.0*i => mRatio[i];

            1.0 => mGain[i];

            mGain[i] + 5.0*i => mGain[i];

            <<<mGain[i]>>>;

            [.01,.4,1.0,.1] @=> mADSR[i];

            fm[i].out => dac.chan(0);

        }

        

        // -------------------------------------------------------------

        // global parameters

        

        // set a common note duration

        100::ms => dur duration;

        // starting inter-onset interval (inverse of tempo)

        800::ms => dur ioi;

        // accelerate to this smallest IOI (inter-onset-interval - the length of silence between notes!)                

        83.8::ms => dur minIoi;

        // which pitch is next

        0 => int p;

        // which instrument is next                         

        0 => int i;

        

        now => time start;

        // infinite loop

        while (start + myDur >= now) {

            // print pitch index, instrument index

            <<< "P =", p, "\tI =", i >>>;

            Std.mtof(keyn[p]) => float cFreq;

            // assign pitch

            spork ~fm[i].playFM(duration, cFreq, cADSR[i], mRatio[i], mGain[i], mADSR[i]);

            // increment note and instrument

            p++;

            i++;

            // cycle pitch through full array

            nP %=> p;

            // cycle instrument through full array

            nI %=> i;

            

            // advance time by interval and calculate the next time interval

            ioi => now;

            // accelerate

            if (ioi > minIoi) 

                ioi * 0.95 => ioi;

            else

                // can't go faster than minIoi 

                minIoi => ioi;

            

        } 

    }

    

    fun void drums(dur myDur) {

        

        "/Users/maxmelin/Desktop/Kick04.wav" => string kick;

        if( me.args() ) me.arg(0) => kick;

        

        "/Users/maxmelin/Desktop/Closedhat01.wav" => string hat;

        if( me.args() ) me.arg(0) => hat;

        

        "/Users/maxmelin/Desktop/Clap02.wav" => string clap;

        if( me.args() ) me.arg(0) => clap;

        

        // the patch 

        SndBuf buf1 => dac.chan(2);

        SndBuf buf2 => dac.chan(2);

        SndBuf buf3 => dac.chan(2);

        

        // load the file

        kick => buf1.read;

        250::ms => now;

        hat => buf2.read;

        250::ms => now;

        clap => buf3.read;

        

        //loops

        buf1.loop(1);

        buf1.freq(2);


        

        buf2.loop(1);

        buf2.freq(2);

        

        

        buf3.loop(1);

        buf3.freq(1);

        

        myDur => now;

    }

    

    fun void bassNote(dur myDur, float freq) {

        now => time start;

        SinOsc m => SinOsc c => LPF low => dac.chan(3);

        2 => c.sync;    

        1000 =>  m.gain;

        900 => float f;

        low.freq(f);

        while (start+myDur>=now) {

            freq => float carFreq;

            float modFreq;

            .05 => c.gain;

            (145*carFreq/220) => modFreq;

            carFreq => c.freq;

            modFreq => m.freq;

            1::samp => now;

        }

    }


    //RUNNING THE CLIPS

    now => time time0;

    spork ~clip(60::second); // launch clip in independent shred

    15::second => now; // this master shred needs to remain alive while it's playing

    spork ~drums(15::second); // launch clip in independent shred

    

  430 => float glob; 

  while (time0+45::second >= now) {


  spork ~bassNote(500::ms, glob/2);

  500::ms => now; // this master shred needs to remain alive while it's playing

  spork ~bassNote(375::ms, glob);

  375::ms => now;

  spork ~bassNote(375::ms, glob/1.12);

  375::ms => now;

  spork ~bassNote(250::ms, glob);

  250::ms => now;

  spork ~bassNote(500::ms, glob/1.33333);

  500::ms => now;

        }

       me.yield(); // on this exact sample, yield master shred so sporked one can finish first

    

    now => time time1;

    

    // last item in this program is this print statement

    <<<"clips played for",(time1-time0)/second,"seconds">>>;

    // and with nothing left to do this program exits