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
Formvalue, 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.Randomfor 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.datawrites andcomposition.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
Formvalues — the whole timeline is known, so a bar maps to a section by accumulatingSection.bars). Graph and generator forms have no fixed layout ahead of the playhead, so they returnNone(callers fall back to the playhead section). A looping form wraps; a finite form past its end returnsNone.Used to key a relative
pin_chordto 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); liveform_jumpis not reflected, which is acceptable for pre-set pins (the playhead path stays authoritative for live moves).
- class subsequence.form_state.SectionInfo[source]¶
An immutable snapshot of the current section in the compositional form.
Patterns read
p.sectionto make context-aware decisions, such as increasing intensity as a section progresses or playing variation only in certain blocks.- next_section[source]¶
The name of the upcoming section (or
Noneif 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 withcomposition.form_next().
- energy[source]¶
The section’s energy payload (0.5 unless a bound
Formsays otherwise; thecomposition.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)