484 lines
12 KiB
Racket
484 lines
12 KiB
Racket
#lang scribble/manual
|
|
@(require (for-label racket))
|
|
|
|
@title{TempoClock}
|
|
@section{categories}
|
|
Scheduling>Clocks
|
|
tempo based scheduler@section{related}
|
|
Classes/AppClock, Classes/SystemClock
|
|
|
|
@section{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.
|
|
|
|
@section{CLASSMETHODS}
|
|
|
|
|
|
@section{private}
|
|
initClass
|
|
|
|
@section{method}
|
|
new
|
|
Creates a new instance of TempoClock.
|
|
|
|
@section{argument}
|
|
tempo
|
|
The initial link::#-tempo#tempo::. Defaults to
|
|
@racketblock[1::.
|
|
|
|
]
|
|
@section{argument}
|
|
beats
|
|
The time in beats, corresponding to the reference time given with the
|
|
@racketblock[seconds:: argument.
|
|
Defaults to ]
|
|
|
|
@racketblock[0::.
|
|
|
|
]
|
|
@section{argument}
|
|
seconds
|
|
The reference time in seconds, to which the
|
|
@racketblock[beats:: argument corresponds.
|
|
Defaults to the current Thread's logical time
|
|
(see link::Classes/Thread#-seconds::).
|
|
|
|
]
|
|
@section{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::.
|
|
|
|
@section{discussion}
|
|
|
|
The TempoClock will be created strong::as if:: it started counting beats at the time given in the
|
|
|
|
@racketblock[seconds:: argument with the starting amount given in the ]
|
|
|
|
@racketblock[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 ]
|
|
|
|
@racketblock[0:: at the current
|
|
logical time.
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
// 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;
|
|
)
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
default
|
|
Sets or gets the permanent default TempoClock instantiated at startup.
|
|
|
|
@racketblock[
|
|
TempoClock.default.beats // beats since default TempoClock was started
|
|
::
|
|
|
|
]
|
|
@section{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
|
|
@racketblock[TempoClock.default::.
|
|
|
|
]
|
|
@section{method}
|
|
stop
|
|
@section{method}
|
|
play
|
|
@section{method}
|
|
sched
|
|
@section{method}
|
|
schedAbs
|
|
@section{method}
|
|
clear
|
|
@section{method}
|
|
tempo
|
|
@section{method}
|
|
etempo
|
|
@section{method}
|
|
beats
|
|
@section{method}
|
|
beats2secs
|
|
@section{method}
|
|
secs2beats
|
|
@section{method}
|
|
nextTimeOnGrid
|
|
@section{method}
|
|
timeToNextBeat
|
|
@section{method}
|
|
setTempoAtBeat
|
|
@section{method}
|
|
setTempoAtSec
|
|
@section{method}
|
|
setMeterAtBeat
|
|
@section{method}
|
|
beatsPerBar
|
|
@section{method}
|
|
baseBarBeat
|
|
@section{method}
|
|
baseBar
|
|
@section{method}
|
|
playNextBar
|
|
@section{method}
|
|
beatDur
|
|
@section{method}
|
|
elapsedBeats
|
|
@section{method}
|
|
beats2bars
|
|
@section{method}
|
|
bars2beats
|
|
@section{method}
|
|
bar
|
|
@section{method}
|
|
nextBar
|
|
@section{method}
|
|
beatInBar
|
|
|
|
@section{INSTANCEMETHODS}
|
|
|
|
|
|
@section{private}
|
|
prDump, prStart, prStop, prClear
|
|
|
|
@section{method}
|
|
stop
|
|
Destroys the scheduler and releases the OS thread running the scheduler.
|
|
|
|
@section{method}
|
|
clear
|
|
Removes all tasks from the scheduling queue.
|
|
|
|
@section{method}
|
|
tempo
|
|
Sets or gets the current tempo in beats per second.
|
|
|
|
@racketblock[
|
|
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;
|
|
::
|
|
|
|
]
|
|
@section{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.
|
|
|
|
@section{method}
|
|
beats
|
|
|
|
Gets or sets the current logical time in beats according to this clock.
|
|
|
|
When strong::getting::
|
|
@racketblock[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:: ]
|
|
|
|
@racketblock[beats:: towards the strong::future::, the clock will
|
|
immediately perform all tasks scheduled until the new time. Likewise, when changing
|
|
]
|
|
|
|
@racketblock[beats:: towards the strong::past::, already scheduled tasks will be postponed, so
|
|
they will still be performed at the scheduled time in beats.
|
|
|
|
]
|
|
@section{note}
|
|
|
|
When changing
|
|
@racketblock[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.
|
|
::
|
|
|
|
]
|
|
|
|
@racketblock[
|
|
(
|
|
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;
|
|
|
|
::
|
|
|
|
|
|
]
|
|
@section{method}
|
|
schedAbs
|
|
|
|
Schedules a task to be performed at a particular time in strong::beats::.
|
|
|
|
When the scheduling time is up, the task's
|
|
@racketblock[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::.
|
|
|
|
|
|
]
|
|
@section{method}
|
|
sched
|
|
|
|
Schedules a task to be performed
|
|
@racketblock[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 ]
|
|
|
|
@racketblock[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::.
|
|
|
|
|
|
]
|
|
@section{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.
|
|
|
|
@racketblock[
|
|
t= TempoClock.default;
|
|
t.play({arg beats, time, clock; [beats, time, clock].postln});
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
playNextBar
|
|
Plays task (a function) at the next bar using link::#-schedAbs::.
|
|
|
|
@section{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.
|
|
|
|
@section{method}
|
|
beatDur
|
|
Returns the duration in seconds of a current whole beat.
|
|
|
|
@section{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.
|
|
|
|
@racketblock[
|
|
t= TempoClock.new;
|
|
t.schedAbs(t.nextBar, {t.beatsPerBar_(3)});
|
|
t.beatsPerBar;
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
bar
|
|
Returns the current bar. See link::#-bars2beats:: for returning beat of current bar.
|
|
|
|
@section{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.
|
|
|
|
@section{method}
|
|
beatInBar
|
|
Returns the current bar beat (as a link::Classes/Float::) in relation to link::#-beatsPerBar::. Values range from 0 to < beatsPerBar.
|
|
|
|
@section{method}
|
|
baseBar
|
|
Returns bar at which link::#-beatsPerBar:: was last changed. If beatsPerBar has not been changed since the clock was created, returns 0.
|
|
|
|
@section{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.
|
|
|
|
@section{method}
|
|
beats2bars
|
|
Returns a bar as a float relative to link::#-baseBarBeat::.
|
|
|
|
@section{method}
|
|
bars2beats
|
|
Returns a beat relative to link::#-baseBar::.
|
|
|
|
@racketblock[
|
|
t= TempoClock.default;
|
|
t.bars2beats(t.bar) // downbeat of the current bar
|
|
::
|
|
|
|
]
|
|
@section{method}
|
|
timeToNextBeat
|
|
Returns the logical time to next beat. strong::quant:: is 1 by default, relative to baseBarBeat, see link::#-nextTimeOnGrid::.
|
|
|
|
@section{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
|
|
|
|
@racketblock[
|
|
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. ]
|
|
|
|
@racketblock[nextTimeOnGrid(4, 2):: will return the next 3rd beat of a bar (of 4 beats), whereas ]
|
|
|
|
@racketblock[nextBar-2:: may return an elapsed beat.
|
|
|
|
]
|
|
@section{method}
|
|
elapsedBeats
|
|
Returns the current elapsed time in beats. This is equivalent to
|
|
@racketblock[tempoClock.secs2beats(Main.elapsedTime)::. It is often preferable to use link::#-beats:: instead of elapsedBeats because beats uses a thread's logical time.
|
|
|
|
]
|
|
@section{method}
|
|
seconds
|
|
Returns the current elapsed time. (This method is inherited from link::Classes/Clock::.)
|
|
|
|
@section{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.
|
|
|
|
@racketblock[
|
|
t= TempoClock.default;
|
|
t.beats2secs(t.beats) // equivalent to t.seconds
|
|
t.beats2secs(0) // how many seconds after startup did beat 0 occur?
|
|
::
|
|
|
|
]
|
|
@section{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.
|
|
|
|
@section{EXAMPLES}
|
|
|
|
|
|
|
|
@racketblock[
|
|
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;
|
|
::
|
|
]
|
|
|
|
@racketblock[
|
|
(
|
|
// 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;
|
|
)
|
|
::
|
|
]
|
|
|
|
@racketblock[
|
|
(
|
|
// 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;
|
|
)
|
|
::
|
|
]
|
|
|
|
|