// title: [SIMPLE] Random Pad Generator // author: Checco Ruseo // description: // A random pad generation thing. // Every time you click EXECUTE, a pad with a certain number of melodic lines is generated using the parameters in the GUI. // Use it -> study the code -> modify it -> ? -> profit! // P.S. sorry for my mediocre english ;) // code: // A random pad generation thing. Every time you click EXECUTE, a pad with a certain number of melodic lines is generated using the parameters in the GUI. // Use it -> study the code -> modify it -> ? -> profit! ( // This function creates an instance of the Pad Generator ~padGenerator = { var res = Environment.make({ // Score Parameters // See the GUI code for explanations. ~sp = ( numMelodicLines: 3, numNotesPerMelodLine: 8, baseNote: 40, degrees: [0,7,12], durs: [1,2,4,3], noteOfs: [0,12,24], ); ~sp.minNoteDur = { 1/16 }!~sp.numMelodicLines; ~sp.instrs = { #[sin1,sin2,sin3,sin1,sin1] }!~sp.numMelodicLines; // This Semaphores are used to wait synchronously the ending of all the melodic lines. // Every melodic line has a lock associated. // When a melodic line stop playing it opens the Semaphore. ~locks = List[]; ~executing = false; // Utility. Translate Symbols arrays to Strings ~symbolsArrayToString = { |arr| var res; if( arr.size == 0 ) { res = arr.asString; } { var strs = arr.collect{|el| ~symbolsArrayToString.(el); }; if( arr[0].size == 0 ){ res = "#[" }{ res = "[ "; }; strs.do{ |el,i| res = res + el + ","; }; res = res + "]"; }; res }; // Utility. Resize a multidimensional array. // Ex: // IN arr = [ [1,2,3], [4,] ] size = 5 // OUT arr = [ [1,2,3], [4,], [1,2,3], [4,], [1,2,3] ] ~resizeMultiDimArray = { |arr,size| var res = arr; if( arr.size > size ){ res = Array.newClear( size ); size.do{ |i| res[i] = arr[i]; }; }; if( arr.size < size ){ res = Array.newClear( size ); size.do{ |i| res[i] = arr[ i%(arr.size) ]; }; }; res }; // This function include all the operations to be carried out before the actual playing ~prepare = { SynthDef(\sin1,{ |out = 0, amp = 0.1, gate = 1, freq = 440| var d = (); d.gen = SinOsc.ar(freq) ** 3; d.envAmp = Env.asr(1,1,6).kr(2,Trig1.kr(gate,1,1)) * amp; d.res = Clip.ar( d.gen, -1.01, 1.01 ) * d.envAmp ! 2; Out.ar(out,d.res); }).add; s.sync; SynthDef(\sin2,{ |out = 0, amp = 0.1, gate = 1, freq = 440| var d = (); d.gen = SinOsc.ar(freq+[0,1]) ** 2; d.envAmp = Env.asr(1,1,6).kr(2,Trig1.kr(gate,1,1)) * amp; d.res = Clip.ar( d.gen, -1.01, 1.01 ) * d.envAmp; Out.ar(out,d.res); }).add; s.sync; SynthDef(\sin3,{ |out = 0, amp = 0.1, gate = 1, freq = 440| var d = (); d.gen = SinOsc.ar(freq+[1,0]).abs; d.envAmp = Env.asr(1,1,6).kr(2,Trig1.kr(gate,1,1)) * amp; d.res = Clip.ar( d.gen, -1.01, 1.01 ) * d.envAmp ! 2; Out.ar(out,d.res); }).add; s.sync; }; // Utility // Ex: ~playNote.(\sin1,[\freq,440],1.5); ~playNote = { |sdef,args,dur=1| var nt; s.makeBundle(nil,{ nt = Synth(sdef,args); }); dur.wait; nt.set(\gate,0); }; // This function generates all the melodic lines and play them. // It waits for all of them to finish. ~score = { var str, strd, stri, strl, numMelodicLines, numNotesPerMelodLine, minNoteDur, degrees, durs, noteOfs, instrs; noteOfs = ~sp.noteOfs.reshape(~sp.numMelodicLines); minNoteDur = ~sp.minNoteDur.reshape(~sp.numMelodicLines); ~sp.instrs = ~resizeMultiDimArray.( ~sp.instrs, ~sp.numMelodicLines ); // The notes of every melodic line are generated here str = (~sp.numMelodicLines).collect{|p| { ~sp.degrees.choose + noteOfs[p] + ~sp.baseNote }!~sp.numNotesPerMelodLine }; // The durations of every melodic line are generated here strd = ~sp.numMelodicLines.collect{|p| { ~sp.durs.choose * minNoteDur[p] }!~sp.numNotesPerMelodLine }; // The instrument names for every melodic line are generated here stri = ~sp.numMelodicLines.collect{|p| { ~sp.instrs[p].choose }!~sp.numNotesPerMelodLine }; ~locks = { Semaphore(0) }!~sp.numMelodicLines; // Code for the acutal playing ~sp.numMelodicLines.do{ |i| { // suona, sequenzialmente, le note di una parte str[i].size.do { |j| ~playNote.( stri[i][j], [\freq,str[i][j].midicps,\amp,0.01,\gate,1,], strd[i][j] ); }; // segnala fine parte, rilasciando il Semaphore dedicato ~locks[i].signal; }.fork; }; // Wait for all the melodic lines to end str.size.do{ |i| ~locks[i].wait; }; "Done".postln; }; // Unused ~finish = { }; /* Utility. If this instance of the pad generator is not already generating/playing something, this function executes the input function.*/ ~ifNotAlreadyExecuting = { |func| if( ~executing, { "An instance of the patch is already executing".postln; },{ func.(); }); }; /* Launch the pad generation/execution. */ ~startScore = { |e| var func = { s.waitForBoot{ { ~ifNotAlreadyExecuting.({ ~prepare.(); ~executing = true; ~score.(); ~executing = false; ~finish.(); }); }.fork; }; }; if( e.class == Environment ){ e.use{ func.(); }; }{ func.(); }; }; ~showUi = { |e,parent,bounds| var func = { var wi, // width he=20, // row height rows=8, // rows used for the controls helpRows = 3, dr = 0, // #rows for the documentation hv, // help view mhb, // make help button; helper function tr = rows+1+helpRows+dr, // total number of rows pa; // panel view if( bounds.isNil ){ bounds = Rect(0,0,400,he*(tr)); }{ he = bounds / (tr); }; wi = bounds.width; if( parent.isNil ){ parent = Window("Pad Generator",bounds); parent.front; }; mhb = { |row=0,msg("")| Button(pa,Rect(wi-he,he*(dr+row),he,he)+Rect(2,2,-4,-4)) .states_([["?",Color.white,Color.blue]]) .mouseEnterAction_{ hv.string=msg; } .mouseLeaveAction_{ hv.string=""; }; }; pa = View(parent,bounds); StaticText(pa,Rect(0,he*0,wi,he*dr)+Rect(2,2,-4,-4)) // .background_(Color.blue(0.4)) .stringColor_(Color.blue(0.4)) .align_(\left) .string_("DOCUMENTATION \n "); EZNumber(pa,Rect(0,he*dr,wi-he,he),"# melodic lines",[1,100,\lin,1,1],{ |vv| e.sp.numMelodicLines = vv.value.asInteger; },e.sp.numMelodicLines,false,100,gap:2@2,margin:2@2); mhb.(0,"Number of melodic lines played simultaneously."); EZNumber(pa,Rect(0,he*(dr+1),wi-he,he),"# notes per melodic line",[1,40,\lin,1,1],{ |vv| e.sp.numNotesPerMelodLine = vv.value.asInteger; }, e.sp.numNotesPerMelodLine,false,100,gap:2@2,margin:2@2 ); mhb.(1,"Length (in number of notes) of every melodic line"); EZNumber(pa,Rect(0,he*(dr+2),wi-he,he),"base note",[20,100,\lin,1,40],{ |vv| e.sp.baseNote = vv.value.asInteger; },e.sp.baseNote,false,100,gap:2@2,margin:2@2); mhb.(2,"A base note the generation algorithm uses as root for the melodic lines"); EZText(pa,Rect(0,he*(dr+3),wi-he,he),"degrees set",{|vv| var val; val = vv.value.interpret; if( val.class == Array ){ e.sp.degrees = val; }; },e.sp.degrees.asString,false,100,gap:2@2,margin:2@2); mhb.(3,"Set of degrees used by the generation algorithm to create the melodic lines"); EZText(pa,Rect(0,he*(dr+4),wi-he,he),"relative durs set",{|vv| var val; val = vv.value.interpret; if( val.class == Array ){ e.sp.durs = val; }; },e.sp.durs.asString,false,100,gap:2@2,margin:2@2); mhb.(4,"Set of relative durations used to calculate the final absolute note durations. \n\t\tabs_note_dur = choose_one('relative durs set') * 'min note dur' "); EZText(pa,Rect(0,he*(dr+5),wi-he,he),"note offsets",{|vv| var val; val = vv.value.interpret; if( val.class == Array ){ e.sp.noteOfs = val; }; },e.sp.noteOfs.asString,false,100,gap:2@2,margin:2@2); mhb.(5,"Note offset added to the 'base note' for every melodic line. \n\t'note offsets'[i] = note offset for the ith melodic line"); EZText(pa,Rect(0,he*(dr+6),wi-he,he),"min note durs",{|vv| var val; val = vv.value.interpret; if( val.class == Array ){ e.sp.minNoteDur = val; }; },e.sp.minNoteDur.asString,false,100,gap:2@2,margin:2@2); mhb.(6,"min_note_dur[i] = minimum note duration for the ith melodic line"); EZText(pa,Rect(0,he*(dr+7),wi-he,he),"instrs",{|vv| var val; val = vv.value.interpret; val.class.postln; if( val.class == Array ){ e.sp.instrs = val; }; },~symbolsArrayToString.(e.sp.instrs),false,100,gap:2@2,margin:2@2); mhb.(7,"This array has a sub-array for every melodic line. \nEvery i-th sub-array contains the instruments that the i-th melodic line can randomly choose for its notes"); Button(pa,Rect(0,he*(dr+8),wi,he)) .states_([["EXECUTE",Color.white,Color.black]]) .action_({ e.startScore(); }); hv = StaticText(pa,Rect(0,he*(dr+9),wi,he*helpRows)+Rect(2,2,-4,-4)) .background_(Color.blue(0.4)) .stringColor_(Color.white) .align_(\left) .string_(""); }; if( e.class == Environment ){ e.use({ func.(parent,bounds); }); }{ func.(e,parent); }; }; }); res.know = true; res }; ~p1 = ~padGenerator.(); ~p1.showUi(); ) // CODE FOR DEVELOPMENT/DEBUGGING ~p1.executing = false; ~p1.sp ~p1.startScore(); ~sp = nil s.quit