Skip to content

feat(theory): extended roman numerals + voice-led progressions for the first song - #377

Merged
jeffcrouse merged 2 commits into
mainfrom
feat-progression-voice-leading
Oct 2, 2026
Merged

jeffcrouse merged 2 commits into
mainfrom
feat-progression-voice-leading

Conversation

@jeffcrouse

Copy link
Copy Markdown
Member

Implements docs/BRIEF-progressions-for-first-song.md (included). Hard stop met: "8 bars in D: IVmaj7 V iii vi, then bVIImaj7 IV ii9 Iadd9, voice-led, open voicing, 1 bar each" plays smoothly on a pad (Pigments "Warm Pad"). Confirmed by ear.

Changes

  • Roman numerals take extensions. roman() now accepts any chord() quality as a suffix: IVmaj7, vi9, ii11, Iadd9, Vsus4, bVIImaj7, bVImaj7. Lower case makes the chord minor (vi9 is a minor 9) unless the suffix already sets the third (dim/ø/sus/…). A bare numeral, or one with only a 7, is still diatonic, so V7 is the dominant 7th and bVII7 is a dominant B♭7. chord() and roman() now build chords with the same code (_build/_apply_voicing).
  • voicing now applies to roman numerals in set_progression. Before, it was ignored whenever key was given.
  • Voice leading (voice_lead=True, the default). The first chord keeps its voicing. Each later chord is re-voiced to move as little as possible from the one before: shared notes stay put, the lowest note stays between C3 and C5, and two notes a semitone apart get a penalty. bass=True adds the root (or the slash bass) as its own voice in C2–B2. A slash bass always stays underneath and is never voice-led. The logic lives in the pure theory.progression(), so tests run without the app.
  • Fix: ø7/°7 now normalise correctly. Bø7 used to raise "unknown quality m7b57".

⚠️ Behaviour change: set_progression is now voice-led by default. voice_lead=False restores the old block chords.

Verification

  • uv run --directory mcp test_theory.py: all 12 groups pass. New test_roman_extended and test_voice_leading cover the brief's cases: F A C E, B♭ D F A, and no voice moving more than a 4th.
  • test_mcp_parity.py and test_mcp_arg_parity.py pass.
  • Ran it in the app: wrote the progression with the bridge's set_progression, read it back with get_clip (8 bars, expected voicings), and listened to the voice-led, +bass and block versions side by side.

🤖 Generated with Claude Code

…e first song

Per docs/BRIEF-progressions-for-first-song.md:
- roman() accepts any chord() quality as a suffix (IVmaj7, vi9, ii11, Iadd9, Vsus4,
  bVIImaj7, bVImaj7); the numeral's case sets the third. Bare/7 numerals stay diatonic.
  chord() and roman() now share one root+quality builder (_build/_apply_voicing).
- set_progression applies `voicing` to roman numerals too (was ignored when key was given).
- Voice leading (default on): the first chord keeps its voicing, each next chord is
  re-voiced for least movement (common tones held, lowest note C3..C5, no semitone
  clusters). `bass=True` adds the root / slash bass as its own voice in C2..B2.
- Fix: "ø7"/"°7" normalised to m7b5/dim7 (Bø7 raised "unknown quality m7b57").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Production gate (core): ✅ PASS

  • 91/91 tests passed (0 failed) in 16.29s

@jeffcrouse
jeffcrouse enabled auto-merge (squash) October 2, 2026 00:50
@jeffcrouse
jeffcrouse merged commit de33cb6 into main Oct 2, 2026
8 checks passed
@jeffcrouse
jeffcrouse deleted the feat-progression-voice-leading branch October 2, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant