Repository navigation
feat: an ornament owns the accidental marks written after it - #496
Merged
Merged
Conversation
7 tasks done
MusicXML allows an <accidental-mark> in two places: directly in <notations>, where it stands above or below the note on its own, and inside <ornaments> right after an ornament, where it belongs to that ornament (the sharp over a trill, the accidentals above and below a turn). Both used to read as flat MarkType::accidentalMark* marks, with the link to the ornament lost, and every one was written back at the <notations> level. MarkDataChoice gains a Kind::ornament alternative, OrnamentMarkData, holding a list of OrnamentAccidental entries (an api::Accidental and its position, measured from the ornament). A trill, any turn, a shake, a mordent, an inverted mordent, a schleifer or a haydn holds the accidentals that follow it, in order. An OrnamentMarkData with no accidentals collapses to none. Behaviour change: reading <trill-mark/><accidental-mark>sharp</accidental-mark> used to give two marks, a trill and an accidentalMarkSharp. It now gives one trill mark whose choice.ornament().accidentals holds the sharp, and writing it puts the sharp back inside <ornaments> right after the trill. Code that looked in marks for a trill's accidental must look in the trill. Accidental marks you author, and ones read from <notations>, are unchanged and still write at the <notations> level. An accidental after a tremolo, an other-ornament or a wavy line has no ornament mark to hold it, so it is still read as a standalone accidental mark after the ornament marks, with a warning. On write, an entry left at Accidental::none, and ornament accidentals on a mark that is not written in <ornaments>, are dropped with an error diagnostic. An accidental-mark inside <ornaments> whose value the api has no member for (other) is dropped on read with an error diagnostic. Closes #443.
A tremolo or other-ornament mark given an OrnamentMarkData choice wrote its accidentals inside <ornaments> after the ornament, with no diagnostic, but the reader reads an accidental there as a standalone accidental mark, so the score did not read back as it was written. The writer now asks the same question the reader does (which ornaments can own accidentals) and drops them on any other ornament with an error diagnostic, as it already did for a mark that is not an ornament. The OrnamentMarkData comment now says which marks can hold accidentals and what happens to an accidental written with a tremolo, an other-ornament or a wavy line. A test checks that every ornament that can own accidentals writes and reads them back.
Covers an accidental after an other-ornament and after a wavy line (read as standalone marks, one warning each), an "other" accidental on a turn (dropped with an error, the turn's other accidental kept), and a note with both a standalone accidental and a trill that owns one.
webern
force-pushed
the
m/mxdev-accmark
branch
from
November 7, 2026 11:58
d608138 to
ab75e67
Compare
webern
marked this pull request as ready for review
November 7, 2026 12:45
webern
added a commit
that referenced
this pull request
Nov 7, 2026
## AI Summary An accidental-mark lost how it is drawn when read into mx::api and written back. Only its accidental value, position and placement were kept; its parentheses, bracket, size, SMuFL glyph, id, font and color were dropped on read and could not be authored. This applied both to a standalone accidental mark in notations and to an accidental that belongs to an ornament, such as the sharp over a trill. What changes in mx::api: - A new SymbolSize enum in ApiCommon.h (unspecified, full, cue, graceCue, large) carries MusicXML's size attribute. unspecified leaves the size to the application. - MarkDataChoice gains an AccidentalMarkData alternative (Kind::accidentalMark, isAccidentalMark(), accidentalMark()) for the accidentalMark* mark types: - parentheses and bracket (Bool): yes draws the accidental in parentheses or square brackets, common ways to show an editorial accidental. unspecified leaves it to the application. - size (SymbolSize): how large the accidental is drawn. - smufl: a specific SMuFL accidental glyph, for example accidentalSharp. A name that is not a SMuFL accidental (one starting with acc, medRenFla, medRenNatura, medRenShar or kievanAccidental) is not written, and the write reports an error diagnostic with the dropped-data code. - id: the element's id. The accidental's font and color go in the existing MarkData::printData. A payload with every field at its default is stored as none, so a mark built from its MarkType alone still compares equal after a write and a read. - OrnamentAccidental gains the same five fields directly, plus PrintData for its font and color. An owned accidental has no MarkData around it, so its fields sit on the one struct a caller already reads. One reader helper and one writer helper fill both shapes. ## Behaviour change for callers reading files Reading an accidental mark that sets any of parentheses, bracket, size, smufl or id now gives a choice holding AccidentalMarkData where it used to be none. Its MarkData::printData, and an OrnamentAccidental's printData, now holds the font and color from the file where it used to be left at its default. Code that builds accidental marks is unaffected, and nothing was removed or renamed, so this is labelled non-breaking. ## Out of scope - MarkData::name is still filled in by the reader for an accidental mark (for example "sharp") but left empty by the MarkData constructor, as it already is for ornaments. The writer ignores name for these marks. Changing the constructor would change what existing callers build, so it is left alone here. - printObject on an accidental mark's PrintData, and the alignment fields of its PositionData, have no MusicXML attribute to go to and are not written. The headers say so. Other marks drop these the same way without a diagnostic, and adding one for every mark belongs in its own change. - A MarkDataChoice payload of another kind on an accidental mark is ignored, as on every other mark. An ornament payload on an accidental mark is still reported, as before. ## Pinned files - synthetic/accidental-mark.3.0.xml - synthetic/accidental-mark.3.1.xml ## Testing - [x] MarkRoundTripTest: AccidentalMarkAttributes, AccidentalMarkSizes, AccidentalMarkDefaultsToNoPayload, OrnamentAccidentalAttributes, AccidentalMarkSmuflNotAnAccidental, AccidentalMarkChoiceAccessorsAndEquality (every new field's equality, both shapes) - [x] NoteDataTest: readsSyntheticAccidentalMarkFixture reads every attribute of the trill's sharp from the 3.1 fixture; standaloneAccidentalMarkKeepsHowItIsDrawn reads a standalone accidental-mark's attributes, checks the written xml carries each one, and checks a bare one reads with no payload - [x] make api-test: 6884 assertions in 806 test cases pass - [x] make api-roundtrip: 460 of 460 pinned files pass, two more than before - [x] make api-roundtrip-discover: 461 PASS, 380 FAIL; the PASS list is main's plus the two pinned files - [x] make audit-test passes; make fmt-check passes - [x] Unity build of target mx (CMAKE_UNITY_BUILD, batch size 0) succeeds ## References - Stacked on #496 - Progresses #403 (the accidental-mark id) - Completes the accidental-mark work started in #443 and #496
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.
AI Summary
MusicXML allows an accidental-mark in two places: directly in notations, where it stands above or below the note on its own, and inside ornaments right after an ornament, where it belongs to that ornament (the sharp over a trill that raises its upper note, the accidentals above and below a turn). mx read both as flat MarkType::accidentalMark* marks, losing which ornament an accidental belonged to, and since #462 wrote every one back at the notations level, outside the ornament.
An ornament mark now owns its accidentals. MarkDataChoice gains a new alternative, Kind::ornament, holding OrnamentMarkData:
The reader fills the list for each ornament from the accidental marks that follow it, and the writer puts each one back inside ornaments right after its ornament. Accidental marks in notations are unchanged.
Defined fallbacks, each with a diagnostic:
Behaviour change
Reading
<trill-mark/><accidental-mark>sharp</accidental-mark>used to give two marks, a trill and an accidentalMarkSharp. It now gives one trill mark whose choice.ornament().accidentals holds the sharp, and writing puts the sharp back inside ornaments after the trill. Code that looked in marks for a trill's accidental must now look in the trill. Accidental marks you author, and ones read from notations, are unchanged and still write at the notations level.Out of scope
The accidental mark's drawing attributes (parentheses, bracket, size, SMuFL glyph, id, font and color) are still not read or written, for standalone and ornament accidentals alike. They come in the next layer of this stack. The two synthetic accidental-mark fixtures need them, so this change pins no new file.
Testing
References
midi-unpitchedand accidental marks #443mx::api#403 (the accidental-mark id attribute)