Skip to content

Fix #222, #224 — place notes for the clef in force; apply octave= - #225

Merged
sbeitzel merged 3 commits into
developfrom
feature/fix_222
Oct 4, 2026
Merged

sbeitzel merged 3 commits into
developfrom
feature/fix_222

Conversation

@sbeitzel

@sbeitzel sbeitzel commented Oct 4, 2026

Copy link
Copy Markdown
Owner

Fix #222
Fix #224

Summary

Two bugs that had been hiding each other.

Both multi-voice conformance fixtures, §7 Zocharti Loch and §14.4 Canzonetta, put their bass voices on clef=bass octave=-2. Treble placement drew bass notes 12 diatonic steps too low, and the missing octave=-2 left them 14 steps too high, so the pages looked nearly right. Each fix alone breaks those fixtures, so they land together.

How

#224 (94b38f3)

  • BodyContext tracks the octave= each voice is under. buildNoteEvent and buildChordEvent shift Note.pitch by it, the way abcm2ps's get_note does.
  • Scoped as abcm2ps scopes it (get_key, get_voice):
    • On a header K: it applies to every voice.
    • On a body K: or a V: it applies to that voice.
    • It lasts until another octave= replaces it, and octave=0 resets it.
  • KeyFieldParser reads octave=.
  • Transposition gets an additive statesOctave flag (defaulted, so 2.x API is unchanged). It is what tells octave=0 apart from no octave= at all.

#222 (bfbed66)

  • New ClefSpec.staffPosition(of:) counts from the clef's bottom-line pitch. That table was moved out of FloatingVoiceAssigner, which now uses the shared copy.
  • The emitter gets the clef in force through configuredForMusic, so notes, chords, stems, ties, slurs, ledger lines and collisions all follow it.
  • VerticalLayoutEngine reserves ledger-line space with the same mapping.
  • clef=treble-8 and other octave clefs move no notes, as in abcm2ps.
  • SharedStaffMerger keeps the treble origin, because it only compares intervals between voices on one staff.

Tests

  • New OctaveModifierTests (10 tests):

    • §4.6's example (K:C clef=bass octave=-1 CDEF gives the same pitches as C,D,E,F,). abcm2ps draws the two identically.
    • Chord, grace and tuplet notes.
    • Header, body-K: and V: scope; unstated vs. octave=0.
    • Accidental memory.
  • New ClefNotePositionTests:

    Every non-treble case fails on develop.

  • Re-recorded the Zocharti Loch and Canzonetta page snapshots. Both were compared with abcm2ps -g first, and every bass note lands where abcm2ps puts it.

  • Full suite passes (793 + 704 + 22).

Not covered

🤖 Generated with Claude Code

sbeitzel and others added 3 commits October 3, 2026 21:40
octave= was parsed on V: and ignored, and not parsed on K: at all. It now
shifts Note.pitch in the semantic pass, as abcm2ps's get_note does, scoped as
abcm2ps scopes it: a header K: covers every voice, a body K: or a V: its own
voice, and each lasts until another octave= replaces it. Transposition gains
an additive statesOctave flag so octave=0 can reset one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every notehead, stem, ledger line, accidental, tie and slur was placed at its
treble-clef position whatever clef the staff carried. Staff position is now
counted from the clef's bottom-line pitch (ClefSpec.staffPosition(of:)), the
same table the floating-voice split already used, now shared. An octave clef
(treble-8) moves nothing, as in abcm2ps. The vertical layout's ledger-line
reservation uses the same mapping.

The §7 and §14.4 page snapshots are re-recorded: their bass voices are on
clef=bass octave=-2, which the treble placement had nearly cancelled out
(#224). Both now match abcm2ps -g note for note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	Tests/CeolKitSVGRendererTests/__Snapshots__/CanzonettaConformanceTests/pageMatchesSnapshot.1.txt
@sbeitzel
sbeitzel merged commit 7109f18 into develop Oct 4, 2026
2 checks passed
@sbeitzel
sbeitzel deleted the feature/fix_222 branch October 4, 2026 22:29
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