0 => int device; //MIDI device to open (see: chuck --probe) //thanks to: polyfony.ck (base code), fm2.ck (modulation code), Mike Mulshine's 808 code (ADSR/pitch envelope help), Chuck for Music (scale help for MEGAMODE) //inspired by VCV Rack, Chuck, Roland JD-XA //PRIMARY GLITCH: sometimes, playing with resonance and cutoff can cause the sound to cut out for ~10 seconds, //only to come back with a bunch of noise and really loud (the same kind of sound each time), //which fades out after ~10 seconds, and then the souund goes back to normal. //I have limited the possible values of filter cutoff & Q, and yet the problem persists. //todo: crossmod CV //todo: allow osc2 to be sinosc //future TODO: Make OSC2 a second voice (eg =>dac) (note: turn gain down 300x to 1x, turn cv 300x)? this would require for loops every place that osc2 gets updated, and triggering it with piano keys. //future TODO: panning? phasor? //mega future todo: universal patching 5=>int NUMOSCS; //1? 5? 20? The world is yours with an n-phonic synth! //thanks to wikipedia for help with scale intervals! I'm L E A R N I N G: https://en.wikipedia.org/wiki/Diatonic_scale#Modes [0,2,4,5,7,9,11,12] @=> int ionian[]; //thanks to "Notes on Chuck for Music". AKA Ionian scale [0,2,3,5,7,9,10,12] @=> int dorian[]; [0,1,3,5,7,8,10,12] @=> int phrygian[]; [0,2,4,6,7,9,11,12] @=> int lydian[]; [0,2,4,5,7,9,10,12] @=> int mixolydian[]; [0,2,3,5,7,8,10,12] @=> int aeolian[]; //natural minor scale [0,1,3,5,6,8,10,12] @=> int locrian[]; 0 => int MEGASCALE; //pick a scale, any scale! [0,1] @=> int menuvals[]; 0 => int menuselector => int paramselector; ["MEGAMODE?","Filter?","OSC2 => OSC1 CV","Filter Cutoff CV","Filter Resonance CV","Tremolo CV","Noise CV"] @=> string parameterstrings[]; [0.,1.,1.,0.,0.,0.,0.] @=> float parametervals[];//control voltages will go from 0 - 2x for further manipulation Gain CutoffCV; Gain QCV; Gain TremoloCV; Gain NoiseCV; 0 => CutoffCV.gain => QCV.gain => TremoloCV.gain => NoiseCV.gain; 1 => float MEGASWING; //0 => int ARPPATTERN; //0,1,2,3: UP, DOWN, UPDOWN, RND 0. => float ArpSpeed; //for arp() MidiIn min; MidiMsg msg; Hid hi; HidMsg kbmsg; ["Greetings,","Good day to you,","It's great to see you","I bid thee well","The Sun doth smile upon thee today","In a Twisted World it is a joy to see thee","I am honored by your presence"] @=> string greetings[]; ["O Great","Sir Fairest","Your Excellency","My Lord","My Dearest","The One and Only","My Liege","King of the Synths","Tiger King","Fartknocker"] @=> string titles[]; // try to open MIDI port (see chuck --probe for available devices) if( !min.open( device ) ) me.exit(); <<< greetings[Math.random2(0,greetings.cap()-1)],titles[Math.random2(0,titles.cap()-1)],min.name(),"!" >>>; if( !hi.openKeyboard( device ) ) me.exit(); <<< "I have ensured that" , hi.name() , "will serve as your companion for this long journey. Fare thee well!">>>; <<<"Take to the menu with a knob, or perhaps the arrow keys, if you should find yourself in Need.","">>>; // make our own event class NoteEvent extends Event { int note; int velocity; } // make our own event class KnobEvent extends Event { int id1; int id2; int amount; } // make our own event class ButtonEvent extends Event { int id1; int id2; float press; } Event @ offs[128]; NoteEvent on; KnobEvent knob; ButtonEvent buttonpress; //the main protagonist, osc PulseOsc osc[NUMOSCS]; 1=>float OSC1GAIN; 0.5 => float OSC1PW; Gain OSC1PWCV; 0 => OSC1PWCV.gain; 1=> float OSC1FINE; Math.random2(300,1000) => float OSC1FREQ; //start osc1 at a random frequency for(0=>int i; i<5; i++) { 2 => osc[i].sync; OSC1GAIN => osc[i].gain; OSC1PW => osc[i].width; OSC1FREQ => osc[i].freq; } // the base patch LPF filter => Echo delay => PRCRev r => Gain amp => dac; //set our variables Math.random2f(700,7000) => float FILTERFREQ; 1 => float FILTERQ; filter.set(FILTERFREQ,FILTERQ); .1 => amp.gain; 0 => delay.mix; -1 => delay.op; //start the delay as passthru, minimize glitches Math.random2f(0,0.15) => r.mix; //osc => oscEnv ADSR oscEnv[NUMOSCS]; for(0=>int i; i oscEnv[i]; //oscEnv[i].set(50::ms,150::ms,.35,50::ms); } //oscEnv => pitchEnv => filter Envelope pitchEnv[NUMOSCS]; 1 => float pitchEnvDepth; for (0=> int i; i pitchEnv[i].duration; pitchEnvDepth=> pitchEnv[i].target; oscEnv[i] => pitchEnv[i] => filter; } //noise component Noise aux[NUMOSCS]; 0=>float NOISEGAIN; for(0=>int i; iaux[i].gain; } //ADSR for each noise instance ADSR noiseEnv[NUMOSCS]; for(0=>int i; i noiseEnv[i] => filter; noiseEnv[i].set(1::ms, 30::ms, 0.0, 0.0::ms); } //LFO SinOsc LFO =>blackhole; Math.random2(15,30) => float LFOFREQ => LFO.freq; //TriOsc LFO_tri; //=>blackhole; //SqrOsc LFO_sqr; //=>blackhole; //OSC2 //DEAR USER: NEVER, NEVER, UNDER ANY CIRCSTUMANCES, //SHOULD YOU TAKE OSC2 OUT OF THE BLACKHOLE INTO DAC. EVER. //(at least turn down the gain from 300x first...) PulseOsc osc2 => blackhole; //want to change to sinosc as option 1. => float OSC2FINE; 0.5 => float OSC2PW; Gain OSC2PWCV; 0 => OSC2PWCV.gain; Math.random2(100,4000) => float OSC2FREQ=> osc2.freq; 0 => osc2.gain; //osc2 freq => osc1 freq Gain Osc1CV[NUMOSCS]; for(0=>int i; i Osc1CV[i] => osc[i]; } //set up osc1 self-feedback Gain feedback[NUMOSCS]; for(0=> int i; i feedback[i].gain; osc[i] => feedback[i] => osc[i]; } //set up feedback within delay delay => Gain delay_feedback => delay; 0 => delay_feedback.gain; //press a key to get us started! keyOn(0); //the LiSa loopers //0= nothing; 1= recording; 2= playing. //once state 2 is reached, will oscillate between states 0 and 2 [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] @=> int lisa_state[]; for(0=>int i; i<16; i++) spork ~ looper(i); fun void looper(int instance) { amp => LiSa lisa=>dac; 0.5 => lisa.gain; lisa.op(0);//speak only when spoken to 30::second => dur buffer => lisa.duration; 0::ms => dur len; lisa.recRamp(100::ms); //lisa.rampDown(10::ms); time earlier; while(true) { //global notification upon any button press buttonpress => now; if(buttonpress.id2 == instance) { //is the button calling to meee??? //transition button state lisa_state[instance]++; if(lisa_state[instance]==1) {//then start recording <<<"Lisa: \"go ahead, i'm listening..\"","">>>; lisa.op(1); // start recording input lisa.record(1); now => earlier; } else if(lisa_state[instance]==2) {//then stop recording, start playingz if(len==0::ms) { //stop recording lisa.record(0); //set up first-time playback if(len==0::ms) { now - earlier => len; if(len>buffer) buffer => len; } } <<<"Lisa: *hums along*","">>>; //start playing repeat(1) { lisa.op(1); //lisa.getVoice() => int n; lisa.loopEnd(len); lisa.playPos(0::ms); lisa.loopStart(150::ms);// note sure why i need this compensation! kinda macgyver'd it lisa.rate(1);// set playback rate lisa.loop(1); lisa.bi(0); //loop backwards? lisa.play(1); } } else if(lisa_state[instance]>2) {//then stop playing 1=> lisa_state[instance]; //next button press, go right back to playing! <<<"LISA: \"bye.\"","">>>; lisa.op(0); lisa.play(0); //30::second => dur buffer => lisa.duration; //if(instance%2!=0) } } } } //I made all of my modulation functions gain-indepdendentk //because that granted power to the CV //LFO => Filter Cutoff spork~cutoff_mod(); fun void cutoff_mod() { while(true) { while(CutoffCV.gain() >0) { ((LFO.last()*CutoffCV.gain()*(FILTERFREQ/100))/LFO.gain()) + FILTERFREQ => float val; //gain-independent filter-proportionate bipolar modulation if(val<100) 100=> val; if(val>15000) 15000=>val; //low values for "safety" :( if (!Math.isnan(val)) val => filter.freq; //<<<"filtermod: ", filter.freq()>>>; 1::ms => now; } //update the var to its correct value when modulation is taken away FILTERFREQ => filter.freq; 0.5::second => now; } } //LFO => Filter Resonance spork~Q_mod(); fun void Q_mod() { while(true) { while(QCV.gain() >0) { //<<< (Math.fabs(LFO_sin.last())*QCV.gain())>>>; ((LFO.last()*QCV.gain())/LFO.gain()) + FILTERQ => float val; //gain-independent unipolar modulation if(val<0.5) 0.5=>val; if(val>10) 10=>val; if (!Math.isnan(val)) val => filter.Q; //<<<"Q: ", filter.Q()>>>; 1::ms => now; } //update the var to its correct value when modulation is taken away FILTERQ => filter.Q; 0.5::second=>now; } } //LFO => OSC1 PW spork~OSC1PW_mod(); fun void OSC1PW_mod() { while(true) { while(OSC1PWCV.gain()>0) { (((LFO.last())*OSC1PWCV.gain())/LFO.gain()) + OSC1PW => float val; //gain-independent bipolar modulation if(val>1) 1=>val; if(val<0) 0=>val; if (!Math.isnan(val)) { for(0=>int i; i osc[i].width; } //<<<"pulse width: ", osc[0].width()>>>; 1::ms => now; } //update the var to its correct value when modulation is taken away for(0=>int i; i osc[i].width; 0.5::second => now; } } //LFO => OSC2 PW spork~OSC2PW_mod(); fun void OSC2PW_mod() { while(true) { while(OSC2PWCV.gain()>0) { (((LFO.last())*OSC2PWCV.gain())/LFO.gain()) + OSC2PW => float val; //gain-independent bipolar modulation if(val>1) 1=>val; if(val<0) 0=>val; if (!Math.isnan(val)) val => osc2.width; //<<<"pulse width: ", osc2.width()>>>; 1::ms => now; } //update the var to its correct value when modulation is taken away OSC2PW => osc2.width; 0.5::second => now; } } //LFO => OSC1.gain spork~tremolo_mod(); fun void tremolo_mod() { while(true) { while(TremoloCV.gain()>0) { (((LFO.last())*TremoloCV.gain())/LFO.gain()) + OSC1GAIN => float val; //gain-independent bipolar modulation if(val>1) 1=>val; if(val<0) 0=>val; if (!Math.isnan(val)) { for(0=>int i; i osc[i].gain; } 1::ms => now; } //update the var to its correct value when modulation is taken away for(0=>int i; i osc[i].gain; 0.5::second => now; } } //LFO => Noise.gain spork~noise_mod(); fun void noise_mod() { while(true) { while(NoiseCV.gain()>0) { ((((LFO.last())*NoiseCV.gain()))/LFO.gain()) + NOISEGAIN => float val; //gain-independent bipolar modulation if(val>1) 1=>val; if(val<0) 0=>val; if (!Math.isnan(val)) { for(0=>int i; i aux[i].gain; } 1::ms => now; } //update the var to its correct value when modulation is taken away for(0=>int i; i aux[i].gain; 0.5::second => now; } } //this function presses keys all by itself //you can also think of this as a SPEED MULTIPLIER for MEGAMODE! spork ~ arp(); fun void arp() { while(true) { float speedo; while(ArpSpeed> 0) { Math.random2(0,NUMOSCS-1) => int voice; //uhhhh my hacked method of voice management. just random. //wait in fragments of time, in case arp speed changes! 10 => int fragment; repeat(fragment) { (25/ArpSpeed)/fragment => speedo; if(ArpSpeed >0) speedo::ms=>now; else break; } keyOn(voice); repeat(fragment) { (25/ArpSpeed)/fragment => speedo; if(ArpSpeed >0) speedo::ms=>now; else break; } keyOff(voice); } 0.5::second => now; } } //self-playing mode,aka MEGAMODE. plays notes from the major scale because it is majorly cool. //controllable params: Speed (synced to LFO), Pitch(use pitch knob), Swing (use pitch env depth knob) spork ~ self_playing(); fun void self_playing() { float speedo; int note; while (true) { //while MEGAMODE enabled while(parametervals[0]==1) { //(Math.random2(-8,8) + Std.ftom(OSC1FREQ*OSC1FINE))$int => on.note; Math.random2(0,ionian.cap()-1) => int index; if(MEGASCALE==0) ionian[index] => note; else if(MEGASCALE==1) dorian[index] => note; else if(MEGASCALE==2) phrygian[index] => note; else if(MEGASCALE==3) lydian[index] => note; else if(MEGASCALE==4) mixolydian[index] => note; else if(MEGASCALE==5) aeolian[index] => note; else if(MEGASCALE==6) locrian[index] => note; //wait in fragments of time, in case LFO speed changes! 10 => int fragment; repeat(fragment) { LFO.freq() => speedo; if(speedo > 0) { (1/speedo)/fragment => speedo; speedo::second=>now; } else break; } //calculate freq; send on event note + (Std.ftom(OSC1FREQ * OSC1FINE)$int) => note; for(0 => int i; i osc[i].freq; //play friendly with arp note => on.note; on.signal(); repeat(fragment) { LFO.freq() => speedo; if(speedo > 0) { (1/speedo)/fragment => speedo; //calculate swing speedo + (MEGASWING*speedo*(Math.random2f(-0.5,1)))=> speedo; speedo::second=>now; } else break; } if(offs[note]!=null) offs[note].signal(); } 0.5::second=>now; } } //sets paramselector, prints new selection fun void menuselecthandler(int amount) { amount => paramselector; <<>>; } //assuming paramselector & corresponsing parametervals value is updated fun void menuvalhandler(float amount) { //paramselector maps to values in parametervals if(paramselector==0) { set_megamode(amount, 0); } else if(paramselector==1) { set_filter(amount,1); } else if(paramselector==2) { set_osc1cv(amount,2); } else if(paramselector==3) { set_cutoffcv(amount,3); } else if(paramselector==4) { set_qcv(amount,4); } else if(paramselector==5) { set_tremolocv(amount,5); } else if(paramselector==6) { set_noisecv(amount,6); } } //exponential response curve from 0.0002 - 15000 Hz fun void set_osc1freq(float amount) { .0002 * Math.pow(amount,3.75) => OSC1FREQ; OSC1FINE * OSC1FREQ => float freq; for(0=>int i; i osc[i].freq; 1=>oscEnv[i].gain; //reset gain (just in case!) } keyOn(0); //default to voice 0 <<<"OSC1 F-R-E-Q: ",OSC1FREQ,"Hz">>>; } //FINE and COARSE control separate parameters now stored in global variables //each time the freq updates it will reference both of these variables //OSC1FINE scale: 0.5x - 2x (thanks Wolfram Alpha for help with a good algorithm) fun void set_osc1fine(float amount) { 0.502649 * Math.pow(Math.e, 0.0108768*amount) => OSC1FINE; OSC1FINE * OSC1FREQ => float freq; for(0=>int i; i osc[i].freq; //oscEnv.keyOn(); <<<"OSC1 F-I-N-E:",OSC1FINE,"x">>>; } fun void set_osc2freq(float amount) { //exponential response curve from 0.0002 - 9000Hz .0002 * Math.pow(amount,3.5) => OSC2FREQ; OSC2FINE * OSC2FREQ => osc2.freq; <<<"OSC2 F-R-E-Q: ",osc2.freq(),"Hz">>>; } //OSC2FINE scale: 0.5x - 2x (thanks Wolfram Alpha for help with a good algorithm) fun void set_osc2fine(float amount) { 0.502649 * Math.pow(Math.e, 0.0108768*amount) => OSC2FINE; OSC2FINE * OSC2FREQ => osc2.freq; <<<"OSC2 F-I-N-E:",OSC2FINE,"x">>>; } fun void set_lfofreq(float amount) { //exponential response curve from 0.02 - 30Hz. //set min to .02Hz bc megamode must run through a full cycle before changing and .02 is plenty long. (.0002 * Math.pow(amount,2.6))+.02 => LFO.freq; <<<"LFO F-R-E-Q: ",LFO.freq(),"Hz">>>; } fun void set_parameterselect(float amount) { ((amount$int/3) % (parametervals.cap())) => int val; menuselecthandler(val); //room for 42 parameters with this granularity } //this knobs controls anything we select in the menu fun void set_controlknob(float amount) { //update the value of the selected parameter amount => parametervals[paramselector] => float val; menuvalhandler(val); } fun void set_reverb(float amount) { amount /127.0 => r.mix; <<<"R*E*V*E*R*B: ",r.mix()*100,"%">>>; } fun void set_delay(float amount) { //scale: 0 - 100 (amount /127.0) => delay.mix; if(amount==0) { delay.op(-1); //hopefully this will clean up the signal } else { delay.op(1); } <<<"D*E*L*A*Y: ",delay.mix()*100,"%">>>; } fun void set_delaytime(float amount) { //scale: 0 - 1 seconds (amount /127.0)::second => delay.max => delay.delay; <<<"D*E*L*A*Y T*I*M*E: ",delay.delay(),"samples">>>; } fun void set_delayfeedback(float amount) { //scale: ? risk management should be practiced here //:!!! note that this is a direct & raw feedback if used with delay off; Be warned!!! (amount /127.0) => delay_feedback.gain; <<<"D*E*L*A*Y F*E*E*D*B*A*C*K: ",delay_feedback.gain()*100,"%">>>; } fun void set_osc1gain(float amount) { (amount/127.) => OSC1GAIN; for(0=>int i; i osc[i].gain; <<<"OSC1: ",OSC1GAIN*100,"%">>>; } fun void set_osc1pw(float amount) { //scale: 1 - 0.5 (so it's normal at bottom) 1-((amount) /(127.0*2)) => OSC1PW; for(0=>int i; i osc[i].width; <<<"OSC1 P-U-L-S-E W-I-D-T-H: ",OSC1PW,"nanometres">>>; } fun void set_osc2pw(float amount) { //scale: 1 - 0.5 (so it's normal at bottom) 1-((amount) /(127.0*2)) => OSC2PW; OSC2PW => osc2.width; <<<"OSC2 P-U-L-S-E W-I-D-T-H: ",OSC2PW,"nanometres">>>; } fun void set_osc1pwm (float amount) { //range is exponential 0-1 Math.pow(amount /(127.0), 2) =>OSC1PWCV.gain; <<<"OSC1 P-W-M", OSC1PWCV.gain()*100, "%">>>; } fun void set_osc2pwm (float amount) { //range is exponential 0-1 Math.pow(amount /(127.0), 2) =>OSC2PWCV.gain; <<<"OSC2 P-W-M", OSC2PWCV.gain()*100, "%">>>; } fun void set_osc2gain (float amount) { //exponential response curve from 0 - 300 .025 * Math.pow(amount,1.95) => osc2.gain; <<<"OSC2: ",osc2.gain(),"x">>>; } fun void set_feedbackgain (float amount) { for(0=>int i; i feedback[i].gain; //safety-reduced modulation gain Maxes out @ 127 <<<"OSC1 F*E*E*D*B*A*C*K: ",amount,"%">>>; } //noise gain fun void set_auxgain (float amount) { //exponential response curve from 0 - 300 amount/(127./2) => NOISEGAIN; for(0=>int i; i aux[i].gain; <<<"Noise: ",NOISEGAIN,"x">>>; } fun void set_ampgain(float amount) { (amount /127.0)*.1 => amp.gain;//safety-enhanced gain Maxes out @ 0.1 <<<"Amp: ",amp.gain()*100,"%">>>; } fun void set_filtergain(float amount) { (amount/127.) => filter.gain; <<<"Filter G-A-I-N: ",filter.gain()*100,"%">>>; } fun void set_filtercutoff (float amount) { //scale: exponential 100 - 10000 Hz //had to limit the range on both ends due to audio glitches, im kind of disappointed by this; what gives? //why is sound getting quieter towards the higher end?! this is unexpected behavior. Math.pow((amount /127.0),2) => amount; (amount * 15000)+100=> FILTERFREQ=> filter.freq; <<<"Filter F-R-E-Q: ",FILTERFREQ, "Hz">>>; } fun void set_filterresonance(float amount) { //scale: 0.5 - 1 //YO this causes scary sounds for some reason so lets not let that lower limit be too low, aight? 0.5 +(amount /127.0) * 10 =>FILTERQ=>filter.Q; <<<"Filter R-E-S: ",FILTERQ,"x">>>; } //set swing for megamode fun void set_megaswing (float amount) { //range is 0-1 amount /(127.0) => MEGASWING; <<<"MEGASWING",":", MEGASWING + "-MEGA">>>; } //set speed for arpeggiator fun void set_arpspeed (float amount) { //range is 0-1 amount /(127.0) => ArpSpeed; <<<"Arpeggiator Speed",":", ArpSpeed>>>; } //note: doesnt really set any 'filter' ADSR? just a regular ol osc ADSR? fun void set_filterA(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i oscEnv[i].attackTime; <<<"Attack: ",oscEnv[0].attackTime()/samp,"samp">>>; } fun void set_filterD(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i oscEnv[i].decayTime; <<<"Decay: ",oscEnv[0].decayTime(),"ms">>>; } fun void set_filterS(float amount) { //scale: 0.0 - 1.0 (Exponential) Math.pow((amount/127.0), 2) => amount; for(0=>int i; i oscEnv[i].sustainLevel; <<<"Sustain: ",oscEnv[0].sustainLevel()*100,"%">>>; } fun void set_filterR(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i oscEnv[i].releaseTime; <<<"Release: ",oscEnv[0].releaseTime(),"ms">>>; } //noise ADSR fun void set_noiseA(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i noiseEnv[i].attackTime; <<<"Noise Attack: ",amount>>>; } fun void set_noiseD(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i noiseEnv[i].decayTime; <<<"Noise Decay: ",amount>>>; } fun void set_noiseS(float amount) { //scale: 0.0 - 1.0 (Exponential) Math.pow((amount/127.0), 2) => amount; for(0=>int i; i noiseEnv[i].sustainLevel; <<<"Noise Sustain: ",noiseEnv[0].sustainLevel()*100,"%">>>; } fun void set_noiseR(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i noiseEnv[i].releaseTime; <<<"Noise Release: ",amount>>>; } fun void set_pitchEnvA(float amount) { Math.pow((amount/127.0), 2) => amount; for(0=>int i; i pitchEnv[i].duration; <<<"Pitch Env Attack: ",amount>>>; } fun void set_pitchEnvDepth(float amount) { //scale: ? numbers are of no use here amount + 1 => pitchEnvDepth; for(0=> int i; i pitchEnv[i].target; <<<"\"Pitch Env Depth\": ",pitchEnvDepth, "MEGAWATTS">>>; } //set menu parameters fun void set_megamode(float amount, int id) { //either YES or NO for MEGAMODE if(amount > (127.0/2)) { 1. => parametervals[id]; <<>>; } else { 0. => parametervals[id]; <<>>; } } fun void set_filter(float amount, int id) { //either YES, enable filter, or NO, passthru filter if(amount > (127.0/2)) { filter.op(1); 1. => parametervals[id]; <<>>; } else { filter.op(-1); 0. => parametervals[id]; <<>>; } } fun void set_osc1cv (float amount, int id) { //range is exponential 0 - 100 Math.pow(10,amount /(127.0/2))-1 =>parametervals[id]; for(0=> int i; i Osc1CV[i].gain; <<>>; } fun void set_cutoffcv (float amount, int id) { //range is exponential 0 - 100 Math.pow(10,amount /(127.0/2))-1 =>parametervals[id]=>CutoffCV.gain; <<>>; } fun void set_qcv (float amount, int id) { //range is exponential 0 - 4 Math.pow(amount /(127.0/2), 2) => parametervals[id]=>QCV.gain; <<>>; } //aka osc1 gain cv fun void set_tremolocv (float amount, int id) { //range is 0-1 amount/127.0 => parametervals[id]=> TremoloCV.gain; <<>>; } fun void set_noisecv (float amount, int id) { //range is 0-1 amount/127.0 => parametervals[id]=> NoiseCV.gain; <<>>; } //fun fact: also a button handler spork ~ knob_handler(); fun void knob_handler() { float amount; int id1; int id2; while(true) { knob => now; knob.amount => amount; knob.id1 => id1; knob.id2 => id2; //the MIDI protocol uses a combination of two id's to designate unique knobs //in the comments I put down the knobs that each of these map to as labeled on my JD-XA if(id1 ==191) { //knobs on the right side. these maintain the same ID in or out of MIDI ctrl mode, for some reason. if (id2 == 12) { //Reverb set_reverb(amount); } else if (id2 == 13) { //Delay set_delay(amount); } else if (id2 == 14) { //TFX 1 CTRL (we will use as our CONTROL KNOB for the menu) set_controlknob(amount); } else if (id2 == 15) { //TFX 2 CTRL //(currently unused) } else if (id2 == 80) { //TFX 1 SELECT (our SETTINGS/PARAMETERS menu selector) set_parameterselect(amount); } else if (id2 == 81) { //TFX 2 SELECT //(currently unused) } else if (id2 == 82) { //Time [Delay] set_delaytime(amount); } else if (id2 == 83) { //Knob: "Mic Level" (we will use it for delay feedback) set_delayfeedback(amount); } } else if(id1 ==176) { //for when I wasn't using MIDI Ctrl mode } else if(id1 ==184) { //for using MIDI Control mode if(id2 == 1 || id2 == 2 || id2 == 3 || id2 == 4 || id2 == 5 || id2 == 6 || id2 == 7 || id2 == 8 || id2 == 9 || id2 == 10 || id2 == 11 || id2 == 12 || id2 == 13 || id2 == 14 || id2 == 15 || id2 == 16) { //buttons 1-16! id1=>buttonpress.id1; id2=>buttonpress.id2; amount=>buttonpress.press; buttonpress.broadcast(); //finally have gotten to use broadcast() in ChucK, i feel complete! } else if (id2 == 17) {//mixer amp gain set_ampgain(amount); } else if(id2 == 18) { //OSC 1 pitch (in addition to dedicated keyboard) set_osc1freq(amount); } if(id2 == 19) { //mapped to: LFO speed set_lfofreq(amount); } else if(id2 == 20) { //OSC 2 pitch set_osc2freq(amount); } else if(id2 == 22) { //OSC 1 pitch FINE set_osc1fine(amount); } else if(id2 == 23) { //OSC 2 pitch FINE set_osc2fine(amount); } else if (id2 == 25) { //crossmod (i use it for osc1 self-feedback) set_feedbackgain(amount); } else if(id2 == 26) { //mapped to: Pitch Env Depth set_pitchEnvDepth(amount); } else if(id2 == 27) { //mapped to: OSC1 PWM set_osc1pwm(amount); } else if(id2 == 28) { //mapped to: OSC 1 PW set_osc1pw(amount); } else if (id2 == 29) { //mixer osc 1 gain set_osc1gain(amount); } else if (id2 == 30) { //mixer osc 2 gain set_osc2gain(amount); } else if (id2 == 31) { //mixer Aux gain set_auxgain(amount); } else if(id2 == 33) { //mapped to: OSC2 PWM set_osc2pwm(amount); } else if(id2 == 34) { //mapped to: OSC2 PW set_osc2pw(amount); } else if (id2 == 70) { //pitch Env /A/D(sr) set_pitchEnvA(amount); } else if (id2 == 71) { //filter resonance set_filterresonance(amount); } else if (id2 == 72) { //map: amp ADS/R/ set_noiseR(amount); } else if (id2 == 73) { //map: amp /A/DSR set_noiseA(amount); } else if (id2 == 74) { //filter cutoff freq set_filtercutoff(amount); } else if (id2 == 75) { //map: amp A/D/SR set_noiseD(amount); } if(id2 == 76) { //mapped to: filter /A/DSR set_filterA(amount); } else if(id2 == 77) { //mapped to: filter A/D/SR set_filterD(amount); } else if(id2 == 78) { //mapped to: filter AD/S/R set_filterS(amount); } else if(id2 == 79) { //mapped to: filter ADS/R/ set_filterR(amount); } else if (id2 == 95) { //pitch Env A/D/(sr) //n/a } else if (id2 == 108) { //mapped to: filter env depth set_megaswing(amount); } else if (id2 == 111) { //mapped to: filter key follow set_arpspeed(amount); } else if (id2 == 114) { //mapped to: HPF } else if (id2 == 115) { //filter drive (is this diff than filter gain?) set_filtergain(amount); } else if (id2 == 117) { //map: amp AD/S/R set_noiseS(amount); } } } } spork ~ keyboard_receiver(); fun void keyboard_receiver () { //this variable will act as our digital //keyboard-controlled knob 5 => int CYCLE; while( true ) { // wait on event hi => now; // get one or more messages while( hi.recv( kbmsg ) ) { // check for action type if( kbmsg.isButtonDown() ) { //<<< "down:", kbmsg.which, "(code)", kbmsg.key, "(usb key)", kbmsg.ascii, "(ascii)" >>>; if(kbmsg.which==203) { //left arrow-key paramselector--; if(paramselector<0) parametervals.cap()-1 => paramselector; ((paramselector) % parametervals.cap()) => int val; menuselecthandler(val); //reset value changer for new selection 5 => int CYCLE; } else if(kbmsg.which==205) {//right arrow-key ((paramselector+1) % parametervals.cap()) => int val; menuselecthandler(val); //reset value changers for new selection 5 => int CYCLE; } else if(kbmsg.which==200) {//up arrow CYCLE++; if (CYCLE>10) 0=>CYCLE; (CYCLE*12.7)$int => int temp; menuvalhandler(temp); } else if(kbmsg.which==208) { //down arrow CYCLE--; if (CYCLE <0) 10=> CYCLE; (CYCLE*12.7)$int => int temp; menuvalhandler(temp); } } else { //<<< "up:", msg.which, "(code)", msg.key, "(usb key)", msg.ascii, "(ascii)" >>>; } } } } //every keypress, trigger the relevant envelopes fun void keyOn(int i) { oscEnv[i].keyOn(); pitchEnv[i].keyOn(); //pitch envelope noiseEnv[i].keyOn(); //noise } fun void keyOff(int i) { oscEnv[i].keyOff(); pitchEnv[i].keyOff(); //pitch envelope //noiseEnv[i].keyOff(); //no clue why this shouldnt be here } // MIDI note handler aka Keyboard God //takes MIDI notes signalled to the 'on' event //by MIDI msg receiver for( 0 => int i; i < NUMOSCS; i++ ) spork ~ handler(i); fun void handler(int i) { int velocity; int note; Event off; while( true ) { on => now; on.note => note; on.velocity => velocity; //<<<"READ:",note,"ON">>>; if(parametervals[0]==0) {//would like to not lose oscfreq here. note => OSC1FREQ;//if megamode is on, you lose piano functionality. this seems fair. this prevents a random-walk piano } Std.mtof( note ) => osc[i].freq; keyOn(i); //set the velocity of the note using adsr //(velocity/127.0) => oscEnv[i].gain; off @=> offs[note]; off => now; null @=> offs[note]; //<<<"NOW:",note,"OFF">>>; keyOff(i); } } //MIDI message catcher. This is the time/load-bearing function of this entire program, so don't knock it! while( true ) { // wait on midi event min => now; // print components of MIDI msg <<>>; while( min.recv( msg ) ) { // catch only noteon if( msg.data1 ==144 || msg.data1 ==152){ // check velocity if( msg.data3 > 0 ) { // store midi note number msg.data2 => on.note; // store velocity msg.data3 => on.velocity; // signal the event on.signal(); // yield without advancing time to allow shred to run me.yield(); } else { //off.signal(); } } //KNOBS, BUTTONS, SLIDERS (on my JD-XA) //note to self: the settings may be different on different hardware, put a global var for this or something. else if(msg.data1 ==191 || msg.data1 ==176 || msg.data1==184) { msg.data1 => knob.id1; msg.data2 => knob.id2; msg.data3 => knob.amount; knob.signal(); } //i think this is our fabled key-off signal! else if( (msg.data1==128 || msg.data1 ==136) ){ //<<>>; if(offs[msg.data2]!=null) offs[msg.data2].signal(); //dont want any nullpointerexceptions } } }