subsequence.composition

The top-level Composition — the object a whole piece is built on.

Composition is the single entry point: it owns the clock, the patterns, the harmony and form state, and the MIDI output. You create one, decorate pattern functions onto it with @composition.pattern, and call play(). HarmonyView and ScheduleContext here are the read-only views handed to pattern and callback functions at run time.

Module Contents

class subsequence.composition.Composition(output_device: str | None = None, bpm: float = 120, time_signature: Tuple[int, int] = (4, 4), key: str | None = None, scale: str | None = None, seed: int | None = None, record: bool = False, record_filename: str | None = None, zero_indexed_channels: bool = False, latency_ms: float = 0.0)[source]

The top-level controller for a musical piece.

The Composition object manages the global clock (Sequencer), the harmonic progression (HarmonicState), the song structure (subsequence.form_state.FormState), and all MIDI patterns. It serves as the main entry point for defining your music.

Typical workflow:

  1. Initialize Composition with BPM and Key.

  2. Define harmony and form (optional).

  3. Register patterns using the @composition.pattern decorator.

  4. Call composition.play() to start the music.

Initialize a new composition.

Parameters:
  • output_device

    The exact name of the MIDI output port to use, as reported by mido.get_output_names(). Matching is strict — the string must equal an entry in that list verbatim. On Linux/ALSA, names include the client and port IDs (e.g. "Scarlett 2i4 USB:Scarlett 2i4 USB MIDI 1 16:0"); the trailing :client:port digits are assigned in connection order and can change between reboots or when a virtual port is recreated. To look up the current names:

    import mido
    for n in mido.get_output_names(): print(n)
    

    If None, Subsequence auto-discovers — uses the only available device, or prompts to choose if several exist.

  • bpm – Initial tempo in beats per minute (default 120).

  • key – The root key of the piece (e.g., “C”, “F#”, “Bb”). Required if you plan to use harmony().

  • scale – The scale/mode of the piece (e.g. “minor”, “dorian”, or any registered scale name). Used to resolve scale degrees in motifs; defaults to major (ionian) when unset.

  • seed – An optional integer for deterministic randomness. When set, every random decision (chord choices, drum probability, etc.) will be identical on every run.

  • record – When True, record all MIDI events to a file.

  • record_filename – Optional filename for the recording (defaults to timestamp).

  • zero_indexed_channels – When False (default), MIDI channels use 1-based numbering (1-16) matching instrument labelling. Channel 10 is drums, the way musicians and hardware panels show it. When True, channels use 0-based numbering (0-15) matching the raw MIDI protocol.

  • latency_ms – Physical output latency of the primary device in milliseconds, for delay compensation (default 0.0, must be non-negative). Set this when the primary output sounds late (e.g. a software sampler) so Subsequence delays faster devices to line everything up. See midi_output() for additional devices.

Example

comp = subsequence.Composition(bpm=128, key="Eb", seed=123)
cc_forward(cc: int, output: str | Callable, *, channel: int | None = None, output_channel: int | None = None, mode: str = 'instant', input_device: subsequence.midi_utils.DeviceId = None, output_device: subsequence.midi_utils.DeviceId = None) None[source]

Forward an incoming MIDI CC to the MIDI output in real-time.

Unlike cc_map() which writes incoming CC values to composition.data for use at pattern rebuild time, cc_forward() routes the signal directly to the MIDI output — bypassing the pattern cycle entirely.

Both cc_map() and cc_forward() may be registered for the same CC number; they operate independently.

Parameters:
  • cc – Incoming CC number to listen for (0–127).

  • output

    What to send. Either a preset string:

    • "cc" — identity forward, same CC number and value.

    • "cc:N" — forward as CC number N (e.g. "cc:74").

    • "pitchwheel" — scale 0–127 to -8192..8191 and send as pitch bend.

    Or a callable with signature (value: int, channel: int) -> Optional[mido.Message]. Return a fully formed mido.Message to send, or None to suppress. channel is 0-indexed (the incoming channel).

  • channel – If given, only respond to CC messages on this channel. Uses the same numbering convention as cc_map(). None matches any channel (default).

  • output_channel – Override the output channel. None uses the incoming channel. Uses the same numbering convention as pattern().

  • mode

    Dispatch mode:

    • "instant" (default) — send immediately on the MIDI input callback thread. Lowest latency (~1–5 ms). Instant forwards are not recorded when recording is enabled.

    • "queued" — inject into the sequencer event queue and send at the next pulse boundary (~0–20 ms at 120 BPM). Queued forwards are recorded when recording is enabled.

Example

comp.midi_input("Arturia KeyStep")

# CC 1 → CC 1 (identity, instant)
comp.cc_forward(1, "cc")

# CC 1 → pitch bend on channel 1, queued (recordable)
comp.cc_forward(1, "pitchwheel", output_channel=1, mode="queued")

# CC 1 → CC 74, custom channel
comp.cc_forward(1, "cc:74", output_channel=2)

# Custom transform — remap CC range 0–127 to CC 74 range 40–100
import subsequence.midi as midi
comp.cc_forward(1, lambda v, ch: midi.cc(74, int(v / 127 * 60) + 40, channel=ch))

# Forward AND map to data simultaneously — both active on the same CC
comp.cc_map(1, "mod_wheel")
comp.cc_forward(1, "cc:74")
cc_map(cc: int, data_key: str, channel: int | None = None, min_val: float = 0.0, max_val: float = 1.0, input_device: subsequence.midi_utils.DeviceId = None) None[source]

Map an incoming MIDI CC to a composition.data key.

When the composition receives a CC message on the configured MIDI input port, the value is scaled from the CC range (0–127) to [min_val, max_val] and stored in composition.data[data_key].

This lets hardware knobs, faders, and expression pedals control live parameters without writing any callback code.

Requires midi_input() to be called first to open an input port.

Parameters:
  • cc – MIDI Control Change number (0–127).

  • data_key – The composition.data key to write.

  • channel – If given, only respond to CC messages on this channel. Uses the same numbering convention as pattern() (1-16 by default, or 0-15 with zero_indexed_channels=True). None matches any channel (default).

  • min_val – Scaled minimum — written when CC value is 0 (default 0.0).

  • max_val – Scaled maximum — written when CC value is 127 (default 1.0).

  • input_device – Only respond to CC messages from this input device (index or name). None responds to any input device (default).

Example

comp.midi_input("Arturia KeyStep")
comp.cc_map(74, "filter_cutoff")           # knob → 0.0–1.0
comp.cc_map(7, "volume", min_val=0, max_val=127)  # volume fader

# Multi-device: only listen to CC 74 from the "faders" controller
comp.cc_map(74, "filter", input_device="faders")
chords(*, channel: int, progression: subsequence.progressions.ProgressionSource, harmonic_rhythm: subsequence.progressions.HarmonicRhythmSpec, bars: float | None = None, beats: float | None = None, voicing: subsequence.progressions.VoicingSpec = (3, 4), velocity: int | Tuple[int, int] = subsequence.constants.velocity.DEFAULT_CHORD_VELOCITY, detached: float | None = None, root: int = 60, key: str | None = None, seed: int | None = None, device: subsequence.midi_utils.DeviceId = None, mirrors: Iterable[subsequence.pattern.MirrorSpec] | None = None) subsequence.progressions.Progression[source]

Declare a self-contained chord part: a progression at a chosen harmonic rhythm.

The one-call form of p.progression() — it registers a pattern on channel that plays progression across bars (or beats), each chord lasting a length drawn from harmonic_rhythm (the musical term for how often the chords change). It needs no composition.harmony() call and, with an explicit chord list or a key=, no composition key either — so a drums-plus-one-chord-part sketch stays simple.

The progression is realised once, up front, and the same timeline plays every cycle (a stable phrase). That timeline is returned so you can see exactly what was chosen — print(comp.chords(...)).

Parameters:
  • channel – MIDI channel for the chord part.

  • progression – A chord-graph style name to generate from, or an explicit list of chords (Chord objects or names like ["Cm7", "Dbmaj7"]).

  • harmonic_rhythm – How long each chord lasts — a number, a list of lengths, or between(low, high, step=...). See p.progression().

  • beats (bars /) – Length of the part (defaults to 4 beats if neither is given). bars uses the composition’s time signature.

  • voicing – Notes per chord — an int, or a (low, high) range (e.g. (3, 4)).

  • velocity – MIDI velocity, or a (low, high) tuple for per-voice humanisation.

  • detached – Beats of silence before each next chord (duration = length - detached).

  • root – MIDI root the voicings are centred on (e.g. 48 = C3).

  • key – Key for a generated progression; defaults to the composition key.

  • seed – Seed for the (otherwise fixed) realisation; defaults to the composition seed, so the part is reproducible.

  • device – Optional output-device override.

  • mirrors – Optional additional (device, channel) destinations.

Returns:

The realised Progression.

clear_tweak(name: str, *param_names: str) None[source]

Remove tweaked parameters from a running pattern.

If no parameter names are given, all tweaks for the pattern are cleared and every p.param() call reverts to its default.

Parameters:
  • name – The function name of the pattern.

  • *param_names – Specific parameter names to clear. If omitted, all tweaks are removed.

clock_output(enabled: bool = True) None[source]

Send MIDI timing clock to connected hardware.

When enabled, Subsequence acts as a MIDI clock master and sends standard clock messages on the output port: a Start message (0xFA) when playback begins, a Clock tick (0xF8) on every pulse (24 PPQN), and a Stop message (0xFC) when playback ends.

This allows hardware synthesizers, drum machines, and effect units to slave their tempo to Subsequence automatically.

Note: Clock output is automatically disabled when midi_input() is called with clock_follow=True, to prevent a clock feedback loop.

Parameters:

enabled – Whether to send MIDI clock (default True).

Example

comp = subsequence.Composition(bpm=120, output_device="...")
comp.clock_output()   # hardware will follow Subsequence tempo
current_chord() Any | None[source]

The chord sounding at the playhead, or None without harmony.

Reads the harmony window at the current pulse, so it stays accurate under variable harmonic rhythm and clock lookahead (the engine’s current_chord flips lookahead beats early — this does not). Falls back to the engine’s chord before playback starts. The chord may be a decorated wrapper (Am9, C/G) when the sounding span is spiced; it duck-types the Chord voicing protocol either way.

display(enabled: bool = True, grid: bool = False, grid_scale: float = 1.0) None[source]

Enable or disable the live terminal dashboard.

When enabled, Subsequence uses a safe logging handler that allows a persistent status line (BPM, Key, Bar, Section, Chord) to stay at the bottom of the terminal while logs scroll above it.

Parameters:
  • enabled – Whether to show the display (default True).

  • grid – When True, render an ASCII grid visualisation of all running patterns above the status line. The grid updates once per bar, showing which steps have notes and at what velocity.

  • grid_scale – Horizontal zoom factor for the grid (default 1.0). Higher values add visual columns between grid steps, revealing micro-timing from swing and groove. Snapped to the nearest integer internally for uniform marker spacing.

energy(energies: Dict[str, float | Tuple[float, float]]) None[source]

Set per-section energy — the arranging dial, as one plain dict.

{"verse": 0.5, "chorus": 0.9, "build": (0.3, 1.0)} — a float is the section’s level; a (start, end) tuple interpolates across the section (a build). Patterns read p.energy (0.5 when nothing is configured) and gate themselves, or declare min_energy= on pattern() for automatic muting.

The dict overrides any energy payload carried by bound Section values — it is the later, performance-level dial. Re-calling replaces the whole mapping (idempotent, live-reload friendly).

Example:

composition.energy({"intro": 0.2, "verse": 0.55, "drop": 0.95})
form(sections: subsequence.forms.Form | List[Any] | Iterator[Tuple[str, int]] | Dict[str, Tuple[int, List[Tuple[str, int]] | None]], loop: bool = False, start: str | None = None, at_end: str = 'stop', key: str | None = None, scale: str | None = None) None[source]

Define the structure (sections) of the composition.

You can define form in four ways:

  1. Form value: a frozen Form of Section values — the payload home (energy, key per section); editable, navigable.

  2. Sequence (List): a fixed order of (name, bars) tuples or Sections (lists coerce — they are the same form).

  3. Graph (Dict): dynamic transitions based on weights.

  4. Generator: a Python generator that yields (name, bars) pairs.

Form-value and list forms are navigable: form_jump() and form_next() work on them (the jump lands on the next occurrence of the name, wrapping).

Parameters:
  • sections – The form definition (Form, List, Dict, or Generator).

  • loop – Sugar for at_end="loop".

  • start – The section to start with (Graph mode only).

  • at_end – What happens when a sequence form runs out — "stop" (the form finishes and patterns see no section; default), "hold" (the final section repeats until navigated away from), or "loop" (start over). Graphs end via their terminal sections instead.

  • key – A form-level key — the form tier of the key-source chain (Section.key overrides it; it overrides the composition key). Re-anchors key-relative content for the whole form. When sections is a Form value carrying its own key, that value is used unless this argument overrides.

  • scale – A form-level scale/mode, paired with key.

Example

# A simple pop structure
comp.form([
        ("verse", 8),
        ("chorus", 8),
        ("verse", 8),
        ("chorus", 16)
])

# The same structure with payloads, held open at the end
S = subsequence.Section
comp.form(subsequence.Form([
        S("verse", 8, energy=0.5), S("chorus", 8, energy=0.9),
]), at_end="hold")
form_freeze(sections: int | None = None) subsequence.forms.Form[source]

Freeze the graph form’s walk into an editable Form.

Walks a clone of the live form state — the same RNG state, so the frozen path is exactly the path the live graph would have played — and returns it as a Form value: inspect it, edit it (path.replace(3, bars=16)), and rebind it with composition.form(path, at_end=...). The live form state is untouched (rebinding replaces it).

Parameters:

sections – Number of sections to freeze. Without it, the walk runs until a terminal section; a graph with no terminal sections requires sections= explicitly.

Raises:

ValueError – If no graph form is bound (a list form is already a frozen sequence), the form has already finished, or the walk cannot terminate.

Example:

composition.form({...}, start="intro")
path = composition.form_freeze()          # the walk, frozen
composition.form(path, at_end="stop")     # rebind the editable value
form_jump(section_name: str) None[source]

Jump the form to a named section immediately.

Delegates to subsequence.form_state.FormState.jump_to(). Only works when the composition uses graph-mode form (a dict passed to form()).

The musical effect is heard at the next pattern rebuild cycle — already- queued MIDI notes are unaffected. This natural delay means form_jump is effective without needing explicit quantization.

Parameters:

section_name – The section to jump to.

Raises:

ValueError – If no form is configured, or the form is not in graph mode, or section_name is unknown.

Example:

composition.hotkey("c", lambda: composition.form_jump("chorus"))
form_next(section_name: str) None[source]

Queue the next section — takes effect when the current section ends.

Unlike form_jump(), this does not interrupt the current section. The queued section replaces the automatically pre-decided next section and takes effect at the natural section boundary. The performer can change their mind by calling form_next again before the boundary.

Delegates to subsequence.form_state.FormState.queue_next(). Only works when the composition uses graph-mode form (a dict passed to form()).

Parameters:

section_name – The section to queue.

Raises:

ValueError – If no form is configured, or the form is not in graph mode, or section_name is unknown.

Example:

composition.hotkey("c", lambda: composition.form_next("chorus"))
freeze(bars: int, end: Any | None = None, pins: Dict[int, Any] | None = None, avoid: Sequence[Any] | None = None, cadence: str | None = None) Progression[source]

Capture a chord progression from the live harmony engine.

Runs the harmony engine forward by bars chord changes, records each chord, and returns it as a Progression that can be bound to a form section with section_chords().

The engine state advances — successive freeze() calls produce a continuing compositional journey so section progressions feel like parts of a whole rather than isolated islands.

The hybrid constraints compile into the walk: end= fixes the last bar (“end on V at bar 8”), pins= fix any 1-based bar, avoid= excludes chords throughout. Specs follow the progression-element grammar (ints where diatonic, roman/name strings where chromatic) and resolve against the composition key and scale. A backward feasibility pass guarantees satisfiability before any chord is drawn; the forward walk keeps the engine’s real history-dependent weighting. Bar 1 is always the engine’s current chord — the journey continues — so pins={1: ...} may only name it redundantly.

Parameters:
  • bars – Number of chords to capture (one per harmony cycle).

  • end – The chord at the final bar — end="V" is the cadential major dominant in minor.

  • pins{bar: chord} — 1-based fiat positions.

  • avoid – Chords excluded from the walk.

  • cadence – A cadence name ("strong"/"soft"/"open"/ "fakeout", theory aliases accepted) — its formula pins the final bars, so the walk approaches the close. Conflicts with end= or pins on those bars.

Returns:

A Progression with the captured chords and trailing history for NIR continuity.

Raises:

ValueError – If harmony() has not been called first, or the constraints are contradictory or unsatisfiable.

Example:

composition.harmony(style="functional_major", cycle_beats=4)
verse  = composition.freeze(8, end="V")   # the verse sets up the chorus
chorus = composition.freeze(4)            # next 4 chords, continuing on
composition.section_chords("verse",  verse)
composition.section_chords("chorus", chorus)
get_tweaks(name: str) Dict[str, Any][source]

Return a copy of the current tweaks for a running pattern.

Parameters:

name – The function name of the pattern.

harmony(style: str | subsequence.chord_graphs.ChordGraph | None = None, cycle_beats: int = 4, dominant_7th: bool = True, gravity: float = 1.0, nir_strength: float = 0.5, minor_turnaround_weight: float = 0.0, root_diversity: float = subsequence.harmonic_state.DEFAULT_ROOT_DIVERSITY, reschedule_lookahead: float = 1, progression: Any | None = None) None[source]

Configure the harmonic logic and chord change intervals.

Two sources, combinable: a bound progression (progression= — a Progression value, an element list like [1, 6, 3, "bVII7"], or chord names) walked span by span on the global clock; and/or a graph style stepping live chords. With only a progression bound, it loops on exhaustion; with a style configured too, exhaustion falls through to live stepping (the frozen-replay bridge). Calling with neither argument keeps today’s default live engine (style="functional_major").

Parameters:
  • style – The harmonic style to use. Built-in: “functional_major” (alias “diatonic_major”), “hooktheory_major” (alias “pop_major”), “turnaround”, “aeolian_minor”, “phrygian_minor”, “lydian_major”, “dorian_minor”, “chromatic_mediant”, “suspended”, “mixolydian”, “whole_tone”, “diminished”. See README for full descriptions.

  • cycle_beats – How many beats each live chord lasts (default 4). Bound progressions carry their own harmonic rhythm in their spans, so this applies to live stepping only.

  • dominant_7th – Whether to include V7 chords (default True).

  • gravity – Key gravity (0.0 to 1.0). High values stay closer to the root chord.

  • nir_strength – Melodic inertia (0.0 to 1.0). Influences chord movement expectations.

  • minor_turnaround_weight – For “turnaround” style, influences major vs minor feel.

  • root_diversity – Root-repetition damping (0.0 to 1.0). Each recent chord sharing a candidate’s root reduces the weight to 40% at the default (0.4). Set to 1.0 to disable.

  • reschedule_lookahead – How many beats in advance to calculate the next chord.

  • progression – A progression to bind to the global clock. Key- relative content resolves now, against the composition key and scale (binding freezes one realisation).

Example

# A moody minor progression that changes every 8 beats
comp.harmony(style="aeolian_minor", cycle_beats=8, gravity=0.4)

# Manual harmony driving everything — loops forever
comp.harmony(progression=subsequence.progression([1, 6, 3, 7]))
hotkey(key: str, action: Callable[[], None], quantize: int = 0, label: str | None = None) None[source]

Register a single-key shortcut that fires during playback.

The listener must be enabled first with hotkeys().

Most actions — form jumps, composition.data writes, and tweak() calls — should use quantize=0 (the default). Their musical effect is naturally delayed to the next pattern rebuild cycle, which provides automatic musical quantization without extra configuration.

Use quantize=N for actions where you want an explicit bar-boundary guarantee, such as mute() / unmute().

The ? key is reserved and cannot be overridden.

Parameters:
  • key – A single character trigger (e.g. "a", "1", " ").

  • action – Zero-argument callable to execute.

  • quantize0 = execute immediately (default). N = execute on the next global bar number divisible by N.

  • label – Display name for the ? help listing. Auto-derived from the function name or lambda body if omitted.

Raises:

ValueError – If key is the reserved ? character, or if key is not exactly one character.

Example:

composition.hotkeys()

# Immediate — musical effect happens at next pattern rebuild
composition.hotkey("a", lambda: composition.form_jump("chorus"))
composition.hotkey("1", lambda: composition.data.update({"mode": "chill"}))

# Explicit 4-bar phrase boundary
composition.hotkey("s", lambda: composition.mute("drums"), quantize=4)

# Named function — label is derived automatically
def drop_to_breakdown ():
    composition.form_jump("breakdown")
    composition.mute("lead")

composition.hotkey("d", drop_to_breakdown)

composition.play()
hotkeys(enabled: bool = True) None[source]

Enable or disable the global hotkey listener.

Must be called before play() to take effect. When enabled, a background thread reads single keystrokes from stdin without requiring Enter. The ? key is always reserved and lists all active bindings.

Hotkeys have zero impact on playback when disabled — the listener thread is never started.

Parameters:

enabledTrue (default) to enable hotkeys; False to disable.

Example:

composition.hotkeys()
composition.hotkey("a", lambda: composition.form_jump("chorus"))
composition.play()
layer(*builder_fns: Callable, channel: int, beats: float | None = None, bars: float | None = None, steps: float | None = None, step_duration: float | None = None, drum_note_map: Dict[str, int] | None = None, cc_name_map: Dict[str, int] | None = None, nrpn_name_map: Dict[str, int] | None = None, reschedule_lookahead: float = 1, voice_leading: bool = False, device: subsequence.midi_utils.DeviceId = None, mirrors: Iterable[subsequence.pattern.MirrorSpec] | None = None) None[source]

Combine multiple functions into a single MIDI pattern.

This is useful for composing complex patterns out of reusable building blocks (e.g., a ‘kick’ function and a ‘snare’ function).

See pattern() for the full description of beats, bars, steps, and step_duration.

Parameters:
  • builder_fns – One or more pattern builder functions.

  • channel – MIDI channel (1-16, or 0-15 with zero_indexed_channels=True).

  • beats – Duration in beats (quarter notes).

  • bars – Duration in bars (uses the composition’s time signature — 4 beats each in 4/4).

  • steps – Step count for step mode. Requires step_duration=.

  • step_duration – Duration of one step in beats. Requires steps=.

  • drum_note_map – Optional mapping for drum instruments.

  • cc_name_map – Optional mapping of CC names to MIDI CC numbers.

  • nrpn_name_map – Optional mapping of NRPN parameter names to 14-bit parameter numbers.

  • reschedule_lookahead – Beats in advance to compute the next cycle.

  • voice_leading – If True, chords use smooth voice leading.

  • mirrors – Optional list of additional (device, channel) destinations to duplicate every event onto. See pattern() for details.

Enable Ableton Link tempo and phase synchronisation.

When enabled, Subsequence joins the local Link session and slaves its clock to the shared network tempo and beat phase. All other Link-enabled apps on the same LAN — Ableton Live, iOS synths, other Subsequence instances — will automatically stay in time.

Playback starts on the next bar boundary aligned to the Link quantum, so downbeats stay in sync across all participants.

Requires the link optional extra:

pip install subsequence[link]
Parameters:

quantum – Beat cycle length. 4.0 (default) = one bar in 4/4 time. Change this if your composition uses a different meter.

Example:

comp = subsequence.Composition(bpm=120, key="C")
comp.link()          # join the Link session
comp.play()

# On another machine / instance:
comp2 = subsequence.Composition(bpm=120)
comp2.link()         # tempo and phase will lock to comp
comp2.play()

Note

set_bpm() proposes the new tempo to the Link network when Link is active. The network-authoritative tempo is applied on the next pulse, so there may be a brief lag before the change is visible.

live(port: int = 5555) None[source]

Enable the live coding eval server.

This allows you to connect to a running composition using the subsequence.live_client REPL and hot-swap pattern code or modify variables in real-time.

Security:

The server executes arbitrary Python in this process — it is not a sandbox. It binds to localhost only and is opt-in, but any process on the same machine that can reach the port gains full code execution here. Do not enable it on shared or multi-user hosts, and never expose the port to a network.

Parameters:

port – The TCP port to listen on (default 5555).

live_info() Dict[str, Any][source]

Return a dictionary containing the current state of the composition.

Includes BPM, key, current bar, active section, current chord, running patterns, and custom data.

load_patterns(source: str, source_label: str = '<string>') None[source]

Compile and apply a pattern-source string to the composition.

Equivalent to one watch() reload triggered by save, but with the source presented in-memory rather than on disk. Useful for web / REST handlers that accept pattern uploads from a trusted contributor, or for one-shot session loads with no file backing.

Behaviour mirrors watch():

  • The source is exec’d into a fresh namespace with composition and subsequence in scope.

  • @composition.pattern decorators in the source hot-swap their corresponding running patterns in place.

  • Patterns currently running but not declared in the source are unregistered — the source is treated as the full new truth.

  • If the composition is already playing, the swap happens on the event loop thread; the call blocks until it completes.

  • If the composition has not yet called play(), the source runs on the caller’s thread; decorators populate _pending_patterns and play() picks them up in the usual way.

Errors are raised so the caller can act on them:

  • SyntaxError if source fails to compile.

  • The exception raised inside exec() for any runtime error.

  • RuntimeError if called from inside the composition’s own event loop thread (would deadlock — see Threading below).

In either failure case, existing composition state is preserved — the diff-and-unregister phase is skipped if exec raised, so a half-broken upload cannot tear down working patterns.

Threading:

Designed to be called from a thread DIFFERENT from the composition’s event loop — typically a web-handler worker. Cannot be called from inside the loop itself (a pattern callback, an asyncio task spawned by the composition). From there, await composition._apply_source_async(...) directly.

SECURITY WARNING: exec() is not sandboxed. The source has full Python access in this process. Only pass source from trusted senders. The built-in blocklist (help, input, breakpoint, exit, quit) prevents calls that would stall the event loop; it is not a security boundary.

Parameters:
  • source – Python source declaring @composition.pattern functions.

  • source_label – Identifier used in compile errors and tracebacks (appears as the filename in SyntaxError and __file__- style traceback lines). Default "<string>".

lock(name: str) None[source]

Pin a named stream: keep its current effective seed and realization.

Engine-side state, so it survives live reload (it is never a builder swap): a locked pattern re-deals its stream from the same effective seed on every rebuild, so every cycle realizes identically, and reroll() refuses with a message until unlock().

Parameters:

name – The stream name — usually a pattern name.

midi_input(device: str, clock_follow: bool = False, name: str | None = None) None[source]

Configure a MIDI input device for external sync and MIDI messages.

May be called multiple times to register additional input devices. The first call sets the primary input (device 0). Subsequent calls add additional input devices (device 1, 2, …). Only one device may have clock_follow=True.

Parameters:
  • device – The name of the MIDI input port.

  • clock_follow – If True, Subsequence will slave its clock to incoming MIDI Ticks. It will also follow MIDI Start/Stop/Continue commands. Only one device can have this enabled at a time.

  • name – Optional alias for use with cc_map(input_device=…) and cc_forward(input_device=…). When omitted, the raw device name is used.

Example

# Single controller (unchanged usage)
comp.midi_input("Scarlett 2i4", clock_follow=True)

# Multiple controllers
comp.midi_input("Arturia KeyStep", name="keys")
comp.midi_input("Faderfox EC4", name="faders")
midi_output(device: str, name: str | None = None, latency_ms: float = 0.0) int[source]

Register an additional MIDI output device.

The first output device is always the one passed to Composition(output_device=…) — that is device 0. Each call to midi_output() adds the next device (1, 2, …).

Parameters:
  • device – The exact name of the MIDI output port, as reported by mido.get_output_names(). Matching is strict — partial names and substrings are rejected. See Composition.__init__ for the lookup snippet and a note on ALSA name stability on Linux.

  • name – Optional alias for use with pattern(device=…), cc_forward(output_device=…), etc. When omitted, the raw device name is used.

  • latency_ms – Physical output latency of this device in milliseconds, for delay compensation (default 0.0, must be non-negative). Set this when the device sounds late (e.g. a software sampler) so Subsequence delays faster devices to line everything up.

Returns:

The integer device index assigned (1, 2, 3, …).

Example

comp = subsequence.Composition(bpm=120, output_device="MOTU Express")

# Returns 1 — use as device=1 or device="integra"
comp.midi_output("Roland Integra", name="integra")

# A software sampler that sounds 20ms late
comp.midi_output("Subsample", name="sampler", latency_ms=20)

@comp.pattern(channel=1, beats=4, device="integra")
def strings (p):
        p.note(60, beat=0)
mirror(name: str, device: int, channel: int, drum_note_map: Dict[str, int] | None = None) None[source]

Add a mirror destination to a running pattern.

Every note, CC, pitch bend, NRPN/RPN, program change, SysEx, and drone event the pattern emits will also be sent to (device, channel), starting from the next cycle rebuild. Idempotent on (device, channel) — calling with the same destination twice does not double-fan; calling again with a different drum_note_map re-points it in place.

Parameters:
  • name – Function name of the pattern to mirror.

  • device – Output device index (the integer returned from midi_output(); 0 = primary device).

  • channel – MIDI channel using this composition’s numbering convention (1-16 by default; 0-15 if zero_indexed_channels=True).

  • drum_note_map – Optional per-destination drum map. When set, mirrored drum hits are re-resolved by name through it, so a named voice lands on this device’s own note number — see the README “MIDI mirroring” section.

Bandwidth: each mirror adds another full copy of the pattern’s events. See the README “MIDI mirroring” section for the tradeoffs.

mute(name: str) None[source]

Mute a running pattern by name.

The pattern continues to ‘run’ and increment its cycle count in the background, but it will not produce any MIDI notes until unmuted.

Parameters:

name – The function name of the pattern to mute.

note_input(channel: int | None = None, release_ms: float = 30.0, latch: bool = False, input_device: subsequence.midi_utils.DeviceId = None) None[source]

Track notes held on a MIDI keyboard for live arpeggiation.

Incoming note-on/note-off messages build a live “currently held” set that any pattern reads via p.held_notes() — typically fed straight to p.arpeggio(). The composition still authors the rhythm and motion; the player’s hands supply the pitch set. This is a live performance layer over the deterministic, seeded composition: when rendering headlessly there is no input, so p.held_notes() is empty and seeded output is unchanged.

Requires midi_input() to be called first to open an input port.

Parameters:
  • channel – If given, only track notes on this channel. Uses the same numbering convention as pattern() (1-16 by default, or 0-15 with zero_indexed_channels=True). None tracks any channel (default).

  • release_ms – How long (milliseconds) a released note keeps counting as held. This smooths the momentary all-keys-up gap during a hand-position change so the arp does not drop to silence. Default 30.0; set 0.0 to release instantly. Ignored when latch is True.

  • latch – When True, the held set persists after you lift your hands until you play a new chord (the first key after every key is up replaces it) — like a hardware arp’s latch.

  • input_device – Only track notes from this input device (index or name). None tracks any input device (default).

Example

comp.midi_input("Arturia KeyStep")
comp.note_input(channel=1, release_ms=30)

@comp.pattern(channel=6, beats=4)
def arp (p):
    p.arpeggio(p.held_notes(), direction="up")  # rests when silent
on_event(event_name: str, callback: Callable[Ellipsis, Any]) None[source]

Register a callback for a sequencer event (e.g., “bar”, “start”, “stop”).

on_section(callback: Callable[Ellipsis, Any]) None[source]

Register a callback fired on every section change.

The callback receives the new SectionInfo (or None when the form finishes). It fires from the form clock, one lookahead-beat early — in time to affect the new section’s first patterns — and once at play start for the opening section.

Example:

composition.on_section(lambda info: print(f"now: {info.name if info else 'end'}"))
osc(receive_port: int = 9000, send_port: int = 9001, send_host: str = '127.0.0.1', receive_host: str = '0.0.0.0') None[source]

Enable bi-directional Open Sound Control (OSC).

Subsequence will listen for commands (like /bpm or /mute) and broadcast its internal state (like /chord or /bar) over UDP.

Parameters:
  • receive_port – Port to listen for incoming OSC messages (default 9000).

  • send_port – Port to send state updates to (default 9001).

  • send_host – The IP address to send updates to (default “127.0.0.1”).

  • receive_host – Interface to listen on (default “0.0.0.0” — all interfaces, so external OSC controllers on the LAN can reach it). The listener can change tempo, mute patterns, and write data, so on an untrusted network restrict it with receive_host="127.0.0.1".

osc_map(address: str, handler: Callable) None[source]

Register a custom OSC handler.

Must be called after osc() has been configured.

Parameters:
  • address – OSC address pattern to match (e.g. "/my/param").

  • handler – Callable invoked with (address, *args) when a matching message arrives.

Example:

composition.osc()

def on_intensity (address, value):
        composition.data["intensity"] = float(value)

composition.osc_map("/intensity", on_intensity)
pattern(channel: int, beats: float | None = None, bars: float | None = None, steps: float | None = None, step_duration: float | None = None, drum_note_map: Dict[str, int] | None = None, cc_name_map: Dict[str, int] | None = None, nrpn_name_map: Dict[str, int] | None = None, reschedule_lookahead: float = 1, voice_leading: bool = False, device: subsequence.midi_utils.DeviceId = None, mirrors: Iterable[subsequence.pattern.MirrorSpec] | None = None, min_energy: float | None = None) Callable[source]

Register a function as a repeating MIDI pattern.

The decorated function will be called once per cycle to ‘rebuild’ its content. This allows for generative logic that evolves over time.

Two ways to specify pattern length:

  • Duration mode (default): use beats= or bars=. The grid defaults to sixteenth-note resolution.

  • Step mode: use steps= paired with step_duration=. The grid equals the step count, so p.hit_steps() indices map directly to steps.

Parameters:
  • channel – MIDI channel. By default uses 1-based numbering (1-16). Set zero_indexed_channels=True on the Composition to use 0-based numbering (0-15), matching the raw MIDI protocol, instead.

  • beats – Duration in beats (quarter notes). beats=4 = 1 bar.

  • bars – Duration in bars (uses the composition’s time signature — 4 beats each in 4/4). bars=2 = 8 beats.

  • steps – Step count for step mode. Requires step_duration=.

  • step_duration – Duration of one step in beats (e.g. dur.SIXTEENTH). Requires steps=.

  • drum_note_map – Optional mapping for drum instruments.

  • cc_name_map – Optional mapping of CC names to MIDI CC numbers. Enables string-based CC names in p.cc() and p.cc_ramp().

  • nrpn_name_map – Optional mapping of NRPN parameter names (strings) to 14-bit parameter numbers (0–16383). Enables string-based names in p.nrpn() and p.nrpn_ramp() — typically a device-specific dictionary (e.g. Sequential Take 5’s Osc1FreqFine → 9).

  • reschedule_lookahead – Beats in advance to compute the next cycle.

  • voice_leading – If True, chords in this pattern will automatically use inversions that minimize voice movement.

  • mirrors – Optional list of additional (device, channel) destinations to duplicate every event from this pattern onto. Notes, CCs, pitch bend, NRPN/RPN bursts, program changes, SysEx, and drone events are all mirrored; OSC events are not (OSC is not bound to a MIDI port). device is the integer index returned by midi_output() (0 = primary). channel follows this composition’s channel-numbering convention. See also mirror() / unmirror() for live toggling.

  • min_energy – Automatic energy gating — the pattern is silent while the current section’s energy (composition.energy() dict, or the bound Section payload) is below this threshold. Composes with mute(): a performer mute always wins.

Example

@comp.pattern(channel=1, beats=4)
def chords (p):
        p.chord([60, 64, 67], beat=0, velocity=80, duration=3.9)

@comp.pattern(channel=1, bars=2)
def long_phrase (p):
        ...

@comp.pattern(channel=1, steps=6, step_duration=dur.SIXTEENTH)
def riff (p):
        p.sequence(steps=[0, 1, 3, 5], pitches=60)
phrase_part(*, channel: int, part: str | None = None, root: int = 60, bars: float | None = None, beats: float | None = None, velocity: int | Tuple[int, int] | None = None, fit: float | None = None, device: subsequence.midi_utils.DeviceId = None, mirrors: Iterable[subsequence.pattern.MirrorSpec] | None = None) None[source]

Declare a part that plays each section’s bound Motif/Phrase.

The one-call consumer for section_motifs() — it registers a pattern on channel that walks whatever value is bound to the current section for part (stateless position from the cycle counter, via p.phrase()). A section with no binding for the part is silent for that part — bind material or don’t; no fallback guessing.

Parameters:
  • channel – MIDI channel for the part.

  • part – The part label to read from the registry (None = the unlabelled binding).

  • root – Register anchor for degree resolution.

  • beats (bars /) – Cycle length of the part (defaults to 4 beats); the phrase is sliced one cycle window at a time.

  • velocity – Optional override applied to every note.

  • fit – Passed through (active with the melody engine stage).

  • device – Optional output-device override.

  • mirrors – Optional additional (device, channel) destinations.

Example:

composition.section_motifs("verse",  verse_line,  part="lead")
composition.section_motifs("chorus", chorus_line, part="lead")
composition.phrase_part(channel=4, part="lead", root=72, bars=2)
pin_chord(bar: int, chord: Any | None) None[source]

Force the chord sounding at a bar — fiat over live generation.

Whatever the harmonic source (live walk, bound progression, section progression) produces for bar, the pinned chord overrides it. Pass None to remove a pin.

Parameters:
  • bar – 1-based bar number (the musician count).

  • chord – A chord name, int degree, roman string, Chord, PitchSet, or None to unpin. A key-relative spec (int degree, roman) re-keys like section harmony: it resolves late, against the effective key of the section sounding at that bar (so pin_chord(8, "V") is the dominant of wherever bar 8 lands). A concrete spec (name, Chord, PitchSet) is absolute and never moves.

Example:

composition.pin_chord(8, "E7")    # the turnaround lands on E7
composition.pin_chord(8, "V")     # the dominant of bar 8's section
composition.pin_chord(8, None)    # let it walk again
play() None[source]

Start the composition.

This call blocks until the program is interrupted (e.g., via Ctrl+C). It initializes the MIDI hardware, launches the background sequencer, and begins playback.

render(bars: int | None = None, filename: str = 'render.mid', max_minutes: float | None = 60.0) None[source]

Render the composition to a MIDI file without real-time playback.

Runs the sequencer as fast as possible (no timing delays) and stops when the first active limit is reached. The result is saved as a standard MIDI file that can be imported into any DAW.

All patterns, scheduled callbacks, and harmony logic run exactly as they would during live playback — BPM transitions, generative fills, and probabilistic gates all work in render mode. The only difference is that time is simulated rather than wall-clock driven.

Parameters:
  • bars – Number of bars to render, or None for no bar limit (default None). When both bars and max_minutes are active, playback stops at whichever limit is reached first.

  • filename – Output MIDI filename (default "render.mid").

  • max_minutes – Safety cap on the length of rendered MIDI in minutes (default 60.0). Pass None to disable the time cap — you must then provide an explicit bars value.

Raises:

ValueError – If both bars and max_minutes are None, which would produce an infinite render.

Examples

# Default: renders up to 60 minutes of MIDI content.
composition.render()

# Render exactly 64 bars (time cap still active as backstop).
composition.render(bars=64, filename="demo.mid")

# Render up to 5 minutes of an infinite generative composition.
composition.render(max_minutes=5, filename="five_min.mid")

# Remove the time cap — must supply bars instead.
composition.render(bars=128, max_minutes=None, filename="long.mid")
request_cadence(cadence: str = 'strong', bar: int | None = None) None[source]

Ask the live engine to approach a cadence arriving at a bar.

The request hook: where pin_chord() is fiat, this is a steered approach — at the next chord boundary the clock plans the remaining changes up to bar as a constrained walk through the engine’s real weights, pinned to the cadence formula at the tail ("strong" arrives V→I, "soft" IV→I, "open" IV→V, "fakeout" V→vi; theory aliases accepted). The chords still commit one boundary at a time, so the journey continues through the close.

One-shot: the request is consumed when planned. Live harmony only — bound/section progressions are data and cannot be steered; a request whose bar passes unserved expires with a warning. If the formula is not walkable from where the harmony stands, the arrival lands by fiat (loudly). Ask at least a pattern-lookahead ahead: patterns may already have rendered against the previously planned chord.

Parameters:
  • cadence – The cadence name.

  • bar – The 1-based bar the cadence’s final chord arrives at (required; in practice ≥ 2 — bar 1 cannot be approached).

Example:

composition.request_cadence("open", bar=16)    # hang on V at bar 16
reroll(name: str) None[source]

Deal a named stream a fresh deterministic seed — try a new variation.

Bumps the per-name nonce and prints the new effective seed. The nonce lives only in this process, so the printed seed is what lets a variation you like survive a restart: note it down, or lock() the name to pin it for the session. Refuses on locked names.

Parameters:

name – The stream name — usually a pattern name.

Example

comp.reroll("lead")    # prints: reroll('lead') -> effective seed ...
schedule(fn: Callable, cycle_beats: int, reschedule_lookahead: int = 1, wait_for_initial: bool = False, defer: bool = False) None[source]

Register a custom function to run on a repeating beat-based cycle.

Subsequence automatically runs synchronous functions in a thread pool so they don’t block the timing-critical MIDI clock. Async functions are run directly on the event loop.

Parameters:
  • fn – The function to call.

  • cycle_beats – How often to call it (e.g., 4 = every bar).

  • reschedule_lookahead – How far in advance to schedule the next call.

  • wait_for_initial – If True, run the function once during startup and wait for it to complete before playback begins. This ensures composition.data is populated before patterns first build. Implies defer=True for the repeating schedule.

  • defer – If True, skip the pulse-0 fire and defer the first repeating call to just before the second cycle boundary.

Raises:

RuntimeError – If called after play() has started — scheduled tasks register at startup, so a late registration would be silently ignored otherwise.

section_cadence(section_name: str, cadence: str | None = 'strong') None[source]

Close every pass of a section with a cadence — the standing request.

Each time section_name is entered, the clock registers a request_cadence() arriving at the section’s final bar, so the harmony approaches the close as the section ends. Live harmony only: a section with bound chords (section_chords()) is data and ignores the registration — its closes are written, not steered. Pass None to unregister.

Example:

composition.form([("verse", 8), ("chorus", 8)])
composition.section_cadence("verse", "open")     # every verse hangs on V
composition.section_cadence("chorus", "strong")  # every chorus lands home
section_chords(section_name: str, progression: Any) None[source]

Bind a Progression to a named form section.

Every time section_name plays, the harmonic clock walks the progression’s spans instead of calling the live engine. Sections without a bound progression continue generating live chords.

Accepts a Progression value (from freeze(), the progression() factory, or hand-built) or anything the factory accepts — an element list like [1, 6, 3, "bVII7"] or chord names.

Key-relative content re-keys per occurrence. A progression written in degrees or romans is key-relative content: it resolves late, each time the section plays, against that section’s effective key and scale (Section.key > form key > composition key, with mode following the same chain). So a Section(key="A") plays the same numbered progression a tone higher — its chords and its degrees share one tonic. Absolute content — chord names ("Am"), PitchSet, and frozen captures from freeze() — names exact chords and is never transposed by a key.

On exhaustion mid-section the progression loops when no graph style is configured (and always when it contains a PitchSet); with a live engine, exhaustion falls through to live stepping in the COMPOSITION key — the live graph engine does not transpose for a section (a stateful walk does not modulate mid-stream), so a re-keyed section that runs out of written chords hands off to composition-key harmony. Bind a full-length progression (or set at_end/loop intent) if you need the whole section in its key.

Parameters:
  • section_name – Name of the section as defined in form().

  • progression – The progression to bind.

Raises:

ValueError – If a graph-based form has been configured and section_name is not one of its sections. List and generator forms yield names lazily, so they cannot be validated here. (A key-relative progression with no resolvable key for the section is caught at play()/render(), once the form’s keys are known.)

Example:

composition.section_chords("verse",  verse_progression)
composition.section_chords("chorus", [1, 6, 3, 7])
# "bridge" is not bound — it generates live chords
section_motifs(section_name: str, value: Any, part: str | None = None) None[source]

Bind a Motif or Phrase to a named form section (per optional part).

Patterns read the binding back with p.section_motif(part) (or use the one-call phrase_part()); a section with no binding for the part is silent for that part — bind material or don’t, no fallback guessing. Re-binding is idempotent, so the call is safe in a live file: re-executing on save is the desired rebind.

Parameters:
  • section_name – Name of the section as defined in form().

  • value – A Motif or Phrase (anything exposing .length/.slice places).

  • part – Optional part label, so one section can carry several bindings ("lead", "bass", …).

Raises:

ValueError – If a graph-based form has been configured and section_name is not one of its sections.

Example:

composition.section_motifs("verse",  verse_line,  part="lead")
composition.section_motifs("chorus", chorus_line, part="lead")
seed_for(name: str) int | None[source]

Surface the effective derived seed for a named stream.

Works for pattern names and equally for any name you invent for a standalone value generator (seed=composition.seed_for("hook")), so its randomness keys off the composition seed without sharing any other consumer’s stream. Reflects reroll() nonces. Returns None when the composition is unseeded.

Example

hook_seed = composition.seed_for("hook")
set_bpm(bpm: float) None[source]

Instantly change the tempo.

Parameters:

bpm – The new tempo in beats per minute.

When Ableton Link is active, this proposes the new tempo to the Link network instead of applying it locally. The network-authoritative tempo is picked up on the next pulse.

target_bpm(bpm: float, bars: int, shape: str = 'linear') None[source]

Smoothly ramp the tempo to a target value over a number of bars.

Parameters:
  • bpm – Target tempo in beats per minute.

  • bars – Duration of the transition in bars.

  • shape – Easing curve name. Defaults to "linear". "ease_in_out" or "s_curve" are recommended for natural- sounding tempo changes. See subsequence.easing for all available shapes.

Example

# Accelerate to 140 BPM over the next 8 bars with a smooth S-curve
comp.target_bpm(140, bars=8, shape="ease_in_out")

Note

Ignored while Ableton Link is active — the shared session tempo is authoritative. Use set_bpm() to propose a tempo to the Link network.

transition(before: str, fill: Any | None = None, channel: int | None = None, beat: float = 0.0, mute: List[str] | None = None, beats: float | None = None, drum_note_map: Dict[str, int] | None = None, device: subsequence.midi_utils.DeviceId = None) None[source]

Declare boundary material — the automatic fill or mute, one line.

before names the incoming section ("chorus"), or "*" for any different section (repeats don’t fire it). Two actions, combinable:

  • fill= (+ channel=, beat=): a Motif played in the last bar before the boundary, starting at beat of that bar. Drum names resolve through drum_note_map= if given, otherwise the map is borrowed from a registered pattern on the same channel.

  • mute= (+ beats=): pattern names muted over the approach and unmuted at the boundary. Muting is bar-granular (the existing rule), so beats rounds up to whole bars. Performer mutes win: a pattern you muted yourself stays muted.

Transitions stack — call once per rule. Registration is additive and idempotent per identical rule.

Example:

composition.transition(before="*", fill=FILL, channel=10, beat=2.0)
composition.transition(before="drop", mute=["pads"], beats=4)
trigger(fn: Callable, channel: int, beats: float | None = None, bars: float | None = None, steps: float | None = None, step_duration: float | None = None, quantize: float = 0, drum_note_map: Dict[str, int] | None = None, cc_name_map: Dict[str, int] | None = None, nrpn_name_map: Dict[str, int] | None = None, chord: bool = False, device: subsequence.midi_utils.DeviceId = None, mirrors: Iterable[subsequence.pattern.MirrorSpec] | None = None) None[source]

Trigger a one-shot pattern immediately or on a quantized boundary.

This is useful for real-time response to sensors, OSC messages, or other external events. The builder function is called immediately with a fresh PatternBuilder, and the generated events are injected into the queue at the specified quantize boundary.

The builder function has the same API as a @composition.pattern decorated function and can use all PatternBuilder methods: p.note(), p.euclidean(), p.arpeggio(), and so on.

See pattern() for the full description of beats, bars, steps, and step_duration. Default is 1 beat.

Parameters:
  • fn – The pattern builder function (same signature as @comp.pattern).

  • channel – MIDI channel (1-16, or 0-15 with zero_indexed_channels=True).

  • beats – Duration in beats (quarter notes, default 1).

  • bars – Duration in bars (uses the composition’s time signature — 4 beats each in 4/4).

  • steps – Step count for step mode. Requires step_duration=.

  • step_duration – Duration of one step in beats. Requires steps=.

  • quantize – Snap the trigger to a beat boundary: 0 = immediate (default), 1 = next beat (quarter note), 4 = next bar. Use dur.* constants from subsequence.constants.durations.

  • drum_note_map – Optional drum name mapping for this pattern.

  • cc_name_map – Optional mapping of CC names to MIDI CC numbers.

  • nrpn_name_map – Optional mapping of NRPN parameter names to 14-bit parameter numbers.

  • chord – If True, the builder function receives the current chord as a second parameter (same as @composition.pattern).

  • mirrors – Optional list of additional (device, channel) destinations to fire this one-shot onto in parallel with the primary destination.

Example

# Immediate single note (channels are 1-16 by default)
composition.trigger(
        lambda p: p.note(60, beat=0, velocity=100, duration=0.5),
        channel=1
)

# Quantized fill (next bar) — channel 10 is the GM drum channel
import subsequence.constants.durations as dur
composition.trigger(
        lambda p: p.euclidean("snare", pulses=7, velocity=90),
        channel=10,
        drum_note_map=gm_drums.GM_DRUM_MAP,
        quantize=dur.WHOLE
)

# With chord context — the builder receives the chord as a second
# argument when chord=True.
composition.trigger(
        lambda p, chord: p.arpeggio(chord.tones(root=60), spacing=dur.SIXTEENTH),
        channel=1,
        quantize=dur.QUARTER,
        chord=True
)
tuning(source: str | os.PathLike | None = None, *, cents: List[float] | None = None, ratios: List[float] | None = None, equal: int | None = None, bend_range: float = 2.0, channels: List[int] | None = None, reference_note: int = 60, exclude_drums: bool = True) None[source]

Set a global microtonal tuning for the composition.

The tuning is applied automatically after each pattern rebuild (before the pattern is scheduled). Drum patterns (those registered with a drum_note_map) are excluded by default.

Supply exactly one of the source parameters:

  • source: path to a Scala .scl file.

  • cents: list of cent offsets for degrees 1..N (degree 0 = 0.0 is implicit).

  • ratios: list of frequency ratios (e.g., [9/8, 5/4, 4/3, 3/2, 2]).

  • equal: integer for N-tone equal temperament (e.g., equal=19).

For polyphonic parts, supply a channels pool. Notes are spread across those MIDI channels so each can carry an independent pitch bend. The synth must be configured to match bend_range (its pitch-bend range setting in semitones).

Parameters:
  • source – Path to a .scl file.

  • cents – Cent offsets for scale degrees 1..N.

  • ratios – Frequency ratios for scale degrees 1..N.

  • equal – Number of equal divisions of the period.

  • bend_range – Synth pitch-bend range in semitones (default ±2).

  • channels – Channel pool for polyphonic rotation.

  • reference_note – MIDI note mapped to scale degree 0 (default 60 = C4).

  • exclude_drums – When True (default), skip patterns that have a drum_note_map (they use fixed GM pitches, not tuned ones).

Example

# Quarter-comma meantone from a Scala file
comp.tuning("meanquar.scl")

# Just intonation from ratios
comp.tuning(ratios=[9/8, 5/4, 4/3, 3/2, 5/3, 15/8, 2])

# 19-TET, monophonic
comp.tuning(equal=19, bend_range=2.0)

# 31-TET with channel rotation for polyphony (channels 1-6)
comp.tuning("31tet.scl", channels=[0, 1, 2, 3, 4, 5])
tweak(name: str, **kwargs: Any) None[source]

Override parameters for a running pattern.

Values set here are available inside the pattern’s builder function via p.param(). They persist across rebuilds until explicitly changed or cleared. Changes take effect on the next rebuild cycle.

Parameters:
  • name – The function name of the pattern.

  • **kwargs – Parameter names and their new values.

Example (from the live REPL):

composition.tweak("bass", pitches=[48, 52, 55, 60])
unlock(name: str) None[source]

Release a lock(): the stream runs free and reroll() works again.

unmirror(name: str, device: int, channel: int) None[source]

Remove a single mirror destination from a running pattern.

Matches on (device, channel) only — any attached drum_note_map is ignored. Idempotent: silently does nothing if the destination is not currently mirrored. The change applies on the next cycle rebuild.

unmirror_all(name: str) None[source]

Remove every mirror destination from a running pattern.

unmute(name: str) None[source]

Unmute a previously muted pattern.

unregister(name: str) None[source]

Fully remove a running pattern from rotation.

Unlike mute() (which keeps the pattern alive but silent), unregister() tears the pattern down entirely. It sets pattern._removed = True so the sequencer’s reschedule loop skips re-adding it on the next pulse; sends note_off for any of the pattern’s currently-sounding notes on the primary destination AND on every mirror destination (so drones and sustaining notes stop immediately); and removes the entry from _running_patterns so it no longer appears in live_info(), the terminal grid, or any other consumer that enumerates running patterns.

Already-queued events in the sequencer’s event queue play out — note_offs are paired with their note_ons at queue time, so notes end at their natural duration; only drones rely on the targeted _stop_pattern_notes pass.

Idempotent: silently logs a debug and returns if the pattern is already absent. Useful from both the live REPL (composition.live()) and the file watcher (composition.watch()), which calls this for any pattern removed from the watched file between reloads.

Parameters:

name – Function name of the pattern to remove.

watch(path: str | pathlib.Path, poll_interval: float = 0.25) None[source]

Watch a Python file and reload it into the composition on every save.

The watched file is exec’d into a namespace with composition and subsequence available. @composition.pattern decorators inside the file hot-swap their corresponding running patterns in place; patterns whose function bodies have been deleted from the file are unregistered automatically on the next reload (notes stopped, removed from the running-pattern set).

An initial synchronous load happens here — if the file has a SyntaxError or doesn’t exist at this moment, the exception propagates so the user knows immediately. Subsequent reloads happen on the composition’s event loop and tolerate transient errors (logged, skipped).

Call BEFORE composition.play(). Reloads happen on the composition’s event loop, so all mutations are thread-safe.

See the “Live coding via file watching” section of the README for the recommended wrapper-script + live-file split.

Parameters:
  • path – Path to the Python file to watch.

  • poll_interval – Seconds between mtime polls (default 0.25 s).

Example:

# live_init.py — runs once
composition = subsequence.Composition(bpm=120, key="E")
composition.harmony(style="aeolian_minor")
composition.watch("live_patterns.py")
composition.play()
web_ui(http_host: str = '127.0.0.1', ws_host: str = '127.0.0.1') None[source]

Enable the realtime Web UI Dashboard.

When enabled, Subsequence instantiates a WebSocket server that broadcasts the current state, signals, and active patterns (with high-res timing and note data) to any connected browser clients.

Both servers bind to localhost by default. Pass http_host / ws_host (e.g. “0.0.0.0”) to opt into LAN exposure — the dashboard is read-only but broadcasts full composition state, so only do so on a trusted network.

property builder_bar: int[source]

Current bar index used by pattern builders.

property form_state: subsequence.form_state.FormState | None[source]

The active subsequence.form_state.FormState, or None if form() has not been called.

property harmonic_state: subsequence.harmonic_state.HarmonicState | None[source]

The active HarmonicState, or None if harmony() has not been called.

property is_clock_following: bool[source]

True if either the primary or any additional device is following external clock.

property running_patterns: Dict[str, Any][source]

The currently active patterns, keyed by name.

property seed: int | None[source]

The composition’s random seed, or None when unseeded.

When set, every random decision derives deterministically from this value through named streams (see seed_for()), so the same script produces the same music on every run. Assign to set it:

comp.seed = 42

(Formerly the method comp.seed(42) — the call form is a hard break per the pre-1.0 rename policy.)

property sequencer: subsequence.sequencer.Sequencer[source]

The underlying Sequencer instance.

class subsequence.composition.HarmonyView(horizon: _HarmonyHorizon, origin_beat: float)[source]

Read-only harmony context for one pattern cycle (p.harmony).

Anchored at the cycle’s start beat, so all beat arguments are cycle-relative — chord_at(0) is the chord at the cycle’s first beat (what the two-parameter chord convention injects), chord_at(3.5) the chord sounding under beat 3.5 of this cycle.

Under bound/frozen progressions the future is data and any beat answers; in live graph mode the window is the current chord plus one pre-committed step, and next_chord is planned and revocable.

Anchor the view at a cycle-start beat.

chord_at(beat: float) Any | None[source]

The chord sounding at beat of THIS cycle (0-based beats).

next_chord_at(beat: float) Any | None[source]

The chord following the one sounding at beat of THIS cycle, when known.

property chord: Any | None[source]

The chord at this cycle’s start (the cycle-start snapshot).

property next_chord: Any | None[source]

The chord after the current one — for anticipation and approach tones.

property until_change: float | None[source]

Beats from the cycle start until the next chord boundary, when known.

class subsequence.composition.HotkeyBinding[source]

A registered keyboard shortcut and its associated action.

key[source]

The single character that triggers this binding.

action[source]

Zero-argument callable executed when the key fires.

quantize[source]

0 = execute immediately; N = execute on the next global bar divisible by N.

label[source]

Human-readable description shown by the ? help key.

class subsequence.composition.ScheduleContext[source]

Context object passed to composition.schedule() callbacks whose signature declares a first parameter (conventionally named p).

cycle[source]

How many times this callback has been called so far (0-indexed). 0 on the first call, including the blocking wait_for_initial run.

async subsequence.composition.run_until_stopped(sequencer: subsequence.sequencer.Sequencer) None[source]

Run the sequencer until a stop signal is received.

async subsequence.composition.schedule_form(sequencer: subsequence.sequencer.Sequencer, form_state: subsequence.form_state.FormState, reschedule_lookahead: float = 1, on_bar: Callable[[int, bool], None] | None = None) None[source]

Schedule the form state to advance each bar.

Emits a "section" event on the sequencer’s emitter at play start and on every section change (one lookahead-beat early, like every form decision), carrying the new SectionInfo (None when the form finishes). on_bar is the boundary hook — called once per bar with (boundary_pulse, section_changed) after the form advances; the transition machinery rides it.

async subsequence.composition.schedule_harmonic_clock(sequencer: subsequence.sequencer.Sequencer, get_harmonic_state: Callable[[], subsequence.harmonic_state.HarmonicState | None], horizon: _HarmonyHorizon, bar_beats: float, cycle_beats: int = 4, get_bound_progression: Callable[[], Progression | None] | None = None, get_section_progression: Callable[[], Tuple[str, int, int, Progression | None] | None] | None = None, get_pinned: Callable[[int], Any | None] | None = None, cadence_requests: Dict[int, str] | None = None, resolve_cadence: Callable[[str], List[subsequence.chords.Chord]] | None = None, get_section_cadence: Callable[[str], str | None] | None = None, reschedule_lookahead: float = 1) None[source]

Schedule the harmonic clock — a span walker over the bound harmony sources.

Generalises the old fixed-cycle clock: chords last as long as their spans say, the clock fires at min(next span boundary, next bar boundary) (so section bookkeeping stays bar-aligned under variable harmonic rhythm), and every realised span is published to horizon (the harmony window patterns read through p.harmony).

Priority chain per chord boundary: section progression > composition-bound progression > live ``step()``. A bound progression loops on exhaustion when no live engine is configured (or when it contains a PitchSet); with a live engine, exhaustion falls through to live stepping — the frozen-replay bridge. In live mode the engine pre-commits one step so the window always holds [current, next].

get_harmonic_state, get_bound_progression, and get_pinned are evaluated on every tick so mid-playback calls to harmony(), re-binds, and new pins take effect immediately. get_section_progression returns (name, index, bars, Progression|None) for the current section (index increments on every entry, so verse→verse re-entry resets correctly) or None when no form is active.

cadence_requests is the request-hook seam: a mutable {bar: name} dict (shared with Composition.request_cadence) the live walk steers toward — at the first boundary with a pending request, the remaining changes up to its bar are planned as a constrained walk pinned to the cadence formula (resolved by resolve_cadence) and then committed one boundary at a time. get_section_cadence turns a section entry into a request arriving at that section’s final bar (live sections only). Requests whose bar passes unserved expire with a warning.

The clock fires reschedule_lookahead beats before each boundary — raised by the caller to the maximum pattern lookahead, so the window always covers a pattern’s next cycle before it rebuilds.

async subsequence.composition.schedule_patterns(sequencer: subsequence.sequencer.Sequencer, patterns: Iterable[subsequence.pattern.Pattern], start_pulse: int = 0) None[source]

Schedule a collection of repeating patterns from a shared start pulse.

async subsequence.composition.schedule_task(sequencer: subsequence.sequencer.Sequencer, fn: Callable, cycle_beats: int, reschedule_lookahead: int = 1, defer: bool = False) None[source]

Schedule a non-blocking repeating task on the sequencer’s beat clock.

If fn declares a first parameter named p, it is called with a ScheduleContext on every invocation (same behaviour as composition.schedule()).

When defer is True the backshift fire at pulse 0 is skipped; the first call happens one full cycle_beats later. Direct API users who need the equivalent of initial=True can simply await fn() themselves before calling this function.