class HardPluckSawLead extends StereoVoiceBankVoice { // override 0.35 => gainAtZeroVelocity; 0.3 => highCutoffSensitivity; -0.7 => lowCutoffSensitivity; 5 => int unison; 400 => float myLPFCutoff; 1 => float myFreqModifier; LPF lpfL => adsrL; LPF lpfR => adsrR; Pan2 pans[unison]; SawOsc osc1[unison]; TriOsc osc2[unison]; // TODO: how to make a "sync" sawosc? SawOsc osc3[unison]; // osc 1: sawosc +0c, 40% volume // osc 2: triosc +33c, 40% volume // osc 3: sync sawosc -33c, 20% volume for( int i; i < unison; i++ ) { 0.40 / unison => osc1[i].gain; 0.40 / unison => osc2[i].gain; 0.20 / unison => osc3[i].gain; osc1[i] => pans[i]; osc2[i] => pans[i]; osc3[i] => pans[i]; Std.scalef( i, 0, unison - 1, -1, 1 ) => pans[i].pan; pans[i].left => lpfL; pans[i].right => lpfR; } fun void sync() { for( int i; i < unison; i++ ) { 0 => osc1[i].phase => osc2[i].phase => osc3[i].phase; } } // TODO: I am not sure how to do this without // it being insanely expensive fun void SyncOsc3() { while( true ) { for( int i; i < unison; i++ ) { if( osc1[i].phase() < 0.01 ) { 0 => osc3[i].phase; } } 1::samp => now; } } spork ~ SyncOsc3(); Envelope pitchEnv => blackhole; fun void PitchEnv() { 1 => pitchEnv.value; 0 => pitchEnv.target; 4.8::ms => pitchEnv.duration; repeat( 10 ) { Std.scalef( Math.pow( pitchEnv.value(), 4 ), 0, 1, 1, 2 ) => myFreqModifier; 0.5::ms => now; } 1 => myFreqModifier; } // 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; 470::ms => dur origDecayTime; lpfEnv.set( 0.02::ms, origDecayTime, 0.0, 0.0::ms ); fun void triggerLPFEnv() { // reset (also sets sustain to 0) 0 => lpfEnv.value; 0.114 => float minCutoff; // higher cutoff at higher pitch and at higher velocity Std.scalef( myVelocity, 0, 1, 0.44, 0.89 ) + Std.scalef( myMidi, 0, 128, 0, 0.4 ) => float maxCutoff; //<<< minCutoff, maxCutoff >>>; minCutoff => float currentCutoff; maxCutoff - minCutoff => float cutoffDiff; lpfEnv.keyOn( 1 ); 0.5::ms => dur delta; now + lpfEnv.attackTime() + lpfEnv.decayTime() + 2 * delta => time end; //<<< this.cutoffToHz( minCutoff ), this.cutoffToHz( maxCutoff ) >>>; while( now < end ) { // set minCutoff + cutoffDiff * Math.pow( lpfEnv.value(), 3 ) => currentCutoff; // Math.pow( currentCutoff, 3 ) => currentCutoff; currentCutoff + myCutoff => this.cutoffToHz => myLPFCutoff; // wait delta => now; } } spork ~ this.triggerLPFEnv() @=> Shred triggerLPFEnvShred; // lpf is not resonant 1.0 => lpfL.Q => lpfR.Q; // then ADSR with A = 0ms, D = 870ms, S = 0, R = 280ms 81::ms => rTime; adsrL.set( 0::ms, 1000::ms, 0.001, rTime ); adsrR.set( 0::ms, 1000::ms, 0.001, rTime ); // analog: randomly alter pitch and cutoff (0 to 1: 1. units?) 0.013 => float analog; float analogs[unison]; fun void calculateAnalog() { for( int i; i < unison; i++ ) { Math.random2f( 1 - analog, 1 + analog ) => analogs[i]; } } fun void applyFreqs() { float a; float f1, f2, f3; while( true ) { Math.min( myLPFCutoff * analogs[0], 21000 ) => lpfL.freq => lpfR.freq; // myFreq myFreq * myFreqModifier => float f1; // f1 + 33 cents f1 * 1.019244 => float f2; // f1 - 33 cents f1 * 0.981119 => float 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; } 0.5::ms => now; } } spork ~ this.applyFreqs() @=> Shred applyFreqsShred; // trigger note on fun void noteOn() { // somehow resetting phase is vital to // the attack not sounding mushy sync(); // key on adsrL.keyOn( 1 ); adsrR.keyOn( 1 ); triggerLPFEnvShred.exit(); spork ~ this.triggerLPFEnv() @=> triggerLPFEnvShred; calculateAnalog(); spork ~ PitchEnv(); } // trigger note off fun void noteOff() { adsrL.keyOff( 1 ); adsrR.keyOff( 1 ); } } HardPluckSawLead h; LPF lpfL => JCRev revL => dac.left; LPF lpfR => JCRev revR => dac.right; 0.6 => h.gain; h.connect( lpfL, lpfR ); 0.0 => 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 => h.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 => h.velocity; m => playingNote => h.note; h.noteOn(); //<<< "on", m, v >>>; } fun void NoteOff( int m ) { if( m == playingNote ) { spork ~ h.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 >>>; } }