# Chapter 6 · Harmony as Context: Following the Current Chord [Chapter 5](05-pitch-and-scales) ended on a promise: a pitch is a *specification resolved late*, and the richest thing to resolve it against is not a key but a *living chord*. This chapter makes good on it — we turn on Subsequence's chord engine and write parts that follow wherever the harmony goes. So far the "context" a pitch lands in has been static: one key, one scale, set once at the top. Now it moves. The composition grows a harmony underneath your patterns — a chord that changes every few beats — and any pattern can ask "what chord is sounding *right now?*" and build its notes from the answer. Write one bassline, and it walks a different root under every chord. Write one pad, and it re-voices itself bar by bar. This is the first time harmony enters the guide, and it rests entirely on the idea you already have: **the same written part legitimately sounds different under different chords.** ```{testsetup} ch6 # Hidden per-chapter setup: the imports the first example shows in full, so later # blocks can use these names without repeating them. import subsequence import subsequence.constants.instruments.gm_drums as gm_drums import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") ``` (sec-harmony-on)= ## 6.1 Turning on the chord engine (`harmony()`) A fresh `Composition` has no harmony — `p.note(...)` places exactly the pitches you write and nothing reacts to anything. You switch on the chord engine with one call, **`composition.harmony(...)`**, after creating the composition: ```{testcode} ch6 import subsequence import subsequence.constants.instruments.gm_drums as gm_drums import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4) composition.render(bars=2, filename="harmony-on.mid") ``` That's the whole switch. From now on the composition runs a *harmonic clock* alongside the beat clock: every `cycle_beats` beats it steps to a new chord, chosen by the `style` you named. The chords don't make any sound on their own — the engine is a *generator of context*, not an instrument. Nothing is heard until a pattern reads the chord and plays something (that's [§6.2](#sec-receive)). The two arguments here are the ones you'll reach for first: ```{list-table} :header-rows: 1 :widths: 26 74 * - Argument - What it does * - `style="aeolian_minor"` - The *harmonic palette* — which chords are likely, and how they tend to move from one to the next. `"aeolian_minor"` is natural-minor harmony with a dark cadence option; the full list is below. * - `cycle_beats=4` - How many beats each chord lasts before the engine steps to the next one. `4` is one chord per bar in 4/4; `8` is one chord every two bars; `2` is two chords a bar. Defaults to `4`. ``` ```{important} **The chord engine needs a key.** `harmony()` builds its chords *relative* to the composition's key (the `key="E"` you set on the `Composition`), so calling it without a key raises a `ValueError` telling you to set one. A key is the home the whole harmony orbits — exactly the `key`/`scale` you met in [Chapter 5](05-pitch-and-scales), now doing double duty. ``` ### The built-in styles `style` names a *chord-transition palette* — think of it as choosing the genre of the harmony before you write a note. Each shifts which chords appear and how they lead into each other. These are the built-in names: ```{list-table} :header-rows: 1 :widths: 32 68 * - Style name - Character * - `"functional_major"` (alias `"diatonic_major"`) - Standard major-key harmony — I, ii, iii, IV, V, vi. The default if you call `harmony()` with no `style` at all. * - `"hooktheory_major"` (alias `"pop_major"`) - Major harmony weighted toward common pop progressions. * - `"aeolian_minor"` - Natural minor, with a Phrygian cadence option — dark, classic minor-key feel. * - `"phrygian_minor"` - A darker, minimal minor palette (i–♭II–iv–v). * - `"dorian_minor"` - Minor with a major IV — the soul/funk colour. * - `"lydian_major"` - Bright and floating, with the raised-fourth (♯IV) colour. * - `"mixolydian"` - Major with a flat 7th — open and unresolved (EDM, synthwave). * - `"chromatic_mediant"` - Film-score third-relation shifts between distant major/minor chords. * - `"suspended"` - An ambiguous sus2/sus4 palette — no decisive major or minor third. * - `"whole_tone"` - A symmetrical augmented palette; dreamlike drift (IDM, ambient). * - `"diminished"` - Minor-third symmetry; angular and disorienting (dark, experimental). * - `"turnaround"` - A jazz ii–V–I turnaround, optionally modulating to the relative minor. ``` ```{tip} Pick a `style` the same way you picked a `scale` in [Chapter 5](05-pitch-and-scales): as a quick mood setting before any detail. `"aeolian_minor"` for brooding, `"dorian_minor"` for groovy-minor, `"mixolydian"` for an unresolved EDM lift, `"functional_major"` when you just want it to sound "correct." An unknown name raises a `ValueError` listing the valid ones, so a typo fails loudly. ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.composition.Composition.harmony` ``` (sec-receive)= ## 6.2 Receiving the chord by parameter name (not a flag) Here is the heart of the chapter, and the one idea to get exactly right. A normal pattern function takes a single argument, the builder `p`: ```python @composition.pattern(channel=6, beats=4) def bass(p): ... ``` To make a pattern *follow the harmony*, you **declare a second parameter named `chord`** — and Subsequence injects the sounding chord into it on every cycle: ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4) @composition.pattern(channel=6, beats=4) def bass(p, chord): # ← the second parameter, named `chord` root = chord.root_note(notes.E2) # the chord's root, in the bass register p.note(root, beat=0, duration=4.0) composition.render(bars=4, filename="follow-chord.mid") ``` The name is the whole mechanism. Subsequence inspects your function's parameters; if it sees one called `chord`, it passes the current chord in. If it doesn't, your pattern simply never sees the harmony. There is no flag to set, no decorator argument to remember — **you opt in by asking for it by name.** ```{important} **The chord is injected by *declaring a parameter named* `chord` — there is no `chord=True`.** Earlier drafts of Subsequence used a `chord=True` flag on the decorator; that is retired and will do nothing now. The current, only form is to add `chord` to your function's parameter list: `def bass(p, chord):` ✓ receives the live chord `def bass(p):` ✓ valid, but never sees the harmony This mirrors how `p` itself arrives — you don't *request* the builder with a flag, you *name a parameter* and it's handed to you. The `chord` is the same: name it, and the engine fills it in fresh every cycle. ``` ### What `chord` actually is The `chord` you receive is a small read-only object that knows the sounding chord *and* the composition's key context, so the MIDI notes it hands back are already transposed correctly. You never construct it; you only ask it questions. Three methods cover almost everything: ```{list-table} :header-rows: 1 :widths: 40 60 * - Method - What it returns * - `chord.root_note(near)` - One MIDI note: the chord's root, in the octave nearest `near`. The single most useful call for a root-driven bass. * - `chord.bass_note(near, octave_offset=-1)` - The root shifted down by whole octaves (one by default) — a bass register below the chord voicing. * - `chord.tones(root=near, count=N)` - A list of MIDI notes: the chord's tones, voiced from the octave nearest `root`. `count=` cycles tones up into higher octaves so you can ask for as many voices as you like. ``` Every one of these takes a *reference* MIDI note (`near` / `root`) and finds the chord tone in the octave closest to it. You're saying "give me this chord, *around here*" — `notes.E2` for a bass, `notes.C4` for a mid pad. The chord supplies the *which note*; you supply the *register*. ```{note} **`chord` is the live counterpart of "a pitch resolved late."** In [Chapter 5](05-pitch-and-scales) a written degree resolved against a fixed key. Here `chord.root_note(notes.E2)` is a specification — "the root, near E2" — that resolves against *whatever chord is sounding this cycle*. Same call, different answer each time the harmony moves. The pattern function re-runs every cycle (the rebuild loop from [Chapter 2](02-rebuild-loop)), and each run gets the chord for that cycle, so the resolution happens fresh, on the beat. ``` ```{admonition} Reference :class: seealso {py:class}`~subsequence.chords.Chord`, {py:meth}`~subsequence.chords.Chord.root_note`, {py:meth}`~subsequence.chords.Chord.bass_note`, {py:meth}`~subsequence.chords.Chord.tones` ``` (sec-basslines)= ## 6.3 Basslines from the chord A bass that follows the harmony is the most immediate payoff, so let's build one onto the running drum loop. We keep the channel-10 GM drums exactly as they've been since [Chapter 0](00-setup), and add a bass on its own channel that plays the root of whatever chord is sounding: ```{testcode} ch6 import subsequence import subsequence.constants.instruments.gm_drums as gm_drums import subsequence.constants.midi_notes as notes DRUMS_CHANNEL = 10 BASS_CHANNEL = 6 composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4, gravity=0.8) @composition.pattern(channel=DRUMS_CHANNEL, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP) def drums(p): p.hit_steps("kick_1", [0, 4, 8, 12], velocity=100) p.hit_steps("snare_1", [4, 12], velocity=100) p.hit_steps("hi_hat_closed", range(16), velocity=80) @composition.pattern(channel=BASS_CHANNEL, beats=4) def bass(p, chord): root = chord.root_note(notes.E2) # the sounding chord's root, low p.sequence(steps=[0, 4, 8, 12], pitches=root) # a note on every beat p.legato(0.9) # hold each almost to the next composition.render(bars=8, filename="chord-bass.mid") ``` The drums know nothing about harmony — no `chord` parameter, so the engine never touches them. The bass declares `chord`, so every cycle it gets the current chord, takes its root near E2, and plays it four times. Across eight bars the engine walks through several chords, and the bass walks with it — a different root under each one, all of it in E minor because that's the key the harmony lives in. You can do more than thump the root. Because `chord.tones(...)` hands back a plain list of MIDI numbers — exactly like the `scale_notes` lists from [§5.3](05-pitch-and-scales) — you index into it to pick *which* chord tone to play. Here the bass alternates root and fifth, a classic walking feel, by reaching for elements `[0]` and `[2]` of the chord: ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4) @composition.pattern(channel=6, beats=4) def root_fifth_bass(p, chord): tones = chord.tones(root=notes.E2, count=3) # [root, third, fifth] near E2 p.note(tones[0], beat=0, duration=1.0) # root p.note(tones[2], beat=1, duration=1.0) # fifth p.note(tones[0], beat=2, duration=1.0) # root p.note(tones[2], beat=3, duration=1.0) # fifth composition.render(bars=4, filename="root-fifth-bass.mid") ``` `tones[0]` is the root, `tones[1]` the third, `tones[2]` the fifth — the chord's notes in order, just as `scale_notes(...)[0]` was the root of a scale. The difference is that this list is *rebuilt every cycle from the current chord*, so `tones[2]` is the fifth of an A-minor chord in one bar and the fifth of an F-major chord in the next, with no extra work from you. ```{tip} **`bass_note` is `root_note` an octave (or more) down.** For a bass that sits well below a mid-register pad, `chord.bass_note(notes.E3)` gives you the root one octave under E3 in a single call, instead of computing `root_note` and subtracting 12 yourself. Pass `octave_offset=-2` to drop it two octaves for a sub-bass. ``` (sec-pads-arps)= ## 6.4 Pads & arpeggios from chord tones A bass takes one note from the chord; a pad takes them all, and an arpeggio takes them all but one at a time. Both come straight off `chord.tones(...)`, but Subsequence gives you two verbs that take the *chord itself* and do the voicing for you — no need to pull the list out by hand. **`p.chord(...)`** plays a chord as a held block. Hand it the `chord` parameter, a `root` register, and how many voices you want: ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4) @composition.pattern(channel=2, beats=4) def pad(p, chord): # Four voices of the current chord, near E3, ringing almost the whole bar. p.chord(chord, root=notes.E3, count=4, velocity=70, detached=0.25) composition.render(bars=4, filename="chord-pad.mid") ``` `p.chord(chord, root=notes.E3, count=4)` voices four notes of the sounding chord from the octave nearest E3. `velocity=70` keeps a pad soft, and `detached=0.25` makes every voice release a quarter-beat before the next chord arrives — a tidy habit that stops one bar's pad from bleeding into the next chord's. The pad re-voices itself automatically: when the engine steps to a new chord next cycle, the rebuild runs again and `p.chord` lays down *that* chord's tones. **`p.arpeggio(...)`** takes the same chord and rolls its tones out one at a time instead of stacking them. It's a one-word swap from `p.chord`: ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="E", scale="minor") composition.harmony(style="aeolian_minor", cycle_beats=4) @composition.pattern(channel=3, beats=4) def arp(p, chord): # The same chord, but as a rising-then-falling sixteenth-note arpeggio. p.arpeggio(chord, root=notes.E4, count=4, spacing=0.25, velocity=85, direction="up_down") composition.render(bars=4, filename="chord-arp.mid") ``` The arguments echo `p.chord` — same `chord`, same `root`, same `count` — plus two that shape the roll: ```{list-table} :header-rows: 1 :widths: 28 72 * - Argument - What it does * - `spacing=0.25` - Beats between successive notes. `0.25` is a sixteenth note, so a four-voice arp covers a beat per pass and repeats across the bar. * - `direction="up_down"` - The order the tones cycle: `"up"` (low to high), `"down"`, `"up_down"` (ascend then descend, ping-pong), or `"random"`. ``` ```{note} **`p.arpeggio` also takes a plain list of pitches**, not only a chord. Pass it a list — say a fragment of `scale_notes(...)` — and it arpeggiates exactly those notes; in that case you drop `root`/`count` (there's no chord to voice). The chord form here is the harmony-following version: `p.arpeggio(chord, root=..., count=...)` re-voices off the live chord every cycle, just like `p.chord`. ``` ```{tip} `p.chord`, `p.arpeggio`, and `p.strum` all share the same `(chord, root=, count=, inversion=)` front end, so "play this chord as a block, a roll, or a strum" is a single verb swap with the same arguments. (A fourth verb, `p.broken_chord`, plays the tones in an order *you* spell out — it takes the same `chord`/`root` but an explicit `order=` list rather than a `count=`.) That's the "learn one verb, predict the rest" pattern from [§4.5](04-generators-euclidean) showing up again, this time for harmony. ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.pattern_builder.PatternBuilder.chord`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.arpeggio`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.strum`, {py:meth}`~subsequence.pattern_builder.PatternBuilder.broken_chord` ``` (sec-same-notes)= ## 6.5 "The same notes sound different under different chords" This is the idea the whole chapter is built to land, and it's worth seeing on the page. A musician knows it in their hands: hum "root, third, fifth" over a C chord and you get C–E–G; hum the *same* "root, third, fifth" over an A-minor chord and you get A–C–E. The instruction didn't change. The chord did. The sound followed the chord. Your `chord` parameter behaves exactly the same way. `chord.tones(root=notes.C4, count=3)` is one written instruction — "root, third, fifth, near C4" — and it produces different MIDI notes depending on which chord is asked. Here is that made literal: the *identical* line of code, run against two different chords, printing the pitches that come out. ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes # Two chords from the same key. (parse_chord turns a chord name into a chord # object with the same .tones() the engine injects — handy for showing the point.) for name in ("Am", "F"): chord = subsequence.parse_chord(name) voiced = chord.tones(root=notes.C4, count=3) # SAME instruction both times print(name, "->", voiced) ``` ```{testoutput} ch6 Am -> [57, 60, 64] F -> [65, 69, 72] ``` The written specification was byte-for-byte identical — `chord.tones(root=notes.C4, count=3)` both times. Under A minor it resolved to A–C–E (`57, 60, 64`); under F major it resolved to F–A–C (`65, 69, 72`). **Same spec, different sound** — because the tones were voiced against the chord *late*, at the moment the chord was known, not when you typed the call. This is precisely the structural-vs-sonic distinction from [§5.5](05-pitch-and-scales), now with the chord as the context instead of the key: ```{list-table} :header-rows: 1 :widths: 30 70 * - Kind of "same" - What it means here * - **Structural equality** - The two calls are written identically — same instruction, `chord.tones(root= notes.C4, count=3)`. They are structurally the same. * - **Sonic equality** - The two calls *sound the same*. They do **not** — A minor and F major resolve the instruction to different pitches. ``` That gap is the entire point of following the chord. You write *one* bass, *one* pad, *one* arpeggio — structurally a single part — and the moving harmony makes it sonically new under every chord, for free. The part that took ten lines to write plays a whole progression's worth of music. ```{note} **Under the hood: why the chord object knows the right octave.** Each chord carries a *root pitch class* (0–11, where C = 0) and a *quality* (major, minor, dominant 7th, …) — a pitch class, not a specific octave, exactly like a key. When you call `chord.tones(root=notes.C4)`, the object finds the copy of its root pitch class nearest your reference note, then stacks the quality's intervals on top. That's why the same chord gives `[48, 52, 55]` near C3 and `[60, 64, 67]` near C4 — it resolves the abstract chord into your chosen register on demand. The injected `chord` adds one thing the bare chord lacks: the composition's key context, so a key-relative engine chord transposes to the right absolute pitches. A chord, like a pitch, is a specification resolved late. ``` ```{admonition} Reference :class: seealso {py:func}`~subsequence.chords.parse_chord` ``` (sec-steering)= ## 6.6 Steering the engine (`gravity`, `cycle_beats`) Left to its defaults, the engine wanders the style's palette on its own. A few arguments to `harmony()` let you steer *how* it wanders — how restless it is, how fast it moves, how much it favours home. You've met `cycle_beats`; here are the rest you'll actually reach for: ```{list-table} :header-rows: 1 :widths: 24 76 * - Argument - What it does * - `cycle_beats=4` - Beats per chord. Lower it for busy, fast-changing harmony; raise it for long, static chords. `8` = one chord every two bars. * - `gravity=1.0` - **Key gravity**, `0.0`–`1.0`. High values pull the harmony back toward the home chord — it stays close to the tonic and resolves often. Low values let it roam further before coming home. Default `1.0`. * - `nir_strength=0.5` - **Melodic inertia (Narmour expectation)**, `0.0`–`1.0`. How strongly the engine prefers smooth, expected chord moves over surprising leaps — the same Narmour model scored on melody in [§12.8](12-deep-generative). Default `0.5`. * - `dominant_7th=True` - Whether the engine may use V7 (dominant-seventh) chords. `False` keeps the harmony plainer, on triads. Default `True`. * - `root_diversity=0.4` - **Root-repetition damping**, `0.0`–`1.0`. Lower values discourage landing on the same root twice in a row, pushing the harmony to keep moving; `1.0` disables the nudge entirely. Default `0.4`. ``` A worked example: a brooding, slow-moving minor harmony that rarely strays far from home — long chords (`cycle_beats=8`), strong pull to the tonic (`gravity=0.9`), and triads only (`dominant_7th=False`): ```{testcode} ch6 import subsequence import subsequence.constants.midi_notes as notes composition = subsequence.Composition(bpm=120, key="A", scale="minor") composition.harmony( style="aeolian_minor", cycle_beats=8, # one chord every two bars — slow, brooding gravity=0.9, # stays close to the home chord dominant_7th=False, # plain triads, no V7 ) @composition.pattern(channel=6, beats=8) def slow_bass(p, chord): p.note(chord.bass_note(notes.A3), beat=0, duration=4.0) p.note(chord.bass_note(notes.A3), beat=4, duration=4.0) composition.render(bars=8, filename="steered-harmony.mid") ``` Turn `gravity` down to `0.3` and the same engine roams much further from A minor before resolving; push `cycle_beats` to `2` and it changes chord twice a bar. These are mood dials — set them by ear, the way you'd set a filter cutoff, rather than calculating them. ```{tip} **You can re-call `harmony()` to change the steering live.** Calling it again with new arguments updates the engine on the fly — the chord currently sounding finishes out, and the new settings take over from the next change. That's how you'd open up the harmony (`gravity` down) for a bridge and pull it home again for the final chorus. ``` ### Asking the composition directly: `current_chord()` Patterns receive the chord automatically, but sometimes you want to *read* the sounding chord from outside a pattern — to print it, log it, or drive your own logic. **`composition.current_chord()`** returns the chord at the playhead (or `None` if you never called `harmony()`): ```{testcode} ch6 import subsequence # seed= makes the engine's chord walk reproducible (see §4.6), so this prints # the same chord every run — handy for a doctested example. composition = subsequence.Composition(bpm=120, key="C", scale="major", seed=42) composition.harmony(style="functional_major", cycle_beats=4) composition.render(bars=2, filename="read-chord.mid") print("Sounding now:", composition.current_chord().name()) ``` ```{testoutput} ch6 Sounding now: C ``` `.name()` prints a human-friendly label like `C`, `Am`, or `G7` — the same method the injected `chord` offers, useful for sanity-checking what the engine is doing. (Without a `seed=` the engine still walks the same *style*, but picks a different specific path each run — that's the point of a generative harmony.) ```{note} **A preview of voice leading.** The `pad` in [§6.4](#sec-pads-arps) re-voices off the current chord each bar, but by default it always voices in *root position* — which can make the top notes jump around as chords change. Registering the pattern with `@composition.pattern(channel=2, beats=4, voice_leading=True)` tells Subsequence to choose, each bar, the inversion of the chord that moves the fewest voices from the last one — smooth, connected part-writing instead of jumps. It's a single flag and it works today; the full treatment of voicing and voice leading is [Chapter 7](07-progressions). ``` ```{admonition} Reference :class: seealso {py:meth}`~subsequence.composition.Composition.current_chord` ``` --- You can now grow a living harmony under a piece, follow it from any pattern by declaring a `chord` parameter, and turn the sounding chord into a bassline, a pad, or an arpeggio — all resting on the one idea that one written part legitimately sounds different under different chords. So far the engine has *chosen* the chords for you. Next we take the wheel: in [Chapter 7](07-progressions) we build *progressions as values* — writing exact chord sequences, voicing and voice-leading them, and placing them with `p.progression` and `composition.chords`.