From 13a19a5f567c5607577521a048c69501bb50af12 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Mon, 6 Jul 2026 19:55:44 +0000 Subject: [PATCH 1/3] feat: unified spanner numbering with writer-side assignment (#297) Replace the raw int numberLevel on the six spanner start/continue/stop structs (CurveStart/Continue/Stop, WedgeStart/Stop, SpannerStart/Stop) with a single SpannerNumber value type in ApiCommon.h. Exactly one of three states -- unspecified, explicit level 1..16 (emitted verbatim), or an author-only identity label (never serialized) -- so a contradictory object is unrepresentable by construction (design principle #3). The writer now owns number assignment for identity spanners: a new SpannerNumberResolver walks each part in true serialization order (measures -> staves -> voices ascending -> notes, directions in vector order, mirroring MeasureWriter/NotationsWriter/DirectionWriter) and assigns the lowest free number from a 1..16 pool per staff and per spanner class (slur, tied, wedge, octave-shift, bracket, dashes). Explicit levels reserve their number for their serialized extent so explicit and identity spanners never collide. Pool exhaustion throws rather than emitting an illegal number. Wedge and octave-shift writers now emit the resolved number (they previously dropped numbers entirely). Readers produce only unspecified or explicit; identity is authoring-only. Adds the slurs_overbars fixture from #297 (two slurs disjoint in musical time that overlap in the serialized stream once voice 1 is written ahead of voice 2), pinned in roundtrip-baseline.txt; corert count 833 -> 834. This is a deliberate breaking change to mx::api. --- data/corpus.xml | 939 +++++++++--------- data/custom/slurs_overbars.features.xml | 338 +++++++ data/custom/slurs_overbars.musicxml | 367 +++++++ src/include/mx/api/ApiCommon.h | 134 ++- src/include/mx/api/CurveData.h | 23 +- src/include/mx/api/SpannerData.h | 12 +- src/include/mx/api/WedgeData.h | 14 +- src/private/mx/impl/CurveFunctions.h | 44 +- src/private/mx/impl/DirectionReader.cpp | 9 +- src/private/mx/impl/DirectionWriter.cpp | 36 +- src/private/mx/impl/DirectionWriter.h | 8 +- src/private/mx/impl/MeasureWriter.cpp | 2 +- src/private/mx/impl/NotationsWriter.cpp | 34 +- src/private/mx/impl/NotationsWriter.h | 1 + src/private/mx/impl/ScoreWriter.cpp | 10 +- src/private/mx/impl/ScoreWriter.h | 10 + src/private/mx/impl/SpannerFunctions.h | 28 +- src/private/mx/impl/SpannerNumberResolver.cpp | 456 +++++++++ src/private/mx/impl/SpannerNumberResolver.h | 70 ++ .../mxtest/api/ApiK009bSlurScoreData.h | 8 +- src/private/mxtest/api/NoteDataTest.cpp | 8 +- src/private/mxtest/api/SlurTest.cpp | 4 +- .../mxtest/api/SpannerIdentityTest.cpp | 315 ++++++ src/private/mxtest/api/SpannerNumberTest.cpp | 110 ++ src/private/mxtest/api/roundtrip-baseline.txt | 6 + .../mxtest/impl/CurveFunctionsTest.cpp | 90 +- .../mxtest/impl/DirectionReaderTest.cpp | 6 +- .../mxtest/impl/DirectionWriterTest.cpp | 14 +- 28 files changed, 2480 insertions(+), 616 deletions(-) create mode 100644 data/custom/slurs_overbars.features.xml create mode 100644 data/custom/slurs_overbars.musicxml create mode 100644 src/private/mx/impl/SpannerNumberResolver.cpp create mode 100644 src/private/mx/impl/SpannerNumberResolver.h create mode 100644 src/private/mxtest/api/SpannerIdentityTest.cpp create mode 100644 src/private/mxtest/api/SpannerNumberTest.cpp diff --git a/data/corpus.xml b/data/corpus.xml index 651184a42..b4bceeb52 100644 --- a/data/corpus.xml +++ b/data/corpus.xml @@ -4,23 +4,24 @@ usage (descending). Do not edit by hand; regenerate with the tool. --> - + - + - - + + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -29,8 +30,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -425,14 +425,15 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -441,8 +442,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -837,11 +837,12 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -850,8 +851,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -1246,9 +1246,9 @@ synthetic/work-title.3.0.xml - + - + @@ -1260,10 +1260,11 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -1272,8 +1273,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -1668,14 +1668,15 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -1684,8 +1685,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -2080,14 +2080,15 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2096,8 +2097,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -2492,11 +2492,12 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2505,8 +2506,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/cancel.location.3.0.xml @@ -2525,11 +2525,12 @@ synthetic/suffix.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2538,8 +2539,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/beat-repeat.3.0.xml @@ -2585,11 +2585,12 @@ synthetic/transpose.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2598,8 +2599,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/cancel.location.3.0.xml @@ -2612,9 +2612,9 @@ synthetic/segno.3.1.xml - + - + @@ -2636,10 +2636,11 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2648,8 +2649,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -2877,14 +2877,15 @@ synthetic/work-title.3.0.xml - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2893,8 +2894,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/cancel.location.3.0.xml @@ -2907,7 +2907,7 @@ synthetic/segno.3.1.xml - + @@ -2925,8 +2925,9 @@ - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2937,8 +2938,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2948,9 +2948,10 @@ synthetic/key-accidental.smufl.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2961,8 +2962,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2970,9 +2970,10 @@ synthetic/key-accidental.smufl.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -2983,8 +2984,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2995,7 +2995,7 @@ synthetic/part-clef.4.0.xml - + @@ -3014,8 +3014,9 @@ - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3026,8 +3027,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3042,9 +3042,10 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3055,8 +3056,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3070,9 +3070,10 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3083,8 +3084,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3098,11 +3098,12 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3111,8 +3112,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -3340,11 +3340,12 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3353,8 +3354,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -3582,11 +3582,12 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3595,8 +3596,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -3824,11 +3824,12 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -3837,8 +3838,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - + synthetic/accent.3.0.xml @@ -4150,9 +4150,10 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4163,8 +4164,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/creator.3.0.xml @@ -4177,7 +4177,7 @@ synthetic/source.3.0.xml - + @@ -4192,8 +4192,9 @@ - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4204,8 +4205,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -4216,9 +4216,10 @@ synthetic/key.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4229,8 +4230,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -4259,12 +4259,13 @@ synthetic/cancel.location.3.0.xml - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4275,23 +4276,23 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/bar-style.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4302,8 +4303,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/bar-style.3.0.xml @@ -4314,9 +4314,10 @@ synthetic/repeat.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4327,8 +4328,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/encoder.3.0.xml @@ -4336,9 +4336,10 @@ synthetic/software.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4349,16 +4350,16 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/software.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4369,8 +4370,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4381,12 +4381,13 @@ synthetic/virtual-name.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4397,8 +4398,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4409,9 +4409,10 @@ synthetic/virtual-name.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4422,18 +4423,18 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4444,17 +4445,17 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/elevation.3.0.xml synthetic/midi-name.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4465,15 +4466,15 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4484,15 +4485,15 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4503,15 +4504,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4522,22 +4523,22 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/transposition.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4548,18 +4549,18 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - foundsuite/Invention_11.xml - + synthetic/stem.3.0.xml - + - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4571,19 +4572,19 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml foundsuite/Adagio and Fugue in C minor, K.546.xml @@ -4594,8 +4595,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - + synthetic/accent.3.0.xml @@ -4761,15 +4761,16 @@ synthetic/work-title.3.0.xml - + - + - + + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml foundsuite/Adagio and Fugue in C minor, K.546.xml @@ -4781,16 +4782,16 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - foundsuite/Invention_11.xml - + synthetic/beam.3.0.xml synthetic/beam.3.1.xml - - + + + custom/slurs_overbars.musicxml custom/transposition.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4802,13 +4803,12 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - + - + @@ -4817,8 +4817,9 @@ - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4829,8 +4830,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + synthetic/measure-distance.3.0.xml @@ -4864,7 +4864,7 @@ synthetic/creator.3.0.xml - + @@ -4877,7 +4877,8 @@ - + + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml @@ -4889,23 +4890,23 @@ foundsuite/Invention 3.xml foundsuite/Invention_10.xml foundsuite/Invention_11.xml - foundsuite/Invention_12.xml - + synthetic/dot.3.0.xml - + - + - + custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml @@ -4915,8 +4916,7 @@ foundsuite/Invention 1.xml foundsuite/Moments Musicaux Op16 No4.xml foundsuite/PepAiraSco.xml - foundsuite/PezR44Sco.xml - + synthetic/accordion-registration.3.0.xml @@ -5012,13 +5012,14 @@ synthetic/words.3.1.xml - + - + custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml @@ -5028,8 +5029,7 @@ foundsuite/Invention 1.xml foundsuite/Moments Musicaux Op16 No4.xml foundsuite/PepAiraSco.xml - foundsuite/PezR44Sco.xml - + synthetic/accordion-registration.3.0.xml @@ -5125,9 +5125,10 @@ synthetic/words.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5138,8 +5139,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + synthetic/concert-score.4.0.xml @@ -5153,9 +5153,10 @@ synthetic/word-font.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5166,15 +5167,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5185,15 +5186,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5204,15 +5205,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5223,15 +5224,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5242,15 +5243,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5261,15 +5262,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5280,15 +5281,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5299,15 +5300,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -5318,17 +5319,17 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + synthetic/left-divider.3.0.xml synthetic/right-divider.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5339,18 +5340,18 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5361,15 +5362,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -5380,15 +5381,15 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -5399,15 +5400,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -5418,14 +5419,14 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - + - - + + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -5437,16 +5438,15 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - + - + + - @@ -5461,7 +5461,8 @@ - + + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml foundsuite/Adagio and Fugue in C minor, K.546.xml @@ -5473,8 +5474,7 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - foundsuite/Invention_11.xml - + synthetic/accidental.3.0.xml @@ -5524,6 +5524,54 @@ synthetic/miscellaneous-field.3.0.xml + + + + + + + + + + + + + + + + + + + + + + + custom/slurs_overbars.musicxml + custom/systems-and-pages.xml + foundsuite/Adagio and Fugue in C minor, K.546.xml + foundsuite/An Chloe.xml + foundsuite/Berlioz_Le_Corsaire.xml + foundsuite/Black Note Study Op 10 no 5.xml + foundsuite/Deutscher Tanz D.820.1.xml + foundsuite/Invention 1.xml + foundsuite/Moments Musicaux Op16 No4.xml + foundsuite/O_Holy_Night-Adam-1871.xml + foundsuite/O_Holy_Night-Adam-1974.xml + foundsuite/O_Holy_Night.xml + + + + synthetic/first.4.0.xml + synthetic/instrument-change.4.0.xml + synthetic/second.4.0.xml + synthetic/sound.3.0.xml + synthetic/sound.3.1.xml + synthetic/straight.4.0.xml + synthetic/swing-style.4.0.xml + synthetic/swing-type.4.0.xml + synthetic/swing.4.0.xml + + @@ -5603,54 +5651,6 @@ synthetic/credit-words.3.1.xml - - - - - - - - - - - - - - - - - - - - - - - custom/systems-and-pages.xml - foundsuite/Adagio and Fugue in C minor, K.546.xml - foundsuite/An Chloe.xml - foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - foundsuite/Invention 1.xml - foundsuite/Moments Musicaux Op16 No4.xml - foundsuite/O_Holy_Night-Adam-1871.xml - foundsuite/O_Holy_Night-Adam-1974.xml - foundsuite/O_Holy_Night.xml - foundsuite/PepAiraSco.xml - - - - synthetic/first.4.0.xml - synthetic/instrument-change.4.0.xml - synthetic/second.4.0.xml - synthetic/sound.3.0.xml - synthetic/sound.3.1.xml - synthetic/straight.4.0.xml - synthetic/swing-style.4.0.xml - synthetic/swing-type.4.0.xml - synthetic/swing.4.0.xml - - @@ -5684,15 +5684,16 @@ synthetic/part-abbreviation.3.0.xml - + - - - - + + + + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/Berlioz_Le_Corsaire.xml @@ -5703,14 +5704,14 @@ foundsuite/Invention_10.xml foundsuite/Invention_11.xml foundsuite/Invention_12.xml - foundsuite/Invention_13.xml - + - - + + + custom/slurs_overbars.musicxml foundsuite/ARBEAU Belle qui tiens ma vie.xml foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -5722,39 +5723,14 @@ foundsuite/Invention_10.xml foundsuite/Invention_11.xml foundsuite/Invention_12.xml - foundsuite/Invention_13.xml - + - + - - - - - foundsuite/12 Horn Duets.xml - foundsuite/Adagio and Fugue in C minor, K.546.xml - foundsuite/An Chloe.xml - foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Deutscher Tanz D.820.1.xml - foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - foundsuite/Invention 3.xml - foundsuite/Invention_10.xml - foundsuite/Invention_11.xml - foundsuite/Invention_12.xml - - - - synthetic/tie.3.0.xml - - - - - + @@ -5774,7 +5750,8 @@ - + + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml @@ -5786,8 +5763,7 @@ foundsuite/Invention 3.xml foundsuite/Invention_10.xml foundsuite/Invention_11.xml - foundsuite/Invention_12.xml - + synthetic/notations.3.0.xml @@ -5796,15 +5772,40 @@ synthetic/tied.3.1.xml - + + + + + + + foundsuite/12 Horn Duets.xml + foundsuite/Adagio and Fugue in C minor, K.546.xml + foundsuite/An Chloe.xml + foundsuite/Berlioz_Le_Corsaire.xml + foundsuite/Black Note Study Op 10 no 5.xml + foundsuite/Deutscher Tanz D.820.1.xml + foundsuite/Invention 1.xml + foundsuite/Invention 2.xml + foundsuite/Invention 3.xml + foundsuite/Invention_10.xml + foundsuite/Invention_11.xml + foundsuite/Invention_12.xml + + + + synthetic/tie.3.0.xml + + + - - + + - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml @@ -5815,8 +5816,7 @@ foundsuite/Invention_11.xml foundsuite/Invention_12.xml foundsuite/Invention_13.xml - foundsuite/Invention_14.xml - + synthetic/word-font.3.0.xml @@ -6403,14 +6403,15 @@ - + - - + + - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml @@ -6422,15 +6423,15 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - kiritan_singing/kiritan_singing_05.xml - + synthetic/music-font.3.0.xml - - + + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml @@ -6442,19 +6443,19 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - kiritan_singing/kiritan_singing_05.xml - + synthetic/glyph.3.1.xml synthetic/other-appearance.3.0.xml - + - + - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml @@ -6466,17 +6467,17 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - kiritan_singing/kiritan_singing_05.xml - + - + - + - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml @@ -6488,8 +6489,7 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - kiritan_singing/kiritan_singing_05.xml - + @@ -6597,6 +6597,28 @@ synthetic/per-minute.3.0.xml + + + + + + custom/slurs_overbars.musicxml + custom/systems-and-pages.xml + foundsuite/PepAiraSco.xml + foundsuite/RonCLunSco.xml + kiritan_singing/kiritan_singing_01.xml + kiritan_singing/kiritan_singing_02.xml + kiritan_singing/kiritan_singing_03.xml + kiritan_singing/kiritan_singing_04.xml + kiritan_singing/kiritan_singing_05.xml + kiritan_singing/kiritan_singing_06.xml + kiritan_singing/kiritan_singing_07.xml + kiritan_singing/kiritan_singing_08.xml + + + + + @@ -6676,37 +6698,15 @@ - - - - - - custom/systems-and-pages.xml - foundsuite/PepAiraSco.xml - foundsuite/RonCLunSco.xml - kiritan_singing/kiritan_singing_01.xml - kiritan_singing/kiritan_singing_02.xml - kiritan_singing/kiritan_singing_03.xml - kiritan_singing/kiritan_singing_04.xml - kiritan_singing/kiritan_singing_05.xml - kiritan_singing/kiritan_singing_06.xml - kiritan_singing/kiritan_singing_07.xml - kiritan_singing/kiritan_singing_08.xml - kiritan_singing/kiritan_singing_09.xml - - - - - - + - - - - - - - + + + + + + + @@ -6720,8 +6720,9 @@ - + custom/musescore-slur-start-stop.musicxml + custom/slurs_overbars.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml foundsuite/An Chloe.xml @@ -6732,8 +6733,7 @@ foundsuite/O_Holy_Night-Adam-1871.xml foundsuite/O_Holy_Night-Adam-1974.xml foundsuite/O_Holy_Night.xml - foundsuite/PepAiraSco.xml - + synthetic/slur.3.0.xml @@ -6838,11 +6838,11 @@ synthetic/staccato.3.0.xml - + - - - + + + @@ -6858,7 +6858,8 @@ - + + custom/slurs_overbars.musicxml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -6870,8 +6871,7 @@ foundsuite/RonCLunSco.xml foundsuite/SCHUBERT An die Sonne.xml foundsuite/Yeah.xml - ksuite/k007a_Notations_Dynamics.xml - + synthetic/dynamics.3.1.xml @@ -7098,11 +7098,11 @@ synthetic/tuplet.3.1.xml - + - - - + + + @@ -7114,7 +7114,8 @@ - + + custom/slurs_overbars.musicxml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml @@ -7126,8 +7127,7 @@ lysuite/ly31a_Directions.xml lysuite/ly33a_Spanners.xml lysuite/ly43e_Multistaff_ClefDynamics.xml - mjbsuite/FreezingSmall.xml - + synthetic/wedge.3.0.xml @@ -7229,27 +7229,9 @@ - - - foundsuite/ARBEAU Belle qui tiens ma vie.xml - foundsuite/SCHUBERT An die Sonne.xml - ksuite/k006a_Clefs_sign_G_ln_2_oct_0_Treble.xml - ksuite/k006b_Clefs_sign_F_ln_4_oct_0_Bass.xml - ksuite/k006c_Clefs_sign_C_ln_3_oct_0_Viola.xml - ksuite/k006d_Clefs_sign_C_ln_4_oct_0_Tenor.xml - ksuite/k006e_Clefs_sign_C_ln_5_oct_0_Baritone.xml - ksuite/k006f_Clefs_sign_C_ln_1_oct_0_Soprano.xml - ksuite/k006g_Clefs_sign_C_ln_2_oct_0_Mezzo_Soprano.xml - ksuite/k006h_Clefs_sign_G_ln_1_oct_0_French.xml - ksuite/k006i_Clefs_sign_F_ln_3_oct_0_Varbaritone.xml - ksuite/k006j_Clefs_sign_F_ln_5_oct_0_Subbass.xml - - - - - - - + + + custom/slurs_overbars.musicxml foundsuite/Moments Musicaux Op16 No4.xml foundsuite/O_Holy_Night.xml foundsuite/PepAiraSco.xml @@ -7261,14 +7243,14 @@ lysuite/ly02b_Rests_PitchedRests.xml lysuite/ly22d_Parenthesized_Noteheads.xml lysuite/ly41c_StaffGroups.xml - lysuite/ly42a_MultiVoice_TwoVoicesOnStaff_Lyrics.xml - + - - + + + custom/slurs_overbars.musicxml foundsuite/Moments Musicaux Op16 No4.xml foundsuite/O_Holy_Night.xml foundsuite/PepAiraSco.xml @@ -7280,7 +7262,25 @@ lysuite/ly02b_Rests_PitchedRests.xml lysuite/ly22d_Parenthesized_Noteheads.xml lysuite/ly41c_StaffGroups.xml - lysuite/ly42a_MultiVoice_TwoVoicesOnStaff_Lyrics.xml + + + + + + + + foundsuite/ARBEAU Belle qui tiens ma vie.xml + foundsuite/SCHUBERT An die Sonne.xml + ksuite/k006a_Clefs_sign_G_ln_2_oct_0_Treble.xml + ksuite/k006b_Clefs_sign_F_ln_4_oct_0_Bass.xml + ksuite/k006c_Clefs_sign_C_ln_3_oct_0_Viola.xml + ksuite/k006d_Clefs_sign_C_ln_4_oct_0_Tenor.xml + ksuite/k006e_Clefs_sign_C_ln_5_oct_0_Baritone.xml + ksuite/k006f_Clefs_sign_C_ln_1_oct_0_Soprano.xml + ksuite/k006g_Clefs_sign_C_ln_2_oct_0_Mezzo_Soprano.xml + ksuite/k006h_Clefs_sign_G_ln_1_oct_0_French.xml + ksuite/k006i_Clefs_sign_F_ln_3_oct_0_Varbaritone.xml + ksuite/k006j_Clefs_sign_F_ln_5_oct_0_Subbass.xml @@ -7579,8 +7579,9 @@ - - + + + custom/slurs_overbars.musicxml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml @@ -7592,8 +7593,7 @@ ksuite/k007a_Notations_Dynamics.xml ksuite/k007c_Directions_Dynamics.xml ksuite/temp.xml - lysuite/ly31a_Directions.xml - + @@ -9665,6 +9665,20 @@ synthetic/fingering.3.0.xml + + + custom/slurs_overbars.musicxml + custom/systems-and-pages.xml + ksuite/k011a_Tuplets.xml + ksuite/k012a_Tempo_Markings.xml + mjbsuite/FreezingSmall.xml + mjbsuite/freezing.old.xml + mjbsuite/freezing.xml + rpatters1/timesigs_composite-ref.musicxml + + + + foundsuite/Berlioz_Le_Corsaire.xml @@ -9786,19 +9800,6 @@ synthetic/group-name.3.0.xml - - - custom/systems-and-pages.xml - ksuite/k011a_Tuplets.xml - ksuite/k012a_Tempo_Markings.xml - mjbsuite/FreezingSmall.xml - mjbsuite/freezing.old.xml - mjbsuite/freezing.xml - rpatters1/timesigs_composite-ref.musicxml - - - - diff --git a/data/custom/slurs_overbars.features.xml b/data/custom/slurs_overbars.features.xml new file mode 100644 index 000000000..0e731a0a3 --- /dev/null +++ b/data/custom/slurs_overbars.features.xml @@ -0,0 +1,338 @@ + + + + custom/slurs_overbars.musicxml + 4.0 + + + accidental + + parentheses + + + + alter + + + appearance + + + attributes + + + backup + + + bar-style + + + barline + + location + + + + beam + + number + + + + beat-type + + + beats + + + bottom-margin + + + clef + + + defaults + + + direction + + placement + + + + direction-type + + + display-octave + + + display-step + + + distance + + type + + + + divisions + + + dot + + + duration + + + dynamics + + default-x + default-y + halign + + + + encoding + + + encoding-date + + + fifths + + + identification + + + instrument-name + + + key + + + left-margin + + + line + + + line-width + + type + + + + measure + + number + width + + + + mf + + + midi-bank + + + midi-channel + + + midi-instrument + + id + + + + midi-program + + + millimeters + + + music-font + + font-family + font-size + + + + notations + + + note + + default-x + + + + note-size + + type + + + + octave + + + page-height + + + page-layout + + + page-margins + + type + + + + page-width + + + pan + + + part + + id + + + + part-list + + + part-name + + print-object + + + + pitch + + + print + + + rest + + + right-margin + + + scaling + + + score-instrument + + id + + + + score-part + + id + + + + score-partwise + + version + + + + sign + + + slur + + bezier-x + bezier-y + default-x + default-y + number + placement + type + + + + software + + + sound + + dynamics + tempo + + + + stem + + default-y + + + + step + + + supports + + attribute + element + type + value + + + + system-distance + + + system-layout + + + system-margins + + + tenths + + + tied + + type + + + + time + + + top-margin + + + top-system-distance + + + type + + + voice + + + volume + + + wedge + + default-y + spread + type + + + + word-font + + font-family + font-size + + + + diff --git a/data/custom/slurs_overbars.musicxml b/data/custom/slurs_overbars.musicxml new file mode 100644 index 000000000..6db177f81 --- /dev/null +++ b/data/custom/slurs_overbars.musicxml @@ -0,0 +1,367 @@ + + + + + + Finale v27.4 for Mac + 2026-07-04 + + + + + + + + + + 7.2319 + 40 + + + 1545 + 1194 + + 70 + 70 + 88 + 88 + + + + + 0 + 0 + + 121 + 70 + + + 0.9115 + 5 + 0.9115 + 1.0026 + 5 + 1.1003 + 1.3021 + 1.0026 + 1.3021 + 1.0026 + 60 + 60 + 60 + 7.5 + + + + + + + MusicXML Part + + SmartMusic SoftSynth 1 + + + 1 + 15489 + 1 + 80 + 0 + + + + + + + + + + 70 + 0 + + 211 + + + + 2 + + 0 + + + + F + 4 + + + + + + + + + + + + + + G + 2 + + 4 + 1 + half + up + + + + + + + G + 2 + + 1 + 1 + eighth + up + begin + + + + + + + B + -1 + 2 + + 1 + 1 + eighth + flat + up + end + + + + + + + B + -1 + 2 + + 2 + 1 + quarter + up + + + + + + + 8 + + + + D + 2 + + 1 + 2 + eighth + down + + + + + + + C + 1 + 2 + + 3 + 2 + quarter + + sharp + down + + + + + + + C + 1 + 2 + + 1 + 2 + eighth + down + begin + + + + + + + + E + 2 + + 1 + 2 + eighth + down + end + + + + + + + E + -1 + 2 + + 2 + 2 + quarter + flat + down + + + + + + + + + + + + + + + B + -1 + 2 + + 2 + 1 + quarter + up + + + + + + + + + + + + E + 3 + + 1 + 1 + eighth + natural + up + begin + + + + + + + E + -1 + 3 + + 1 + 1 + eighth + flat + up + end + + + + + + + E + -1 + 3 + + 2 + 1 + quarter + up + + + + + + + + C + 4 + + 2 + 1 + quarter + + + 8 + + + + E + -1 + 2 + + 2 + 2 + quarter + down + + + + + + + + A + 2 + + 4 + 2 + half + down + + + + E + 2 + + 2 + 2 + quarter + + + light-heavy + + + + + diff --git a/src/include/mx/api/ApiCommon.h b/src/include/mx/api/ApiCommon.h index 47186cee6..6f25f7665 100644 --- a/src/include/mx/api/ApiCommon.h +++ b/src/include/mx/api/ApiCommon.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include namespace mx { @@ -28,7 +30,7 @@ constexpr int TICK_TIME_INFINITY = std::numeric_limits::max(); // "unspecified" or "absent". Do not use these for new fields; new absent-able fields use // std::optional instead (see "mx::api conventions" in AGENTS.md and issue #249). constexpr int INDEX_UNSPECIFIED = -1; // staff/part indices and numbers, e.g. KeyData::staffIndex -constexpr int NUMBER_LEVEL_UNSPECIFIED = -1; // MusicXML 'number' attributes, e.g. SpannerStart::numberLevel +constexpr int NUMBER_LEVEL_UNSPECIFIED = -1; // MusicXML 'number' attributes, e.g. TupletStart::numberLevel constexpr int VALUE_UNSPECIFIED = -1; // other absent-able ints, e.g. DirectionData::voice constexpr Double DOUBLE_UNSPECIFIED = -1.0; // absent-able doubles, e.g. StaffData::staffSize @@ -56,6 +58,136 @@ inline bool isSpecified(Bool b) return b != Bool::unspecified; } +// A spanner's number identity, shared by curves (slur/tied), wedges, and the +// generic spanner start/stop structs. Exactly one of three states; a +// contradictory state (both an explicit level and an identity) is +// unrepresentable by construction. +// +// - unspecified : no number information. The writer applies its default +// (omit the attribute for a lone spanner). +// - explicit(n) : a literal MusicXML number, 1..16. Emitted verbatim. Set by +// the reader to preserve an imported number, or by a caller +// who wants to control the number themselves. +// - identity(id) : an author-only, NEVER-serialized label. Start/continue/stop +// that share the same id are the same logical spanner; the +// writer assigns the MusicXML number from serialization +// order. The reader never produces this state. +// +// Misuse (an out-of-range level, an empty id, or calling an accessor for the +// wrong state) is a programming error and throws std::logic_error. Consumers +// should branch on kind() before touching a payload accessor. +class SpannerNumber +{ + public: + enum class Kind + { + unspecified, + explicitLevel, + identity + }; + + // Default is unspecified. + SpannerNumber() = default; + + // Factory: an explicit MusicXML number. `level` must be in [1, 16]; passing a + // value outside that range is a programming error. + static SpannerNumber makeLevel(int level) + { + if (level < 1 || level > 16) + { + throw std::logic_error{"mx::api::SpannerNumber::makeLevel: level must be in the range [1, 16]"}; + } + SpannerNumber result; + result.myKind = Kind::explicitLevel; + result.myLevel = level; + return result; + } + + // Factory: an author identity label. `id` must be non-empty; an empty string is + // a programming error (use the default-constructed unspecified state instead). + // Because unspecified is its own first-class state, empty-string-as-sentinel + // does not exist here. + static SpannerNumber makeIdentity(std::string id) + { + if (id.empty()) + { + throw std::logic_error{"mx::api::SpannerNumber::makeIdentity: id must be non-empty"}; + } + SpannerNumber result; + result.myKind = Kind::identity; + result.myIdentity = std::move(id); + return result; + } + + inline Kind kind() const + { + return myKind; + } + + inline bool isUnspecified() const + { + return myKind == Kind::unspecified; + } + + inline bool isExplicit() const + { + return myKind == Kind::explicitLevel; + } + + inline bool isIdentity() const + { + return myKind == Kind::identity; + } + + // Precondition: isExplicit(). Returns the literal number 1..16. + inline int level() const + { + if (myKind != Kind::explicitLevel) + { + throw std::logic_error{"mx::api::SpannerNumber::level: not an explicit level"}; + } + return myLevel; + } + + // Precondition: isIdentity(). Returns the (non-empty) id. + inline const std::string &identity() const + { + if (myKind != Kind::identity) + { + throw std::logic_error{"mx::api::SpannerNumber::identity: not an identity"}; + } + return myIdentity; + } + + inline bool operator==(const SpannerNumber &other) const + { + if (myKind != other.myKind) + { + return false; + } + switch (myKind) + { + case Kind::explicitLevel: + return myLevel == other.myLevel; + case Kind::identity: + return myIdentity == other.myIdentity; + case Kind::unspecified: + default: + return true; + } + } + + inline bool operator!=(const SpannerNumber &other) const + { + return !(*this == other); + } + + private: + Kind myKind = Kind::unspecified; + int myLevel = 0; + std::string myIdentity; +}; + enum class Placement { unspecified, diff --git a/src/include/mx/api/CurveData.h b/src/include/mx/api/CurveData.h index 444148e5e..67adc22d0 100644 --- a/src/include/mx/api/CurveData.h +++ b/src/include/mx/api/CurveData.h @@ -91,7 +91,7 @@ struct CurvePoints struct CurveStart { CurveType curveType; - int numberLevel; + SpannerNumber number; CurvePoints curvePoints; CurveOrientation curveOrientation; Placement placement; @@ -100,9 +100,8 @@ struct CurveStart ColorData colorData; CurveStart(CurveType inCurveType) - : curveType{inCurveType}, numberLevel{NUMBER_LEVEL_UNSPECIFIED}, curvePoints{}, - curveOrientation{CurveOrientation::unspecified}, placement{Placement::unspecified}, lineData{}, - isColorSpecified{false}, colorData{} + : curveType{inCurveType}, number{}, curvePoints{}, curveOrientation{CurveOrientation::unspecified}, + placement{Placement::unspecified}, lineData{}, isColorSpecified{false}, colorData{} { } }; @@ -110,7 +109,7 @@ struct CurveStart struct CurveContinue { CurveType curveType; - int numberLevel; + SpannerNumber number; CurvePoints curvePoints; bool isBezierX2Specified; double bezierX2; @@ -120,8 +119,8 @@ struct CurveContinue double bezierOffset2; CurveContinue(CurveType inCurveType) - : curveType{inCurveType}, numberLevel{NUMBER_LEVEL_UNSPECIFIED}, curvePoints{}, isBezierX2Specified{false}, - bezierX2{0.0}, isBezierY2Specified{false}, bezierY2{0.0}, isBezierOffset2Specified{false}, bezierOffset2{0.0} + : curveType{inCurveType}, number{}, curvePoints{}, isBezierX2Specified{false}, bezierX2{0.0}, + isBezierY2Specified{false}, bezierY2{0.0}, isBezierOffset2Specified{false}, bezierOffset2{0.0} { } }; @@ -129,10 +128,10 @@ struct CurveContinue struct CurveStop { CurveType curveType; - int numberLevel; + SpannerNumber number; CurvePoints curvePoints; - CurveStop(CurveType inCurveType) : curveType{inCurveType}, numberLevel{NUMBER_LEVEL_UNSPECIFIED}, curvePoints{} + CurveStop(CurveType inCurveType) : curveType{inCurveType}, number{}, curvePoints{} { } }; @@ -150,7 +149,7 @@ MXAPI_NOT_EQUALS_AND_VECTORS(CurvePoints); MXAPI_EQUALS_BEGIN(CurveStart) MXAPI_EQUALS_MEMBER(curveType) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(curvePoints) MXAPI_EQUALS_MEMBER(curveOrientation) MXAPI_EQUALS_MEMBER(placement) @@ -162,7 +161,7 @@ MXAPI_NOT_EQUALS_AND_VECTORS(CurveStart); MXAPI_EQUALS_BEGIN(CurveContinue) MXAPI_EQUALS_MEMBER(curveType) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(curvePoints) MXAPI_EQUALS_MEMBER(isBezierX2Specified) MXAPI_EQUALS_MEMBER(bezierX2) @@ -175,7 +174,7 @@ MXAPI_NOT_EQUALS_AND_VECTORS(CurveContinue); MXAPI_EQUALS_BEGIN(CurveStop) MXAPI_EQUALS_MEMBER(curveType) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(curvePoints) MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(CurveStop); diff --git a/src/include/mx/api/SpannerData.h b/src/include/mx/api/SpannerData.h index fcb2757ae..ee7f7dc4e 100644 --- a/src/include/mx/api/SpannerData.h +++ b/src/include/mx/api/SpannerData.h @@ -18,31 +18,31 @@ namespace api struct SpannerStart { - int numberLevel; + SpannerNumber number; int tickTimePosition; PositionData positionData; PrintData printData; LineData lineData; - SpannerStart() : numberLevel{NUMBER_LEVEL_UNSPECIFIED}, tickTimePosition{0}, positionData{}, printData{}, lineData{} + SpannerStart() : number{}, tickTimePosition{0}, positionData{}, printData{}, lineData{} { } }; struct SpannerStop { - int numberLevel; + SpannerNumber number; int tickTimePosition; PositionData positionData; LineData lineData; - SpannerStop() : numberLevel{NUMBER_LEVEL_UNSPECIFIED}, tickTimePosition{0}, positionData{}, lineData{} + SpannerStop() : number{}, tickTimePosition{0}, positionData{}, lineData{} { } }; MXAPI_EQUALS_BEGIN(SpannerStart) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(printData) @@ -51,7 +51,7 @@ MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(SpannerStart); MXAPI_EQUALS_BEGIN(SpannerStop) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(tickTimePosition) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(lineData) diff --git a/src/include/mx/api/WedgeData.h b/src/include/mx/api/WedgeData.h index 7d7a2bab1..f8868bbb8 100644 --- a/src/include/mx/api/WedgeData.h +++ b/src/include/mx/api/WedgeData.h @@ -22,7 +22,7 @@ enum class WedgeType struct WedgeStart { - int numberLevel; + SpannerNumber number; WedgeType wedgeType; bool isSpreadSpecified; double spread; @@ -32,26 +32,26 @@ struct WedgeStart ColorData colorData; WedgeStart() - : numberLevel{NUMBER_LEVEL_UNSPECIFIED}, wedgeType{WedgeType::unspecified}, isSpreadSpecified{false}, - spread{0.0}, lineData{}, positionData{}, isColorSpecified{false}, colorData{} + : number{}, wedgeType{WedgeType::unspecified}, isSpreadSpecified{false}, spread{0.0}, lineData{}, + positionData{}, isColorSpecified{false}, colorData{} { } }; struct WedgeStop { - int numberLevel; + SpannerNumber number; PositionData positionData; bool isSpreadSpecified; double spread; - WedgeStop() : numberLevel{NUMBER_LEVEL_UNSPECIFIED}, positionData{}, isSpreadSpecified{false}, spread{0.0} + WedgeStop() : number{}, positionData{}, isSpreadSpecified{false}, spread{0.0} { } }; MXAPI_EQUALS_BEGIN(WedgeStart) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(wedgeType) MXAPI_EQUALS_MEMBER(isSpreadSpecified) MXAPI_EQUALS_MEMBER(spread) @@ -63,7 +63,7 @@ MXAPI_EQUALS_END; MXAPI_NOT_EQUALS_AND_VECTORS(WedgeStart); MXAPI_EQUALS_BEGIN(WedgeStop) -MXAPI_EQUALS_MEMBER(numberLevel) +MXAPI_EQUALS_MEMBER(number) MXAPI_EQUALS_MEMBER(positionData) MXAPI_EQUALS_MEMBER(isSpreadSpecified) MXAPI_EQUALS_MEMBER(spread) diff --git a/src/private/mx/impl/CurveFunctions.h b/src/private/mx/impl/CurveFunctions.h index 4e2456f8a..21b275ee5 100644 --- a/src/private/mx/impl/CurveFunctions.h +++ b/src/private/mx/impl/CurveFunctions.h @@ -14,6 +14,7 @@ #include "mx/impl/SpannerFunctions.h" #include +#include #include namespace mx @@ -87,11 +88,7 @@ api::CurveStart parseCurveStart(const SLUR_OR_TIE_ELEMENT_TYPE &inSlurOrTie) if (inAttributes.number().has_value()) { - c.numberLevel = impl::checkNumber(&inAttributes); - } - else - { - c.numberLevel = api::NUMBER_LEVEL_UNSPECIFIED; + c.number = api::SpannerNumber::makeLevel(impl::checkNumber(&inAttributes)); } c.curvePoints = parseCurvePoints(inAttributes); @@ -120,11 +117,7 @@ api::CurveContinue parseCurveContinue(const SLUR_OR_TIE_ELEMENT_TYPE &inSlurOrTi if (inAttributes.number().has_value()) { - c.numberLevel = impl::checkNumber(&inAttributes); - } - else - { - c.numberLevel = api::NUMBER_LEVEL_UNSPECIFIED; + c.number = api::SpannerNumber::makeLevel(impl::checkNumber(&inAttributes)); } if (inAttributes.bezierX2().has_value()) @@ -157,19 +150,20 @@ template api::CurveStop parseCurveStop(const if (inAttributes.number().has_value()) { - c.numberLevel = impl::checkNumber(&inAttributes); - } - else - { - c.numberLevel = api::NUMBER_LEVEL_UNSPECIFIED; + c.number = api::SpannerNumber::makeLevel(impl::checkNumber(&inAttributes)); } c.curvePoints = parseCurvePoints(inAttributes); return c; } +// The number each writer helper emits is the one the SpannerNumberResolver +// pass resolved for the object (verbatim for an explicit level, assigned from +// serialization order for an identity, absent for unspecified) -- the helpers +// never look at the SpannerNumber directly. template -void writeAttributesFromCurveStart(const api::CurveStart inCurve, ATTRIBUTES_TYPE &outAttributes) +void writeAttributesFromCurveStart(const api::CurveStart inCurve, ATTRIBUTES_TYPE &outAttributes, + const std::optional &inResolvedNumber) { using CurveTypeAttribute = std::decay_t; outAttributes.setType(CurveTypeAttribute::start()); @@ -181,9 +175,9 @@ void writeAttributesFromCurveStart(const api::CurveStart inCurve, ATTRIBUTES_TYP setAttributesFromColorData(inCurve.colorData, outAttributes); } - if (inCurve.numberLevel > 0) + if (inResolvedNumber.has_value()) { - outAttributes.setNumber(core::NumberLevel{inCurve.numberLevel}); + outAttributes.setNumber(core::NumberLevel{*inResolvedNumber}); } if (inCurve.curvePoints.isBezierOffsetSpecified) @@ -211,15 +205,16 @@ void writeAttributesFromCurveStart(const api::CurveStart inCurve, ATTRIBUTES_TYP } template -void writeAttributesFromCurveContinue(const api::CurveContinue inCurve, ATTRIBUTES_TYPE &outAttributes) +void writeAttributesFromCurveContinue(const api::CurveContinue inCurve, ATTRIBUTES_TYPE &outAttributes, + const std::optional &inResolvedNumber) { using CurveTypeAttribute = std::decay_t; outAttributes.setType(CurveTypeAttribute::continue_()); impl::setAttributesFromPositionData(inCurve.curvePoints.positionData, outAttributes); - if (inCurve.numberLevel > 0) + if (inResolvedNumber.has_value()) { - outAttributes.setNumber(core::NumberLevel{inCurve.numberLevel}); + outAttributes.setNumber(core::NumberLevel{*inResolvedNumber}); } if (inCurve.curvePoints.isBezierOffsetSpecified) @@ -255,15 +250,16 @@ void writeAttributesFromCurveContinue(const api::CurveContinue inCurve, ATTRIBUT } template -void writeAttributesFromCurveStop(const api::CurveStop inCurve, ATTRIBUTES_TYPE &outAttributes) +void writeAttributesFromCurveStop(const api::CurveStop inCurve, ATTRIBUTES_TYPE &outAttributes, + const std::optional &inResolvedNumber) { using CurveTypeAttribute = std::decay_t; outAttributes.setType(CurveTypeAttribute::stop()); impl::setAttributesFromPositionData(inCurve.curvePoints.positionData, outAttributes); - if (inCurve.numberLevel > 0) + if (inResolvedNumber.has_value()) { - outAttributes.setNumber(core::NumberLevel{inCurve.numberLevel}); + outAttributes.setNumber(core::NumberLevel{*inResolvedNumber}); } if (inCurve.curvePoints.isBezierOffsetSpecified) diff --git a/src/private/mx/impl/DirectionReader.cpp b/src/private/mx/impl/DirectionReader.cpp index a01b385ce..4fd376245 100644 --- a/src/private/mx/impl/DirectionReader.cpp +++ b/src/private/mx/impl/DirectionReader.cpp @@ -506,7 +506,7 @@ void DirectionReader::parseWedge(const core::DirectionType &directionType) api::WedgeStop stop; if (wedge.number().has_value()) { - stop.numberLevel = wedge.number()->value(); + stop.number = api::SpannerNumber::makeLevel(wedge.number()->value()); } if (isSpreadSpecified) { @@ -524,7 +524,7 @@ void DirectionReader::parseWedge(const core::DirectionType &directionType) api::WedgeStart start; if (wedge.number().has_value()) { - start.numberLevel = wedge.number()->value(); + start.number = api::SpannerNumber::makeLevel(wedge.number()->value()); } if (isSpreadSpecified) { @@ -667,7 +667,10 @@ void DirectionReader::parseBracket(const core::DirectionType &directionType) { api::SpannerStart start; start.tickTimePosition = myCursor.tickTimePosition; - start.numberLevel = impl::checkNumber(&bracket); + if (impl::checkHasNumber(&bracket)) + { + start.number = api::SpannerNumber::makeLevel(impl::checkNumber(&bracket)); + } start.positionData = this->parsePositionData(bracket); start.lineData = makeBracketLineData(); start.printData = impl::getPrintData(bracket); diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index 703f8b4c9..ef542ab05 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -102,9 +102,10 @@ static void applyBracketLineData(const api::LineData &lineData, core::Bracket &b } } -DirectionWriter::DirectionWriter(const api::DirectionData &inDirectionData, const Cursor &inCursor) - : myDirectionData{inDirectionData}, myCursor{inCursor}, myConverter{}, myPlacements{}, - myIsFirstDirectionTypeAdded{false} +DirectionWriter::DirectionWriter(const api::DirectionData &inDirectionData, const Cursor &inCursor, + const SpannerNumberResolver &inNumberResolver) + : myDirectionData{inDirectionData}, myCursor{inCursor}, myNumberResolver{inNumberResolver}, myConverter{}, + myPlacements{}, myIsFirstDirectionTypeAdded{false} { } @@ -269,6 +270,12 @@ void DirectionWriter::emitWedgeStop(const api::WedgeStop &wedgeStop, core::Direc core::Wedge wedge{}; wedge.setType(core::WedgeType::stop()); + const auto number = myNumberResolver.emittedNumber(wedgeStop.number, &wedgeStop); + if (number.has_value()) + { + wedge.setNumber(core::NumberLevel{*number}); + } + if (wedgeStop.isSpreadSpecified) { wedge.setSpread(core::Tenths{core::Decimal{static_cast(wedgeStop.spread)}}); @@ -288,6 +295,12 @@ void DirectionWriter::emitWedgeStart(const api::WedgeStart &wedgeStart, core::Di wedge.setType(myConverter.convert(wedgeStart.wedgeType)); } + const auto number = myNumberResolver.emittedNumber(wedgeStart.number, &wedgeStart); + if (number.has_value()) + { + wedge.setNumber(core::NumberLevel{*number}); + } + if (wedgeStart.isSpreadSpecified) { wedge.setSpread(core::Tenths{core::Decimal{static_cast(wedgeStart.spread)}}); @@ -307,7 +320,8 @@ void DirectionWriter::emitWedgeStart(const api::WedgeStart &wedgeStart, core::Di void DirectionWriter::emitOttavaStop(const api::OttavaStop &ottavaStop, core::Direction &direction) { core::OctaveShift os{}; - setAttributesFromSpannerStop(ottavaStop.spannerStop, os); + setAttributesFromSpannerStop(ottavaStop.spannerStop, os, + myNumberResolver.emittedNumber(ottavaStop.spannerStop.number, &ottavaStop.spannerStop)); os.setType(core::UpDownStopContinue::stop()); os.setSize(ottavaStop.size); core::DirectionType dt{}; @@ -320,6 +334,12 @@ void DirectionWriter::emitOttavaStart(const api::OttavaStart &ottavaStart, core: core::OctaveShift os{}; impl::setAttributesFromLineData(ottavaStart.spannerStart.lineData, os); + const auto number = myNumberResolver.emittedNumber(ottavaStart.spannerStart.number, &ottavaStart.spannerStart); + if (number.has_value()) + { + os.setNumber(core::NumberLevel{*number}); + } + switch (ottavaStart.ottavaType) { case api::OttavaType::o15ma: { @@ -354,7 +374,7 @@ void DirectionWriter::emitOttavaStart(const api::OttavaStart &ottavaStart, core: void DirectionWriter::emitBracketStart(const api::SpannerStart &item, core::Direction &direction) { core::Bracket bracket{}; - setAttributesFromSpannerStart(item, bracket); + setAttributesFromSpannerStart(item, bracket, myNumberResolver.emittedNumber(item.number, &item)); bracket.setType(core::StartStopContinue::start()); setAttributesFromPositionData(item.positionData, bracket); setAttributesFromPrintData(item.printData, bracket); @@ -367,7 +387,7 @@ void DirectionWriter::emitBracketStart(const api::SpannerStart &item, core::Dire void DirectionWriter::emitBracketStop(const api::SpannerStop &item, core::Direction &direction) { core::Bracket bracket{}; - setAttributesFromSpannerStop(item, bracket); + setAttributesFromSpannerStop(item, bracket, myNumberResolver.emittedNumber(item.number, &item)); bracket.setType(core::StartStopContinue::stop()); applyBracketLineData(item.lineData, bracket, myConverter); core::DirectionType dt{}; @@ -378,7 +398,7 @@ void DirectionWriter::emitBracketStop(const api::SpannerStop &item, core::Direct void DirectionWriter::emitDashesStart(const api::SpannerStart &item, core::Direction &direction) { core::Dashes dashes{}; - setAttributesFromSpannerStart(item, dashes); + setAttributesFromSpannerStart(item, dashes, myNumberResolver.emittedNumber(item.number, &item)); dashes.setType(core::StartStopContinue::start()); setAttributesFromPositionData(item.positionData, dashes); setAttributesFromPrintData(item.printData, dashes); @@ -391,7 +411,7 @@ void DirectionWriter::emitDashesStart(const api::SpannerStart &item, core::Direc void DirectionWriter::emitDashesStop(const api::SpannerStop &item, core::Direction &direction) { core::Dashes dashes{}; - setAttributesFromSpannerStop(item, dashes); + setAttributesFromSpannerStop(item, dashes, myNumberResolver.emittedNumber(item.number, &item)); dashes.setType(core::StartStopContinue::stop()); core::DirectionType dt{}; dt.setChoice(core::DirectionTypeChoice::dashes(dashes)); diff --git a/src/private/mx/impl/DirectionWriter.h b/src/private/mx/impl/DirectionWriter.h index 19ac81b21..b801aa390 100644 --- a/src/private/mx/impl/DirectionWriter.h +++ b/src/private/mx/impl/DirectionWriter.h @@ -9,6 +9,7 @@ #include "mx/core/generated/MusicDataChoice.h" #include "mx/impl/Converter.h" #include "mx/impl/Cursor.h" +#include "mx/impl/SpannerNumberResolver.h" #include #include @@ -20,7 +21,11 @@ namespace impl class DirectionWriter { public: - DirectionWriter(const api::DirectionData &inDirectionData, const Cursor &inCursor); + // inNumberResolver supplies the resolved 'number' for each wedge, octave + // shift, bracket, and dashes start/stop (see SpannerNumberResolver); it + // must outlive this writer. + DirectionWriter(const api::DirectionData &inDirectionData, const Cursor &inCursor, + const SpannerNumberResolver &inNumberResolver); std::vector getDirectionLikeThings(); private: @@ -52,6 +57,7 @@ class DirectionWriter private: const api::DirectionData &myDirectionData; const Cursor myCursor; + const SpannerNumberResolver &myNumberResolver; const Converter myConverter; bool myIsFirstDirectionTypeAdded; std::set myPlacements; diff --git a/src/private/mx/impl/MeasureWriter.cpp b/src/private/mx/impl/MeasureWriter.cpp index bbc5c885f..ce966bad0 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -762,7 +762,7 @@ void MeasureWriter::writeDirection(const api::DirectionData &inDirectionData) myPropertiesWriter->flushBuffer(); } - DirectionWriter directionWriter{inDirectionData, myHistory.getCursor()}; + DirectionWriter directionWriter{inDirectionData, myHistory.getCursor(), myScoreWriter.getSpannerNumberResolver()}; auto mdcSet = directionWriter.getDirectionLikeThings(); for (const auto &mdc : mdcSet) { diff --git a/src/private/mx/impl/NotationsWriter.cpp b/src/private/mx/impl/NotationsWriter.cpp index 97f8cc8b6..e5b27cec3 100644 --- a/src/private/mx/impl/NotationsWriter.cpp +++ b/src/private/mx/impl/NotationsWriter.cpp @@ -56,6 +56,7 @@ #include "mx/impl/DynamicsWriter.h" #include "mx/impl/MarkDataFunctions.h" #include "mx/impl/PositionFunctions.h" +#include "mx/impl/ScoreWriter.h" #include @@ -87,8 +88,8 @@ void setMordentSpecificAttributes(const api::MarkData &mark, core::Mordent &mord } // namespace NotationsWriter::NotationsWriter(const api::NoteData &inNoteData, const MeasureCursor &inCursor, - const ScoreWriter & /*inScoreWriter*/) - : myNoteData{inNoteData}, myCursor{inCursor}, myConverter{} + const ScoreWriter &inScoreWriter) + : myNoteData{inNoteData}, myCursor{inCursor}, myScoreWriter{inScoreWriter}, myConverter{} { } @@ -99,50 +100,67 @@ core::Notations NotationsWriter::getNotations() const core::Ornaments ornaments; core::Technical technicals; + const auto &numberResolver = myScoreWriter.getSpannerNumberResolver(); + for (const auto &curve : myNoteData.noteAttachmentData.curveStops) { + if (curve.curveType != api::CurveType::tie && curve.curveType != api::CurveType::slur) + { + continue; + } + const auto resolvedNumber = numberResolver.emittedNumber(curve.number, &curve); if (curve.curveType == api::CurveType::tie) { core::Tied tied; - writeAttributesFromCurveStop(curve, tied); + writeAttributesFromCurveStop(curve, tied, resolvedNumber); outNotations.addChoice(core::NotationsChoice::tied(tied)); } else if (curve.curveType == api::CurveType::slur) { core::Slur slur; - writeAttributesFromCurveStop(curve, slur); + writeAttributesFromCurveStop(curve, slur, resolvedNumber); outNotations.addChoice(core::NotationsChoice::slur(slur)); } } for (const auto &curve : myNoteData.noteAttachmentData.curveContinuations) { + if (curve.curveType != api::CurveType::tie && curve.curveType != api::CurveType::slur) + { + continue; + } + const auto resolvedNumber = numberResolver.emittedNumber(curve.number, &curve); if (curve.curveType == api::CurveType::tie) { core::Tied tied; - writeAttributesFromCurveContinue(curve, tied); + writeAttributesFromCurveContinue(curve, tied, resolvedNumber); outNotations.addChoice(core::NotationsChoice::tied(tied)); } else if (curve.curveType == api::CurveType::slur) { core::Slur slur; - writeAttributesFromCurveContinue(curve, slur); + writeAttributesFromCurveContinue(curve, slur, resolvedNumber); outNotations.addChoice(core::NotationsChoice::slur(slur)); } } for (const auto &curve : myNoteData.noteAttachmentData.curveStarts) { + if (curve.curveType != api::CurveType::tie && curve.curveType != api::CurveType::slur) + { + continue; + } + const auto resolvedNumber = numberResolver.emittedNumber(curve.number, &curve); if (curve.curveType == api::CurveType::tie) { core::Tied tied; - writeAttributesFromCurveStart(curve, tied); + writeAttributesFromCurveStart(curve, tied, resolvedNumber); outNotations.addChoice(core::NotationsChoice::tied(tied)); } else if (curve.curveType == api::CurveType::slur) { core::Slur slur; - writeAttributesFromCurveStart(curve, slur); + writeAttributesFromCurveStart(curve, slur, resolvedNumber); outNotations.addChoice(core::NotationsChoice::slur(slur)); } } diff --git a/src/private/mx/impl/NotationsWriter.h b/src/private/mx/impl/NotationsWriter.h index 924f96a96..e930f4bc5 100644 --- a/src/private/mx/impl/NotationsWriter.h +++ b/src/private/mx/impl/NotationsWriter.h @@ -27,6 +27,7 @@ class NotationsWriter private: const api::NoteData &myNoteData; const MeasureCursor &myCursor; + const ScoreWriter &myScoreWriter; const Converter myConverter; private: diff --git a/src/private/mx/impl/ScoreWriter.cpp b/src/private/mx/impl/ScoreWriter.cpp index 88191dc94..447f1bbf0 100644 --- a/src/private/mx/impl/ScoreWriter.cpp +++ b/src/private/mx/impl/ScoreWriter.cpp @@ -31,9 +31,17 @@ namespace mx { namespace impl { -ScoreWriter::ScoreWriter(const api::ScoreData &inScoreData) : myScoreData{inScoreData}, myMutex{}, myOutScorePartwise{} +ScoreWriter::ScoreWriter(const api::ScoreData &inScoreData) + : myScoreData{inScoreData}, mySpannerNumberResolver{}, myMutex{}, myOutScorePartwise{} { myScoreData.sort(); + + // Resolve after sort() so the resolved spanner object addresses are the + // ones the measure/note writers will visit. + for (const auto &part : myScoreData.parts) + { + mySpannerNumberResolver.resolvePart(part); + } } core::ScorePartwise ScoreWriter::getScorePartwise() const diff --git a/src/private/mx/impl/ScoreWriter.h b/src/private/mx/impl/ScoreWriter.h index 06da5ade7..aa39b1622 100644 --- a/src/private/mx/impl/ScoreWriter.h +++ b/src/private/mx/impl/ScoreWriter.h @@ -10,6 +10,7 @@ #include "mx/core/generated/ScorePart.h" #include "mx/core/generated/ScorePartwise.h" #include "mx/impl/Cursor.h" +#include "mx/impl/SpannerNumberResolver.h" #include #include @@ -38,8 +39,17 @@ class ScoreWriter std::optional findPageLayoutData(api::MeasureIndex measureIndex) const; api::SystemData getSystemData(int measureIndex) const; + // Spanner 'number' assignments for the ScoreData held by this writer, + // computed once at construction (after the score is sorted, so the + // resolved object addresses match what the measure/note writers visit). + inline const SpannerNumberResolver &getSpannerNumberResolver() const + { + return mySpannerNumberResolver; + } + private: api::ScoreData myScoreData; + SpannerNumberResolver mySpannerNumberResolver; mutable std::mutex myMutex; mutable core::ScorePartwise myOutScorePartwise; diff --git a/src/private/mx/impl/SpannerFunctions.h b/src/private/mx/impl/SpannerFunctions.h index 0894f3340..f6d22dbe7 100644 --- a/src/private/mx/impl/SpannerFunctions.h +++ b/src/private/mx/impl/SpannerFunctions.h @@ -8,6 +8,9 @@ #include "mx/impl/LineFunctions.h" #include "mx/impl/PositionFunctions.h" #include "mx/impl/PrintFunctions.h" +#include "mx/utility/Unused.h" + +#include namespace mx { @@ -19,10 +22,9 @@ MX_OPTIONAL_GET_INT_FUNC(number, Number, api::NUMBER_LEVEL_UNSPECIFIED); template api::SpannerStart getSpannerStart(const ATTRIBUTES_TYPE &inAttributes) { api::SpannerStart start; - start.numberLevel = api::NUMBER_LEVEL_UNSPECIFIED; if (checkHasNumber(&inAttributes)) { - start.numberLevel = checkNumber(&inAttributes); + start.number = api::SpannerNumber::makeLevel(checkNumber(&inAttributes)); } start.positionData = getPositionData(inAttributes); start.printData = getPrintData(inAttributes); @@ -33,10 +35,9 @@ template api::SpannerStart getSpannerStart(const ATTR template api::SpannerStop getSpannerStop(const ATTRIBUTES_TYPE &inAttributes) { api::SpannerStop stop; - stop.numberLevel = api::NUMBER_LEVEL_UNSPECIFIED; if (checkHasNumber(&inAttributes)) { - stop.numberLevel = checkNumber(&inAttributes); + stop.number = api::SpannerNumber::makeLevel(checkNumber(&inAttributes)); } stop.positionData = getPositionData(inAttributes); stop.lineData = getLineData(inAttributes); @@ -46,33 +47,36 @@ template api::SpannerStop getSpannerStop(const ATTRIB MX_OPTIONAL_SET_HAS_FUNC(number, setNumber, Number); MX_OPTIONAL_SET_INT_FUNC(number, setNumber, Number); +// The number written is the one the SpannerNumberResolver pass resolved for +// the object; these helpers never look at the SpannerNumber directly. template -void setAttributesFromSpannerStart(const api::SpannerStart &start, ATTRIBUTES_TYPE &outAttributes) +void setAttributesFromSpannerStart(const api::SpannerStart &start, ATTRIBUTES_TYPE &outAttributes, + const std::optional &inResolvedNumber) { - if (start.numberLevel > 0) + MX_UNUSED(start); + if (inResolvedNumber.has_value()) { lookForAndSetHasNumber(true, &outAttributes); - lookForAndSetNumber(start.numberLevel, &outAttributes); + lookForAndSetNumber(*inResolvedNumber, &outAttributes); } else { lookForAndSetHasNumber(false, &outAttributes); - lookForAndSetNumber(1, &outAttributes); } } template -void setAttributesFromSpannerStop(const api::SpannerStop &stop, ATTRIBUTES_TYPE &outAttributes) +void setAttributesFromSpannerStop(const api::SpannerStop &stop, ATTRIBUTES_TYPE &outAttributes, + const std::optional &inResolvedNumber) { - if (stop.numberLevel > 0) + if (inResolvedNumber.has_value()) { lookForAndSetHasNumber(true, &outAttributes); - lookForAndSetNumber(stop.numberLevel, &outAttributes); + lookForAndSetNumber(*inResolvedNumber, &outAttributes); } else { lookForAndSetHasNumber(false, &outAttributes); - lookForAndSetNumber(1, &outAttributes); } setAttributesFromPositionData(stop.positionData, outAttributes); diff --git a/src/private/mx/impl/SpannerNumberResolver.cpp b/src/private/mx/impl/SpannerNumberResolver.cpp new file mode 100644 index 000000000..737c28c6a --- /dev/null +++ b/src/private/mx/impl/SpannerNumberResolver.cpp @@ -0,0 +1,456 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#include "mx/impl/SpannerNumberResolver.h" +#include "mx/utility/Throw.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mx +{ +namespace impl +{ +// Spanner element families that draw numbers from separate pools. CurveType +// distinguishes slur from tied; the SpannerStart/Stop structs serve several +// elements (octave-shift, bracket, dashes) whose number attributes are +// independent of each other in MusicXML, so each gets its own pool. +enum class SpannerNumberClass +{ + slur, + tie, + wedge, + octaveShift, + bracket, + dashes +}; + +// One start/continue/stop occurrence at its position in the serialized stream. +struct SpannerNumberEvent +{ + int position; + int staffIndex; + const void *object; + const api::SpannerNumber *number; + bool opens; // a start + bool closes; // a stop +}; + +// [first, last] positions (inclusive) during which a spanner is open in the +// serialized stream. +struct SpannerNumberInterval +{ + int first; + int last; +}; + +inline bool spannerNumberIntervalsOverlap(const SpannerNumberInterval &inLeft, const SpannerNumberInterval &inRight) +{ + return inLeft.first <= inRight.last && inRight.first <= inLeft.last; +} + +// Collects spanner events in the exact order MeasureWriter serializes them. +// The position counter is global; only events within the same class (and +// staff pool) are ever compared, so cross-class interleaving is irrelevant, +// but within a class the order matches what a streaming reader sees. +class SpannerNumberEventCollector +{ + public: + void addNote(int inStaffIndex, const api::NoteData &inNote) + { + // NotationsWriter emits curve stops, then continues, then starts, and + // skips curves whose type is neither tie nor slur. + const auto &attachments = inNote.noteAttachmentData; + for (const auto &stop : attachments.curveStops) + { + addCurve(inStaffIndex, stop.curveType, &stop, stop.number, false, true); + } + for (const auto &curveContinue : attachments.curveContinuations) + { + addCurve(inStaffIndex, curveContinue.curveType, &curveContinue, curveContinue.number, false, false); + } + for (const auto &start : attachments.curveStarts) + { + addCurve(inStaffIndex, start.curveType, &start, start.number, true, false); + } + } + + void addDirection(int inStaffIndex, const api::DirectionData &inDirection) + { + if (inDirection.orderedComponents.empty()) + { + addDirectionFixedOrder(inStaffIndex, inDirection); + } + else + { + addDirectionOrderedComponents(inStaffIndex, inDirection); + } + } + + const std::map> &events() const + { + return myEvents; + } + + private: + void add(SpannerNumberClass inClass, int inStaffIndex, const void *inObject, const api::SpannerNumber &inNumber, + bool inOpens, bool inCloses) + { + myEvents[inClass].push_back( + SpannerNumberEvent{myPosition, inStaffIndex, inObject, &inNumber, inOpens, inCloses}); + ++myPosition; + } + + void addCurve(int inStaffIndex, api::CurveType inCurveType, const void *inObject, + const api::SpannerNumber &inNumber, bool inOpens, bool inCloses) + { + if (inCurveType == api::CurveType::slur) + { + add(SpannerNumberClass::slur, inStaffIndex, inObject, inNumber, inOpens, inCloses); + } + else if (inCurveType == api::CurveType::tie) + { + add(SpannerNumberClass::tie, inStaffIndex, inObject, inNumber, inOpens, inCloses); + } + } + + // Mirrors DirectionWriter::emitFixedOrder. Pedals are skipped: has + // no number attribute. + void addDirectionFixedOrder(int inStaffIndex, const api::DirectionData &inDirection) + { + for (const auto &item : inDirection.wedgeStops) + { + add(SpannerNumberClass::wedge, inStaffIndex, &item, item.number, false, true); + } + for (const auto &item : inDirection.wedgeStarts) + { + add(SpannerNumberClass::wedge, inStaffIndex, &item, item.number, true, false); + } + for (const auto &item : inDirection.ottavaStops) + { + add(SpannerNumberClass::octaveShift, inStaffIndex, &item, item.number, false, true); + } + for (const auto &item : inDirection.ottavaStarts) + { + add(SpannerNumberClass::octaveShift, inStaffIndex, &item.spannerStart, item.spannerStart.number, true, + false); + } + for (const auto &item : inDirection.bracketStarts) + { + add(SpannerNumberClass::bracket, inStaffIndex, &item, item.number, true, false); + } + for (const auto &item : inDirection.bracketStops) + { + add(SpannerNumberClass::bracket, inStaffIndex, &item, item.number, false, true); + } + for (const auto &item : inDirection.dashesStarts) + { + add(SpannerNumberClass::dashes, inStaffIndex, &item, item.number, true, false); + } + for (const auto &item : inDirection.dashesStops) + { + add(SpannerNumberClass::dashes, inStaffIndex, &item, item.number, false, true); + } + } + + // Mirrors DirectionWriter::emitOrderedComponents (same bounds checks). + void addDirectionOrderedComponents(int inStaffIndex, const api::DirectionData &inDirection) + { + for (const auto &component : inDirection.orderedComponents) + { + const int i = component.index; + if (i < 0) + { + continue; + } + const auto index = static_cast(i); + + switch (component.kind) + { + case api::DirectionComponentKind::wedgeStop: + if (index < inDirection.wedgeStops.size()) + { + const auto &item = inDirection.wedgeStops.at(index); + add(SpannerNumberClass::wedge, inStaffIndex, &item, item.number, false, true); + } + break; + + case api::DirectionComponentKind::wedgeStart: + if (index < inDirection.wedgeStarts.size()) + { + const auto &item = inDirection.wedgeStarts.at(index); + add(SpannerNumberClass::wedge, inStaffIndex, &item, item.number, true, false); + } + break; + + case api::DirectionComponentKind::ottavaStop: + if (index < inDirection.ottavaStops.size()) + { + const auto &item = inDirection.ottavaStops.at(index); + add(SpannerNumberClass::octaveShift, inStaffIndex, &item, item.number, false, true); + } + break; + + case api::DirectionComponentKind::ottavaStart: + if (index < inDirection.ottavaStarts.size()) + { + const auto &item = inDirection.ottavaStarts.at(index); + add(SpannerNumberClass::octaveShift, inStaffIndex, &item.spannerStart, item.spannerStart.number, + true, false); + } + break; + + case api::DirectionComponentKind::bracketStart: + if (index < inDirection.bracketStarts.size()) + { + const auto &item = inDirection.bracketStarts.at(index); + add(SpannerNumberClass::bracket, inStaffIndex, &item, item.number, true, false); + } + break; + + case api::DirectionComponentKind::bracketStop: + if (index < inDirection.bracketStops.size()) + { + const auto &item = inDirection.bracketStops.at(index); + add(SpannerNumberClass::bracket, inStaffIndex, &item, item.number, false, true); + } + break; + + case api::DirectionComponentKind::dashesStart: + if (index < inDirection.dashesStarts.size()) + { + const auto &item = inDirection.dashesStarts.at(index); + add(SpannerNumberClass::dashes, inStaffIndex, &item, item.number, true, false); + } + break; + + case api::DirectionComponentKind::dashesStop: + if (index < inDirection.dashesStops.size()) + { + const auto &item = inDirection.dashesStops.at(index); + add(SpannerNumberClass::dashes, inStaffIndex, &item, item.number, false, true); + } + break; + + default: + break; + } + } + } + + int myPosition = 0; + std::map> myEvents; +}; + +// Assigns numbers within one spanner class. Explicit levels reserve their +// number for the serialized extent of their start/stop pair; identity groups +// then take the lowest number whose reservations and prior assignments do not +// overlap the group's own extent, per staff pool. +static void spannerNumberAssignClass(const std::vector &inEvents, + std::unordered_map &ioResolved) +{ + constexpr int kSpannerNumberMax = 16; + + // occupied[staffIndex][number] -> intervals during which that number is taken + std::map>> occupied; + + const auto reserve = [&occupied](int inNumber, const SpannerNumberInterval &inInterval, int inStaffA, + int inStaffB) { + occupied[inStaffA][inNumber].push_back(inInterval); + if (inStaffB != inStaffA) + { + occupied[inStaffB][inNumber].push_back(inInterval); + } + }; + + // Match explicit start/stop pairs by level in stream order and reserve + // their intervals. A pair that spans staves reserves in both pools. + std::map> openExplicit; + for (const auto &event : inEvents) + { + if (!event.number->isExplicit()) + { + continue; + } + const int level = event.number->level(); + if (event.opens) + { + openExplicit[level].push_back(&event); + } + else if (event.closes) + { + auto &stack = openExplicit[level]; + if (!stack.empty()) + { + const auto *start = stack.back(); + stack.pop_back(); + reserve(level, SpannerNumberInterval{start->position, event.position}, start->staffIndex, + event.staffIndex); + } + else + { + // a stop with no visible start (authoring error); reserve its + // own position so at least the stop itself cannot collide + reserve(level, SpannerNumberInterval{event.position, event.position}, event.staffIndex, + event.staffIndex); + } + } + else + { + // a continue between its start and stop is already covered by the + // pair's interval; this point reservation only matters when the + // continue dangles + reserve(level, SpannerNumberInterval{event.position, event.position}, event.staffIndex, event.staffIndex); + } + } + for (const auto &levelAndStack : openExplicit) + { + for (const auto *start : levelAndStack.second) + { + // a start with no stop stays open to the end of the part + reserve(levelAndStack.first, SpannerNumberInterval{start->position, std::numeric_limits::max()}, + start->staffIndex, start->staffIndex); + } + } + + // Group identity events: same id (within this class and part) is the same + // logical spanner. Groups are assigned in order of first appearance. + struct SpannerNumberGroup + { + SpannerNumberInterval interval; + std::vector objects; + std::set staves; + }; + + std::vector groups; + std::map groupIndexByIdentity; + for (const auto &event : inEvents) + { + if (!event.number->isIdentity()) + { + continue; + } + const auto found = groupIndexByIdentity.emplace(event.number->identity(), groups.size()); + if (found.second) + { + groups.push_back(SpannerNumberGroup{SpannerNumberInterval{event.position, event.position}, {}, {}}); + } + auto &group = groups.at(found.first->second); + group.interval.first = std::min(group.interval.first, event.position); + group.interval.last = std::max(group.interval.last, event.position); + group.objects.push_back(event.object); + group.staves.insert(event.staffIndex); + } + + for (const auto &group : groups) + { + int chosen = 0; + for (int candidate = 1; candidate <= kSpannerNumberMax && chosen == 0; ++candidate) + { + bool isFree = true; + for (const int staffIndex : group.staves) + { + const auto staffIter = occupied.find(staffIndex); + if (staffIter == occupied.cend()) + { + continue; + } + const auto numberIter = staffIter->second.find(candidate); + if (numberIter == staffIter->second.cend()) + { + continue; + } + for (const auto &interval : numberIter->second) + { + if (spannerNumberIntervalsOverlap(interval, group.interval)) + { + isFree = false; + break; + } + } + if (!isFree) + { + break; + } + } + if (isFree) + { + chosen = candidate; + } + } + if (chosen == 0) + { + MX_THROW("more than 16 spanners of one type are open at the same point in the serialized " + "stream; MusicXML number attributes only range from 1 to 16"); + } + for (const int staffIndex : group.staves) + { + occupied[staffIndex][chosen].push_back(group.interval); + } + for (const void *object : group.objects) + { + ioResolved[object] = chosen; + } + } +} + +void SpannerNumberResolver::resolvePart(const api::PartData &inPart) +{ + SpannerNumberEventCollector collector; + + for (const auto &measure : inPart.measures) + { + int staffIndex = 0; + for (const auto &staff : measure.staves) + { + for (const auto &voicePair : staff.voices) + { + for (const auto ¬e : voicePair.second.notes) + { + collector.addNote(staffIndex, note); + } + } + for (const auto &direction : staff.directions) + { + collector.addDirection(staffIndex, direction); + } + ++staffIndex; + } + } + + for (const auto &classAndEvents : collector.events()) + { + spannerNumberAssignClass(classAndEvents.second, myResolved); + } +} + +std::optional SpannerNumberResolver::emittedNumber(const api::SpannerNumber &inNumber, const void *inObject) const +{ + switch (inNumber.kind()) + { + case api::SpannerNumber::Kind::unspecified: + return std::nullopt; + case api::SpannerNumber::Kind::explicitLevel: + return inNumber.level(); + case api::SpannerNumber::Kind::identity: { + const auto iter = myResolved.find(inObject); + if (iter == myResolved.cend()) + { + MX_THROW("identity spanner was not visited by resolvePart; the writer would emit an " + "unnumbered spanner that could collide with another"); + } + return iter->second; + } + default: + return std::nullopt; + } +} +} // namespace impl +} // namespace mx diff --git a/src/private/mx/impl/SpannerNumberResolver.h b/src/private/mx/impl/SpannerNumberResolver.h new file mode 100644 index 000000000..0850f87e0 --- /dev/null +++ b/src/private/mx/impl/SpannerNumberResolver.h @@ -0,0 +1,70 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#pragma once + +#include "mx/api/ScoreData.h" + +#include +#include + +namespace mx +{ +namespace impl +{ +// Resolves the MusicXML 'number' attribute for every spanner start/continue/stop +// in a part before the part is written. +// +// A SpannerNumber self-describes as unspecified, explicit, or identity (see +// mx/api/ApiCommon.h). Explicit levels are emitted verbatim and unspecified +// spanners emit no number, so only identity spanners need writer-side +// assignment -- but explicit spanners still participate here, because an +// explicit level must be treated as reserved while it is open so a +// concurrently-open identity spanner is never handed the same number. +// +// Numbers come from a pool of 1..16 per staff and per spanner class (slur, +// tied, wedge, octave-shift, bracket, and dashes each have their own pool; a +// slur numbered 1 and a wedge numbered 1 do not conflict). Two spanners +// conflict only when they overlap in the order a streaming reader encounters +// them, so resolvePart walks the part in the exact order MeasureWriter +// serializes it: measures in order, staves in order, voices ascending, notes +// in vector order (curve stops, then continues, then starts per note -- +// mirroring NotationsWriter), and each staff's directions in vector order +// (mirroring DirectionWriter's per-direction emission order). An identity +// spanner takes the lowest number that is free across its whole serialized +// extent -- from its first event to its last, whichever of start/stop comes +// first in the stream -- and releases it afterward. +// +// Identity ids are scoped per part and per spanner class: events in the same +// part sharing a class and id are one logical spanner, even across staves. A +// cross-staff identity spanner reserves its number in every staff pool it +// touches. Pedal starts/stops carry SpannerNumber but the element has +// no number attribute, so pedals are ignored here. +// +// If more than 16 spanners of one class are open at once in a staff (which no +// real score approaches), resolution fails loudly with an exception rather +// than emitting an illegal number. +class SpannerNumberResolver +{ + public: + SpannerNumberResolver() = default; + + // Walks inPart in serialization order and assigns a number to every + // identity spanner event. May be called once per part of a score; results + // accumulate (object addresses are unique across parts). + void resolvePart(const api::PartData &inPart); + + // The number the writer should emit for the given spanner object, or + // nullopt to omit the attribute. inObject must be the address of the same + // start/continue/stop object (within the ScoreData being written) that + // resolvePart visited. Throws if inNumber is an identity that resolvePart + // never saw -- that is a wiring bug, and omitting the number could emit a + // colliding spanner pair. + std::optional emittedNumber(const api::SpannerNumber &inNumber, const void *inObject) const; + + private: + std::unordered_map myResolved; +}; +} // namespace impl +} // namespace mx diff --git a/src/private/mxtest/api/ApiK009bSlurScoreData.h b/src/private/mxtest/api/ApiK009bSlurScoreData.h index 30501064a..00eca4d04 100644 --- a/src/private/mxtest/api/ApiK009bSlurScoreData.h +++ b/src/private/mxtest/api/ApiK009bSlurScoreData.h @@ -48,7 +48,7 @@ inline mx::api::ScoreData apiK009bSlurAttributesScoreData() note->pitchData.accidental = Accidental::none; note->noteAttachmentData.curveStarts.emplace_back(CurveType::slur); auto &slurStart = note->noteAttachmentData.curveStarts.back(); - slurStart.numberLevel = 1; + slurStart.number = SpannerNumber::makeLevel(1); slurStart.curvePoints.isBezierXSpecified = true; slurStart.curvePoints.bezierX = 1.0; slurStart.curvePoints.isBezierYSpecified = true; @@ -76,7 +76,7 @@ inline mx::api::ScoreData apiK009bSlurAttributesScoreData() note->pitchData.accidental = Accidental::none; note->noteAttachmentData.curveContinuations.emplace_back(CurveType::slur); auto &continue1 = note->noteAttachmentData.curveContinuations.back(); - continue1.numberLevel = 1; + continue1.number = SpannerNumber::makeLevel(1); continue1.isBezierX2Specified = true; continue1.bezierX2 = 5.0; continue1.isBezierY2Specified = true; @@ -98,7 +98,7 @@ inline mx::api::ScoreData apiK009bSlurAttributesScoreData() note->pitchData.accidental = Accidental::none; note->noteAttachmentData.curveContinuations.emplace_back(CurveType::slur); auto &continue2 = note->noteAttachmentData.curveContinuations.back(); - continue2.numberLevel = 1; + continue2.number = SpannerNumber::makeLevel(1); continue2.curvePoints.isBezierXSpecified = true; continue2.curvePoints.bezierX = 9.0; continue2.curvePoints.isBezierYSpecified = true; @@ -120,7 +120,7 @@ inline mx::api::ScoreData apiK009bSlurAttributesScoreData() note->pitchData.accidental = Accidental::none; note->noteAttachmentData.curveStops.emplace_back(CurveType::slur); auto &end = note->noteAttachmentData.curveStops.back(); - end.numberLevel = 1; + end.number = SpannerNumber::makeLevel(1); end.curvePoints.isBezierXSpecified = true; end.curvePoints.bezierX = 53.0; end.curvePoints.isBezierYSpecified = true; diff --git a/src/private/mxtest/api/NoteDataTest.cpp b/src/private/mxtest/api/NoteDataTest.cpp index 805c2c569..2a9a38fb6 100644 --- a/src/private/mxtest/api/NoteDataTest.cpp +++ b/src/private/mxtest/api/NoteDataTest.cpp @@ -677,7 +677,7 @@ TEST(miscFields, NoteData) T_END; // SlurTieNumberLevel tests: build a score with a note that has a tie/slur using -// mx::api directly, round-trip it, then check that numberLevel == -1 (absent). +// mx::api directly, round-trip it, then check that the number is unspecified (absent). TEST(SlurTieNumberLevelA, NoteData) { @@ -715,7 +715,7 @@ TEST(SlurTieNumberLevelA, NoteData) const auto ¬eData = scoreData.parts.at(0).measures.at(0).staves.at(0).voices.at(0).notes.front(); const auto &cs = noteData.noteAttachmentData.curveStarts.front(); - CHECK_EQUAL(-1, cs.numberLevel); + CHECK(cs.number.isUnspecified()); CHECK(cs.curveType == mx::api::CurveType::tie); } @@ -757,7 +757,7 @@ TEST(SlurTieNumberLevelB, NoteData) const auto ¬eData = scoreData.parts.at(0).measures.at(0).staves.at(0).voices.at(0).notes.front(); const auto &cc = noteData.noteAttachmentData.curveContinuations.front(); - CHECK_EQUAL(-1, cc.numberLevel); + CHECK(cc.number.isUnspecified()); CHECK(cc.curveType == mx::api::CurveType::tie); } @@ -799,7 +799,7 @@ TEST(SlurTieNumberLevelC, NoteData) const auto ¬eData = scoreData.parts.at(0).measures.at(0).staves.at(0).voices.at(0).notes.front(); const auto &cs = noteData.noteAttachmentData.curveStops.front(); - CHECK_EQUAL(-1, cs.numberLevel); + CHECK(cs.number.isUnspecified()); CHECK(cs.curveType == mx::api::CurveType::tie); } diff --git a/src/private/mxtest/api/SlurTest.cpp b/src/private/mxtest/api/SlurTest.cpp index 9fcf2ae43..c404eee4d 100644 --- a/src/private/mxtest/api/SlurTest.cpp +++ b/src/private/mxtest/api/SlurTest.cpp @@ -33,9 +33,9 @@ TEST(startStop, Slurs) auto voice = staff.voices.at(0); auto note = voice.notes.at(3); auto stop = note.noteAttachmentData.curveStops.at(0); - CHECK_EQUAL(1, stop.numberLevel); + CHECK(SpannerNumber::makeLevel(1) == stop.number); auto start = note.noteAttachmentData.curveStarts.at(0); - CHECK_EQUAL(1, start.numberLevel); + CHECK(SpannerNumber::makeLevel(1) == start.number); // Write to XML and assert that stop happens before start const auto xml = toXml(scoreData); diff --git a/src/private/mxtest/api/SpannerIdentityTest.cpp b/src/private/mxtest/api/SpannerIdentityTest.cpp new file mode 100644 index 000000000..d70acd99e --- /dev/null +++ b/src/private/mxtest/api/SpannerIdentityTest.cpp @@ -0,0 +1,315 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +// Tests for writer-side spanner number assignment (issue #297). Spanners +// authored with SpannerNumber::makeIdentity carry no number of their own; the +// writer must assign MusicXML numbers from the actual serialization order, +// which differs from musical-time order when voices are interleaved with +// . + +#include "mxtest/control/CompileControl.h" +#ifdef MX_COMPILE_API_TESTS + +#include "cpul/cpulTestHarness.h" +#include "mx/api/DocumentManager.h" +#include "mxtest/api/TestHelpers.h" + +using namespace mx::api; +using namespace mxtest; + +namespace spannerIdentityTest +{ +constexpr int ticksPerQuarter = 2; + +inline NoteData makeQuarter(int inTick, Step inStep, int inOctave) +{ + NoteData note; + note.tickTimePosition = inTick; + note.pitchData.step = inStep; + note.pitchData.octave = inOctave; + note.durationData.durationName = DurationName::quarter; + note.durationData.durationTimeTicks = ticksPerQuarter; + return note; +} + +// The #297 scenario: one staff, two voices, two measures. +// +// voice 1 (map key 0): m1 has no slurs; m2 carries slur "c" (tick 2 -> 4). +// voice 2 (map key 1): m1 carries slur "b" (tick 0 -> 2) and starts slur "a" +// (tick 4); "a" stops in m2 at tick 0. +// +// Musically "a" and "c" do not overlap: "a" spans m1 tick 4 through m2 tick 0 +// and "c" spans m2 ticks 2..4. But voice 1 of m2 is serialized before voice 2 +// of m2, so a streaming reader encounters "c"'s start and stop while "a" is +// still open: they must carry different numbers. +inline ScoreData makeVoiceReorderingScore(const SpannerNumber &inA, const SpannerNumber &inB, const SpannerNumber &inC) +{ + ScoreData score; + score.ticksPerQuarter = ticksPerQuarter; + score.parts.emplace_back(); + auto &part = score.parts.back(); + + // measure 1 + part.measures.emplace_back(); + { + auto &measure = part.measures.back(); + measure.timeSignature.beats = "4"; + measure.timeSignature.beatType = "4"; + measure.timeSignature.isImplicit = false; + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + + auto &voice1 = staff.voices[0]; + for (int i = 0; i < 4; ++i) + { + voice1.notes.push_back(makeQuarter(i * ticksPerQuarter, Step::g, 4)); + } + + auto &voice2 = staff.voices[1]; + for (int i = 0; i < 4; ++i) + { + voice2.notes.push_back(makeQuarter(i * ticksPerQuarter, Step::c, 3)); + } + + CurveStart bStart{CurveType::slur}; + bStart.number = inB; + voice2.notes.at(0).noteAttachmentData.curveStarts.push_back(bStart); + + CurveStop bStop{CurveType::slur}; + bStop.number = inB; + voice2.notes.at(1).noteAttachmentData.curveStops.push_back(bStop); + + CurveStart aStart{CurveType::slur}; + aStart.number = inA; + voice2.notes.at(2).noteAttachmentData.curveStarts.push_back(aStart); + } + + // measure 2 + part.measures.emplace_back(); + { + auto &measure = part.measures.back(); + measure.timeSignature.beats = "4"; + measure.timeSignature.beatType = "4"; + measure.timeSignature.isImplicit = true; + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + + auto &voice1 = staff.voices[0]; + for (int i = 0; i < 4; ++i) + { + voice1.notes.push_back(makeQuarter(i * ticksPerQuarter, Step::a, 4)); + } + + auto &voice2 = staff.voices[1]; + for (int i = 0; i < 4; ++i) + { + voice2.notes.push_back(makeQuarter(i * ticksPerQuarter, Step::d, 3)); + } + + CurveStart cStart{CurveType::slur}; + cStart.number = inC; + voice1.notes.at(1).noteAttachmentData.curveStarts.push_back(cStart); + + CurveStop cStop{CurveType::slur}; + cStop.number = inC; + voice1.notes.at(2).noteAttachmentData.curveStops.push_back(cStop); + + CurveStop aStop{CurveType::slur}; + aStop.number = inA; + voice2.notes.at(0).noteAttachmentData.curveStops.push_back(aStop); + } + + return score; +} +} // namespace spannerIdentityTest + +// Three identity slurs. In the serialized stream: "b" opens and closes, "a" +// opens (all in measure 1 voice 2), then measure 2 serializes voice 1 first, +// so "c" opens and closes while "a" is still open. Expect "b" to take 1, +// "a" to reuse 1 (no stream overlap with "b"), and "c" to be forced to 2. +TEST(identityAssignsNumbersFromSerializationOrder, SpannerIdentity) +{ + using namespace spannerIdentityTest; + const auto score = makeVoiceReorderingScore(SpannerNumber::makeIdentity("a"), SpannerNumber::makeIdentity("b"), + SpannerNumber::makeIdentity("c")); + + const auto xml = toXml(score); + REQUIRE(!xml.empty()); + const auto roundTripped = fromXml(xml); + + REQUIRE(roundTripped.parts.size() == 1); + const auto &measure1 = roundTripped.parts.at(0).measures.at(0); + const auto &measure2 = roundTripped.parts.at(0).measures.at(1); + const auto &m1Voice2 = measure1.staves.at(0).voices.at(1); + const auto &m2Voice1 = measure2.staves.at(0).voices.at(0); + const auto &m2Voice2 = measure2.staves.at(0).voices.at(1); + + // b + REQUIRE(m1Voice2.notes.at(0).noteAttachmentData.curveStarts.size() == 1); + CHECK(SpannerNumber::makeLevel(1) == m1Voice2.notes.at(0).noteAttachmentData.curveStarts.at(0).number); + REQUIRE(m1Voice2.notes.at(1).noteAttachmentData.curveStops.size() == 1); + CHECK(SpannerNumber::makeLevel(1) == m1Voice2.notes.at(1).noteAttachmentData.curveStops.at(0).number); + + // a: reuses 1 because b released it earlier in the stream + REQUIRE(m1Voice2.notes.at(2).noteAttachmentData.curveStarts.size() == 1); + CHECK(SpannerNumber::makeLevel(1) == m1Voice2.notes.at(2).noteAttachmentData.curveStarts.at(0).number); + REQUIRE(m2Voice2.notes.at(0).noteAttachmentData.curveStops.size() == 1); + CHECK(SpannerNumber::makeLevel(1) == m2Voice2.notes.at(0).noteAttachmentData.curveStops.at(0).number); + + // c: overlaps a in the stream (voice 1 of measure 2 is serialized before + // voice 2 carries a's stop) so it must not share a's number + REQUIRE(m2Voice1.notes.at(1).noteAttachmentData.curveStarts.size() == 1); + CHECK(SpannerNumber::makeLevel(2) == m2Voice1.notes.at(1).noteAttachmentData.curveStarts.at(0).number); + REQUIRE(m2Voice1.notes.at(2).noteAttachmentData.curveStops.size() == 1); + CHECK(SpannerNumber::makeLevel(2) == m2Voice1.notes.at(2).noteAttachmentData.curveStops.at(0).number); +} + +T_END + +// An explicit spanner reserves its number while it is open in the stream: an +// identity spanner whose extent covers the explicit one must skip that number. +TEST(explicitAndIdentityShareThePool, SpannerIdentity) +{ + using namespace spannerIdentityTest; + const auto score = makeVoiceReorderingScore(SpannerNumber::makeIdentity("a"), SpannerNumber::makeIdentity("b"), + SpannerNumber::makeLevel(1)); + + const auto xml = toXml(score); + REQUIRE(!xml.empty()); + const auto roundTripped = fromXml(xml); + + const auto &measure1 = roundTripped.parts.at(0).measures.at(0); + const auto &measure2 = roundTripped.parts.at(0).measures.at(1); + const auto &m1Voice2 = measure1.staves.at(0).voices.at(1); + const auto &m2Voice1 = measure2.staves.at(0).voices.at(0); + const auto &m2Voice2 = measure2.staves.at(0).voices.at(1); + + // c is explicit and emitted verbatim + CHECK(SpannerNumber::makeLevel(1) == m2Voice1.notes.at(1).noteAttachmentData.curveStarts.at(0).number); + CHECK(SpannerNumber::makeLevel(1) == m2Voice1.notes.at(2).noteAttachmentData.curveStops.at(0).number); + + // a's stream extent covers c's reservation of 1, so a takes 2 + CHECK(SpannerNumber::makeLevel(2) == m1Voice2.notes.at(2).noteAttachmentData.curveStarts.at(0).number); + CHECK(SpannerNumber::makeLevel(2) == m2Voice2.notes.at(0).noteAttachmentData.curveStops.at(0).number); + + // b does not overlap the reservation and still gets 1 + CHECK(SpannerNumber::makeLevel(1) == m1Voice2.notes.at(0).noteAttachmentData.curveStarts.at(0).number); + CHECK(SpannerNumber::makeLevel(1) == m1Voice2.notes.at(1).noteAttachmentData.curveStops.at(0).number); +} + +T_END + +// Slur and tied numbers come from separate pools: two concurrent identity +// spanners of different classes both get number 1. +TEST(slurAndTiePoolsAreIndependent, SpannerIdentity) +{ + using namespace spannerIdentityTest; + + ScoreData score; + score.ticksPerQuarter = ticksPerQuarter; + score.parts.emplace_back(); + auto &part = score.parts.back(); + part.measures.emplace_back(); + auto &measure = part.measures.back(); + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + auto &voice = staff.voices[0]; + voice.notes.push_back(makeQuarter(0, Step::c, 4)); + voice.notes.push_back(makeQuarter(ticksPerQuarter, Step::c, 4)); + + CurveStart slurStart{CurveType::slur}; + slurStart.number = SpannerNumber::makeIdentity("s"); + CurveStart tieStart{CurveType::tie}; + tieStart.number = SpannerNumber::makeIdentity("t"); + voice.notes.at(0).noteAttachmentData.curveStarts.push_back(slurStart); + voice.notes.at(0).noteAttachmentData.curveStarts.push_back(tieStart); + + CurveStop slurStop{CurveType::slur}; + slurStop.number = SpannerNumber::makeIdentity("s"); + CurveStop tieStop{CurveType::tie}; + tieStop.number = SpannerNumber::makeIdentity("t"); + voice.notes.at(1).noteAttachmentData.curveStops.push_back(slurStop); + voice.notes.at(1).noteAttachmentData.curveStops.push_back(tieStop); + + const auto xml = toXml(score); + REQUIRE(!xml.empty()); + const auto roundTripped = fromXml(xml); + + const auto ¬es = roundTripped.parts.at(0).measures.at(0).staves.at(0).voices.at(0).notes; + REQUIRE(notes.at(0).noteAttachmentData.curveStarts.size() == 2); + for (const auto &start : notes.at(0).noteAttachmentData.curveStarts) + { + CHECK(SpannerNumber::makeLevel(1) == start.number); + } + REQUIRE(notes.at(1).noteAttachmentData.curveStops.size() == 2); + for (const auto &stop : notes.at(1).noteAttachmentData.curveStops) + { + CHECK(SpannerNumber::makeLevel(1) == stop.number); + } +} + +T_END + +// Wedges now round-trip their numbers: an explicit number is emitted verbatim +// and an identity wedge gets an assigned number. +TEST(wedgeNumbersRoundTrip, SpannerIdentity) +{ + using namespace spannerIdentityTest; + + ScoreData score; + score.ticksPerQuarter = ticksPerQuarter; + score.parts.emplace_back(); + auto &part = score.parts.back(); + part.measures.emplace_back(); + auto &measure = part.measures.back(); + measure.staves.emplace_back(); + auto &staff = measure.staves.back(); + auto &voice = staff.voices[0]; + for (int i = 0; i < 4; ++i) + { + voice.notes.push_back(makeQuarter(i * ticksPerQuarter, Step::e, 4)); + } + + DirectionData startDirection; + startDirection.tickTimePosition = 0; + WedgeStart wedgeStart; + wedgeStart.wedgeType = WedgeType::crescendo; + wedgeStart.number = SpannerNumber::makeLevel(3); + startDirection.wedgeStarts.push_back(wedgeStart); + staff.directions.push_back(startDirection); + + DirectionData stopDirection; + stopDirection.tickTimePosition = 2 * ticksPerQuarter; + WedgeStop wedgeStop; + wedgeStop.number = SpannerNumber::makeLevel(3); + stopDirection.wedgeStops.push_back(wedgeStop); + staff.directions.push_back(stopDirection); + + const auto xml = toXml(score); + REQUIRE(!xml.empty()); + const auto roundTripped = fromXml(xml); + + const auto &directions = roundTripped.parts.at(0).measures.at(0).staves.at(0).directions; + bool foundStart = false; + bool foundStop = false; + for (const auto &direction : directions) + { + for (const auto &start : direction.wedgeStarts) + { + foundStart = true; + CHECK(SpannerNumber::makeLevel(3) == start.number); + } + for (const auto &stop : direction.wedgeStops) + { + foundStop = true; + CHECK(SpannerNumber::makeLevel(3) == stop.number); + } + } + CHECK(foundStart); + CHECK(foundStop); +} + +T_END + +#endif diff --git a/src/private/mxtest/api/SpannerNumberTest.cpp b/src/private/mxtest/api/SpannerNumberTest.cpp new file mode 100644 index 000000000..97c12077a --- /dev/null +++ b/src/private/mxtest/api/SpannerNumberTest.cpp @@ -0,0 +1,110 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +#include "mxtest/control/CompileControl.h" +#ifdef MX_COMPILE_API_TESTS + +#include "cpul/cpulTestHarness.h" +#include "mx/api/ApiCommon.h" + +#include + +using namespace mx::api; + +TEST(defaultIsUnspecified, SpannerNumber) +{ + const SpannerNumber number; + CHECK(SpannerNumber::Kind::unspecified == number.kind()); + CHECK(number.isUnspecified()); + CHECK(!number.isExplicit()); + CHECK(!number.isIdentity()); +} + +T_END + +TEST(makeLevelRoundTrip, SpannerNumber) +{ + const auto lowest = SpannerNumber::makeLevel(1); + CHECK(SpannerNumber::Kind::explicitLevel == lowest.kind()); + CHECK(lowest.isExplicit()); + CHECK(!lowest.isUnspecified()); + CHECK(!lowest.isIdentity()); + CHECK_EQUAL(1, lowest.level()); + + const auto highest = SpannerNumber::makeLevel(16); + CHECK_EQUAL(16, highest.level()); +} + +T_END + +TEST(makeIdentityRoundTrip, SpannerNumber) +{ + const auto number = SpannerNumber::makeIdentity("the-id"); + CHECK(SpannerNumber::Kind::identity == number.kind()); + CHECK(number.isIdentity()); + CHECK(!number.isUnspecified()); + CHECK(!number.isExplicit()); + CHECK_EQUAL(std::string{"the-id"}, number.identity()); +} + +T_END + +TEST(makeLevelRangePrecondition, SpannerNumber) +{ + CHECK_THROWS_AS(SpannerNumber::makeLevel(0), std::logic_error); + CHECK_THROWS_AS(SpannerNumber::makeLevel(17), std::logic_error); + CHECK_THROWS_AS(SpannerNumber::makeLevel(-1), std::logic_error); +} + +T_END + +TEST(makeIdentityEmptyPrecondition, SpannerNumber) +{ + CHECK_THROWS_AS(SpannerNumber::makeIdentity(""), std::logic_error); +} + +T_END + +TEST(wrongKindAccessors, SpannerNumber) +{ + const SpannerNumber unspecified; + CHECK_THROWS_AS(unspecified.level(), std::logic_error); + CHECK_THROWS_AS(unspecified.identity(), std::logic_error); + + const auto level = SpannerNumber::makeLevel(3); + CHECK_THROWS_AS(level.identity(), std::logic_error); + + const auto identity = SpannerNumber::makeIdentity("x"); + CHECK_THROWS_AS(identity.level(), std::logic_error); +} + +T_END + +TEST(equality, SpannerNumber) +{ + const SpannerNumber unspecifiedA; + const SpannerNumber unspecifiedB; + const auto level2 = SpannerNumber::makeLevel(2); + const auto level2Again = SpannerNumber::makeLevel(2); + const auto level3 = SpannerNumber::makeLevel(3); + const auto identityA = SpannerNumber::makeIdentity("a"); + const auto identityAAgain = SpannerNumber::makeIdentity("a"); + const auto identityB = SpannerNumber::makeIdentity("b"); + + // same kind + CHECK(unspecifiedA == unspecifiedB); + CHECK(level2 == level2Again); + CHECK(level2 != level3); + CHECK(identityA == identityAAgain); + CHECK(identityA != identityB); + + // different kinds are never equal + CHECK(unspecifiedA != level2); + CHECK(unspecifiedA != identityA); + CHECK(level2 != identityA); +} + +T_END + +#endif diff --git a/src/private/mxtest/api/roundtrip-baseline.txt b/src/private/mxtest/api/roundtrip-baseline.txt index 02d9bb0e0..e9e0f307e 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -320,3 +320,9 @@ lysuite/ly11h_TimeSignatures_SenzaMisura.xml # trims just those unrelated gaps so the composite time-signature round-trip # itself is pinned. rpatters1/timesigs_composite-ref-modified.musicxml + +# The issue #297 spanner-number scenario (unified SpannerNumber + writer-side +# assignment): two slurs that do not overlap in musical time but do overlap in +# the serialized stream once voice 1 is written ahead of voice 2, plus a wedge. +# Pins explicit slur/wedge numbers surviving read -> write -> read verbatim. +custom/slurs_overbars.musicxml diff --git a/src/private/mxtest/impl/CurveFunctionsTest.cpp b/src/private/mxtest/impl/CurveFunctionsTest.cpp index c8dd0220d..cb8f71089 100644 --- a/src/private/mxtest/impl/CurveFunctionsTest.cpp +++ b/src/private/mxtest/impl/CurveFunctionsTest.cpp @@ -45,7 +45,7 @@ template T seed() api::CurveStart seedStart(api::CurveType t) { api::CurveStart c{t}; - c.numberLevel = 2; + c.number = api::SpannerNumber::makeLevel(2); c.curvePoints.isBezierOffsetSpecified = true; c.curvePoints.bezierOffset = 1.0; c.curvePoints.isBezierXSpecified = true; @@ -78,7 +78,7 @@ api::CurveStart seedStart(api::CurveType t) api::CurveContinue seedContinue(api::CurveType t) { api::CurveContinue c{t}; - c.numberLevel = 2; + c.number = api::SpannerNumber::makeLevel(2); c.curvePoints.isBezierOffsetSpecified = true; c.curvePoints.bezierOffset = 1.0; c.curvePoints.isBezierXSpecified = true; @@ -106,7 +106,7 @@ api::CurveContinue seedContinue(api::CurveType t) api::CurveStop seedStop(api::CurveType t) { api::CurveStop c{t}; - c.numberLevel = 2; + c.number = api::SpannerNumber::makeLevel(2); c.curvePoints.isBezierOffsetSpecified = true; c.curvePoints.bezierOffset = 1.0; c.curvePoints.isBezierXSpecified = true; @@ -137,13 +137,13 @@ TEST(parseCurveStart_curveType, CurveFunctions) T_END -TEST(parseCurveStart_numberLevel, CurveFunctions) +TEST(parseCurveStart_number, CurveFunctions) { using namespace mx::impl; using namespace mx; auto e = seed(); auto c = impl::parseCurveStart(e); - CHECK_EQUAL(2, c.numberLevel); + CHECK(api::SpannerNumber::makeLevel(2) == c.number); } T_END @@ -311,13 +311,13 @@ TEST(parseCurveContinue_curveType, CurveFunctions) T_END -TEST(parseCurveContinue_numberLevel, CurveFunctions) +TEST(parseCurveContinue_number, CurveFunctions) { using namespace mx::impl; using namespace mx; auto e = seed(); auto c = impl::parseCurveContinue(e); - CHECK_EQUAL(2, c.numberLevel); + CHECK(api::SpannerNumber::makeLevel(2) == c.number); } T_END @@ -455,13 +455,13 @@ TEST(parseCurveStop_curveType, CurveFunctions) T_END -TEST(parseCurveStop_numberLevel, CurveFunctions) +TEST(parseCurveStop_number, CurveFunctions) { using namespace mx::impl; using namespace mx; auto e = seed(); auto c = impl::parseCurveStop(e); - CHECK_EQUAL(2, c.numberLevel); + CHECK(api::SpannerNumber::makeLevel(2) == c.number); } T_END @@ -558,7 +558,7 @@ TEST(writeAttributesFromCurveStart_type, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(core::StartStopContinue::start() == attr.type()); } @@ -570,7 +570,7 @@ TEST(writeAttributesFromCurveStart_number, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK_EQUAL(2, attr.number()->value()); } @@ -582,7 +582,7 @@ TEST(writeAttributesFromCurveStart_bezierOffset, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.bezierOffset().has_value()); CHECK_DOUBLES_EQUAL(1.0, attr.bezierOffset()->value().value(), 0.01); } @@ -595,7 +595,7 @@ TEST(writeAttributesFromCurveStart_bezierX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.bezierX().has_value()); CHECK_DOUBLES_EQUAL(13.0, attr.bezierX()->value().value(), 0.01); } @@ -608,7 +608,7 @@ TEST(writeAttributesFromCurveStart_bezierY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.bezierY().has_value()); CHECK_DOUBLES_EQUAL(14.0, attr.bezierY()->value().value(), 0.01); } @@ -621,7 +621,7 @@ TEST(writeAttributesFromCurveStart_defaultX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.defaultX().has_value()); CHECK_DOUBLES_EQUAL(3.0, attr.defaultX()->value().value(), 0.01); } @@ -634,7 +634,7 @@ TEST(writeAttributesFromCurveStart_defaultY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.defaultY().has_value()); CHECK_DOUBLES_EQUAL(4.0, attr.defaultY()->value().value(), 0.01); } @@ -647,7 +647,7 @@ TEST(writeAttributesFromCurveStart_relativeX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.relativeX().has_value()); CHECK_DOUBLES_EQUAL(5.0, attr.relativeX()->value().value(), 0.01); } @@ -660,7 +660,7 @@ TEST(writeAttributesFromCurveStart_relativeY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.relativeY().has_value()); CHECK_DOUBLES_EQUAL(6.0, attr.relativeY()->value().value(), 0.01); } @@ -673,7 +673,7 @@ TEST(writeAttributesFromCurveStart_color, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.color().has_value()); CHECK_EQUAL(8, static_cast(attr.color()->red())); CHECK_EQUAL(9, static_cast(attr.color()->green())); @@ -689,7 +689,7 @@ TEST(writeAttributesFromCurveStart_lineType, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.lineType().has_value()); CHECK(core::LineType::wavy() == *attr.lineType()); } @@ -702,7 +702,7 @@ TEST(writeAttributesFromCurveStart_dashLength, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.dashLength().has_value()); CHECK_DOUBLES_EQUAL(11.0, attr.dashLength()->value().value(), 0.01); } @@ -715,7 +715,7 @@ TEST(writeAttributesFromCurveStart_spaceLength, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.spaceLength().has_value()); CHECK_DOUBLES_EQUAL(12.0, attr.spaceLength()->value().value(), 0.01); } @@ -728,7 +728,7 @@ TEST(writeAttributesFromCurveStart_placement, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.placement().has_value()); CHECK(core::AboveBelow::below() == *attr.placement()); } @@ -741,7 +741,7 @@ TEST(writeAttributesFromCurveStart_orientation, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStart(api::CurveType::slur); - impl::writeAttributesFromCurveStart(c, attr); + impl::writeAttributesFromCurveStart(c, attr, 2); CHECK(attr.orientation().has_value()); CHECK(core::OverUnder::over() == *attr.orientation()); } @@ -756,7 +756,7 @@ TEST(writeAttributesFromCurveContinue_type, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(core::StartStopContinue::continue_() == attr.type()); } @@ -768,7 +768,7 @@ TEST(writeAttributesFromCurveContinue_number, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK_EQUAL(2, attr.number()->value()); } @@ -780,7 +780,7 @@ TEST(writeAttributesFromCurveContinue_bezierOffset, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierOffset().has_value()); CHECK_DOUBLES_EQUAL(1.0, attr.bezierOffset()->value().value(), 0.01); } @@ -793,7 +793,7 @@ TEST(writeAttributesFromCurveContinue_bezierX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierX().has_value()); CHECK_DOUBLES_EQUAL(13.0, attr.bezierX()->value().value(), 0.01); } @@ -806,7 +806,7 @@ TEST(writeAttributesFromCurveContinue_bezierY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierY().has_value()); CHECK_DOUBLES_EQUAL(14.0, attr.bezierY()->value().value(), 0.01); } @@ -819,7 +819,7 @@ TEST(writeAttributesFromCurveContinue_defaultX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.defaultX().has_value()); CHECK_DOUBLES_EQUAL(3.0, attr.defaultX()->value().value(), 0.01); } @@ -832,7 +832,7 @@ TEST(writeAttributesFromCurveContinue_defaultY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.defaultY().has_value()); CHECK_DOUBLES_EQUAL(4.0, attr.defaultY()->value().value(), 0.01); } @@ -845,7 +845,7 @@ TEST(writeAttributesFromCurveContinue_relativeX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.relativeX().has_value()); CHECK_DOUBLES_EQUAL(5.0, attr.relativeX()->value().value(), 0.01); } @@ -858,7 +858,7 @@ TEST(writeAttributesFromCurveContinue_relativeY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.relativeY().has_value()); CHECK_DOUBLES_EQUAL(6.0, attr.relativeY()->value().value(), 0.01); } @@ -871,7 +871,7 @@ TEST(writeAttributesFromCurveContinue_bezierX2, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierX2().has_value()); CHECK_DOUBLES_EQUAL(100.0, attr.bezierX2()->value().value(), 0.01); } @@ -884,7 +884,7 @@ TEST(writeAttributesFromCurveContinue_bezierY2, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierY2().has_value()); CHECK_DOUBLES_EQUAL(101.0, attr.bezierY2()->value().value(), 0.01); } @@ -897,7 +897,7 @@ TEST(writeAttributesFromCurveContinue_bezierOffset2, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedContinue(api::CurveType::slur); - impl::writeAttributesFromCurveContinue(c, attr); + impl::writeAttributesFromCurveContinue(c, attr, 2); CHECK(attr.bezierOffset2().has_value()); CHECK_DOUBLES_EQUAL(102.0, attr.bezierOffset2()->value().value(), 0.01); } @@ -912,7 +912,7 @@ TEST(writeAttributesFromCurveStop_type, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(core::StartStopContinue::stop() == attr.type()); } @@ -924,7 +924,7 @@ TEST(writeAttributesFromCurveStop_number, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK_EQUAL(2, attr.number()->value()); } @@ -936,7 +936,7 @@ TEST(writeAttributesFromCurveStop_bezierOffset, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.bezierOffset().has_value()); CHECK_DOUBLES_EQUAL(1.0, attr.bezierOffset()->value().value(), 0.01); } @@ -949,7 +949,7 @@ TEST(writeAttributesFromCurveStop_bezierX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.bezierX().has_value()); CHECK_DOUBLES_EQUAL(13.0, attr.bezierX()->value().value(), 0.01); } @@ -962,7 +962,7 @@ TEST(writeAttributesFromCurveStop_bezierY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.bezierY().has_value()); CHECK_DOUBLES_EQUAL(14.0, attr.bezierY()->value().value(), 0.01); } @@ -975,7 +975,7 @@ TEST(writeAttributesFromCurveStop_defaultX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.defaultX().has_value()); CHECK_DOUBLES_EQUAL(3.0, attr.defaultX()->value().value(), 0.01); } @@ -988,7 +988,7 @@ TEST(writeAttributesFromCurveStop_defaultY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.defaultY().has_value()); CHECK_DOUBLES_EQUAL(4.0, attr.defaultY()->value().value(), 0.01); } @@ -1001,7 +1001,7 @@ TEST(writeAttributesFromCurveStop_relativeX, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.relativeX().has_value()); CHECK_DOUBLES_EQUAL(5.0, attr.relativeX()->value().value(), 0.01); } @@ -1014,7 +1014,7 @@ TEST(writeAttributesFromCurveStop_relativeY, CurveFunctions) using namespace mx; core::Slur attr; auto c = seedStop(api::CurveType::slur); - impl::writeAttributesFromCurveStop(c, attr); + impl::writeAttributesFromCurveStop(c, attr, 2); CHECK(attr.relativeY().has_value()); CHECK_DOUBLES_EQUAL(6.0, attr.relativeY()->value().value(), 0.01); } diff --git a/src/private/mxtest/impl/DirectionReaderTest.cpp b/src/private/mxtest/impl/DirectionReaderTest.cpp index 50ff08320..204ead65e 100644 --- a/src/private/mxtest/impl/DirectionReaderTest.cpp +++ b/src/private/mxtest/impl/DirectionReaderTest.cpp @@ -79,12 +79,12 @@ TEST(ottavaStart8vaAnd8vb, DirectionReader) CHECK_EQUAL(tickTimePosition, ottavaStart.spannerStart.tickTimePosition); CHECK(api::OttavaType::o8vb == ottavaStart.ottavaType); - CHECK_EQUAL(-1, ottavaStart.spannerStart.numberLevel); + CHECK(ottavaStart.spannerStart.number.isUnspecified()); ottavaStart = directionData.ottavaStarts.at(1); CHECK_EQUAL(tickTimePosition, ottavaStart.spannerStart.tickTimePosition); CHECK(api::OttavaType::o8va == ottavaStart.ottavaType); - CHECK_EQUAL(3, ottavaStart.spannerStart.numberLevel); + CHECK(api::SpannerNumber::makeLevel(3) == ottavaStart.spannerStart.number); } T_END @@ -106,7 +106,7 @@ TEST(ottavaStop, DirectionReader) CHECK_EQUAL(1, directionData.ottavaStops.size()); const auto &ottavaStop = directionData.ottavaStops.front(); CHECK_EQUAL(tickTimePosition, ottavaStop.spannerStop.tickTimePosition); - CHECK_EQUAL(-1, ottavaStop.spannerStop.numberLevel); + CHECK(ottavaStop.spannerStop.number.isUnspecified()); CHECK(ottavaStop.size.has_value()); CHECK_EQUAL(15, *ottavaStop.size); } diff --git a/src/private/mxtest/impl/DirectionWriterTest.cpp b/src/private/mxtest/impl/DirectionWriterTest.cpp index 4dc8b2590..9c3f7ce8f 100644 --- a/src/private/mxtest/impl/DirectionWriterTest.cpp +++ b/src/private/mxtest/impl/DirectionWriterTest.cpp @@ -14,6 +14,7 @@ #include "mx/core/generated/OctaveShift.h" #include "mx/impl/DirectionReader.h" #include "mx/impl/DirectionWriter.h" +#include "mx/impl/SpannerNumberResolver.h" #include @@ -28,7 +29,7 @@ TEST(ottavaStartStop, DirectionWriter) directionData.ottavaStops.emplace_back(api::OttavaStop{}); auto &stop = directionData.ottavaStops.back(); - stop.spannerStop.numberLevel = 2; + stop.spannerStop.number = api::SpannerNumber::makeLevel(2); stop.size = 15; directionData.ottavaStarts.emplace_back(api::OttavaStart{}); @@ -37,7 +38,8 @@ TEST(ottavaStartStop, DirectionWriter) start.spannerStart.positionData.isDefaultXSpecified = true; start.spannerStart.positionData.defaultX = 100.0; - DirectionWriter writer{directionData, cursor}; + SpannerNumberResolver numberResolver; + DirectionWriter writer{directionData, cursor, numberResolver}; const auto mdcSet = writer.getDirectionLikeThings(); CHECK(mdcSet.front().isDirection()); const auto &direction = mdcSet.front().asDirection(); @@ -49,7 +51,7 @@ TEST(ottavaStartStop, DirectionWriter) const auto roundTripped = reader.getDirectionData(); REQUIRE(roundTripped.ottavaStops.size() == 1); const auto &roundTrippedStop = roundTripped.ottavaStops.front(); - CHECK_EQUAL(2, roundTrippedStop.spannerStop.numberLevel); + CHECK(api::SpannerNumber::makeLevel(2) == roundTrippedStop.spannerStop.number); CHECK(roundTrippedStop.size.has_value()); CHECK_EQUAL(15, *roundTrippedStop.size); } @@ -110,7 +112,8 @@ TEST(segnoAndCodaRoundTrip, DirectionWriter) directionData.codas.push_back(coda); Cursor cursor{1, 100}; - DirectionWriter writer{directionData, cursor}; + SpannerNumberResolver numberResolver; + DirectionWriter writer{directionData, cursor, numberResolver}; const auto mdcSet = writer.getDirectionLikeThings(); REQUIRE(mdcSet.size() >= 1); CHECK(mdcSet.front().isDirection()); @@ -161,7 +164,8 @@ TEST(rehearsalRoundTrip, DirectionWriter) directionData.rehearsals.push_back(rehearsal); Cursor cursor{1, 100}; - DirectionWriter writer{directionData, cursor}; + SpannerNumberResolver numberResolver; + DirectionWriter writer{directionData, cursor, numberResolver}; const auto mdcSet = writer.getDirectionLikeThings(); REQUIRE(mdcSet.size() >= 1); CHECK(mdcSet.front().isDirection()); From e36ed5e8c988f258fa60828c4472ecdc018c6169 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Tue, 7 Jul 2026 17:48:37 +0000 Subject: [PATCH 2/3] fix: repair rebase fallout in SpannerNumberResolver The rebase onto main picked up the new api::OttavaStop type (a SpannerStop plus an optional size, from the octave-shift stop-size change) everywhere except SpannerNumberResolver.cpp, which still read item.number on ottava stops and did not compile. Register ottava stops by their nested spannerStop (matching the address DirectionWriter uses for lookup) and re-run clang-format over the conflict-resolved files. --- src/private/mx/impl/DirectionWriter.cpp | 5 +++-- src/private/mx/impl/SpannerNumberResolver.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/private/mx/impl/DirectionWriter.cpp b/src/private/mx/impl/DirectionWriter.cpp index ef542ab05..dd3d523f5 100644 --- a/src/private/mx/impl/DirectionWriter.cpp +++ b/src/private/mx/impl/DirectionWriter.cpp @@ -320,8 +320,9 @@ void DirectionWriter::emitWedgeStart(const api::WedgeStart &wedgeStart, core::Di void DirectionWriter::emitOttavaStop(const api::OttavaStop &ottavaStop, core::Direction &direction) { core::OctaveShift os{}; - setAttributesFromSpannerStop(ottavaStop.spannerStop, os, - myNumberResolver.emittedNumber(ottavaStop.spannerStop.number, &ottavaStop.spannerStop)); + setAttributesFromSpannerStop( + ottavaStop.spannerStop, os, + myNumberResolver.emittedNumber(ottavaStop.spannerStop.number, &ottavaStop.spannerStop)); os.setType(core::UpDownStopContinue::stop()); os.setSize(ottavaStop.size); core::DirectionType dt{}; diff --git a/src/private/mx/impl/SpannerNumberResolver.cpp b/src/private/mx/impl/SpannerNumberResolver.cpp index 737c28c6a..344849e47 100644 --- a/src/private/mx/impl/SpannerNumberResolver.cpp +++ b/src/private/mx/impl/SpannerNumberResolver.cpp @@ -135,7 +135,7 @@ class SpannerNumberEventCollector } for (const auto &item : inDirection.ottavaStops) { - add(SpannerNumberClass::octaveShift, inStaffIndex, &item, item.number, false, true); + add(SpannerNumberClass::octaveShift, inStaffIndex, &item.spannerStop, item.spannerStop.number, false, true); } for (const auto &item : inDirection.ottavaStarts) { @@ -194,7 +194,8 @@ class SpannerNumberEventCollector if (index < inDirection.ottavaStops.size()) { const auto &item = inDirection.ottavaStops.at(index); - add(SpannerNumberClass::octaveShift, inStaffIndex, &item, item.number, false, true); + add(SpannerNumberClass::octaveShift, inStaffIndex, &item.spannerStop, item.spannerStop.number, + false, true); } break; From 6e8766681095746db4bdd02474073a75a46ec93b Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Sat, 11 Jul 2026 14:51:20 +0000 Subject: [PATCH 3/3] fix: finish repairing rebase fallout from the #321 time-signature rebase The rebase onto main also picked up #321's TimeChoice split of MeasureData::timeSignature into a variant of TimeSignatureData/ ComplexTimeSignature, but SpannerIdentityTest.cpp (added by this PR) still set the old direct beats/beatType members, which no longer exist and failed to compile everywhere. Separately, main had already grown its own corert-pinned file count to 834 (a new rpatters1 fixture from #321) before this PR's own slurs_overbars fixture was rebased on top, so the true total is 835, not the 834 the previous repair commit left in place. Ran `make audit` to regenerate the missing feature sidecar and corpus.xml, and bumped the pinned count accordingly. Verified locally: make test, test-core-dev, test-api-roundtrip, test-cpp-unit, validate-cpp, probe-cpp, check, check-core-dev, and a unity build all pass. --- data/corpus.xml | 687 +++++++++--------- ...mesigs_composite-ref-modified.features.xml | 249 +++++++ .../mxtest/api/SpannerIdentityTest.cpp | 6 +- .../mxtest/corert/CoreRoundtripTest.cpp | 4 +- 4 files changed, 597 insertions(+), 349 deletions(-) create mode 100644 data/rpatters1/timesigs_composite-ref-modified.features.xml diff --git a/data/corpus.xml b/data/corpus.xml index b4bceeb52..fc713d1a4 100644 --- a/data/corpus.xml +++ b/data/corpus.xml @@ -4,20 +4,20 @@ usage (descending). Do not edit by hand; regenerate with the tool. --> - + - + - - + + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -30,7 +30,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -425,11 +425,11 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -442,7 +442,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -837,8 +837,8 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -851,7 +851,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -1246,9 +1246,9 @@ synthetic/work-title.3.0.xml - + - + @@ -1260,7 +1260,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -1273,7 +1273,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -1668,11 +1668,11 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -1685,7 +1685,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -2080,11 +2080,11 @@ synthetic/work-title.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2097,7 +2097,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -2492,8 +2492,8 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2506,7 +2506,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/cancel.location.3.0.xml @@ -2525,8 +2525,8 @@ synthetic/suffix.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2539,7 +2539,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/beat-repeat.3.0.xml @@ -2585,8 +2585,8 @@ synthetic/transpose.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2599,7 +2599,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/cancel.location.3.0.xml @@ -2612,7 +2612,7 @@ synthetic/segno.3.1.xml - + @@ -2636,7 +2636,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2649,7 +2649,7 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml @@ -2877,37 +2877,7 @@ synthetic/work-title.3.0.xml - - - - - - custom/musescore-slur-start-stop.musicxml - custom/segno_coda_roundtrip.3.0.xml - custom/segno_coda_roundtrip.3.1.xml - custom/slurs_overbars.musicxml - custom/systems-and-pages.xml - custom/transposition.musicxml - foundsuite/12 Horn Duets.xml - foundsuite/ARBEAU Belle qui tiens ma vie.xml - foundsuite/Adagio and Fugue in C minor, K.546.xml - foundsuite/An Chloe.xml - foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - - - - synthetic/cancel.location.3.0.xml - synthetic/coda.3.0.xml - synthetic/coda.3.1.xml - synthetic/grace-cue.4.0.xml - synthetic/key-accidental.3.0.xml - synthetic/key-accidental.smufl.3.1.xml - synthetic/segno.3.0.xml - synthetic/segno.3.1.xml - - - + @@ -2925,7 +2895,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -2938,7 +2908,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -2948,8 +2918,8 @@ synthetic/key-accidental.smufl.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -2962,7 +2932,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -2970,8 +2940,8 @@ synthetic/key-accidental.smufl.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -2984,7 +2954,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -2995,7 +2965,37 @@ synthetic/part-clef.4.0.xml - + + + + + + custom/musescore-slur-start-stop.musicxml + custom/segno_coda_roundtrip.3.0.xml + custom/segno_coda_roundtrip.3.1.xml + custom/slurs_overbars.musicxml + custom/systems-and-pages.xml + custom/transposition.musicxml + foundsuite/12 Horn Duets.xml + foundsuite/ARBEAU Belle qui tiens ma vie.xml + foundsuite/Adagio and Fugue in C minor, K.546.xml + foundsuite/An Chloe.xml + foundsuite/Berlioz_Le_Corsaire.xml + foundsuite/Black Note Study Op 10 no 5.xml + + + + synthetic/cancel.location.3.0.xml + synthetic/coda.3.0.xml + synthetic/coda.3.1.xml + synthetic/grace-cue.4.0.xml + synthetic/key-accidental.3.0.xml + synthetic/key-accidental.smufl.3.1.xml + synthetic/segno.3.0.xml + synthetic/segno.3.1.xml + + + @@ -3014,7 +3014,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -3027,7 +3027,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/beat-type.3.0.xml @@ -3042,8 +3042,8 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -3056,7 +3056,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/beat-type.3.0.xml @@ -3070,8 +3070,8 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -3084,7 +3084,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/beat-type.3.0.xml @@ -3098,8 +3098,8 @@ synthetic/time.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -3112,9 +3112,9 @@ foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml - + - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -3122,6 +3122,8 @@ synthetic/accidental-text.3.1.xml synthetic/accidental.3.0.xml synthetic/accidental.3.1.xml + synthetic/accordion-registration.3.0.xml + synthetic/accordion-registration.3.1.xml synthetic/arpeggiate.3.0.xml synthetic/arpeggiate.3.1.xml synthetic/arpeggiate.4.0.xml @@ -3133,13 +3135,16 @@ synthetic/assess.4.0.xml synthetic/beam.3.0.xml synthetic/beam.3.1.xml + synthetic/beat-unit-tied.3.1.xml + synthetic/beater.3.0.xml synthetic/bend.3.0.xml synthetic/bend.4.0.xml synthetic/bookmark.3.0.xml + synthetic/bracket.3.0.xml + synthetic/bracket.3.1.xml synthetic/brass-bend.3.1.xml synthetic/breath-mark.3.0.xml synthetic/caesura.3.0.xml - synthetic/cancel.location.3.0.xml synthetic/circular-arrow.3.0.xml synthetic/coda.3.0.xml synthetic/coda.3.1.xml @@ -3152,9 +3157,18 @@ synthetic/credit-words.3.0.xml synthetic/credit-words.3.1.xml synthetic/credit.3.1.xml + synthetic/damp-all.3.0.xml + synthetic/damp-all.3.1.xml + synthetic/damp.3.0.xml + synthetic/damp.3.1.xml + synthetic/dashes.3.0.xml + synthetic/dashes.3.1.xml synthetic/delayed-inverted-turn.3.0.xml synthetic/delayed-turn.3.0.xml synthetic/detached-legato.3.0.xml + synthetic/direction-type.3.1.xml + synthetic/direction.3.1.xml + synthetic/direction.4.0.xml synthetic/display-text.3.0.xml synthetic/doit.3.0.xml synthetic/doit.3.1.xml @@ -3162,20 +3176,27 @@ synthetic/double-tongue.3.0.xml synthetic/down-bow.3.0.xml synthetic/dynamics.3.1.xml + synthetic/effect.3.0.xml + synthetic/effect.4.0.xml synthetic/elevation.3.0.xml synthetic/elision.3.0.xml synthetic/elision.3.1.xml synthetic/encoder.3.0.xml synthetic/encoding-description.3.0.xml synthetic/extend.3.0.xml + synthetic/eyeglasses.3.0.xml + synthetic/eyeglasses.3.1.xml synthetic/falloff.3.0.xml synthetic/falloff.3.1.xml synthetic/fermata.3.1.xml synthetic/fingering.3.0.xml synthetic/fingernails.3.0.xml + synthetic/first.4.0.xml synthetic/flip.3.1.xml synthetic/footnote.3.0.xml synthetic/fret.3.0.xml + synthetic/glass.3.0.xml + synthetic/glass.3.1.xml synthetic/glissando.3.0.xml synthetic/glissando.3.1.xml synthetic/glyph.3.1.xml @@ -3197,6 +3218,8 @@ synthetic/harmon-closed.3.1.xml synthetic/harmon-mute.3.1.xml synthetic/harmonic.3.0.xml + synthetic/harp-pedals.3.0.xml + synthetic/harp-pedals.3.1.xml synthetic/haydn.3.1.xml synthetic/heel.3.0.xml synthetic/hole-closed.3.0.xml @@ -3204,7 +3227,10 @@ synthetic/hole-type.3.0.xml synthetic/hole.3.0.xml synthetic/humming.3.0.xml + synthetic/image.3.0.xml + synthetic/image.3.1.xml synthetic/instrument-abbreviation.3.0.xml + synthetic/instrument-change.4.0.xml synthetic/instrument-link.4.0.xml synthetic/instrument-name.3.0.xml synthetic/instrument-sound-enum.3.0.xml @@ -3213,20 +3239,34 @@ synthetic/inverted-turn.3.0.xml synthetic/inverted-vertical-turn.3.1.xml synthetic/ipa.3.0.xml - synthetic/key-accidental.3.0.xml - synthetic/key-accidental.smufl.3.1.xml synthetic/laughing.3.0.xml synthetic/left-divider.3.0.xml synthetic/level.3.0.xml synthetic/level.4.0.xml synthetic/link.3.0.xml synthetic/listen.4.0.xml + synthetic/listening.4.0.xml synthetic/lyric-font.3.0.xml synthetic/lyric-language.3.0.xml synthetic/lyric.3.0.xml synthetic/lyric.3.1.xml synthetic/measure.3.0.xml synthetic/measure.3.1.xml + synthetic/membrane.3.0.xml + synthetic/membrane.4.0.xml + synthetic/metal.3.0.xml + synthetic/metal.4.0.xml + synthetic/metronome-arrows.3.1.xml + synthetic/metronome-beam.3.0.xml + synthetic/metronome-dot.3.0.xml + synthetic/metronome-note.3.0.xml + synthetic/metronome-relation.3.0.xml + synthetic/metronome-tied.3.1.xml + synthetic/metronome-tuplet.3.0.xml + synthetic/metronome-type.3.0.xml + synthetic/metronome.3.0.xml + synthetic/metronome.3.1.xml + synthetic/metronome.4.0.xml synthetic/midi-device.3.0.xml synthetic/midi-name.3.0.xml synthetic/miscellaneous-field.3.0.xml @@ -3245,6 +3285,8 @@ synthetic/notehead-text.3.0.xml synthetic/notehead.3.0.xml synthetic/notehead.3.1.xml + synthetic/octave-shift.3.0.xml + synthetic/octave-shift.3.1.xml synthetic/open-string.3.0.xml synthetic/open.3.1.xml synthetic/opus.3.0.xml @@ -3252,13 +3294,18 @@ synthetic/other-appearance.3.0.xml synthetic/other-articulation.3.0.xml synthetic/other-articulation.3.1.xml + synthetic/other-direction.3.0.xml + synthetic/other-direction.3.1.xml synthetic/other-dynamics.3.0.xml synthetic/other-dynamics.3.1.xml synthetic/other-listen.4.0.xml + synthetic/other-listening.4.0.xml synthetic/other-notation.3.0.xml synthetic/other-notation.3.1.xml synthetic/other-ornament.3.0.xml synthetic/other-ornament.3.1.xml + synthetic/other-percussion.3.0.xml + synthetic/other-percussion.3.1.xml synthetic/other-play.3.0.xml synthetic/other-technical.3.0.xml synthetic/other-technical.3.1.xml @@ -3267,14 +3314,25 @@ synthetic/part-link.4.0.xml synthetic/part-name-display.3.0.xml synthetic/part-name.3.0.xml + synthetic/pedal.3.0.xml + synthetic/pedal.3.1.xml + synthetic/per-minute.3.0.xml + synthetic/percussion.3.0.xml + synthetic/percussion.3.1.xml synthetic/pf.3.1.xml + synthetic/pitched.3.0.xml + synthetic/pitched.3.1.xml synthetic/play.3.0.xml synthetic/player-name.4.0.xml synthetic/player.4.0.xml synthetic/plop.3.0.xml synthetic/plop.3.1.xml synthetic/pluck.3.0.xml + synthetic/principal-voice.3.0.xml + synthetic/principal-voice.3.1.xml synthetic/pull-off.3.0.xml + synthetic/rehearsal.3.0.xml + synthetic/rehearsal.3.1.xml synthetic/relation.3.0.xml synthetic/release.4.0.xml synthetic/right-divider.3.0.xml @@ -3282,6 +3340,8 @@ synthetic/schleifer.3.0.xml synthetic/scoop.3.0.xml synthetic/scoop.3.1.xml + synthetic/scordatura.3.1.xml + synthetic/second.4.0.xml synthetic/segno.3.0.xml synthetic/segno.3.1.xml synthetic/semi-pitched.3.0.xml @@ -3295,16 +3355,32 @@ synthetic/snap-pizzicato.3.0.xml synthetic/soft-accent.3.1.xml synthetic/software.3.0.xml + synthetic/sound.3.0.xml + synthetic/sound.3.1.xml synthetic/source.3.0.xml synthetic/spiccato.3.0.xml synthetic/staccatissimo.3.0.xml synthetic/staccato.3.0.xml + synthetic/staff-divide.3.1.xml synthetic/stem.3.0.xml + synthetic/stick-location.3.0.xml + synthetic/stick-material.3.0.xml + synthetic/stick-type.3.0.xml + synthetic/stick.3.0.xml + synthetic/stick.3.1.xml synthetic/stopped.3.0.xml synthetic/stopped.3.1.xml + synthetic/straight.4.0.xml synthetic/stress.3.0.xml + synthetic/string-mute.3.0.xml + synthetic/string-mute.3.1.xml synthetic/string.3.0.xml synthetic/strong-accent.3.0.xml + synthetic/swing-style.4.0.xml + synthetic/swing-type.4.0.xml + synthetic/swing.4.0.xml + synthetic/symbol.3.1.xml + synthetic/sync.4.0.xml synthetic/tap.3.0.xml synthetic/tap.3.1.xml synthetic/technical.3.1.xml @@ -3314,6 +3390,8 @@ synthetic/tie.3.0.xml synthetic/tied.3.0.xml synthetic/tied.3.1.xml + synthetic/timpani.3.0.xml + synthetic/timpani.4.0.xml synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml @@ -3334,13 +3412,19 @@ synthetic/wait.4.0.xml synthetic/wavy-line.3.0.xml synthetic/wavy-line.4.0.xml + synthetic/wedge.3.0.xml + synthetic/wedge.3.1.xml synthetic/with-bar.3.0.xml + synthetic/wood.3.0.xml + synthetic/wood.4.0.xml synthetic/word-font.3.0.xml + synthetic/words.3.0.xml + synthetic/words.3.1.xml synthetic/work-number.3.0.xml synthetic/work-title.3.0.xml - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml @@ -3582,7 +3666,7 @@ synthetic/work-title.3.0.xml - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml @@ -3824,7 +3908,7 @@ synthetic/work-title.3.0.xml - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml @@ -3840,7 +3924,7 @@ foundsuite/Black Note Study Op 10 no 5.xml - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -3848,8 +3932,6 @@ synthetic/accidental-text.3.1.xml synthetic/accidental.3.0.xml synthetic/accidental.3.1.xml - synthetic/accordion-registration.3.0.xml - synthetic/accordion-registration.3.1.xml synthetic/arpeggiate.3.0.xml synthetic/arpeggiate.3.1.xml synthetic/arpeggiate.4.0.xml @@ -3861,16 +3943,13 @@ synthetic/assess.4.0.xml synthetic/beam.3.0.xml synthetic/beam.3.1.xml - synthetic/beat-unit-tied.3.1.xml - synthetic/beater.3.0.xml synthetic/bend.3.0.xml synthetic/bend.4.0.xml synthetic/bookmark.3.0.xml - synthetic/bracket.3.0.xml - synthetic/bracket.3.1.xml synthetic/brass-bend.3.1.xml synthetic/breath-mark.3.0.xml synthetic/caesura.3.0.xml + synthetic/cancel.location.3.0.xml synthetic/circular-arrow.3.0.xml synthetic/coda.3.0.xml synthetic/coda.3.1.xml @@ -3883,18 +3962,9 @@ synthetic/credit-words.3.0.xml synthetic/credit-words.3.1.xml synthetic/credit.3.1.xml - synthetic/damp-all.3.0.xml - synthetic/damp-all.3.1.xml - synthetic/damp.3.0.xml - synthetic/damp.3.1.xml - synthetic/dashes.3.0.xml - synthetic/dashes.3.1.xml synthetic/delayed-inverted-turn.3.0.xml synthetic/delayed-turn.3.0.xml synthetic/detached-legato.3.0.xml - synthetic/direction-type.3.1.xml - synthetic/direction.3.1.xml - synthetic/direction.4.0.xml synthetic/display-text.3.0.xml synthetic/doit.3.0.xml synthetic/doit.3.1.xml @@ -3902,27 +3972,20 @@ synthetic/double-tongue.3.0.xml synthetic/down-bow.3.0.xml synthetic/dynamics.3.1.xml - synthetic/effect.3.0.xml - synthetic/effect.4.0.xml synthetic/elevation.3.0.xml synthetic/elision.3.0.xml synthetic/elision.3.1.xml synthetic/encoder.3.0.xml synthetic/encoding-description.3.0.xml synthetic/extend.3.0.xml - synthetic/eyeglasses.3.0.xml - synthetic/eyeglasses.3.1.xml synthetic/falloff.3.0.xml synthetic/falloff.3.1.xml synthetic/fermata.3.1.xml synthetic/fingering.3.0.xml synthetic/fingernails.3.0.xml - synthetic/first.4.0.xml synthetic/flip.3.1.xml synthetic/footnote.3.0.xml synthetic/fret.3.0.xml - synthetic/glass.3.0.xml - synthetic/glass.3.1.xml synthetic/glissando.3.0.xml synthetic/glissando.3.1.xml synthetic/glyph.3.1.xml @@ -3944,8 +4007,6 @@ synthetic/harmon-closed.3.1.xml synthetic/harmon-mute.3.1.xml synthetic/harmonic.3.0.xml - synthetic/harp-pedals.3.0.xml - synthetic/harp-pedals.3.1.xml synthetic/haydn.3.1.xml synthetic/heel.3.0.xml synthetic/hole-closed.3.0.xml @@ -3953,10 +4014,7 @@ synthetic/hole-type.3.0.xml synthetic/hole.3.0.xml synthetic/humming.3.0.xml - synthetic/image.3.0.xml - synthetic/image.3.1.xml synthetic/instrument-abbreviation.3.0.xml - synthetic/instrument-change.4.0.xml synthetic/instrument-link.4.0.xml synthetic/instrument-name.3.0.xml synthetic/instrument-sound-enum.3.0.xml @@ -3965,34 +4023,20 @@ synthetic/inverted-turn.3.0.xml synthetic/inverted-vertical-turn.3.1.xml synthetic/ipa.3.0.xml + synthetic/key-accidental.3.0.xml + synthetic/key-accidental.smufl.3.1.xml synthetic/laughing.3.0.xml synthetic/left-divider.3.0.xml synthetic/level.3.0.xml synthetic/level.4.0.xml synthetic/link.3.0.xml synthetic/listen.4.0.xml - synthetic/listening.4.0.xml synthetic/lyric-font.3.0.xml synthetic/lyric-language.3.0.xml synthetic/lyric.3.0.xml synthetic/lyric.3.1.xml synthetic/measure.3.0.xml synthetic/measure.3.1.xml - synthetic/membrane.3.0.xml - synthetic/membrane.4.0.xml - synthetic/metal.3.0.xml - synthetic/metal.4.0.xml - synthetic/metronome-arrows.3.1.xml - synthetic/metronome-beam.3.0.xml - synthetic/metronome-dot.3.0.xml - synthetic/metronome-note.3.0.xml - synthetic/metronome-relation.3.0.xml - synthetic/metronome-tied.3.1.xml - synthetic/metronome-tuplet.3.0.xml - synthetic/metronome-type.3.0.xml - synthetic/metronome.3.0.xml - synthetic/metronome.3.1.xml - synthetic/metronome.4.0.xml synthetic/midi-device.3.0.xml synthetic/midi-name.3.0.xml synthetic/miscellaneous-field.3.0.xml @@ -4011,8 +4055,6 @@ synthetic/notehead-text.3.0.xml synthetic/notehead.3.0.xml synthetic/notehead.3.1.xml - synthetic/octave-shift.3.0.xml - synthetic/octave-shift.3.1.xml synthetic/open-string.3.0.xml synthetic/open.3.1.xml synthetic/opus.3.0.xml @@ -4020,18 +4062,13 @@ synthetic/other-appearance.3.0.xml synthetic/other-articulation.3.0.xml synthetic/other-articulation.3.1.xml - synthetic/other-direction.3.0.xml - synthetic/other-direction.3.1.xml synthetic/other-dynamics.3.0.xml synthetic/other-dynamics.3.1.xml synthetic/other-listen.4.0.xml - synthetic/other-listening.4.0.xml synthetic/other-notation.3.0.xml synthetic/other-notation.3.1.xml synthetic/other-ornament.3.0.xml synthetic/other-ornament.3.1.xml - synthetic/other-percussion.3.0.xml - synthetic/other-percussion.3.1.xml synthetic/other-play.3.0.xml synthetic/other-technical.3.0.xml synthetic/other-technical.3.1.xml @@ -4040,25 +4077,14 @@ synthetic/part-link.4.0.xml synthetic/part-name-display.3.0.xml synthetic/part-name.3.0.xml - synthetic/pedal.3.0.xml - synthetic/pedal.3.1.xml - synthetic/per-minute.3.0.xml - synthetic/percussion.3.0.xml - synthetic/percussion.3.1.xml synthetic/pf.3.1.xml - synthetic/pitched.3.0.xml - synthetic/pitched.3.1.xml synthetic/play.3.0.xml synthetic/player-name.4.0.xml synthetic/player.4.0.xml synthetic/plop.3.0.xml synthetic/plop.3.1.xml synthetic/pluck.3.0.xml - synthetic/principal-voice.3.0.xml - synthetic/principal-voice.3.1.xml synthetic/pull-off.3.0.xml - synthetic/rehearsal.3.0.xml - synthetic/rehearsal.3.1.xml synthetic/relation.3.0.xml synthetic/release.4.0.xml synthetic/right-divider.3.0.xml @@ -4066,8 +4092,6 @@ synthetic/schleifer.3.0.xml synthetic/scoop.3.0.xml synthetic/scoop.3.1.xml - synthetic/scordatura.3.1.xml - synthetic/second.4.0.xml synthetic/segno.3.0.xml synthetic/segno.3.1.xml synthetic/semi-pitched.3.0.xml @@ -4081,32 +4105,16 @@ synthetic/snap-pizzicato.3.0.xml synthetic/soft-accent.3.1.xml synthetic/software.3.0.xml - synthetic/sound.3.0.xml - synthetic/sound.3.1.xml synthetic/source.3.0.xml synthetic/spiccato.3.0.xml synthetic/staccatissimo.3.0.xml synthetic/staccato.3.0.xml - synthetic/staff-divide.3.1.xml synthetic/stem.3.0.xml - synthetic/stick-location.3.0.xml - synthetic/stick-material.3.0.xml - synthetic/stick-type.3.0.xml - synthetic/stick.3.0.xml - synthetic/stick.3.1.xml synthetic/stopped.3.0.xml synthetic/stopped.3.1.xml - synthetic/straight.4.0.xml synthetic/stress.3.0.xml - synthetic/string-mute.3.0.xml - synthetic/string-mute.3.1.xml synthetic/string.3.0.xml synthetic/strong-accent.3.0.xml - synthetic/swing-style.4.0.xml - synthetic/swing-type.4.0.xml - synthetic/swing.4.0.xml - synthetic/symbol.3.1.xml - synthetic/sync.4.0.xml synthetic/tap.3.0.xml synthetic/tap.3.1.xml synthetic/technical.3.1.xml @@ -4116,8 +4124,6 @@ synthetic/tie.3.0.xml synthetic/tied.3.0.xml synthetic/tied.3.1.xml - synthetic/timpani.3.0.xml - synthetic/timpani.4.0.xml synthetic/toe.3.0.xml synthetic/tremolo.3.0.xml synthetic/tremolo.3.1.xml @@ -4138,20 +4144,14 @@ synthetic/wait.4.0.xml synthetic/wavy-line.3.0.xml synthetic/wavy-line.4.0.xml - synthetic/wedge.3.0.xml - synthetic/wedge.3.1.xml synthetic/with-bar.3.0.xml - synthetic/wood.3.0.xml - synthetic/wood.4.0.xml synthetic/word-font.3.0.xml - synthetic/words.3.0.xml - synthetic/words.3.1.xml synthetic/work-number.3.0.xml synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4164,7 +4164,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/creator.3.0.xml @@ -4177,7 +4177,7 @@ synthetic/source.3.0.xml - + @@ -4192,7 +4192,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4205,7 +4205,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/cancel.location.3.0.xml @@ -4216,8 +4216,8 @@ synthetic/key.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4230,7 +4230,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/cancel.location.3.0.xml @@ -4239,8 +4239,8 @@ synthetic/key.3.1.xml - - + + custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4253,17 +4253,17 @@ foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/Rimsky-Korsakov Op11 No4.xml - + synthetic/cancel.location.3.0.xml - + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4276,21 +4276,21 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/bar-style.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4303,7 +4303,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/bar-style.3.0.xml @@ -4314,8 +4314,8 @@ synthetic/repeat.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4328,7 +4328,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/encoder.3.0.xml @@ -4336,8 +4336,8 @@ synthetic/software.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4350,14 +4350,14 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/software.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4370,7 +4370,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4381,11 +4381,11 @@ synthetic/virtual-name.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4398,7 +4398,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4409,8 +4409,8 @@ synthetic/virtual-name.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4423,16 +4423,16 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4445,15 +4445,15 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/elevation.3.0.xml synthetic/midi-name.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4466,13 +4466,13 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4485,13 +4485,13 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4504,13 +4504,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4523,7 +4523,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + @@ -4555,11 +4555,11 @@ synthetic/stem.3.0.xml - + - + - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4572,7 +4572,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + @@ -4808,7 +4808,7 @@ - + @@ -4817,7 +4817,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -4830,7 +4830,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/measure-distance.3.0.xml @@ -5125,8 +5125,8 @@ synthetic/words.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5139,7 +5139,7 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + synthetic/concert-score.4.0.xml @@ -5153,8 +5153,8 @@ synthetic/word-font.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5167,13 +5167,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5186,13 +5186,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5205,13 +5205,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5224,13 +5224,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5243,13 +5243,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5262,13 +5262,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5281,13 +5281,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5300,13 +5300,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5319,15 +5319,15 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/left-divider.3.0.xml synthetic/right-divider.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5340,16 +5340,16 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5362,13 +5362,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5381,13 +5381,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5400,13 +5400,13 @@ foundsuite/Black Note Study Op 10 no 5.xml foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5419,13 +5419,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -5438,7 +5438,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + @@ -5524,9 +5524,9 @@ synthetic/miscellaneous-field.3.0.xml - + - + @@ -5545,7 +5545,7 @@ - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Adagio and Fugue in C minor, K.546.xml @@ -5558,7 +5558,7 @@ foundsuite/O_Holy_Night-Adam-1871.xml foundsuite/O_Holy_Night-Adam-1974.xml foundsuite/O_Holy_Night.xml - + synthetic/first.4.0.xml @@ -5684,14 +5684,14 @@ synthetic/part-abbreviation.3.0.xml - + - - - - + + + + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5704,7 +5704,7 @@ foundsuite/Invention_10.xml foundsuite/Invention_11.xml foundsuite/Invention_12.xml - + @@ -5796,14 +5796,14 @@ synthetic/tie.3.0.xml - + - - + + - + custom/musescore-slur-start-stop.musicxml custom/slurs_overbars.musicxml custom/systems-and-pages.xml @@ -5816,7 +5816,7 @@ foundsuite/Invention_11.xml foundsuite/Invention_12.xml foundsuite/Invention_13.xml - + synthetic/word-font.3.0.xml @@ -6403,14 +6403,14 @@ - + - - + + - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -6423,14 +6423,14 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - + synthetic/music-font.3.0.xml - - + + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -6443,18 +6443,18 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - + synthetic/glyph.3.1.xml synthetic/other-appearance.3.0.xml - + - + - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -6467,16 +6467,16 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - + - + - + - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -6489,7 +6489,7 @@ kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml kiritan_singing/kiritan_singing_04.xml - + @@ -6597,11 +6597,11 @@ synthetic/per-minute.3.0.xml - + - + - + custom/slurs_overbars.musicxml custom/systems-and-pages.xml foundsuite/PepAiraSco.xml @@ -6614,12 +6614,12 @@ kiritan_singing/kiritan_singing_06.xml kiritan_singing/kiritan_singing_07.xml kiritan_singing/kiritan_singing_08.xml - + - + @@ -6637,7 +6637,7 @@ - + custom/systems-and-pages.xml foundsuite/PepAiraSco.xml foundsuite/RonCLunSco.xml @@ -6650,7 +6650,7 @@ kiritan_singing/kiritan_singing_07.xml kiritan_singing/kiritan_singing_08.xml kiritan_singing/kiritan_singing_09.xml - + synthetic/measure-numbering.3.0.xml @@ -9562,6 +9562,21 @@ synthetic/slash.3.0.xml + + + custom/slurs_overbars.musicxml + custom/systems-and-pages.xml + ksuite/k011a_Tuplets.xml + ksuite/k012a_Tempo_Markings.xml + mjbsuite/FreezingSmall.xml + mjbsuite/freezing.old.xml + mjbsuite/freezing.xml + rpatters1/timesigs_composite-ref-modified.musicxml + rpatters1/timesigs_composite-ref.musicxml + + + + @@ -9665,20 +9680,6 @@ synthetic/fingering.3.0.xml - - - custom/slurs_overbars.musicxml - custom/systems-and-pages.xml - ksuite/k011a_Tuplets.xml - ksuite/k012a_Tempo_Markings.xml - mjbsuite/FreezingSmall.xml - mjbsuite/freezing.old.xml - mjbsuite/freezing.xml - rpatters1/timesigs_composite-ref.musicxml - - - - foundsuite/Berlioz_Le_Corsaire.xml diff --git a/data/rpatters1/timesigs_composite-ref-modified.features.xml b/data/rpatters1/timesigs_composite-ref-modified.features.xml new file mode 100644 index 000000000..700b12b63 --- /dev/null +++ b/data/rpatters1/timesigs_composite-ref-modified.features.xml @@ -0,0 +1,249 @@ + + + + rpatters1/timesigs_composite-ref-modified.musicxml + 4.0 + + + appearance + + + attributes + + + bar-style + + + barline + + location + + + + beat-type + + + beats + + + bottom-margin + + + clef + + + defaults + + + distance + + type + + + + divisions + + + duration + + + encoding + + + encoding-date + + + fifths + + + identification + + + instrument-name + + + key + + + left-margin + + + line + + + line-width + + type + + + + measure + + number + width + + + + measure-numbering + + + midi-bank + + + midi-channel + + + midi-instrument + + id + + + + midi-program + + + millimeters + + + mode + + + music-font + + font-family + font-size + + + + note + + + note-size + + type + + + + page-height + + + page-layout + + + page-margins + + type + + + + page-width + + + pan + + + part + + id + + + + part-list + + + part-name + + print-object + + + + print + + + rest + + measure + + + + right-margin + + + scaling + + + score-instrument + + id + + + + score-part + + id + + + + score-partwise + + version + + + + sign + + + software + + + sound + + tempo + + + + supports + + attribute + element + type + value + + + + system-distance + + + system-layout + + + system-margins + + + tenths + + + time + + + top-margin + + + top-system-distance + + + voice + + + volume + + + word-font + + font-family + font-size + + + + diff --git a/src/private/mxtest/api/SpannerIdentityTest.cpp b/src/private/mxtest/api/SpannerIdentityTest.cpp index d70acd99e..f653d48c9 100644 --- a/src/private/mxtest/api/SpannerIdentityTest.cpp +++ b/src/private/mxtest/api/SpannerIdentityTest.cpp @@ -54,8 +54,7 @@ inline ScoreData makeVoiceReorderingScore(const SpannerNumber &inA, const Spanne part.measures.emplace_back(); { auto &measure = part.measures.back(); - measure.timeSignature.beats = "4"; - measure.timeSignature.beatType = "4"; + measure.timeSignature = TimeChoice(TimeSignatureData{"4", "4"}); measure.timeSignature.isImplicit = false; measure.staves.emplace_back(); auto &staff = measure.staves.back(); @@ -89,8 +88,7 @@ inline ScoreData makeVoiceReorderingScore(const SpannerNumber &inA, const Spanne part.measures.emplace_back(); { auto &measure = part.measures.back(); - measure.timeSignature.beats = "4"; - measure.timeSignature.beatType = "4"; + measure.timeSignature = TimeChoice(TimeSignatureData{"4", "4"}); measure.timeSignature.isImplicit = true; measure.staves.emplace_back(); auto &staff = measure.staves.back(); diff --git a/src/private/mxtest/corert/CoreRoundtripTest.cpp b/src/private/mxtest/corert/CoreRoundtripTest.cpp index 187992737..a03f1560e 100644 --- a/src/private/mxtest/corert/CoreRoundtripTest.cpp +++ b/src/private/mxtest/corert/CoreRoundtripTest.cpp @@ -127,12 +127,12 @@ const CoreRoundtripRegistrar g_coreRoundtripRegistrar; } // namespace -// Pinned counts: 834 eligible files, none skipped. Count drift is a failure +// Pinned counts: 835 eligible files, none skipped. Count drift is a failure // even with zero individual fails, so a corpus or version-gate change is a // conscious decision, not silent decay. Registered last (registration is // discovery order; "zz" keeps it last alphabetically for shuffled runs too). TEST_CASE("zz-corert-pinned-counts", "[core-roundtrip]") { - CHECK(mxtest::corert::discoverInputFiles().size() == 834); + CHECK(mxtest::corert::discoverInputFiles().size() == 835); CHECK(g_skippedCount == 0); }