subsequence.form_state ====================== .. py:module:: subsequence.form_state .. autoapi-nested-parse:: Compositional form tracking — section sequences, transitions, and lookahead. Defines :class:`SectionInfo` (immutable per-bar snapshot) and :class:`FormState` (the stateful form engine that advances through sections). These are registered on the :class:`~subsequence.composition.Composition` via :meth:`~subsequence.composition.Composition.form` and read by pattern builders through ``p.section``. A form may be a :class:`~subsequence.forms.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 :class:`~subsequence.forms.Section` internally — the payload travels with the section either way. Module Contents --------------- .. py:class:: FormState(sections: Union[subsequence.forms.Form, List[Any], Iterator[Tuple[str, int]], Dict[str, Tuple[int, Optional[List[Tuple[str, int]]]]]], loop: bool = False, start: Optional[str] = None, rng: Optional[random.Random] = None, at_end: str = 'stop') Track compositional form as a sequence of named sections with bar durations. Initialize from a Form, list, iterator, or dict of weighted section transitions. :param sections: Form definition. A :class:`~subsequence.forms.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. :param loop: Sugar for ``at_end="loop"`` (sequence mode). :param start: Name of the starting section when using a graph dict. If omitted, it defaults to the first key in the dictionary. :param rng: Optional seeded ``random.Random`` for deterministic graph decisions. :param 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"``. .. py:method:: advance() -> bool Advance one bar, transitioning to the next section when needed, returning True if section changed. .. py:method:: get_section_info() -> Optional[SectionInfo] Return current section info, or None if the form is exhausted. .. py:method:: jump_to(section_name: str) -> None 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. :param 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 .. py:method:: queue_next(section_name: str) -> None 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. :param section_name: The section to queue. :raises ValueError: If the form is a generator, or the name is unknown. .. py:method:: section_info_at_bar(bar: int) -> Optional[SectionInfo] 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). .. py:property:: total_bars :type: int Return the global bar count since the form started. .. py:class:: SectionInfo 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. .. attribute:: name The string name of the section (e.g., "verse"). .. attribute:: bar The current bar index within this section (0-indexed). .. attribute:: bars Total number of bars in this section. .. attribute:: index The global index of this section in the form's timeline. .. attribute:: next_section 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()``. .. attribute:: energy The section's energy payload (0.5 unless a bound :class:`~subsequence.forms.Form` says otherwise; the ``composition.energy()`` dict overrides it at read time). .. attribute:: key The section's key override, or ``None`` (a higher tier — form key, then composition key — supplies it). .. attribute:: scale The section's scale/mode override, or ``None`` (falls back through the form scale to the composition scale). .. rubric:: Example .. code-block:: python @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) .. py:property:: ending :type: bool 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. .. py:property:: first_bar :type: bool Return True if this is the first bar of the section. .. py:property:: last_bar :type: bool Return True if this is the last bar of the section. .. py:property:: progress :type: float Return how far through this section we are (0.0 to ~1.0).