1 // clocks for timing threads.
5 *play { | task | this.sched(0, task) }
6 *seconds { ^thisThread.seconds }
8 // tempo clock compatibility
9 *beats { ^thisThread.seconds }
10 *beats2secs { | beats | ^beats }
11 *secs2beats { | secs | ^secs }
14 *timeToNextBeat { ^0 }
15 *nextTimeOnGrid { | quant = 1, phase = 0|
16 if (quant ==0) { ^this.beats + phase };
17 if (phase < 0) { phase = phase % quant };
18 ^roundUp(this.beats - (phase % quant), quant) + phase;
24 var queue = thisProcess.prSchedulerQueue;
26 forBy(1, queue.size-1, 3) {|i|
27 queue[i+1].removedFromScheduler
32 *sched { arg delta, item;
36 *schedAbs { arg time, item;
49 scheduler = Scheduler.new(this, drift:true, recursive:false);
54 *sched { arg delta, item;
55 scheduler.sched(delta, item);
59 var saveClock = thisThread.clock;
60 thisThread.clock = this;
61 scheduler.seconds = Main.elapsedTime;
62 thisThread.clock = saveClock;
63 ^scheduler.queue.topPriority;
66 // notify clients that something has been scheduled
72 var clock, drift, <>recursive, beats = 0.0, <seconds = 0.0, <queue, expired, wakeup;
74 *new { arg clock, drift = false, recursive=true;
75 ^super.newCopyArgs(clock, drift, recursive).init;
78 beats = thisThread.beats;
79 queue = PriorityQueue.new;
80 expired = Array.new(8);
84 delta = item.awake( beats, seconds, clock );
85 if (delta.isNumber) { this.sched(delta, item) };
87 Error.handling = true;
88 if (Error.debug) { error.inspect } { error.reportError };
89 Error.handling = false;
98 schedAbs { | time, item |
99 queue.put(time, item);
102 sched { | delta, item |
105 fromTime = if (drift, { Main.elapsedTime },{ seconds });
106 queue.put(fromTime + delta, item);
110 queue.do {|x| x.removedFromScheduler };
114 isEmpty { ^queue.isEmpty }
117 this.seconds = seconds + delta;
120 seconds_ { | newSeconds |
123 seconds = queue.topPriority;
124 seconds.notNil and: { seconds <= newSeconds }
126 beats = clock.secs2beats(seconds);
130 // First pop all the expired items and only then wake
131 // them up, in order for control to return to the caller
132 // before any tasks scheduled as a result of this call are
136 seconds = queue.topPriority;
137 seconds.notNil and: { seconds <= newSeconds }
139 expired = expired.add(seconds);
140 expired = expired.add(queue.pop);
143 expired.pairsDo { | time, item |
145 beats = clock.secs2beats(time);
148 expired.extend(0); // clear
151 seconds = newSeconds;
152 beats = clock.secs2beats(newSeconds);
157 classvar <all, <>default;
161 var <beatsPerBar=4.0, barsPerBeat=0.25;
162 var <baseBarBeat=0.0, <baseBar=0.0;
163 var <>permanent=false;
166 You should only change the tempo 'now'. You can't set the tempo at some beat in the future or past, even though you might think so from the methods.
168 There are several ideas of now:
169 elapsed time, i.e. "real time"
170 logical time in the current time base.
171 logical time in another time base.
173 logical time is time that is incremented by exact amounts from the time you started. It is not affected by the actual time your task gets scheduled, which may shift around somewhat due to system load. By calculating using logical time instead of actual time, your process will not drift out of sync over long periods. every thread stores a clock and its current logical time in seconds and beats relative to that clock.
175 elapsed time is whatever the system clock says it is right now. elapsed time is always advancing. logical time only advances when your task yields or returns.
179 *new { arg tempo, beats, seconds, queueSize=256;
180 ^super.new.init(tempo, beats, seconds, queueSize)
184 default = this.new(queueSize: 2048).permanent_(true);
189 all.do({ arg item; item.clear(false) });
190 all.do({ arg item; if (item.permanent.not, { item.stop }) })
193 init { arg tempo, beats, seconds, queueSize;
194 queue = Array.new(queueSize);
195 this.prStart(tempo, beats, seconds);
201 this.releaseDependants;
206 play { arg task, quant = 1;
207 this.schedAbs(quant.nextTimeOnGrid(this), task)
210 playNextBar { arg task; this.schedAbs(this.nextBar, task) }
214 ^this.primitiveFailed
218 ^this.primitiveFailed
221 _TempoClock_ElapsedBeats
222 ^this.primitiveFailed
223 /* primitive does this:
224 ^this.secs2beats(Main.elapsedTime).
228 // returns the appropriate beats for this clock from any thread
230 ^this.primitiveFailed
231 /* primitive does this:
232 if (thisThread.clock == this) { ^thisThread.beats }
233 ^this.secs2beats(thisThread.seconds)
237 seconds { ^thisThread.seconds }
239 sched { arg delta, item;
241 ^this.primitiveFailed
243 schedAbs { arg beat, item;
245 ^this.primitiveFailed
247 clear { | releaseNodes = true |
248 // flag tells EventStreamPlayers that CmdPeriod is removing them, so
249 // nodes are already freed
250 // NOTE: queue is an Array, not a PriorityQueue, but it's used as such internally. That's why each item uses 3 slots.
251 if (queue.size > 1) {
252 forBy(1, queue.size-1, 3) {|i|
253 queue[i+1].removedFromScheduler(releaseNodes)
260 // for setting the tempo at the current logical time
261 // (even another TempoClock's logical time).
262 tempo_ { arg newTempo;
263 this.setTempoAtBeat(newTempo, this.beats);
264 this.changed(\tempo); // this line is added
266 beatsPerBar_ { arg newBeatsPerBar;
267 if (thisThread.clock != this) {
268 "should only change beatsPerBar within the scheduling thread.".error;
271 this.setMeterAtBeat(newBeatsPerBar, thisThread.beats);
274 // for setting the tempo at the current elapsed time .
275 etempo_ { arg newTempo;
276 this.setTempoAtSec(newTempo, Main.elapsedTime);
279 beats2secs { arg beats;
280 _TempoClock_BeatsToSecs
281 ^this.primitiveFailed
283 secs2beats { arg secs;
284 _TempoClock_SecsToBeats
285 ^this.primitiveFailed
290 ^this.primitiveFailed
293 nextTimeOnGrid { arg quant = 1, phase = 0;
294 if (quant == 0) { ^this.beats + phase };
295 if (quant < 0) { quant = beatsPerBar * quant.neg };
296 if (phase < 0) { phase = phase % quant };
297 ^roundUp(this.beats - baseBarBeat - (phase % quant), quant) + baseBarBeat + phase
300 timeToNextBeat { arg quant=1.0; // logical time to next beat
301 ^quant.nextTimeOnGrid(this) - this.beats
304 beats2bars { arg beats;
305 ^(beats - baseBarBeat) * barsPerBeat + baseBar;
307 bars2beats { arg bars;
308 ^(bars - baseBar) * beatsPerBar + baseBarBeat;
311 // return the current bar.
312 ^this.beats2bars(this.beats).floor;
315 // given a number of beats, determine number beats at the next bar line.
316 if (beat.isNil) { beat = this.beats };
317 ^this.bars2beats(this.beats2bars(beat).ceil);
320 // return the beat of the bar, range is 0 to < t.beatsPerBar
321 ^this.beats - this.bars2beats(this.bar)
327 ^this.primitiveFailed
331 ^this.primitiveFailed
335 ^this.primitiveFailed
337 setTempoAtBeat { arg newTempo, beats;
338 _TempoClock_SetTempoAtBeat
339 ^this.primitiveFailed
341 setTempoAtSec { arg newTempo, secs;
342 _TempoClock_SetTempoAtTime
343 ^this.primitiveFailed
345 // meter should only be changed in the TempoClock's thread.
346 setMeterAtBeat { arg newBeatsPerBar, beats;
347 // bar must be integer valued when meter changes or confusion results later.
348 baseBar = round((beats - baseBarBeat) * barsPerBeat + baseBar, 1);
350 beatsPerBar = newBeatsPerBar;
351 barsPerBeat = beatsPerBar.reciprocal;
352 this.changed(\meter);
355 // these methods allow TempoClock to act as TempoClock.default
356 *stop { TempoClock.default.stop }
357 *play { | task, quant | TempoClock.default.play(task, quant) }
358 *sched { | delta, item | TempoClock.default.sched(delta, item) }
359 *schedAbs { | beat, item | TempoClock.default.schedAbs(beat, item) }
360 *clear { | releaseNodes | TempoClock.default.clear(releaseNodes) }
361 *tempo_ { | newTempo | TempoClock.default.tempo_(newTempo) }
362 *etempo_ { | newTempo | TempoClock.default.etempo_(newTempo) }
364 *tempo { ^TempoClock.default.tempo }
365 *beats { ^TempoClock.default.beats }
366 *beats2secs { | beats | ^TempoClock.default.beats2secs(beats) }
367 *secs2beats { | secs | ^TempoClock.default.secs2beats(secs) }
368 *nextTimeOnGrid { | quant = 1, phase = 0 | ^TempoClock.default.nextTimeOnGrid(quant, phase) }
369 *timeToNextBeat { | quant = 1 | ^TempoClock.default.timeToNextBeat(quant) }
371 *setTempoAtBeat { | newTempo, beats | TempoClock.default.setTempoAtBeat(newTempo, beats) }
372 *setTempoAtSec { | newTempo, secs | TempoClock.default.setTempoAtSec(newTempo, secs) }
373 *setMeterAtBeat { | newBeatsPerBar, beats | TempoClock.default.setMeterAtBeat(newBeatsPerBar, beats) }
375 *beatsPerBar { ^TempoClock.default.beatsPerBar }
376 *baseBarBeat { ^TempoClock.default.baseBarBeat }
377 *baseBar { ^TempoClock.default.baseBar }
378 *playNextBar { | task | ^TempoClock.default.playNextBar(task) }
379 *beatDur { ^TempoClock.default.beatDur }
380 *elapsedBeats { ^TempoClock.default.elapsedBeats }
381 *beatsPerBar_ { | newBeatsPerBar | TempoClock.default.beatsPerBar_(newBeatsPerBar) }
382 *beats2bars { | beats | ^TempoClock.default.beats2bars(beats) }
383 *bars2beats { | bars | ^TempoClock.default.bars2beats(bars) }
384 *bar { ^TempoClock.default.bar }
385 *nextBar { | beat | ^TempoClock.default.nextBar(beat) }
386 *beatInBar { ^TempoClock.default.beatInBar }
388 archiveAsCompileString { ^true }