class library: Volume - remove debug message
[supercollider.git] / SCClassLibrary / Common / Core / Clock.sc
blobf913412547825305ea4eecd2afbc8ea6440226f7
1 // clocks for timing threads.
3 Clock {
4         // abstract class
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 }
12         *beats2bars { ^0 }
13         *bars2beats { ^0 }
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;
19         }
22 SystemClock : Clock {
23         *clear {
24                 var queue = thisProcess.prSchedulerQueue;
25                 if (queue.size > 1) {
26                         forBy(1, queue.size-1, 3) {|i|
27                                 queue[i+1].removedFromScheduler
28                         };
29                 };
30                 this.prClear;
31         }
32         *sched { arg delta, item;
33                 _SystemClock_Sched
34                 ^this.primitiveFailed
35         }
36         *schedAbs { arg time, item;
37                 _SystemClock_SchedAbs
38                 ^this.primitiveFailed
39         }
40         *prClear {
41                 _SystemClock_Clear
42                 ^this.primitiveFailed
43         }
46 AppClock : Clock {
47         classvar scheduler;
48         *initClass {
49                 scheduler = Scheduler.new(this, drift:true, recursive:false);
50         }
51         *clear {
52                 scheduler.clear;
53         }
54         *sched { arg delta, item;
55                 scheduler.sched(delta, item);
56                 this.prSchedNotify;
57         }
58         *tick {
59                 var saveClock = thisThread.clock;
60                 thisThread.clock = this;
61                 scheduler.seconds = Main.elapsedTime;
62                 thisThread.clock = saveClock;
63                 ^scheduler.queue.topPriority;
64         }
65         *prSchedNotify {
66                 // notify clients that something has been scheduled
67                 _AppClock_SchedNotify
68         }
71 Scheduler {
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;
76         }
77         init {
78                 beats = thisThread.beats;
79                 queue = PriorityQueue.new;
80                 expired = Array.new(8);
81                 wakeup = { |item|
82                         var delta;
83                         try {
84                                 delta = item.awake( beats, seconds, clock );
85                                 if (delta.isNumber) { this.sched(delta, item) };
86                         } { |error|
87                                 Error.handling = true;
88                                 if (Error.debug) { error.inspect } { error.reportError };
89                                 Error.handling = false;
90                         }
91                 };
92         }
94         play { arg task;
95                 this.sched(0, task)
96         }
98         schedAbs { | time, item |
99                 queue.put(time, item);
100         }
102         sched { | delta, item |
103                 var fromTime;
104                 if (delta.notNil, {
105                         fromTime = if (drift, { Main.elapsedTime },{ seconds });
106                         queue.put(fromTime + delta, item);
107                 });
108         }
109         clear {
110                 queue.do {|x| x.removedFromScheduler };
111                 queue.clear
112         }
114         isEmpty { ^queue.isEmpty }
116         advance { | delta |
117                 this.seconds = seconds + delta;
118         }
120         seconds_ { | newSeconds |
121                 if( recursive ) {
122                         while {
123                                 seconds = queue.topPriority;
124                                 seconds.notNil and: { seconds <= newSeconds }
125                         } {
126                                 beats = clock.secs2beats(seconds);
127                                 wakeup.(queue.pop);
128                         }
129                 } {
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
133                         // awaken.
135                         while {
136                                 seconds = queue.topPriority;
137                                 seconds.notNil and: { seconds <= newSeconds }
138                         } {
139                                 expired = expired.add(seconds);
140                                 expired = expired.add(queue.pop);
141                         };
143                         expired.pairsDo { | time, item |
144                                 seconds = time;
145                                 beats = clock.secs2beats(time);
146                                 wakeup.(item);
147                         };
148                         expired.extend(0); // clear
149                 };
151                 seconds = newSeconds;
152                 beats = clock.secs2beats(newSeconds);
153         }
156 TempoClock : Clock {
157         classvar <all, <>default;
159         var <queue, ptr;
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)
181         }
183         *initClass {
184                 default = this.new(queueSize: 2048).permanent_(true);
185                 CmdPeriod.add(this);
186         }
188         *cmdPeriod {
189                 all.do({ arg item; item.clear(false) });
190                 all.do({ arg item; if (item.permanent.not, { item.stop })  })
191         }
193         init { arg tempo, beats, seconds, queueSize;
194                 queue = Array.new(queueSize);
195                 this.prStart(tempo, beats, seconds);
196                 all = all.add(this);
197         }
199         stop {
200                 this.changed(\stop);
201                 this.releaseDependants;
202                 all.take(this);
203                 this.prStop;
204         }
206         play { arg task, quant = 1;
207                 this.schedAbs(quant.nextTimeOnGrid(this), task)
208         }
210         playNextBar { arg task; this.schedAbs(this.nextBar, task) }
212         tempo {
213                 _TempoClock_Tempo
214                 ^this.primitiveFailed
215         }
216         beatDur {
217                 _TempoClock_BeatDur
218                 ^this.primitiveFailed
219         }
220         elapsedBeats {
221                 _TempoClock_ElapsedBeats
222                 ^this.primitiveFailed
223                 /* primitive does this:
224                         ^this.secs2beats(Main.elapsedTime).
225                 */
226         }
227         beats {
228                 // returns the appropriate beats for this clock from any thread
229                 _TempoClock_Beats
230                 ^this.primitiveFailed
231                 /* primitive does this:
232                         if (thisThread.clock == this) { ^thisThread.beats }
233                         ^this.secs2beats(thisThread.seconds)
234                 */
235         }
237         seconds { ^thisThread.seconds }
239         sched { arg delta, item;
240                 _TempoClock_Sched
241                 ^this.primitiveFailed
242         }
243         schedAbs { arg beat, item;
244                 _TempoClock_SchedAbs
245                 ^this.primitiveFailed
246         }
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)
254                         };
255                 };
256                 ^this.prClear;
257         }
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
265         }
266         beatsPerBar_ { arg newBeatsPerBar;
267                 if (thisThread.clock != this) {
268                         "should only change beatsPerBar within the scheduling thread.".error;
269                         ^this
270                 };
271                 this.setMeterAtBeat(newBeatsPerBar, thisThread.beats);
272         }
274         // for setting the tempo at the current elapsed time .
275         etempo_ { arg newTempo;
276                 this.setTempoAtSec(newTempo, Main.elapsedTime);
277         }
279         beats2secs { arg beats;
280                 _TempoClock_BeatsToSecs
281                 ^this.primitiveFailed
282         }
283         secs2beats { arg secs;
284                 _TempoClock_SecsToBeats
285                 ^this.primitiveFailed
286         }
288         prDump {
289                 _TempoClock_Dump
290                 ^this.primitiveFailed
291         }
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
298         }
300         timeToNextBeat { arg quant=1.0; // logical time to next beat
301                 ^quant.nextTimeOnGrid(this) - this.beats
302         }
304         beats2bars { arg beats;
305                 ^(beats - baseBarBeat) * barsPerBeat + baseBar;
306         }
307         bars2beats { arg bars;
308                 ^(bars - baseBar) * beatsPerBar + baseBarBeat;
309         }
310         bar {
311                 // return the current bar.
312                 ^this.beats2bars(this.beats).floor;
313         }
314         nextBar { arg beat;
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);
318         }
319         beatInBar {
320                 // return the beat of the bar, range is 0 to < t.beatsPerBar
321                 ^this.beats - this.bars2beats(this.bar)
322         }
324         // PRIVATE
325         prStart { arg tempo;
326                 _TempoClock_New
327                 ^this.primitiveFailed
328         }
329         prStop {
330                 _TempoClock_Free
331                 ^this.primitiveFailed
332         }
333         prClear {
334                 _TempoClock_Clear
335                 ^this.primitiveFailed
336         }
337         setTempoAtBeat { arg newTempo, beats;
338                 _TempoClock_SetTempoAtBeat
339                 ^this.primitiveFailed
340         }
341         setTempoAtSec { arg newTempo, secs;
342                 _TempoClock_SetTempoAtTime
343                 ^this.primitiveFailed
344         }
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);
349                 baseBarBeat = beats;
350                 beatsPerBar = newBeatsPerBar;
351                 barsPerBeat = beatsPerBar.reciprocal;
352                 this.changed(\meter);
353         }
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 }