// title: beat grid // author: vividsnow // description: // prototype of simple metre beat grid running on server w/ synced TempoBusClock // code: ( // beat grid object prototype ~g = { var l = 6, // base pulse: 2**l, so max tempo (on 48khz) is around 48000/64/(2**6) = 11.7 state_bus = Bus.control(numChannels:2), // base beat and beat counter rate_bus = { var b = Bus.control; b.set(0.5.rrand(2).debug('beat rate')); b }.(), // set beat rate beat_runner = { var // base beat runner base = Impulse.kr(In.kr(rate_bus)*(2**l)), // base pulse c = PulseCount.kr(base, PulseDivider.kr(base,2**(l*4))); // l*4 - max multi beat partition Out.kr(state_bus, [base,c]) }.play, q = { |n=0,s=0,p=0,q| var i = In.kr(state_bus,2), c = Latch.kr(i[1],1); // server grid quantizer n = n + l; q = if(q.isNil, {n}, {q+l}); // if q is defined than start on specified by q grid place (PulseCount.kr(i[0]) + neg(2**q-(c%(2**q))) + (s*2.pow(n-(p?0)-1)) % (2**n))/*.abs*/.sign bitXor: 1 bitAnd: i[0] }, // pulse divider extension // proto object grid = (l:l,q:q,state_bus:state_bus,rate_bus:rate_bus,beat_runner:beat_runner); rate_bus.get({ |rate| var // sclang-side synced clock status = [nil], rater = { Out.kr(rate_bus, \tempo.kr(rate)) }.play, clock = TempoBusClock(rater, rate), notifier = { var bc = In.kr(state_bus,2); SendReply.kr(bc[0], '/pulse', [bc[1],In.kr(rate_bus)]) }.play, osc = OSCFunc({ |msg| status[0] = msg[3]; clock.beats = msg[3]/(2**l) + (Server.default.latency / clock.beatDur) }, '/pulse'); grid.putPairs([status: status, notifier: notifier, rater: rater, osc: osc, clock: clock, on:[0], nextTimeOnGrid: { |self, clock| var n = l + self.on[0], // client grid quantizer beats = (2**n) - (self.status[0] % (2**n)) + (if(self.on.size > 1, {2**(n-1-(self.on[2]?0))*self.on[1]}, {0})) / (2**l); self.on.debug('on'); clock.beats + beats }])}); grid }.() ) ( // survive cmdperiod CmdPeriod.add(~g.beat_runner.addUniqueMethod(\cmdPeriod, _.debug('still running'))); CmdPeriod.add(~g.rater.addUniqueMethod(\cmdPeriod, _.debug('still running'))); CmdPeriod.add(~g.notifier.addUniqueMethod(\cmdPeriod, _.debug('still running'))); ~g.clock.permanent = true; ~g.osc.permanent = true; ServerTree.add(~g, Server.default); ~g[\doOnServerTree] = { Synth(~g.beat_runner.defName); Synth(~g.rater.defName); Synth(~g.notifier.defName); ~g.clock.tempo = ~g.clock.tempo } ) // examples ~g.clock.beats; // elapsed beats ~g.status; // elapsed base pulses ~g.clock.tempo; // current tempo ~g.clock.tempo = 1; // change tempo (both sclang+scsynth) ( // shortcuts ~q = ~g[\q]; // quantizer: ~q.(n,m,k) means impulse each 2**n beat with m*(2**(n-1-k)) beat shift ~d = {|l=0| 2**l/In.kr(~g.rate_bus) }; // beat duration: ~d.(l) means duration of beatdur**l ~r = {|l=0| In.kr(~g.rate_bus)/(2**l) }; // beat rate: ~d.(l) means rate of beatrate**l ~e = {|p,d,a,c|EnvGen.ar(Env.perc(a?0.05,d?0.5,1,c?(-4)),p).lag(1e-3)}; // trigable perc envelope: triger, duration, attack, curve ~sn = {|...a|SinOsc.ar(*a)}; ~w = {|p,mn,mx|Demand.kr(p,0,Diwhite(mn,mx)).max(mn)}; ~l = {|d=12,s=1,e=0|Line.ar(s,e,d,doneAction:2)}; ) /* grid quantizer explained: ~q.(n,m,k,q) means impulse each 2**n beat (starting from 2**(q?n) beat) with m*(2**(n-1-k)) beat shift default values: n=0,m=0,k=0,q=null */ // simple examples {~sn.([60,61]) * ~e.(~q.(),~d.())/4 * ~l.()}.play // every beat {~sn.(160) * ~e.(~q.(0,0.5),0.2)/4!2 * ~l.()}.play // every beat + 1/4 beat shift {~sn.(320) * ~e.(~q.(0,1,1),0.2)/4!2 * ~l.()}.play // same since: ~q.(0,0.5) equals ~q.(0,1,1) {~sn.(Stepper.kr(~q.(),0,60,90,2).midicps) * ~e.(~q.(0,0.5),0.2)/4!2 * ~l.()}.play // every beat + 1/4 beat shift {~sn.(360) * ~e.(~q.(0,3,1),0.2)/4!2 * ~l.()}.play // every beat + 3/4 beat shift {~sn.(120) * ~e.(~q.(0,1),0.05)/4!2 * ~l.()}.play // every beat + 1/2 beat shift {~sn.(240) * ~e.(~q.(1,1),0.05)/4!2 * ~l.()}.play // every 2 beat + 1 beat shift {~sn.(200.exprand(1000)) * ~e.(~q.(1,1,0),0.05)/4!2 * ~l.()}.play // same on other freq {~sn.(200.exprand(1000)) * ~e.(~q.(1,3,1),0.05)/4!2 * ~l.()}.play // every 2 beat + 3/2 beat shift {~sn.(200.exprand(1000)) * ~e.(~q.(1,3,2),0.05)/4!2 * ~l.()}.play // every 2 beat + 3/4 beat shift {~sn.(200.exprand(1000)) * ~e.(~q.(1,[2,3],2),0.05)/4 * ~l.()}.play // every 2 beat + 2/4 beat shift on left and 3/4 beat shift on right {~sn.(Stepper.kr(~q.(2),0,1)*60) * ~e.(~q.(2,[0,1,2],2).sum,0.1)/4!2 * ~l.(30)}.play // every 4 beat make series of 3 pulses separated by 1/2 beat // run pattern in sync with server tempo grid (Pdef(\t, Pbind(*[ degree:Pseries(), delta: Pseq(1!10), dur:1 ])).play(~g.clock, quant:[1,0.5])) // default quant: i.e. on first 1.5 beat Pdef(\t).stop; (Pdef(\tt, Pbind(*[ // grid degree:Pseries(), delta: Pseq(2!10,inf), dur:0.2, ])).play(~g.clock, quant:(parent:~g, on:[2,1.5]))) // position like ~q.(...), but args in "on" key (here: on nearest 4 beat line + 3 beat shift) Pdef(\tt).stop; // live coding example ~g.clock.tempo = 1; Ndef(\bk, {Formlet.ar(LPF.ar(T2A.ar(~q.(-2) bitXor: ~q.()), ~w.(~q.(3),250,500)), Demand.kr(~q.(2), 0, Dbrown(120,420,20)).max(120), 0.01, ~d.(-1)) ! 2 / 4 }).play; Ndef(\k, {~sn.(60) * ~e.(~q.(), ~d.(-2)) ! 2 / 8 }).play; Ndef(\k1, {~sn.(490.rrand(800)) * ~e.(~q.(1,1.5), ~d.(-2)) ! 2 / 6 }).play; Ndef(\k2, { FreeVerb.ar(RLPF.ar(Saw.ar([90,140]).mean,370) * ~e.(~q.(-1) bitXor: ~q.(1), 0.15) ! 2) / 2 }).play; Ndef(\k3, {RLPF.ar(Pulse.ar(~w.(~q.(2),60,110)),~w.(~q.(4),180,300)) * ~e.(~q.(0,0.5), 0.1) ! 2 / 3 }).play; Ndef(\k2, {BPF.ar(Saw.ar(~w.(~q.(2),60,110)),~w.(~q.(3),180,300)) * ~e.(~q.(-1) bitXor: ~q.(1), 0.15) ! 2 / 2 }).play; ( Ndef(\k1, { ~sn.(~w.(~q.(3),60,80).midicps*~sn.(~w.(~q.(),5,18)).range(0.05,1.03)) .madd(0.95,0.05*~sn.(~w.(~q.(4),200,800))).tanh ! 2 * ~e.(~q.(3,3,1), ~d.(1)) / 8 }).play ) Ndef(\k, {~sn.(~w.(~q.(3),60,80)) * ~e.(~q.(), ~d.() * ~w.(~q.(2),4,8)/4) ! 2 / 12 }).play; ( Ndef(\p, { var a = Array; DynKlank.ar(`[a.exprand(8,2e2,2e4).sort, a.exprand(8,0.1,1).sort.reverse, a.exprand(8,0.25,1).sort.reverse], T2A.ar(~q.(-1,1) bitXor: ~q.(1,1,1)) / 30 * LFNoise0.ar(1/4).madd(0.7,0.3), ~w.(~q.(3),4,8)/4, 0, ~w.(~q.(3),1,4)/4) ! 2 }).play ) (Ndef(\trt, { var q = ~q.(2,{~w.(~q.(3),0,15)}!6,3).scramble.clump(2).sum; ~sn.(Stepper.kr(q,0,1,20).linexp(1,20,60+[0,2],~w.(~q.(3,[0,1]),0,300,800))).madd(4).tanh.madd(2).pow(3).tanh.madd(0.1) * ~e.(q, ~d.(-2)) }).play) ~g.clock.tempo = 2; // make it faster