Skip to content

Patterns

Pattern-based sequencing: value patterns (Pseq, Prand, Pwhite, ...), the event pattern Pbind and its composites (Ppar, Pmono, Pdef, ...), and the Clock/Player scheduling pair. See the Patterns guide for the event model, the pitch chain, and quantization.

nanosynth.patterns

Pattern-based sequencing system for musical event scheduling.

Patterns are reusable templates that produce fresh iterators each time (standard Python Iterable[T] protocol). This mirrors SuperCollider's Pattern/Stream split mapped to Python idioms: Pattern.__iter__() returns a generator.

Basic usage::

from nanosynth.patterns import Pseq, Pbind, Clock

pattern = Pbind(
    instrument="default",
    freq=Pseq([440, 550, 660], repeats=2),
    dur=0.5,
    amp=0.3,
)

clock = Clock(bpm=120)
player = pattern.play(clock, server)
# ... later ...
player.stop()
clock.stop()

Rest

Silence marker.

When dur in an event is a Rest instance, the Player advances time by rest.dur beats but does not create a synth.

Pattern

Bases: ABC, Generic[T]

Abstract base class for all patterns.

Subclasses must implement __iter__ which returns a fresh iterator each time it is called.

take

take(n: int) -> list[T]

Consume and return the first n values from this pattern.

__or__

__or__(other: Pattern[T]) -> _Chain[T]

Chain two patterns: p1 | p2 yields p1 then p2.

play

play(
    clock: Clock,
    server: Any,
    latency: float | None = None,
    quant: float | None = None,
    offset: float = 0.0,
) -> Player

Start playing this pattern on the given clock and server.

Defined here rather than on :class:EventPattern so that the generic wrappers -- Pn(Pbind(...)), Pseq([...]), Pfin(...) -- stay playable. Only patterns that yield events are meaningful to play; a value pattern will produce nonsense.

Args: clock: The Clock providing tempo. server: A Server instance for synth creation. latency: Scheduling latency override in seconds; defaults to the clock's latency. quant: Quantization in beats. Playback starts on the next quant-beat boundary of the clock's grid instead of immediately, so patterns launched at different moments stay phase-aligned. None (the default) starts at once. offset: Beats past the quantization boundary at which to start.

Returns: A Player that can be stopped.

Pseq

Bases: Pattern[T]

Sequential playback of a sequence.

If an element is itself a Pattern, it is flattened (yielded from).

Args: sequence: Items to yield. repeats: Number of times to cycle through the sequence.

Prand

Bases: Pattern[T]

Random selection from a sequence.

Args: sequence: Pool of items to choose from. repeats: Number of values to produce. seed: Optional RNG seed. When given, each iteration restarts from this seed, so the pattern is reproducible (e.g. for NRT rendering). When None (default) a fresh per-instance RNG is used -- still independent of the global random state, so unrelated random calls elsewhere cannot perturb the sequence.

Pwhite

Bases: Pattern[float]

Uniform random float between lo and hi.

Args: lo: Lower bound (inclusive). hi: Upper bound (inclusive). repeats: Number of values to produce. seed: Optional RNG seed (see :class:Prand for the seeding semantics).

Pseries

Bases: Pattern[float]

Arithmetic series: start, start+step, start+2*step, ...

Args: start: Initial value. step: Increment per step. repeats: Number of values to produce.

Pgeom

Bases: Pattern[float]

Geometric series: start, start*grow, start*grow^2, ...

Args: start: Initial value. grow: Multiplier per step. repeats: Number of values to produce.

Pchoose

Bases: Pattern[T]

Weighted random selection from items.

Args: items: Pool of items to choose from. weights: Relative weights for each item (must sum to > 0). repeats: Number of values to produce. seed: Optional RNG seed (see :class:Prand for the seeding semantics).

Pn

Bases: Pattern[T]

Repeat a pattern N times.

Each repetition creates a fresh iterator from the wrapped pattern.

Args: pattern: The pattern to repeat. repeats: Number of full repetitions.

Pconst

Bases: Pattern[float]

Yield values from pattern until their sum reaches total.

The last value is clipped so the sum equals total exactly.

Args: total: Target sum. pattern: Source pattern for values.

Pkey

Bases: Pattern[Any]

Reference another key of the event currently being built.

Resolved by :class:Pbind alongside its other bindings, so it sees their values for this event::

Pbind(freq=Pseq([440, 660]), amp=Pkey("freq", lambda f: 100.0 / f))

Ordering follows from what the bound key is. A Pkey bound to an input of the derivation chain (degree, dur, db, ...) resolves before the chain runs, so it can drive it. A Pkey bound to anything else resolves after, so it can read the chain's output::

# amp follows the derived freq, not the raw degree
Pbind(degree=Pseq([0, 4]), amp=Pkey("freq", lambda f: 40.0 / f))

Args: key: Name of the event key to read. transform: Optional callable applied to the referenced value. default: Value used when the referenced key is absent.

Note that patterns do not overload arithmetic operators, so combine values with transform rather than writing Pkey("freq") * 2.

EventPattern

Bases: Pattern[Event]

Base class for patterns that yield events rather than bare values.

A marker for the event layer -- Pbind and the composites built on it. :meth:Pattern.play lives on the base class, so generic wrappers such as Pn(Pbind(...)) are playable too.

Pbind

Bases: EventPattern

Bind keys to patterns/values to produce a stream of events.

Stops when any bound pattern is exhausted. Scalar values repeat forever. Events are merged with _EVENT_DEFAULTS.

Args: **bindings: Key-value pairs where values can be floats, strings, Rest instances, or Pattern instances.

Ppar

Bases: EventPattern

Play several event patterns in parallel, merged into one stream.

Events from all sub-patterns are interleaved in time order. Each yielded event's delta is rewritten to the gap until the next event in the merged stream, while its sustain (and every other key) is left alone, so each voice keeps its own note lengths. Simultaneous events land in the same bundle timestamp and therefore start on the same sample.

The merged stream ends when every sub-pattern is exhausted.

Args: patterns: Event patterns to run concurrently.

Ptpar

Bases: Ppar

Like :class:Ppar, but each pattern starts at its own beat offset.

Args: pairs: (offset_in_beats, pattern) tuples.

Pmono

Bases: EventPattern

One persistent synth whose parameters are updated per event.

Where :class:Pbind creates a synth per event, Pmono creates a single synth on its first event and sends /n_set for every event after that, releasing it when the pattern ends. This is how sclang models a monophonic, continuously-gliding line (portamento, filter sweeps) that a stream of separate synths cannot produce.

Accepts the same bindings as :class:Pbind, including the pitch chain. sustain is ignored: the synth is held for the whole pattern rather than released per note.

Args: instrument: SynthDef name for the persistent synth. **bindings: As :class:Pbind.

Pdef

Bases: EventPattern

A named, hot-swappable event pattern.

Pdef(name, pattern) registers or replaces the pattern stored under name; Pdef(name) looks up the existing one. A player iterating a Pdef picks up a replacement at the next event boundary, so a running part can be redefined without stopping playback -- the pattern analogue of :class:~nanosynth.proxy.Ndef.

The stream ends when the current source is exhausted; wrap the source in :class:Pn to loop it.

Args: name: Registry key. pattern: New source pattern, or None to look up an existing entry.

name property

name: str

The registry key this Pdef is stored under.

source property writable

source: Pattern[Event] | None

The current source pattern, or None if never assigned.

clear classmethod

clear() -> None

Forget every registered Pdef. Does not stop running players.

Pfin

Bases: Pattern[T]

Yield at most count values from a pattern.

Args: count: Maximum number of values. pattern: Source pattern.

Pfindur

Bases: EventPattern

Yield events until their accumulated duration reaches duration.

The final event's delta is clipped so the total is exact, which is what makes a bounded pattern line up with a bar boundary.

Args: duration: Total duration in beats. pattern: Source event pattern.

Clock

Tempo clock that drives pattern playback.

Runs a background daemon thread. Uses time.monotonic() for drift-free absolute scheduling. Multiple players share one clock for synchronized timing.

Call :meth:stop when done: the running thread holds a reference back to the clock (and its players), so a clock that is never stopped lives -- with its thread -- for the rest of the process. The thread is a daemon, so this does not block interpreter exit, but it is a leak within a long-running process.

Args: bpm: Beats per minute (default 120). latency: Scheduling latency in seconds (default :data:DEFAULT_LATENCY). Events are sent as OSC bundles stamped this far ahead, so onset accuracy is set by the engine rather than by when the Python thread woke. Raise it if playback stutters under load; lower it for tighter response to live input.

bpm property writable

bpm: float

Beats per minute. Settable; takes effect on next beat.

latency property writable

latency: float

Scheduling latency in seconds. Settable; applies to later events.

beat_duration property

beat_duration: float

Duration of one beat in seconds (read-only: 60 / bpm).

elapsed_beats property

elapsed_beats: float

Beats since this clock's grid origin.

Measured from the origin at the current tempo, so changing bpm mid-session redefines where past beats fall. Set the tempo before starting quantized players if you need the grid to be stable.

next_boundary

next_boundary(quant: float, offset: float = 0.0) -> float

Monotonic time of the next quant-beat boundary.

With quant=4 this is the downbeat of the next bar in 4/4. A non-positive quant means "no quantization" and returns now.

Args: quant: Grid size in beats. offset: Beats past the boundary to return.

reset_grid

reset_grid() -> None

Move the quantization grid origin to now (beat 0 starts here).

stop

stop() -> None

Stop the clock and all its players.

Mirrors :meth:Player.stop for every player: held Pmono synths are gated off, not just marked stopped. Without this a live Pmono voice (held with gate=1) would ring until the server quits, since only per-event Pbind synths have their gate-release bundles already queued in the engine.

Player

Drives event playback from a pattern on a clock.

Created by Pbind.play() or directly.

Args: pattern: An event pattern to play. clock: The tempo clock. server: A Server instance for synth creation. latency: Scheduling latency override in seconds. Defaults to the clock's latency, so players sharing a clock stay phase-aligned.

latency property

latency: float

Effective scheduling latency: this player's override, or the clock's.

play

play(
    quant: float | None = None, offset: float = 0.0
) -> Player

Start playback. Returns self for chaining.

Args: quant: Quantization in beats -- start on the next quant-beat boundary of the clock's grid rather than immediately. None starts at once. offset: Beats past the quantization boundary at which to start.

stop

stop() -> None

Stop playback and release any held Pmono synths.