class GlassPluckVoice extends VoiceBankVoice { // overrides 0.5 => gainAtZeroVelocity; 0.4 => highCutoffSensitivity; -0.3 => lowCutoffSensitivity; 1 => int unison; 4 => float attackPitchModifier; 400 => float myLPFCutoff; 1 => float myFreqModifier; LPF lpf => adsr; WaveTable osc1[unison]; WaveTable osc2[unison]; WaveTable osc3[unison]; // oscs, gain, widths for( int i; i < unison; i++ ) { 0.25 / unison => osc1[i].gain; 0.18 / unison => osc2[i].gain; 0.50 / unison => osc3[i].gain; osc1[i] => lpf; osc2[i] => lpf; osc3[i] => lpf; osc1[i].init( me.dir() + "glass1_c2_3cycle.wav", 36 ); osc2[i].init( me.dir() + "glass2_c2_1cycle.wav", 36 ); osc3[i].init( me.dir() + "glass3_c2_2cycle.wav", 36 ); } fun void sync() { for( int i; i < unison; i++ ) { 0 => osc1[i].phase => osc2[i].phase => osc3[i].phase; } } Envelope pitchEnv => blackhole; fun void PitchEnv() { 20::ms => pitchEnv.duration; 1 => pitchEnv.value; 0 => pitchEnv.target; now + pitchEnv.duration() => time end; while( now < end ) { // TODO: consider inverting 4 to 0.25, also sounds cool Std.scalef( Math.pow( pitchEnv.value(), 4 ), 0, 1, 1, attackPitchModifier ) => myFreqModifier; 0.1::ms => now; } 1 => myFreqModifier; } // cubic scale 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; 0.05 => float lpfSustain; 280::ms => dur lpfDecay; lpfEnv.set( 1::ms, lpfDecay, lpfSustain, 89::ms ); fun void triggerLPFEnv() { // reset 0 => lpfEnv.value; lpfSustain => lpfEnv.sustainLevel; // bounds Math.pow( myVelocity, 1.6 ) => float v; Std.scalef( v, 0, 1, 0.2, 0.45 ) => float minCutoff; // higher cutoff at higher velocity Std.scalef( v, 0, 1, 0.192, 0.85 ) + minCutoff => float maxCutoff; //<<< this.cutoffToHz( minCutoff ), this.cutoffToHz( maxCutoff ) >>>; //<<< minCutoff, maxCutoff >>>; minCutoff => float currentCutoff; maxCutoff - minCutoff => float cutoffDiff; lpfEnv.keyOn( 1 ); 5::ms => dur delta; while( true ) { // 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; fun void endLPFEnv() { lpfEnv.keyOff( 1 ); } // resonances 1.0 => lpf.Q; // then ADSR on volume 380::ms => rTime; adsr.set( 2.7::ms, 400::ms, 0.4, rTime ); // osc1: freq // osc2: +3 // osc3: -3 fun void applyFreqs() { float f1, f2, f3; while( true ) { Math.min( myLPFCutoff, 21000 ) => lpf.freq; // myFreq myFreq * myFreqModifier => f1; // +3c f1 * 1.001734 => f2; // -3c f1 * 0.998269 => f3; for( int i; i < unison; i++ ) { f1 => osc1[i].freq; f2 => osc2[i].freq; f3 => osc3[i].freq; } 0.5::ms => now; } } spork ~ this.applyFreqs(); fun void setAttackTypeUpwards( int upwards ) { if( upwards ) { 0.25 => attackPitchModifier; } else { 4 => attackPitchModifier; } } // Overrides // trigger note on fun void noteOn() { // key on adsr.keyOn( 1 ); triggerLPFEnvShred.exit(); spork ~ this.triggerLPFEnv() @=> triggerLPFEnvShred; sync(); spork ~ PitchEnv(); } // trigger note off fun void noteOff() { adsr.keyOff( 1 ); endLPFEnv(); } } class GlassPluck extends VoiceBank { // override 8 => numVoices; // voices GlassPluckVoice myVoices[numVoices]; // assign to superclass v.size( myVoices.size() ); for( int i; i < myVoices.size(); i++ ) { myVoices[i] @=> v[i]; } // and connect init( true ); // custom function fun void setAttackTypeUpwards( int upwards ) { for( int i; i < myVoices.size(); i++ ) { myVoices[i].setAttackTypeUpwards( upwards ); } } } GlassPluck g => LPF l => JCRev rev => dac; 0.3 => g.gain; 0.0 => rev.mix; // does the pitch approach from 2 octaves above or below? g.setAttackTypeUpwards( true ); 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 => g.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; g.noteOn( m, velocity ); //<<< "on", m, v >>>; } fun void NoteOff( int m ) { spork ~ g.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 >>>; } }