class SawFluteVoice extends VoiceBankVoice { // override 0.5 => gainAtZeroVelocity; 0.1 => highCutoffSensitivity; -0.1 => lowCutoffSensitivity; 1 => int unison; float myFreqWaver; LPF lpf => adsr; SawOsc osc1[unison]; TriOsc osc2[unison]; // oscs for( int i; i < unison; i++ ) { 0.6 / unison => osc1[i].gain; 0.4 / unison => osc2[i].gain; osc1[i] => lpf; osc2[i] => lpf; } TriOsc lfo1 => Envelope lfo1env => blackhole; TriOsc lfo2 => lfo1env; 270::ms => lfo1env.duration; 2.7 => lfo1.freq; 3.6 => lfo2.freq; 0.5 => lfo1.gain => lfo2.gain; fun void startLFO1( float freq1, float freq2 ) { 1 => lfo1env.keyOn; -0.25 => lfo1.phase => lfo2.phase; freq1 => lfo1.freq; freq2 => lfo2.freq; while( true ) { // TODO scale and hook up 0.02 * lfo1env.last() + 1 => myFreqWaver; myFreq * myFreqWaver => float f1; f1 * 2.006943 => float f2; for( int i; i < unison; i++ ) { f1 => osc1[i].freq; f2 => osc2[i].freq; } 5::ms => now; } } null @=> Shred startLFO1Shred; fun void sync() { for( int i; i < unison; i++ ) { // TODO: necessary? // 0 => osc1[i].phase => osc2[i].phase; } if( startLFO1Shred != null ) { startLFO1Shred.exit(); } spork ~ startLFO1( Math.random2f( 2.7, 4.8 ), Math.random2f( 2.7, 4.8 ) ) @=> startLFO1Shred; } fun float cutoffToHz( float cutoff ) { return Math.min( Std.scalef( Math.pow( Std.clampf( cutoff, 0, 1 ), 3 ), 0, 1, myFreq, 18000 ), 18000 ); } // lpf resonance 1.6 => lpf.Q; // then ADSR on volume 75::ms => rTime; 0.491 => float adsrSustain; adsr.set( 80::ms, 140::ms, adsrSustain, rTime ); 0.05 => float baseCutoff; fun void setBaseFilterCutoff() { 0.05 + 0.15 * myVelocity + Std.scalef( myMidi, 0, 128, 0, 0.24 ) => baseCutoff; } fun void setFilterCutoff() { while( true ) { baseCutoff + myCutoff => this.cutoffToHz => lpf.freq; 10::ms => now; } } spork ~ setFilterCutoff(); // trigger note on fun void noteOn() { // sync sync(); // filter cutoff setBaseFilterCutoff(); // key on adsr.keyOn( 1 ); } // trigger note off fun void noteOff() { adsr.keyOff( 1 ); } } class SawFlute extends VoiceBank { 8 => numVoices; // voices SawFluteVoice myVoices[numVoices]; // assign to superclass v.size( myVoices.size() ); for( int i; i < myVoices.size(); i++ ) { myVoices[i] @=> v[i]; } // connect init( true ); } SawFlute s => LPF l => JCRev rev => dac; 0.3 => s.gain; 0.2 => rev.mix; 15000 => l.freq; // knobs global float gReverb; global float gCutoff; global float gLowpass; fun void ApplyGlobals() { while( true ) { 10::ms => now; gReverb => rev.mix; gLowpass => l.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 >>>; } }