subsequence.progressions ======================== .. py:module:: subsequence.progressions .. autoapi-nested-parse:: Progressions — chord sequences laid out in time, as a governing value. The one progression type: a frozen tuple of :class:`ChordSpan` — replacing the old engine ``Progression`` (the ``freeze()`` capture) and ``ChordTimeline`` (the realised iterable) with a single value that is constructible, queryable, transformable, and bindable to the harmonic clock. Construction (the standard form — lists, parsed per element): subsequence.progression([1, 6, 3, 7]) # diatonic degrees subsequence.progression([1, 6, 3, "bVII7"]) # romans where chromatic subsequence.progression(["Am", "F", "C", "G"]) # chord names subsequence.progression([("Am", 4), ("F", 2)]) # per-chord beats subsequence.progression(style="aeolian_minor", key="A", bars=8, seed=3) Key-relative content (ints and romans) stays relative inside the value and resolves at query time — change the key once, everything follows. Spice transforms (``extend``, ``inversions``, ``spread``, ``over``, ``borrow``) decorate the spans, never the chords: the engine's currency stays the bare ``(root_pc, quality)`` triad, and decoration travels with the span to the voicing layer. Module Contents --------------- .. py:class:: ChordEvent Bases: :py:obj:`NamedTuple` One chord on a realised timeline: which chord, when it starts, and how long it lasts (in beats from the start of the part). A ``NamedTuple``, so it unpacks positionally as ``(chord, start, length)`` — the idiom for looping a progression — while also offering ``.chord`` / ``.start`` / ``.length`` attribute access. .. py:class:: ChordSpan One chord with a duration and its decoration — the unit of harmonic time. Decoration (extensions, slash bass, inversion, spread) lives HERE, never on :class:`~subsequence.chords.Chord`: the engine's graph identity stays the bare triad, and the decorated voicing is what patterns hear. .. attribute:: chord A concrete ``Chord``, a key-relative :class:`RomanChord`, or a :class:`PitchSet`. .. attribute:: beats Span length in beats. .. attribute:: extensions Extension markers — ints (``7``, ``9``, ``11``, ``13``) or names (``"sus2"``, ``"sus4"``, ``"add9"``, ``"6"``). .. attribute:: bass Slash/pedal bass — a pitch class int, a note name, or ``"tonic"`` (resolved against the key at query time). .. attribute:: inversion Chord inversion for the voicing (0 = root position). .. attribute:: spread Voicing spread — ``"close"`` (default), ``"open"`` (drop-2), or ``"wide"`` (drop-2-and-4). .. attribute:: extension_intervals Pre-computed semitone offsets for the extensions, set by :meth:`Progression.resolve` for diatonic degrees. ``None`` means "derive from the chord's own colour". .. py:method:: decorated_intervals() -> List[int] Semitone offsets of the decorated voicing (before inversion/spread/bass). Numeric extensions deepen the chord in its own colour — a minor third gets a minor seventh, a major third a major seventh, a diminished triad a diminished seventh. Diatonic degrees extended with ``extend(...)`` carry pre-computed scale-true intervals instead (so V gets its dominant seventh). Write ``"G7"``/``"V7"`` when you want the dominant colour on a concrete major chord. .. py:method:: label(key_pc: Optional[int] = None, scale: str = 'ionian') -> str A printable chord label: roman text when relative, decorated name when concrete. .. py:method:: resolve(key_pc: int, scale: str = 'ionian') -> ChordSpan Return a concrete span: romans resolved, bass resolved to a pitch class. .. py:method:: tones(root: int = 60, count: Optional[int] = None) -> List[int] MIDI notes of the decorated voicing nearest *root* (concrete spans only). Applies, in order: extensions, inversion, spread, then the slash/pedal bass below the voicing. ``PitchSet`` spans return their absolute pitches (decoration other than ``count`` does not apply). .. py:property:: is_concrete :type: bool True when the chord (and any pedal bass) needs no key context to sound. A ``"tonic"`` pedal bass is key-relative, so a span carrying one is not concrete until :meth:`resolve` pins it to a key. Note-name basses are resolved to a pitch class eagerly in :meth:`Progression.over`, so they never linger here as strings. .. py:property:: is_decorated :type: bool True when the span carries any decoration beyond the bare chord. .. py:class:: DecoratedChord(span: ChordSpan) Duck-types the ``Chord`` voicing protocol over a decorated span. What patterns and the injected ``chord`` receive when a span carries decoration: ``tones()`` voices the extensions/inversion/spread/bass, ``intervals()`` reports the decorated intervals (so per-pattern voice leading works over them), and ``name()`` prints the decorated name (``Am9``, ``C/G``). The engine itself never sees this — graph identity stays the bare triad underneath (:attr:`base`). Wrap a concrete, decorated span. .. py:method:: bass_note(root_midi: int, octave_offset: int = -1) -> int The chord root shifted by octaves (the slash bass pc when one is set). The slash bass uses the same register as the plain root bass — an octave below the chord at the default ``octave_offset=-1`` — so a bass line over a mix of plain and slash chords doesn't jump up an octave on the slash ones. .. py:method:: intervals() -> List[int] Decorated semitone offsets from the root. .. py:method:: name() -> str The decorated chord name (``Am9``, ``C/G``). .. py:method:: root_note(root_midi: int) -> int The MIDI note of the (undecorated) chord root nearest *root_midi*. .. py:method:: tones(root: int = 60, inversion: int = 0, count: Optional[int] = None) -> List[int] Decorated voicing nearest *root*; an explicit *inversion* overrides the span's. .. py:property:: base :type: Any The undecorated chord (the engine's currency). .. py:property:: quality :type: str The quality of the underlying chord. .. py:property:: root_pc :type: int The root pitch class of the underlying chord. .. py:property:: span :type: ChordSpan The wrapped span (decoration and all). .. py:class:: PitchSet(pitches: Iterable[int]) A nameless sonority — a frozen set of absolute MIDI pitches. The escape hatch for chords with no root or quality: clusters, spectral stacks, found objects. It duck-types ``.tones()`` so every placement verb and the injected ``chord`` accept it unchanged. By design it is excluded from generation and diatonic spice (there is nothing to transpose diatonically), and a progression containing one loops on exhaustion rather than falling through to live graph stepping. Pitches are absolute: ``tones()`` ignores its ``root`` argument — you chose the register when you chose the pitches. Normalise any iterable of MIDI pitches into a sorted frozen tuple. .. py:method:: intervals() -> List[int] Semitone offsets from the lowest pitch (the ``Chord`` protocol). .. py:method:: name() -> str A readable label for describe() output. .. py:method:: tones(root: int = 60, inversion: int = 0, count: Optional[int] = None) -> List[int] Return the pitches (absolute — *root* is ignored by design). ``inversion`` rotates pitches up an octave; ``count`` cycles the set into higher octaves, matching the ``Chord.tones`` contract. .. py:class:: Progression A frozen sequence of :class:`ChordSpan` — the governing harmony value. Always a realised value: binding it to the clock freezes one realisation; ``p.progression()`` keeps its breathing behaviour by re-realising a fresh one each rebuild. Iterating yields ``(chord, start, length)`` :class:`ChordEvent` tuples (the old ``ChordTimeline`` contract), so placement loops keep working unchanged. The governing family supports ``+`` (concatenate) and ``*`` (tile) but never ``&`` — there is one current chord (P1, the type law). .. attribute:: spans The chord spans, in order. .. attribute:: trailing_history Engine continuity metadata set by :meth:`Composition.freeze` — the NIR history at capture time, restored on each frozen replay. Empty for hand-built values. .. py:method:: borrow(slot: Union[int, List[int]]) -> Progression Borrow the chord(s) at the given 1-based slot(s) from the parallel scale. Modal interchange for key-relative content: the degree re-resolves against the parallel mode (minor under a major scale and vice versa). Concrete chords raise — there is nothing relative to borrow. .. py:method:: cadence(name: str = 'strong') -> Progression Substitute a cadence formula into the tail — the close, named. The final spans take the formula's chords (``"strong"`` is V→I, ``"soft"`` IV→I, ``"open"`` IV→V, ``"fakeout"`` V→vi; theory names — authentic, plagal, half, deceptive — work as aliases). Each replaced span keeps its beats; its old chord and decorations go. Formula chords are key-relative (ints follow the bound scale's qualities, ``"V"`` is the major dominant by convention), so the tail resolves wherever the progression is bound — a concrete progression becomes mixed and resolves its tail at bind time, like any roman content. Example:: verse = subsequence.progression(["Am", "F", "C", "G"]).cadence("open") # Am F F E — the half close, hanging on the dominant :raises ValueError: If the cadence name is unknown, or the progression has fewer spans than the formula. .. py:method:: describe(key: Optional[Union[str, int]] = None, scale: str = 'ionian') -> str A readable, one-chord-per-line summary. Key-relative spans print as written (romans/degrees) when unbound, and as concrete chord names under a *key*. .. py:method:: elaborate(depth: int = 1, seed: Optional[int] = None) -> Progression Steedman-inspired chord elaboration — approach each chord by fifths. Implements the heart of Mark Steedman's generative grammar for jazz/blues chord sequences: every chord is **approached** by a chain of secondary dominants propagated backward around the cycle of fifths (Rule 3, "the perfect cadence propagated backward"), carved out of that chord's own span (Rule 1, metric subdivision). ``depth`` is literally how many fifth-steps back the chain extends: - ``depth=0`` — identity (the bare progression). - ``depth=1`` — a secondary dominant before each chord: ``[X]`` → ``[V7/X, X]`` (e.g. a bar of C becomes G7 C). - ``depth=2`` — a secondary ii–V: ``[ii/X, V7/X, X]`` (Dm7 G7 C). - ``depth≥3`` — the chain extends (…V7/V7/X), the furthest-back chord is made minor — the ``ii`` of *its own local dominant* (the next link in the chain), forming a ii–V into that link, not the target's own ii — and dominants are recoloured by **tritone substitution** with even odds (Rule 4) for chromatic descents. This tritone choice is the only nondeterministic part, so ``seed`` is taken (or warned) at depth ≥ 3. Its flagship is the 12-bar blues with depth-per-chorus — elaborate a ``"twelve_bar_blues"`` more each chorus and the ii–V turnarounds and tritone subs accumulate. The progression must be **concrete** (resolved to rooted chords); the inserted dominants are computed by pitch-class arithmetic. Each chord keeps its decorations on the final (resolved) sub-span; the inserted approach chords are bare dominant/minor sevenths. Note that each span is divided into ``depth + 1`` equal sub-spans, so deep elaboration of a short harmonic rhythm can drop sub-spans below the harmony clock's lookahead floor — which raises at ``play()``/ ``render()`` if the result is bound to the global clock (it is free of that floor at the part level, ``p.progression()``). :param depth: Elaboration depth (≥ 0). :param seed: Seed for the depth-≥3 tritone-substitution choices. :returns: A new :class:`Progression` with the approach chords inserted. :raises ValueError: If *depth* is negative, the progression is key-relative, or any span is a rootless :class:`PitchSet`. .. rubric:: Example .. code-block:: python blues = subsequence.progression("twelve_bar_blues").resolve("C") chorus2 = blues.elaborate(2, seed=4) # ii–V turnarounds throughout .. py:method:: events() -> Tuple[ChordEvent, Ellipsis] The realised timeline as a tuple (iteration, materialised). .. py:method:: extend(*extensions: Any, only: Optional[List[int]] = None) -> Progression Add chord extensions (``7``/``9``/``11``/``13``/``"sus4"``/...) to every span. ``only=`` restricts the spice to the given 1-based chord slots. .. py:method:: generate(style: Union[str, Any] = 'functional_major', bars: int = 8, beats: Union[float, List[float]] = DEFAULT_SPAN_BEATS, *, key: Optional[str] = None, scale: Optional[str] = None, seed: Optional[int] = None, rng: Optional[random.Random] = None, pins: Optional[Dict[int, Any]] = None, end: Optional[Any] = None, avoid: Optional[Sequence[Any]] = None, cadence: Optional[str] = None, 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) -> Progression :classmethod: Generate a progression from a chord-graph walk — the hybrid generator. Full parameter pass-through to the engine (no more throwaway default engines), plus the hybrid constraints: ``pins`` fix chords at 1-based bars, ``end`` fixes the last bar, ``avoid`` excludes chords everywhere. Constraints compile into the walk — a backward feasibility pass guarantees satisfiability before any chord is drawn (unsatisfiable constraints raise immediately), then a forward walk samples through the engine's real history-dependent weights (NIR, gravity, diversity keep their character). **Without** ``key=`` the result is key-relative — the walk runs against a reference tonic and the spans store scale-proof major-relative romans, so the value prints meaningfully unbound and resolves wherever it is bound (the walk itself is key-invariant). **With** ``key=`` the result is concrete. :param style: A chord-graph style name (or ``ChordGraph`` instance). :param bars: How many chords to generate. :param beats: Span length per chord — a scalar, or a list cycled. :param key: Key for a concrete result; omit for a key-relative value. :param scale: Scale for int constraints' quality inference (e.g. ``end=1``). Defaults from the style (aeolian_minor → minor); explicit strings (``"V"``, ``"bVII7"``) never need it. :param seed: Seed for the walk. A standalone generated value without a seed warns — module-level nondeterminism breaks live reload. :param rng: An explicit random stream (overrides ``seed``). :param pins: ``{bar: chord}`` — 1-based; values parse like progression elements (ints, romans, names, ``Chord``). :param end: The chord at the final bar — ``end="V"`` is the cadential major dominant in minor (a string because it is chromatic; no int can ask for it). :param avoid: Chords excluded from the walk. Naming a chord outside the style's vocabulary is allowed (trivially satisfied). :param cadence: A cadence name (``"strong"``/``"soft"``/``"open"``/ ``"fakeout"``, theory aliases accepted) — its formula becomes pins on the final bars, so the walk *approaches* the close. Conflicts with ``end=`` or pins on those bars. :param dominant_7th / gravity / nir_strength / minor_turnaround_weight /: root_diversity: The engine parameters, exactly as :meth:`Composition.harmony` takes them. .. rubric:: Example .. code-block:: python chorus = subsequence.Progression.generate( style="aeolian_minor", bars=4, end="V", seed=7, ) print(chorus) # romans until bound .. py:method:: inversions(spec: Union[int, List[int]]) -> Progression Set chord inversions — a single int for all spans, or a list cycled per span. .. py:method:: over(bass: Union[int, str], only: Optional[List[int]] = None) -> Progression Put the progression over a slash/pedal bass — *the* trance/techno move. *bass* is a pitch class int, a note name (``"G"``), or ``"tonic"``. A note name is key-independent, so it resolves to its pitch class right here; ``"tonic"`` follows the key and stays relative until the progression is resolved. ``only=`` restricts it to the given 1-based slots (slash chords rather than a full pedal). .. py:method:: replace(slot: int, chord: Any) -> Progression Replace the chord at a 1-based slot (the span keeps its beats). .. py:method:: resolve(key: Union[str, int], scale: str = 'ionian') -> Progression Resolve every key-relative span against a key (name or pitch class). .. py:method:: span_at(beat: float) -> Tuple[ChordSpan, float, float] Return ``(span, start, end)`` for the span sounding at *beat*. *beat* wraps modulo the progression length, so the lookup also serves looped playback. .. py:method:: spread(style: str) -> Progression Set the voicing spread: ``"close"``, ``"open"`` (drop-2), or ``"wide"``. .. py:method:: with_rhythm(beats: Union[float, List[float]]) -> Progression Reshape the harmonic rhythm — a scalar for all spans, or a list cycled per span. .. py:property:: chords :type: Tuple[Any, Ellipsis] The bare chords, one per span (concrete progressions only). .. py:property:: is_concrete :type: bool True when every span is key-independent (no romans/degrees). .. py:property:: length :type: float Total length in beats (the sum of span lengths). .. py:property:: loops_on_exhaustion :type: bool True when the clock must loop rather than fall through to live stepping. .. py:class:: RomanChord A key-relative chord — a scale degree with optional explicit quality. Internal: users only ever meet it as an int or roman string element inside a progression list. It stays relative inside the value and resolves to a concrete :class:`~subsequence.chords.Chord` at query time against a key and scale. .. attribute:: degree 1-based scale degree. .. attribute:: accidental -1 for a ``b`` prefix, +1 for ``#``. An accidental- prefixed degree reads against the **major** scale, the universal roman convention — ``bVII`` is always the whole step below the tonic (Bb in C major, G in A minor); unprefixed degrees read the current scale (``VII`` in A minor is already G). .. attribute:: quality Explicit quality name, or ``None`` to infer diatonically from the key and scale (the bare-int path). .. attribute:: of Secondary-function target degree (``V/x`` — one level only). The numeral resolves against the major scale on the target's root, the common-practice reading. .. attribute:: borrowed When True, the degree resolves against the parallel scale (modal interchange) — set by :meth:`Progression.borrow`. .. attribute:: source_text The element as written, for unbound ``describe()``. .. attribute:: major_relative When True, the degree always reads the major scale (with the accidental applied), whatever scale ``resolve()`` is given — the scale-proof spelling :meth:`Progression.generate` emits, where quality is always explicit and the resolve scale must not re-interpret the root. .. py:method:: diatonic_extension_intervals(key_pc: int, scale: str, extensions: Tuple[Any, Ellipsis]) -> Tuple[int, Ellipsis] Stack diatonic thirds above the triad for numeric extensions. Only meaningful for inferred-quality degrees (the bare-int path): ``extend(7)`` on V in C major yields F natural (a dominant seventh), where the colour rule on a concrete G chord would yield F#. .. py:method:: label() -> str The element as written (for unbound describe() output). .. py:method:: resolve(key_pc: int, scale: str = 'ionian') -> subsequence.chords.Chord Resolve to a concrete chord against a key and scale. :raises ValueError: If the scale is unknown, the degree exceeds the scale, or quality inference is needed but the scale has no chord qualities registered. .. py:function:: cadence_pins(name: str, bars: int, pins: Optional[Dict[int, Any]], end: Optional[Any]) -> Dict[int, Any] Compile a cadence name into pins on the final bars of a walk. The shared translation for ``Progression.generate(cadence=)`` and ``Composition.freeze(cadence=)``: the formula occupies the last bars, merged with the caller's own pins. Conflicts raise loudly — ``end=`` and a pin on a formula bar both name what the cadence already fixes. .. py:function:: parse_element(element: Any, beats: float = DEFAULT_SPAN_BEATS) -> ChordSpan Parse one progression-list element into a :class:`ChordSpan`. Elements mix freely and are parsed per element (decision 16): ints are diatonic degrees (1-based, quality inferred from key+scale at query time); strings are chord names where they start with a note letter (``"Am"``) and romans otherwise (``"VI"``, ``"bVII7"``); ``Chord``, ``PitchSet``, and ``ChordSpan`` values pass through; an ``(element, beats)`` tuple sets the span length. .. py:function:: parse_roman(text: str) -> Tuple[RomanChord, int] Parse a roman numeral element into a (RomanChord, inversion) pair. The ~music21 semantics grammar: case is quality (``IV`` major, ``iv`` minor), ``°``/``o``/``dim`` diminished, ``ø`` half-diminished, ``+``/ ``aug`` augmented; ``maj7`` forces the major seventh; figured-bass suffixes give sevenths and inversions (``7``/``65``/``43``/``42``; ``6``/``64`` for triads); ``b``/``#`` prefixes shift the degree; one level of ``/x`` secondary function (``V7/IV``). Raises ``ValueError`` for anything it can't read. .. py:function:: progression(source: Optional[Any] = None, beats: Union[float, List[float]] = DEFAULT_SPAN_BEATS, *, style: Optional[str] = None, bars: int = 8, key: Optional[str] = None, scale: Optional[str] = None, seed: Optional[int] = None, rng: Optional[random.Random] = None, pins: Optional[Dict[int, Any]] = None, end: Optional[Any] = None, avoid: Optional[Sequence[Any]] = None, cadence: Optional[str] = None, 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) -> Progression Build a :class:`Progression` — the lowercase factory. Dispatch by argument type: a **list** parses per element (ints where diatonic, name/roman strings where nominal/chromatic, ``(element, beats)`` tuples for per-chord durations); a bare **string** names a preset from the curated table; ``style=`` generates *bars* chords from a chord-graph walk (requires ``key=``). :param source: The element list, preset name, or an existing Progression (returned unchanged). :param beats: Span length per chord — a scalar, or a list cycled per chord (``beats=[4, 4, 2, 6]`` shapes the harmonic rhythm). :param style: A chord-graph style name to generate from (e.g. ``"aeolian_minor"``). :param bars: How many chords to generate (style mode only). :param key: Key for style generation. :param seed: Seed for style generation. A standalone generated value without a seed warns — module-level nondeterminism breaks live reload. :param rng: An explicit random stream (overrides ``seed``; used by engine-mediated calls). :param dominant_7th / gravity / nir_strength: Graph-walk parameters, matching :meth:`Composition.harmony` (style mode only; full pass-through arrives with ``Progression.generate``). .. rubric:: Example .. code-block:: python verse = subsequence.progression([1, 6, 3, 7]) # i–VI–III–VII in A minor blues = subsequence.progression(["I7"] * 4 + ["IV7", "IV7", "I7", "I7", "V7", "IV7", "I7", "I7"]) walk = subsequence.progression(style="aeolian_minor", key="A", bars=8, seed=3) .. py:function:: realize(source: ProgressionSource, harmonic_rhythm: HarmonicRhythmSpec, key: Optional[str], length: float, rng: random.Random, scale: str = 'ionian') -> Progression Lay a progression out across *length* beats and return a frozen value. Walks the chord source, giving each chord a harmonic-rhythm length, until the part is full. The final chord is trimmed so the timeline ends exactly on *length* and therefore loops cleanly. Voicing and articulation are not decided here — they belong to whatever places the chords (the verb you call in the loop, or :meth:`Composition.chords`). .. py:function:: resolve_constraint(spec: Any, key_pc: int, scale: str, what: str) -> subsequence.chords.Chord Parse one hybrid-constraint spec (pin/end/avoid) into a concrete chord. Specs follow the progression-element grammar: ints are diatonic degrees (quality inferred from *scale*), strings are chord names or romans, ``Chord`` objects pass through. ``PitchSet`` objects are rejected — generation needs rooted chords. .. py:function:: resolve_voices(voicing: VoicingSpec, rng: random.Random) -> int Resolve the voice count for one chord — a fixed int, or a ``(low, high)`` draw.