Name a fingering by the measure's place, not the number printed on it - #350
Open
isc wants to merge 1 commit into
Open
Name a fingering by the measure's place, not the number printed on it#350isc wants to merge 1 commit into
isc wants to merge 1 commit into
Conversation
A fingering is stored under a name for the note it belongs to, and that name began with the measure's `number` attribute. That attribute is a label, not an identity, and the library has scores that prove it. Satie's Gnossienne No. 1 has no barlines, so MuseScore exports all eleven of its measures as `number="0"`: its 872 notes shared 86 names, and a fingering written on one measure was drawn on all eleven, with no way to take it off any of them. The 1902 Entertainer prints "X1" on a second ending, which parseInt reads as NaN while OSMD reads it as null -- so those 57 notes were filed under one name and looked for under another, and their fingerings never appeared at all. The name also numbered the staff within its part, where OSMD numbers it across the whole sheet. On the two-part Entertainer that put the whole lower staff -- 963 notes -- under names the injection gave to the upper staff's notes, and on the Chopin Ballade it put the second part's two staves on top of the first's. And the three walks that produce the name disagreed about which notes to count: the injector counted every note the measure has, the extraction skipped cue and hidden notes, the click map skipped a third set. An index is a running total, so one walk skipping a note moves every name after it in the measure -- fourteen more scores carry a cue or hidden note, and past it the fingerings slid onto the note next door. A name is now `m<measure index>:<sheet-wide staff>:<voice>:<note>`, defined once in fingeringKeys.js, and the counter spends a place for every non-rest note whatever a walk then does with it. The `m` matters: it makes old and new names impossible to confuse, which they have to be, since a record syncs whole and a device still on the old build must show nothing rather than something wrong. The click map no longer walks the sheet at all -- it reads the note model, which already holds the name -- so what had been three copies of the counting rule is now two, and the two that remain cannot be merged: injection happens before osmd.load(). A browser test holds them against each other across the whole library instead: 114,903 notes, every name the same note in both walks. The same test fails on thirteen scores against the code this replaces. Fingerings already stored are rewritten on the first load of each score, from a map the extraction builds as it names the notes, so the translation is exact rather than re-derived. A name several notes answered to is copied onto all of them -- the score looks exactly as it did, and the copies can now be deleted one at a time -- and a name no note answers to, which could never be drawn, is dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A fingering is stored under a name for the note it belongs to. That name began
with the measure's
numberattribute, which is a label and not an identity, andthe library ships scores that prove it.
What was wrong
number="0". 872 notes shared 86 names: a fingering written on one measure was drawn on all eleven, and could not be taken off any of them.number="X1".parseIntreads NaN, OSMD readsnull— so 57 notes were stored under one name and looked for under another, and their fingerings never appeared at all.print-object="no"notes: the three walks that produce a name disagreed about which to count. An index is a running total, so past the first such note every fingering in the measure slid onto the note next door.Eighteen of the ninety-eight shipped scores, not the four the ambiguity is
visible on.
The name
m<measure index>:<sheet-wide staff>:<voice>:<note>, defined once inpublic/js/fingeringKeys.js.SourceMeasuresindex and the nth
<measure>of a<part>. Repeats do not disturb it: itnames the source measure, engraved once, and the playback sequence already
maps passes onto sources.
<staves>.<note>that is not a rest, cue and hidden and grace notesincluded. Each walk keeps its own reasons to leave a note out of what it
builds; none of them may reach the counter.
m— old and new names can never be confused. That matters because arecord syncs whole and last-write-wins, so a device still on the old build
must show nothing rather than something wrong.
The walks
The click map no longer walks the sheet at all — it reads the note model, which
already holds the name — so three copies of the counting rule become two. The
two that remain cannot be merged: injection has to happen before
osmd.load().So
test/fingering_key_scheme_test.rbholds them against each other across thewhole library instead, checking that each name means a note of the same
pitch in both: 114,903 notes, 98 scores, no disagreement. The same test fails
on thirteen scores against the code this replaces.
Migration
There is no migration system, and none is needed. Each score's extraction builds
Map<old name, new name[]>as it names the notes, so the translation is exactrather than re-derived from the file — which could not work anyway, since the
stored names carry the editor's spelling of the measure number
(
MeasureNumberXML,null) and the file only offers the injection's(
parseInt,NaN).On the first load of a score whose record still holds old names, they are
rewritten and saved with a fresh
updatedAtso the fix reaches the player'sother devices, and what the injection could not place is drawn by the light
re-render a newly entered fingering already takes.
Two judgement calls, both in the code's comments:
looks exactly as it did — the player sees the same fingerings in the same
places — and each is now its own name, so the ten they never wrote can be
deleted one at a time. Picking a single winner would move their fingering to a
measure they never touched.
NaN:names, whichcould never be drawn; nothing visible is lost.
Testing
0:0:0:4on the Gnossienne becomes 11explicit names and all 11 fingerings are drawn where they were.
Note for #348
That PR mirrors this scheme deliberately. It will want to rebase onto
fingeringNotesInDocument, which is the injection walk lifted out so a secondconsumer — and the invariant test — can share it.
🤖 Generated with Claude Code