class SquareCelloVoice extends StereoVoiceBankVoice { // override 0.15 => gainAtZeroVelocity; 0.4 => highCutoffSensitivity; -0.2 => lowCutoffSensitivity; 2 => int unison; 0.5 => float osc1Width => float osc2Width; 400 => float myLPFCutoff; DelayA chorusL => LPF lpfL => adsrL; DelayA chorusR => LPF lpfR => adsrR; SqrOsc osc1[unison]; SqrOsc osc2[unison]; SawOsc osc3[unison]; Pan2 pans[unison]; SinOsc root => lpfL; root => lpfR; 0.2 => root.gain; 1.0 => float spread; // osc 1: sqrosc +7c, 46% volume // osc 2: sqrosc -7c, 31% volume // osc 3: sawosc +0c, 30% volume for( int i; i < unison; i++ ) { 0.46 / unison => osc1[i].gain; 0.31 / unison => osc2[i].gain; 0.30 / unison => osc3[i].gain; osc1[i] => pans[i]; osc2[i] => pans[i]; osc3[i] => pans[i]; Std.scalef( i, 0, unison-1, -spread, spread ) => pans[i].pan; pans[i] => lpfL; pans[i] => lpfR; pans[i] => chorusL; pans[i] => chorusR; } // chorus 1.3Hz, "8%" intensity 0.2::second => chorusL.max => chorusR.max; SinOsc chorusLFO => blackhole; 1.3 => chorusLFO.freq; 0.08 => chorusL.gain => chorusR.gain; fun void doChorus() { 3::ms => dur baseDelay; while( true ) { 0.3::ms * chorusLFO.last() + baseDelay => chorusL.delay => chorusR.delay; 5::ms => now; } } spork ~ doChorus(); TriOsc lfo1 => blackhole; 1.9 => float lfo1BaseFreq => lfo1.freq; fun void startLFO1() { -0.25 => lfo1.phase; while( true ) { // scale 0.19 * lfo1.last() + 0.7 => osc1Width; 5::ms => now; } } spork ~ startLFO1(); TriOsc lfo2 => blackhole; 0.59 => lfo2.freq; fun void startLFO2() { -0.25 => lfo2.phase; float lfo2last; while( true ) { // scale lfo2.last() => lfo2last; 0.13 * lfo2last + 0.83 => osc2Width; if( lfo2last > 0 ) { Std.scalef( lfo2last, 0, 1, 1, 1.72 ) * lfo1BaseFreq => lfo1.freq; } else { Std.scalef( lfo2last, 0, -1, 1, 0.9232 ) * lfo1BaseFreq => lfo1.freq; } 5::ms => now; } } spork ~ startLFO2(); fun void sync() { for( int i; i < unison; i++ ) { // TODO: necessary? // 0 => osc1[i].phase => osc2[i].phase; } } // lpf cutoff fun float cutoffToHz( float cutoff ) { return Math.min( Std.scalef( Math.pow( Std.clampf( cutoff, 0, 1 ), 3 ), 0, 1, myFreq, 18000 ), 18000 ); } // LPF cutoff envelope ADSR lpfEnv => blackhole; lpfEnv.set( 0.1::ms, 0.05::ms, 1.0, 10000::ms ); fun void triggerLPFEnv() { // set value to 0, then sustain level back to 1 to reset 0 => lpfEnv.value; 1 => lpfEnv.sustainLevel; 0.0 => float minCutoff; // higher cutoff at higher pitch and at higher velocity 0.47 => float tehTop; Std.scalef( Math.pow( myVelocity, 1.6 ), 0, 1, 0.45 * tehTop, tehTop ) => float maxCutoff; //<<< maxCutoff >>>; minCutoff => float currentCutoff; maxCutoff - minCutoff => float cutoffDiff; lpfEnv.keyOn( 1 ); 5::ms => dur delta; while( true ) { // set minCutoff + cutoffDiff * Math.pow( lpfEnv.value(), 1 ) => currentCutoff; // Math.pow( currentCutoff, 3 ) => currentCutoff; currentCutoff + myCutoff => this.cutoffToHz => myLPFCutoff; // wait delta => now; } } null @=> Shred triggerLPFEnvShred; fun void endLPFEnv() { lpfEnv.keyOff( 1 ); } // lpf is a little resonant 1.6 => lpfL.Q => lpfR.Q; // then ADSR on volume 1600::ms => rTime; adsrL.set( 980::ms, 99::ms, 1.0, rTime ); adsrR.set( 980::ms, 99::ms, 1.0, rTime ); // analog: randomly alter pitch and cutoff (0 to 1: 1. units?) 0.008 => float analog; float analogs[unison + 1]; fun void calculateAnalog() { for( int i; i < analogs.size(); i++ ) { Math.random2f( 1 - analog, 1 + analog ) => analogs[i]; } } fun void applyFreqs() { float f1, f2; while( true ) { Math.min( myLPFCutoff * analogs[unison], 21000 ) => lpfL.freq => lpfR.freq; // myFreq +- 7 cents myFreq * 1.004052 => f1; myFreq * 0.995965 => f2; for( int i; i < unison; i++ ) { f1 * analogs[i] => osc1[i].freq; f2 * analogs[i] => osc2[i].freq; myFreq * analogs[i] => osc3[i].freq; } f1 => root.freq; 10::ms => now; } } spork ~ this.applyFreqs(); // trigger note on fun void noteOn() { // sync sync(); // key on adsrL.keyOn( 1 ); adsrR.keyOn( 1 ); if( triggerLPFEnvShred != null ) { triggerLPFEnvShred.exit(); } spork ~ this.triggerLPFEnv() @=> triggerLPFEnvShred; calculateAnalog(); } // trigger note off fun void noteOff() { adsrL.keyOff( 1 ); adsrR.keyOff( 1 ); endLPFEnv(); } } class SquareCello extends StereoVoiceBank { 10 => numVoices; // voices SquareCelloVoice myVoices[numVoices]; // assign to superclass v.size( myVoices.size() ); for( int i; i < myVoices.size(); i++ ) { myVoices[i] @=> v[i]; } // connect init( true ); } SquareCello s; 0.3 => s.gain; LPF lpfL => JCRev revL => dac.left; LPF lpfR => JCRev revR => dac.right; s.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 => s.cutoff; } } spork ~ ApplyGlobals(); // end knobs global Event midiMessage; global int midiCommand; global int midiNote; global int midiVelocity; fun void NoteOn( int m, int v ) { v * 1.0 / 128 => float velocity; s.noteOn( m, velocity ); //<<< "on", m, v >>>; } fun void NoteOff( int m ) { spork ~ s.noteOff( m ); //<<< "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 >>>; } }