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: object

Recursively 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 use run_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 ClockFamilyOptions bundling the family-level timing/threading knobs (timing_policy, precise_timing, spin_guard_duration, pool_size, prewarm_pool). The live knobs are also adjustable afterwards via timing_policy, precise_timing, and spin_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 TempoEnvelope onto this clock's internal tempo envelope, starting from the current beat.

apply_tempo_function(function[, ...])

Drive this clock's tempo (BPM) from a function.

bring_up_to_date()

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.

descendants()

Get all children, grandchildren, etc.

extract_absolute_tempo_envelope([...])

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_in_beats(b)

Fast-forward, skipping ahead instantaneously by b beats.

fast_forward_in_time(t)

Fast-forward, skipping ahead instantaneously by t seconds.

fast_forward_to_beat(b)

Fast-forward, skipping instantaneously up to beat b on this clock.

fast_forward_to_time(t)

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.

hold_scheduler()

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.

is_fast_forwarding()

Whether the clock is currently fast-forwarding.

is_master()

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_family_tree()

Print a hierarchical representation of this clock's family tree.

print_status([verbose])

Print this clock's status() snapshot.

projected_beat()

A wall-clock-interpolated estimate of beat.

projected_time()

A wall-clock-interpolated estimate of time, advancing smoothly between events rather than holding still between them.

run_as_server()

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 by when.

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 at when.

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 at when.

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_tempo_loop_or_function()

Stop following any function or looping envelope previously applied to this clock's tempo.

terminate_forked_children()

Kill this clock's children and their descendants in turn, returning once they have finished unwinding.

use_absolute_timing_policy()

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).

use_relative_timing_policy()

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.

wait_for_children_to_finish()

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.

wait_forever()

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

absolute_beat_length

Beat length in true seconds, with all parent rates folded in.

absolute_rate

Rate of this clock in beats / (true) second, with all parent rates folded in.

absolute_tempo

Tempo (BPM) in true minutes, with all parent rates folded in.

alive

True if this clock is currently running (PENDING and DEAD both return False).

beat

How many beats have passed since this clock was created.

beat_length

The length of a beat in this clock in seconds.

master

The master clock under which this clock operates (possibly itself)

precise_timing

Whether to use a busy wait in the final moments leading up to a scheduled event.

rate

The rate of this clock in beats / second.

spin_guard_duration

Width (seconds) of the busy-wait guard band used when precise_timing is on.

tempo

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.

time

How much time has passed since this clock was created.

timing_policy

How the family trades off relative vs. absolute timing, as a float from 0 to 1.

property master: Clock

The master clock under which this clock operates (possibly itself)

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

print_family_tree() → None[source]

Print a hierarchical representation of this clock’s family tree.

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. See projected_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.

print_status(verbose: bool = False) → None[source]

Print this clock’s status() snapshot.

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 by when. This is the underlying representation behind set_tempo_target() and set_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 a MetricPhaseTarget (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; > 0 keeps the old tempo longer and changes late; < 0 changes early then eases in. When align_to solves for the curvature (see below) this acts only as a starting hint, and with a fixed align_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. If False, 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_to share an axis, or if no curvature can reach the requested align_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 at when. Rate is the reciprocal of beat length: a higher rate is a faster tempo.

Parameters:
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 at when. Tempo is measured in beats per minute; this is usually the most natural of the three equivalent ways to set a target (see also set_rate_target() and set_beat_length_target()).

Parameters:
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 a TempoEnvelope and use apply_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 a Moment or MetricPhaseTarget. Note that every when is resolved against the clock’s current position, so an after_* 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 in set_beat_length_target(). None for the whole argument (the default), or a per-element None, 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; if False the run is appended after whatever is already scheduled.

  • align_to –

    optional curvature-solved alignment, extending set_beat_length_target()’s align_to to runs of segments. Either:

    • a single Moment/MetricPhaseTarget — align the whole call as one run, landing its end on the target; or

    • a per-segment list/tuple (same length as beat_length_targets) of None/targets — each non-None entry 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 whens on a single axis (all beats or all time), with the alignment target on the opposite (free) axis.

Raises:

ValueError – if whens/curve_shapes/align_to lengths are inconsistent with beat_length_targets; if the whens do not come out strictly increasing in clock-time (the error names the offending index); if a multi-segment aligned run mixes beat- and time-axis whens; 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:
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:
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 TempoEnvelope onto this clock’s internal tempo envelope, starting from the current beat. With loop=True the envelope repeats indefinitely until stop_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() (or at_time()) for an absolute point, or after_beats() (or after_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 a MetricPhaseTarget which 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 raises ClockKilledError. 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 ClockKilledError rather 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 ClockKilledError rather 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 as fork() and wait().

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_duration seconds 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_timing is 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() raises ClockKilledError; any later wait() or fork() made through a reference to a dead clock raises DeadClockError.

Killing the master also tears down the family’s scheduler, and releases its owning thread: that thread’s current_clock() becomes None, so the module-level helpers (wait(), fork(), get_beat(), …) raise NoActiveClockError there 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.)