Quellcode durchsuchen

merge osmd-public (1.8.0): Many fixes and new features (see changelog)

sschmidTU vor 1 Jahr
Ursprung
Commit
c0ea528f3e
27 geänderte Dateien mit 2070 neuen und 51 gelöschten Zeilen
  1. 1 1
      package.json
  2. 28 1
      src/MusicalScore/Graphical/EngravingRules.ts
  3. 2 0
      src/MusicalScore/Graphical/GraphicalLabel.ts
  4. 5 0
      src/MusicalScore/Graphical/GraphicalLyricEntry.ts
  5. 22 6
      src/MusicalScore/Graphical/GraphicalSlur.ts
  6. 29 5
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  7. 4 1
      src/MusicalScore/Graphical/MusicSystemBuilder.ts
  8. 22 1
      src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts
  9. 26 2
      src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts
  10. 3 1
      src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts
  11. 3 1
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  12. 93 2
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  13. 7 2
      src/MusicalScore/Graphical/VexFlow/VexflowStafflineNoteCalculator.ts
  14. 24 11
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  15. 14 6
      src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts
  16. 5 5
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  17. 1 0
      src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts
  18. 9 5
      src/MusicalScore/VoiceData/Note.ts
  19. 1 1
      src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
  20. 4 0
      test/Util/generateImages_browserless.mjs
  21. 151 0
      test/data/test_cajon_2-note-system.musicxml
  22. 215 0
      test/data/test_lyrics_centering.musicxml
  23. 417 0
      test/data/test_note-ticks-0_issue1073.musicxml
  24. 415 0
      test/data/test_place_words_inside_staffline_bongo.musicxml
  25. 106 0
      test/data/test_repeat_left_barline_simple.musicxml
  26. 195 0
      test/data/test_tremolo_unmeasured_buzz_roll.musicxml
  27. 268 0
      test/data/test_tremolo_unmeasured_buzz_roll_drums.musicxml

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "osmd-extended",
-  "version": "1.7.6",
+  "version": "1.8.0",
   "description": "Private / sponsor exclusive OSMD mirror/audio player.",
   "main": "build/opensheetmusicdisplay.min.js",
   "types": "build/dist/src/index.d.ts",

+ 28 - 1
src/MusicalScore/Graphical/EngravingRules.ts

@@ -78,6 +78,10 @@ export class EngravingRules {
     public PercussionUseXMLDisplayStep: boolean;
     public PercussionXMLDisplayStepNoteValueShift: number;
     public PercussionOneLineXMLDisplayStepOctaveOffset: number;
+    /** Makes the score position notes on the 2 cajon stafflines, and use 2 stafflines even if PercussionOneLineCutoff set.
+     * Should only be set for cajon scores, as this will disable the PercussionOneLineCutoff.
+     */
+    public PercussionUseCajon2NoteSystem: boolean;
     public BetweenKeySymbolsDistance: number;
     public KeyRightMargin: number;
     public RhythmRightMargin: number;
@@ -175,6 +179,7 @@ export class EngravingRules {
     public TupletVerticalLineLength: number;
     public TupletNumbersInTabs: boolean;
 
+    public RepetitionAllowFirstMeasureBeginningRepeatBarline: boolean;
     public RepetitionEndingLabelHeight: number;
     public RepetitionEndingLabelXOffset: number;
     public RepetitionEndingLabelYOffset: number;
@@ -189,6 +194,12 @@ export class EngravingRules {
     public LyricsHeight: number;
     public LyricsYOffsetToStaffHeight: number;
     public LyricsYMarginToBottomLine: number;
+    /** Extra x-shift (to the right) for short lyrics to be better vertically aligned.
+     * Also see ChordSymbolExtraXShiftForShortChordSymbols, same principle, same default value.
+     */
+    public LyricsExtraXShiftForShortLyrics: number;
+    /** Threshold of the lyric entry's width below which the x-shift is applied. Default 1.4. */
+    public LyricsExtraXShiftForShortLyricsWidthThreshold: number;
     /** Whether to enable x padding (to the right) for short notes, see LyricsXPaddingFactorForLongLyrics for the degree. */
     public LyricsUseXPaddingForShortNotes: boolean;
     /** How much spacing/padding should be added after notes with long lyrics on short notes
@@ -210,6 +221,8 @@ export class EngravingRules {
     public MaximumLyricsElongationFactor: number;
 
     public SlurPlacementFromXML: boolean;
+    public SlurPlacementAtStems: boolean;
+    public SlurPlacementUseSkyBottomLine: boolean;
     public BezierCurveStepSize: number;
     public TPower3: number[];
     public OneMinusTPower3: number[];
@@ -251,6 +264,7 @@ export class EngravingRules {
     public VexFlowDefaultTabFontScale: number;
     public TremoloStrokeScale: number;
     public TremoloYSpacingScale: number;
+    public TremoloBuzzRollThickness: number;
     public StaffLineWidth: number;
     public StaffLineColor: string;
     public LedgerLineWidth: number;
@@ -276,6 +290,7 @@ export class EngravingRules {
      *  in the target score are, because using a too low width will cause overlaps in Vexflow.
      */
     public FixedMeasureWidthFixedValue: number;
+    public FixedMeasureWidthUseForPickupMeasures: boolean;
     public DistanceBetweenVerticalSystemLines: number;
     public DistanceBetweenDotAndLine: number;
     public RepeatEndStartPadding: number;
@@ -412,6 +427,8 @@ export class EngravingRules {
      * but were inserted as a words element in the MusicXML, which can't be matched to the note anymore,
      * and would otherwise just be placed somewhere else. See OSMD Issue 1251. */
     public IgnoreBracketsWords: boolean;
+    public PlaceWordsInsideStafflineFromXml: boolean;
+    public PlaceWordsInsideStafflineYOffset: number;
     // public PositionMarcatoCloseToNote: boolean;
     public SpacingBetweenTextLines: number;
 
@@ -525,6 +542,7 @@ export class EngravingRules {
         this.PercussionUseXMLDisplayStep = true;
         this.PercussionXMLDisplayStepNoteValueShift = 0;
         this.PercussionOneLineXMLDisplayStepOctaveOffset = 0;
+        this.PercussionUseCajon2NoteSystem = false;
         this.BetweenKeySymbolsDistance = 0.2;
         this.KeyRightMargin = 0.75;
         this.RhythmRightMargin = 1.25;
@@ -584,7 +602,7 @@ export class EngravingRules {
         this.ChordSymbolTextHeight = 2.0;
         this.ChordSymbolTextAlignment = TextAlignmentEnum.LeftBottom;
         this.ChordSymbolRelativeXOffset = -1.0;
-        this.ChordSymbolExtraXShiftForShortChordSymbols = 0.3;
+        this.ChordSymbolExtraXShiftForShortChordSymbols = 0.3; // also see LyricsExtraXShiftForShortLyrics, same principle
         this.ChordSymbolExtraXShiftWidthThreshold = 2.0;
         this.ChordSymbolXSpacing = 1.0;
         this.ChordOverlapAllowedIntoNextMeasure = 0;
@@ -626,6 +644,8 @@ export class EngravingRules {
 
         // Slur and Tie variables
         this.SlurPlacementFromXML = true;
+        this.SlurPlacementAtStems = false;
+        this.SlurPlacementUseSkyBottomLine = false;
         this.BezierCurveStepSize = 1000;
         this.calculateCurveParametersArrays();
         this.TieGhostObjectWidth = 0.75;
@@ -659,6 +679,7 @@ export class EngravingRules {
         this.GlissandoDefaultWidth = 0.1;
 
         // Repetitions
+        this.RepetitionAllowFirstMeasureBeginningRepeatBarline = true;
         this.RepetitionEndingLabelHeight = 2.0;
         this.RepetitionEndingLabelXOffset = 0.5;
         this.RepetitionEndingLabelYOffset = 0.3;
@@ -671,6 +692,8 @@ export class EngravingRules {
         this.LyricsHeight = 2.0; // actually size of lyrics
         this.LyricsYOffsetToStaffHeight = 0.0; // distance between lyrics and staff. could partly be even lower/dynamic
         this.LyricsYMarginToBottomLine = 0.2;
+        this.LyricsExtraXShiftForShortLyrics = 0.5; // also see ChordSymbolExtraXShiftForShortChordSymbols, same principle
+        this.LyricsExtraXShiftForShortLyricsWidthThreshold = 1.4; // width of '+': 1.12, 'II': 1.33 (benefits from x-shift), 'III': 1.99 (doesn't benefit)
         this.LyricsUseXPaddingForShortNotes = true;
         this.LyricsXPaddingFactorForLongLyrics = 0.8;
         this.LyricsXPaddingWidthThreshold = 3.3;
@@ -698,6 +721,7 @@ export class EngravingRules {
         this.VexFlowDefaultTabFontScale = 39;
         this.TremoloStrokeScale = 1;
         this.TremoloYSpacingScale = 1;
+        this.TremoloBuzzRollThickness = 0.25;
         this.StemWidth = 0.15; // originally 0.13. vexflow default 0.15. should probably be adjusted when increasing vexFlowDefaultNotationFontScale,
         this.StaffLineWidth = 0.10; // originally 0.12, but this will be pixels in Vexflow (*10).
         this.StaffLineColor = undefined; // if undefined, vexflow default (grey). not a width, but affects visual line clarity.
@@ -723,6 +747,7 @@ export class EngravingRules {
 
         this.FixedMeasureWidth = false;
         this.FixedMeasureWidthFixedValue = undefined; // only set to a number x if the width should be always x
+        this.FixedMeasureWidthUseForPickupMeasures = false;
 
         // Line Widths
         this.MinimumCrossedBeamDifferenceMargin = 0.0001;
@@ -810,6 +835,8 @@ export class EngravingRules {
         this.RestoreCursorAfterRerender = true;
         this.StretchLastSystemLine = false;
         this.IgnoreBracketsWords = true;
+        this.PlaceWordsInsideStafflineFromXml = false;
+        this.PlaceWordsInsideStafflineYOffset = 0.9;
         // this.PositionMarcatoCloseToNote = true;
 
         this.UseJustifiedBuilder = true;

+ 2 - 0
src/MusicalScore/Graphical/GraphicalLabel.ts

@@ -17,6 +17,8 @@ export class GraphicalLabel extends Clickable {
      *  For the Canvas backend, this is unfortunately not possible.
      */
     public SVGNode: Node;
+    /** Read-only informational variable only set once by lyrics centering algorithm. */
+    public CenteringXShift: number = 0;
 
     /**
      * Creates a new GraphicalLabel from a Label

+ 5 - 0
src/MusicalScore/Graphical/GraphicalLyricEntry.ts

@@ -38,6 +38,11 @@ export class GraphicalLyricEntry {
         );
         this.graphicalLabel.Label.colorDefault = rules.DefaultColorLyrics; // if undefined, no change. saves an if check
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0, staffHeight);
+        this.graphicalLabel.setLabelPositionAndShapeBorders(); // needed to have Size.width
+        if (this.graphicalLabel.PositionAndShape.Size.width < rules.LyricsExtraXShiftForShortLyricsWidthThreshold) {
+            this.graphicalLabel.PositionAndShape.RelativePosition.x += rules.LyricsExtraXShiftForShortLyrics;
+            this.graphicalLabel.CenteringXShift = rules.LyricsExtraXShiftForShortLyrics;
+        }
         if (lyricsTextAlignment === TextAlignmentEnum.LeftBottom) {
             this.graphicalLabel.PositionAndShape.RelativePosition.x -= 1; // make lyrics optically left-aligned
         }

+ 22 - 6
src/MusicalScore/Graphical/GraphicalSlur.ts

@@ -483,14 +483,20 @@ export class GraphicalSlur extends GraphicalCurve {
 
             if (this.placement === PlacementEnum.Above) {
                 startY = slurStartVE.PositionAndShape.RelativePosition.y + slurStartVE.PositionAndShape.BorderTop;
-                // for (const articulation of slurStartVE.parentVoiceEntry.Articulations) {
-                //     if (articulation.placement === PlacementEnum.Above) {
-                //         startY -= 1;
-                //         break;
-                //     }
-                // }
+                if (this.rules.SlurPlacementUseSkyBottomLine) {
+                    startY = Math.min(endY, slurStartVE.parentStaffEntry.getSkylineMin());
+                    // for (const articulation of slurStartVE.parentVoiceEntry.Articulations) {
+                    //     if (articulation.placement === PlacementEnum.Above) {
+                    //         startY -= this.rules.SlurEndArticulationYOffset;
+                    //         break;
+                    //     }
+                    // }
+                }
             } else {
                 startY = slurStartVE.PositionAndShape.RelativePosition.y + slurStartVE.PositionAndShape.BorderBottom;
+                if (this.rules.SlurPlacementUseSkyBottomLine) {
+                    startY = Math.max(endY, slurStartVE.parentStaffEntry.getBottomlineMax());
+                }
                 // for (const articulation of slurStartVE.parentVoiceEntry.Articulations) {
                 //     if (articulation.placement === PlacementEnum.Below) {
                 //         startY += 1;
@@ -538,6 +544,7 @@ export class GraphicalSlur extends GraphicalCurve {
             //   TODO alternatively, we could fix the bounding box of the note to include the ornament, but that seems tricky
             let articulationPlacement: PlacementEnum; // whether there's an articulation and where
             for (const articulation of slurEndVE.parentVoiceEntry.Articulations) {
+                articulationPlacement = articulation.placement;
                 if (articulation.placement === PlacementEnum.NotYetDefined) {
                     for (const modifier of ((slurEndNote as VexFlowGraphicalNote).vfnote[0] as any).modifiers) {
                         if (modifier.getCategory() === VF.Articulation.CATEGORY) {
@@ -555,11 +562,17 @@ export class GraphicalSlur extends GraphicalCurve {
             }
             if (this.placement === PlacementEnum.Above) {
                 endY = slurEndVE.PositionAndShape.RelativePosition.y + slurEndVE.PositionAndShape.BorderTop;
+                if (this.rules.SlurPlacementUseSkyBottomLine) {
+                    endY = Math.min(endY, slurEndVE.parentStaffEntry.getSkylineMin());
+                }
                 if (articulationPlacement === PlacementEnum.Above) {
                     endY -= this.rules.SlurEndArticulationYOffset;
                 }
             } else {
                 endY = slurEndVE.PositionAndShape.RelativePosition.y + slurEndVE.PositionAndShape.BorderBottom;
+                if (this.rules.SlurPlacementUseSkyBottomLine) {
+                    endY = Math.max(endY, slurEndVE.parentStaffEntry.getBottomlineMax());
+                }
                 if (articulationPlacement === PlacementEnum.Below) {
                     endY += this.rules.SlurEndArticulationYOffset;
                 }
@@ -695,6 +708,9 @@ export class GraphicalSlur extends GraphicalCurve {
         if (startStemDirection  ===
             endStemDirection) {
             this.placement = (startStemDirection === StemDirectionType.Up) ? PlacementEnum.Below : PlacementEnum.Above;
+            if (this.rules.SlurPlacementAtStems) {
+                this.placement = (startStemDirection === StemDirectionType.Up) ? PlacementEnum.Above : PlacementEnum.Below;
+            }
         } else {
             // Placement at the side with the minimum border
             let sX: number = startStaffEntry.PositionAndShape.BorderLeft + startStaffEntry.PositionAndShape.RelativePosition.x

+ 29 - 5
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -317,7 +317,7 @@ export abstract class MusicSheetCalculator {
             MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minimumStaffEntriesWidth);
             // minLength = minimumStaffEntriesWidth * 1.2 + maxInstrNameLabelLength + maxInstructionsLength;
             let maxWidth: number = 0;
-            for (let i: number = 1; i < this.graphicalMusicSheet.MeasureList.length; i++) {
+            for (let i: number = 0; i < this.graphicalMusicSheet.MeasureList.length; i++) {
                 measures = this.graphicalMusicSheet.MeasureList[i];
                 minimumStaffEntriesWidth = this.calculateMeasureXLayout(measures);
                 minimumStaffEntriesWidth = this.calculateMeasureWidthFromStaffEntries(measures, minimumStaffEntriesWidth);
@@ -337,8 +337,12 @@ export abstract class MusicSheetCalculator {
                 if (this.rules.FixedMeasureWidthFixedValue) {
                     targetWidth = this.rules.FixedMeasureWidthFixedValue;
                 }
-                for (let i: number = 1; i < this.graphicalMusicSheet.MeasureList.length; i++) {
+                for (let i: number = 0; i < this.graphicalMusicSheet.MeasureList.length; i++) {
                     measures = this.graphicalMusicSheet.MeasureList[i];
+                    if (!this.rules.FixedMeasureWidthUseForPickupMeasures && measures[0]?.parentSourceMeasure.ImplicitMeasure) {
+                        // note that measures[0] is undefined for multi-measure rests
+                        continue;
+                    }
                     MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, targetWidth);
                 }
             }
@@ -754,6 +758,7 @@ export abstract class MusicSheetCalculator {
         const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
         let relative: PointF2D = new PointF2D();
 
+        const defaultYXml: number = multiExpression.UnknownList[0]?.defaultYXml;
         if ((multiExpression.MoodList.length > 0) || (multiExpression.UnknownList.length > 0)) {
         let combinedExprString: string  = "";
         for (let idx: number = 0, len: number = multiExpression.EntriesList.length; idx < len; ++idx) {
@@ -789,6 +794,13 @@ export abstract class MusicSheetCalculator {
                                                                 multiExpression.getFontstyleOfFirstEntry(),
                                                                 placement,
                                                                 fontHeight);
+        if (this.rules.PlaceWordsInsideStafflineFromXml) {
+            if (defaultYXml < 0 && defaultYXml > -50) { // within staffline
+                let newY: number = defaultYXml / 10; // OSMD units
+                newY += this.rules.PlaceWordsInsideStafflineYOffset;
+                graphLabel.PositionAndShape.RelativePosition.y = newY;
+            }
+        }
 
         const gue: GraphicalUnknownExpression = new GraphicalUnknownExpression(
             staffLine, graphLabel, placement, measures[staffIndex]?.parentSourceMeasure, multiExpression);
@@ -2779,7 +2791,7 @@ export abstract class MusicSheetCalculator {
                     continue;
                 }
                 //This property is active...
-                if (this.rules.PercussionOneLineCutoff !== undefined && this.rules.PercussionOneLineCutoff !== 0) {
+                if (this.rules.PercussionOneLineCutoff > 0 && !this.rules.PercussionUseCajon2NoteSystem) {
                     //We have a percussion clef, check to see if this property applies...
                     if (staffIsPercussionArray[idx2]) {
                         //-1 means always trigger, or we are under the cutoff number specified
@@ -3112,7 +3124,8 @@ export abstract class MusicSheetCalculator {
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
                 lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
-                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight -
+                lyricEntry.GraphicalLabel.CenteringXShift; // TODO not sure why this is necessary, see Christbaum measure 9+11, Land der Berge 11-12
 
             const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffentry.PositionAndShape.RelativePosition.x +
@@ -3208,8 +3221,19 @@ export abstract class MusicSheetCalculator {
     private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
         const label: Label = new Label("-");
         label.colorDefault = this.rules.DefaultColorLyrics; // if undefined, no change. saves an if check
+        let textHeight: number = this.rules.LyricsHeight;
+        if (endX - startX < 0.8) {
+            textHeight *= 0.8;
+            y -= 0.1 * textHeight; // dash moves downwards when textHeight is reduced. counteract that.
+            //xShift = -0.1;
+            // x-position is situational, sometimes it's slightly right-leaning and tends to overlap with the right LyricsEntry
+            //   (see Cornelius - Christbaum, measure 9 and 11 ("li-che", "li-ger"), due to centering x-shift = GraphicalLabel.CenteringXShift)
+            // sometimes the x-position is perfect and the interval is extremely narrow
+            //   (see Mozart/Holzer Land der Berge measure 11-12)
+            // or even slightly too far left (Beethoven Geliebte measure 4, due to centering x-shift = GraphicalLabel.CenteringXShift)
+        }
         const dash: GraphicalLabel = new GraphicalLabel(
-            label, this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
+            label, textHeight, TextAlignmentEnum.CenterBottom, this.rules);
         dash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(dash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {

+ 4 - 1
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -714,8 +714,11 @@ export class MusicSystemBuilder {
     protected getMeasureStartLine(): SystemLinesEnum {
         const thisMeasureBeginsLineRep: boolean = this.thisMeasureBeginsLineRepetition();
         if (thisMeasureBeginsLineRep) {
-            const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
             const isGlobalFirstMeasure: boolean = this.measureListIndex === 0;
+            if (isGlobalFirstMeasure && this.rules.RepetitionAllowFirstMeasureBeginningRepeatBarline) {
+                return SystemLinesEnum.BoldThinDots;
+            }
+            const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
             if (this.previousMeasureEndsLineRepetition() && !isSystemStartMeasure) {
                 return SystemLinesEnum.DotsBoldBoldDots;
             }

+ 22 - 1
src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts

@@ -128,7 +128,7 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         return undefined; // can't return dom node like with SVG
     }
 
-    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number= 2): Node {
+    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2, id?: string): Node {
         const oldStyle: string | CanvasGradient | CanvasPattern = this.CanvasRenderingCtx.strokeStyle;
         this.CanvasRenderingCtx.strokeStyle = color;
         this.CanvasRenderingCtx.beginPath();
@@ -166,6 +166,27 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         return undefined;
     }
 
+    public renderPath(points: PointF2D[], fill: boolean = true, id?: string): Node {
+        this.ctx.beginPath();
+        let currentPoint: PointF2D;
+        for (const point of points) {
+            if (!currentPoint) {
+                this.ctx.moveTo(point.x, point.y);
+                currentPoint = point;
+                continue;
+            }
+            this.ctx.lineTo(point.x, point.y);
+            // this.ctx.stroke();
+        }
+        this.ctx.closePath();
+        if (fill) {
+            this.ctx.fill();
+        } else {
+            this.ctx.stroke(); // just trace outline, don't fill inner area
+        }
+        return undefined;
+    }
+
     private ctx: VF.CanvasContext;
 
     public get CanvasRenderingCtx(): CanvasRenderingContext2D {

+ 26 - 2
src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts

@@ -168,9 +168,9 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         return node;
     }
 
-    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2): Node {
+    public renderLine(start: PointF2D, stop: PointF2D, color: string = "#FF0000FF", lineWidth: number = 2, id?: string): Node {
         this.ctx.save();
-        const node: Node = this.ctx.openGroup("line");
+        const node: Node = this.ctx.openGroup("line", id);
         this.ctx.beginPath();
         this.ctx.moveTo(start.x, start.y);
         this.ctx.lineTo(stop.x, stop.y);
@@ -217,6 +217,30 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         return node;
     }
 
+    public renderPath(points: PointF2D[], fill: boolean = true, id?: string): Node {
+        const node: Node = this.ctx.openGroup("path", id);
+        this.ctx.beginPath();
+        let currentPoint: PointF2D;
+        for (const point of points) {
+            if (!currentPoint) {
+                this.ctx.moveTo(point.x, point.y);
+                currentPoint = point;
+                continue;
+            }
+            this.ctx.lineTo(point.x, point.y);
+            // this.ctx.stroke();
+        }
+        this.ctx.closePath();
+        if (fill) {
+            this.ctx.fill();
+        } else {
+            this.ctx.stroke(); // just trace outline, don't fill inner area
+        }
+        this.ctx.stroke();
+        this.ctx.closeGroup();
+        return node;
+    }
+
     public export(): void {
         // See: https://stackoverflow.com/questions/38477972/javascript-save-svg-element-to-file-on-disk
 

+ 3 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts

@@ -102,10 +102,12 @@ public abstract getContext(): Vex.IRenderContext;
    */
   public abstract renderRectangle(rectangle: RectangleF2D, styleId: number, colorHex: string, alpha: number): Node;
 
-  public abstract renderLine(start: PointF2D, stop: PointF2D, color: string, lineWidth: number): Node;
+  public abstract renderLine(start: PointF2D, stop: PointF2D, color: string, lineWidth: number, id?: string): Node;
 
   public abstract renderCurve(points: PointF2D[]): Node;
 
+  public abstract renderPath(points: PointF2D[], fill: boolean, id?: string): Node;
+
   public abstract getVexflowBackendType(): VF.Renderer.Backends;
 
   /** The general type of backend: Canvas or SVG.

+ 3 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -1318,8 +1318,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
 
                 const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
                 if (vexFlowVoiceEntry.vfStaveNote.getTicks().denominator === 0) {
-                    continue; // TODO not sure why the ticks aren't calculated correctly, see #1073
+                    vexFlowVoiceEntry.vfStaveNote.getTicks().denominator = 1;
+                    // TODO not sure why the ticks aren't calculated correctly, see #1073
                     // if denominator === 0, addTickable() below goes into an infinite loop.
+                    // continue; // previous solution, but can lead to valid notes skipped, further problems, see #1073
                 }
                 if (voiceEntry.notes.length === 0 || !voiceEntry.notes[0] || !voiceEntry.notes[0].sourceNote.PrintObject) {
                     // GhostNote, don't add modifiers like in-measure clefs

+ 93 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -32,6 +32,9 @@ import { GraphicalUnknownExpression } from "../GraphicalUnknownExpression";
 import { VexFlowPedal } from "./VexFlowPedal";
 import { GraphicalGlissando } from "../GraphicalGlissando";
 import { VexFlowGlissando } from "./VexFlowGlissando";
+import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
+import { SvgVexFlowBackend } from "./SvgVexFlowBackend";
+import { CanvasVexFlowBackend } from "./CanvasVexFlowBackend";
 import { VexflowVibratoBracket } from "./VexflowVibratoBracket";
 
 /**
@@ -226,12 +229,84 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         } catch (ex) {
             log.warn("VexFlowMusicSheetDrawer.drawMeasure", ex);
         }
+
+        let newBuzzRollId: number = 0;
         // Draw the StaffEntries
         for (const staffEntry of measure.staffEntries) {
             this.drawStaffEntry(staffEntry);
+            newBuzzRollId = this.drawBuzzRolls(staffEntry, newBuzzRollId);
         }
     }
 
+    protected drawBuzzRolls(staffEntry: GraphicalStaffEntry, newBuzzRollId): number {
+        for (const gve of staffEntry.graphicalVoiceEntries) {
+            for (const note of gve.notes) {
+                if (note.sourceNote.TremoloInfo?.tremoloUnmeasured) {
+                    const thickness: number = this.rules.TremoloBuzzRollThickness;
+                    const baseLength: number = 0.9;
+                    const baseHeight: number = 0.5;
+
+                    const vfNote: VexFlowGraphicalNote = note as VexFlowGraphicalNote;
+                    let stemTip: PointF2D;
+                    let stemHeight: number;
+                    const directionSign: number = vfNote.vfnote[0].getStemDirection(); // 1 or -1
+                    if (this.backend instanceof SvgVexFlowBackend) {
+                        const stemElement: HTMLElement = vfNote.getStemSVG();
+                        const rect: SVGRect = (stemElement as any).getBBox();
+                        stemTip = new PointF2D(rect.x / 10, rect.y / 10);
+                        stemHeight = rect.height / 10;
+                    } else if (this.backend instanceof CanvasVexFlowBackend) {
+                        stemHeight = vfNote.vfnote[0].getStemLength() / 10;
+                        stemTip = new PointF2D(
+                            (vfNote.vfnote[0].getStem() as any).x_begin / 10,
+                            (vfNote.vfnote[0].getStem() as any).y_top / 10,
+                        );
+                        if (directionSign === 1) {
+                            stemTip.y -= stemHeight;
+                        }
+                    }
+                    // this.DrawOverlayLine(stemTip, new PointF2D(stemTip.x + 5, stemTip.y), vfNote.ParentMusicPage); // debug
+
+                    let startHeight: number = stemTip.y + stemHeight / 3;
+                    if (vfNote.vfnote[0].getBeamCount() > 1) {
+                        startHeight = stemTip.y + (stemHeight / 2);
+                        if (directionSign === -1) {
+                            // downwards stem, z paints in downwards direction, so we need to start further up
+                            startHeight -= (baseHeight + 0.2);
+                        }
+                        // note that buzz rolls usually don't appear on notes smaller than 16ths, rather on longer ones
+                    }
+
+                    const buzzStartX: number = stemTip.x - 0.5; // top left start point
+                    const buzzStartY: number = startHeight;
+                    const pathPoints: PointF2D[] = [];
+                    // movements to draw the "z" point by point: (drawing by numbers)
+                    const movements: PointF2D[] = [
+                        new PointF2D(0, -thickness), // down a bit
+                        new PointF2D(baseLength-thickness, 0), // to the right
+                        new PointF2D(-baseLength+thickness,-baseHeight), // down left (etc)
+                        new PointF2D(0, -thickness),
+                        new PointF2D(baseLength, 0),
+                        new PointF2D(0, thickness),
+                        new PointF2D(-baseLength+thickness, 0),
+                        new PointF2D(baseLength-thickness, baseHeight),
+                        new PointF2D(0, thickness),
+                        new PointF2D(-baseLength, 0)
+                    ];
+                    let currentPoint: PointF2D = new PointF2D(buzzStartX, buzzStartY);
+                    pathPoints.push(currentPoint);
+                    for (const movement of movements) {
+                        currentPoint = pathPoints.last();
+                        pathPoints.push(new PointF2D(currentPoint.x + movement.x, currentPoint.y - movement.y));
+                    }
+                    this.DrawPath(pathPoints, vfNote.ParentMusicPage, true, `buzzRoll${newBuzzRollId}`);
+                    newBuzzRollId++;
+                }
+            }
+        }
+        return newBuzzRollId;
+    }
+
     // private drawPixel(coord: PointF2D): void {
     //     coord = this.applyScreenTransformation(coord);
     //     const ctx: any = this.backend.getContext();
@@ -260,7 +335,8 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
      *  To get a MusicPage, use GraphicalNote.ParentMusicPage.
      */
     public DrawOverlayLine(start: PointF2D, stop: PointF2D, musicPage: GraphicalMusicPage,
-                           color: string = "#FF0000FF", lineWidth: number = 0.2): Node {
+                           color: string = "#FF0000FF", lineWidth: number = 0.2,
+                           id?: string): Node {
         if (!musicPage.PageNumber || musicPage.PageNumber > this.backends.length || musicPage.PageNumber < 1) {
             console.log("VexFlowMusicSheetDrawer.drawOverlayLine: invalid page number / music page number doesn't correspond to an existing backend.");
             return;
@@ -270,7 +346,22 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
         start = this.applyScreenTransformation(start);
         stop = this.applyScreenTransformation(stop);
-        return backendToUse.renderLine(start, stop, color, lineWidth * unitInPixels);
+        if (!id) {
+            id = `overlayLine ${start.x}/${start.y}`;
+        }
+        return backendToUse.renderLine(start, stop, color, lineWidth * unitInPixels, id);
+    }
+
+    public DrawPath(inputPoints: PointF2D[], musicPage: GraphicalMusicPage,
+        fill: boolean = true, id?: string): Node {
+        const musicPageIndex: number = musicPage.PageNumber - 1;
+        const backendToUse: VexFlowBackend = this.backends[musicPageIndex];
+
+        const transformedPoints: PointF2D[] = [];
+        for (const inputPoint of inputPoints) {
+            transformedPoints.push(this.applyScreenTransformation(inputPoint));
+        }
+        return backendToUse.renderPath(transformedPoints, fill, id);
     }
 
     protected drawSkyLine(staffline: StaffLine): void {

+ 7 - 2
src/MusicalScore/Graphical/VexFlow/VexflowStafflineNoteCalculator.ts

@@ -92,7 +92,7 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
         //const xmlSingleStaffline: boolean = graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaff.StafflineCount === 1;
         const positionByXml: boolean = this.rules.PercussionUseXMLDisplayStep &&
             graphicalNote.sourceNote.displayStepUnpitched !== undefined;
-        if (currentPitchList.length > this.rules.PercussionOneLineCutoff && !positionByXml) {
+        if (currentPitchList.length > this.rules.PercussionOneLineCutoff && !positionByXml && !this.rules.PercussionUseCajon2NoteSystem) {
             //Don't need to position notes. We aren't under the cutoff
             return graphicalNote;
         }
@@ -101,7 +101,12 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
 
         let displayNote: NoteEnum = this.baseLineNote;
         let displayOctave: number = this.baseLineOctave;
-        if (this.rules.PercussionUseXMLDisplayStep
+        if (this.rules.PercussionUseCajon2NoteSystem) {
+            if (notePitch.FundamentalNote === NoteEnum.C) {
+                displayNote = NoteEnum.G;
+                displayOctave = 1;
+            }
+        } else if (this.rules.PercussionUseXMLDisplayStep
             && graphicalNote.sourceNote.displayStepUnpitched !== undefined) {
             //&& xmlSingleStaffline) {
             displayNote = graphicalNote.sourceNote.displayStepUnpitched;

+ 24 - 11
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -26,6 +26,7 @@ import {StemDirectionType} from "../VoiceData/VoiceEntry";
 import {NoteType, NoteTypeHandler} from "../VoiceData/NoteType";
 import { SystemLinesEnumHelper } from "../Graphical/SystemLinesEnum";
 import { ReaderPluginManager } from "./ReaderPluginManager";
+import { TremoloInfo } from "../VoiceData/Note";
 // import {Dictionary} from "typescript-collections";
 
 // FIXME: The following classes are missing
@@ -321,13 +322,13 @@ export class InstrumentReader {
           // check stem element
           const [stemDirectionXml, stemColorXml, noteheadColorXml] = this.getStemDirectionAndColors(xmlNode);
 
-          // check Tremolo
-          let tremoloStrokes: number = 0;
+          // check Tremolo, Vibrato
+          let tremoloInfo: TremoloInfo;
           //let vibratoStrokes: boolean = false; // not necessary, handled by wavy-line
           if (notationsNode) {
             const ornamentsNode: IXmlElement = notationsNode.element("ornaments");
             if (ornamentsNode) {
-              tremoloStrokes = this.getTremoloStrokes(ornamentsNode);
+              tremoloInfo = this.getTremoloInfo(ornamentsNode);
               this.getWavyLines(ornamentsNode, xmlNode, currentFraction, previousFraction);
             }
           }
@@ -389,7 +390,7 @@ export class InstrumentReader {
             this.currentStaffEntry, this.currentMeasure,
             measureStartAbsoluteTimestamp,
             this.maxTieNoteFraction, isChord, octavePlusOne,
-            printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml,
+            printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloInfo, stemColorXml, noteheadColorXml,
             dots
           );
 
@@ -1422,19 +1423,31 @@ export class InstrumentReader {
     return null;
   }
 
-  private getTremoloStrokes(ornamentsNode: IXmlElement): number {
+  private getTremoloInfo(ornamentsNode: IXmlElement): TremoloInfo {
+    let tremoloStrokes: number;
+    let tremoloUnmeasured: boolean;
     const tremoloNode: IXmlElement = ornamentsNode.element("tremolo");
     if (tremoloNode) {
       const tremoloType: Attr = tremoloNode.attribute("type");
-      if (tremoloType && tremoloType.value === "single") {
-        const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10);
-        if (tremoloStrokesGiven > 0) {
-          return tremoloStrokesGiven;
+      if (tremoloType) {
+        if (tremoloType.value === "single") {
+          const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10);
+          if (tremoloStrokesGiven > 0) {
+            tremoloStrokes = tremoloStrokesGiven;
+          }
+        } else {
+          tremoloStrokes = 0;
+        }
+        if (tremoloType.value === "unmeasured") {
+          tremoloUnmeasured = true;
         }
+        // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
       }
-      // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
     }
-    return 0;
+    return {
+      tremoloStrokes: tremoloStrokes,
+      tremoloUnmeasured: tremoloUnmeasured
+    };
   }
 
   private getWavyLines(ornamentsNode: IXmlElement, xmlNode: IXmlElement, currentFraction: Fraction, previousFraction: Fraction): void {

+ 14 - 6
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -576,6 +576,13 @@ export class ExpressionReader {
                 fontStyle = FontStyles.Italic;
             }
         }
+        let defaultYXml: number;
+        if (currentMeasure.Rules.PlaceWordsInsideStafflineFromXml) {
+            const defaultYString: string = wordsNode.attribute("default-y")?.value;
+            if (defaultYString?.length > 0) {
+                defaultYXml = Number.parseInt(defaultYString, 10);
+            }
+        }
         if (text.length > 0) {
             if (wordsNode.hasAttributes && wordsNode.attribute("default-x")) {
                 this.directionTimestamp = Fraction.createFromFraction(inSourceMeasureCurrentFraction);
@@ -583,7 +590,7 @@ export class ExpressionReader {
             if (this.checkIfWordsNodeIsRepetitionInstruction(text)) {
                 return;
             }
-            this.fillMultiOrTempoExpression(text, currentMeasure, inSourceMeasureCurrentFraction, fontStyle);
+            this.fillMultiOrTempoExpression(text, currentMeasure, inSourceMeasureCurrentFraction, fontStyle, defaultYXml);
             this.initialize();
         }
     }
@@ -701,7 +708,7 @@ export class ExpressionReader {
         }
     }
     private fillMultiOrTempoExpression(inputString: string, currentMeasure: SourceMeasure, inSourceMeasureCurrentFraction: Fraction,
-        fontStyle: FontStyles): void {
+        fontStyle: FontStyles, defaultYXml: number = undefined): void {
         if (!inputString) {
             return;
         }
@@ -710,7 +717,7 @@ export class ExpressionReader {
         //const splitStrings: string[] = tmpInputString.split(/([\s,\r\n]and[\s,\r\n]|[\s,\r\n]und[\s,\r\n]|[\s,\r\n]e[\s,\r\n]|[\s,\r\n])+/g);
 
         //for (const splitStr of splitStrings) {
-        this.createExpressionFromString("", tmpInputString, currentMeasure, inSourceMeasureCurrentFraction, inputString, fontStyle);
+        this.createExpressionFromString("", tmpInputString, currentMeasure, inSourceMeasureCurrentFraction, inputString, fontStyle, defaultYXml);
         //}
     }
     /*
@@ -743,7 +750,8 @@ export class ExpressionReader {
     */
     private createExpressionFromString(prefix: string, stringTrimmed: string,
                                        currentMeasure: SourceMeasure, inSourceMeasureCurrentFraction, inputString: string,
-                                       fontStyle: FontStyles): boolean {
+                                       fontStyle: FontStyles,
+                                       defaultYXml: number = undefined): boolean {
         if (InstantaneousTempoExpression.isInputStringInstantaneousTempo(stringTrimmed) ||
             ContinuousTempoExpression.isInputStringContinuousTempo(stringTrimmed)) {
             // first check if there is already a tempo expression with the same function
@@ -855,9 +863,9 @@ export class ExpressionReader {
         }
         const unknownExpression: UnknownExpression = new UnknownExpression(
             stringTrimmed, this.placement, textAlignment, this.staffNumber);
-            unknownExpression.fontStyle = fontStyle;
+        unknownExpression.fontStyle = fontStyle;
+        unknownExpression.defaultYXml = defaultYXml;
         unknownMultiExpression.addExpression(unknownExpression, prefix);
-
         return false;
     }
     private closeOpenContinuousDynamic(openContinuousDynamicExpression: ContinuousDynamicExpression, endMeasure: SourceMeasure, timestamp: Fraction): void {

+ 5 - 5
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -2,7 +2,7 @@ import { LinkedVoice } from "../VoiceData/LinkedVoice";
 import { Voice } from "../VoiceData/Voice";
 import { MusicSheet } from "../MusicSheet";
 import { VoiceEntry, StemDirectionType } from "../VoiceData/VoiceEntry";
-import { Note } from "../VoiceData/Note";
+import { Note, TremoloInfo } from "../VoiceData/Note";
 import { SourceMeasure } from "../VoiceData/SourceMeasure";
 import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
 import { Beam } from "../VoiceData/Beam";
@@ -153,7 +153,7 @@ export class VoiceGenerator {
   public read(noteNode: IXmlElement, noteDuration: Fraction, typeDuration: Fraction, noteTypeXml: NoteType, normalNotes: number, restNote: boolean,
               parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
               measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, octavePlusOne: boolean,
-              printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
+              printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloInfo: TremoloInfo,
               stemColorXml: string, noteheadColorXml: string,
               dotsXml: number): Note {
     this.currentStaffEntry = parentStaffEntry;
@@ -164,7 +164,7 @@ export class VoiceGenerator {
       this.currentNote = restNote
         ? this.addRestNote(noteNode.element("rest"), noteDuration, noteTypeXml, typeDuration, normalNotes, printObject, isCueNote, noteheadColorXml)
         : this.addSingleNote(noteNode, noteDuration, noteTypeXml, typeDuration, normalNotes, chord, octavePlusOne,
-                             printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml);
+                             printObject, isCueNote, isGraceNote, stemDirectionXml, tremoloInfo, stemColorXml, noteheadColorXml);
       this.currentNote.DotsXml = dotsXml;
       // read lyrics
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
@@ -377,7 +377,7 @@ export class VoiceGenerator {
    */
   private addSingleNote(node: IXmlElement, noteDuration: Fraction, noteTypeXml: NoteType, typeDuration: Fraction,
                         normalNotes: number, chord: boolean, octavePlusOne: boolean,
-                        printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
+                        printObject: boolean, isCueNote: boolean, isGraceNote: boolean, stemDirectionXml: StemDirectionType, tremoloInfo: TremoloInfo,
                         stemColorXml: string, noteheadColorXml: string): Note {
     //log.debug("addSingleNote called");
     let noteAlter: number = 0;
@@ -541,7 +541,7 @@ export class VoiceGenerator {
     note.TypeLength = typeDuration;
     note.IsGraceNote = isGraceNote;
     note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
-    note.TremoloStrokes = tremoloStrokes; // could be a Tremolo object in future if we have more data to manage like two-note tremolo
+    note.TremoloInfo = tremoloInfo;
     note.PlaybackInstrumentId = playbackInstrumentId;
     if ((noteheadShapeXml !== undefined && noteheadShapeXml !== "normal") || noteheadFilledXml !== undefined) {
       note.Notehead = new Notehead(note, noteheadShapeXml, noteheadFilledXml);

+ 1 - 0
src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts

@@ -16,6 +16,7 @@ export class UnknownExpression extends AbstractExpression {
     private textAlignment: TextAlignmentEnum;
     private staffNumber: number;
     public fontStyle: FontStyles;
+    public defaultYXml: number;
 
     public get Label(): string {
         return this.label;

+ 9 - 5
src/MusicalScore/VoiceData/Note.ts

@@ -85,7 +85,7 @@ export class Note {
     /** The number of tremolo strokes this note has (16th tremolo = 2 strokes).
      * Could be a Tremolo object in future when there is more data like tremolo between two notes.
      */
-    private tremoloStrokes: number;
+    public TremoloInfo: TremoloInfo;
     /** Color of the stem given in the XML Stem tag. RGB Hexadecimal, like #00FF00.
      * This is not used for rendering, which takes VoiceEntry.StemColor.
      * It is merely given in the note's stem element in XML and stored here for reference.
@@ -229,10 +229,7 @@ export class Note {
         this.stemDirectionXml = value;
     }
     public get TremoloStrokes(): number {
-        return this.tremoloStrokes;
-    }
-    public set TremoloStrokes(value: number) {
-        this.tremoloStrokes = value;
+        return this.TremoloInfo?.tremoloStrokes;
     }
     public get StemColorXml(): string {
         return this.stemColorXml;
@@ -311,3 +308,10 @@ export enum Appearance {
     Grace,
     Cue
 }
+
+export interface TremoloInfo {
+    tremoloStrokes: number;
+    /** Buzz roll (type="unmeasured" in XML) */
+    tremoloUnmeasured: boolean;
+    // could in future be extended e.g. for tremolo between notes
+}

+ 1 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -35,7 +35,7 @@ import { DynamicsCalculator } from "../MusicalScore/ScoreIO/MusicSymbolModules/D
  * After the constructor, use load() and render() to load and render a MusicXML file.
  */
 export class OpenSheetMusicDisplay {
-    private version: string = "1.7.6-audio-extended"; // getter: this.Version
+    private version: string = "1.8.0-audio-extended"; // getter: this.Version
     // at release, bump version and change to -release, afterwards to -dev again
 
     /**

+ 4 - 0
test/Util/generateImages_browserless.mjs

@@ -321,6 +321,7 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
         const isTestPageBreakImpliesSystemBreak = sampleFilename.startsWith("test_pagebreak_implies_systembreak");
         const isTestPageBottomMargin0 = sampleFilename.includes("PageBottomMargin0");
         const isTestTupletBracketTupletNumber = sampleFilename.includes("test_tuplet_bracket_tuplet_number");
+        const isTestCajon2NoteSystem = sampleFilename.includes("test_cajon_2-note-system");
         const enableNewSystemAtSystemBreak = sampleFilename.includes("test_octaveshift_extragraphicalmeasure");
         osmdInstance.EngravingRules.loadDefaultValues(); // note this may also be executed in setOptions below via drawingParameters default
         if (isTestEndClefStaffEntryBboxes) {
@@ -367,6 +368,9 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
             osmdInstance.EngravingRules.TupletNumberMaxConsecutiveRepetitions = 2;
             osmdInstance.EngravingRules.TupletNumberAlwaysDisableAfterFirstMax = true; // necessary to trigger bug
         }
+        if (isTestCajon2NoteSystem) {
+            osmdInstance.EngravingRules.PercussionUseCajon2NoteSystem = true;
+        }
         if (enableNewSystemAtSystemBreak) {
             osmdInstance.EngravingRules.NewSystemAtXMLNewSystemAttribute = true;
         }

+ 151 - 0
test/data/test_cajon_2-note-system.musicxml

@@ -0,0 +1,151 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!DOCTYPE score-partwise PUBLIC '-//Recordare//DTD MusicXML 4.0 Partwise//EN' 'http://www.musicxml.org/dtds/partwise.dtd'>
+<score-partwise version='4.0'>
+	<movement-title>test_cajon_2-note-system</movement-title>
+	<identification>
+		<encoding>
+			<software>Sibelius 2022.12</software>
+			<software>Dolet 8.0 for Sibelius</software>
+			<encoding-date>2023-02-08</encoding-date>
+			<supports element='accidental' type='yes'/>
+			<supports element='transpose' type='yes'/>
+			<supports attribute='new-page' element='print' type='yes' value='yes'/>
+			<supports attribute='new-system' element='print' type='yes' value='yes'/>
+		</encoding>
+	</identification>
+	<defaults>
+		<scaling>
+			<millimeters>6.5</millimeters>
+			<tenths>40</tenths>
+		</scaling>
+		<page-layout>
+			<page-height>1828</page-height>
+			<page-width>1292</page-width>
+			<page-margins type='both'>
+				<left-margin>78.1538</left-margin>
+				<right-margin>78.1538</right-margin>
+				<top-margin>78.1538</top-margin>
+				<bottom-margin>78.1538</bottom-margin>
+			</page-margins>
+		</page-layout>
+		<system-layout>
+			<system-margins>
+				<left-margin>0</left-margin>
+				<right-margin>0</right-margin>
+			</system-margins>
+			<system-distance>130</system-distance>
+			<top-system-distance>153.75</top-system-distance>
+		</system-layout>
+		<?DoletSibelius StaffJustificationPercentage=40?>
+		<appearance>
+			<line-width type='beam'>5</line-width>
+			<line-width type='heavy barline'>5</line-width>
+			<line-width type='leger'>1.5625</line-width>
+			<line-width type='light barline'>1.5625</line-width>
+			<line-width type='slur middle'>2.1875</line-width>
+			<line-width type='slur tip'>0.625</line-width>
+			<line-width type='staff'>1.25</line-width>
+			<line-width type='stem'>1.25</line-width>
+			<line-width type='tie middle'>2.1875</line-width>
+			<line-width type='tie tip'>0.625</line-width>
+			<note-size type='grace'>60</note-size>
+			<note-size type='cue'>75</note-size>
+		</appearance>
+		<music-font font-family='Helsinki Std,engraved'/>
+		<word-font font-family='Palatino,serif'/>
+	</defaults>
+	<credit page='1'>
+		<credit-type>title</credit-type>
+		<credit-words/>
+	</credit>
+	<?DoletSibelius Unexported credit in style text.system.page_aligned.header_notp1:   - p.  of ?>
+	<part-list>
+		<score-part id='P1'>
+			<part-name print-object='no'>Cajon</part-name>
+			<part-abbreviation print-object='no'>Cajon</part-abbreviation>
+			<group>score</group>
+			<score-instrument id='P1-I1'>
+				<instrument-name>Cajon [2 lines]</instrument-name>
+				<instrument-abbreviation>Cajon</instrument-abbreviation>
+			</score-instrument>
+			<midi-instrument id='P1-I1'>
+				<volume>79</volume>
+				<pan>0</pan>
+			</midi-instrument>
+		</score-part>
+	</part-list>
+<!--=========================================================-->
+	<part id='P1'>
+		<measure number='1'>
+			<print>
+				<system-layout>
+					<top-system-distance>246.25</top-system-distance>
+				</system-layout>
+			</print>
+			<attributes>
+				<divisions>768</divisions>
+				<time>
+					<beats>4</beats>
+					<beat-type>4</beat-type>
+				</time>
+				<clef>
+					<sign>percussion</sign>
+				</clef>
+				<staff-details>
+					<staff-lines>2</staff-lines>
+				</staff-details>
+			</attributes>
+			<direction placement='above' directive='yes' system='only-top'>
+				<direction-type>
+					<metronome>
+						<beat-unit>quarter</beat-unit>
+						<per-minute>84</per-minute>
+					</metronome>
+				</direction-type>
+				<sound tempo='84'/>
+			</direction>
+			<direction placement='above'>
+				<direction-type>
+					<words default-y='25' relative-x='-6' font-size='9.36'>w/brushes</words>
+				</direction-type>
+				<voice>1</voice>
+			</direction>
+			<note>
+				<unpitched>
+					<display-step>C</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>up</stem>
+			</note>
+			<note>
+				<rest/>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+			</note>
+			<note>
+				<unpitched>
+					<display-step>G</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>down</stem>
+			</note>
+			<note>
+				<rest/>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+			</note>
+			<barline location='right'>
+				<bar-style>light-heavy</bar-style>
+			</barline>
+		</measure>
+	</part>
+<!--=========================================================-->
+</score-partwise>

+ 215 - 0
test/data/test_lyrics_centering.musicxml

@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_lyrics_centering</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-06-20</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>6.99911</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1596.77</page-height>
+      <page-width>1233.87</page-width>
+      <page-margins type="even">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="616.935" default-y="1511.05" justify="center" valign="top" font-size="22">test_lyrics_centering</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="501.56">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="80.72" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>1</text>
+          </lyric>
+        </note>
+      <note default-x="185.48" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>and</text>
+          </lyric>
+        </note>
+      <note default-x="290.24" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>2</text>
+          </lyric>
+        </note>
+      <note default-x="395.00" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>+</text>
+          </lyric>
+        </note>
+      </measure>
+    <measure number="2" width="477.14">
+      <note default-x="13.00" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>3</text>
+          </lyric>
+        </note>
+      <note default-x="123.87" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>II</text>
+          </lyric>
+        </note>
+      <note default-x="234.75" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <alter>1</alter>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <accidental>sharp</accidental>
+        <stem>down</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>4</text>
+          </lyric>
+        </note>
+      <attributes>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          <clef-octave-change>1</clef-octave-change>
+          </clef>
+        </attributes>
+      <note default-x="355.62" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.50" default-y="-42.57" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>+</text>
+          </lyric>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 417 - 0
test/data/test_note-ticks-0_issue1073.musicxml

@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_note_ticks_0</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2022-11-03</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    <source>https://musescore.com/user/35295177/scores/8930748</source>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">test_note_ticks_0</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="248.72">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>65.90</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        <staff-layout number="2">
+          <staff-distance>49.37</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>4</divisions>
+        <key>
+          <fifths>-3</fifths>
+          </key>
+        <time>
+          <beats>6</beats>
+          <beat-type>8</beat-type>
+          </time>
+        <staves>2</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        <clef number="2">
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <direction placement="above">
+        <direction-type>
+          <metronome parentheses="no" default-x="-39.22" relative-y="20.00">
+            <beat-unit>quarter</beat-unit>
+            <per-minute>76</per-minute>
+            </metronome>
+          </direction-type>
+        <staff>1</staff>
+        <sound tempo="76"/>
+        </direction>
+      <note>
+        <rest measure="yes"/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>12</duration>
+        </backup>
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.50" default-y="-54.69" relative-y="-25.00">
+            <p/>
+            </dynamics>
+          </direction-type>
+        <staff>2</staff>
+        <sound dynamics="54.44"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-74.69"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <note default-x="123.10" default-y="-139.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>2</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">begin</beam>
+        <notations>
+          <slur type="start" placement="below" number="1"/>
+          </notations>
+        </note>
+      <note default-x="142.26" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="161.43" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">end</beam>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      <note default-x="180.59" default-y="-119.37">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>2</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">begin</beam>
+        <notations>
+          <slur type="start" placement="below" number="1"/>
+          </notations>
+        </note>
+      <note default-x="199.76" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="218.92" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">end</beam>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      </measure>
+    <measure number="2" width="205.93">
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+        </note>
+      <note>
+        <rest/>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <staff>1</staff>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        </note>
+      <note default-x="105.09" default-y="-55.00">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <slur type="start" placement="above" number="1"/>
+          </notations>
+        </note>
+      <note default-x="127.09" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="145.59" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="160.86" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="176.14" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <backup>
+        <duration>12</duration>
+        </backup>
+      <note default-x="16.50" default-y="-139.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>2</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">begin</beam>
+        <notations>
+          <slur type="start" placement="below" number="2"/>
+          </notations>
+        </note>
+      <note default-x="40.94" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="65.38" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">end</beam>
+        <notations>
+          <slur type="stop" number="2"/>
+          </notations>
+        </note>
+      <note default-x="89.82" default-y="-119.37">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>2</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">begin</beam>
+        <notations>
+          <slur type="start" placement="below" number="2"/>
+          </notations>
+        </note>
+      <note default-x="127.09" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="160.86" default-y="-104.37">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        <beam number="1">end</beam>
+        <notations>
+          <slur type="stop" number="2"/>
+          </notations>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 415 - 0
test/data/test_place_words_inside_staffline_bongo.musicxml

@@ -0,0 +1,415 @@
+<?xml version="1.0"?>
+<score-partwise version="3.0">
+ <work>
+  <work-title>test_place_words_inside_staffline_bongo</work-title>
+ </work>
+ <identification>
+  <encoding>
+   <encoding-date>2023-04-19</encoding-date>
+   <software>Sibelius 22.12.0</software>
+   <software>Direct export, not from Dolet</software>
+   <encoding-description>Sibelius / MusicXML 3.0</encoding-description>
+   <supports element="print" type="yes" value="yes" attribute="new-system"/>
+   <supports element="print" type="yes" value="yes" attribute="new-page"/>
+   <supports element="accidental" type="yes"/>
+   <supports element="beam" type="yes"/>
+   <supports element="stem" type="yes"/>
+  </encoding>
+ </identification>
+ <defaults>
+  <scaling>
+   <millimeters>210</millimeters>
+   <tenths>1200</tenths>
+  </scaling>
+  <page-layout>
+   <page-height>1697</page-height>
+   <page-width>1200</page-width>
+   <page-margins type="both">
+    <left-margin>72</left-margin>
+    <right-margin>72</right-margin>
+    <top-margin>72</top-margin>
+    <bottom-margin>72</bottom-margin>
+   </page-margins>
+  </page-layout>
+  <system-layout>
+   <system-margins>
+    <left-margin>50</left-margin>
+    <right-margin>0</right-margin>
+   </system-margins>
+   <system-distance>92</system-distance>
+  </system-layout>
+  <appearance>
+   <line-width type="stem">0.9375</line-width>
+   <line-width type="beam">5</line-width>
+   <line-width type="staff">0.9375</line-width>
+   <line-width type="light barline">1.5625</line-width>
+   <line-width type="heavy barline">5</line-width>
+   <line-width type="leger">1.5625</line-width>
+   <line-width type="ending">1.5625</line-width>
+   <line-width type="wedge">1.25</line-width>
+   <line-width type="enclosure">0.9375</line-width>
+   <line-width type="tuplet bracket">1.25</line-width>
+   <line-width type="bracket">5</line-width>
+   <line-width type="dashes">1.5625</line-width>
+   <line-width type="extend">0.9375</line-width>
+   <line-width type="octave shift">1.5625</line-width>
+   <line-width type="pedal">1.5625</line-width>
+   <line-width type="slur middle">1.5625</line-width>
+   <line-width type="slur tip">0.625</line-width>
+   <line-width type="tie middle">1.5625</line-width>
+   <line-width type="tie tip">0.625</line-width>
+   <note-size type="cue">75</note-size>
+   <note-size type="grace">60</note-size>
+  </appearance>
+  <music-font font-family="Opus Std" font-size="19.8425"/>
+  <word-font font-family="Times New Roman" font-size="11.9365"/>
+  <lyric-font font-family="Times New Roman" font-size="11.4715"/>
+  <lyric-language xml:lang="en"/>
+ </defaults>
+ <part-list>
+  <score-part id="P1">
+   <part-name>Percussion</part-name>
+   <part-name-display>
+    <display-text>Percussion</display-text>
+   </part-name-display>
+   <part-abbreviation>Perc.</part-abbreviation>
+   <part-abbreviation-display>
+    <display-text>Perc.</display-text>
+   </part-abbreviation-display>
+   <score-instrument id="P1-I1">
+    <instrument-name>Percussion (8)</instrument-name>
+    <virtual-instrument>
+     <virtual-library>General MIDI</virtual-library>
+     <virtual-name>Standard Set</virtual-name>
+    </virtual-instrument>
+   </score-instrument>
+  </score-part>
+ </part-list>
+ <part id="P1"><measure number="6" width="498"><attributes><divisions>256</divisions><key color="#000000">
+     <fifths>0</fifths>
+     <mode>major</mode>
+    </key><time color="#000000">
+     <beats>4</beats>
+     <beat-type>4</beat-type>
+    </time><staves>1</staves><clef number="1" color="#000000">
+     <sign>percussion</sign>
+     <line>2</line>
+    </clef><staff-details number="1" print-object="yes"/></attributes>
+   
+   
+   <direction>
+    <direction-type>
+     <words default-x="51" default-y="40" justify="left" valign="middle" font-family="Times New Roman" font-style="normal" font-size="10.0763" font-weight="normal">Tambourine</words>
+    </direction-type>
+    <voice>1</voice>
+    <staff>1</staff>
+   </direction>
+   <note color="#000000" default-x="57" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="83" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="109" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="135" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <direction>
+    <direction-type>
+     <words default-x="165" default-y="-71" justify="left" valign="middle" font-family="Times New Roman" font-style="normal" font-size="10.0763" font-weight="normal">Foot tambourine</words>
+    </direction-type>
+    <voice>1</voice>
+    <staff>1</staff>
+   </direction>
+   <note color="#000000" default-x="162" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="188" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="214" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <direction>
+    <direction-type>
+     <words default-x="251" default-y="-16" justify="left" valign="middle" font-family="Times New Roman" font-style="normal" font-size="10.0763" font-weight="normal">Bongo</words>
+    </direction-type>
+    <voice>1</voice>
+    <staff>1</staff>
+   </direction>
+   <note color="#000000" default-x="240" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="266" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="292" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="318" default-y="37">
+    <unpitched>
+     <display-step>C</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="318">
+    <chord/>
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <type>16th</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="345" default-y="37">
+    <unpitched>
+     <display-step>C</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   <note color="#000000" default-x="345">
+    <chord/>
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <type>16th</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="371" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">begin</beam>
+    <beam number="2">begin</beam>
+   </note>
+   <note color="#000000" default-x="397" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="423" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">continue</beam>
+    <beam number="2">continue</beam>
+   </note>
+   <note color="#000000" default-x="449" default-y="37">
+    <unpitched>
+     <display-step>A</display-step>
+     <display-octave>5</display-octave>
+    </unpitched>
+    <duration>64</duration>
+    <instrument id="P1-I1"/>
+    <voice>1</voice>
+    <type>16th</type>
+    <stem>up</stem>
+    <staff>1</staff>
+    <beam number="1">end</beam>
+    <beam number="2">end</beam>
+   </note>
+   
+   <backup>
+    <duration>1024</duration>
+   </backup>
+   <note default-x="57">
+    <rest/>
+    <duration>256</duration>
+    <instrument id="P1-I1"/>
+    <voice>2</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="162" default-y="-62">
+    <unpitched>
+     <display-step>F</display-step>
+     <display-octave>4</display-octave>
+    </unpitched>
+    <duration>256</duration>
+    <instrument id="P1-I1"/>
+    <voice>2</voice>
+    <type>quarter</type>
+    <stem>down</stem>
+    <staff>1</staff>
+   </note>
+   <note default-x="266">
+    <rest/>
+    <duration>256</duration>
+    <instrument id="P1-I1"/>
+    <voice>2</voice>
+    <type>quarter</type>
+    <staff>1</staff>
+   </note>
+   <note color="#000000" default-x="371" default-y="-62">
+    <unpitched>
+     <display-step>F</display-step>
+     <display-octave>4</display-octave>
+    </unpitched>
+    <duration>256</duration>
+    <instrument id="P1-I1"/>
+    <voice>2</voice>
+    <type>quarter</type>
+    <stem>down</stem>
+    <staff>1</staff>
+   </note>
+  <barline location="right">
+    <bar-style>light-heavy</bar-style>
+    <repeat direction="backward"/>
+   </barline></measure></part>
+</score-partwise>

+ 106 - 0
test/data/test_repeat_left_barline_simple.musicxml

@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_repeat_left_barline_simple</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-06-22</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>6.99911</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1596.77</page-height>
+      <page-width>1233.87</page-width>
+      <page-margins type="even">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7252</left-margin>
+        <right-margin>85.7252</right-margin>
+        <top-margin>85.7252</top-margin>
+        <bottom-margin>85.7252</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="616.935" default-y="1511.05" justify="center" valign="top" font-size="22">test_repeat_left_simple</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="380.56">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>598.15</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <barline location="left">
+        <bar-style>heavy-light</bar-style>
+        <repeat direction="forward"/>
+        </barline>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="109.57" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        <repeat direction="backward"/>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 195 - 0
test/data/test_tremolo_unmeasured_buzz_roll.musicxml

@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_tremolo_unmeasured_buzzroll</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-06-23</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">test_tremolo_unmeasured_buzzroll</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>brace</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="551.88">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>426.69</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>4</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="80.72" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+		<notations>
+			<ornaments>
+				<tremolo type='unmeasured'>0</tremolo>
+			</ornaments>
+		</notations>
+        </note>
+      <note default-x="168.06" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+		<notations>
+			<ornaments>
+				<tremolo type='unmeasured'>0</tremolo>
+			</ornaments>
+		</notations>
+        </note>
+      <note default-x="255.40" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+		<notations>
+			<ornaments>
+				<tremolo type='unmeasured'>0</tremolo>
+			</ornaments>
+		</notations>
+        </note>
+      <note default-x="295.10" default-y="0.00">
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <note default-x="398.32" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+		<notations>
+			<ornaments>
+				<tremolo type='unmeasured'>0</tremolo>
+			</ornaments>
+		</notations>
+        </note>
+      <note default-x="438.02" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 268 - 0
test/data/test_tremolo_unmeasured_buzz_roll_drums.musicxml

@@ -0,0 +1,268 @@
+<?xml version='1.0' encoding='UTF-8' standalone='no'?>
+<!DOCTYPE score-partwise PUBLIC '-//Recordare//DTD MusicXML 4.0 Partwise//EN' 'http://www.musicxml.org/dtds/partwise.dtd'>
+<score-partwise version='4.0'>
+	<work>
+    	<work-title>test_tremolo_unmeasured_buzz_roll_drums</work-title>
+  	</work>
+	<identification>
+		<encoding>
+			<software>Sibelius 2022.12</software>
+			<software>Dolet 8.1 for Sibelius</software>
+			<encoding-date>2023-02-02</encoding-date>
+			<supports element='accidental' type='yes'/>
+			<supports element='transpose' type='yes'/>
+			<supports attribute='new-page' element='print' type='yes' value='yes'/>
+			<supports attribute='new-system' element='print' type='yes' value='yes'/>
+		</encoding>
+	</identification>
+	<defaults>
+		<scaling>
+			<millimeters>6.5</millimeters>
+			<tenths>40</tenths>
+		</scaling>
+		<page-layout>
+			<page-height>1828</page-height>
+			<page-width>1292</page-width>
+			<page-margins type='both'>
+				<left-margin>78.1538</left-margin>
+				<right-margin>369.2308</right-margin>
+				<top-margin>78.1538</top-margin>
+				<bottom-margin>78.1538</bottom-margin>
+			</page-margins>
+		</page-layout>
+		<system-layout>
+			<system-margins>
+				<left-margin>0</left-margin>
+				<right-margin>0</right-margin>
+			</system-margins>
+			<system-distance>130</system-distance>
+			<top-system-distance>153.75</top-system-distance>
+		</system-layout>
+		<?DoletSibelius StaffJustificationPercentage=40?>
+		<appearance>
+			<line-width type='beam'>5</line-width>
+			<line-width type='heavy barline'>5</line-width>
+			<line-width type='leger'>1.5625</line-width>
+			<line-width type='light barline'>1.5625</line-width>
+			<line-width type='slur middle'>2.1875</line-width>
+			<line-width type='slur tip'>0.625</line-width>
+			<line-width type='staff'>1.25</line-width>
+			<line-width type='stem'>1.25</line-width>
+			<line-width type='tie middle'>2.1875</line-width>
+			<line-width type='tie tip'>0.625</line-width>
+			<note-size type='grace'>60</note-size>
+			<note-size type='cue'>75</note-size>
+		</appearance>
+		<music-font font-family='Helsinki Std,engraved'/>
+		<word-font font-family='Palatino,serif'/>
+	</defaults>
+	<part-list>
+		<score-part id='P1'>
+			<part-name print-object='no'>Drum Set</part-name>
+			<part-abbreviation print-object='no'>Dr.</part-abbreviation>
+			<group>score</group>
+			<score-instrument id='P1-I1'>
+				<instrument-name>Drum Set (Rock)</instrument-name>
+				<instrument-abbreviation>Dr.</instrument-abbreviation>
+			</score-instrument>
+			<midi-instrument id='P1-I1'>
+				<volume>83</volume>
+				<pan>0</pan>
+			</midi-instrument>
+		</score-part>
+	</part-list>
+<!--=========================================================-->
+	<part id='P1'>
+		<measure number='0' implicit='yes'>
+			<print>
+				<system-layout>
+					<top-system-distance>61.5625</top-system-distance>
+				</system-layout>
+			</print>
+			<attributes>
+				<divisions>768</divisions>
+				<time>
+					<beats>4</beats>
+					<beat-type>4</beat-type>
+				</time>
+				<clef>
+					<sign>percussion</sign>
+				</clef>
+			</attributes>
+			<sound tempo='78'/>
+			<note>
+				<unpitched>
+					<display-step>F</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>up</stem>
+			</note>
+			<note>
+				<unpitched>
+					<display-step>C</display-step>
+					<display-octave>5</display-octave>
+				</unpitched>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>up</stem>
+			</note>
+			<backup>
+				<duration>768</duration>
+			</backup>
+			<note print-object='no'>
+				<rest/>
+				<duration>768</duration>
+				<voice>2</voice>
+				<type>quarter</type>
+			</note>
+			<note>
+				<rest/>
+				<duration>192</duration>
+				<voice>1</voice>
+				<type>16th</type>
+			</note>
+			<backup>
+				<duration>192</duration>
+			</backup>
+			<note print-object='no'>
+				<rest/>
+				<duration>384</duration>
+				<voice>2</voice>
+				<type>eighth</type>
+			</note>
+			<backup>
+				<duration>192</duration>
+			</backup>
+			<note>
+				<unpitched>
+					<display-step>C</display-step>
+					<display-octave>5</display-octave>
+				</unpitched>
+				<duration>192</duration>
+				<voice>1</voice>
+				<type>16th</type>
+				<stem>up</stem>
+				<beam number='1'>begin</beam>
+				<beam number='2'>forward hook</beam>
+			</note>
+			<note>
+				<unpitched>
+					<display-step>F</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>384</duration>
+				<voice>1</voice>
+				<type>eighth</type>
+				<stem>up</stem>
+				<beam number='1'>end</beam>
+			</note>
+			<note>
+				<unpitched>
+					<display-step>C</display-step>
+					<display-octave>5</display-octave>
+				</unpitched>
+				<duration>192</duration>
+				<voice>1</voice>
+				<type>16th</type>
+				<stem>up</stem>
+				<beam number='1'>begin</beam>
+				<beam number='2'>forward hook</beam>
+			</note>
+			<backup>
+				<duration>192</duration>
+			</backup>
+			<note print-object='no'>
+				<rest/>
+				<duration>192</duration>
+				<voice>2</voice>
+				<type>16th</type>
+			</note>
+			<note>
+				<unpitched>
+					<display-step>F</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>384</duration>
+				<voice>1</voice>
+				<type>eighth</type>
+				<stem>up</stem>
+				<beam number='1'>continue</beam>
+			</note>
+			<direction placement='above'>
+				<direction-type>
+					<words default-y='50' relative-x='-6' font-size='9.36' xml:space='preserve'>Buzz
+Roll</words>
+				</direction-type>
+				<voice>1</voice>
+			</direction>
+			<note>
+				<unpitched>
+					<display-step>C</display-step>
+					<display-octave>5</display-octave>
+				</unpitched>
+				<duration>192</duration>
+				<voice>1</voice>
+				<type>16th</type>
+				<stem>up</stem>
+				<beam number='1'>end</beam>
+				<beam number='2'>backward hook</beam>
+				<notations>
+					<ornaments>
+						<tremolo type='unmeasured'>0</tremolo>
+					</ornaments>
+				</notations>
+			</note>
+			<backup>
+				<duration>192</duration>
+			</backup>
+			<note print-object='no'>
+				<rest/>
+				<duration>192</duration>
+				<voice>2</voice>
+				<type>16th</type>
+			</note>
+		</measure>
+<!--=========================================================-->
+		<measure number='1'>
+			<note>
+				<unpitched>
+					<display-step>F</display-step>
+					<display-octave>4</display-octave>
+				</unpitched>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+				<stem>up</stem>
+			</note>
+			<note>
+				<rest/>
+				<duration>768</duration>
+				<voice>1</voice>
+				<type>quarter</type>
+			</note>
+			<note>
+				<rest/>
+				<duration>1536</duration>
+				<voice>1</voice>
+				<type>half</type>
+			</note>
+			<backup>
+				<duration>1536</duration>
+			</backup>
+			<note print-object='no'>
+				<rest/>
+				<duration>1536</duration>
+				<voice>2</voice>
+				<type>half</type>
+			</note>
+			<barline location='right'>
+				<bar-style>light-heavy</bar-style>
+			</barline>
+		</measure>
+	</part>
+<!--=========================================================-->
+</score-partwise>