Subsystem

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,
    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

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

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

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

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)