HARMONY LAB

CHANGES · THE PLATE LEARNS THE CHORD · D1–D10 ADOPTED 2026-09-20 · v1
You put the ii–V–I on the stand, hit play, and the metronome tells you where you are in the changes the way a good rhythm section does: the chord you’re on, big, where the section name sits today; the chord that’s coming, dimmer, warming through the last bar so the top never surprises you; the key you’re in, small and steady beside it; and a strip along the bottom showing the whole cycle — twelve keys around the fourths, or fifths, or wherever you told it to go — with the playhead crawling through it. Every guest phone sees the same chord at the same instant, and the horn players read it in their own key.

Nothing here is a new surface. The harmony plate is the approved section plate with a chord in the name slot; NEXT is the approach gutter this folder already ruled; the cycle strip is the form ribbon with key-aware spans. Every symbol is generated from a spelled key and a scale degree — nothing is typed — so G♭ by fourths and F♯ by fifths is a table row, not a special case. No chord audio in v1. The click still ignites once a bar on the downbeat, and that is the only light on the page.
THE SHEETHarmonyGenerator(template, order, harmonicRhythm, startKey) → [SongMapBar] · chips: ControlChips.swift:40-43
TEMPLATEii–V–I
ORDER4THS
STARTC
BEATS / CHORD4
MAP48 BARS
DRONEOFF 1.2 · ARM C
TEMPLATE
KEY ORDER
START KEY
BEATS / CHORD
SPELLING
D6 · D11 OPEN
TEMPO 120
NOTATION
D1 · cycles BOTH · tunes SYMBOLS · persists
GUEST VIEW
FACE · LAB
Silent unless you arm CLICK (iOS needs the tap). The click is a courtesy so you can feel the downbeat LIT and the chord swap land as one event — it is not a timing reference. SQUINT halves the render to approximate 2 m. NOTATION persists per map class — on a clean load cycles open in BOTH, tunes in SYMBOLS (D1).
HOSTCONCERT · CLASSIC FACEplate / NEXT gutter / cycle strip — one clock, LIT once a bar
METRONOME
PERFORMANCE · CHANGES
120
BPM
The KEY CENTRE sits in the caption row, small and steady, so the 106-pt plate never grows (verifier §3.2). The filament fills across the key span — ruled: no per-bar reset.
GUEST · CONCERTthe same bar, the same instant — rendered in the player’s written key (local, never sent)
METRONOME
GUEST · MIRROR
120
BPM
The wire already carries [SongMapBar] (SyncMessage.swift:131-134); harmony rides for free between same-build phones. Per-guest transposition is a render-time offset held outside bars — v2, but drawn here because it is the flagship’s harmony story. EAR MODE: coal plate, KEY only.
REFERENCE SHIPPED TODAY — SongMapBandsView.swift:50-120 recreated at true geometry. The defect is the argument: the PERFORMANCE face never renders the chord (0 occurrences of key in 257 lines).
“ii–V–I IN 12 KEYS” · SongMapPreset.swift:316-317 · ii = .bridge
BRIDGE
1/1
BARS
“CIRCLE OF 4THS · 2 BARS/KEY” · :279-309 · all .verse
VERSE
1/24
BARS
WHAT THE PLATE CANNOT TELL YOU TODAY
· which chord — the ii reads BRIDGE, the V PRE-CHORUS, the I CHORUS, each 1/1
· which key — the circle drill collapses to one VERSE block: 1/12, 1/24 or 1/48
· what is coming — no NEXT band shipped (band 2 cut, :5-10)
· the shape of the cycle — spans group by section only (SongMapModel.swift:247-259), so twelve keys are one span
· the only chord drawn anywhere is a 9-pt string under the EDIT-mode pill (SongMapPillTrack.swift:193-199)

The plate above is the fix: same 106 pt, same three rows, different content. HYPOTHESIS, labelled: that this is what “presented better” meant. The code behaviour is verified; the meaning is inferred.

THE PLATE LEARNS THE CHORD · THREE BANDS

SAME GEOMETRY AS THE APPROVED SONG MAP DISPLAY · CONTENT SWAP ONLY
BAND 1HARMONY PLATE
What chord, how deep in the key. NOW chord Bebas 46 in the name slot; the cell strip (Overpass Mono 16 — Bebas has one glyph for i and I, measured, so a Bebas numeral cannot say minor — active degree in ink, the rest amberDimText) rides beside it. In BOTH (the cycle default) it carries the other fact — symbol big, numerals on the strip; in SYMBOLS / NUMERALS it carries the cell in the same notation on a cycle and disappears on a tune (this bar already fills the name slot) — packet 1’s “ii–V bracketed → I” at zero geometry cost. A long cell windows on onsets (this chord + the next two), never on bars, so 8 beats a chord reads ii · V · I, not ii · ii; and the strip is measured after every paint — whole or absent, 16 → 13 → a shorter window → gone, because a clipped A♭−7 reads A♭−. The counter is bar-in-key at Bebas 44. Row two: CHANGES · KEY F · 4THS → B♭ in the role-label slot the port left empty (the word CHANGES yields to the guide tones when they are on — the modechip already says it), guide tones in the centre when toggled, BARS on the right. The filament measures the key span, ash over the span’s hue. Two-chord bars: both symbols side by side, the active one in ink — a weight swap on beat 3, never a second light. Minor prints as −7: Bebas has one outline for m and M (measured), so the repo’s Dm7 would read DM7 at 46 — the MINOR chip on the sheet lets you see that.
BAND 2NEXT
What chord is coming, and when. The approach gutter verbatim: name Bebas 38, numeral Bebas 44, geometry constant, arming a luminance onset. Idle → C7 quiet; two bars out BARS 2 / BARS 1 at ink .55; the last bar of the current chord counts BEATS 4·3·2·1 while the name warms .55 → 1 beat by beat and the ramp fills in the next key span’s hue beneath it — two channels agreeing, the name first. The numeral carries a unit because on this surface a bare IN 3 cannot say beats from bars. A key change reads → B♭ · C−7 — spelled in the coming key, on both phones. At four beats a chord almost every bar is a last bar, so the gutter is armed almost always — the intended tight-cycle feel, eye-test whether it is urgency or noise.
BAND 3CYCLE STRIP
Where am I in the cycle. The form ribbon with key-aware spans: twelve pills for a twelve-key cycle, roots engraved through the existing tiers (a 12-key span is ~29 pt on a 346-pt track at any bars/key → the initial tier, which keeps the accidental). Spent bars are ash; adjacent keys alternate two luminance steps of one practice hue (D4 — amber / litUnderlay, bars not yet played at .60) so the blocks separate without a second meaning for colour. Foot: BAR 6 / 48 · KEY 2 / 12.
SCALE LAWunchanged
beat → the dot lane (LIT, downbeat only).  bar → the playhead steps.  key span → the filament fills.  chord → a content swap, on a downbeat, carried by the light that was already there.
THE LIT LAW, FOR HARMONY. A chord change is a bar-level event and it lands on a downbeat that is already LIT (currentMeasure advances only on wrap, MetronomeEngine.swift:1460-1470 — the same tick as the accent dot). The harmony surface therefore gets no alive colour of its own: no flash, no #F0A040, no armedBloom. What changes is the content, the spine’s hue, and NEXT’s luminance the bar before. Count the sacred hex on this page: the downbeat dot. The running transport is amber, like songmap-lab’s RUNNING toggle. The census below is walked out of the live CSSOM, not grepped.

THE TELEGRAPH · FOR CHORDS

NEXT WARMS THROUGH THE LAST BAR OF THE CURRENT CHORD · THE CATCH IS A CONTENT SWAP ON A LIT DOWNBEAT
G−7
bar n−2
BARS 1
ink .55
BEATS 4·3·2·1
name .55→1 + ramp
C7
STAGE 1 text · STAGE 2 name luminance + beats + ramp · catch = the swap, on the LIT downbeat (the dot above the block is drawn in ink here — a static card cannot fire)
SUB-BARbeat 3 of a 2-chord bar
The plate’s two symbols swap weight — ink ↔ amberDimText — and the gutter counts 2·1. No ignition on beat 3, ever: that would be a second amber event in the bar. The step is measured below.
COUNT-INguard
Stage 2 is gated on !engine.isCountingIn (songmap-lab :1655-1659), and Reduce Motion renders the static 0.85 tier with a full ramp and a playhead that jumps (:1661-1663). The Reduce-Motion tier is in the lab clock (the chip); the count-in bars themselves are not drawn on this page — the guard is quoted, and the STOPPED state holds the gutter idle and the filament at zero, which is the same rule the count-in must obey.
DOES IT READ? — MEASURED LIVE OFF THE RENDERED CSS
elementsizecap@1 m@2 mtier
EVERY STATE CHANGE IS A LUMINANCE STEP — BOTH PALETTES, LIVE
stepfromtoratio
WHY THE STRIP IS THE SMALL FACT. Overpass 16 subtends ~6′ at 1 m — under the 14′ secondary-label floor (R1 §2.3). It is a LOOK-tier element, so the NOTATION picker decides which fact is the big one (D1) and the strip carries the other in BOTH, or the cell in the same notation in a single mode; if the row cannot hold it whole, it is hidden rather than clipped. Why not Bebas: in the bundled BebasNeue-Regular.ttf i and I are the same outline, so “ii” would print as “II” — the minor/major channel of a roman numeral is case, and only the mono face carries it. That is a port-level finding, not a lab preference. Accidentals are a fallback run on device (neither bundled face has ♭/♯, measured); the size and baseline here are designed, and the receipt flags the row.

THE SPELLING TABLE · ALL 12 KEYS, BOTH WAYS

GENERATED FROM (SPELLED KEY, STEP, ALTERATION, QUALITY) · NOTHING TYPED · FOLLOWS THE SHEET’S TEMPLATE AND START KEY
BY 4THS · C → F → B♭
BY 5THS · C → G → D
THE QUALITY TABLE · SYMBOL · NUMERAL MARK · GUIDE TONES (spelled in C, from the same functions)
qualitysymbolnumeralguide tonescase
WHY STEP + ALTERATION, NOT SEMITONES. Semitones alone cannot spell ♯iv° against ♭V7 (both 6), or ♯ii° against ♭III7 (both 3), and a pitch-class key cannot distinguish G♭ from F♯ — the exact ambiguity already in the repo (fourths table says G♭, SongMapPreset.swift:264-266; fifths says F♯, :269-271; the ii–V–I preset spells B major’s ii as C♯m7 at :326 with no rule). The table above is that rule. Countdown in D spells F♯△ / C♯7 where every chart prints G♭△ / D♭7 — D11, open: in-key by default with a per-chord spelling override. Minor keys simplify automatically (G♭ minor → F♯ minor) because the key signature is a circle position, not a name.

DENSITY LADDER · KEYS AND BARS

LABEL DENSITY VARIES WITH KEY COUNT; BLOCK LEGIBILITY WITH BAR COUNT (verifier §3.2 band 3) · EVERY ROW PART-PLAYED SO ASH IS TESTED
A 12-key cycle’s span is ~29 pt at any bars/key on a 346-pt track (Theme.swift:540 gutter 22), so it always lands in the initial tier — and the initial tier keeps the accidental (2 chars × 6.2 = 12.4 pt fits). Only a weak-key subset (3 keys → 115 pt) reaches the full tier, which for a key root is the same text. Bar blocks degrade separately: max(w−3,1) (SongMapBandsView.swift:166) reaches 1-pt slivers past ~86 bars, which is why baked 144-bar choruses are out and chorus-per-key is a display-time offset (v2).

THE BUDGET RULER · D10

CLASSIC TREE ON 390×844 · 1 px = 1 pt · WORK_ORDER_2026-09-19:410
PLATE 106
RIBBON 62
178SHIPS TODAY
PLATE 106
NEXT 46
RIBBON 62
234+56 · v1b
Band 2 was cut for budget, not only time: plate + gutter + ribbon ≈ 234 pt “will likely engage the latent scroll”, which captures the swipe-up morph (MetronomeView.swift:231-234); the 7+-beat case already overflowed 402×874 by ~8 pt (:83-90). D10 as adopted: harmony maps first, and only once ~56 pt is found — v1a ships the plate and the strip with no new geometry; v1b brings NEXT back after the on-glass measurement.
LEGEND
LIT · the downbeat, once a bar
ink dot · beats 2–4 (dotLitNorm)
G−7ink · the chord you are on
C7amberDimText · the other chord / the cell
key span A · amber, the practice hue
key span B · litUnderlay, the second luminance step
ash · spent, whatever the key
playhead · ink, moving — never LIT