clockblocks.clock.Clock
- class clockblocks.clock.Clock(name: str | None = None, parent: Clock | None = None, initial_rate: float | None = None, initial_tempo: float | None = None, initial_beat_length: float | None = None, clock_family_options: ClockFamilyOptions | None = None)[source]
Bases:
objectRecursively nestable clock. Clocks can fork child-clocks, which can in turn fork their own child-clocks. A clock with no parent is the master, and the whole family stays coordinated under it.
A master created on the main thread needs no cleanup. If you create one on another thread, call
master.kill()when you’re done with it so its background timing thread doesn’t linger (or userun_as_server(), which manages that thread for you).- Parameters:
name – (optional) can be useful for keeping track in confusing multi-threaded situations
parent – the parent clock for this clock; a value of None indicates the master clock
initial_rate – starting rate of this clock (if set, don’t set initial tempo or beat length)
initial_tempo – starting tempo of this clock (if set, don’t set initial rate or beat length)
initial_beat_length – starting beat length of this clock (if set, don’t set initial tempo or rate)
clock_family_options – (master-only) a
ClockFamilyOptionsbundling the family-level timing/threading knobs (timing_policy,precise_timing,spin_guard_duration,pool_size,prewarm_pool). The live knobs are also adjustable afterwards viatiming_policy,precise_timing, andspin_guard_duration. Passing it to a forked child raises an error, since a child shares its master’s scheduler and pool.
- Variables:
name – the name of this clock (string)
parent – the parent Clock to which this clock belongs (Clock, or None if master clock)
tempo_history – TempoHistory describing how this clock has changed or will change tempo
Methods
apply_beat_length_function(function[, ...])Drive this clock's beat_length from a function.
apply_rate_function(function[, ...])Drive this clock's rate from a function.
apply_tempo_envelope(envelope[, truncate, loop])Append the given
TempoEnvelopeonto this clock's internal tempo envelope, starting from the current beat.apply_tempo_function(function[, ...])Drive this clock's tempo (BPM) from a function.
Advance this clock's tempo_history to match where it currently is in scheduler time.
children()Get all direct child clocks forked by this clock.
clock_to_scheduler_time(beat_or_time[, units])Gets the time in the scheduler for a given beat or time in this clock, working recursively up the chain of clocks.
Get all children, grandchildren, etc.
Extract this clock's absolute tempo curve — its tempo as observed in master (scheduler) time, with parent rate-changes folded in.
fast_forward([on_or_off])Turn indefinite fast-forwarding on or off.
Fast-forward, skipping ahead instantaneously by b beats.
Fast-forward, skipping ahead instantaneously by t seconds.
Fast-forward, skipping instantaneously up to beat b on this clock.
Fast-forward, skipping instantaneously up to time t (in seconds) on this clock, then resume real-time playback.
fork(forked_function[, args, kwargs, name, ...])Spawns a child clock running forked_function as a coordinated parallel timeline.
Context manager that: 1) ensures that the scheduler is not executing scheduled actions — waiting if one is in flight, then preventing others from starting; 2) rouses the scheduler and updates its committed time to the current projected time.
inheritance([include_self])Get all parent, grandparent, etc.
Whether the clock is currently fast-forwarding.
Check if this is the master clock
iterate_all_relatives([include_self])Iterate through all related clocks to this clock.
iterate_descendants([include_self])Iterate through all children, grandchildren, etc.
iterate_inheritance([include_self])Iterate through parent, grandparent, etc.
kill()End this clock (and the corresponding forked function if not master) and cascade to all descendant clocks.
Print a hierarchical representation of this clock's family tree.
print_status([verbose])Print this clock's
status()snapshot.A wall-clock-interpolated estimate of
beat.A wall-clock-interpolated estimate of
time, advancing smoothly between events rather than holding still between them.Run this (master) clock on a background daemon thread so the calling thread stays free — the approach for driving clockblocks from an interactive REPL:
c = Clock().run_as_server().schedule_action(action, when[, args, kwargs])The lightweight alternative to fork() for immediately returning functions.
scheduler_to_clock_time(scheduler_time[, ...])Gets beats or time in this clock for a given time in the scheduler.
set_beat_length_target(beat_length_target, when)Smoothly change this clock's beat length (seconds per beat) to
beat_length_target, arriving at the moment given bywhen.set_beat_length_targets(beat_length_targets, ...)Smoothly change this clock's beat length (seconds per beat) through a series of targets, building a multi-segment tempo curve in one call — the multi-segment form of
set_beat_length_target().set_rate_target(rate_target, when[, ...])Smoothly change this clock's rate (beats per second) to
rate_target, arriving atwhen.set_rate_targets(rate_targets, whens[, ...])Smoothly change this clock's rate (beats per second; the reciprocal of beat length, so a higher rate is a faster tempo) through a series of targets — the multi-segment form of
set_rate_target().set_tempo_target(tempo_target, when[, ...])Smoothly change this clock's tempo to
tempo_target, arriving atwhen.set_tempo_targets(tempo_targets, whens[, ...])Smoothly change this clock's tempo (beats per minute) through a series of targets — the multi-segment form of
set_tempo_target(), and usually the most natural of the three equivalent plural setters.status([verbose])A snapshot of this clock's name and position, alongside the scheduler's elapsed wall time and how far behind schedule it is currently running.
Stop following any function or looping envelope previously applied to this clock's tempo.
Kill this clock's children and their descendants in turn, returning once they have finished unwinding.
Shorthand for
timing_policy = 0.0(always catch up to absolute schedule).use_mixed_timing_policy(absolute_relative_mix)Shorthand for
timing_policy = absolute_relative_mix(a blend between 0=absolute and 1=relative).Shorthand for
timing_policy = 1.0(always wait the full requested delay).wait(duration[, units])Block this clock for duration beats (or seconds, if units="time") from now, yielding to the scheduler.
Block this clock's own thread until all of its child clocks have finished, yielding to the scheduler so they can run, then return as soon as the last child ends.
wait_for_clock_to_finish(clock)Block this clock's own thread until clock has finished, yielding to the scheduler so it (and everything else) keeps running.
Block this clock's own thread indefinitely, yielding to the scheduler so child clocks keep running.
wait_until(when[, units])Block this clock until the beat (or time, if units="time") indicated by when — a convenience for absolute targets given as a bare number (equivalent to wait(Moment.at_beat(when)), or Moment.at_time for units="time").
Attributes
Beat length in true seconds, with all parent rates folded in.
Rate of this clock in beats / (true) second, with all parent rates folded in.
Tempo (BPM) in true minutes, with all parent rates folded in.
True if this clock is currently running (PENDING and DEAD both return False).
How many beats have passed since this clock was created.
The length of a beat in this clock in seconds.
The master clock under which this clock operates (possibly itself)
Whether to use a busy wait in the final moments leading up to a scheduled event.
The rate of this clock in beats / second.
Width (seconds) of the busy-wait guard band used when
precise_timingis on.The rate of this clock in beats / minute Note that beat_length, tempo and rate are interconnected properties, and that by setting one of them the other two are automatically set in response according to the relationship: beat_length = 1/rate = 60/tempo.
How much time has passed since this clock was created.
How the family trades off relative vs. absolute timing, as a float from 0 to 1.
- is_master() bool[source]
Check if this is the master clock
- Returns:
True if this is the master clock, False otherwise
- hold_scheduler()[source]
Context manager that: 1) ensures that the scheduler is not executing scheduled actions — waiting if one is in flight, then preventing others from starting; 2) rouses the scheduler and updates its committed time to the current projected time. Code under this context manager therefore acts like an immediately scheduled action, holding exclusive access to an up-to-date scheduler.
Note that, when used from within the clock system, this context manager is a no-op, since the current thread already holds exclusive access to an up-to-date scheduler. In fact, invoking clock.scheduler.held() in that context would cause deadlock, which is why this method exists as the main entry point.
- children() Sequence[Clock][source]
Get all direct child clocks forked by this clock.
- Returns:
tuple of all child clocks of this clock
- iterate_inheritance(include_self: bool = True) Iterator[Clock][source]
Iterate through parent, grandparent, etc. of this clock up until the master clock
- Parameters:
include_self – whether or not to include this clock in the iterator or start with the parent
- Returns:
iterator going up the clock family tree up to the master clock
- inheritance(include_self: bool = True) Sequence[Clock][source]
Get all parent, grandparent, etc. of this clock up until the master clock
- Parameters:
include_self – whether or not to include this clock in the iterator or start with the parent
- Returns:
tuple containing the clock’s inheritance
- iterate_all_relatives(include_self: bool = False) Iterator[Clock][source]
Iterate through all related clocks to this clock.
- Parameters:
include_self – whether or not to include this clock in the iterator
- Returns:
iterator going through all clocks in the family tree, starting with the master
- iterate_descendants(include_self: bool = False) Iterator[Clock][source]
Iterate through all children, grandchildren, etc. of this clock
- Parameters:
include_self – whether or not to include this clock in the iterator
- Returns:
iterator going through all descendants
- descendants() Sequence[Clock][source]
Get all children, grandchildren, etc. of this clock
- Returns:
tuple of all descendants
- property time: float
How much time has passed since this clock was created. Either in seconds, if this is the master clock, or in beats in the parent clock, if this clock was the result of a call to fork. Note that this is quantized to the scheduler’s event stream; for a live estimate when outside the clock system use
projected_time().(Read-only property. Before clockblocks 1.1 this was a method; the
clock.time()spelling still works, with a DeprecationWarning.)
- property beat: float
How many beats have passed since this clock was created. Note that this is quantized to the scheduler’s event stream; for a live estimate when outside the clock system use
projected_time().(Read-only property. Before clockblocks 1.1 this was a method; the
clock.beat()spelling still works, with a DeprecationWarning.)
- projected_time() float[source]
A wall-clock-interpolated estimate of
time, advancing smoothly between events rather than holding still between them. For readers outside the clock system; see the section comment above. (A method rather than a property, because it is a live sample: every call returns a different value.)This is an estimate, and it can step backward — see
projected_time().- Returns:
the estimated elapsed time, in the same units as
time.
- projected_beat() float[source]
A wall-clock-interpolated estimate of
beat. Seeprojected_time().- Returns:
the estimated elapsed beats.
- status(verbose: bool = False) str[source]
A snapshot of this clock’s name and position, alongside the scheduler’s elapsed wall time and how far behind schedule it is currently running.
- property beat_length: float
The length of a beat in this clock in seconds. Note that beat_length, tempo and rate are interconnected properties, and that by setting one of them the other two are automatically set in response according to the relationship: beat_length = 1/rate = 60/tempo. Also, note that “seconds” refers to actual seconds only in the master clock; otherwise it refers to beats in the parent clock.
- property rate: float
The rate of this clock in beats / second. Note that beat_length, tempo and rate are interconnected properties, and that by setting one of them the other two are automatically set in response according to the relationship: beat_length = 1/rate = 60/tempo. Also, note that “seconds” refers to actual seconds only in the master clock; otherwise it refers to beats in the parent clock.
- property tempo: float
The rate of this clock in beats / minute Note that beat_length, tempo and rate are interconnected properties, and that by setting one of them the other two are automatically set in response according to the relationship: beat_length = 1/rate = 60/tempo. Also, note that “seconds” refers to actual seconds only in the master clock; otherwise it refers to beats in the parent clock.
- property absolute_rate: float
Rate of this clock in beats / (true) second, with all parent rates folded in.
(Read-only property. Before clockblocks 1.1 this was a method; the
clock.absolute_rate()spelling still works, with a DeprecationWarning.)
- property absolute_tempo: float
Tempo (BPM) in true minutes, with all parent rates folded in. (Read-only property; the pre-1.1
clock.absolute_tempo()spelling still works, with a DeprecationWarning.)
- property absolute_beat_length: float
Beat length in true seconds, with all parent rates folded in. (Read-only property; the pre-1.1
clock.absolute_beat_length()spelling still works, with a DeprecationWarning.)
- set_beat_length_target(beat_length_target: float, when: ResolvableMoment, curve_shape: float = None, truncate: bool = True, align_to: ResolvableMoment = None) None[source]
Smoothly change this clock’s beat length (seconds per beat) to
beat_length_target, arriving at the moment given bywhen. This is the underlying representation behindset_tempo_target()andset_rate_target(); a longer beat length means a slower tempo.- Parameters:
beat_length_target – the beat length to arrive at, in seconds per beat.
when – when the target should be reached, as a
Moment(e.g.Moment.after_beats(4),Moment.at_time(10)) or aMetricPhaseTarget(the next point matching a particular phase within the beat cycle).after_*moments count from the clock’s current position. A plain number is rejected; wrap it in a Moment to clarify beat/time and relative/absolute.curve_shape – the bend of the transition.
0(the default) is a straight line;> 0keeps the old tempo longer and changes late;< 0changes early then eases in. Whenalign_tosolves for the curvature (see below) this acts only as a starting hint, and with a fixedalign_to, it is ignored entirely (and a warning is issued if set).truncate – if
True(the default), any tempo curve already scheduled past the current beat is discarded first, so the change starts from where the clock is right now. IfFalse, this target is appended after whatever is already scheduled.align_to – optional. If when is an arrival beat, this specifies the desired arrival time. If when is an arrival time, this specifies the desired arrival beat. A MetricPhaseTarget can be given instead of a singular time/beat, to express when we should arrive within a time/beat cycle. Note that the coordination of beat and time is done via mutating the curve shape, so an explicit curve shape is only a hint (ignored completely if align_to is a fixed point rather than a metric phase target).
- Raises:
ValueError – if
when/align_toshare an axis, or if no curvature can reach the requestedalign_to(in which case the tempo curve is left unchanged).
- set_rate_target(rate_target: float, when: ResolvableMoment, curve_shape: float = None, truncate: bool = True, align_to: ResolvableMoment = None) None[source]
Smoothly change this clock’s rate (beats per second) to
rate_target, arriving atwhen. Rate is the reciprocal of beat length: a higher rate is a faster tempo.- Parameters:
rate_target – the rate to arrive at, in beats per second.
when – when the target is reached. See
set_beat_length_target().curve_shape – the bend of the transition. See
set_beat_length_target().truncate – whether to discard already-scheduled tempo changes first. See
set_beat_length_target().align_to – optional; solve the curvature to land the free axis on a phase or coordinate. See
set_beat_length_target().
- Raises:
ValueError – see
set_beat_length_target().
- set_tempo_target(tempo_target: float, when: ResolvableMoment, curve_shape: float = None, truncate: bool = True, align_to: ResolvableMoment = None) None[source]
Smoothly change this clock’s tempo to
tempo_target, arriving atwhen. Tempo is measured in beats per minute; this is usually the most natural of the three equivalent ways to set a target (see alsoset_rate_target()andset_beat_length_target()).- Parameters:
tempo_target – the tempo to arrive at, in beats per minute (quarter-notes per minute by convention).
when – when the target is reached. See
set_beat_length_target().curve_shape – the bend of the transition. See
set_beat_length_target().truncate – whether to discard already-scheduled tempo changes first. See
set_beat_length_target().align_to – optional; solve the curvature to land the free axis on a phase or coordinate. See
set_beat_length_target().
- Raises:
ValueError – see
set_beat_length_target().
- set_beat_length_targets(beat_length_targets: Sequence[float], whens: Sequence[ResolvableMoment], curve_shapes: Sequence[float] = None, truncate: bool = True, align_to: ResolvableMoment | Sequence[ResolvableMoment | None] = None) None[source]
Smoothly change this clock’s beat length (seconds per beat) through a series of targets, building a multi-segment tempo curve in one call — the multi-segment form of
set_beat_length_target(). This is non-looping; to apply a looping tempo shape, build aTempoEnvelopeand useapply_tempo_envelope().- Parameters:
beat_length_targets – the beat lengths (seconds per beat) to arrive at, one per segment.
whens – when each target is reached (same length as
beat_length_targets), each aMomentorMetricPhaseTarget. Note that every when is resolved against the clock’s current position, so anafter_*moment counts from now, not from the previous segment’s. Beats- and time-axis moments may be freely mixed across the list, so long as the whens come out in strictly increasing clock-time.curve_shapes – optional per-segment bends (same length as
beat_length_targets), each as inset_beat_length_target().Nonefor the whole argument (the default), or a per-elementNone, means linear (0) for that segment.truncate – if
True(the default), any tempo curve already scheduled past the current beat is discarded first, so the run starts from where the clock is now; ifFalsethe run is appended after whatever is already scheduled.align_to –
optional curvature-solved alignment, extending
set_beat_length_target()’salign_toto runs of segments. Either:a single
Moment/MetricPhaseTarget— align the whole call as one run, landing its end on the target; ora per-segment list/tuple (same length as
beat_length_targets) ofNone/targets — each non-Noneentry closes a run (all segments since the previous alignment, or the start) and bends them collectively so the run lands on that target.
A group-aligned run of more than one segment must have its
whenson a single axis (all beats or all time), with the alignment target on the opposite (free) axis.
- Raises:
ValueError – if
whens/curve_shapes/align_tolengths are inconsistent withbeat_length_targets; if thewhensdo not come out strictly increasing in clock-time (the error names the offending index); if a multi-segment aligned run mixes beat- and time-axiswhens; or if an alignment target is unreachable by curvature adjustment. On any failure the tempo curve is left unchanged.
- set_rate_targets(rate_targets: Sequence[float], whens: Sequence[ResolvableMoment], curve_shapes: Sequence[float] = None, truncate: bool = True, align_to: ResolvableMoment | Sequence[ResolvableMoment | None] = None) None[source]
Smoothly change this clock’s rate (beats per second; the reciprocal of beat length, so a higher rate is a faster tempo) through a series of targets — the multi-segment form of
set_rate_target().- Parameters:
rate_targets – the rates (beats per second) to arrive at, one per segment.
whens – when each target is reached. See
set_beat_length_targets().curve_shapes – optional per-segment bends. See
set_beat_length_targets().truncate – whether to discard already-scheduled tempo changes first. See
set_beat_length_targets().align_to – optional curvature-solved alignment of segment runs. See
set_beat_length_targets().
- Raises:
ValueError – see
set_beat_length_targets().
- set_tempo_targets(tempo_targets: Sequence[float], whens: Sequence[ResolvableMoment], curve_shapes: Sequence[float] = None, truncate: bool = True, align_to: ResolvableMoment | Sequence[ResolvableMoment | None] = None) None[source]
Smoothly change this clock’s tempo (beats per minute) through a series of targets — the multi-segment form of
set_tempo_target(), and usually the most natural of the three equivalent plural setters.- Parameters:
tempo_targets – the tempos (beats per minute) to arrive at, one per segment.
whens – when each target is reached. See
set_beat_length_targets().curve_shapes – optional per-segment bends. See
set_beat_length_targets().truncate – whether to discard already-scheduled tempo changes first. See
set_beat_length_targets().align_to – optional curvature-solved alignment of segment runs. See
set_beat_length_targets().
- Raises:
ValueError – see
set_beat_length_targets().
- apply_beat_length_function(function: Callable, domain_start: float = 0, domain_end: float = None, duration_units: str = 'beats', truncate: bool = True, loop: bool = False, extension_increment: float = 2.0, **kwargs) None[source]
Drive this clock’s beat_length from a function. See
apply_function()for the full parameter list (passed via**kwargs).
- apply_rate_function(function: Callable, domain_start: float = 0, domain_end: float = None, duration_units: str = 'beats', truncate: bool = True, loop: bool = False, extension_increment: float = 2.0, **kwargs) None[source]
Drive this clock’s rate from a function.
- apply_tempo_function(function: Callable, domain_start: float = 0, domain_end: float = None, duration_units: str = 'beats', truncate: bool = True, loop: bool = False, extension_increment: float = 2.0, **kwargs) None[source]
Drive this clock’s tempo (BPM) from a function.
- apply_tempo_envelope(envelope: TempoEnvelope, truncate: bool = True, loop: bool = False) None[source]
Append the given
TempoEnvelopeonto this clock’s internal tempo envelope, starting from the current beat. With loop=True the envelope repeats indefinitely untilstop_tempo_loop_or_function(). truncate first discards any tempo curve already projected past the current beat so the envelope begins cleanly from now.
- stop_tempo_loop_or_function() None[source]
Stop following any function or looping envelope previously applied to this clock’s tempo.
- clock_to_scheduler_time(beat_or_time, units='beats')[source]
Gets the time in the scheduler for a given beat or time in this clock, working recursively up the chain of clocks.
- Parameters:
beat_or_time – the beat or time of interest in this clock
units – one of (“beats”, “time”), determining the units for the first argument
- Returns:
how much time should pass in the scheduler
- scheduler_to_clock_time(scheduler_time, desired_units='beats')[source]
Gets beats or time in this clock for a given time in the scheduler.
- Parameters:
scheduler_time – the time of interest in the scheduler
desired_units – one of (“beats”, “time”), whether we’re looking for beats or time in this clock
- Returns:
how much time should pass in the scheduler
- bring_up_to_date()[source]
Advance this clock’s tempo_history to match where it currently is in scheduler time.
Called before mutating tempo from a foreign thread: if we don’t, the tempo setter’s truncate() cuts at the last committed beat (whatever the clock last woke at), causing the new tempo to retroactively reshape the segment the clock is currently napping through. From the owning thread this is a no-op since committed == current.
- extract_absolute_tempo_envelope(start_beat: float = 0, end_beat: float | None = None, step_size: float = 0.1, tolerance: float = 0.005) TempoEnvelope[source]
Extract this clock’s absolute tempo curve — its tempo as observed in master (scheduler) time, with parent rate-changes folded in. Used when building a Score from this clock’s perspective.
Extracts over
[start_beat, end_beat], where end_beat defaults to this clock’s current beat. When all ancestors are pure, flat tempos, they simply scale this clock’s tempo curve directly. When the ancestors are changing, there is no exact solution, so we sample every step_size beats. Sampling is aligned to the tempo break points of every clock in the chain, so an instantaneous tempo change stays sharp rather than smearing into a short accelerando or ritardando.
- wait(duration: float | ResolvableMoment, units: str = 'beats') None[source]
Block this clock for duration beats (or seconds, if units=”time”) from now, yielding to the scheduler. duration may also be any ResolvableMoment (a Moment or MetricPhaseTarget), in which case it is resolved directly and units is ignored — e.g. wait(Moment.at_beat(8)) or wait(MetricPhaseTarget(0, 4)). wait_until() is just a convenience for absolute targets given as a bare number; passing an absolute Moment to wait() does the same thing.
- wait_until(when: float | ResolvableMoment, units: str = 'beats') None[source]
Block this clock until the beat (or time, if units=”time”) indicated by when — a convenience for absolute targets given as a bare number (equivalent to wait(Moment.at_beat(when)), or Moment.at_time for units=”time”). when may also be any ResolvableMoment, in which case the units are ignored and behavior is identical to wait(). If when is in the past, returns essentially immediately.
- fork(forked_function: Callable, args: Sequence = (), kwargs: dict = None, name: str = None, initial_rate: float = None, initial_tempo: float = None, initial_beat_length: float = None, when: ResolvableMoment | None = None, done_callback: Callable[[], None] = None)[source]
Spawns a child clock running forked_function as a coordinated parallel timeline.
The child runs on its own thread but stays synchronized under this clock’s master scheduler — “parallel” here means parallel musical time (like a separate voice or layer), not simultaneous CPU execution; the scheduler runs one clock’s code at a time.
- Parameters:
forked_function – the function to be run on the new child clock
args – positional arguments to be passed to the forked function. (Unlike legacy clockblocks, clockblocks does not inject the child clock as an extra first argument when the signature is one short — call
current_clock()from inside the forked function if you need a reference to it.)kwargs – keyword arguments to be passed to the forked function
name – name to be given to the spawned child clock
initial_rate – starting rate of this clock (if set, don’t set initial tempo or beat length)
initial_tempo – starting tempo of this clock (if set, don’t set initial rate or beat length)
initial_beat_length – starting beat length of this clock (if set, don’t set initial tempo or rate)
when – when the forked function should begin, as a
ResolvableMoment. None (default) starts it immediately. Otherwise pass an explicit Moment —at_beat()(orat_time()) for an absolute point, orafter_beats()(orafter_time()) for an offset from now. Unlike wait and wait_until, a bare number is rejected here, since it’s not clear whether it would be relative or absolute. Also possible is aMetricPhaseTargetwhich starts it at the next matching point in a cycle.done_callback – a callback function to be invoked when the clock has terminated
- Returns:
the spawned child clock
- schedule_action(action: Callable, when: ResolvableMoment, args: Sequence = (), kwargs: dict = None) None[source]
The lightweight alternative to fork() for immediately returning functions. Schedules action to run once at when, as a leaf event on the scheduler thread — without spawning a child clock. For functions that don’t wait, this is much more performant: there is no thread creation, no scheduler/clock handoff; just a callable fired at the right musical time.
For work that needs to wait, fork() a child clock instead. The function scheduled here will not have an active clock to wait on, and will hold up the entire scheduler if it sleeps.
- Parameters:
action – the callable to run (exceptions are caught and logged by the scheduler)
when – when to run it, as a
ResolvableMoment— same convention as fork(): an explicit Moment (Moment.at_beat/at_time for an absolute point, Moment.after_beats/ after_time for an offset from now) or a MetricPhaseTarget. Unlike wait and wait_until, a bare number is rejected here, since it’s not clear whether it would be relative or absolute.args – positional arguments to pass to action
kwargs – keyword arguments to pass to action
- wait_forever() None[source]
Block this clock’s own thread indefinitely, yielding to the scheduler so child clocks keep running. Typically called once a clock has forked its work and has nothing left to do itself (e.g. the master clock keeping the main thread alive).
This only ever unblocks via
kill(), at which point it raisesClockKilledError. For a forked clock, this is caught by the fork wrapper and it cleanly unwinds. For a master clock it would need to be caught. A built-in way to do this is by using the clock as a context manager, which does exception handling and teardown for you. (run_as_server()also absorbs the exception automatically within the spawned thread).That said, typically this is used at end of script and killed via ctrl-c/process exit, so no explicit catching of this exception is necessary.
- wait_for_children_to_finish() None[source]
Block this clock’s own thread until all of its child clocks have finished, yielding to the scheduler so they can run, then return as soon as the last child ends.
If this clock is itself killed while waiting, raises
ClockKilledErrorrather than returning — so a normal return always means the children genuinely finished.
- wait_for_clock_to_finish(clock: Clock) None[source]
Block this clock’s own thread until clock has finished, yielding to the scheduler so it (and everything else) keeps running. The counterpart to
wait_for_children_to_finish()for a specific clock rather than one’s own children — clock may be any other clock in the same family (a sibling, a descendant, an unrelated fork), so long as it shares this clock’s scheduler.Returns as soon as clock finishes, whether it ended on its own or was killed. If clock has already finished, returns immediately. If this clock is itself killed while waiting, raises
ClockKilledErrorrather than returning.- Parameters:
clock – the clock to wait on. Must belong to the same family (raises ValueError otherwise), and must not be this clock itself (which would wait forever).
- terminate_forked_children() None[source]
Kill this clock’s children and their descendants in turn, returning once they have finished unwinding. At the natural end of a forked clock that has (or might have) still-active children, either this or
wait_for_children_to_finish()should be called, making explicit what to do with those forked children. (The default is termination with a warning, in case it’s not what the user wants.)
- run_as_server() Clock[source]
Run this (master) clock on a background daemon thread so the calling thread stays free — the approach for driving clockblocks from an interactive REPL:
c = Clock().run_as_server(). The background thread becomes the clock’s owning thread and waits forever; the calling thread relinquishes ownership (its current_clock() becomes None), so further work must be forked on the returned clock object directly (c.fork(...)), not via the context-inferring helpers such asfork()andwait().Returns self. Only valid on the master clock — raises NotMasterClockError on a child.
- property alive: bool
True if this clock is currently running (PENDING and DEAD both return False).
- fast_forward(on_or_off: bool = True) None[source]
Turn indefinite fast-forwarding on or off. While on, all waiting is instantaneous. (Only valid on the master clock.)
- Parameters:
on_or_off – True to start fast-forwarding, False to stop.
- fast_forward_to_time(t: float) None[source]
Fast-forward, skipping instantaneously up to time t (in seconds) on this clock, then resume real-time playback. (Only valid on the master clock.)
- Parameters:
t – time to fast-forward to
- fast_forward_in_time(t: float) None[source]
Fast-forward, skipping ahead instantaneously by t seconds. (Only valid on the master clock.)
- Parameters:
t – number of seconds to fast-forward by
- fast_forward_to_beat(b: float) None[source]
Fast-forward, skipping instantaneously up to beat b on this clock. (Only valid on the master clock.)
- Parameters:
b – beat to fast-forward to
- fast_forward_in_beats(b: float) None[source]
Fast-forward, skipping ahead instantaneously by b beats. (Only valid on the master clock.)
- Parameters:
b – number of beats to fast-forward by
- is_fast_forwarding() bool[source]
Whether the clock is currently fast-forwarding. Since fast-forwarding is a scheduler-wide state, this is true for every clock in the family whenever it’s true for any of them.
- property timing_policy: float
How the family trades off relative vs. absolute timing, as a float from 0 to 1.
At 1.0 (relative) each wait is kept as faithful as possible to its requested duration, timed from when the previous event fired (callback runtime does not count against the wait). This can still let the clock fall behind real time: if a wait overruns (e.g. through a callback that runs longer than its own wait or the OS waking late) that lateness is never made up. At 0.0 (absolute) the clock instead stays faithful to the time elapsed since it began — a wait that ran long is followed by shorter waits to catch up, at the cost of some relative-timing accuracy. A value in between clamps the catch-up: the clock attempts to stay on the absolute schedule but can only compress a wait to wait_dur * timing_policy (or stretch it to wait_dur / timing_policy if trying to compensate for being ahead). (Forwards to the scheduler, settable only on the master clock)
- use_absolute_timing_policy() None[source]
Shorthand for
timing_policy = 0.0(always catch up to absolute schedule).
- use_relative_timing_policy() None[source]
Shorthand for
timing_policy = 1.0(always wait the full requested delay).
- use_mixed_timing_policy(absolute_relative_mix: float) None[source]
Shorthand for
timing_policy = absolute_relative_mix(a blend between 0=absolute and 1=relative).
- property precise_timing: bool
Whether to use a busy wait in the final moments leading up to a scheduled event. The busy wait fully occupies a CPU core and lasts at most
spin_guard_durationseconds per event. (Forwards to the scheduler, settable only on the master clock)
- property spin_guard_duration: float
Width (seconds) of the busy-wait guard band used when
precise_timingis on. (Forwards to the scheduler, settable only on the master clock)
- kill() None[source]
End this clock (and the corresponding forked function if not master) and cascade to all descendant clocks.
Pending scheduled work for this clock and its descendants is canceled and the clocks are marked dead. A clock currently blocked in
wait()raisesClockKilledError; any laterwait()orfork()made through a reference to a dead clock raisesDeadClockError.Killing the master also tears down the family’s scheduler, and releases its owning thread: that thread’s
current_clock()becomesNone, so the module-level helpers (wait(),fork(),get_beat(), …) raiseNoActiveClockErrorthere rather than reporting a dead clock.Returns once the killed clocks have finished unwinding to ensure synchronicity. Emits a warning if this takes more than a few seconds.
Safe to call from any thread, and killing an already-dead clock is a no-op.
(See the “Clock termination / lifecycle paths” note near the top of this module for how the five termination paths work internally.)