// @title hw3-starter.ck // @author Chris Chafe (cc@ccrma), Hongchan Choi (hongchan@ccrma), Madeline Huberth (mhuberth@ccrma) // @desc A starter code for homework 3, Music220a-2018 // @note a demonstration/template for auditory streaming // ------------------------------------------------------------- // This code creates three FM instruments that, when played // at a slow rate, create one auditory stream. When the tempo is sped up, however, // different rhythms and melodic groupings pop out of the texture, due to your mind's // grouping of the sounds. // ------------------------------------------------------------- // array to hold midi pitches (key numbers) // these will be converted into the carrier frequencies [ 48, 60, 63, 65, 67, 70, 72, 75, 78] @=> 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; SndBuf kick => NRev r => dac; SndBuf clap => r; SndBuf hat => r; r.mix(.02); kick.gain(0); clap.gain(0); hat.gain(0); me.sourceDir() + "kick.wav" => kick.read; me.sourceDir() + "clap.wav" => clap.read; me.sourceDir() + "hat.wav" => hat.read; 1::second =>now; kick.gain(.6); clap.gain(.6 ); hat.gain(.5); //0 => kick.pos; //0 => clap.pos; // arrays to hold modulation frequency, timing of modulation envelope, // and timing/gain of carrier ADSRs // really, anything that we want to use to break the repeating pitches // into multiple streams // FMFS is a custom class, "FM From Scratch" // instantiate nI instances of the class // make arrays for carrier amplitude & carrier amplitude envelope breakpoints, // and modulation frequency ratio & index & modulation envelope breakpoints // ADSR stands for "Attack-Decay-Sustain-Release" FMFS fm[nI]; float cAmp[nI]; float cADSR[nI][0]; // 2d array, second dimension will hold an array of float values for ADSR float mRatio[nI]; float mIndex[nI]; float mADSR[nI][0]; for (0 => int i; i < nI; i++) { [.01,.4,.5,1.2] @=> cADSR[i]; 0.5 + Math.pow(i,3.5) => mRatio[i]; Math.pow(i,3) => mIndex[i]; <<<"instrument",i,"has modulation frequency ration of", mRatio[i]>>>; [.01,.4,1.0,.1] @=> mADSR[i]; fm[i].out => dac.chan(i%1); // all sound on left channel } // ------------------------------------------------------------- // 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!) 160::ms => dur minIoi; // variable for which pitch is next 0 => int p; // variable for which instrument is next 0 => int i; 0 => int counter; repeat (300) { // print pitch index, instrument index <<< "P =", p, "\tI =", i >>>; Std.mtof(keyn[p]) => float cFreq; // assign pitch if(counter < 200){ spork ~fm[i].playFM(duration, cFreq, cADSR[i], mRatio[i], mIndex[i], mADSR[i]); } else { spork ~fm[i].playFM(duration, cFreq, cADSR[i], mRatio[(i+counter) % nI], mIndex[i], mADSR[i]); } counter++; 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; if(counter > 60 && counter < 100){ [ 50, 61, 65, 65, 69, 75, 79, 81, 54] @=> keyn; } else { [ 48, 60, 63, 65, 67, 70, 72, 75, 78] @=> keyn; } // accelerate if (ioi > minIoi){ ioi * 0.96 => ioi; } else { // can't go faster than minIoi minIoi => ioi; if(!(counter % 4)){ 0 => kick.pos; } if(counter > 80){ if(!(counter % 8)){ 1000 => clap.pos; } } if(counter > 140){ Math.random2(1,5) => int trap_modulo; if(!(counter % trap_modulo )){ 0 => hat.pos; } } } } // ------------------------------------------------------------- // @class FMFS // fm implementation from scratch with envelopes // @author 2015 Madeline Huberth, 2016 version by CC class FMFS { // two typical uses of the ADSR envelope unit generator... Step unity => ADSR envM => blackhole; //...as a separate signal SinOsc mod => blackhole; SinOsc car => ADSR envC => Gain out; //...as an inline modifier of a signal car.gain(0.2); float freq, index, ratio; // the parameters for our FM patch fun void fm() // this patch is where the work is { while (true) { envM.last() * index => float currentIndex; // time-varying index mod.gain( freq * currentIndex ); // modulator gain (index depends on frequency) mod.freq( freq * ratio ); // modulator frequency (a ratio of frequency) car.freq( freq + mod.last() ); // frequency + modulator signal = FM 1::samp => now; } } spork ~fm(); // run the FM patch // function to play a note on our FM patch fun void playFM( dur length, float pitch, float cADSR[], float mRatio, float mGain, float mADSR[] ) { // set patch values pitch => freq; mRatio => ratio; mGain => index; // run the envelopes spork ~ playEnv( envC, length, cADSR ); spork ~ playEnv( envM, length, mADSR ); length => now; // wait until the note is done } fun void playEnv( ADSR env, dur length, float adsrValues[] ) { // set values for ADSR envelope depending on length length * adsrValues[0] => dur A; length * adsrValues[1] => dur D; adsrValues[2] => float S; length * adsrValues[3] => dur R; // set up ADSR envelope for this note env.set( A, D, S, R ); // start envelope (attack is first segment) env.keyOn(); // wait through A+D+S, before R length-env.releaseTime() => now; // trigger release segment env.keyOff(); // wait for release to finish env.releaseTime() => now; } } // END OF CLASS: FM