Subsystem Search

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

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,
    key_pull: float = 0.0,
    nir_strength: float = 0.5,
    minor_turnaround_weight: float = 0.0,
    root_diversity: float = subsequence.harmonic_state.DEFAULT_ROOT_DIVERSITY,
    **retired: typing.Any,
) -> 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, key pull, 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

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.

A degree, or a numeral that is its key's own, takes its seventh from the key - V becomes G7 in C and E7 in A minor, ii Dm7 - and a secondary V/V takes the seventh of the key it points to, D7 in C. Any other chord deepens in its own colour: II in C, or bVII, gets a major seventh.

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.Optional[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. None takes the bass away. only= restricts it to the given 1-based slots (slash chords rather than a full pedal).

Raises

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 - the one sharing the tonic and differing in its third, so a minor-third scale borrows from ionian and a major-third scale from aeolian. The borrowed degree takes the borrowed mode's own quality, whatever its numeral was written with, so vi in C comes back as G# - the parallel minor's sixth - and not as a G#m belonging to neither key.

Concrete chords raise - there is nothing relative to borrow.

Two kinds of numeral have no parallel to borrow from, and are left alone with a warning rather than silently doing nothing: an accidental degree (bVII), which is already chromatic and means the same thing in either mode, and a secondary one (V/V), which resolves against its own target's key.

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

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:

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

Returns

Raises

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

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

A numeric extension deepens 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 - so extend(9) on a plain C gives Cmaj9. The chord symbol "C9" is a dominant, as a chart means it; write "Cmaj9" for the major seventh. A degree, or a numeral that is its key's own, extended with extend(...) carries pre-computed scale-true intervals instead, so V gets its dominant seventh - E7 in A minor as G7 in C - and so does a secondary V/V (#3026).

Everything below an extension sounds with it: an 11th carries the 9, a 13th carries the 9 and the 11. Two jazz rules then shape it, on both paths - over a major third the natural 11 is left out of a 13th (G13 is G B D F A E), and a dominant 11th drops the third the 11 sits a semitone above (G11 is G D F A C). A minor chord keeps both (Dm11 is D F A C E G).

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,
    key_pull: float = 0.0,
    nir_strength: float = 0.5,
    minor_turnaround_weight: float = 0.0,
    root_diversity: float = subsequence.harmonic_state.DEFAULT_ROOT_DIVERSITY,
    **retired: typing.Any,
) -> 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=).

A chord name says what a chart says: extensions ("Cmaj9", "C7sus4", "Cadd9", "Cm6") and a bass after a slash ("C/E", "Am7/G"). Whatever a progression prints as a chord's label reads back as that chord.

Parameters

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)
chart = subsequence.progression(["Cmaj9", "Am7/G", "Dm9", "G7sus4"])