subsequence.form_state

Compositional form tracking — section sequences, transitions, and lookahead.

Defines SectionInfo (immutable per-bar snapshot) and FormState (the stateful form engine that advances through sections).

These are registered on the Composition via form() and read by pattern builders through p.section.

A form may be a Form value (Sections with energy/key payloads), a plain list of (name, bars) tuples or Sections, a generator yielding (name, bars) pairs, or a weighted-graph dict. Everything normalises to Section internally — the payload travels with the section either way.

Module Contents

class subsequence.form_state.FormState(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, rng: random.Random | None = None, at_end: str = 'stop')[source]

Track compositional form as a sequence of named sections with bar durations.

Initialize from a Form, list, iterator, or dict of weighted section transitions.

Parameters:
  • sections – Form definition. A Form value, a list of Sections / (name, bars) tuples, an iterator yielding (name, bars) tuples, or a dictionary defining a weighted directed graph for generative progression.

  • loop – Sugar for at_end="loop" (sequence mode).

  • start – Name of the starting section when using a graph dict. If omitted, it defaults to the first key in the dictionary.

  • rng – Optional seeded random.Random for deterministic graph decisions.

  • at_end – What happens when a sequence/generator form runs out — "stop" (the form finishes; default), "hold" (the final section repeats until navigated away from), or "loop" (start over). Graphs end via their terminal sections, so a graph only accepts "stop".

advance() bool[source]

Advance one bar, transitioning to the next section when needed, returning True if section changed.

get_section_info() SectionInfo | None[source]

Return current section info, or None if the form is exhausted.

jump_to(section_name: str) None[source]

Force the form to a named section immediately.

Available in graph mode (dict forms) and sequence mode (list/Form forms — the jump lands on the next occurrence of the name, searching forward and wrapping, and the form continues from there). A generator form cannot be navigated.

The section restarts from bar 0. The musical effect is not heard until the next pattern rebuild cycle, because already-queued MIDI notes are unaffected. This is the same natural quantization that applies to all composition.data writes and composition.tweak() calls.

Parameters:

section_name – Name of the section to jump to. Must exist in the form definition passed to composition.form().

Raises:

ValueError – If the form is a generator, or the name is unknown.

Example:

composition.form_jump("chorus")   # via Composition helper
queue_next(section_name: str) None[source]

Queue a section to play after the current one ends.

Overrides the automatically pre-decided next section. The queued section takes effect at the natural section boundary — the current section plays to completion first. In sequence mode the form continues from the queued occurrence onward.

Available in graph and sequence (list/Form) modes; a generator form cannot be navigated.

Parameters:

section_name – The section to queue.

Raises:

ValueError – If the form is a generator, or the name is unknown.

section_info_at_bar(bar: int) SectionInfo | None[source]

Return the section covering a 1-based GLOBAL bar, or None.

Available for sequence forms only (lists and Form values — the whole timeline is known, so a bar maps to a section by accumulating Section.bars). Graph and generator forms have no fixed layout ahead of the playhead, so they return None (callers fall back to the playhead section). A looping form wraps; a finite form past its end returns None.

Used to key a relative pin_chord to the section that owns the pinned bar rather than the section at the playhead — they differ when the harmonic clock’s lookahead projects a pin into a later, possibly differently-keyed, section. This is a layout lookup (linear from bar 1); live form_jump is not reflected, which is acceptable for pre-set pins (the playhead path stays authoritative for live moves).

property total_bars: int[source]

Return the global bar count since the form started.

class subsequence.form_state.SectionInfo[source]

An immutable snapshot of the current section in the compositional form.

Patterns read p.section to make context-aware decisions, such as increasing intensity as a section progresses or playing variation only in certain blocks.

name[source]

The string name of the section (e.g., “verse”).

bar[source]

The current bar index within this section (0-indexed).

bars[source]

Total number of bars in this section.

index[source]

The global index of this section in the form’s timeline.

next_section[source]

The name of the upcoming section (or None if the form will end after this section). This is pre-decided when the current section begins, so patterns can plan lead-ins. A performer or code can override it with composition.form_next().

energy[source]

The section’s energy payload (0.5 unless a bound Form says otherwise; the composition.energy() dict overrides it at read time).

key[source]

The section’s key override, or None (a higher tier — form key, then composition key — supplies it).

scale[source]

The section’s scale/mode override, or None (falls back through the form scale to the composition scale).

Example

@composition.pattern(channel=9)
def drums (p):
        # Always play a basic kick
        p.hit_steps("kick", [0, 8])

        # Only add snare and hats during the "chorus"
        if p.section and p.section.name == "chorus":
                p.hit_steps("snare", [4, 12])

                # Use .progress (0.0 to 1.0) to build a riser
                vel = int(60 + 40 * p.section.progress)
                p.hit_steps("hh", list(range(16)), velocity=vel)

        # Plan a lead-in on the last bar before a different section
        if p.section and p.section.ending:
                p.hit_steps("snare", [0, 2, 4, 6, 8, 10, 12, 14], velocity=100)
property ending: bool[source]

True on the last bar before a DIFFERENT section.

A repeat (verse → verse) is not an ending, and neither is the form’s end — ending marks the bars where transition material (fills, mutes) belongs.

property first_bar: bool[source]

Return True if this is the first bar of the section.

property last_bar: bool[source]

Return True if this is the last bar of the section.

property progress: float[source]

Return how far through this section we are (0.0 to ~1.0).