// ----------------Jason Riggs - Slorktastic Instrument--------------------------- // // For a description of the instrument, visit: // http://cm-wiki.stanford.edu/wiki/Supersaw // // ------------------------------------------------------------------------------- // -----------------------global constants---------------------------| // max number of notes allowed at once 8 => int kMaxPolyphony; // midi value of base (default) note 12 => int kBaseNote; // there will be a bank of oscillators of this amount 8 => int kNumOscillators; // they will be detuned from one another by a maximum of this value (as a freq) 4.0 => float kMaxDetuneSpread; // other constants 0.05 => float kMasterGain; 20.0 => float kMinLpfFreq; 20000.0 => float kMaxLpfFreq; // used for filter scaling function 2.0 => float kMaxxPos; 4 => int kCurvePow; 0.64 => float kLpfSensitivity; // set from 0 to 1 // lpf sensitivity power (used in power fn) kMaxxPos => float xPos; // this value will be used in the function detuneSpreader to fix a volume // problem inherent in the way the oscillators are being detuned. see comment // inside detuneSpreader for more info. it is being placed here because we need // to calculate it only once. (kMasterGain/(2*kMaxDetuneSpread)) => float kSlope; // an array of adsr envelopes, where the 10 different digits on the keyboard // are each assigned a set of 4 values (a, d, s, and r). each time a note is // pressed, that note's adsr envelope is set to the 4 values that correspond // with the array entry of the digit that was most recently pressed. note // the a, d, and r values must be converted from the floats in this array // into durational values when they are used to set an envelope. [ [2.0, 1.6, .75, 2.0], [.001, .1, 0.0, 0.0], [.001, .2, 0.0, 0.0], [.001, .4, 0.0, 0.0], [.001, .8, 0.0, 0.0], [.001, 0.0, 1.0, 0.0], [.25, .1, .95, .25], [.5, .2, .9, .5], [1.0, .4, .85, 1.0], [1.5, .8, .8, 1.5] ] @=> float envelopes[][]; // default envelope value 5 => int kDefaultEnvelope; // ---------------------end global constants-------------------------| // ---------------------global variables-------------------------| // current # notes playing 0 => int currentNotesPlaying; // register stuff 3 => int register; 0 => int reg_change; // detune amount kMaxDetuneSpread / 2 => float detuneSpread; // sensitivity (instrument control) .008 => float detuneSensitivity; 24.0 => float lpfSensitivity; // envelope durations envelopes[kDefaultEnvelope][0]::second => dur att; envelopes[kDefaultEnvelope][1]::second => dur dec; envelopes[kDefaultEnvelope][2] => float sus; envelopes[kDefaultEnvelope][3]::second => dur rel; // -------------------end global variables-----------------------| // -----------------------master patch---------------------------| // LPF LPF l; kMaxLpfFreq => l.freq; 8.0 => l.Q; // reverb JCRev r; 0.05 => r.mix; // gain Gain g; (kSlope)*detuneSpread + (kMasterGain / 2) => g.gain; // patchbay l => r => g => dac; // ---------------------end master patch-------------------------| // ---------------------spork stuff-------------------------| // spork input device control shreds spork~keyboardControl(); spork~trackpadControl(); // -------------------end spork stuff-----------------------| // -----------------------keyboard mapping---------------------------| // key mapping array int key[256]; // fretboard mapping 0 => key[29]; 1 => key[27]; 2 => key[6]; 3 => key[25]; 4 => key[5]; 5 => key[4] => key[17]; 6 => key[22] => key[16]; 7 => key[7] => key[54]; 8 => key[9] => key[55]; 9 => key[10] => key[56]; 10 => key[20] => key[11]; 11 => key[26] => key[13]; 12 => key[8] => key[14]; 13 => key[21] => key[15]; 14 => key[23] => key[51]; 15 => key[28] => key[52]; 16 => key[24]; 17 => key[12]; 18 => key[18]; 19 => key[19]; 20 => key[47]; 21 => key[48]; 22 => key[49]; // which is current 0 => int current; // each key gets a unique noteOff (for polyphony) Event noteOffs[256]; // increment register fun void registerUp() { if( register < 6 ) { register++; 1 => reg_change; } <<< "register:", register >>>; } // decrement register fun void registerDown() { if( register > 0 ) { register--; 1 => reg_change; } <<< "register:", register >>>; } // ---------------------end keyboard mapping-------------------------| // ---------------------keyboard input-------------------------| // the device number to open 0 => int keyboardNum; // instantiate a HidIn object HidIn keyboardIn; // structure to hold HID messages HidMsg keyboardMsg; // open keyboard if( !keyboardIn.openKeyboard( keyboardNum ) ) me.exit(); // successful! print name of device <<< "keyboard '", keyboardIn.name(), "' ready" >>>; fun void keyboardControl() { while( true ) { // wait on event keyboardIn => now; // get one or more messages while( keyboardIn.recv( keyboardMsg ) ) { // if out of bounds, ignore command if( keyboardMsg.which > 256 ) continue; // select envelope if( keyboardMsg.which >= 30 && keyboardMsg.which <= 39 && keyboardMsg.isButtonDown()) { setEnvelope(keyboardMsg.which); } // change register else if( key[keyboardMsg.which] == 0 && keyboardMsg.which != 29 ) { if( keyboardMsg.which == 80 && keyboardMsg.isButtonDown() ) registerDown(); else if( keyboardMsg.which == 79 && keyboardMsg.isButtonDown() ) registerUp(); } // play notes else if( keyboardMsg.isButtonDown() ) { // restrict polyphony if(currentNotesPlaying + 1 <= kMaxPolyphony) { currentNotesPlaying++; spork~playSound(keyboardMsg.which, noteOffs[ keyboardMsg.which]); } else { <<< "YOU ARE TRYING TO PLAY TOO MANY NOTES AT ONCE. SLAP UPSIDE THE HEAD" >>>; } } else { noteOffs[ keyboardMsg.which].signal(); } } } } // -------------------end keyboard input-----------------------| // -------------------trackpad input-----------------------| // the device number to open 0 => int trackpadNum; // instantiate a HidIn object HidIn trackpadIn; // structure to hold HID messages HidMsg trackpadMsg; // open mouse 0, exit on fail if( !trackpadIn.openMouse( trackpadNum ) ) me.exit(); // successful! print name of device <<< "mouse '", trackpadIn.name(), "' ready" >>>; fun void trackpadControl() { // infinite event loop while( true ) { // wait on HidIn as event trackpadIn => now; // messages received while( trackpadIn.recv( trackpadMsg ) ) { // mouse motion if( trackpadMsg.isMouseMotion() ) { // axis of motion if( trackpadMsg.deltaX ) { //<<< "mouse motion:", trackpadMsg.deltaX, "on x-axis" >>>; filterSweep(trackpadMsg.deltaX); } else if( trackpadMsg.deltaY ) { //<<< "mouse motion:", trackpadMsg.deltaY, "on y-axis" >>>; detuneSpreader(trackpadMsg.deltaY); } } // mouse button down else if( trackpadMsg.isButtonDown() ) { //<<< "mouse button", trackpadMsg.which, "down" >>>; } // mouse button up else if( trackpadMsg.isButtonUp() ) { //<<< "mouse button", trackpadMsg.which, "up" >>>; } // mouse wheel motion (requires chuck 1.2.0.8 or higher) else if( trackpadMsg.isWheelMotion() ) { // axis of motion if( trackpadMsg.deltaX ) { //<<< "mouse wheel:", trackpadMsg.deltaX, "on x-axis" >>>; } else if( trackpadMsg.deltaY ) { //<<< "mouse wheel:", trackpadMsg.deltaY, "on y-axis" >>>; } } } } } // -----------------end trackpad input---------------------| // -------------------trackpad control-----------------------| // x-axis filter sweep (intense scaling function makes filter smooth) fun void filterSweep(int change) { // function makes the LPF smooth across the trackpad 1.0/(800.0 - 400.0*kLpfSensitivity) * change +=> xPos; if(xPos < 0) 0 => xPos; if(xPos > kMaxxPos) kMaxxPos => xPos; ((kMaxLpfFreq - kMinLpfFreq)/(Math.pow(kMaxxPos, kCurvePow)))* Math.pow(xPos, kCurvePow) + kMinLpfFreq => float newFreq; newFreq => l.freq; } // y-axis detune change fun void detuneSpreader(int change) { if(detuneSpread >= 0 && detuneSpread <= kMaxDetuneSpread) { // subtraction reverses y-axis direction detuneSpread - detuneSensitivity*change => float newDetuneSpread; if(newDetuneSpread < 0) 0 => detuneSpread; else if(newDetuneSpread > kMaxDetuneSpread) kMaxDetuneSpread => detuneSpread; else newDetuneSpread => detuneSpread; } // detune spread is now where we want it, so alter master gain accordingly (kSlope)*detuneSpread + (kMasterGain / 3.0) => g.gain; } // -----------------end trackpad control---------------------| // ---------------------keyboard control-------------------------| // digit keys select one of the 10 possible envelopes fun void setEnvelope(int message) { // find digit int digit; if(message == 39) 0 => digit; else (message - 30 + 1) => digit; // set global envelope values based upon digit envelopes[digit][0]::second => att; envelopes[digit][1]::second => dec; envelopes[digit][2] => sus; envelopes[digit][3]::second => rel; } // plays a note fun void playSound(int message, Event noteOff) { // oscillators SawOsc s[kNumOscillators]; SinOsc sin; .75 => sin.gain; Noise n; .16 => n.gain; // envelope ADSR env; sus => float curNoteSus; rel => dur curNoteRel; env.set(att, dec, sus, curNoteRel); // connect oscillators to dac via patchbay Std.rand2(0, dac.channels() - 1) => int whichChannel; // CURRENTLY UNUSED for(int i; i < kNumOscillators; i++) { s[i] => env; } sin => env; n => env; // connect env to master patchbay env => l; // convert key pressed into frequency Std.mtof( kBaseNote + (register*12) + key[message] ) => float freq; // set each oscillator to a slightly different tuning for(int i; i < kNumOscillators; i++) { freq + Std.rand2f(-detuneSpread, detuneSpread) => s[i].freq; } // set sin to one octave beneath s (freq / 2) => sin.freq; // trigger note env.keyOn(); // if there's no release, then shreds can be removed // immediately after decay phase of adsr if(curNoteRel == 0::second && curNoteSus == 0.0) { att + dec => now; //env.keyOff(); //env =< dac; currentNotesPlaying--; return; } // wait for note to be released noteOff => now; // end note env.keyOff(); curNoteRel => now; env =< dac; currentNotesPlaying--; } // -------------------end keyboard control-----------------------| // -------------------run-----------------------| while(true) { 1::day => now; } // -----------------end run---------------------|