class VibratoSawLead extends StereoVoiceBankVoice { // override 0.5 => gainAtZeroVelocity; // TODO: this class doesn't respond to myCutoff 0.8 => highCutoffSensitivity; -0.8 => lowCutoffSensitivity; 5 => int unison; float myFreqWaver; LPF lpfL => adsrL; LPF lpfR => adsrR; 15000 => lpfL.freq => lpfR.freq; SawOsc osc1[unison]; SawOsc osc2[unison]; SawOsc osc3[unison]; Pan2 pans[unison]; SinOsc root => adsrL; root => adsrR; 0.2 => root.gain; // osc 1: sawosc +0c, 34% volume // osc 2: sawosc +3c, 33% volume // osc 3: sawosc -3c, 33% volume for( int i; i < unison; i++ ) { 0.34 / unison => osc1[i].gain; 0.33 / unison => osc2[i].gain; 0.33 / unison => osc3[i].gain; osc1[i] => pans[i]; osc2[i] => pans[i]; osc3[i] => pans[i]; Std.scalef( i, -0.5, unison - 0.5, -1, 1 ) => pans[i].pan; pans[i].left => lpfL; pans[i].right => lpfR; } fun void connect( UGen l, UGen r ) { theGainL => l; theGainR => r; } TriOsc lfo1 => Envelope lfo1env => blackhole; 480::ms => lfo1env.duration; 6.8 => lfo1.freq; fun void startLFO1() { 1 => lfo1env.keyOn; -0.25 => lfo1.phase; while( true ) { // TODO scale and hook up 0.03 * lfo1env.last() + 1 => myFreqWaver; 5::ms => now; } } null @=> Shred startLFO1Shred; fun void sync() { for( int i; i < unison; i++ ) { // TODO: necessary? Math.random2f( 0, 1 ) => osc1[i].phase; Math.random2f( 0, 1 ) => osc2[i].phase; Math.random2f( 0, 1 ) => osc3[i].phase; } if( startLFO1Shred != null ) { startLFO1Shred.exit(); } spork ~ startLFO1() @=> startLFO1Shred; } // then ADSR on volume 1200::ms => rTime; adsrL.set( 1::ms, 100::ms, 1, rTime ); adsrR.set( 1::ms, 100::ms, 1, rTime ); // analog: randomly alter pitch and cutoff (0 to 1: 1. units?) 0.006 => float analog; float analogs[unison]; fun void calculateAnalog() { for( int i; i < analogs.size(); i++ ) { Math.random2f( 1 - analog, 1 + analog ) => analogs[i]; } } fun void continuousAnalog() { while( true ) { calculateAnalog(); 20::ms => now; } } spork ~ continuousAnalog(); // osc1: freq // osc2: +3 cents fun void applyFreqs() { float f1, f2, f3; while( true ) { // myFreq + 3 cents myFreq * myFreqWaver => f1; f1 * 1.001734 => f2; f1 * 0.998269 => f3; for( int i; i < unison; i++ ) { f1 * analogs[i] => osc1[i].freq; f2 * analogs[i] => osc2[i].freq; f3 * analogs[i] => osc3[i].freq; } f1 => root.freq; 10::ms => now; } } spork ~ this.applyFreqs(); // trigger note on fun void noteOn( float midiNote, float velocity ) { midiNote => this.note; velocity => this.velocity; // sync sync(); // new analog values calculateAnalog(); // key on adsrL.keyOn( 1 ); adsrR.keyOn( 1 ); } // trigger note off fun void noteOff() { adsrL.keyOff( 1 ); adsrR.keyOff( 1 ); } } VibratoSawLead vi; 0.3 => vi.gain; LPF lpfL => JCRev revL => dac.left; LPF lpfR => JCRev revR => dac.right; vi.connect( lpfL, lpfR ); 0.05 => revL.mix => revR.mix; 15000 => lpfL.freq => lpfR.freq; // knobs global float gReverb; global float gCutoff; global float gLowpass; fun void ApplyGlobals() { while( true ) { 10::ms => now; gReverb => revL.mix => revR.mix; gLowpass => lpfL.freq => lpfR.freq; gCutoff => vi.cutoff; } } spork ~ ApplyGlobals(); // end knobs global Event midiMessage; global int midiCommand; global int midiNote; global int midiVelocity; int playingNote; fun void NoteOn( int m, int v ) { v * 1.0 / 128 => float velocity; m => playingNote; vi.noteOn( playingNote, velocity ); //<<< "on", m, v >>>; } fun void NoteOff( int m ) { if( m == playingNote ) { spork ~ vi.noteOff(); } //<<< "off", m >>>; } while( true ) { midiMessage => now; if( midiCommand >= 144 && midiCommand < 160 ) { if( midiVelocity > 0 ) { NoteOn( midiNote, midiVelocity ); } else { NoteOff( midiNote ); } } else if( midiCommand >= 128 && midiCommand < 144 ) { NoteOff( midiNote ); } else { //<<< "unknown midi command:", midiCommand, midiNote, midiVelocity >>>; } }