Browse Source

merge osmd-public/develop: fix multiline wedges (crescendo), fix single staffline drum whole note position, fix lyrics on secondary whole rest, fix StaffEntry.hasOnlyRests

sschmidTU 1 year ago
parent
commit
97b8af1ecd

+ 5 - 4
demo/embedded_demo.html

@@ -83,7 +83,8 @@
                     <td>openUrl</td>
                     <td>valid URL pointing to a valid musicxml file</td>
                     <td>"auto"</td>
-                    <td>If given, this attribute open the given musicxml file and display it.
+                    <td>If given, this attribute open the given musicxml file and display it.<br>
+                        Note that you need to add <i>&endUrl</i> after the URL.
                         <p>
                             <h5>Important:</h5>
                             For now, the resources must be hosted on the same server, otherwise it will be blocked by
@@ -92,11 +93,11 @@
                         <p>
                             <h5>Examples:</h5>
                             <a
-                                href="http://localhost:8000/?embedded&openUrl=http://localhost:8000/BrookeWestSample.musicxml">
-                                http://localhost:8000/?embedded&openUrl=http://localhost:8000/BrookeWestSample.musicxml</a>
+                                href="http://localhost:8000/?embedded&openUrl=http://localhost:8000/BrookeWestSample.musicxml&endUrl">
+                                http://localhost:8000/?embedded&openUrl=http://localhost:8000/BrookeWestSample.musicxml&endUrl</a>
                                 <br>
                             <a
-                                href="http://localhost:8000/?embedded&openUrl=https://wpmedia.musicxml.com/wp-content/uploads/2017/12/MahlFaGe4Sample.mxl">http://localhost:8000/?embedded&openUrl=https://wpmedia.musicxml.com/wp-content/uploads/2017/12/MahlFaGe4Sample.mxl</a>
+                                href="http://localhost:8000/?embedded&openUrl=https://wpmedia.musicxml.com/wp-content/uploads/2017/12/MahlFaGe4Sample.mxl&endUrl">http://localhost:8000/?embedded&openUrl=https://wpmedia.musicxml.com/wp-content/uploads/2017/12/MahlFaGe4Sample.mxl&endUrl</a>
 
                         </p>
                     </td>

+ 43 - 0
src/MusicalScore/Graphical/GraphicalContinuousDynamicExpression.ts

@@ -260,6 +260,49 @@ export class GraphicalContinuousDynamicExpression extends AbstractGraphicalExpre
         }
     }
 
+    /** Wrapper for createFirstHalfCrescendoLines and createFirstHalfDiminuendoLines, agnostic of crescendo/diminuendo. */
+    public createFirstHalfLines(startX: number, endX: number, y: number,
+        wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
+        wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
+        wedgeLineWidth: number = this.rules.WedgeLineWidth
+    ): void {
+        if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            this.createFirstHalfCrescendoLines(startX, endX, y,
+                wedgeMeasureEndOpeningLength, wedgeLineWidth);
+        } else if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
+            this.createFirstHalfDiminuendoLines(startX, endX, y,
+                wedgeOpeningLength, wedgeMeasureEndOpeningLength, wedgeLineWidth);
+        }
+    }
+
+    /** Wrapper for createSecondHalfCrescendoLines and createSecondHalfDiminuendoLines, agnostic of crescendo/diminuendo. */
+    public createSecondHalfLines(startX: number, endX: number, y: number,
+        wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
+        wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
+        wedgeLineWidth: number = this.rules.WedgeLineWidth
+    ): void {
+        if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            this.createSecondHalfCrescendoLines(startX, endX, y,
+                wedgeMeasureBeginOpeningLength, wedgeOpeningLength, wedgeLineWidth);
+        } else if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
+            this.createSecondHalfDiminuendoLines(startX, endX, y,
+                wedgeMeasureBeginOpeningLength, wedgeLineWidth);
+        }
+    }
+
+    /** Wrapper for createCrescendoLines and createDiminuendoLines, agnostic of crescendo/decrescendo. */
+    public createLines(startX: number, endX: number, y: number,
+        wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth
+    ): void {
+        if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            this.createCrescendoLines(startX, endX, y,
+                wedgeOpeningLength, wedgeLineWidth);
+        } else if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
+            this.createDiminuendoLines(startX, endX, y,
+                wedgeOpeningLength, wedgeLineWidth);
+        }
+    }
+
     /**
      * Calculate the BoundingBox (as a box around the Wedge).
      */

+ 1 - 10
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -305,16 +305,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * Returns true if this staff entry has only rests
      */
     public hasOnlyRests(): boolean {
-        const hasOnlyRests: boolean = true;
-        for (const gve of this.graphicalVoiceEntries) {
-            for (const graphicalNote of gve.notes) {
-                const note: Note = graphicalNote.sourceNote;
-                if (!note.isRest()) {
-                    return false;
-                }
-            }
-        }
-        return hasOnlyRests;
+        return this.sourceStaffEntry.hasOnlyRests;
     }
 
     public getSkylineMin(): number {

+ 104 - 48
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -661,10 +661,11 @@ export abstract class MusicSheetCalculator {
                     this.rules.LyricsYOffsetToStaffHeight;
 
                 // Y-position calculated according to aforementioned mapping
-                let position: number = firstPosition + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * sortedLyricVerseNumberIndex;
-                if (this.leadSheet) {
-                    position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
-                }
+                const position: number = firstPosition + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * sortedLyricVerseNumberIndex;
+                // TODO not sure what this leadsheet lyrics positioning was supposed to be, but it seems to ALWAYS put the lyrics inside the stafflines now.
+                // if (this.leadSheet) {
+                //     position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
+                // }
                 const previousRelativeX: number = lyricsEntryLabel.PositionAndShape.RelativePosition.x;
                 lyricsEntryLabel.PositionAndShape.RelativePosition = new PointF2D(previousRelativeX, position);
                 lyricsEntryLabel.Label.fontStyle = lyricEntry.LyricsEntry.FontStyle;
@@ -1431,28 +1432,63 @@ export abstract class MusicSheetCalculator {
         const placement: PlacementEnum = graphicalContinuousDynamic.ContinuousDynamic.Placement;
 
         // if ContinuousDynamicExpression is given from wedge
-        let secondGraphicalContinuousDynamic: GraphicalContinuousDynamicExpression = undefined;
+        let endGraphicalContinuousDynamic: GraphicalContinuousDynamicExpression = undefined;
 
         // last length check
         if (sameStaffLine && endPosInStaffLine.x - startPosInStaffline.x < this.rules.WedgeMinLength && !isSoftAccent) {
             endPosInStaffLine.x = startPosInStaffline.x + this.rules.WedgeMinLength;
         }
 
-        // Upper staff wedge always starts at the given position and the lower staff wedge always starts at the begin of measure
+        // First staff wedge always starts at the given position and the last and inbetween wedges always start at the begin of measure
+        //   TODO: rename upper / lower to first / last, now that we can have inbetween wedges, though this creates a huge diff, and this should be clear now.
         const upperStartX: number = startPosInStaffline.x;
         let lowerStartX: number = endStaffLine.Measures[0].beginInstructionsWidth - this.rules.WedgeHorizontalMargin - 2;
         //TODO fix this when a range of measures to draw is given that doesn't include all the dynamic's measures (e.g. for crescendo)
         let upperEndX: number = 0;
         let lowerEndX: number = 0;
 
+        /** Wedges between first and last staffline, in case we span more than 2 stafflines. */
+        const inbetweenWedges: GraphicalContinuousDynamicExpression[] = [];
         if (!sameStaffLine) {
+            // add wedge in all stafflines between (including) start and end measure
             upperEndX = staffLine.PositionAndShape.Size.width;
             lowerEndX = endPosInStaffLine.x;
 
-            // must create a new Wedge
-            secondGraphicalContinuousDynamic = new GraphicalContinuousDynamicExpression(
+            // get all stafflines between start measure and end measure, and add wedges for them.
+            //   This would be less lines of code if there was already a list of stafflines for the sheet.
+            const stafflinesCovered: StaffLine[] = [staffLine, endStaffLine]; // start and end staffline already get a wedge
+            const startMeasure: GraphicalMeasure = graphicalContinuousDynamic.StartMeasure;
+            let nextMeasure: GraphicalMeasure = startMeasure;
+            let iterations: number = 0; // safety measure against infinite loop
+            let sourceMeasureIndex: number = startMeasure.parentSourceMeasure.measureListIndex;
+            while (nextMeasure !== endMeasure && iterations < 1000) {
+                const nextSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[sourceMeasureIndex];
+                const potentialNextMeasure: GraphicalMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(
+                    nextSourceMeasure, staffIndex
+                );
+                if (potentialNextMeasure) {
+                    nextMeasure = potentialNextMeasure;
+                    const nextStaffline: StaffLine = nextMeasure.ParentStaffLine;
+                    if (!stafflinesCovered.includes(nextStaffline)) {
+                        stafflinesCovered.push(nextStaffline);
+                        const newWedge: GraphicalContinuousDynamicExpression =
+                            new GraphicalContinuousDynamicExpression(
+                                graphicalContinuousDynamic.ContinuousDynamic,
+                                nextStaffline,
+                                nextStaffline.Measures[0].parentSourceMeasure
+                            );
+                        newWedge.IsSplittedPart = true;
+                        inbetweenWedges.push(newWedge);
+                    }
+                }
+                sourceMeasureIndex++;
+                iterations++;
+            }
+
+            // last wedge at endMeasure
+            endGraphicalContinuousDynamic = new GraphicalContinuousDynamicExpression(
                 graphicalContinuousDynamic.ContinuousDynamic, endStaffLine, endMeasure.parentSourceMeasure);
-            secondGraphicalContinuousDynamic.IsSplittedPart = true;
+            endGraphicalContinuousDynamic.IsSplittedPart = true;
             graphicalContinuousDynamic.IsSplittedPart = true;
         } else {
             upperEndX = endPosInStaffLine.x;
@@ -1471,7 +1507,7 @@ export abstract class MusicSheetCalculator {
 
         // the Height of the Expression's placement
         let idealY: number = 0;
-        let secondIdealY: number = 0;
+        let endIdealY: number = 0;
 
         if (placement === PlacementEnum.Below) {
             // can be a single Staff Instrument or an Instrument with 2 Staves
@@ -1501,7 +1537,7 @@ export abstract class MusicSheetCalculator {
             if (!sameStaffLine) {
                 // Set the value for the splitted y position to the ideal position before we check and modify it with
                 // the skybottom calculator detection
-                secondIdealY = idealY;
+                endIdealY = idealY;
             }
             // must check BottomLine for possible collisions within the Length of the Expression
             // find the corresponding max value for the given Length
@@ -1552,12 +1588,12 @@ export abstract class MusicSheetCalculator {
             if (!sameStaffLine) {
                 maxBottomLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getBottomLineMaxInRange(lowerStartX, lowerEndX);
 
-                if (maxBottomLineValueForExpressionLength > secondIdealY) {
-                    secondIdealY = maxBottomLineValueForExpressionLength;
+                if (maxBottomLineValueForExpressionLength > endIdealY) {
+                    endIdealY = maxBottomLineValueForExpressionLength;
                 }
 
-                secondIdealY += this.rules.WedgeOpeningLength / 2;
-                secondIdealY += this.rules.WedgeVerticalMargin;
+                endIdealY += this.rules.WedgeOpeningLength / 2;
+                endIdealY += this.rules.WedgeVerticalMargin;
             }
 
             if (!withinCrossedBeam) {
@@ -1589,7 +1625,7 @@ export abstract class MusicSheetCalculator {
             // must consider the upperWedge starting/ending tip for the comparison with the SkyLine
             idealY += this.rules.WedgeOpeningLength / 2;
             if (!sameStaffLine) {
-                secondIdealY = idealY;
+                endIdealY = idealY;
             }
 
             // must check SkyLine for possible collisions within the Length of the Expression
@@ -1634,11 +1670,11 @@ export abstract class MusicSheetCalculator {
             if (!sameStaffLine) {
                 minSkyLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getSkyLineMinInRange(lowerStartX, lowerEndX);
 
-                if (minSkyLineValueForExpressionLength < secondIdealY) {
-                    secondIdealY = minSkyLineValueForExpressionLength;
+                if (minSkyLineValueForExpressionLength < endIdealY) {
+                    endIdealY = minSkyLineValueForExpressionLength;
                 }
 
-                secondIdealY -= this.rules.WedgeOpeningLength / 2;
+                endIdealY -= this.rules.WedgeOpeningLength / 2;
             }
 
             if (!withinCrossedBeam) {
@@ -1646,45 +1682,65 @@ export abstract class MusicSheetCalculator {
                 idealY -= this.rules.WedgeVerticalMargin;
             }
             if (!sameStaffLine) {
-                secondIdealY -= this.rules.WedgeVerticalMargin;
+                endIdealY -= this.rules.WedgeVerticalMargin;
             }
         }
 
         // now we have the correct placement Height for the Expression
         // the idealY is calculated relative to the currentStaffLine
 
-        // Crescendo (point to the left, opening to the right)
         graphicalContinuousDynamic.Lines.clear();
-        if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
-            if (isSoftAccent) {
-                graphicalContinuousDynamic.createFirstHalfCrescendoLines(upperStartX, upperEndX, idealY);
-                graphicalContinuousDynamic.createSecondHalfDiminuendoLines(lowerStartX, lowerEndX, idealY);
-                graphicalContinuousDynamic.calcPsi();
-                // secondGraphicalContinuousDynamic.createSecondHalfDiminuendoLines(lowerStartX, lowerEndX, idealY);
-                // secondGraphicalContinuousDynamic.calcPsi();
-            } else if (sameStaffLine && !isSoftAccent) {
-                graphicalContinuousDynamic.createCrescendoLines(upperStartX, upperEndX, idealY);
-                graphicalContinuousDynamic.calcPsi();
-            } else {
-                // two different Wedges
-                graphicalContinuousDynamic.createFirstHalfCrescendoLines(upperStartX, upperEndX, idealY);
-                graphicalContinuousDynamic.calcPsi();
+        if (isSoftAccent) {
+            // either createFirstHalfCrescendoLines or createFirstHalfDiminuendoLines, same principle / parameters.
+            graphicalContinuousDynamic.createFirstHalfCrescendoLines(upperStartX, upperEndX, idealY);
+            graphicalContinuousDynamic.createSecondHalfDiminuendoLines(lowerStartX, lowerEndX, idealY);
+            graphicalContinuousDynamic.calcPsi();
+        } else if (sameStaffLine && !isSoftAccent) {
+            graphicalContinuousDynamic.createLines(upperStartX, upperEndX, idealY);
+            graphicalContinuousDynamic.calcPsi();
+        } else {
+            // two+ different Wedges
+            // first wedge
+            graphicalContinuousDynamic.createFirstHalfLines(upperStartX, upperEndX, idealY);
+            graphicalContinuousDynamic.calcPsi();
+
+            // inbetween wedges
+            for (let i: number = 0; i < inbetweenWedges.length; i++) {
+                const inbetweenWedge: GraphicalContinuousDynamicExpression = inbetweenWedges[i];
+                const inbetweenStaffline: StaffLine = inbetweenWedge.ParentStaffLine;
+                let betweenIdealY: number = endIdealY;
+
+                if (placement === PlacementEnum.Below) {
+                    const maxBottomLineValueForExpressionLength: number =
+                    endStaffLine.SkyBottomLineCalculator.getBottomLineMaxInRange(lowerStartX, upperEndX);
+                    if (maxBottomLineValueForExpressionLength > betweenIdealY) {
+                        betweenIdealY = maxBottomLineValueForExpressionLength;
+                    }
+                    betweenIdealY += this.rules.WedgeOpeningLength / 2;
+                    betweenIdealY += this.rules.WedgeVerticalMargin;
+                } else if (placement === PlacementEnum.Above) {
+                    const minSkyLineValueForExpressionLength: number =
+                        inbetweenStaffline.SkyBottomLineCalculator.getSkyLineMinInRange(lowerStartX, lowerEndX);
+                    if (minSkyLineValueForExpressionLength < endIdealY) {
+                        betweenIdealY = minSkyLineValueForExpressionLength;
+                    }
+                    betweenIdealY -= this.rules.WedgeOpeningLength / 2;
+                }
 
-                secondGraphicalContinuousDynamic.createSecondHalfCrescendoLines(lowerStartX, lowerEndX, secondIdealY);
-                secondGraphicalContinuousDynamic.calcPsi();
+                if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+                    inbetweenWedge.createSecondHalfCrescendoLines(0, inbetweenStaffline.PositionAndShape.Size.width, betweenIdealY);
+                    // for crescendo, we want the same look as on the last staffline: not starting with an intersection / starting wedge
+                } else {
+                    inbetweenWedge.createFirstHalfDiminuendoLines(0, inbetweenStaffline.PositionAndShape.Size.width, betweenIdealY);
+                    // for diminuendo, we want the same look as on the first staffline: not ending in an intersection / looking finished
+                }
+                inbetweenWedge.calcPsi();
             }
-        } else if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
-            if (sameStaffLine) {
-                graphicalContinuousDynamic.createDiminuendoLines(upperStartX, upperEndX, idealY);
-                graphicalContinuousDynamic.calcPsi();
-            } else {
-                graphicalContinuousDynamic.createFirstHalfDiminuendoLines(upperStartX, upperEndX, idealY);
-                graphicalContinuousDynamic.calcPsi();
 
-                secondGraphicalContinuousDynamic.createSecondHalfDiminuendoLines(lowerStartX, lowerEndX, secondIdealY);
-                secondGraphicalContinuousDynamic.calcPsi();
-            }
-        } //End Diminuendo
+            // last wedge
+            endGraphicalContinuousDynamic.createSecondHalfLines(lowerStartX, lowerEndX, endIdealY);
+            endGraphicalContinuousDynamic.calcPsi();
+        }
         this.dynamicExpressionMap.set(endAbsoluteTimestamp.RealValue, graphicalContinuousDynamic.PositionAndShape);
     }
 

+ 3 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -327,6 +327,9 @@ export class VexFlowConverter {
                     baseNoteLength.RealValue === note.sourceNote.SourceMeasure.ActiveTimeSignature.RealValue;
                 if (isWholeMeasureRest) {
                     keys = ["d/5"];
+                    if (gve.parentStaffEntry.parentMeasure.ParentStaff.StafflineCount === 1) {
+                        keys = ["b/4"];
+                    }
                     duration = "w";
                     numDots = 0;
                     // If it's a whole rest we want it smack in the middle. Apparently there is still an issue in vexflow:

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

@@ -640,6 +640,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     for (let i: number = 0; i < this.tuplets[voiceID].length; i++) {
                         const tuplet: Tuplet = this.tuplets[voiceID][i][0];
                         const vftuplet: VF.Tuplet = this.vftuplets[voiceID][i];
+                        if (!vftuplet) { // see #1330, potentially to be investigated. why undefined?
+                            continue;
+                        }
                         if (!tuplet.RenderTupletNumber) {
                             // (vftuplet as any).numerator_glyphs_stored = [...(vftuplet as any).numerator_glyphs];
                             // (vftuplet as any).numerator_glyphs = [];

+ 21 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -8,6 +8,7 @@ import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 import { Note } from "../../VoiceData/Note";
 import { AccidentalEnum } from "../../../Common/DataObjects/Pitch";
+import { BoundingBox } from "../BoundingBox";
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
@@ -36,18 +37,35 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
                     continue;
                 }
                 gve.applyBordersFromVexflow();
+                let isSecondaryWholeRest: boolean = false;
+                let bboxToAdjust: BoundingBox = this.PositionAndShape;
+                if (gve.notes[0].sourceNote.isWholeRest() && !this.hasOnlyRests()) {
+                    isSecondaryWholeRest = true;
+                    // continue; // also an option (simpler), but makes the voice entry bounding boxes very wrong (shifted)
+                    bboxToAdjust = gve.PositionAndShape;
+                    // don't use a whole rest's position for the staffentry.x if we also have a normal note in another voice (#1267)
+                    //   a more ideal solution would probably be to give a secondary whole note its own staffentry and staffentry position,
+                    //   since it's so different from a normal note which is also the first note of the measure.
+                    //   But we probably have some code that assumes there's only one staffentry per staff per timestamp.
+                    //   "A [[SourceStaffEntry]] is a container spanning all the [[VoiceEntry]]s at one timestamp for one [[StaffLine]]"
+                }
                 if (this.parentMeasure.ParentStaff.isTab) {
                     // the x-position could be finetuned for the cursor.
                     // somehow, gve.vfStaveNote.getBoundingBox() is null for a TabNote (which is a StemmableNote).
-                    this.PositionAndShape.RelativePosition.x = (gve.vfStaveNote.getAbsoluteX() + (<any>gve.vfStaveNote).glyph.getWidth()) / unitInPixels;
+                    bboxToAdjust.RelativePosition.x = (gve.vfStaveNote.getAbsoluteX() + (<any>gve.vfStaveNote).glyph.getWidth()) / unitInPixels;
                 } else {
-                    this.PositionAndShape.RelativePosition.x = gve.vfStaveNote.getBoundingBox().getX() / unitInPixels;
+                    bboxToAdjust.RelativePosition.x = gve.vfStaveNote.getBoundingBox().getX() / unitInPixels;
+                    if (isSecondaryWholeRest) {
+                        bboxToAdjust.RelativePosition.x -= stave.getNoteStartX() / unitInPixels;
+                        bboxToAdjust.RelativePosition.x -= 1.3;
+                        // fix whole rest bounding box for these cases, slightly hacky admittedly, probably depends on WholeRestXShiftVexflow
+                    }
                 }
                 const sourceNote: Note = gve.notes[0].sourceNote;
                 if (sourceNote.isRest() && sourceNote.Length.RealValue === this.parentMeasure.parentSourceMeasure.ActiveTimeSignature.RealValue) {
                     // whole rest: length = measure length. (4/4 in a 4/4 time signature, 3/4 in a 3/4 time signature, 1/4 in a 1/4 time signature, etc.)
                     // see Note.isWholeRest(), which is currently not safe
-                    this.PositionAndShape.RelativePosition.x +=
+                    bboxToAdjust.RelativePosition.x +=
                         this.parentMeasure.parentSourceMeasure.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
                     gve.PositionAndShape.BorderLeft = -0.7;
                     gve.PositionAndShape.BorderRight = 0.7;

+ 1 - 1
src/MusicalScore/VoiceData/SourceStaffEntry.ts

@@ -270,7 +270,7 @@ export class SourceStaffEntry {
     public get hasOnlyRests(): boolean {
         for (const voiceEntry of this.voiceEntries) {
             for (const note of voiceEntry.Notes) {
-                if (!note.isRest) {
+                if (!note.isRest()) {
                     return false;
                 }
             }

+ 3 - 0
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -269,6 +269,9 @@ export class OpenSheetMusicDisplay {
                 cursor.init(this.sheet.MusicPartManager, this.graphic);
             });
         }
+        if (this.drawingParameters.DrawingParametersEnum === DrawingParametersEnum.leadsheet) {
+            this.graphic.LeadSheet = true;
+        }
         this.renderingManager.setMusicSheet(this.graphic);
         this.interactionManager.Initialize();
     }

+ 4 - 1
src/VexFlowPatch/src/tables.js

@@ -83,7 +83,10 @@ Flow.keyProperties = (key, clef, params) => {
   let octave = parseInt(pieces[1], 10);
 
   // Octave_shift is the shift to compensate for clef 8va/8vb.
-  octave += -1 * options.octave_shift;
+  octave -= options.octave_shift;
+  // VexFlowPatch: change "+= -1 *" to "-=". No change in OSMD detected,
+  //   though apparently -0 can cause issues on some systems.
+  //   https://github.com/0xfe/vexflow/pull/1596
 
   const base_index = (octave * 7) - (4 * 7);
   let line = (base_index + value.index) / 2;

+ 6 - 1
test/Util/generateImages_browserless.mjs

@@ -327,6 +327,8 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
         isTestOctaveShiftInvisibleInstrument = sampleFilename.includes("test_octaveshift_first_instrument_invisible");
         const isTextOctaveShiftExtraGraphicalMeasure = sampleFilename.includes("test_octaveshift_extragraphicalmeasure");
         isTestInvisibleMeasureNotAffectingLayout = sampleFilename.includes("test_invisible_measure_not_affecting_layout");
+        const isTestWedgeMultilineCrescendo = sampleFilename.includes("test_wedge_multiline_crescendo");
+        const isTestWedgeMultilineDecrescendo = sampleFilename.includes("test_wedge_multiline_decrescendo");
         osmdInstance.EngravingRules.loadDefaultValues(); // note this may also be executed in setOptions below via drawingParameters default
         if (isTestEndClefStaffEntryBboxes) {
             drawBoundingBoxString = "VexFlowStaffEntry";
@@ -375,7 +377,10 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
         if (isTestCajon2NoteSystem) {
             osmdInstance.EngravingRules.PercussionUseCajon2NoteSystem = true;
         }
-        if (isTextOctaveShiftExtraGraphicalMeasure || isTestOctaveShiftInvisibleInstrument) {
+        if (isTextOctaveShiftExtraGraphicalMeasure ||
+            isTestOctaveShiftInvisibleInstrument ||
+            isTestWedgeMultilineCrescendo ||
+            isTestWedgeMultilineDecrescendo) {
             osmdInstance.EngravingRules.NewSystemAtXMLNewSystemAttribute = true;
         }
     }

+ 309 - 0
test/data/test_lyrics_x-alignment_whole_rest_second_voice.musicxml

@@ -0,0 +1,309 @@
+<?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_x-alignment_whole_rest_second_voice</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2022-11-07</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>rights</credit-type>
+    <credit-words default-x="600" default-y="85.7143" justify="center" valign="bottom" font-size="9">e</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name print-object="no">Piano</part-name>
+      <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-group type="start" number="1">
+      <group-symbol>brace</group-symbol>
+      </part-group>
+    <score-part id="P2">
+      <part-name print-object="no">Piano</part-name>
+      <score-instrument id="P2-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P2-I1" port="1"></midi-device>
+      <midi-instrument id="P2-I1">
+        <midi-channel>2</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="462.69">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>70.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="83.49" 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="-20.39" default-y="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>1. lorem</text>
+          </lyric>
+        <lyric number="2" default-x="-22.00" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>2. lorem</text>
+          </lyric>
+        </note>
+      <note default-x="177.84" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>ipsum</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>ipsum</text>
+          </lyric>
+        </note>
+      <note default-x="272.19" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>dolor</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>dolor</text>
+          </lyric>
+        </note>
+      <note default-x="366.54" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>sit</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>sit</text>
+          </lyric>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>2</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="2" width="515.88">
+      <note default-x="30.73" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>amet</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>amet</text>
+          </lyric>
+        </note>
+      <note default-x="149.36" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>consectetur</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>consectetur</text>
+          </lyric>
+        </note>
+      <note default-x="267.98" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>adipiscing</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>adipiscing</text>
+          </lyric>
+        </note>
+      <note default-x="386.61" 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="-40.16" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>elit</text>
+          </lyric>
+        <lyric number="2" default-x="6.50" default-y="-61.82" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>elit</text>
+          </lyric>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <note>
+        <rest/>
+        <duration>4</duration>
+        <voice>2</voice>
+        <type>whole</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="462.69">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>147.81</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="2" width="515.88">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 171 - 0
test/data/test_wedge_multiline_crescendo.musicxml

@@ -0,0 +1,171 @@
+<?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_wedge_multiline_crescendo</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-09-18</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_wedge_multiline_crescendo</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="978.70">
+      <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>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-68.65"/>
+          </direction-type>
+        </direction>
+      <note default-x="84.22" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="2" width="1028.70">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="3" width="1028.70">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="4" width="212.21">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>816.49</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 171 - 0
test/data/test_wedge_multiline_decrescendo.musicxml

@@ -0,0 +1,171 @@
+<?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_wedge_multiline_decrescendo</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-09-18</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_wedge_multiline_decrescendo</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="978.70">
+      <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>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-68.65"/>
+          </direction-type>
+        </direction>
+      <note default-x="84.22" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="2" width="1028.70">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="3" width="1028.70">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      </measure>
+    <measure number="4" width="212.21">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>816.49</right-margin>
+            </system-margins>
+          <system-distance>235.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="58.59" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>