From 9cbac96be4a51640c3238cf9d4b9cb5c38fdf924 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Wed, 26 Aug 2026 23:33:57 +0000 Subject: [PATCH 1/3] Add #429 Finale reference fixtures to the corpus (data/rpatters1) tuplet_singletons-ref.xml and gliss_to_rest-ref.xml, attached by rpatters1 to issue #429: Finale's exports of a bar of two single-note tuplets and of a glissando and a slide drawn to a rest. Both place a spanner's start and stop on one note in start-then-stop order -- the shape NotationsWriter currently inverts. Pinned corert count 838 -> 840; audit artifacts regenerated. --- data/corpus.xml | 1142 +++++++++-------- data/rpatters1/gliss_to_rest-ref.features.xml | 304 +++++ data/rpatters1/gliss_to_rest-ref.xml | 152 +++ .../tuplet_singletons-ref.features.xml | 307 +++++ data/rpatters1/tuplet_singletons-ref.xml | 148 +++ .../mxtest/corert/CoreRoundtripTest.cpp | 4 +- 6 files changed, 1489 insertions(+), 568 deletions(-) create mode 100644 data/rpatters1/gliss_to_rest-ref.features.xml create mode 100644 data/rpatters1/gliss_to_rest-ref.xml create mode 100644 data/rpatters1/tuplet_singletons-ref.features.xml create mode 100644 data/rpatters1/tuplet_singletons-ref.xml diff --git a/data/corpus.xml b/data/corpus.xml index 0719fdbaf..b330bc797 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,9 +30,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -428,11 +428,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 @@ -445,9 +445,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -843,8 +843,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 @@ -857,9 +857,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -1255,9 +1255,9 @@ synthetic/work-title.3.0.xml - + - + @@ -1269,7 +1269,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -1282,9 +1282,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -1680,11 +1680,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 @@ -1697,9 +1697,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -2095,11 +2095,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 @@ -2112,9 +2112,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -2510,8 +2510,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 @@ -2524,7 +2524,7 @@ 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 @@ -2545,8 +2545,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2559,7 +2559,7 @@ 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 @@ -2607,8 +2607,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2621,7 +2621,7 @@ 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 @@ -2636,9 +2636,9 @@ synthetic/words-symbol.4.0.xml - + - + @@ -2660,7 +2660,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -2673,9 +2673,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -2904,7 +2904,7 @@ synthetic/work-title.3.0.xml - + @@ -2922,7 +2922,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -2935,7 +2935,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2946,8 +2946,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -2960,7 +2960,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2969,8 +2969,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -2983,7 +2983,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + synthetic/cancel.location.3.0.xml @@ -2995,11 +2995,11 @@ synthetic/words-symbol.4.0.xml - + - + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -3012,7 +3012,7 @@ 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 @@ -3027,7 +3027,7 @@ synthetic/words-symbol.4.0.xml - + @@ -3046,7 +3046,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -3059,7 +3059,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3075,8 +3075,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -3089,7 +3089,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3104,8 +3104,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -3118,7 +3118,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/beat-type.3.0.xml @@ -3133,8 +3133,8 @@ synthetic/words-symbol.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/segno_coda_roundtrip.3.0.xml custom/segno_coda_roundtrip.3.1.xml @@ -3147,9 +3147,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -3461,8 +3461,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 @@ -3475,9 +3475,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -3706,8 +3706,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 @@ -3720,9 +3720,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -3951,8 +3951,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 @@ -3965,9 +3965,9 @@ 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 synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -4196,8 +4196,8 @@ synthetic/work-title.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4210,7 +4210,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/creator.3.0.xml @@ -4223,7 +4223,7 @@ synthetic/source.3.0.xml - + @@ -4238,7 +4238,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4251,7 +4251,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -4262,8 +4262,8 @@ synthetic/key.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4276,7 +4276,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/cancel.location.3.0.xml @@ -4285,8 +4285,8 @@ synthetic/key.3.1.xml - - + + custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4299,17 +4299,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/systems-and-pages.xml custom/transposition.musicxml @@ -4322,21 +4322,21 @@ 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/systems-and-pages.xml custom/transposition.musicxml @@ -4349,7 +4349,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/bar-style.3.0.xml @@ -4360,8 +4360,8 @@ synthetic/repeat.4.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4374,7 +4374,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/encoder.3.0.xml @@ -4382,8 +4382,8 @@ synthetic/software.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4396,14 +4396,14 @@ 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/systems-and-pages.xml custom/transposition.musicxml @@ -4416,7 +4416,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4427,11 +4427,11 @@ synthetic/virtual-name.3.0.xml - + - + - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4444,7 +4444,7 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + synthetic/instrument-abbreviation.3.0.xml @@ -4455,8 +4455,8 @@ synthetic/virtual-name.3.0.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -4469,16 +4469,16 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4491,15 +4491,15 @@ 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/systems-and-pages.xml custom/transposition.musicxml @@ -4512,13 +4512,13 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4531,13 +4531,13 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4550,13 +4550,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -4569,20 +4569,20 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4595,17 +4595,17 @@ foundsuite/Invention 3.xml foundsuite/Invention_10.xml foundsuite/Invention_11.xml - + synthetic/stem.3.0.xml - + - + custom/systems-and-pages.xml custom/transposition.musicxml foundsuite/12 Horn Duets.xml @@ -4618,17 +4618,17 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - + - + custom/musescore-slur-start-stop.musicxml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -4641,9 +4641,9 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - + - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -4856,7 +4856,7 @@ - + @@ -4865,7 +4865,7 @@ - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -4878,7 +4878,7 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + synthetic/measure-distance.3.0.xml @@ -4889,6 +4889,38 @@ synthetic/print.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/dot.3.0.xml + + @@ -4912,21 +4944,13 @@ synthetic/creator.3.0.xml - - - - - - - - - - - - - - + + + custom/musescore-slur-start-stop.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 @@ -4934,14 +4958,18 @@ 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/dot.3.0.xml + + synthetic/concert-score.4.0.xml + synthetic/glyph.3.1.xml + synthetic/left-divider.3.0.xml + synthetic/lyric-font.3.0.xml + synthetic/lyric-language.3.0.xml + synthetic/music-font.3.0.xml + synthetic/other-appearance.3.0.xml + synthetic/right-divider.3.0.xml + synthetic/word-font.3.0.xml @@ -5175,8 +5203,8 @@ synthetic/words.3.1.xml - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5189,22 +5217,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - synthetic/concert-score.4.0.xml - synthetic/glyph.3.1.xml - synthetic/left-divider.3.0.xml - synthetic/lyric-font.3.0.xml - synthetic/lyric-language.3.0.xml - synthetic/music-font.3.0.xml - synthetic/other-appearance.3.0.xml - synthetic/right-divider.3.0.xml - synthetic/word-font.3.0.xml + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5217,13 +5236,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5236,12 +5255,12 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml @@ -5260,27 +5279,8 @@ - - - custom/musescore-slur-start-stop.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 - foundsuite/Deutscher Tanz D.820.1.xml - foundsuite/Invention 1.xml - foundsuite/Invention 2.xml - - - - - - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5293,13 +5293,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5312,13 +5312,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5331,13 +5331,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5350,13 +5350,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -5369,15 +5369,15 @@ 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/systems-and-pages.xml custom/transposition.musicxml @@ -5390,16 +5390,16 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - + - + - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5412,13 +5412,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -5431,13 +5431,13 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5450,13 +5450,13 @@ foundsuite/Deutscher Tanz D.820.1.xml foundsuite/Invention 1.xml foundsuite/Invention 2.xml - + - - + + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml @@ -5469,13 +5469,13 @@ foundsuite/Invention 1.xml foundsuite/Invention 2.xml foundsuite/Invention 3.xml - + - - + + custom/systems-and-pages.xml foundsuite/12 Horn Duets.xml foundsuite/ARBEAU Belle qui tiens ma vie.xml @@ -5488,7 +5488,7 @@ foundsuite/Invention 2.xml foundsuite/Invention 3.xml foundsuite/Invention_10.xml - + @@ -5574,9 +5574,9 @@ synthetic/miscellaneous-field.3.0.xml - + - + @@ -5595,7 +5595,7 @@ - + custom/systems-and-pages.xml foundsuite/Adagio and Fugue in C minor, K.546.xml foundsuite/An Chloe.xml @@ -5608,7 +5608,7 @@ foundsuite/O_Holy_Night-Adam-1974.xml foundsuite/O_Holy_Night.xml foundsuite/PepAiraSco.xml - + synthetic/first.4.0.xml @@ -5734,14 +5734,14 @@ synthetic/part-abbreviation.3.0.xml - + - - - - + + + + - + custom/musescore-slur-start-stop.musicxml custom/systems-and-pages.xml custom/transposition.musicxml @@ -5754,7 +5754,7 @@ foundsuite/Invention_11.xml foundsuite/Invention_12.xml foundsuite/Invention_13.xml - + @@ -5823,55 +5823,55 @@ synthetic/tied.cue.4.0.xml - + - - + + + + - - foundsuite/12 Horn Duets.xml - foundsuite/Adagio and Fugue in C minor, K.546.xml - foundsuite/An Chloe.xml + + custom/musescore-slur-start-stop.musicxml + custom/systems-and-pages.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 - + foundsuite/Invention_13.xml + foundsuite/Invention_14.xml + - - synthetic/tie.3.0.xml - synthetic/tied.cue.4.0.xml + + synthetic/word-font.3.0.xml - + - - - - + + - - custom/musescore-slur-start-stop.musicxml - custom/systems-and-pages.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 - foundsuite/Invention_13.xml - foundsuite/Invention_14.xml - + - - synthetic/word-font.3.0.xml + + synthetic/tie.3.0.xml + synthetic/tied.cue.4.0.xml @@ -6456,97 +6456,157 @@ - + - - + + - + custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.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 - + synthetic/music-font.3.0.xml - - + + custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.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 - + synthetic/glyph.3.1.xml synthetic/other-appearance.3.0.xml - + - + - + custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.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 - + - + - + - + custom/systems-and-pages.xml foundsuite/Berlioz_Le_Corsaire.xml foundsuite/Black Note Study Op 10 no 5.xml foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.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 - + + + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + synthetic/measure-numbering.3.0.xml + synthetic/measure-numbering.4.0.xml + + foundsuite/Berlioz_Le_Corsaire.xml @@ -6555,7 +6615,7 @@ foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml kiritan_singing/kiritan_singing_01.xml kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml @@ -6596,7 +6656,7 @@ foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml kiritan_singing/kiritan_singing_01.xml kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml @@ -6634,7 +6694,7 @@ foundsuite/PepAiraSco.xml foundsuite/PezR44Sco.xml foundsuite/RonCLunSco.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml kiritan_singing/kiritan_singing_01.xml kiritan_singing/kiritan_singing_02.xml kiritan_singing/kiritan_singing_03.xml @@ -6650,66 +6710,6 @@ synthetic/per-minute.3.0.xml - - - - - - 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 - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - - synthetic/measure-numbering.3.0.xml - synthetic/measure-numbering.4.0.xml - - foundsuite/12 Horn Duets.xml @@ -6990,8 +6990,8 @@ synthetic/breath-mark.3.0.xml - - + + foundsuite/12 Horn Duets.xml foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -7001,17 +7001,17 @@ foundsuite/O_Holy_Night-Adam-1871.xml foundsuite/O_Holy_Night.xml foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/Bombe.xml ksuite/k011a_Tuplets.xml - + synthetic/metronome-tuplet.3.0.xml - - + + foundsuite/12 Horn Duets.xml foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -7021,15 +7021,34 @@ foundsuite/O_Holy_Night-Adam-1871.xml foundsuite/O_Holy_Night.xml foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/Bombe.xml ksuite/k011a_Tuplets.xml - + synthetic/metronome-tuplet.3.0.xml + + + foundsuite/12 Horn Duets.xml + foundsuite/An Chloe.xml + foundsuite/Berlioz_Le_Corsaire.xml + foundsuite/Black Note Study Op 10 no 5.xml + foundsuite/Invention_5.xml + foundsuite/Moments Musicaux Op16 No4.xml + foundsuite/O_Holy_Night-Adam-1871.xml + foundsuite/O_Holy_Night.xml + foundsuite/SCHUBERT An die Sonne.xml + foundsuite/Απτάλικο.xml + ksuite/Bombe.xml + ksuite/k011a_Tuplets.xml + + + + + @@ -7044,7 +7063,7 @@ foundsuite/RonCLunSco.xml foundsuite/Silent_Night-Hartwig.xml foundsuite/Silent_Night_Young_1.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k006a_Header_Scaling_Decimals.xml ksuite/k013a_OrchestralScoreFinale.xml @@ -7053,8 +7072,22 @@ synthetic/rights.3.0.xml - - + + + + + + + + + + + + + + + + foundsuite/12 Horn Duets.xml foundsuite/An Chloe.xml foundsuite/Berlioz_Le_Corsaire.xml @@ -7064,12 +7097,17 @@ foundsuite/O_Holy_Night-Adam-1871.xml foundsuite/O_Holy_Night.xml foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/Bombe.xml ksuite/k011a_Tuplets.xml - + - + + synthetic/tuplet-dot.3.0.xml + synthetic/tuplet-number.3.0.xml + synthetic/tuplet-type.3.0.xml + synthetic/tuplet.3.0.xml + synthetic/tuplet.3.1.xml @@ -7099,59 +7137,21 @@ foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/PepAiraSco.xml - foundsuite/PezR44Sco.xml - foundsuite/RonCLunSco.xml - ksuite/Bombe.xml - ksuite/k006a_Header_Scaling_Decimals.xml - ksuite/k008a_Beaming.xml - ksuite/k009a_Slurs.xml - ksuite/k009b_Slur_Attributes.xml - ksuite/k010a_Ties.xml - ksuite/k010b_Bad_Ties.xml - - - - synthetic/movement-title.3.0.xml - - - - - - - - - - - - - - - - - - - foundsuite/12 Horn Duets.xml - foundsuite/An Chloe.xml - foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Invention_5.xml - foundsuite/Moments Musicaux Op16 No4.xml - foundsuite/O_Holy_Night-Adam-1871.xml - foundsuite/O_Holy_Night.xml - foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Black Note Study Op 10 no 5.xml + foundsuite/PepAiraSco.xml + foundsuite/PezR44Sco.xml + foundsuite/RonCLunSco.xml ksuite/Bombe.xml - ksuite/k011a_Tuplets.xml + ksuite/k006a_Header_Scaling_Decimals.xml + ksuite/k008a_Beaming.xml + ksuite/k009a_Slurs.xml + ksuite/k009b_Slur_Attributes.xml + ksuite/k010a_Ties.xml + ksuite/k010b_Bad_Ties.xml - - synthetic/tuplet-dot.3.0.xml - synthetic/tuplet-number.3.0.xml - synthetic/tuplet-type.3.0.xml - synthetic/tuplet.3.0.xml - synthetic/tuplet.3.1.xml + + synthetic/movement-title.3.0.xml @@ -7344,7 +7344,7 @@ - + @@ -7366,7 +7366,7 @@ lysuite/ly33f_Trill_EndingOnGraceNote.xml - + synthetic/accent.3.0.xml synthetic/accidental-mark.3.0.xml synthetic/accidental-mark.3.1.xml @@ -7739,7 +7739,7 @@ foundsuite/Berlioz_Le_Corsaire.xml foundsuite/PezR44Sco.xml foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013a_OrchestralScoreFinale.xml ksuite/k013b_OrchestralScoreSibelius.xml logicpro/logic01a_homoSapiens.xml @@ -8152,7 +8152,7 @@ foundsuite/Berlioz_Le_Corsaire.xml foundsuite/PezR44Sco.xml foundsuite/SCHUBERT An die Sonne.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013a_OrchestralScoreFinale.xml ksuite/k013b_OrchestralScoreSibelius.xml logicpro/logic01a_homoSapiens.xml @@ -8214,7 +8214,7 @@ foundsuite/Berlioz_Le_Corsaire.xml foundsuite/RonCLunSco.xml foundsuite/Schubert_der_Mueller.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013b_OrchestralScoreSibelius.xml ksuite/k015a_System_Layout.xml lysuite/ly14a_StaffDetails_LineChanges.xml @@ -8231,6 +8231,26 @@ synthetic/staff-type.3.0.xml + + + foundsuite/Berlioz_Le_Corsaire.xml + foundsuite/Black Note Study Op 10 no 5.xml + foundsuite/Moments Musicaux Op16 No4.xml + foundsuite/Απτάλικο.xml + ksuite/Bombe.xml + ksuite/k011a_Tuplets.xml + lysuite/ly23c_Tuplet_Display_NonStandard.xml + lysuite/ly23d_Tuplets_Nested.xml + lysuite/ly23e_Tuplets_Tremolo.xml + mjbsuite/FreezingSmall.xml + mjbsuite/PreserveTimeModificationNormalType.xml + mjbsuite/freezing.old.xml + + + + synthetic/metronome-tuplet.3.0.xml + + @@ -8313,26 +8333,6 @@ synthetic/trill-mark.3.0.xml - - - foundsuite/Berlioz_Le_Corsaire.xml - foundsuite/Black Note Study Op 10 no 5.xml - foundsuite/Moments Musicaux Op16 No4.xml - foundsuite/Απτάλικο.xml - ksuite/Bombe.xml - ksuite/k011a_Tuplets.xml - lysuite/ly23c_Tuplet_Display_NonStandard.xml - lysuite/ly23d_Tuplets_Nested.xml - lysuite/ly23e_Tuplets_Tremolo.xml - mjbsuite/FreezingSmall.xml - mjbsuite/PreserveTimeModificationNormalType.xml - mjbsuite/freezing.old.xml - - - - synthetic/metronome-tuplet.3.0.xml - - foundsuite/Berlioz_Le_Corsaire.xml @@ -9199,7 +9199,7 @@ - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k001b_Articulations_Above.xml ksuite/k001c_Articulations_Below.xml ksuite/k004a_Technical.xml @@ -9326,7 +9326,7 @@ - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k001b_Articulations_Above.xml ksuite/k001c_Articulations_Below.xml ksuite/k004a_Technical.xml @@ -9443,7 +9443,7 @@ - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013a_OrchestralScoreFinale.xml ksuite/k013b_OrchestralScoreSibelius.xml lysuite/ly41c_StaffGroups.xml @@ -9458,6 +9458,23 @@ + + + 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/gliss_to_rest-ref.xml + rpatters1/slurs_overbars.musicxml + rpatters1/timesigs_composite-ref-modified.musicxml + rpatters1/timesigs_composite-ref.musicxml + rpatters1/tuplet_singletons-ref.xml + + + + ksuite/k006a_Header_Scaling_Decimals.xml @@ -9626,27 +9643,12 @@ synthetic/slash.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/slurs_overbars.musicxml - rpatters1/timesigs_composite-ref-modified.musicxml - rpatters1/timesigs_composite-ref.musicxml - - - - - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013a_OrchestralScoreFinale.xml ksuite/k013b_OrchestralScoreSibelius.xml lysuite/ly41i_PartNameDisplay_Override.xml @@ -9970,6 +9972,42 @@ synthetic/sync.4.0.xml + + + + + + + + + + + + + + + + + + + + + + + + ksuite/k005a_Spanners_Slides.xml + lysuite/ly33a_Spanners.xml + lysuite/ly33h_Spanners_Glissando.xml + musuite/A_Walk_through_the_Park.xml + musuite/testArpGliss1.xml + musuite/testArpGliss2.xml + rpatters1/gliss_to_rest-ref.xml + + + synthetic/slide.3.0.xml + synthetic/slide.3.1.xml + + lysuite/ly31c_MetronomeMarks.xml @@ -10268,41 +10306,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - ksuite/k005a_Spanners_Slides.xml - lysuite/ly33a_Spanners.xml - lysuite/ly33h_Spanners_Glissando.xml - musuite/A_Walk_through_the_Park.xml - musuite/testArpGliss1.xml - musuite/testArpGliss2.xml - - - synthetic/slide.3.0.xml - synthetic/slide.3.1.xml - - lysuite/ly31a_Directions.xml @@ -10436,6 +10439,36 @@ + + + + + + + + + + + + + + + + + + + + lysuite/ly33a_Spanners.xml + lysuite/ly33h_Spanners_Glissando.xml + musuite/testArpGliss1.xml + musuite/testArpGliss2.xml + rpatters1/gliss_to_rest-ref.xml + + + synthetic/glissando.3.0.xml + synthetic/glissando.3.1.xml + + ksuite/k013a_OrchestralScoreFinale.xml @@ -10640,7 +10673,7 @@ foundsuite/Schubert_der_Mueller.xml - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k006a_Header_Scaling_Decimals.xml ksuite/k013b_OrchestralScoreSibelius.xml @@ -10737,35 +10770,6 @@ synthetic/frame.3.1.xml - - - - - - - - - - - - - - - - - - - - lysuite/ly33a_Spanners.xml - lysuite/ly33h_Spanners_Glissando.xml - musuite/testArpGliss1.xml - musuite/testArpGliss2.xml - - - synthetic/glissando.3.0.xml - synthetic/glissando.3.1.xml - - lysuite/ly41c_StaffGroups.xml @@ -11135,7 +11139,7 @@ - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k006a_Header_Scaling_Decimals.xml ksuite/k013b_OrchestralScoreSibelius.xml @@ -11223,6 +11227,16 @@ synthetic/suffix.3.0.xml + + + rpatters1/gliss_to_rest-ref.xml + rpatters1/timesigs_composite-ref.musicxml + rpatters1/tuplet_singletons-ref.xml + + + synthetic/group.3.0.xml + + @@ -11264,7 +11278,7 @@ - foundsuite/Απτάλικο.xml + foundsuite/Απτάλικο.xml ksuite/k013b_OrchestralScoreSibelius.xml recsuite/Echigo_Jishi.xml @@ -11330,6 +11344,33 @@ + + + rpatters1/gliss_to_rest-ref.xml + rpatters1/timesigs_composite-ref.musicxml + rpatters1/tuplet_singletons-ref.xml + + + synthetic/straight.4.0.xml + synthetic/swing-style.4.0.xml + synthetic/swing.4.0.xml + + + + + rpatters1/gliss_to_rest-ref.xml + rpatters1/timesigs_composite-ref.musicxml + rpatters1/tuplet_singletons-ref.xml + + + synthetic/first.4.0.xml + synthetic/second.4.0.xml + synthetic/straight.4.0.xml + synthetic/swing-style.4.0.xml + synthetic/swing-type.4.0.xml + synthetic/swing.4.0.xml + + @@ -11881,14 +11922,6 @@ synthetic/eyeglasses.3.1.xml - - - rpatters1/timesigs_composite-ref.musicxml - - - synthetic/group.3.0.xml - - @@ -12313,16 +12346,6 @@ - - - rpatters1/timesigs_composite-ref.musicxml - - - synthetic/straight.4.0.xml - synthetic/swing-style.4.0.xml - synthetic/swing.4.0.xml - - @@ -12342,19 +12365,6 @@ synthetic/suffix.3.0.xml - - - rpatters1/timesigs_composite-ref.musicxml - - - synthetic/first.4.0.xml - synthetic/second.4.0.xml - synthetic/straight.4.0.xml - synthetic/swing-style.4.0.xml - synthetic/swing-type.4.0.xml - synthetic/swing.4.0.xml - - recsuite/ActorPreludeSample.xml diff --git a/data/rpatters1/gliss_to_rest-ref.features.xml b/data/rpatters1/gliss_to_rest-ref.features.xml new file mode 100644 index 000000000..3c94c91e5 --- /dev/null +++ b/data/rpatters1/gliss_to_rest-ref.features.xml @@ -0,0 +1,304 @@ + + + + rpatters1/gliss_to_rest-ref.xml + 4.0 + + + appearance + + + attributes + + + bar-style + + + barline + + location + + + + beat-type + + + beats + + + bottom-margin + + + clef + + + defaults + + + distance + + type + + + + divisions + + + duration + + + encoding + + + encoding-date + + + fifths + + + glissando + + line-type + number + relative-x + relative-y + type + + + + group + + + identification + + + instrument-name + + + key + + + left-margin + + + line + + + line-width + + type + + + + measure + + number + width + + + + measure-numbering + + multiple-rest-always + multiple-rest-range + system + + + + midi-bank + + + midi-channel + + + midi-instrument + + id + + + + midi-program + + + millimeters + + + mode + + + 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 + + + slide + + line-type + number + relative-x + relative-y + type + + + + software + + + sound + + tempo + + + + stem + + default-y + + + + step + + + straight + + + supports + + attribute + element + type + value + + + + swing + + + system-distance + + + system-layout + + + system-margins + + + tenths + + + time + + + top-margin + + + top-system-distance + + + type + + + voice + + + volume + + + word-font + + font-family + font-size + + + + diff --git a/data/rpatters1/gliss_to_rest-ref.xml b/data/rpatters1/gliss_to_rest-ref.xml new file mode 100644 index 000000000..7a41e4aef --- /dev/null +++ b/data/rpatters1/gliss_to_rest-ref.xml @@ -0,0 +1,152 @@ + + + + + + Finale v27.4 for Mac + 2026-08-25 + + + + + + + + + + 7.2319 + 40 + + + 1545 + 1194 + + 70 + 70 + 88 + 88 + + + + + 0 + 0 + + 121 + 70 + + + 0.7487 + 5 + 0.7487 + 0.7487 + 5 + 0.7487 + 0.7487 + 0.7487 + 0.7487 + 0.7487 + 60 + 60 + 60 + 7.5 + + + + + + + MusicXML Part + score + + SmartMusic SoftSynth 1 + + + 1 + 15489 + 1 + 80 + 0 + + + + + + + + + + 70 + 0 + + 211 + + system + + + 2 + + 0 + major + + + + G + 2 + + + + + + + + + + F + 4 + + 2 + 1 + quarter + up + + + + + + + + 2 + 1 + quarter + + + + F + 4 + + 2 + 1 + quarter + up + + + + + + + + 2 + 1 + quarter + + + light-heavy + + + + + diff --git a/data/rpatters1/tuplet_singletons-ref.features.xml b/data/rpatters1/tuplet_singletons-ref.features.xml new file mode 100644 index 000000000..783880f7b --- /dev/null +++ b/data/rpatters1/tuplet_singletons-ref.features.xml @@ -0,0 +1,307 @@ + + + + rpatters1/tuplet_singletons-ref.xml + 4.0 + + + actual-notes + + + appearance + + + attributes + + + bar-style + + + barline + + location + + + + beat-type + + + beats + + + bottom-margin + + + clef + + + defaults + + + distance + + type + + + + divisions + + + dot + + + duration + + + encoding + + + encoding-date + + + fifths + + + group + + + identification + + + instrument-name + + + key + + + left-margin + + + line + + + line-width + + type + + + + measure + + number + width + + + + measure-numbering + + multiple-rest-always + multiple-rest-range + system + + + + midi-bank + + + midi-channel + + + midi-instrument + + id + + + + midi-program + + + millimeters + + + mode + + + music-font + + font-family + font-size + + + + normal-notes + + + normal-type + + + 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 + + + software + + + sound + + tempo + + + + stem + + default-y + + + + step + + + straight + + + supports + + attribute + element + type + value + + + + swing + + + system-distance + + + system-layout + + + system-margins + + + tenths + + + time + + + time-modification + + + top-margin + + + top-system-distance + + + tuplet + + bracket + number + type + + + + type + + + voice + + + volume + + + word-font + + font-family + font-size + + + + diff --git a/data/rpatters1/tuplet_singletons-ref.xml b/data/rpatters1/tuplet_singletons-ref.xml new file mode 100644 index 000000000..5b428c5ef --- /dev/null +++ b/data/rpatters1/tuplet_singletons-ref.xml @@ -0,0 +1,148 @@ + + + + + + Finale v27.4 for Mac + 2026-08-24 + + + + + + + + + + 7.2319 + 40 + + + 1545 + 1194 + + 70 + 70 + 88 + 88 + + + + + 0 + 0 + + 121 + 70 + + + 0.7487 + 5 + 0.7487 + 0.7487 + 5 + 0.7487 + 0.7487 + 0.7487 + 0.7487 + 0.7487 + 60 + 60 + 60 + 7.5 + + + + + + + MusicXML Part + score + + SmartMusic SoftSynth 1 + + + 1 + 15489 + 1 + 80 + 0 + + + + + + + + + + 70 + 0 + + 211 + + system + + + 6 + + 0 + major + + + + G + 2 + + + + + + + + + + B + 4 + + 12 + 1 + half + + + 3 + 2 + quarter + + down + + + + + + + + 12 + 1 + half + + + 3 + 2 + quarter + + + + + + + + light-heavy + + + + + diff --git a/src/private/mxtest/corert/CoreRoundtripTest.cpp b/src/private/mxtest/corert/CoreRoundtripTest.cpp index ecfe343e6..115f4c66c 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: 838 eligible files, none skipped. Count drift is a failure +// Pinned counts: 840 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() == 838); + CHECK(mxtest::corert::discoverInputFiles().size() == 840); CHECK(g_skippedCount == 0); } From 95fffba0387ca5165cd4f155be813493e611db87 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Wed, 26 Aug 2026 23:48:23 +0000 Subject: [PATCH 2/3] fix: write single-note spanner starts before their stops NotationsWriter wrote every spanner stop before every start, which is right for a note that chains two spanners but backwards for a spanner contained in one note: a nested tuplet covering exactly one note, or a glissando or slide drawn to a rest, came out stop-then-start, closing a span that was never open (#429). Tuplets are decided locally: matching numberLevels on one note always mean a single-note tuplet, so each start is followed by its same-note stop and the remaining stops come last. Glissando, slide, and wavy line cannot be decided locally -- a chained note can carry the same number as a single-note span -- so the SpannerResolver's serialized walk now records the pairs (a stop that closes nothing open pairs with a same-bucket start on its own note) and the writer holds those stops back until right after their starts. Chained spanners keep the stop-before-start order of #139. --- src/private/mx/impl/NotationsWriter.cpp | 136 ++++++-- src/private/mx/impl/NotationsWriter.h | 1 + src/private/mx/impl/SpannerResolver.cpp | 88 ++++- src/private/mx/impl/SpannerResolver.h | 28 +- .../mxtest/api/SingleNoteSpannerApiTest.cpp | 317 ++++++++++++++++++ 5 files changed, 537 insertions(+), 33 deletions(-) create mode 100644 src/private/mxtest/api/SingleNoteSpannerApiTest.cpp diff --git a/src/private/mx/impl/NotationsWriter.cpp b/src/private/mx/impl/NotationsWriter.cpp index 8d990fea9..aff3cd1c6 100644 --- a/src/private/mx/impl/NotationsWriter.cpp +++ b/src/private/mx/impl/NotationsWriter.cpp @@ -69,7 +69,9 @@ #include "mx/impl/ScoreWriter.h" #include "mx/impl/WavyLineFunctions.h" +#include #include +#include namespace mx { @@ -95,6 +97,20 @@ void notationsWriterSetMordentSpecificAttributes(const api::MarkData &mark, core } } +core::NotationsChoice notationsWriterMakeTupletStop(const api::TupletStop &inTupletStop) +{ + core::Tuplet tuplet; + tuplet.setType(core::StartStop::stop()); + setId(inTupletStop.id, tuplet); + + if (inTupletStop.numberLevel > 0) + { + tuplet.setNumber(core::NumberLevel{inTupletStop.numberLevel}); + } + + return core::NotationsChoice::tuplet(tuplet); +} + NotationsWriter::NotationsWriter(const api::NoteData &inNoteData, const MeasureCursor &inCursor, const ScoreWriter &inScoreWriter) : myNoteData{inNoteData}, myCursor{inCursor}, myScoreWriter{inScoreWriter}, myConverter{} @@ -185,19 +201,16 @@ core::Notations NotationsWriter::getNotations() const addGlissandoAndSlide(outNotations); - for (const auto &tupletStop : myNoteData.noteAttachmentData.tupletStops) - { - core::Tuplet tuplet; - tuplet.setType(core::StartStop::stop()); - setId(tupletStop.id, tuplet); - - if (tupletStop.numberLevel > 0) - { - tuplet.setNumber(core::NumberLevel{tupletStop.numberLevel}); - } - - outNotations.addChoice(core::NotationsChoice::tuplet(tuplet)); - } + // A tuplet contained in a single note -- an inner tuplet covering exactly one note of its + // outer tuplet, or Finale's export of a one-note tuplet -- has its start and its stop on + // that one note, and the start must be written first (#429). So starts are written before + // stops, each start followed directly by the same-note stop that shares its numberLevel + // when there is one, and the remaining stops (closing tuplets begun on earlier notes) + // follow. Matching numberLevels on one note always mean a single-note tuplet: two + // different tuplets can never share a note, because a note carries only one + // time-modification. + const auto &tupletStops = myNoteData.noteAttachmentData.tupletStops; + std::vector tupletStopWritten(tupletStops.size(), false); for (const auto &tupletStart : myNoteData.noteAttachmentData.tupletStarts) { @@ -261,12 +274,31 @@ core::Notations NotationsWriter::getNotations() const } outNotations.addChoice(core::NotationsChoice::tuplet(tuplet)); + + for (std::size_t stopIndex = 0; stopIndex < tupletStops.size(); ++stopIndex) + { + if (!tupletStopWritten[stopIndex] && tupletStops[stopIndex].numberLevel == tupletStart.numberLevel) + { + outNotations.addChoice(notationsWriterMakeTupletStop(tupletStops[stopIndex])); + tupletStopWritten[stopIndex] = true; + break; + } + } + } + + for (std::size_t stopIndex = 0; stopIndex < tupletStops.size(); ++stopIndex) + { + if (!tupletStopWritten[stopIndex]) + { + outNotations.addChoice(notationsWriterMakeTupletStop(tupletStops[stopIndex])); + } } // Wavy lines live inside , alongside trill-mark/shake/etc. Stops and continues are // emitted before the mark-derived ornaments below (stop-before-start, as with glissando/slide // above); starts are emitted after, matching the common - // shape real files use. + // shape real files use. A wavy line that starts and stops on the same note is the exception: + // its stop is held back and written right after its start (#429). addWavyLineStopsAndContinues(ornaments); for (const auto &mark : myNoteData.noteAttachmentData.marks) @@ -468,22 +500,19 @@ void NotationsWriter::addGlissandoAndSlide(core::Notations &outNotations) const const auto &spannerResolver = myScoreWriter.getSpannerResolver(); // Glissando and slide are top-level children, like slur/tie. Stops are emitted - // before starts so a chain of glissandi on one note keeps score order (see #139). + // before starts so a chain of glissandi on one note keeps score order (see #139). The + // exception is a glissando or slide that starts and stops on this same note, as when a line + // is drawn from a note toward a rest: its stop is held back and written right after its + // start (#429). The SpannerResolver decides which stops those are -- a chained note and a + // single-note span can carry the same number, and only the stream of earlier events can + // tell them apart. for (const auto &glissandoStop : myNoteData.noteAttachmentData.glissandoStops) { - const auto resolvedNumber = spannerResolver.emittedNumber(glissandoStop.number, &glissandoStop); - if (glissandoStop.glissandoType == api::GlissandoType::slide) - { - core::Slide slide; - writeAttributesFromGlissandoStop(glissandoStop, slide, resolvedNumber); - outNotations.addChoice(core::NotationsChoice::slide(slide)); - } - else + if (spannerResolver.sameNoteSpanPartner(&glissandoStop) != nullptr) { - core::Glissando glissando; - writeAttributesFromGlissandoStop(glissandoStop, glissando, resolvedNumber); - outNotations.addChoice(core::NotationsChoice::glissando(glissando)); + continue; } + addGlissandoStop(glissandoStop, outNotations); } for (const auto &glissandoStart : myNoteData.noteAttachmentData.glissandoStarts) @@ -501,6 +530,37 @@ void NotationsWriter::addGlissandoAndSlide(core::Notations &outNotations) const writeAttributesFromGlissandoStart(glissandoStart, glissando, resolvedNumber); outNotations.addChoice(core::NotationsChoice::glissando(glissando)); } + + const void *sameNoteStop = spannerResolver.sameNoteSpanPartner(&glissandoStart); + if (sameNoteStop != nullptr) + { + for (const auto &glissandoStop : myNoteData.noteAttachmentData.glissandoStops) + { + if (&glissandoStop == sameNoteStop) + { + addGlissandoStop(glissandoStop, outNotations); + } + } + } + } +} + +void NotationsWriter::addGlissandoStop(const api::GlissandoStop &inGlissandoStop, core::Notations &outNotations) const +{ + const auto &spannerResolver = myScoreWriter.getSpannerResolver(); + const auto resolvedNumber = spannerResolver.emittedNumber(inGlissandoStop.number, &inGlissandoStop); + + if (inGlissandoStop.glissandoType == api::GlissandoType::slide) + { + core::Slide slide; + writeAttributesFromGlissandoStop(inGlissandoStop, slide, resolvedNumber); + outNotations.addChoice(core::NotationsChoice::slide(slide)); + } + else + { + core::Glissando glissando; + writeAttributesFromGlissandoStop(inGlissandoStop, glissando, resolvedNumber); + outNotations.addChoice(core::NotationsChoice::glissando(glissando)); } } @@ -508,8 +568,14 @@ void NotationsWriter::addWavyLineStopsAndContinues(core::Ornaments &outOrnaments { const auto &spannerResolver = myScoreWriter.getSpannerResolver(); + // A stop whose wavy line starts on this same note is skipped here; addWavyLineStarts writes + // it right after its start (#429). for (const auto &wavyLineStop : myNoteData.noteAttachmentData.wavyLineStops) { + if (spannerResolver.sameNoteSpanPartner(&wavyLineStop) != nullptr) + { + continue; + } const auto resolvedNumber = spannerResolver.emittedNumber(wavyLineStop.number, &wavyLineStop); core::OrnamentsGroup group; group.setChoice(core::OrnamentsGroupChoice::wavyLine(writeWavyLineStop(wavyLineStop, resolvedNumber))); @@ -535,6 +601,24 @@ void NotationsWriter::addWavyLineStarts(core::Ornaments &outOrnaments) const core::OrnamentsGroup group; group.setChoice(core::OrnamentsGroupChoice::wavyLine(writeWavyLineStart(wavyLineStart, resolvedNumber))); outOrnaments.addGroup(group); + + // A wavy line that starts and stops on this same note gets its stop here, right after + // the start (#429). + const void *sameNoteStop = spannerResolver.sameNoteSpanPartner(&wavyLineStart); + if (sameNoteStop != nullptr) + { + for (const auto &wavyLineStop : myNoteData.noteAttachmentData.wavyLineStops) + { + if (&wavyLineStop == sameNoteStop) + { + const auto stopNumber = spannerResolver.emittedNumber(wavyLineStop.number, &wavyLineStop); + core::OrnamentsGroup stopGroup; + stopGroup.setChoice( + core::OrnamentsGroupChoice::wavyLine(writeWavyLineStop(wavyLineStop, stopNumber))); + outOrnaments.addGroup(stopGroup); + } + } + } } } diff --git a/src/private/mx/impl/NotationsWriter.h b/src/private/mx/impl/NotationsWriter.h index 595bd3827..3b852bbd5 100644 --- a/src/private/mx/impl/NotationsWriter.h +++ b/src/private/mx/impl/NotationsWriter.h @@ -44,6 +44,7 @@ class NotationsWriter // stack frame doesn't scale -- it overflowed the default wasm stack (issue found while // fixing #389's Emscripten smoke build). void addGlissandoAndSlide(core::Notations &outNotations) const; + void addGlissandoStop(const api::GlissandoStop &inGlissandoStop, core::Notations &outNotations) const; void addWavyLineStopsAndContinues(core::Ornaments &outOrnaments) const; void addWavyLineStarts(core::Ornaments &outOrnaments) const; }; diff --git a/src/private/mx/impl/SpannerResolver.cpp b/src/private/mx/impl/SpannerResolver.cpp index 5812afa67..2625e980a 100644 --- a/src/private/mx/impl/SpannerResolver.cpp +++ b/src/private/mx/impl/SpannerResolver.cpp @@ -48,6 +48,10 @@ struct SpannerNumberEvent api::SpannerNumber number; bool opens; // a start bool closes; // a stop + + // The note the event sits on, used to detect spanners that start and stop + // on one note. Direction events do not belong to a note and carry nullptr. + const void *noteTag; }; // [first, last] positions (inclusive) during which a spanner is open in the @@ -73,10 +77,11 @@ struct SpannerOttavaEvent bool isStart; }; -// The bucket an ottava endpoint pairs within: identity endpoints pair by id, explicit endpoints -// by level, and unspecified endpoints with each other. The prefix keeps the three kinds from -// colliding (an identity of "3" is not the explicit level 3). -static std::string spannerOttavaPairingKey(const api::SpannerNumber &inNumber) +// The bucket an endpoint pairs within, used for ottava sizes and for same-note span detection: +// identity endpoints pair by id, explicit endpoints by level, and unspecified endpoints with +// each other. The prefix keeps the three kinds from colliding (an identity of "3" is not the +// explicit level 3). +static std::string spannerPairingKey(const api::SpannerNumber &inNumber) { switch (inNumber.kind()) { @@ -100,6 +105,8 @@ class SpannerEventCollector public: void addNote(const api::NoteData &inNote) { + myCurrentNoteTag = &inNote; + // NotationsWriter emits curve stops, then continues, then starts, and // skips curves whose type is neither tie nor slur. const auto &attachments = inNote.noteAttachmentData; @@ -148,6 +155,8 @@ class SpannerEventCollector // skipped: mx::api does not model 's number attribute. void addDirection(const api::DirectionData &inDirection) { + myCurrentNoteTag = nullptr; + for (const auto &choice : inDirection.directionTypes) { switch (choice.kind()) @@ -212,7 +221,8 @@ class SpannerEventCollector void add(SpannerNumberClass inClass, const void *inObject, const api::SpannerNumber &inNumber, bool inOpens, bool inCloses) { - myEvents[inClass].push_back(SpannerNumberEvent{myPosition, inObject, inNumber, inOpens, inCloses}); + myEvents[inClass].push_back( + SpannerNumberEvent{myPosition, inObject, inNumber, inOpens, inCloses, myCurrentNoteTag}); ++myPosition; } @@ -238,6 +248,7 @@ class SpannerEventCollector } int myPosition = 0; + const void *myCurrentNoteTag = nullptr; std::map> myEvents; std::vector myOttavaEvents; }; @@ -379,7 +390,7 @@ static void spannerResolveOttavaSizes(const std::vector &inE for (const auto &event : inEvents) { - auto &stack = openStarts[spannerOttavaPairingKey(event.number)]; + auto &stack = openStarts[spannerPairingKey(event.number)]; if (event.isStart) { stack.push_back(event.ottavaType); @@ -394,6 +405,54 @@ static void spannerResolveOttavaSizes(const std::vector &inE } } +// Finds spanners whose start and stop sit on the same note and records the two endpoints as +// partners. The events arrive in the writer's plain stop-before-start order, which is what makes +// the decision possible: a stop that closes a spanner opened on an earlier note belongs to that +// spanner (a chain), and a stop that closes nothing pairs with a same-bucket start on its own +// note. A leftover stop with no open start and no same-note start is an authoring error and is +// left unpaired, the same as before this detection existed. +static void spannerDetectSameNoteSpans(const std::vector &inEvents, + std::unordered_map &ioPartners) +{ + std::map openCounts; + std::map> unopenedStops; + + for (const auto &event : inEvents) + { + const auto key = spannerPairingKey(event.number); + if (event.closes) + { + auto &openCount = openCounts[key]; + if (openCount > 0) + { + --openCount; + } + else + { + unopenedStops[key].push_back(&event); + } + } + else if (event.opens) + { + auto &stops = unopenedStops[key]; + const auto sameNoteStop = + std::find_if(stops.cbegin(), stops.cend(), [&event](const SpannerNumberEvent *stop) { + return stop->noteTag != nullptr && stop->noteTag == event.noteTag; + }); + if (sameNoteStop != stops.cend()) + { + ioPartners[event.object] = (*sameNoteStop)->object; + ioPartners[(*sameNoteStop)->object] = event.object; + stops.erase(sameNoteStop); + } + else + { + ++openCounts[key]; + } + } + } +} + void SpannerResolver::resolvePart(const api::PartData &inPart) { SpannerEventCollector collector; @@ -419,6 +478,13 @@ void SpannerResolver::resolvePart(const api::PartData &inPart) for (const auto &classAndEvents : collector.events()) { spannerNumberAssignClass(classAndEvents.second, myResolved); + + const auto spannerClass = classAndEvents.first; + if (spannerClass == SpannerNumberClass::glissando || spannerClass == SpannerNumberClass::slide || + spannerClass == SpannerNumberClass::wavyLine) + { + spannerDetectSameNoteSpans(classAndEvents.second, mySameNoteSpanPartners); + } } spannerResolveOttavaSizes(collector.ottavaEvents(), myOttavaStopSizes); @@ -455,5 +521,15 @@ int SpannerResolver::ottavaStopSize(const void *inObject) const } return iter->second; } + +const void *SpannerResolver::sameNoteSpanPartner(const void *inObject) const +{ + const auto iter = mySameNoteSpanPartners.find(inObject); + if (iter == mySameNoteSpanPartners.cend()) + { + return nullptr; + } + return iter->second; +} } // namespace impl } // namespace mx diff --git a/src/private/mx/impl/SpannerResolver.h b/src/private/mx/impl/SpannerResolver.h index ae8413678..0f7b74197 100644 --- a/src/private/mx/impl/SpannerResolver.h +++ b/src/private/mx/impl/SpannerResolver.h @@ -41,7 +41,10 @@ namespace impl // 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. +// start/stop comes first in the stream -- and releases it afterward. The one +// place the writer departs from this order is a same-note span (see below), +// emitted start-first; its two events stay next to each other, so a number +// chosen from this walk still holds. // // 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. @@ -53,6 +56,21 @@ namespace impl // real score approaches), resolution fails loudly with an exception rather // than emitting an illegal number. // +// == Same-note spans == +// +// A spanner can begin and end on one note. Finale produces this shape +// routinely: it can only anchor a glissando or slide line to a notehead, so a +// line drawn toward a rest starts and stops on the note it leaves from. +// NotationsWriter must emit such a pair start-first, while a note that merely +// chains two spanners (one ends where the next begins) keeps its +// stop-before-start order (#139). The two shapes can carry identical numbers, +// so a note cannot tell them apart on its own. The walk here can: a stop that +// closes a spanner opened on an earlier note belongs to a chain, and a stop +// that closes nothing pairs with a same-numbered start on its own note. Both +// endpoints of each detected pair are recorded so the writer can ask about +// either one (see sameNoteSpanPartner). Only the classes the writer reorders +// are tracked: glissando, slide, and wavy-line (#429). +// // == Octave-shift stop sizes == // // An octave-shift's size is stated by api::OttavaStart::ottavaType alone; @@ -94,9 +112,17 @@ class SpannerResolver // gets MusicXML's default size of 8. int ottavaStopSize(const void *inObject) const; + // The other end of a spanner that starts and stops on the same note, or + // nullptr when the spanner extends beyond its note. inObject must be the + // address of the same start/stop object that resolvePart visited. Tracked + // for glissando, slide, and wavy-line only -- the writer does not reorder + // the other spanner classes. + const void *sameNoteSpanPartner(const void *inObject) const; + private: std::unordered_map myResolved; std::unordered_map myOttavaStopSizes; + std::unordered_map mySameNoteSpanPartners; }; } // namespace impl } // namespace mx diff --git a/src/private/mxtest/api/SingleNoteSpannerApiTest.cpp b/src/private/mxtest/api/SingleNoteSpannerApiTest.cpp new file mode 100644 index 000000000..fdb95c436 --- /dev/null +++ b/src/private/mxtest/api/SingleNoteSpannerApiTest.cpp @@ -0,0 +1,317 @@ +// MusicXML Class Library +// Copyright (c) by Matthew James Briggs +// Distributed under the MIT License + +// Coverage for issue #429: a spanner whose start and stop sit on the same note must be written +// start-first. Finale produces this shape routinely -- it can only anchor a glissando or slide +// line to a notehead, so a line drawn toward a rest starts and stops on one note -- and a nested +// tuplet can cover exactly one note of its outer tuplet. A note that merely chains two spanners +// (one ends where the next begins) keeps the stop-before-start order of #139; that case is +// covered in GlissandoApiTest. + +#include "mxtest/control/CompileControl.h" +#ifdef MX_COMPILE_API_TESTS + +#include "cpul/cpulTestHarness.h" +#include "mx/api/DocumentManager.h" +#include "mxtest/api/RoundTrip.h" +#include "mxtest/api/TestHelpers.h" +#include "pugixml.hpp" + +#include +#include +#include + +using namespace mx::api; +using namespace mxtest; + +namespace singleNoteSpannerApiTest +{ +inline NoteData makeGraceNote(Step inStep, int inOctave) +{ + NoteData note; + note.isGrace = true; + note.pitchData.step = inStep; + note.pitchData.octave = inOctave; + note.durationData.durationName = DurationName::eighth; + note.durationData.durationTimeTicks = 0; + return note; +} + +inline TupletStart makeTripletStart(int inNumberLevel) +{ + TupletStart start; + start.numberLevel = inNumberLevel; + start.actualNumber = 3; + start.actualDurationName = DurationName::quarter; + start.normalNumber = 2; + start.normalDurationName = DurationName::quarter; + return start; +} + +inline ScoreData makeScoreWithOneVoice() +{ + ScoreData score; + score.parts.emplace_back(); + score.parts.back().measures.emplace_back(); + score.parts.back().measures.back().staves.emplace_back(); + return score; +} + +inline VoiceData &getVoice(ScoreData &ioScore) +{ + return ioScore.parts.back().measures.back().staves.back().voices[0]; +} + +inline void collectSpanner(const pugi::xml_node &inNode, const std::string &inElementName, + std::vector> &ioResult) +{ + if (std::string{inNode.name()} == inElementName) + { + ioResult.emplace_back(inNode.attribute("type").value(), inNode.attribute("number").value()); + } + for (const auto &child : inNode.children()) + { + collectSpanner(child, inElementName, ioResult); + } +} + +// The (type, number) attribute pairs of every element named inElementName in the note's +// , in document order, looking through and other wrappers. An element +// without a number attribute reports an empty string. +inline std::vector> spannerSequence(const std::string &inXml, int inNoteIndex, + const std::string &inElementName) +{ + pugi::xml_document xdoc; + if (!xdoc.load_string(inXml.c_str())) + { + return {}; + } + const auto measure = xdoc.document_element().child("part").child("measure"); + auto noteIter = measure.children("note").begin(); + for (int i = 0; i < inNoteIndex; ++i) + { + ++noteIter; + } + std::vector> result; + for (const auto ¬ationsChild : noteIter->child("notations").children()) + { + collectSpanner(notationsChild, inElementName, result); + } + return result; +} +} // namespace singleNoteSpannerApiTest + +// A nested tuplet whose inner tuplet covers exactly one note -- the last note of the outer +// tuplet. That note carries the outer stop and the whole inner tuplet, and the inner start must +// come before the inner stop, with the outer stop after both. +TEST(nestedTupletOnFinalNoteWritesInnerStartFirst, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::c, 4)); + voice.notes.back().noteAttachmentData.tupletStarts.push_back(makeTripletStart(1)); + + voice.notes.push_back(makeGraceNote(Step::d, 4)); + + voice.notes.push_back(makeGraceNote(Step::e, 4)); + TupletStop outerStop; + outerStop.numberLevel = 1; + voice.notes.back().noteAttachmentData.tupletStops.push_back(outerStop); + voice.notes.back().noteAttachmentData.tupletStarts.push_back(makeTripletStart(2)); + TupletStop innerStop; + innerStop.numberLevel = 2; + voice.notes.back().noteAttachmentData.tupletStops.push_back(innerStop); + + const auto sequence = spannerSequence(toXml(score), 2, "tuplet"); + REQUIRE(sequence.size() == 3); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"2"}, sequence.at(0).second); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); + CHECK_EQUAL(std::string{"2"}, sequence.at(1).second); + CHECK_EQUAL(std::string{"stop"}, sequence.at(2).first); + CHECK_EQUAL(std::string{"1"}, sequence.at(2).second); +} + +T_END + +// The mirror image: the inner single-note tuplet sits on the first note of the outer tuplet, so +// the outer start comes first and the inner pair follows. +TEST(nestedTupletOnFirstNoteWritesOuterStartFirst, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::c, 4)); + voice.notes.back().noteAttachmentData.tupletStarts.push_back(makeTripletStart(1)); + voice.notes.back().noteAttachmentData.tupletStarts.push_back(makeTripletStart(2)); + TupletStop innerStop; + innerStop.numberLevel = 2; + voice.notes.back().noteAttachmentData.tupletStops.push_back(innerStop); + + voice.notes.push_back(makeGraceNote(Step::d, 4)); + + voice.notes.push_back(makeGraceNote(Step::e, 4)); + TupletStop outerStop; + outerStop.numberLevel = 1; + voice.notes.back().noteAttachmentData.tupletStops.push_back(outerStop); + + const auto sequence = spannerSequence(toXml(score), 0, "tuplet"); + REQUIRE(sequence.size() == 3); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"1"}, sequence.at(0).second); + CHECK_EQUAL(std::string{"start"}, sequence.at(1).first); + CHECK_EQUAL(std::string{"2"}, sequence.at(1).second); + CHECK_EQUAL(std::string{"stop"}, sequence.at(2).first); + CHECK_EQUAL(std::string{"2"}, sequence.at(2).second); +} + +T_END + +// A one-note tuplet on a plain, un-nested note: start then stop. +TEST(oneNoteTupletWritesStartThenStop, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::b, 4)); + voice.notes.back().noteAttachmentData.tupletStarts.push_back(makeTripletStart(1)); + TupletStop stop; + stop.numberLevel = 1; + voice.notes.back().noteAttachmentData.tupletStops.push_back(stop); + + const auto sequence = spannerSequence(toXml(score), 0, "tuplet"); + REQUIRE(sequence.size() == 2); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); +} + +T_END + +// A glissando drawn from a note toward a rest: both ends sit on the note, with an explicit +// number, and the start must be written first. +TEST(glissandoToRestWritesStartThenStop, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::f, 4)); + GlissandoStart start{GlissandoType::glissando}; + start.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.glissandoStarts.push_back(start); + GlissandoStop stop{GlissandoType::glissando}; + stop.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.glissandoStops.push_back(stop); + + voice.notes.push_back(makeGraceNote(Step::f, 4)); + + const auto sequence = spannerSequence(toXml(score), 0, "glissando"); + REQUIRE(sequence.size() == 2); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"1"}, sequence.at(0).second); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); + CHECK_EQUAL(std::string{"1"}, sequence.at(1).second); +} + +T_END + +// The same shape as a slide, and with identity numbers instead of explicit ones. +TEST(slideToRestWritesStartThenStop, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::f, 4)); + GlissandoStart start{GlissandoType::slide}; + start.number = SpannerNumber("s"); + voice.notes.back().noteAttachmentData.glissandoStarts.push_back(start); + GlissandoStop stop{GlissandoType::slide}; + stop.number = SpannerNumber("s"); + voice.notes.back().noteAttachmentData.glissandoStops.push_back(stop); + + const auto sequence = spannerSequence(toXml(score), 0, "slide"); + REQUIRE(sequence.size() == 2); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); + CHECK_EQUAL(sequence.at(0).second, sequence.at(1).second); +} + +T_END + +// A single-note glissando with no number at all still comes out start-first. With the attribute +// absent a reader falls back to number 1 for pairing, so the order is all it has to go on. +TEST(unnumberedGlissandoWritesStartThenStop, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::f, 4)); + voice.notes.back().noteAttachmentData.glissandoStarts.push_back(GlissandoStart{GlissandoType::glissando}); + voice.notes.back().noteAttachmentData.glissandoStops.push_back(GlissandoStop{GlissandoType::glissando}); + + const auto sequence = spannerSequence(toXml(score), 0, "glissando"); + REQUIRE(sequence.size() == 2); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); +} + +T_END + +// A wavy line that starts and stops on one note: both ends live inside , and the stop +// follows its start instead of being written before the mark-derived ornaments. +TEST(wavyLineOnOneNoteWritesStartThenStop, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::a, 4)); + WavyLineStart start; + start.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.wavyLineStarts.push_back(start); + WavyLineStop stop; + stop.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.wavyLineStops.push_back(stop); + + const auto sequence = spannerSequence(toXml(score), 0, "wavy-line"); + REQUIRE(sequence.size() == 2); + CHECK_EQUAL(std::string{"start"}, sequence.at(0).first); + CHECK_EQUAL(std::string{"stop"}, sequence.at(1).first); +} + +T_END + +// The reader accepts what the writer now produces: a single-note glissando survives a round trip +// with both ends still on the one note. +TEST(glissandoToRestRoundTrips, SingleNoteSpanner) +{ + using namespace singleNoteSpannerApiTest; + auto score = makeScoreWithOneVoice(); + auto &voice = getVoice(score); + + voice.notes.push_back(makeGraceNote(Step::f, 4)); + GlissandoStart start{GlissandoType::glissando}; + start.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.glissandoStarts.push_back(start); + GlissandoStop stop{GlissandoType::glissando}; + stop.number = SpannerNumber(1); + voice.notes.back().noteAttachmentData.glissandoStops.push_back(stop); + + const auto roundTripped = roundTrip(score); + REQUIRE(roundTripped.parts.size() == 1); + const auto ¬es = roundTripped.parts.at(0).measures.at(0).staves.at(0).voices.at(0).notes; + REQUIRE(notes.size() == 1); + CHECK(notes.at(0).noteAttachmentData.glissandoStarts.size() == 1); + CHECK(notes.at(0).noteAttachmentData.glissandoStops.size() == 1); +} + +T_END + +#endif From 58e2c8f91a8218abd03f8691cdbd47dbeeb0b1d6 Mon Sep 17 00:00:00 2001 From: Matthew James Briggs Date: Thu, 27 Aug 2026 00:01:21 +0000 Subject: [PATCH 3/3] test: pin gliss_to_rest-ref.xml in the api-roundtrip baseline The #429 fixture passes the strict read -> write -> read comparison now that single-note spanners are written start-first, so it defends the ordering. tuplet_singletons-ref.xml is not pinned: its ordering is fixed too, but the writer restates tuplet-actual/tuplet-normal on a start whose source omits them, which is a separate fidelity gap. --- src/private/mxtest/api/roundtrip-baseline.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/private/mxtest/api/roundtrip-baseline.txt b/src/private/mxtest/api/roundtrip-baseline.txt index 60eaf48a5..112214d07 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -717,3 +717,12 @@ synthetic/lyric.3.0.xml lysuite/ly02b_Rests_PitchedRests.xml lysuite/ly32b_Articulations_Texts.xml lysuite/ly33b_Spanners_Tie.xml + +# Unblocked by the #429 single-note spanner ordering fix: rpatters1's Finale +# export of a glissando and a tab slide drawn from a note toward a rest, so +# each line starts and stops on the one note it can anchor to, written +# start-then-stop. The writer used to emit every stop before every start, +# inverting the pair. (Its sibling tuplet_singletons-ref.xml is not pinned: +# the ordering is fixed, but the writer restates / +# on a start whose source omits them.) +rpatters1/gliss_to_rest-ref.xml