353 lines
11 KiB
Text
353 lines
11 KiB
Text
|
CLASS::TempoClock
|
||
|
categories::Scheduling>Clocks
|
||
|
summary::tempo based scheduler
|
||
|
related::Classes/AppClock, Classes/SystemClock
|
||
|
|
||
|
DESCRIPTION::
|
||
|
|
||
|
TempoClock is a scheduler like link::Classes/SystemClock::, but it schedules relative to a
|
||
|
strong::tempo:: in beats per second.
|
||
|
|
||
|
See link::Classes/Clock:: for general explanation of how clocks operate.
|
||
|
|
||
|
CLASSMETHODS::
|
||
|
|
||
|
private::initClass
|
||
|
|
||
|
method::new
|
||
|
Creates a new instance of TempoClock.
|
||
|
|
||
|
argument:: tempo
|
||
|
The initial link::#-tempo#tempo::. Defaults to code::1::.
|
||
|
|
||
|
argument:: beats
|
||
|
The time in beats, corresponding to the reference time given with the code::seconds:: argument.
|
||
|
Defaults to code::0::.
|
||
|
|
||
|
argument:: seconds
|
||
|
The reference time in seconds, to which the code::beats:: argument corresponds.
|
||
|
Defaults to the current Thread's logical time
|
||
|
(see link::Classes/Thread#-seconds::).
|
||
|
|
||
|
argument:: queueSize
|
||
|
The storage size of the scheduling queue. Each scheduled item takes 2 counts of space, so this size
|
||
|
divided by 2 gives the amount of items that can be scheduled at a time. See also link::#-queue::.
|
||
|
|
||
|
discussion::
|
||
|
The TempoClock will be created strong::as if:: it started counting beats at the time given in the
|
||
|
code::seconds:: argument with the starting amount given in the code::beats:: argument. The current
|
||
|
count of beats will thus be equal to that starting amount plus the amount of beats that would be
|
||
|
counted since the given reference time in seconds, according to the given tempo.
|
||
|
|
||
|
The default arguments create a TempoClock that starts counting beats with code::0:: at the current
|
||
|
logical time.
|
||
|
|
||
|
code::
|
||
|
// Create a TempoClock that starts counting beats with 5 now.
|
||
|
(
|
||
|
t = TempoClock.new(2, 5);
|
||
|
"current beats:" + t.beats;
|
||
|
)
|
||
|
|
||
|
// Create a TempoClock, as if it started counting beats 5 seconds ago with 0.
|
||
|
(
|
||
|
t = TempoClock.new(2, 0, thisThread.seconds - 5);
|
||
|
"current beats:" + t.beats;
|
||
|
)
|
||
|
::
|
||
|
|
||
|
method::default
|
||
|
Sets or gets the permanent default TempoClock instantiated at startup.
|
||
|
code::
|
||
|
TempoClock.default.beats // beats since default TempoClock was started
|
||
|
::
|
||
|
|
||
|
subsection:: Forwarding to the default instance
|
||
|
|
||
|
The following methods only forward to the link::#*default#default instance::, allowing you to use
|
||
|
the TempoClock class itself in place of code::TempoClock.default::.
|
||
|
|
||
|
method::stop
|
||
|
method::play
|
||
|
method::sched
|
||
|
method::schedAbs
|
||
|
method::clear
|
||
|
method::tempo
|
||
|
method::etempo
|
||
|
method:: beats
|
||
|
method:: beats2secs
|
||
|
method:: secs2beats
|
||
|
method:: nextTimeOnGrid
|
||
|
method:: timeToNextBeat
|
||
|
method:: setTempoAtBeat
|
||
|
method:: setTempoAtSec
|
||
|
method:: setMeterAtBeat
|
||
|
method:: beatsPerBar
|
||
|
method:: baseBarBeat
|
||
|
method:: baseBar
|
||
|
method:: playNextBar
|
||
|
method:: beatDur
|
||
|
method:: elapsedBeats
|
||
|
method:: beats2bars
|
||
|
method:: bars2beats
|
||
|
method:: bar
|
||
|
method:: nextBar
|
||
|
method:: beatInBar
|
||
|
|
||
|
INSTANCEMETHODS::
|
||
|
|
||
|
private::prDump, prStart, prStop, prClear
|
||
|
|
||
|
method::stop
|
||
|
Destroys the scheduler and releases the OS thread running the scheduler.
|
||
|
|
||
|
method::clear
|
||
|
Removes all tasks from the scheduling queue.
|
||
|
|
||
|
method::tempo
|
||
|
Sets or gets the current tempo in beats per second.
|
||
|
code::
|
||
|
t= TempoClock.new;
|
||
|
t.tempo_(2.0); // equivalent to t.tempo = 2.0;
|
||
|
t.tempo;
|
||
|
t.tempo_(72/60) // 72 beats per minute
|
||
|
t.tempo;
|
||
|
::
|
||
|
|
||
|
method::permanent
|
||
|
Sets or gets a link::Classes/Boolean:: value indicating whether the clock will survive cmd-period. If false the clock is stopped (and thus removed) on cmd-period. If true the clock survives cmd-period. It is false by default.
|
||
|
|
||
|
method::beats
|
||
|
|
||
|
Gets or sets the current logical time in beats according to this clock.
|
||
|
|
||
|
When strong::getting:: code::beats::, if this clock is the current Thread's
|
||
|
link::Classes/Thread#-clock#associated clock::, the Thread's own
|
||
|
link::Classes/Thread#-beats#time in beats:: is returned, otherwise the Thread's
|
||
|
link::Classes/Thread#-seconds#time in seconds:: converted to beats according to this
|
||
|
clock is returned.
|
||
|
|
||
|
After strong::changing:: code::beats:: towards the strong::future::, the clock will
|
||
|
immediately perform all tasks scheduled until the new time. Likewise, when changing
|
||
|
code::beats:: towards the strong::past::, already scheduled tasks will be postponed, so
|
||
|
they will still be performed at the scheduled time in beats.
|
||
|
|
||
|
note::
|
||
|
When changing code::beats::, you are only changing the clocks's notion of time, and not
|
||
|
the current Thread's link::Classes/Thread#-beats#logical time::, which will stay the
|
||
|
same until the Thread is called again. Hence, if this clock is the current Thread's
|
||
|
link::Classes/Thread#-clock#associated clock::, and you ask the clock for time in beats
|
||
|
just after changing it, you will see no effect. Nevertheless, the effect will be visible
|
||
|
immediately on a different Thread.
|
||
|
::
|
||
|
|
||
|
code::
|
||
|
(
|
||
|
t = TempoClock.new;
|
||
|
|
||
|
t.sched(3, {
|
||
|
t.beats = 100;
|
||
|
t.beats.postln; // still 3
|
||
|
nil
|
||
|
});
|
||
|
)
|
||
|
|
||
|
(
|
||
|
c = TempoClock.new;
|
||
|
fork {
|
||
|
loop {
|
||
|
c.beats.postln; // updates, because ".wait" calls the thread
|
||
|
1.wait;
|
||
|
}
|
||
|
};
|
||
|
)
|
||
|
|
||
|
c.beats = 100;
|
||
|
|
||
|
::
|
||
|
|
||
|
|
||
|
method::schedAbs
|
||
|
|
||
|
Schedules a task to be performed at a particular time in strong::beats::.
|
||
|
|
||
|
When the scheduling time is up, the task's code::awake:: method is called. If the method
|
||
|
returns a number, the task will be rescheduled for the time equal to the last scheduling
|
||
|
time plus the returned value.
|
||
|
|
||
|
See also: link::Classes/Clock#Scheduling::, link::Classes/Object#-awake::.
|
||
|
|
||
|
|
||
|
method::sched
|
||
|
|
||
|
Schedules a task to be performed code::delta:: amount of strong::beats:: after the
|
||
|
current Thread's logical time. If this clock is the current Thread's
|
||
|
link::Classes/Thread#-clock#associated clock::, the Thread's
|
||
|
link::Classes/Thread#-beats#time in beats:: is used, otherwise the Thread's
|
||
|
link::Classes/Thread#-seconds#time in seconds:: is converted to beats according to this
|
||
|
clock's tempo and time of origin.
|
||
|
|
||
|
When the scheduling time is up, the task's code::awake:: method is called. If the method
|
||
|
returns a number, the task will be rescheduled for the time equal to the last scheduling
|
||
|
time plus the returned value.
|
||
|
|
||
|
See also: link::Classes/Clock#Scheduling::, link::Classes/Object#-awake::.
|
||
|
|
||
|
|
||
|
method::play
|
||
|
|
||
|
Plays task (a function) at the next beat, where strong::quant:: is 1 by default. Shortcut for link::#-schedAbs::; see link::#-seconds:: and link::#-nextTimeOnGrid:: for further details on time and quant.
|
||
|
code::
|
||
|
t= TempoClock.default;
|
||
|
t.play({arg beats, time, clock; [beats, time, clock].postln});
|
||
|
::
|
||
|
|
||
|
method::playNextBar
|
||
|
Plays task (a function) at the next bar using link::#-schedAbs::.
|
||
|
|
||
|
method::queue
|
||
|
Returns the scheduling queue Array in the form [beat, function]. The maximum number of items is determined by the clock's queueSize argument upon instantiation. The default queueSize of 256 allows 128 functions to be in the queue at any time.
|
||
|
|
||
|
method::beatDur
|
||
|
Returns the duration in seconds of a current whole beat.
|
||
|
|
||
|
method::beatsPerBar
|
||
|
Gets or sets the number of beats per bar. The default is 4. Setting must be done from within the scheduling thread, e.g.
|
||
|
code::
|
||
|
t= TempoClock.new;
|
||
|
t.schedAbs(t.nextBar, {t.beatsPerBar_(3)});
|
||
|
t.beatsPerBar;
|
||
|
::
|
||
|
|
||
|
method::bar
|
||
|
Returns the current bar. See link::#-bars2beats:: for returning beat of current bar.
|
||
|
|
||
|
method::nextBar
|
||
|
Returns the number of beats at the next bar line relative to the beat argument. If strong::beat:: is not supplied, returns the beat at which the next bar begins.
|
||
|
|
||
|
method::beatInBar
|
||
|
Returns the current bar beat (as a link::Classes/Float::) in relation to link::#-beatsPerBar::. Values range from 0 to < beatsPerBar.
|
||
|
|
||
|
method::baseBar
|
||
|
Returns bar at which link::#-beatsPerBar:: was last changed. If beatsPerBar has not been changed since the clock was created, returns 0.
|
||
|
|
||
|
method::baseBarBeat
|
||
|
Returns beat at which the link::#-beatsPerBar:: was last changed. If beatsPerBar has not been changed since the clock was created, returns 0.
|
||
|
|
||
|
method::beats2bars
|
||
|
Returns a bar as a float relative to link::#-baseBarBeat::.
|
||
|
|
||
|
method::bars2beats
|
||
|
Returns a beat relative to link::#-baseBar::.
|
||
|
code::
|
||
|
t= TempoClock.default;
|
||
|
t.bars2beats(t.bar) // downbeat of the current bar
|
||
|
::
|
||
|
|
||
|
method::timeToNextBeat
|
||
|
Returns the logical time to next beat. strong::quant:: is 1 by default, relative to baseBarBeat, see link::#-nextTimeOnGrid::.
|
||
|
|
||
|
method::nextTimeOnGrid
|
||
|
With default values, returns the next whole beat. strong::quant:: is 1 by default, strong::phase:: is 0. quant is relative to link::#-baseBarBeat::, such that
|
||
|
code::
|
||
|
t= TempoClock.default;
|
||
|
t.nextTimeOnGrid(t.beatsPerBar) == t.nextBar // => true
|
||
|
::
|
||
|
Together strong::quant:: and strong::phase:: are useful for finding the next n beat in a bar, e.g. code::nextTimeOnGrid(4, 2):: will return the next 3rd beat of a bar (of 4 beats), whereas code::nextBar-2:: may return an elapsed beat.
|
||
|
|
||
|
method::elapsedBeats
|
||
|
Returns the current elapsed time in beats. This is equivalent to code::tempoClock.secs2beats(Main.elapsedTime)::. It is often preferable to use link::#-beats:: instead of elapsedBeats because beats uses a thread's logical time.
|
||
|
|
||
|
method::seconds
|
||
|
Returns the current elapsed time. (This method is inherited from link::Classes/Clock::.)
|
||
|
|
||
|
method::beats2secs
|
||
|
Converts absolute strong::beats:: to absolute strong::seconds::, returning the elapsed time of the clock at the given strong::beats::. Only works for times in the current tempo. If the tempo changes any computed time in future will be wrong.
|
||
|
code::
|
||
|
t= TempoClock.default;
|
||
|
t.beats2secs(t.beats) // equivalent to t.seconds
|
||
|
t.beats2secs(0) // how many seconds after startup did beat 0 occur?
|
||
|
::
|
||
|
|
||
|
method::secs2beats
|
||
|
Converts absolute strong::seconds:: to absolute beats. Only works for times in the current tempo. If the tempo changes any computed time in future will be wrong.
|
||
|
|
||
|
EXAMPLES::
|
||
|
|
||
|
code::
|
||
|
t = TempoClock(1); // create a TempoClock
|
||
|
|
||
|
// schedule an event at next whole beat
|
||
|
t.schedAbs(t.beats.ceil, { arg beat, sec; [beat, sec].postln; 1 });
|
||
|
|
||
|
t.tempo = 2;
|
||
|
t.tempo = 4;
|
||
|
t.tempo = 0.5;
|
||
|
t.tempo = 1;
|
||
|
|
||
|
t.clear;
|
||
|
|
||
|
t.schedAbs(t.beats.ceil, { arg beat, sec; [beat, sec].postln; 1 });
|
||
|
|
||
|
t.stop;
|
||
|
::
|
||
|
code::
|
||
|
(
|
||
|
// get elapsed time, round up to next second
|
||
|
v = Main.elapsedTime.ceil;
|
||
|
|
||
|
// create two clocks in a 5:2 relation, starting at time v.
|
||
|
t = TempoClock(1, 0, v);
|
||
|
u = TempoClock(0.4, 0, v);
|
||
|
|
||
|
// start two functions at beat zero in each clock.
|
||
|
t.schedAbs(0, { arg beat, sec; [\t, beat, sec].postln; 1 });
|
||
|
u.schedAbs(0, { arg beat, sec; [\u, beat, sec].postln; 1 });
|
||
|
)
|
||
|
|
||
|
(
|
||
|
u.tempo = u.tempo * 3;
|
||
|
t.tempo = t.tempo * 3;
|
||
|
)
|
||
|
|
||
|
(
|
||
|
u.tempo = u.tempo * 1/4;
|
||
|
t.tempo = t.tempo * 1/4;
|
||
|
)
|
||
|
|
||
|
(
|
||
|
t.stop;
|
||
|
u.stop;
|
||
|
)
|
||
|
::
|
||
|
code::
|
||
|
(
|
||
|
// get elapsed time, round up to next second
|
||
|
v = Main.elapsedTime.ceil;
|
||
|
|
||
|
// create two clocks, starting at time v.
|
||
|
t = TempoClock(1, 0, v);
|
||
|
u = TempoClock(1, 0, v);
|
||
|
|
||
|
// start two functions at beat zero in each clock.
|
||
|
// t controls u's tempo. They should stay in sync.
|
||
|
t.schedAbs(0, { arg beat, sec; u.tempo = t.tempo * [1,2,3,4,5].choose; [\t, beat, sec].postln; 1 });
|
||
|
u.schedAbs(0, { arg beat, sec; [\u, beat, sec].postln; 1 });
|
||
|
)
|
||
|
|
||
|
(
|
||
|
u.tempo = u.tempo * 3;
|
||
|
t.tempo = t.tempo * 3;
|
||
|
)
|
||
|
|
||
|
(
|
||
|
u.tempo = u.tempo * 1/4;
|
||
|
t.tempo = t.tempo * 1/4;
|
||
|
)
|
||
|
|
||
|
(
|
||
|
t.stop;
|
||
|
u.stop;
|
||
|
)
|
||
|
::
|