// @title hw4.ck // @author Chris Chafe (cc@ccrma), Hongchan Choi (hongchan@ccrma), Reza Payami (rpayami@ccrma) // @desc homework 4, Music220a-2012 // @note a demonstration/template for auditory streaming // @version chuck-1.3.1.3 / ma-0.2.2c // @revision 1 // ------------------------------------------------------------- // instantiate UGens 3 => int nInsts; FMFS fm[nInsts]; for (int i; i < nInsts; ++i) { // start with instruments muted 0.0 => fm[i].out.gain; fm[i].out => NRev rev; rev.mix(0.07); rev => dac; } // arrays to hold loudnesses, pitch register transpositions, // channels, fm instrument parameters float loud[]; // loudness contour (100dB = loudest) float tran[]; // pitch register contour e.g., 2.0 for octave higher, 4.0 for two octaves, etc. int chan[]; // channel contour float fmIndex[]; // fmIndex contour float fmRatio[]; // fmRatio contour float skew[]; // skew contour float vib[]; // vibrato contour float pitchOffset[]; // pitch transposition contour int keyn[]; // array to hold midi pitches (key numbers) int nPitches; // size of pitches cycle dur duration; //common note duration dur ioi; // starting inter-onset interval (inverse of tempo) 2::ms => dur minIoi; // minimum ioi float attack[]; //attack time contour float release[]; //release time contour // ------------------------------------------------------------- // Section A // ------------------------------------------------------------- [ 80, 68, 85, 80, 0, 0, 82, 66, 80, 82, 70, 0, 80, 68, 85, 80, 0, 0, 77, 0, 80, 0, 0, 0,78, 68, 80, 78, 0, 78, 75, 66, 77, 75, 70, 75, 77, 68, 77, 77, 0, 0, 73, 0, 73, 0, 0, 0,80, 68, 85, 80, 70, 0, 82, 68, 80, 82, 66, 0, 80, 65, 85, 80, 63, 0, 77, 65, 80, 0, 66, 0,78, 65, 80, 78, 0, 78, 75, 63, 77, 75, 0, 75, 77, 61, 77, 77, 0, 0, 73, 0, 73, 0, 0, 0 ] @=> keyn; keyn.cap() => nPitches; [80.0, 82.0, 67.0] @=> loud; [1.0, 1.0, 2.0] @=> tran; [0, 0, 0] @=> chan; [1.0, 3.0, 1.0] @=> fmIndex; [3.0, 1.0, 2.0] @=> fmRatio; [0.0, 0.0, 0.0] @=> skew; [0.0, 0.0, 0.0] @=> vib; [-12.0, -12.0, 0.0] @=> pitchOffset; 70::ms => duration; 800::ms =>ioi; [10.0, 30.0, 80.0] @=> attack; [200.0, 50.0, 200.0] @=> release; play(); 60::ms => now; // wait at the end of Section A // ------------------------------------------------------------- // Section B // ------------------------------------------------------------- [ 73, 80, 80, 80, 80, 83, 80, 80, 82, 78, 80, 80, 80, 78, 80, 0, 80, 83, 76, 76, 82, 73, 0, 80,73, 80, 80, 80, 80, 83, 80, 80, 82, 78, 80, 80, 80, 78, 80, 0, 80, 0, 76, 76, 80, 73, 0, 0,78, 78, 80, 0, 76, 78, 76, 76, 78, 75, 75, 78, 73, 75, 80, 0, 76, 76, 75, 73, 76, 0, 0, 76, 76, 78, 80, 76, 76, 83, 75, 76, 82, 75, 75, 80, 73, 75, 80, 0, 76, 0, 73, 73, 80, 0, 0, 0 ] @=> keyn; keyn.cap() => nPitches; [77.0, 75.0, 80.0] @=> loud; [1.0, 1.0, 1.0] @=> tran; [0, 0, 0] @=> chan; [2.5, 1.0, 2.2] @=> fmIndex; [5.5, 2.1, 4.5] @=> fmRatio; [0.0, 0.0, 0.0] @=> skew; [0.0, 0.0, 0.0] @=> vib; [7.0, -17.0, -5.0] @=> pitchOffset; 50::ms => duration; 1000::ms =>ioi; 2::ms => minIoi; [10.0, 2.0, 12.0] @=> attack; [7.0, 2.0, 7.0] @=> release; play(); 5000::ms => now; // wait at the end // ------------------------------------------------------------- fun void play() { // which pitch is next 0 => int p; // which instrument is next 0 => int i; 0 => int counter; while (counter < keyn.cap() * 2) { // print pitch index, instrument index chout <= "PITCH = " <= p <= "\t"; chout <= "INST. = " <= i <= "\t"; chout <= "KEYN. = " <= keyn[p] <= IO.newline(); if (keyn[p] != 0) { Std.mtof(keyn[p] + pitchOffset[i]) * 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]); fm[i].setVib(vib[i], 1); // start note fm[i].attack(attack[i]::ms); } // wait for sustain duration => now; if (keyn[p] != 0) { // stop note fm[i].release(release[i]::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.9 => ioi; // can't go faster than minIoi if (ioi < minIoi) { minIoi => ioi; } counter++; } } // ------------------------------------------------------------- // @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