Skip to content

A chord cannot be rooted on the one degree whose fifth is a tritone - #53

Merged
Gamah merged 2 commits into
masterfrom
fix/mode-aware-chord-degrees
Aug 14, 2026
Merged

A chord cannot be rooted on the one degree whose fifth is a tritone#53
Gamah merged 2 commits into
masterfrom
fix/mode-aware-chord-degrees

Conversation

@Gamah

@Gamah Gamah commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Found from 8nj8bjys:0 — a pop song that sounds minor, spooky and out of key.

The chord

--score bar 14 against bar 13, in B lydian (B C# D# E# F# G# A#):

bar 13 [chord 0]  61 66 71 -> 63 66 71    C# F# B -> D# F# B    B sus2 -> B major   ok
bar 14 [chord 1]  65 66 72 -> 65 68 72    F  F# C -> F  G# C    ???    -> F minor

A chord rooted on the tritone, containing C natural — a semitone above the tonic and in no
reading of the key — for one bar in every four, all song. The lead's out-of-key note is that same
C: NearestSoundingTone correctly snaps the tune onto the chord that is actually sounding.

Why

PopProgressions row {0, 3, 5, 4} is commented I-IV-vi-V, which is true in major. Pop also
draws lydian, where degree 3 is the ♯4 and its triad has no perfect fifth. VoicedTone then
forces one — right for the shape a guitarist frets, and on that degree it imports a pitch from
outside the scale, so the chord comes out consonant, confident and in another key.

Every seven-note mode has exactly one such degree. It bites wherever a genre's progressions land
on it:

genre mode degree rows affected
pop lydian (1 in 4) 3 5 of 5
metal aeolian (2 in 7) 1 3 of 6 — the ♭II rows, which are phrygian vocabulary
metal harmonic minor (1 in 7) 1, 2, 6 6 of 6
ska major (2 in 3) 6 2 of 6 — the ♭VII rows, which are mixolydian vocabulary
rock dorian (2 in 5) 5 1 of 6 — the ♭VI row, which is an aeolian reading
punk, country none

17 of 67 (genre, mode, progression) pairings.

The fix

Not a table per mode. Walk the root back two degrees until its fifth is perfect
(Harmony.PlayableProgressions, applied at the draw in Compose.cs). That lands on the triad
containing the original degree as a chord tone, in every scale this engine draws — lydian's ♯4
is II's third, dorian's ♮6 is the major IV's, phrygian's 5th is ♭III's, major's leading tone is V's.
That is not a coincidence to be tuned: a mode's characteristic note lives in a chord, and rooting a
chord on it is the mistake.

So a mode becomes a transformation of one progression table rather than a filter over it, and
variety goes up rather than down:

before after
clean (mode, progression) pairings 50 65
broken pairings 17 0

The seed above now draws [0 1 5 4]I-II-vi-V, a real lydian pop loop — and bar 14 sounds
C# D# G# resolving to C# major, every note in the key.

Two rows do not survive the move and are dropped: ska's I-♭VII-V-I under major (collapses to
I-V-V-I) and rock's i-iv-♭VII-i under dorian (lands on the row above it). A row written as a
pedal keeps its repeats.

VoicedTone is untouched — with nothing ever rooted on the tritone degree it only ever sees
degrees where forcing the fifth is already correct, and it stays right for the power chord it was
written for.

Tests

Four new invariants in the harmony section, each with the vacuity check the file's style asks for:

  • no drawn chord sits on a degree its mode cannot root (the guard)
  • some written row really does name an unrootable degree (or the guard is vacuous)
  • an unrootable degree's substitute contains it as a chord tone (the law itself — a scale added
    later that breaks it needs looking at, not accepting)
  • substitution never turns a changing loop into a pedal

572 checks passed, node suite green, web/_framework re-staged from this engine
(bundle-stamp check clean). The render digests were unaffected — none of the 10 matrix seeds
draws one of the affected modes, which is worth knowing about that matrix rather than reassuring.

One judgement call left open

The written-table cap (no two genres share more than one progression) still holds and is still
asserted. But substitution can land two genres on the same realised changes — metal's ♭II rows
read as rock's ♭VII vamp under aeolian, because that is what those degrees are in aeolian. Six
such pairs. Arguably correct (they are genuinely the same aeolian vamp, separated by tempo, kit and
voicing) and arguably not, so the test prints the number rather than failing on it.

Not in this PR

8nj8bjys:0's other defect: the tune's consequent restates its antecedent verbatim, so the 8-bar
period is a 4-bar loop, and pop/punk get one 2-bar rhythm for the whole tune. Filed as PLAN.md
rank 38.


Second commit: the tune's period (was PLAN rank 38, now deleted)

Melody.Draw gives the consequent exactly one thing to vary — its own AnswerOp — and drawing it
freely lets it be the antecedent's. Over the same call that is the same phrase but for its last
note, so the four-phrase period collapses to the two-phrase tune it was written to be bigger than.
13 of 480 drawn periods did that, 8nj8bjys:0's chorus among them.

Two changes:

  • The consequent's AnswerOp is drawn without replacement against the antecedent's, by zeroing
    a weight rather than by re-drawing — one draw either way, so the tune's stream does not shift. A
    genre that weights a single operator keeps it (an answer it would never write is worse than an
    answer heard twice).
  • Melody.ShortShapeWeights — at MinPhraseBars only Contrasting puts a second rhythm into a
    tune, so Parallel and Varied hand the most exposed voice in the mix one two-bar rhythm to sing
    four times. Worst for pop and punk, whose single-bar four-chord cycle puts every phrase at the
    floor: the genres the period work was written for. {2, 3, 5} instead of {4, 3, 3} there — a
    lean, not a rule, since a parallel period is real at two bars too.

--stats, 100 songs × 6 genres, distinct phrase rhythms and contours within one tune:

before after
phrase rhythms 1.25 – 1.42 1.46 – 1.57
phrase contours 3.35 – 3.49 3.64 – 3.73

A period holds at most two rhythms, so the first figure reads the Contrasting rate almost
directly — half of tunes now, a quarter before.

New check: a parallel period never answers itself the same way twice. Verified non-vacuous — it
reports 13 of 480 with the without-replacement draw removed.

One thing from the original diagnosis was wrong and is not changed. I reported the chorus tune
as sparser than the verse and blamed a missing mechanism. The mechanism exists (move, 1 for the
chorus and 0.8 for the verse, leaning the length draw across the LongFrom split) and the engine
suite already asserts it over 60 tunes. 8nj8bjys:0 is one draw going the other way, which is what
a lean does. docs/composition.md now says so, so the row does not get re-filed off one song.

Digests re-blessed — this is meant to be audible. 573 checks passed, node suite green, bundle
re-staged.

Gamah added 2 commits August 14, 2026 00:53
The progression tables are roman numerals wearing degree numbers: a row
commented I-IV-vi-V is that in the genre's home mode and something else
in the mode beside it. Lydian's degree 3 is the sharp 4, major's degree 6
is the leading tone, aeolian's degree 1 is the natural 2 — and on each of
those the scale gives the triad no perfect fifth. VoicedTone then forces
one, which is right for the shape a guitarist frets and, on that degree,
buys the shape by importing a pitch from outside the scale. The chord
arrives consonant, confident and in another key, and NearestSoundingTone
snaps the tune onto it.

So the changes go through Harmony.PlayableProgressions as they are drawn,
which walks each root back two degrees until its fifth is perfect. That
lands on the triad CONTAINING the original degree as a chord tone in every
scale here — lydian's sharp 4 is II's third, dorian's natural 6 is the
major IV's, phrygian's 5th is flat III's. A mode becomes a transformation
of one progression table rather than a filter over it, which is why this
is not a table per mode: 50 clean (mode, progression) pairings become 65.

Two rows do not survive the move and are dropped: ska's I-flatVII-V-I
under major, which collapses to I-V-V-I, and rock's i-iv-flatVII-i under
dorian, which lands on the substitution of the row above it.

Substituting at the draw rather than at the point of use is what lets the
bass, the voice-leading plan and the tune's chord-tone snap read the same
changes.
… phrases

Melody.Draw gives the consequent one variation: its own AnswerOp. Drawn
freely it can be the antecedent's, and over the same call that produces the
same phrase but for its last note — so the four-phrase period collapses back
to the two-phrase tune it was written to be bigger than. 13 of 480 drawn
periods did exactly that. The operator is now drawn without replacement
against the antecedent's, by zeroing a weight rather than by re-drawing, so
it stays one draw and the tune's stream does not shift; a genre that weights
a single operator keeps it.

And at MinPhraseBars only Contrasting puts a second rhythm into a tune, so
the other two shapes hand the most exposed voice in the mix one two-bar
rhythm to sing four times. That lands hardest on pop and punk, whose
four-chord single-bar cycle puts every phrase at the floor — the genres the
period was written for. ShortShapeWeights leans a floor-length tune toward
Contrasting without forbidding a parallel period, which is a real shape at
two bars too.

--stats over 100 songs x 6 genres, distinct phrase rhythms and contours
within one tune: 1.25-1.42 -> 1.46-1.57 and 3.35-3.49 -> 3.64-3.73. A period
holds at most two rhythms, so the first figure is reading the Contrasting
rate almost directly.

Not changed: a verse is already sparser than its chorus through `move`, and
the engine test measures it over 60 tunes. One song where the verse has more
notes is the draw, not the rule.

Digests re-blessed; the change is meant to be audible.
@Gamah
Gamah merged commit 0b48eba into master Aug 14, 2026
1 check passed
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