//---------- USER SETTINGS ----------// -1 => int device; //MIDI device to open (see: chuck --probe for available devices) (set to -1 for no MIDI device) 0 => int kb_device; //keyboard (u must use a keyboard!) 5=>int NUMOSCS; //1? 5? 20? The world is yours with an n-phonic synth! (use more than 1 if playing on a keyboard!!) "DefaultUser" => string name; //this is what CodySynth will call you! //map your MIDI codes on the left side to the CodySynth parameters on the right! //implemented in knob_handler() int event_mappings[50][2]; [191, 12] @=> event_mappings["reverb"]; [191, 13] @=> event_mappings["delay"]; [191, 82] @=> event_mappings["delaytime"]; [191, 83] @=> event_mappings["delayfeedback"]; [184, 17] @=> event_mappings["ampgain"]; [184, 18] @=> event_mappings["osc1freq"]; [184, 19] @=> event_mappings["lfofreq"]; [184, 20] @=> event_mappings["osc2freq"]; [184, 22] @=> event_mappings["osc1fine"]; [184, 23] @=> event_mappings["osc2fine"]; [184, 25] @=> event_mappings["feedbackgain"]; [184, 26] @=> event_mappings["pitchenvdepth"]; [184, 27] @=> event_mappings["osc1pwm"]; [184, 28] @=> event_mappings["osc1pw"]; [184, 29] @=> event_mappings["osc1gain"]; [184, 30] @=> event_mappings["osc2gain"]; [184, 31] @=> event_mappings["auxgain"]; [184, 33] @=> event_mappings["osc2pwm"]; [184, 34] @=> event_mappings["osc2pw"]; [184, 70] @=> event_mappings["pitchenva"]; [184, 71] @=> event_mappings["filterresonance"]; [184, 72] @=> event_mappings["noiseR"]; [184, 73] @=> event_mappings["noiseA"]; [184, 74] @=> event_mappings["filtercutoff"]; [184, 75] @=> event_mappings["noiseD"]; [184, 76] @=> event_mappings["filterA"]; [184, 77] @=> event_mappings["filterD"]; [184, 78] @=> event_mappings["filterS"]; [184, 79] @=> event_mappings["filterR"]; [184, 111]@=> event_mappings["arpspeed"]; [184, 115]@=> event_mappings["filtergain"]; [184, 117]@=> event_mappings["noiseS"]; [184, 118]@=> event_mappings["osc1cv"]; [184, 119]@=> event_mappings["cutoffcv"]; [184, 120]@=> event_mappings["qcv"]; [184, 121]@=> event_mappings["tremolocv"]; [184, 122]@=> event_mappings["noisecv"]; //the main protagonist, osc! //NOTE: CAN SWITCH THIS TO PULSEOSC, TRIOSC, SINOSC TriOsc osc[NUMOSCS]; //OSC2 (WARNING: do not unplug from blackhole!!!) //NOTE: OPTIONS FOR SINOSC, PULSEOSC PulseOsc osc2 => blackhole; //---------- GENERAL NOTES ----------// //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 keyboard-only mode/generalize to chunity //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 //----------INITIALIZE CODYSYNTH ----------// //scale intervals: 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; //the list of string below is controlled by the list of parameters below it ["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 1 => float MEGASWING; //0 => int ARPPATTERN; //0,1,2,3: UP, DOWN, UPDOWN, RND 0. => float ArpSpeed; //for arp() Gain CutoffCV; Gain QCV; Gain TremoloCV; Gain NoiseCV; 0 => CutoffCV.gain => QCV.gain => TremoloCV.gain => NoiseCV.gain; MidiIn min; MidiMsg msg; Hid hi; HidMsg kbmsg;// try to open MIDI port 0 => int midi_detected; //if no MIDI device detected, this stays 0 and we run in keyboard-only mode if( device != -1) { if(min.open( device )) { 1 => midi_detected; min.name() => name; } } if( !hi.openKeyboard(kb_device)) me.exit(); //this synth has custom greetings-- this isn't your grandma's synth! ["Greetings,","Good day,","Welcome,","CodySynth is fun! "] @=> string greetings[]; ["O Great","Fairest Sir","Your Excellency","My Lord","My Dearest","My Liege","SynthMaster"] @=> string titles[]; greetings[Math.random2(0,greetings.cap()-1)]+ " " +titles[Math.random2(0,titles.cap()-1)] + " " + name + "!" => string greeting; //<<>>; //<<< "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; 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; 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) } } } } //---------- Chunity Code ----------// //note that the below list is assigned to each of the planes you can land on in my chunity project, in order (the 1st plane is not interactable). //Removed these from the list below for a more satisfying/safer experience. put them back in if you like: "pitchenvdepth",osc1pw, osc2pw, delayfeedback "filterA","filterD","filterS","noiseR","filterR","noiseD","noiseA","noiseS","filtergain","delayfeedback","delay","delaytime","arpspeed","osc1freq","lfofreq","osc2freq","ampgain","pitchenva", [ "filtercutoff", "osc2gain", "osc1cv", "cutoffcv", "qcv", "tremolocv", "noisecv", "reverb", "osc1fine", "osc2fine", "feedbackgain", "filterresonance", "osc1pwm", "auxgain", "osc2pwm"] @=> string event_mapping_keys[]; //global Event beat_event; global Event collision; 0 => global int plane_id; 0 => global int chunity_amount; //player fall speed will be used to set the parameters of our synth event_mapping_keys.cap() => int max_params; greeting => global string recent_plane_string; //boots up w a custom greeting spork~unity_receiver(); fun void unity_receiver() { while(true) { collision => now; plane_id % max_params => int ind; event_mapping_keys[ind] => string parameter; trigger_knob(parameter, chunity_amount); parameter + ": " + chunity_amount+ "/128" => recent_plane_string; //set text for chunity } } global Event reset_event; spork ~ reset_func(); //sets all synth settings to 0 fun void reset_func(){ while(true) { reset_event => now; spork ~ special_sound(); "Alone!Alone.alone!Alone?ALONE" => recent_plane_string; for(0=> int i; i < max_params; i++) { //trigger_knob(event_mapping_keys[i], 1); trigger_knob(event_mapping_keys[i]); 3::ms => now; } } } //play a special squidward reference when you reset/randomize the synth SndBuf special_buf => dac; 0 => int reset_num; fun void special_sound() { reset_num++; if(reset_num > 7) 1 => reset_num; me.dir() + "alone-0" + reset_num + ".wav" => special_buf.read; 0 => special_buf.pos; 10::second => now; } //stores ball pos (relative to main plane?) 0.0 => global float ball_xpos; 0.0 => global float ball_ypos; 0.0 => global float ball_zpos; 0.0 => global float ball_yvel; spork~unity_position_receiver(); fun void unity_position_receiver() { while(true) { 1::ms => now; set_osc1freq(((ball_xpos+500)/20) % 128); set_osc2freq(((ball_zpos+500)/20) % 128); set_lfofreq(((ball_yvel)) % 128); } } //---------- MODULATION FUNCTIONS---------- //computes low-latency modulation of our sound using our controllable parameters //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) { //note: both arpmode & megamode are trying to use voices at the same time. so for arpmode, we just pick a random voice to minimize tripping up 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; //chance pitch 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; } } //----------MENU CONTROL CODE---------- //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); } } //----------PARAMETER CONTROL---------- //the 'set' functions take in a float from 0.0-127.0 and use that number to alter a parameter //according to that parameter's logic fun void set_osc1freq(float amount) { //exponential response curve from 0.0002 - 15000 Hz //.0002 * Math.pow(amount,3.75) => OSC1FREQ; .002 * Math.pow(amount,3.75) => OSC1FREQ; //chunity version 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; .002 * Math.pow(amount,3.5) => OSC2FREQ; //chunity version 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/70.0) + 1 => pitchEnvDepth; //2021 have limited this feature so as to not scare off the customers [it's loud] 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; //<<>>; } //----------PHYSICAL INPUT & NOTE HANDLERS---------- //fun fact: also a button handler //this function turns knob/button movement into variables changes, //as such this function allows for CodySynth to be generalized to the computer keyboard. //TODO make this function non-hard-coded by utilizing the mapping at the top. 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); } else if (id2 == 118) { set_osc1cv(amount,2); } else if (id2 == 119) { set_cutoffcv(amount,3); } else if (id2 == 120) { set_qcv(amount,4); } else if (id2 == 121) { set_tremolocv(amount,5); } else if (id2 == 122) { set_noisecv(amount,6); } } } } //simulate physical input of a specified amount on a given parameter fun void trigger_knob(string input_key, int amount) { chout <= "knob triggered " + input_key + " with amount "+ amount <= IO.newline(); event_mappings[input_key][0] => knob.id1; event_mappings[input_key][1] => knob.id2; amount => knob.amount; knob.signal(); } //simulate random physical input on a given parameter fun void trigger_knob(string input_key) { event_mappings[input_key][0] => knob.id1; event_mappings[input_key][1] => knob.id2; Math.round(Math.random2f(0,128))$int => knob.amount; chout <= "knob triggered " + input_key + " with amount "+ knob.amount <= IO.newline(); knob.signal(); } //receives computer keyboard input! 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() ) { //if we're using an external synth/MIDI device, just use the keyboard as a peripheral menu if(midi_detected) { 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); } } //if no midi device is detected, we run in keyboard-only mode, increasing functionality of the keyboard to a ridiculous extent else { string input_key; //(we actually dont want this enabled during our chunity project; it conflicts with the experience) //first row (keys 1 to 0) are 2-11 /*if(kbmsg.which==2) { trigger_knob("reverb"); } else if(kbmsg.which==3) { trigger_knob("delay"); } else if(kbmsg.which==4) { trigger_knob("delaytime"); } else if(kbmsg.which==5) { trigger_knob("delayfeedback"); } else if(kbmsg.which==6) { trigger_knob("ampgain"); } else if(kbmsg.which==7) { trigger_knob("osc1freq"); } else if(kbmsg.which==8) { trigger_knob("lfofreq"); } else if(kbmsg.which==9) { trigger_knob("osc2freq"); } else if(kbmsg.which==10) { trigger_knob("osc1fine"); } else if(kbmsg.which==11) { trigger_knob("osc2fine"); } //2nd row is 16-25 else if (kbmsg.which==16) { trigger_knob("feedbackgain"); } else if(kbmsg.which==17) { trigger_knob("pitchenvdepth"); } else if(kbmsg.which==18) { trigger_knob("osc1pwn"); } else if(kbmsg.which==19) { trigger_knob("osc1pw"); } else if(kbmsg.which==20) { trigger_knob("osc1gain"); } else if(kbmsg.which==21) { trigger_knob("osc2gain"); } else if(kbmsg.which==22) { trigger_knob("auxgain"); } else if(kbmsg.which==23) { trigger_knob("osc2pwm"); } else if(kbmsg.which==24) { trigger_knob("osc2pw"); } else if(kbmsg.which==25) { trigger_knob("pitchenva"); } //3rd row is 30-39 else if (kbmsg.which==30) { trigger_knob("filterresonance"); } else if(kbmsg.which==31) { trigger_knob("noiseR"); } else if(kbmsg.which==32) { trigger_knob("noiseA"); } else if(kbmsg.which==33) { trigger_knob("filtercutoff"); } else if(kbmsg.which==34) { trigger_knob("noiseD"); } else if(kbmsg.which==35) { trigger_knob("filterA"); } else if(kbmsg.which==36) { trigger_knob("filterD"); } else if(kbmsg.which==37) { trigger_knob("filterS"); } else if(kbmsg.which==38) { trigger_knob("filterR"); } else if(kbmsg.which==39) { trigger_knob("arpspeed"); } //4th row is 44-53 else if (kbmsg.which==44) { trigger_knob("filtergain"); } else if(kbmsg.which==45) { trigger_knob("noiseS"); } else if(kbmsg.which==46) { } else if(kbmsg.which==47) { } else if(kbmsg.which==48) { } else if(kbmsg.which==49) { } else if(kbmsg.which==50) { } else if(kbmsg.which==51) { } else if(kbmsg.which==52) { } else if(kbmsg.which==53) { }*/ } //<<< "down:", kbmsg.which, "(code)", kbmsg.key, "(usb key)", kbmsg.ascii, "(ascii)" >>>; } else { } } } } //every keypress, trigger each voice's 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 } // 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 ) { if(midi_detected == 0) { // 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 } } } else { //if no midi device is connected to the computer, we will run codysynth in keyboard-only mode, with limited features. 1::second => now; } }