// @title hw4-starter.ck // @author Chris Chafe (cc@ccrma), Hongchan Choi (hongchan@ccrma) // @desc A starter code for homework 4, Music220a-2012 // @note a demonstration/template for auditory streaming // @version chuck-1.3.1.3 / ma-0.2.2c // @revision 1 // ------------------------------------------------------------- // "Try me, I don't break into multiple streams... why?" // // ANSWER: the streaming illusion demonstrates grouping by // similarity here there is only one group of sounds and // therefore only one stream // ------------------------------------------------------------- // play a cycle of pitches 6 => int nPitches; // array to hold midi pitches (key numbers) int keyn[nPitches]; [63, 65, 64, 66, 65, 67] @=> keyn; // ------------------------------------------------------------- // against a cycle of a different length which varies instrument // parameters 3 => int nInsts; // arrays to hold loudnesses, pitch register transpositions, // channels, fm instrument parameters // loudness contour (100dB = loudest) [80.0, 85.0, 90.0] @=> float loud[]; // pitch register contour // e.g., 2.0 for octave higher, 4.0 for two octaves, etc. [1.0, 1.0, 2.0] @=> float tran[]; // channel contour [0, 1, 0] @=> int chan[]; // fmIndex contour [1.0, 4.0, 8.0] @=> float fmIndex[]; // fmRatio contour [8.0, 4.0, 2.0] @=> float fmRatio[]; // skew contour [0.2, 0.4, 0.8] @=> float skew[]; // instantiate UGens FMFS fm[3]; for (int i; i < 3; ++i) { // start with instruments muted 0.0 => fm[0].out.gain; fm[0].out => NRev abc; abc.mix(0.1); fm[0].out => dac.left; 0.0 => fm[1].out.gain; fm[1].out => dac.left; 0.0 => fm[2].out.gain; fm[2].out => dac.right; } // ------------------------------------------------------------- // global parameters for section 1 // set a common note duration 100::ms => dur duration; // starting inter-onset interval (inverse of tempo) 1000::ms => dur ioi; // accelerate to this smallest ioi 10::ms => dur minIoi; // which pitch is next 0 => int p; // which instrument is next 0 => int i; 15::second + now => time end1; //Section 1 while (now < end1) { // print pitch index, instrument index chout <= "PITCH = " <= p <= "\t\t"; chout <= "INST. = " <= i <= IO.newline(); Std.mtof(keyn[p]) * tran[i] => float tmp; // assign pitch fm[i].setPitch(tmp); // assign loudness after converting to amplitude fm[i].out.gain(Math.dbtorms(loud[i])); // assign modulator index fm[i].setIndex(fmIndex[i]); // assign modulator / carrier frequency ratio fm[i].setRatio(fmRatio[i]); // assign pitch skew fm[i].setSkew(skew[i]); // start note fm[i].attack(100::ms); // wait duration => now; // stop note fm[i].release(10::ms); // increment note and instrument p++; i++; // cycle pitch through full array nPitches %=> p; // cycle instrument through full array nInsts %=> i; // advance time by interval and calculate the next time interval ioi => now; // accelerate ioi * 0.8 => ioi; // can't go faster than minIoi if (ioi < minIoi) { minIoi => ioi; } } //Section 2 instrument settings arrays // play a cycle of pitches 3 => int nPitches2; // array to hold midi pitches (key numbers) int keyn2[nPitches2]; [65, 64, 63] @=> keyn2; // ------------------------------------------------------------- // against a cycle of a different length which varies instrument // parameters 3 => int nInsts2; // arrays to hold loudnesses, pitch register transpositions, // channels, fm instrument parameters // loudness contour (100dB = loudest) [90.0, 85.0, 80.0] @=> float loud2[]; // pitch register contour // e.g., 2.0 for octave higher, 4.0 for two octaves, etc. [1.0, 2.0, 1.0] @=> float tran2[]; // channel contour [1, 0, 0] @=> int chan2[]; // fmIndex contour [.8, .16, .32] @=> float fmIndex2[]; // fmRatio contour [.32, .16, .8] @=> float fmRatio2[]; // skew contour [0.8, 0.6, 1.0] @=> float skew2[]; // instantiate UGens FMFS fm2[3]; for (int i; i < 3; ++i) { // start with instruments muted 0.0 => fm2[0].out.gain; fm2[0].out => NRev abc; abc.mix(0.1); fm2[0].out => dac.right; 0.0 => fm2[1].out.gain; fm2[1].out => dac.left; 0.0 => fm2[2].out.gain; fm2[2].out => dac.right; } // global parameters for section 2 // set a common note duration 100::ms => dur duration2; // starting inter-onset interval (inverse of tempo) 1000::ms => dur ioi2; // accelerate to this smallest ioi 10::ms => dur minIoi2; // which pitch is next 0 => int p2; // which instrument is next 0 => int i2; 15::second + now => time end2; //Section 2 while (now < end2) { // print pitch index, instrument index chout <= "PITCH = " <= p2 <= "\t\t"; chout <= "INST. = " <= i2 <= IO.newline(); Std.mtof(keyn2[p2]) * tran2[i2] => float tmp2; // assign pitch fm2[i2].setPitch(tmp2); // assign loudness after converting to amplitude fm2[i2].out.gain(Math.dbtorms(loud2[i])); // assign modulator index fm2[i2].setIndex(fmIndex2[i]); // assign modulator / carrier frequency ratio fm2[i2].setRatio(fmRatio2[i]); // assign pitch skew fm2[i2].setSkew(skew2[i]); // start note fm2[i2].attack(100::ms); // wait duration => now; // stop note fm2[i2].release(10::ms); // increment note and instrument p2++; i2++; // cycle pitch through full array nPitches2 %=> p2; // cycle instrument through full array nInsts2 %=> i2; // advance time by interval and calculate the next time interval ioi2 => now; // accelerate ioi2 * 0.8 => ioi2; // can't go faster than minIoi if (ioi2 < minIoi2) { minIoi2 => ioi2; } } //Section 3 instrument settings arrays // play a cycle of pitches 8 => int nPitches3; // array to hold midi pitches (key numbers) int keyn3[nPitches3]; [58, 59, 60, 61, 62, 63, 64, 65] @=> keyn3; // ------------------------------------------------------------- // against a cycle of a different length which varies instrument // parameters 3 => int nInsts3; // arrays to hold loudnesses, pitch register transpositions, // channels, fm instrument parameters // loudness contour (100dB = loudest) [85.0, 80.0, 90.0] @=> float loud3[]; // pitch register contour // e.g., 2.0 for octave higher, 4.0 for two octaves, etc. [2.0, 1.0, 1.0] @=> float tran3[]; // channel contour [0, 0, 1] @=> int chan3[]; // fmIndex contour [.16, .32, .64] @=> float fmIndex3[]; // fmRatio contour [.64, .32, .16] @=> float fmRatio3[]; // skew contour [1.0, 0.6, 0.8] @=> float skew3[]; // instantiate UGens FMFS fm3[3]; for (int i; i < 3; ++i) { // start with instruments muted 0.0 => fm3[0].out.gain; fm3[0].out => NRev abc; abc.mix(0.1); fm3[0].out => dac.left; 0.0 => fm3[1].out.gain; fm3[1].out => dac.right; 0.0 => fm3[2].out.gain; fm3[2].out => dac.left; } // global parameters for section 3 // set a common note duration 100::ms => dur duration3; // starting inter-onset interval (inverse of tempo) 1000::ms => dur ioi3; // accelerate to this smallest ioi 10::ms => dur minIoi3; // which pitch is next 0 => int p3; // which instrument is next 0 => int i3; 20::second + now => time end3; //Section 3 while (now < end3) { // print pitch index, instrument index chout <= "PITCH = " <= p3 <= "\t\t"; chout <= "INST. = " <= i3 <= IO.newline(); Std.mtof(keyn3[p3]) * tran3[i3] => float tmp3; // assign pitch fm3[i3].setPitch(tmp3); // assign loudness after converting to amplitude fm3[i3].out.gain(Math.dbtorms(loud3[i])); // assign modulator index fm3[i3].setIndex(fmIndex3[i]); // assign modulator / carrier frequency ratio fm3[i3].setRatio(fmRatio3[i]); // assign pitch skew fm3[i3].setSkew(skew3[i]); // start note fm3[i3].attack(100::ms); // wait duration => now; // stop note fm3[i3].release(10::ms); // increment note and instrument p3++; i3++; // cycle pitch through full array nPitches3 %=> p3; // cycle instrument through full array nInsts3 %=> i3; // advance time by interval and calculate the next time interval ioi3 => now; // accelerate ioi3 * 0.8 => ioi3; // can't go faster than minIoi if (ioi3 < minIoi3) { minIoi3 => ioi3; } } // ------------------------------------------------------------- // @class FMFS fm implementation from scratch with envelopes // @author Chris Chafe (cc@ccrma) class FMFS { // modulator with index envelope and carrier with // amplitude envelope SinOsc mod => Gain ind => ADSR indEnv => SinOsc car; car => ADSR ampEnv => Gain out => blackhole; // generate detailed pitch contour with skew, periodic // vibrato and random jitter // unity constant for center pitch of event Step unity => Gain pit; // add in skew offset controlled by simple Envelope Step skew => Envelope skewEnv => pit; // add in vibrato controlled by ADSR Envelope SinOsc perVib => ADSR vibEnv => pit; // add in some low-frequency randomness Noise ranVib => ResonZ lpf => pit; // apply pitch everywhere that depends on it pit => car; pit => Gain rat => mod; pit => ind; // configure modes of above UG's // config oscillator for fm input (see SinOsc class) 2 => car.sync; // freq is controlled by input only 0.0 => car.freq; // config oscillator for fm input 2 => mod.sync; // freq is controlled by input only 0.0 => mod.freq; // config gain to multiply inputs (see UG class) 3 => ind.op; // initial values setPitch(440.0); setIndex(1.0); setRatio(1.0); // set A, D, S, and R all at once ampEnv.set (10::ms, 10::ms, 0.5, 100::ms); indEnv.set (50::ms, 50::ms, 1, 100::ms); skewEnv.duration (100::ms); vibEnv.set (50::ms, 500::ms, 0.4, 100::ms); // skew in semitones setSkew(1.0); // vibrato frequency, excursion in semitones setVib(6.5, 1.0); // randomness frequency, excursion in semitones setJit(6.5, 1.0); // setPitch() fun void setPitch(float pitch) { pit.gain(pitch); } // setIndex() fun void setIndex(float index) { mod.gain(index); } // setRatio() fun void setRatio(float ratio) { rat.gain(ratio); } // setSkew() fun void setSkew(float semitones) { // units are equal-tempered semitones (Math.pow(2.0, semitones/12.0) - 1.0) => float skewAmt; skew.next( skewAmt ); } // setVib() fun void setVib(float f, float semitones) { perVib.freq(f); // scale it to equal-tempered quartertones // in each direction Math.pow(2.0, semitones/24.0) - 1.0 => float jitAmt; perVib.gain( jitAmt ); } // setJit() fun void setJit(float f, float semitones) { lpf.freq(f); // bandwidth of low-frequency filter resonance, // ok as a constant lpf.Q(1.0); // scale it to equal-tempered quartertones // in each direction Math.pow(2.0, semitones/24.0) - 1.0 => float jitAmt; // empirically scaled up to where it's noticeable 12.0 *=> jitAmt; ranVib.gain(jitAmt); } // attack(): sculpt a note using envelopes fun void attack(dur attack) { // rise time of ADSR ampEnv.attackTime(attack); indEnv.attackTime(attack); vibEnv.attackTime(attack); // duration of simple Envelope skewEnv.duration(attack); // trigger ADSR ampEnv.keyOn(); indEnv.keyOn(); vibEnv.keyOn(); // kind of counterintuitive, but skew from skewAmt // to 0 for attack skewEnv.keyOff(); } // release() fun void release(dur release) { // release time of ADSR ampEnv.releaseTime(release); indEnv.releaseTime(release); vibEnv.releaseTime(release); // duration of simple Envelope skewEnv.duration(release); // trigger ADSR release ampEnv.keyOff(); indEnv.keyOff(); vibEnv.keyOff(); // also counterintuitive, but skew from 0 // to skewAmt during note off skewEnv.keyOn(); } } // END OF CLASS: FMFS