subsequence.progressions

Progressions — chord sequences laid out in time, as a governing value.

The one progression type: a frozen tuple of 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

class subsequence.progressions.ChordEvent[source]

Bases: 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.

class subsequence.progressions.ChordSpan[source]

One chord with a duration and its decoration — the unit of harmonic time.

Decoration (extensions, slash bass, inversion, spread) lives HERE, never on Chord: the engine’s graph identity stays the bare triad, and the decorated voicing is what patterns hear.

chord[source]

A concrete Chord, a key-relative RomanChord, or a PitchSet.

beats[source]

Span length in beats.

extensions[source]

Extension markers — ints (7, 9, 11, 13) or names ("sus2", "sus4", "add9", "6").

bass[source]

Slash/pedal bass — a pitch class int, a note name, or "tonic" (resolved against the key at query time).

inversion[source]

Chord inversion for the voicing (0 = root position).

spread[source]

Voicing spread — "close" (default), "open" (drop-2), or "wide" (drop-2-and-4).

extension_intervals[source]

Pre-computed semitone offsets for the extensions, set by Progression.resolve() for diatonic degrees. None means “derive from the chord’s own colour”.

decorated_intervals() List[int][source]

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.

label(key_pc: int | None = None, scale: str = 'ionian') str[source]

A printable chord label: roman text when relative, decorated name when concrete.

resolve(key_pc: int, scale: str = 'ionian') ChordSpan[source]

Return a concrete span: romans resolved, bass resolved to a pitch class.

tones(root: int = 60, count: int | None = None) List[int][source]

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).

property is_concrete: bool[source]

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 resolve() pins it to a key. Note-name basses are resolved to a pitch class eagerly in Progression.over(), so they never linger here as strings.

property is_decorated: bool[source]

True when the span carries any decoration beyond the bare chord.

class subsequence.progressions.DecoratedChord(span: ChordSpan)[source]

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 (base).

Wrap a concrete, decorated span.

bass_note(root_midi: int, octave_offset: int = -1) int[source]

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.

intervals() List[int][source]

Decorated semitone offsets from the root.

name() str[source]

The decorated chord name (Am9, C/G).

root_note(root_midi: int) int[source]

The MIDI note of the (undecorated) chord root nearest root_midi.

tones(root: int = 60, inversion: int = 0, count: int | None = None) List[int][source]

Decorated voicing nearest root; an explicit inversion overrides the span’s.

property base: Any[source]

The undecorated chord (the engine’s currency).

property quality: str[source]

The quality of the underlying chord.

property root_pc: int[source]

The root pitch class of the underlying chord.

property span: ChordSpan[source]

The wrapped span (decoration and all).

class subsequence.progressions.PitchSet(pitches: Iterable[int])[source]

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.

intervals() List[int][source]

Semitone offsets from the lowest pitch (the Chord protocol).

name() str[source]

A readable label for describe() output.

tones(root: int = 60, inversion: int = 0, count: int | None = None) List[int][source]

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.

class subsequence.progressions.Progression[source]

A frozen sequence of 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) 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).

spans[source]

The chord spans, in order.

trailing_history[source]

Engine continuity metadata set by Composition.freeze() — the NIR history at capture time, restored on each frozen replay. Empty for hand-built values.

borrow(slot: int | List[int]) Progression[source]

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.

cadence(name: str = 'strong') Progression[source]

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.

describe(key: str | int | None = None, scale: str = 'ionian') str[source]

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.

elaborate(depth: int = 1, seed: int | None = None) Progression[source]

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()).

Parameters:
  • depth – Elaboration depth (≥ 0).

  • seed – Seed for the depth-≥3 tritone-substitution choices.

Returns:

A new Progression with the approach chords inserted.

Raises:

ValueError – If depth is negative, the progression is key-relative, or any span is a rootless PitchSet.

Example

blues = subsequence.progression("twelve_bar_blues").resolve("C")
chorus2 = blues.elaborate(2, seed=4)      # ii–V turnarounds throughout
events() Tuple[ChordEvent, Ellipsis][source]

The realised timeline as a tuple (iteration, materialised).

extend(*extensions: Any, only: List[int] | None = None) Progression[source]

Add chord extensions (7/9/11/13/"sus4"/…) to every span.

only= restricts the spice to the given 1-based chord slots.

classmethod generate(style: str | Any = 'functional_major', bars: int = 8, beats: float | List[float] = DEFAULT_SPAN_BEATS, *, key: str | None = None, scale: str | None = None, seed: int | None = None, rng: random.Random | None = None, pins: Dict[int, Any] | None = None, end: Any | None = None, avoid: Sequence[Any] | None = None, cadence: str | None = 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[source]

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.

Parameters:
  • style – A chord-graph style name (or ChordGraph instance).

  • bars – How many chords to generate.

  • beats – Span length per chord — a scalar, or a list cycled.

  • key – Key for a concrete result; omit for a key-relative value.

  • 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.

  • seed – Seed for the walk. A standalone generated value without a seed warns — module-level nondeterminism breaks live reload.

  • rng – An explicit random stream (overrides seed).

  • pins{bar: chord} — 1-based; values parse like progression elements (ints, romans, names, Chord).

  • 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).

  • avoid – Chords excluded from the walk. Naming a chord outside the style’s vocabulary is allowed (trivially satisfied).

  • 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.

  • / (dominant_7th / gravity / nir_strength / minor_turnaround_weight) – root_diversity: The engine parameters, exactly as Composition.harmony() takes them.

Example

chorus = subsequence.Progression.generate(
        style="aeolian_minor", bars=4, end="V", seed=7,
)
print(chorus)        # romans until bound
inversions(spec: int | List[int]) Progression[source]

Set chord inversions — a single int for all spans, or a list cycled per span.

over(bass: int | str, only: List[int] | None = None) Progression[source]

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).

replace(slot: int, chord: Any) Progression[source]

Replace the chord at a 1-based slot (the span keeps its beats).

resolve(key: str | int, scale: str = 'ionian') Progression[source]

Resolve every key-relative span against a key (name or pitch class).

span_at(beat: float) Tuple[ChordSpan, float, float][source]

Return (span, start, end) for the span sounding at beat.

beat wraps modulo the progression length, so the lookup also serves looped playback.

spread(style: str) Progression[source]

Set the voicing spread: "close", "open" (drop-2), or "wide".

with_rhythm(beats: float | List[float]) Progression[source]

Reshape the harmonic rhythm — a scalar for all spans, or a list cycled per span.

property chords: Tuple[Any, Ellipsis][source]

The bare chords, one per span (concrete progressions only).

property is_concrete: bool[source]

True when every span is key-independent (no romans/degrees).

property length: float[source]

Total length in beats (the sum of span lengths).

property loops_on_exhaustion: bool[source]

True when the clock must loop rather than fall through to live stepping.

class subsequence.progressions.RomanChord[source]

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 Chord at query time against a key and scale.

degree[source]

1-based scale degree.

accidental[source]

-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).

quality[source]

Explicit quality name, or None to infer diatonically from the key and scale (the bare-int path).

of[source]

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.

borrowed[source]

When True, the degree resolves against the parallel scale (modal interchange) — set by Progression.borrow().

source_text[source]

The element as written, for unbound describe().

major_relative[source]

When True, the degree always reads the major scale (with the accidental applied), whatever scale resolve() is given — the scale-proof spelling Progression.generate() emits, where quality is always explicit and the resolve scale must not re-interpret the root.

diatonic_extension_intervals(key_pc: int, scale: str, extensions: Tuple[Any, Ellipsis]) Tuple[int, Ellipsis][source]

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#.

label() str[source]

The element as written (for unbound describe() output).

resolve(key_pc: int, scale: str = 'ionian') subsequence.chords.Chord[source]

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.

subsequence.progressions.cadence_pins(name: str, bars: int, pins: Dict[int, Any] | None, end: Any | None) Dict[int, Any][source]

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.

subsequence.progressions.parse_element(element: Any, beats: float = DEFAULT_SPAN_BEATS) ChordSpan[source]

Parse one progression-list element into a 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.

subsequence.progressions.parse_roman(text: str) Tuple[RomanChord, int][source]

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.

subsequence.progressions.progression(source: Any | None = None, beats: float | List[float] = DEFAULT_SPAN_BEATS, *, style: str | None = None, bars: int = 8, key: str | None = None, scale: str | None = None, seed: int | None = None, rng: random.Random | None = None, pins: Dict[int, Any] | None = None, end: Any | None = None, avoid: Sequence[Any] | None = None, cadence: str | None = 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[source]

Build a 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=).

Parameters:
  • source – The element list, preset name, or an existing Progression (returned unchanged).

  • beats – Span length per chord — a scalar, or a list cycled per chord (beats=[4, 4, 2, 6] shapes the harmonic rhythm).

  • style – A chord-graph style name to generate from (e.g. "aeolian_minor").

  • bars – How many chords to generate (style mode only).

  • key – Key for style generation.

  • seed – Seed for style generation. A standalone generated value without a seed warns — module-level nondeterminism breaks live reload.

  • rng – An explicit random stream (overrides seed; used by engine-mediated calls).

  • nir_strength (dominant_7th / gravity /) – Graph-walk parameters, matching Composition.harmony() (style mode only; full pass-through arrives with Progression.generate).

Example

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)
subsequence.progressions.realize(source: ProgressionSource, harmonic_rhythm: HarmonicRhythmSpec, key: str | None, length: float, rng: random.Random, scale: str = 'ionian') Progression[source]

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 Composition.chords()).

subsequence.progressions.resolve_constraint(spec: Any, key_pc: int, scale: str, what: str) subsequence.chords.Chord[source]

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.

subsequence.progressions.resolve_voices(voicing: VoicingSpec, rng: random.Random) int[source]

Resolve the voice count for one chord — a fixed int, or a (low, high) draw.