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..dd3d523f5 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,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); + 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 +335,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 +375,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 +388,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 +399,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 +412,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 a9b548a2b..c529f7eb2 100644 --- a/src/private/mx/impl/MeasureWriter.cpp +++ b/src/private/mx/impl/MeasureWriter.cpp @@ -754,7 +754,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..344849e47 --- /dev/null +++ b/src/private/mx/impl/SpannerNumberResolver.cpp @@ -0,0 +1,457 @@ +// 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.spannerStop, item.spannerStop.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.spannerStop, item.spannerStop.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 412bc20af..abab295f6 100644 --- a/src/private/mxtest/api/ApiK009bSlurScoreData.h +++ b/src/private/mxtest/api/ApiK009bSlurScoreData.h @@ -49,7 +49,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; @@ -77,7 +77,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; @@ -99,7 +99,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; @@ -121,7 +121,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 c4b19619e..e51ff92f6 100644 --- a/src/private/mxtest/api/roundtrip-baseline.txt +++ b/src/private/mxtest/api/roundtrip-baseline.txt @@ -298,3 +298,9 @@ musuite/testAccidentals2.xml # MeasureWriter: the field and its Converter existed but neither side ever # called it, so was silently dropped on round-trip. ksuite/k015a_System_Layout.xml + +# 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/corert/CoreRoundtripTest.cpp b/src/private/mxtest/corert/CoreRoundtripTest.cpp index 0a57d0c4a..187992737 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: 833 eligible files, none skipped. Count drift is a failure +// Pinned counts: 834 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() == 833); + CHECK(mxtest::corert::discoverInputFiles().size() == 834); CHECK(g_skippedCount == 0); } 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());