// ----------------Permutations----------------| // // by Jason Riggs, Jacob Shenker, and Jay Bhat // // For a full description of the project, visit: // http://cm-wiki.stanford.edu/wiki/Permutations // // --------------end Permutations--------------| // -----------------------global constants---------------------------| // gain 0.03 => float kMasterGain; // keyboard thing 1 => int kKbdSpacebar; // max number of notes allowed at once 4 => int kMaxPolyphony; // midi value of base (default) note 24 => 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; // filter 20.0 => float kMinLpfFreq; 20000.0 => float kMaxLpfFreq; // timbre 4 => int kMaxTimbres; 0 => int kDefaultTimbre; // tempo range allowed (expressed as time between notes) 0.030::second => dur kMaxTempo; 8::second => dur kMinTempo; // 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, .01, 0.0, 0.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], [.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 2 => int kDefaultEnvelope; // ---------------------end global constants-------------------------| // ---------------------global variables-------------------------| // keyboard modifier keys 0 => int button_action; // tempo for autoplay 1000::ms => dur T; // note arrays (rows of GUI) //[[0, 2, 3, 7, 8, 13], //[0,1,2,3], //[0,-12,0,12]] @=> int rows[][]; // experimentation below [[16, 12, 9, 5], [0, -12, 0, -12], [0, -12, 0, 12]] @=> int rows[][]; // positions int positions[rows.cap()]; // timbre kDefaultTimbre => int timbre; // enable trackpad input (initially disabled) 0 => int trackpadEnabled; // 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 sound gen patch---------------------------| // LPF LPF l; kMaxLpfFreq => l.freq; 6.4 => l.Q; // reverb JCRev r; 0.064 => r.mix; // gain Gain g; (kSlope)*detuneSpread + (kMasterGain / 2) => g.gain; // limiter //Dyno d; //d.limit(); //0.1 => d.thresh; // patchbay l => r => g => dac; // ---------------------end master sound gen patch-------------------------| // -----------------------MAUI stuff---------------------------| MAUI_View control_view; MAUI_LED led_rows[rows.cap()][0];//[rows[0].cap()]; MAUI_Button button_rows[rows.cap()][0];//[rows[0].cap()]; control_view.size(rows[0].cap()*100, rows.cap()*100+50+50+50); MAUI_Button dummy_button; dummy_button.size(1,1); dummy_button.position(0,0); control_view.addElement(dummy_button); 0 => int play; MAUI_Button play_button; play_button.toggleType(); play_button.name("play (tilde)"); play_button.size(control_view.width()/3., 100); play_button.position(0, control_view.height()-100); control_view.addElement(play_button); fun void controlPlayButton() { while (true) { play_button => now; <<< "play button:", play_button.state() >>>; togglePlay(play_button.state()); } } spork ~ controlPlayButton(); MAUI_Button spacebar_button; spacebar_button.pushType(); spacebar_button.name("next (space)"); spacebar_button.size(control_view.width()/3., 100); spacebar_button.position(control_view.width()/3., control_view.height()-100); control_view.addElement(spacebar_button); fun void controlSpacebarButton() { while (true) { spacebar_button => now; advanceTime(); displayPositions(); playNow(); spacebar_button => now; // not needed for key-based "clicks" } } spork ~ controlSpacebarButton(); for (0 => int i; i < rows.cap(); i++) { MAUI_LED leds[rows[i].cap()] @=> led_rows[i]; MAUI_Button buttons[rows[i].cap()] @=> button_rows[i]; for (0 => int k; k < rows[i].cap(); k++) { leds[k] @=> MAUI_LED led; led.color(MAUI_LED.blue); led.size(30,30); led.position(k*100+20,i*100+35); control_view.addElement(led); buttons[k] @=> MAUI_Button button; button.pushType(); button.size(100,100); button.position(k*100,i*100+50); nameButton(i, k); control_view.addElement(button); } } displayPositions(); control_view.display(); // ---------------------end MAUI stuff-------------------------| // -----------------------sequencer helpers---------------------------| fun void change_action(int increment) { increment +=> button_action; if (button_action < 0) { 0 => button_action; } } fun void togglePlay(int state) { state => play; if (play == 1) { spork ~ timeLoop(); } //advanceTime(); //displayPositions(); } fun void timeLoop() { //T => now; while (play == 1) { displayPositions(); playNow(); advanceTime(); T => now; } } fun void nameButton(int i, int k) { button_rows[i][k] @=> MAUI_Button button; button.name(rows[i][k] + ""); } fun void controlButton(int i, int k) { button_rows[i][k] @=> MAUI_Button button; while( true ) { // wait for the button to be pushed down button => now; // button down <<< "button pushed:", button.name(), button_action >>>; if (button_action == 0) // click: swap k-th and (k+1)-th (modulo length of tone row) { (k+1) % rows[i].cap() => int k1; rows[i][k] @=> int temp; rows[i][k1] @=> rows[i][k]; temp @=> rows[i][k1]; nameButton(i, k1); } else if (button_action == 4) // click+shift: increment { 1 +=> rows[i][k]; } else if (button_action == 8) // click+apple: decrement { 1 -=> rows[i][k]; } else if (button_action == 6) // click+opt+shift: increment by an octave { 12 +=> rows[i][k]; } else if (button_action == 10) // click+opt+apple: decrement by an octave { 12 -=> rows[i][k]; } nameButton(i, k); button => now; // button up } } for (0 => int i; i < rows.cap(); i++) { for (0 => int k; k < rows[i].cap(); k++) { spork ~ controlButton(i, k); } } fun void displayPosition(MAUI_LED leds[], int position) { for (0 => int i; i < leds.cap(); i++) { if (position == i) { leds[i].light(); } else { leds[i].unlight(); } } } fun void displayPositions() { for (0 => int i; i < rows.cap(); i++) { displayPosition(led_rows[i], positions[i]); } } fun void advanceTime() { 1 => int advance; for (0 => int i; i < rows.cap(); i++) { if (advance) { (1 + positions[i]) % rows[i].cap() => positions[i]; } if (positions[i] == 0 && advance) { 1 => advance; } else { 0 => advance; } } } fun void playNow() { 0 => int note; for (0 => int i; i < rows.cap(); i++) { rows[i][positions[i]] +=> note; } // play sounds while restricting polyphony if(currentNotesPlaying + 1 <= kMaxPolyphony) { currentNotesPlaying++; Event e; spork~playSound(note, e); spork~killSound(e, att+dec+rel); } else { //<<< "Polyphony limit reached. Current # Notes Playing: ", currentNotesPlaying >>>; } } fun void killSound(Event e, dur note_time) { note_time => now; e.signal(); } // ---------------------end sequencer helpers-------------------------| // ---------------------spork stuff-------------------------| // spork input device control shreds spork~keyboardControl(); spork~trackpadControl(); // -------------------end spork stuff-----------------------| // -----------------------keyboard mapping---------------------------| // FRETBOARD MAPPING NOT CURRENTLY USED // 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; } if (keyboardMsg.which == 53 && keyboardMsg.isButtonDown()) { <<< "tilde down" >>>; play_button.state(!play_button.state()); togglePlay(play_button.state()); } else if (keyboardMsg.which == 44 && keyboardMsg.isButtonDown()) { <<< "space down" >>>; if (kKbdSpacebar == 1) { advanceTime(); displayPositions(); playNow(); } } else if (keyboardMsg.which == 44 && !keyboardMsg.isButtonDown()) { <<< "space up" >>>; // do stuff } else if (keyboardMsg.which == 224 && keyboardMsg.isButtonDown()) { <<< "ctl down" >>>; change_action(1); } else if (keyboardMsg.which == 224 && !keyboardMsg.isButtonDown()) { <<< "ctl up" >>>; change_action(-1); } else if (keyboardMsg.which == 226 && keyboardMsg.isButtonDown()) { <<< "opt down" >>>; change_action(2); } else if (keyboardMsg.which == 226 && !keyboardMsg.isButtonDown()) { <<< "opt up" >>>; change_action(-2); } else if (keyboardMsg.which == 225 && keyboardMsg.isButtonDown()) { <<< "shift down" >>>; change_action(4); } else if (keyboardMsg.which == 225 && !keyboardMsg.isButtonDown()) { <<< "shift up" >>>; change_action(-4); } else if (keyboardMsg.which == 227 && keyboardMsg.isButtonDown()) { <<< "apple down" >>>; change_action(8); } else if (keyboardMsg.which == 227 && !keyboardMsg.isButtonDown()) { <<< "apple up" >>>; change_action(-8); } // 'up' and 'down' control timbre else if( keyboardMsg.which == 81 || keyboardMsg.which == 82 ) { if( keyboardMsg.which == 81 && keyboardMsg.isButtonDown() ) { adjustTempo(keyboardMsg.which); } else if( keyboardMsg.which == 82 && keyboardMsg.isButtonDown() ) { adjustTempo(keyboardMsg.which); } } // select envelope else if( keyboardMsg.which >= 30 && keyboardMsg.which <= 39 && keyboardMsg.isButtonDown() ) { setEnvelope(keyboardMsg.which); } // enbable/disable trackpad input else if( keyboardMsg.which == 55 ) { if( keyboardMsg.isButtonDown() ) { 1 => trackpadEnabled; <<< "trackpadenabled" >>>; } else if( keyboardMsg.isButtonUp() ) { 0 => trackpadEnabled; <<< "trackpadDisabled" >>>; } } // timbre control else if( keyboardMsg.which == 47 || keyboardMsg.which == 48 ) { if( keyboardMsg.which == 47 && keyboardMsg.isButtonDown() ) { changeTimbre(keyboardMsg.which); } else if( keyboardMsg.which == 48 && keyboardMsg.isButtonDown() ) { changeTimbre(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 fun void filterSweep(int change) { if(trackpadEnabled) { if(l.freq() >= kMinLpfFreq && l.freq() <= kMaxLpfFreq) { l.freq() + lpfSensitivity*change => float newFreq; if(newFreq < kMinLpfFreq) { kMinLpfFreq => l.freq; } else if (newFreq > kMaxLpfFreq) { kMaxLpfFreq => l.freq; } else { newFreq => l.freq; } } } } // y-axis detune change fun void detuneSpreader(int change) { if (trackpadEnabled) { 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 // this fixes the problem inherent in detuning the oscillators whereby // more-detuned oscillators have lower average amplitude than less-detuned ones (kSlope)*detuneSpread + (kMasterGain / 3.0) => g.gain; } // -----------------end trackpad control---------------------| // ---------------------keyboard control-------------------------| // adjust tempo in multiples of 2 fun void adjustTempo(int message) { if(message == 82 && (T / 2) >= kMaxTempo) { T / 2 => T; <<< "Tempo: ", T/(1::second) >>>; } if(message == 81 && (T * 2) <= kMinTempo) { T * 2 => T; <<< "Tempo: ", T/(1::second) >>>; } } // "[" and "]" control changes in timbre fun void changeTimbre(int message) { // "[" if(message == 47) { // decrease timbre if(timbre > 0) timbre--; <<< "Current Timbre: ", timbre >>>; } // "]" if(message == 48) { // increase timbre if(timbre + 1 < kMaxTimbres) timbre++; <<< "Current Timbre: ", timbre >>>; } } // 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; <<< "Enevelope set: ", digit >>>; } // plays a note fun void playSound(int message, Event noteOff) { // envelope ADSR env; att => dur curNoteAtt; dec => dur curNoteDec; sus => float curNoteSus; rel => dur curNoteRel; env.set(curNoteAtt, curNoteDec, curNoteSus, curNoteRel); // set freqs and connect oscillators to envelopes Std.rand2(0, dac.channels() - 1) => int whichChannel; // CURRENTLY UNUSED // convert key pressed into frequency Std.mtof( kBaseNote + (register*12) + message ) => float freq; if(timbre == 0) { SinOsc oscs[kNumOscillators]; for(int i; i < kNumOscillators; i++) { freq + Std.rand2f(-detuneSpread, detuneSpread) => oscs[i].freq; oscs[i] => env; } } else if(timbre == 1) { TriOsc oscs[kNumOscillators]; for(int i; i < kNumOscillators; i++) { freq + Std.rand2f(-detuneSpread, detuneSpread) => oscs[i].freq; oscs[i] => env; } } else if(timbre == 2) { SqrOsc oscs[kNumOscillators]; for(int i; i < kNumOscillators; i++) { freq + Std.rand2f(-detuneSpread, detuneSpread) => oscs[i].freq; oscs[i] => env; } } else if(timbre == 3) { SawOsc oscs[kNumOscillators]; for(int i; i < kNumOscillators; i++) { Std.rand2(0, dac.channels() - 1) => int whichChannel; freq + Std.rand2f(-detuneSpread, detuneSpread) => oscs[i].freq; oscs[i] => env; } } // mmm... beeeeefy SinOsc sin; (freq / 2) => sin.freq; .75 => sin.gain; sin => env; // mmm... crispy (sinosc, no crisp for you) if(timbre != 0) { Noise n; .04 + .08 => n.gain; n => env; } // connect env to master patchbay env => l; // trigger note env.keyOn(); // 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---------------------|