( var width= Window.screenBounds.width, height= Window.screenBounds.height; var fps= 60; var buffersize= 512; var scale= Window.screenBounds.width; var perspective= 1; var distance= 1; var window = Window("Article Codes: Triggered Euclidean Sequencer", Rect(100,height, width, Window.screenBounds.height, false)); var can= Canvas3D(window, Rect(480, 0, width-480, height)).scale_(scale).perspective_(perspective).distance_(distance) .background_(Color.green(0.1,0.8)); var itemWave= Canvas3DItem.new .color_(Color.new255(200,200,200,210)) .transforms = [ Canvas3D.mTranslate(0,0,-10), ]; /* Paths */ var path = []; /* Buffers */ var buffer; var rate = 0.005; /* Add items */ can.add(itemWave); s = Server.default; s.boot; /* Synthdefs */ s.waitForBoot({ SynthDef(\SOSkick, { arg amp = 0, out = 0,freq = 70; var hatosc, hatenv, hatoutput; hatenv = EnvGen.ar(Env.perc(0.003,0.3),doneAction:2); hatoutput = (PMOsc.ar([MouseX.kr(freq,freq+6,'exponential'), MouseX.kr(MouseY.kr(freq-5,freq,\exponential),freq+5,'exponential')],VOsc3.ar(freq/1000,20,30,40,0.3),1,6)* hatenv); Out.ar([0,2],Pan2.ar(hatoutput, PMOsc.ar(18,freq/12000,6), amp)); } ).add; SynthDef(\snare909,{ arg out =0,amp=0,mul=1,velocity=1, freq = 8040; var env = 0.5*EnvGen.ar(Env.perc(0.02,0.5),doneAction:2); var output = (PMOsc.ar([MouseX.kr(freq -10 ,freq,'exponential'), MouseX.kr(MouseY.kr(freq-8,freq-6,\exponential),freq,'exponential')],freq/300,3,4)* env); Out.ar([0],0.25*amp*Rotate2.ar(output[0],output[1],LFNoise0.kr(0.2))); Out.ar([4],1.5*amp*Rotate2.ar(output[0],output[1],LFNoise0.kr(0.2))); }).add; SynthDef(\closedhat, { arg amp = 0, freq=8000; var hatosc, hatenv, hatnoise, hatoutput; hatenv = {Line.ar(1, 0, 1, doneAction:2)}; hatoutput = (PMOsc.ar(freq,MouseX.kr(freq -5 ,freq,'exponential'),2,4,1)*SinOsc.ar([6000,MouseX.kr(5998,5999,\exponential)]) * hatenv*0.6*amp); Out.ar([0],0.1*Pan2.ar(hatoutput,LFTri.kr(0.01))); Out.ar([6],0.6*Pan2.ar(hatoutput,LFTri.kr(0.01))); }).add; SynthDef(\openhat, {arg amp = 0, freq=4000; var hatosc, hatenv, hatoutput; var excitation = LPF.ar(WhiteNoise.ar(1), freq, 1) * (0.1); var env_perc = EnvGen.kr(Env.perc(0.0005,0.3),doneAction:2); var membrane = excitation * env_perc * amp; var env_gen; env_gen = EnvGen.kr(Env.perc,amp,timeScale: 0.4, doneAction: 0); Out.ar([0,10], 0.2*Pan2.ar(MembraneHexagon.ar(env_gen * PMOsc.ar(freq,2,4,2,0.2), freq/40000 + 0.012, MouseY.kr(0.9992, 0.999, 1)),LFNoise0.kr(0.2),0.25)); /*hatenv = EnvGen.ar(Env.perc(0.003,1.5),doneAction:2); hatoutput = (PMOsc.ar([MouseX.kr(freq,freq+6,'exponential'), MouseX.kr(MouseY.kr(freq-5,freq,\exponential),freq+5,'exponential')],VOsc3.ar(freq/1000,20,30,40,0.3),1,6)* hatenv); Out.ar([0,10],Pan2.ar(hatoutput, PMOsc.ar(18,freq/12000,6), amp));*/ } ).add; // Create the pattern maker and set tempo. t = TempoClock(130/60.0); ~velocity = 0.5; ~patternMaker = { arg name, f, s, v, freq = [400]; Pbind( \instrument, name, \dur, Prand([1, 0.2, 0.5, 3], inf), \amp, v*Pseq(Bjorklund(f,s),inf), \freq, Pseq(freq,inf); ).asStream; }; //OSC SynthDef(\clockTrigger,{ |clock| SendTrig.kr(Impulse.kr(clock*4),0,1); }).add; s.sync; // register to receive this message o = OSCFunc({ arg msg, time; //[time, msg].postln; switch(msg[2]) {0}{ ~kick.next(Event.default).play(t); ~snare.next(Event.default).play(t); ~closedhat.next(Event.default).play(t); ~openhat.next(Event.default).play(t); }; },'/tr', s.addr); //Default values ~kick = ~patternMaker.value(\SOSkick, 0, 16, ~velocity, [70]); ~closedhat = ~patternMaker.value(\closedhat, 0, 21, ~velocity, [8040]); ~openhat = ~patternMaker.value(\openhat, 0, 33, ~velocity, [8000]); ~snare = ~patternMaker.value(\snare909, 0, 18, ~velocity, [4000]); w = Synth(\clockTrigger, (["clock", t.tempo])); // UI Function ~generateUI = { arg window, instr, cc1, cc2, note; var slider, updater, model, setValueFunction, setValueFunction2, setValueFunction3, updater2, updater3, model2, model3, button, numberbox, numberbox2, ctrlSpec, ctrlSpec2; var control1, control2, triggernote; var numSliders, multislider; var o; /*** MVC ***/ //model model = (myValue: 0); setValueFunction = {|value| model [\myValue] = value; model.changed(\value, value); }; //updater updater = {|theChanger, what, val| { if(what == \value, { numberbox.string_(val); slider.value_(val/numberbox2.value); }); }.defer; }; model.addDependant(updater); //model2 model2 = (myValue: 0); setValueFunction2 = {|value| model2 [\myValue] = value; model2.changed(\value, value); }; //updater2 updater2 = {|theChanger, what, val| { if(what == \value, { ctrlSpec2 = ControlSpec(0, val, \lin, 1, 0, ); ctrlSpec = ControlSpec(0, ctrlSpec2.map(val), \lin, 1, 0, ); if(numberbox.string.asInteger > val){ numberbox.string_(val); slider.doAction; slider.value_(ctrlSpec.map(val)); }; numberbox2.value_(val); numSliders=numberbox2.value; }); }.defer; }; model2.addDependant(updater2); //model3 model3 = (myValue: 0); setValueFunction3 = {|value| model3 [\myValue] = value; model3.changed(\value, value); }; //updater3 updater3 = {|theChanger, what, val| { if(what == \value, { val.postln; switch(instr) {\kick}{ ~kick = ~patternMaker.value(\SOSkick, numberbox.string.asInteger, numberbox2.value, ~velocity,[50,13000, \exp, 1].asSpec.map(multislider.value));} {\snare}{ ~snare = ~patternMaker.value(\snare909, numberbox.string.asInteger, numberbox2.value, ~velocity,[50,13000, \exp, 1].asSpec.map(multislider.value));} {\closedhat}{ ~closedhat = ~patternMaker.value(\closedhat, numberbox.string.asInteger, numberbox2.value, ~velocity,[50,13000, \exp, 1].asSpec.map(multislider.value));} {\openhat}{ ~openhat = ~patternMaker.value(\openhat, numberbox.string.asInteger, numberbox2.value, ~velocity,[50,13000, \exp, 1].asSpec.map(multislider.value));}; }); }.defer; }; model3.addDependant(updater3); /*** GUI ***/ //view ctrlSpec = ControlSpec(0, 0, \lin, 1, 0, ""); ctrlSpec2 = ControlSpec(0, 64, \lin, 1, 16, "" ); //Slider slider = Slider(window, Rect(110,0, 200, 40)) .value_(model[\myValue]) .action_({|view| setValueFunction.value(ctrlSpec.map(view.value))}); //Number numberbox = StaticText(window, Rect(320, 0, 40,40)) .string_(model[\myValue]) .stringColor_(Color.white) .font_(Font("Arial Rounded MT Bold", 20)) .action_({|view| setValueFunction.value(ctrlSpec.map(view.value))}); //Number numberbox2 = EZNumber(window, 110@40, label:"Length", controlSpec: ctrlSpec2 ) .setColors(Color.new255(60,140,60),Color.white,background: Color.new255(60,140,60), numTypingColor:Color.red) .font_(Font("Arial Rounded MT Bold", 14)) .action_({|view| ctrlSpec2 = ControlSpec(0, view.value, \lin, 1, 0, ); ctrlSpec = ControlSpec(0, ctrlSpec2.map(view.value), \lin, 1, 0, ); }); numberbox2.doAction; //Button button = Button(window, Rect(340,0, 50, 40)) .states_([["Trigger"], ["Trigger"]]) .font_(Font("Arial Rounded MT Bold", 12)) .action_({|v| if(v.value==0){ }{ switch(instr) {\kick}{ ~kick = ~patternMaker.value(\SOSkick, numberbox.string.asInteger, numberbox2.value, ~velocity, [50,13000, \exp, 1].asSpec.map(multislider.value));} {\snare}{ ~snare = ~patternMaker.value(\snare909, numberbox.string.asInteger, numberbox2.value, ~velocity, [50,13000, \exp, 1].asSpec.map(multislider.value));} {\closedhat}{ ~closedhat = ~patternMaker.value(\closedhat, numberbox.string.asInteger, numberbox2.value, ~velocity, [50,13000, \exp, 1].asSpec.map(multislider.value));} {\openhat}{ ~openhat = ~patternMaker.value(\openhat, numberbox.string.asInteger, numberbox2.value, ~velocity, [50,13000, \exp, 1].asSpec.map(multislider.value));}; button.value_(0); } }); //Multislider multislider = MultiSliderView(window,Rect(10,150,480-20,110)); multislider.gap_(500/(numberbox2.value*2)); // width in pixels of each stick multislider.value=Array.fill(numberbox2.value, {|v| 0.5}); multislider.colors_(Color.new255(150,0,0,255),Color.new255(150,0,0,255)); multislider.action = { arg q; // size is set automatically when you set the value [50,13000, \exp, 1].asSpec.map(q.value).postln; }; multislider.isFilled_(true); multislider.elasticMode_(1); //multislider.showIndex = true; // cursor mode control1 = CCResponder({ |src,chan,num,value| setValueFunction.value(ctrlSpec.map((value/127))); }, nil, // any source nil, // any channel cc1, // CC number cc1 ); control2 = CCResponder({ |src,chan,num,value| setValueFunction2.value(value/2); }, nil, // any source nil, // any channel cc2, // CC number cc2 ); triggernote = NoteOnResponder({ |src,chan,note,vel| setValueFunction3.value(note); }, nil, // any source nil, // any channel note, // note number nil // any vel ); //Cleanup /* window.onClose_({model.removeDependant(updater); model2.removeDependant(updater2); model3.removeDependant(updater3); control1.remove; control2.remove; triggernote.remove; numberbox.remove;});*/ }; //window.addFlowLayout( 10@10, 20@5 ); window.view.decorator = FlowLayout( Rect(0,0,480,height), 10@10, 20@5 ); window.view.background_(Color.new255(50,50,50)); //Title StaticText(window, Rect(0, 0, 500, 30)) .string_("Triggered Euclidean Sequencer") .align_(\center) .stringColor_(Color.white) .font_(Font("Arial Rounded MT Bold", 20)); // Generate UI for each timbre with Trigger and CC control numbers ~row1 = ~generateUI.value(window, \kick, 1, 2, 40); ~row2 = ~generateUI.value(window, \snare, 3, 4, 42); ~row3 = ~generateUI.value(window, \closedhat, 5, 6, 36); ~row4 = ~generateUI.value(window, \openhat, 7, 8, 38); /* TEMPO */ ~tempoSpec = ControlSpec(20, 400, \lin, 1, 130, "" ); ~tempoControl = EZNumber(window, 110@40, label:"Tempo", controlSpec: ~tempoSpec ) .setColors(Color.new255(60,140,60),Color.white,background: Color.new255(60,140,60), numTypingColor:Color.red) .font_(Font("Arial Rounded MT Bold", 14)) .action_({|view| t.tempo = view.value/60.0; t.tempo.postln; }); ~tempoControl.doAction; /*** MVC ***/ //model ~tempoModel = (myValue: 0); ~tempoSetValueFunction = {|value| ~tempoModel [\myValue] = value; ~tempoModel.changed(\value, value); }; //updater ~tempoUpdater = {|theChanger, what, val| { if(what == \value, { ~tempoControl.value_(val); ~tempoControl.doAction; }); }.defer; }; ~tempoModel.addDependant(~tempoUpdater); ~tempoCC = CCResponder({ |src,chan,num,value| ~tempoSetValueFunction.value(value.linlin(0,127,20,400)); }, nil, // any source nil, // any channel 9, // CC number 9 ); /* Tempo Trigger */ //Button ~tempTrigger = Button(window, 50@40) .states_([["Trigger"], ["Trigger"]]) .font_(Font("Arial Rounded MT Bold", 12)) .action_({|v| w.free; w = Synth(\clockTrigger, (["clock", t.tempo])); }); ~tempoMidiTrigger = NoteOnResponder({ |src,chan,note,vel| [src,chan,note,vel].postln; w.free; w = Synth(\clockTrigger, (["clock", t.tempo])); }, nil, // any source nil, // any channel 35, // note number nil // any vel ); /* Visualization */ buffer = Buffer.alloc(s, buffersize, 2); s.sync; { RecordBuf.ar(In.ar(0,2), buffer); Silent.ar; }.play; s.sync; can.animate(fps, {|frame| buffer.getn(0, 1023, {|data| path= data.clump(3)}); itemWave.paths= [path]; can.transforms = [ Canvas3D.mRotateY(frame/100 + (sin(frame/70)*2) %2pi), Canvas3D.mRotateX(frame/100%2pi), Canvas3D.mScale(sin(frame/80*4)*0.3+1) ]; itemWave.transforms = [ Canvas3D.mRotateY(frame/60%2pi) ]; }); can.mouseMoveAction = {|v,x,y, mod| can.transforms = [ Canvas3D.mRotateY(x / -200 % 2pi), Canvas3D.mRotateX(y / 200 % 2pi) ]; can.refresh; }; CmdPeriod.doOnce({window.close; buffer.free;NoteOnResponder.removeAll; CCResponder.removeAll;}); // ~bus = Bus.audio(s,2); window.front; window.onClose_({ w.free;z.free;s.freeAll;o.free; ~row1.free; ~row2.free; ~row3.free; ~row4.free; NoteOnResponder.removeAll; CCResponder.removeAll; }); }); //s.scope; ) s.makeWindow;