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:
NamedTupleOne 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/.lengthattribute 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-relativeRomanChord, or aPitchSet.
- bass[source]¶
Slash/pedal bass — a pitch class int, a note name, or
"tonic"(resolved against the key at query time).
- extension_intervals[source]¶
Pre-computed semitone offsets for the extensions, set by
Progression.resolve()for diatonic degrees.Nonemeans “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.
PitchSetspans return their absolute pitches (decoration other thancountdoes 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 untilresolve()pins it to a key. Note-name basses are resolved to a pitch class eagerly inProgression.over(), so they never linger here as strings.
- class subsequence.progressions.DecoratedChord(span: ChordSpan)[source]¶
Duck-types the
Chordvoicing protocol over a decorated span.What patterns and the injected
chordreceive 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), andname()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.
- root_note(root_midi: int) int[source]¶
The MIDI note of the (undecorated) chord root nearest root_midi.
- 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 injectedchordaccept 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 itsrootargument — you chose the register when you chose the pitches.Normalise any iterable of MIDI pitches into a sorted frozen tuple.
- 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)ChordEventtuples (the oldChordTimelinecontract), so placement loops keep working unchanged.The governing family supports
+(concatenate) and*(tile) but never&— there is one current chord (P1, the type law).- 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).
depthis 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 + 1equal sub-spans, so deep elaboration of a short harmonic rhythm can drop sub-spans below the harmony clock’s lookahead floor — which raises atplay()/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 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:
pinsfix chords at 1-based bars,endfixes the last bar,avoidexcludes 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). Withkey=the result is concrete.- Parameters:
style – A chord-graph style name (or
ChordGraphinstance).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 withend=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.
- 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
Chordat query time against a key and scale.- accidental[source]¶
-1 for a
bprefix, +1 for#. An accidental- prefixed degree reads against the major scale, the universal roman convention —bVIIis always the whole step below the tonic (Bb in C major, G in A minor); unprefixed degrees read the current scale (VIIin A minor is already G).
- quality[source]¶
Explicit quality name, or
Noneto 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().
- major_relative[source]¶
When True, the degree always reads the major scale (with the accidental applied), whatever scale
resolve()is given — the scale-proof spellingProgression.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#.
- 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=)andComposition.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, andChordSpanvalues 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 (
IVmajor,ivminor),°/o/dimdiminished,øhalf-diminished,+/augaugmented;maj7forces the major seventh; figured-bass suffixes give sevenths and inversions (7/65/43/42;6/64for triads);b/#prefixes shift the degree; one level of/xsecondary function (V7/IV).Raises
ValueErrorfor 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 (requireskey=).- 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 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)
- 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,
Chordobjects pass through.PitchSetobjects 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.