subsequence.progressions
The API reference for Progression, ChordSpan, PitchSet, and progression.
Progression
class Progression(
spans: typing.Tuple[ChordSpan, ...],
trailing_history: typing.Tuple[subsequence.chords.Chord, ...] = (),
)
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).
Attributes
spans: The chord spans, in order.trailing_history: Engine continuity metadata set byComposition.freeze— the NIR history at capture time, restored on each frozen replay. Empty for hand-built values.
Members: borrow, cadence, chords, describe, elaborate, events, extend, generate, inversions, is_concrete, length, loops_on_exhaustion, over, replace, resolve, span_at, spans, spread, trailing_history, with_rhythm
Progression.spans
Progression.spans: typing.Tuple[ChordSpan, ...]
Progression.trailing_history
Progression.trailing_history: typing.Tuple[subsequence.chords.Chord, ...] = ()
Progression.length
property Progression.length: float
Total length in beats (the sum of span lengths).
Progression.is_concrete
property Progression.is_concrete: bool
True when every span is key-independent (no romans/degrees).
Progression.chords
property Progression.chords: typing.Tuple[typing.Any, ...]
The bare chords, one per span (concrete progressions only).
Progression.loops_on_exhaustion
property Progression.loops_on_exhaustion: bool
True when the clock must loop rather than fall through to live stepping.
Progression.events
Progression.events() -> typing.Tuple[ChordEvent, ...]
The realised timeline as a tuple (iteration, materialised).
Progression.span_at
Progression.span_at(
beat: float,
) -> typing.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.
Progression.resolve
Progression.resolve(
key: typing.Union[str, int],
scale: str = 'ionian',
) -> Progression
Resolve every key-relative span against a key (name or pitch class).
Progression.generate
classmethod Progression.generate(
style: typing.Union[str, typing.Any] = 'functional_major',
bars: int = 8,
beats: typing.Union[float, typing.List[float]] = DEFAULT_SPAN_BEATS,
*,
key: typing.Optional[str] = None,
scale: typing.Optional[str] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
pins: typing.Optional[typing.Dict[int, typing.Any]] = None,
end: typing.Optional[typing.Any] = None,
avoid: typing.Optional[typing.Sequence[typing.Any]] = None,
cadence: typing.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
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 (orChordGraphinstance).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 (overridesseed).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 withend=or pins on those bars.
Example
chorus = subsequence.Progression.generate(
style="aeolian_minor", bars=4, end="V", seed=7,
)
print(chorus) # romans until bound
Progression.extend
Progression.extend(
*extensions: typing.Any,
only: typing.Optional[typing.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.
Progression.inversions
Progression.inversions(
spec: typing.Union[int, typing.List[int]],
) -> Progression
Set chord inversions — a single int for all spans, or a list cycled per span.
Progression.spread
Progression.spread(style: str) -> Progression
Set the voicing spread: "close", "open" (drop-2), or "wide".
Progression.over
Progression.over(
bass: typing.Union[int, str],
only: typing.Optional[typing.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).
Progression.borrow
Progression.borrow(
slot: typing.Union[int, typing.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.
Progression.replace
Progression.replace(slot: int, chord: typing.Any) -> Progression
Replace the chord at a 1-based slot (the span keeps its beats).
Progression.cadence
Progression.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")
# Bound in A minor: Am F Dm 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.
Progression.with_rhythm
Progression.with_rhythm(
beats: typing.Union[float, typing.List[float]],
) -> Progression
Reshape the harmonic rhythm — a scalar for all spans, or a list cycled per span.
Progression.elaborate
Progression.elaborate(
depth: int = 1,
seed: typing.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 — theiiof 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, soseedis 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
Progressionwith the approach chords inserted.
Raises
ValueError: If depth is negative, the progression is key-relative, or any span is a rootlessPitchSet.
Example
blues = subsequence.progression("twelve_bar_blues").resolve("C")
chorus2 = blues.elaborate(2, seed=4) # ii–V turnarounds throughout
Progression.describe
Progression.describe(
key: typing.Optional[typing.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.
ChordSpan
class ChordSpan(
chord: typing.Any,
beats: float,
extensions: typing.Tuple[typing.Any, ...] = (),
bass: typing.Optional[typing.Union[int, str]] = None,
inversion: int = 0,
spread: typing.Optional[str] = None,
extension_intervals: typing.Optional[typing.Tuple[int, ...]] = None,
)
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.
Attributes
chord: A concreteChord, a key-relativeRomanChord, or aPitchSet.beats: Span length in beats.extensions: Extension markers — ints (7,9,11,13) or names ("sus2","sus4","add9","6").bass: Slash/pedal bass — a pitch class int, a note name, or"tonic"(resolved against the key at query time).inversion: Chord inversion for the voicing (0 = root position).spread: Voicing spread —"close"(default),"open"(drop-2), or"wide"(drop-2-and-4).extension_intervals: Pre-computed semitone offsets for the extensions, set byProgression.resolvefor diatonic degrees.Nonemeans "derive from the chord's own colour".
Members: bass, beats, chord, decorated_intervals, extension_intervals, extensions, inversion, is_concrete, is_decorated, label, resolve, spread, tones
ChordSpan.chord
ChordSpan.chord: typing.Any
ChordSpan.beats
ChordSpan.beats: float
ChordSpan.extensions
ChordSpan.extensions: typing.Tuple[typing.Any, ...] = ()
ChordSpan.bass
ChordSpan.bass: typing.Optional[typing.Union[int, str]] = None
ChordSpan.inversion
ChordSpan.inversion: int = 0
ChordSpan.spread
ChordSpan.spread: typing.Optional[str] = None
ChordSpan.extension_intervals
ChordSpan.extension_intervals: typing.Optional[typing.Tuple[int, ...]] = None
ChordSpan.is_concrete
property ChordSpan.is_concrete: 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 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.
ChordSpan.is_decorated
property ChordSpan.is_decorated: bool
True when the span carries any decoration beyond the bare chord.
ChordSpan.resolve
ChordSpan.resolve(key_pc: int, scale: str = 'ionian') -> ChordSpan
Return a concrete span: romans resolved, bass resolved to a pitch class.
ChordSpan.label
ChordSpan.label(
key_pc: typing.Optional[int] = None,
scale: str = 'ionian',
) -> str
A printable chord label: roman text when relative, decorated name when concrete.
ChordSpan.decorated_intervals
ChordSpan.decorated_intervals() -> typing.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.
ChordSpan.tones
ChordSpan.tones(
root: int = 60,
count: typing.Optional[int] = None,
) -> typing.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).
PitchSet
class PitchSet(pitches: typing.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.
Members: intervals, name, pitches, tones
PitchSet.pitches
PitchSet.pitches: typing.Tuple[int, ...]
PitchSet.tones
PitchSet.tones(
root: int = 60,
inversion: int = 0,
count: typing.Optional[int] = None,
) -> typing.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.
PitchSet.intervals
PitchSet.intervals() -> typing.List[int]
Semitone offsets from the lowest pitch (the Chord protocol).
PitchSet.name
PitchSet.name() -> str
A readable label for describe() output.
progression
progression(
source: typing.Optional[typing.Any] = None,
beats: typing.Union[float, typing.List[float]] = DEFAULT_SPAN_BEATS,
*,
style: typing.Optional[str] = None,
bars: int = 8,
key: typing.Optional[str] = None,
scale: typing.Optional[str] = None,
seed: typing.Optional[int] = None,
rng: typing.Optional[random.Random] = None,
pins: typing.Optional[typing.Dict[int, typing.Any]] = None,
end: typing.Optional[typing.Any] = None,
avoid: typing.Optional[typing.Sequence[typing.Any]] = None,
cadence: typing.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 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 (overridesseed; used by engine-mediated calls).dominant_7th / gravity / nir_strength: Graph-walk parameters, matchingComposition.harmony(style mode only; full pass-through arrives withProgression.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)