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