//----------------------------------------------------------------------------- // name: supersaw.ck // desc: experiments with making a supersaw class // // author: Jack Atherton // //----------------------------------------------------------------------------- // Description of how to make a supersaw: // using a carrier wave "saw oscillator for example," // and modulating its signal using a comb filter // where the filter cutoff frequency is usually // modulated with an LFO, which the LFO's depth (or amplitude) // is equal to the saw oscillator's current frequency. // It can also be done by using a copied signal and // have the copy run throught a delay which the // delay's time is modulated again by an LFO where the // LFO's depth is equal to the saw oscillator's current frequency. class SupersawVoice extends VoiceBankVoice { // override 0.52 => highCutoffSensitivity; -0.52 => lowCutoffSensitivity; SawOsc osc => Gain out => Gain ampMod => LPF lpf => adsr; 15000 => lpf.freq; 12::ms => rTime; adsr.set( 40::ms, 72::ms, 0.85, rTime ); 5 => int numDelays; 1.0 / (1 + numDelays) => out.gain; DelayA theDelays[numDelays]; SinOsc lfos[numDelays]; dur baseDelays[numDelays]; float baseFreqs[numDelays]; for( int i; i < numDelays; i++ ) { theDelays[i] => out; 0.15::second => theDelays[i].max; // crucial to modify! Math.random2f( 0.001, 0.002 )::second => baseDelays[i]; lfos[i] => blackhole; // Math.random2f( -0.1, 0.1) => baseFreqs[i]; Math.random2f( 0, pi ) => lfos[i].phase; } fun void connectDelays() { for( int i; i < numDelays; i++ ) { osc => theDelays[i]; } } fun void disconnectDelays() { for( int i; i < numDelays; i++ ) { osc =< theDelays[i]; } } // 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); } // track /* fun void RespondToCutoff() { while( true ) { myCutoff => cutoffToHz => lpf.freq; 10::ms => now; } } spork ~ RespondToCutoff(); */ 0.05::second => dur baseDelay; 0.333 => float baseFreq; 1 => float lfoGain; fun void AttachLFOs() { while( true ) { for( int i; i < numDelays; i++ ) { baseFreq + baseFreqs[i] => lfos[i].freq; lfoGain * lfos[i].last()::second + baseDelay + baseDelays[i] => theDelays[i].delay; } 1::ms => now; } } spork ~ AttachLFOs(); SinOsc pitchLFO => blackhole; 0.77 => pitchLFO.freq; 1.0 / 300 => float pitchLFODepth; 0.13 => float ampModRate; // default 440 => myFreq; fun void FreqMod() { while( true ) { // calc freq myFreq + ( myFreq * pitchLFODepth ) * pitchLFO.last() => float f; // set f => osc.freq; 1.0 / f => lfoGain; // seconds per cycle == gain amount // wait 1::ms => now; } } spork ~ FreqMod(); fun void AmpMod() { SinOsc lfo => blackhole; while( true ) { ampModRate => lfo.freq; 0.85 + 0.15 * lfo.last() => ampMod.gain; 1::ms => now; } } spork ~ AmpMod(); fun void delay( dur d ) { d => baseDelay; } fun void timbreLFO( float f ) { f => baseFreq; } fun void pitchLFORate( float f ) { f => pitchLFO.freq; } fun void pitchLFODepthMultiplier( float r ) { r => pitchLFODepth; } fun void ampModLFO( float r ) { r => ampModRate; } fun void noteOn() { connectDelays(); adsr.keyOn( true ); } fun void noteOff() { adsr.keyOff( true ); disconnectDelays(); } } HPF hpf => LPF lpf => NRev reverb => dac; 1800 => hpf.freq; 5300 => lpf.freq; 200 => hpf.freq; 6000 => lpf.freq; 0.05 => reverb.mix; class Supersaw extends VoiceBank { 6 => numVoices; // voices SupersawVoice myVoices[numVoices]; // assign to superclass v.size( myVoices.size() ); for( int i; i < myVoices.size(); i++ ) { myVoices[i] @=> v[i]; } // connect init( true ); // other params for( int i; i < numVoices; i++ ) { // how much freq waves up and down as a % of current freq 1.2 / 300 => myVoices[i].pitchLFODepthMultiplier; // how quickly the freq lfo moves 3.67 => myVoices[i].pitchLFORate; // higher == wider pitch spread 0.33 => myVoices[i].timbreLFO; } } Supersaw saws => hpf; 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; saws.noteOn( m, velocity ); //<<< "on", m, v >>>; } fun void NoteOff( int m ) { spork ~ saws.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 >>>; } }