Browse Source

merge osmd-public (1.7.5): many fixes and improvements (see public osmd changelog)

sschmidTU 2 years ago
parent
commit
52c5ff7239

+ 1 - 1
package.json

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

+ 31 - 0
src/Common/FileIO/Xml.ts

@@ -103,4 +103,35 @@ export class IXmlElement {
         }
         return ret;
     }
+
+    /**
+     * Get the first child element with the given node name
+     * with all the children of consequent child elements with the same node name.
+     * for example two <notations> tags will be combined for better processing
+     * @param elementName
+     * @returns {IXmlElement}
+     */
+    public combinedElement(elementName: string): IXmlElement {
+        const nodes: NodeList = this.elem.childNodes;
+        if (nodes.length > 0) {
+            let firstNode: Node;
+            for (let i: number = 0, length: number = nodes.length; i < length; i += 1) {
+                const otherNode: Node = nodes[i];
+                if (otherNode.nodeType === Node.ELEMENT_NODE && otherNode.nodeName.toLowerCase() === elementName) {
+                    if (firstNode) {
+                        const childNodes: NodeList = otherNode.childNodes;
+                        for (let j: number = 0, numChildNodes: number = childNodes.length; j < numChildNodes; j += 1) {
+                            const childNode: Node = childNodes[j];
+                            firstNode.appendChild(childNode.cloneNode(true));
+                        }
+                    } else {
+                        firstNode = otherNode;
+                    }
+                }
+            }
+            if (firstNode) {
+                return new IXmlElement(firstNode as Element);
+            }
+        }
+    }
 }

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

@@ -241,6 +241,7 @@ export class EngravingRules {
     public SystemRepetitionEndingLineWidth: number;
     public SystemDotWidth: number;
     public MultipleRestMeasureDefaultWidth: number;
+    public MultipleRestMeasureAddKeySignature: boolean;
     public DistanceBetweenVerticalSystemLines: number;
     public DistanceBetweenDotAndLine: number;
     public RepeatEndStartPadding: number;
@@ -668,6 +669,7 @@ export class EngravingRules {
         this.GraceLineWidth = this.StaffLineWidth * this.GraceNoteScalingFactor;
 
         this.MultipleRestMeasureDefaultWidth = 4;
+        this.MultipleRestMeasureAddKeySignature = true;
 
         // Line Widths
         this.MinimumCrossedBeamDifferenceMargin = 0.0001;

+ 1 - 0
src/MusicalScore/Graphical/GraphicalMeasure.ts

@@ -73,6 +73,7 @@ export abstract class GraphicalMeasure extends GraphicalObject {
     public IsExtraGraphicalMeasure: boolean;
     public ExtraGraphicalMeasurePreviousMeasure: GraphicalMeasure;
     public ShowTimeSignature: boolean = true;
+    public ShowKeySignature: boolean = true;
 
     public get ParentStaff(): Staff {
         return this.parentStaff;

+ 1 - 1
src/MusicalScore/Graphical/GraphicalRectangle.ts

@@ -7,7 +7,7 @@ export class GraphicalRectangle extends GraphicalObject {
 
     constructor(upperLeftPoint: PointF2D, lowerRightPoint: PointF2D, parent: BoundingBox, style: OutlineAndFillStyleEnum) {
         super();
-        this.boundingBox = new BoundingBox(parent);
+        this.boundingBox = new BoundingBox(this, parent);
         this.boundingBox.RelativePosition = upperLeftPoint;
         this.boundingBox.BorderRight = lowerRightPoint.x - upperLeftPoint.x;
         this.boundingBox.BorderBottom = lowerRightPoint.y - upperLeftPoint.y;

+ 31 - 4
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -72,6 +72,7 @@ import { IStafflineNoteCalculator } from "../Interfaces/IStafflineNoteCalculator
 import { GraphicalUnknownExpression } from "./GraphicalUnknownExpression";
 import { GraphicalChordSymbolContainer } from ".";
 import { LyricsEntry } from "../VoiceData/Lyrics/LyricsEntry";
+import { Voice } from "../VoiceData/Voice";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -181,7 +182,13 @@ export abstract class MusicSheetCalculator {
                 // console.log(`skipping ${measuresToSkip} measures for measure #${sourceMeasure.MeasureNumber}.`);
                 idx += measuresToSkip;
                 for (let idx2: number = 1; idx2 <= measuresToSkip; idx2++) {
-                    const nextSourceMeasure: SourceMeasure = musicSheet.SourceMeasures[sourceMeasure.MeasureNumber - 1 + idx2];
+                    const nextMeasureIndex: number = musicSheet.SourceMeasures.indexOf(sourceMeasure) + idx2;
+                    // note that if there are pickup measures in the sheet, the measure index is not MeasureNumber - 1.
+                    //   (if first measure in the sheet is a pickup measure, its index and measure number will be 0)
+                    if (nextMeasureIndex >= musicSheet.SourceMeasures.length) {
+                        break; // shouldn't happen, but for safety.
+                    }
+                    const nextSourceMeasure: SourceMeasure = musicSheet.SourceMeasures[nextMeasureIndex];
                     // TODO handle the case that a measure after the first multiple rest measure can't be reduced
                     nextSourceMeasure.multipleRestMeasureNumber = idx2 + 1;
                     nextSourceMeasure.isReducedToMultiRest = true;
@@ -2614,8 +2621,19 @@ export abstract class MusicSheetCalculator {
                                                           measure.parentSourceMeasure.AbsoluteTimestamp,
                                                           measure.parentSourceMeasure.CompleteNumberOfStaves),
                     staff);
+                if (staff.Voices.length === 0) {
+                    const newVoice: Voice = new Voice(measure.ParentStaff.ParentInstrument, -1);
+                    // this is problematic because we don't know the MusicXML voice ids and how many voices with which ids will be created after this.
+                    //   but it should only happen when the first measure of the piece is empty.
+                    staff.Voices.push(newVoice);
+                }
                 const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
-                const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined, sourceMeasure);
+                let duration: Fraction = sourceMeasure.Duration;
+                if (duration.RealValue === 0) {
+                    duration = sourceMeasure.ActiveTimeSignature.clone();
+                }
+                const note: Note = new Note(voiceEntry, sourceStaffEntry, duration, undefined, sourceMeasure, true);
+                note.IsWholeMeasureRest = true; // there may be a more elegant solution
                 note.PrintObject = this.rules.FillEmptyMeasuresWithWholeRest === FillEmptyMeasuresWithWholeRests.YesVisible;
                   // don't display whole rest that wasn't given in XML, only for layout/voice completion
                 voiceEntry.addNote(note);
@@ -2628,7 +2646,8 @@ export abstract class MusicSheetCalculator {
                     note,
                     gve,
                     new ClefInstruction(),
-                    OctaveEnum.NONE, undefined);
+                    OctaveEnum.NONE,
+                    this.rules);
                 MusicSheetCalculator.stafflineNoteCalculator.trackNote(graphicalNote);
                 gve.notes.push(graphicalNote);
             }
@@ -2778,6 +2797,14 @@ export abstract class MusicSheetCalculator {
         }
     }
 
+    private getFingeringPlacement(measure: GraphicalMeasure): PlacementEnum {
+        let placement: PlacementEnum = this.rules.FingeringPosition;
+        if (placement === PlacementEnum.NotYetDefined || placement === PlacementEnum.AboveOrBelow) {
+            placement = measure.isUpperStaffOfInstrument() ? PlacementEnum.Above : PlacementEnum.Below;
+        }
+        return placement;
+    }
+
     public calculateFingerings(): void {
         if (this.rules.FingeringPosition === PlacementEnum.Left ||
             this.rules.FingeringPosition === PlacementEnum.Right) {
@@ -2786,7 +2813,7 @@ export abstract class MusicSheetCalculator {
         for (const system of this.musicSystems) {
             for (const line of system.StaffLines) {
                 for (const measure of line.Measures) {
-                    const placement: PlacementEnum = measure.isUpperStaffOfInstrument() ? PlacementEnum.Above : PlacementEnum.Below;
+                    const placement: PlacementEnum = this.getFingeringPlacement(measure);
                     for (const gse of measure.staffEntries) {
                         gse.FingeringEntries = [];
                         const skybottomcalculator: SkyBottomLineCalculator = line.SkyBottomLineCalculator;

+ 17 - 0
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -20,6 +20,7 @@ import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {SystemLinePosition} from "./SystemLinePosition";
+import { MusicSheet } from "../MusicSheet";
 
 export class MusicSystemBuilder {
     protected measureList: GraphicalMeasure[][];
@@ -838,6 +839,22 @@ export class MusicSystemBuilder {
             const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
             if (measure.endsWithLineRepetition()) {
                 return true;
+            } else if (measure.parentSourceMeasure?.isReducedToMultiRest) {
+                // check if the last measure in the multiple rest measure sequence has a repeat ending
+                const sheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
+                let currentMultirestMeasure: SourceMeasure = measure.parentSourceMeasure;
+                const startMeasureIndex: number = sheet.SourceMeasures.indexOf(currentMultirestMeasure);
+                let currentMultirestMeasureNumber: number = currentMultirestMeasure.multipleRestMeasureNumber;
+                for (let i: number = startMeasureIndex + 1; i < sheet.SourceMeasures.length; i++) {
+                    const nextMultirestMeasure: SourceMeasure = sheet.SourceMeasures[i];
+                    if (nextMultirestMeasure.multipleRestMeasureNumber >= currentMultirestMeasureNumber) {
+                        currentMultirestMeasure = nextMultirestMeasure;
+                        currentMultirestMeasureNumber = nextMultirestMeasure.multipleRestMeasureNumber;
+                    } else {
+                        break; // end of current multirest chain = last multirest measure
+                    }
+                }
+                return currentMultirestMeasure.endsWithLineRepetition();
             }
         }
         return false;

+ 2 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -320,7 +320,8 @@ export class VexFlowConverter {
                 //   (a whole rest note signifies a whole measure duration, unless the time signature is longer than 4 quarter notes, e.g. 6/4 or 3/2.
                 //   Note: this should not apply to most pickup measures, e.g. with an 8th pickup measure in a 3/4 time signature)
                 // const measureDuration: number = note.sourceNote.SourceMeasure.Duration.RealValue;
-                const isWholeMeasureRest: boolean =  baseNoteLength.RealValue === note.sourceNote.SourceMeasure.ActiveTimeSignature.RealValue;
+                const isWholeMeasureRest: boolean = note.sourceNote.IsWholeMeasureRest ||
+                    baseNoteLength.RealValue === note.sourceNote.SourceMeasure.ActiveTimeSignature.RealValue;
                 if (isWholeMeasureRest) {
                     keys = ["d/5"];
                     duration = "w";

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

@@ -247,7 +247,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
      * @param currentClef the valid clef. Needed to put the accidentals on the right y-positions.
      */
     public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
-        if (!this.rules.RenderKeySignatures) {
+        if (!this.rules.RenderKeySignatures || !this.ShowKeySignature) {
+            return;
+        }
+        if (this.parentSourceMeasure?.isReducedToMultiRest && !this.rules.MultipleRestMeasureAddKeySignature) {
             return;
         }
         this.stave.setKeySignature(

+ 238 - 208
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -16,7 +16,6 @@ import {ClefEnum} from "../VoiceData/Instructions/ClefInstruction";
 import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction";
 import {KeyEnum} from "../VoiceData/Instructions/KeyInstruction";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
-import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
 import log from "loglevel";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
@@ -42,11 +41,6 @@ import { ReaderPluginManager } from "./ReaderPluginManager";
 //  public static readMetronomeInstructions(xmlNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { }
 //  public static readTempoInstruction(soundNode: IXmlElement, musicSheet: MusicSheet, currentXmlMeasureIndex: number): void { }
 //}
-//
-//class ChordSymbolReader {
-//  public static readChordSymbol(xmlNode:IXmlElement, musicSheet:MusicSheet, activeKey:any): void {
-//  }
-//}
 
 
 /**
@@ -93,7 +87,8 @@ export class InstrumentReader {
   private activeClefsHaveBeenInitialized: boolean[];
   private activeKeyHasBeenInitialized: boolean = false;
   private abstractInstructions: [number, AbstractNotationInstruction, Fraction][] = [];
-  private openChordSymbolContainers: ChordSymbolContainer[] = [];
+  //TODO: remove line below if it is not needed anymore?
+  //private openChordSymbolContainers: ChordSymbolContainer[] = [];
   private expressionReaders: ExpressionReader[];
   private currentVoiceGenerator: VoiceGenerator;
   //private openSlurDict: { [n: number]: Slur; } = {};
@@ -163,6 +158,86 @@ export class InstrumentReader {
           if (newPageAttr?.value === "yes") {
             currentMeasure.printNewPageXml = true;
           }
+        } else if (xmlNode.name === "attributes") {
+          const divisionsNode: IXmlElement = xmlNode.element("divisions");
+          if (divisionsNode) {
+            this.divisions = parseInt(divisionsNode.value, 10);
+            if (isNaN(this.divisions)) {
+              const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
+                  "Invalid divisions value at Instrument: ");
+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
+              this.divisions = this.readDivisionsFromNotes();
+              if (this.divisions > 0) {
+                this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
+              } else {
+                divisionsException = true;
+                throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
+              }
+            }
+          }
+          if (
+              !xmlNode.element("divisions") &&
+              this.divisions === 0 &&
+              this.currentXmlMeasureIndex === 0
+          ) {
+            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", "Invalid divisions value at Instrument: ");
+            this.divisions = this.readDivisionsFromNotes();
+            if (this.divisions > 0) {
+              this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
+            } else {
+              divisionsException = true;
+              throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
+            }
+          }
+          this.addAbstractInstruction(xmlNode, octavePlusOne, previousNode, currentFraction.clone());
+          if (currentFraction.Equals(new Fraction(0, 1)) &&
+              this.isAttributesNodeAtBeginOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
+            this.saveAbstractInstructionList(this.instrument.Staves.length, true);
+          }
+          if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode, currentFraction)) {
+            this.saveClefInstructionAtEndOfMeasure();
+          }
+          const staffDetailsNodes: IXmlElement[] = xmlNode.elements("staff-details"); // there can be multiple, even if redundant. see #1041
+          for (const staffDetailsNode of staffDetailsNodes) {
+            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
+            if (staffLinesNode) {
+              let staffNumber: number = 1;
+              const staffNumberAttr: Attr = staffDetailsNode.attribute("number");
+              if (staffNumberAttr) {
+                staffNumber = parseInt(staffNumberAttr.value, 10);
+              }
+              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10);
+            }
+          }
+          // check multi measure rest
+          const measureStyle: IXmlElement = xmlNode.element("measure-style");
+          if (measureStyle) {
+            const multipleRest: IXmlElement = measureStyle.element("multiple-rest");
+            if (multipleRest) {
+              // TODO: save multirest per staff info a dictionary, to display a partial multirest if multirest values across staffs differ.
+              //   this makes the code bulkier though, and for now we only draw multirests if the staffs have the same multirest lengths.
+              // if (!currentMeasure.multipleRestMeasuresPerStaff) {
+              //   currentMeasure.multipleRestMeasuresPerStaff = new Dictionary<number, number>();
+              // }
+              const multipleRestValueXml: string = multipleRest.value;
+              let multipleRestNumber: number = 0;
+              try {
+                multipleRestNumber = Number.parseInt(multipleRestValueXml, 10);
+                if (currentMeasure.multipleRestMeasures !== undefined && multipleRestNumber !== currentMeasure.multipleRestMeasures) {
+                  // different multi-rest values in same measure for different staffs
+                  currentMeasure.multipleRestMeasures = 0; // for now, ignore multirest here. TODO: take minimum
+                  // currentMeasure.multipleRestMeasuresPerStaff.setValue(this.currentStaff?.Id, multipleRestNumber);
+                  //   issue: currentStaff can be undefined for first measure
+                } else {
+                  currentMeasure.multipleRestMeasures = multipleRestNumber;
+                  this.currentMultirestStartMeasure = currentMeasure;
+                  this.followingMultirestMeasures = multipleRestNumber + 1; // will be decremented at the start of the loop
+                }
+              } catch (e) {
+                console.log("multirest parse error: " + e);
+              }
+            }
+          }
         } else if (xmlNode.name === "note") {
           let printObject: boolean = true;
           if (xmlNode.attribute("print-object")?.value === "no") {
@@ -171,16 +246,7 @@ export class InstrumentReader {
               //   if (xmlNode.attribute("print-spacing").value === "yes" {
               //     // TODO give spacing for invisible notes even when not displayed. might be hard with Vexflow formatting
           }
-          let noteStaff: number = 1;
-          if (this.instrument.Staves.length > 1) {
-            if (xmlNode.element("staff")) {
-              noteStaff = parseInt(xmlNode.element("staff").value, 10);
-              if (isNaN(noteStaff)) {
-                log.debug("InstrumentReader.readNextXmlMeasure.get staff number");
-                noteStaff = 1;
-              }
-            }
-          }
+          const noteStaff: number = this.getNoteStaff(xmlNode);
 
           this.currentStaff = this.instrument.Staves[noteStaff - 1];
           const isChord: boolean = xmlNode.element("chord") !== undefined;
@@ -225,7 +291,7 @@ export class InstrumentReader {
           const restNote: boolean = xmlNode.element("rest") !== undefined;
           //log.info("New note found!", noteDivisions, noteDuration.toString(), restNote);
 
-          const notationsNode: IXmlElement = xmlNode.element("notations"); // used for multiple checks further on
+          const notationsNode: IXmlElement = xmlNode.combinedElement("notations"); // select all notation nodes
 
           const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
           let graceNoteSlash: boolean = false;
@@ -243,59 +309,17 @@ export class InstrumentReader {
 
             noteDuration = this.getNoteDurationFromTypeNode(xmlNode);
 
-            const notationNode: IXmlElement = xmlNode.element("notations");
-            if (notationNode) {
-              if (notationNode.element("slur")) {
-                graceSlur = true;
-                // grace slurs could be non-binary, but VexFlow.GraceNoteGroup modifier system is currently only boolean for slurs.
-              }
+            if (notationsNode && notationsNode.element("slur")) {
+              graceSlur = true;
+              // grace slurs could be non-binary, but VexFlow.GraceNoteGroup modifier system is currently only boolean for slurs.
             }
           }
 
           // check for cue note
-          let isCueNote: boolean = false;
-          const cueNode: IXmlElement = xmlNode.element("cue");
-          if (cueNode) {
-            isCueNote = true;
-          }
-          // alternative: check for <type size="cue">
-          const typeNode: IXmlElement = xmlNode.element("type");
-          let noteTypeXml: NoteType = NoteType.UNDEFINED;
-          if (typeNode) {
-            const sizeAttr: Attr = typeNode.attribute("size");
-            if (sizeAttr?.value === "cue") {
-              isCueNote = true;
-            }
-            noteTypeXml = NoteTypeHandler.StringToNoteType(typeNode.value);
-          }
+          const [isCueNote, noteTypeXml] = this.getCueNoteAndNoteTypeXml(xmlNode);
 
           // check stem element
-          let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
-          let stemColorXml: string;
-          const stemNode: IXmlElement = xmlNode.element("stem");
-          if (stemNode) {
-            switch (stemNode.value) {
-              case "down":
-                stemDirectionXml = StemDirectionType.Down;
-                break;
-              case "up":
-                stemDirectionXml = StemDirectionType.Up;
-                break;
-              case "double":
-                stemDirectionXml = StemDirectionType.Double;
-                break;
-              case "none":
-                stemDirectionXml = StemDirectionType.None;
-                break;
-              default:
-                stemDirectionXml = StemDirectionType.Undefined;
-            }
-
-            const stemColorAttr: Attr = stemNode.attribute("color");
-            if (stemColorAttr) { // can be null, maybe also undefined
-              stemColorXml = this.parseXmlColor(stemColorAttr.value);
-            }
-          }
+          const [stemDirectionXml, stemColorXml, noteheadColorXml] = this.getStemDirectionAndColors(xmlNode);
 
           // check Tremolo
           let tremoloStrokes: number = 0;
@@ -303,64 +327,12 @@ export class InstrumentReader {
           if (notationsNode) {
             const ornamentsNode: IXmlElement = notationsNode.element("ornaments");
             if (ornamentsNode) {
-              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) {
-                    tremoloStrokes = tremoloStrokesGiven;
-                  }
-                }
-                // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
-              }
-              const wavyLineNodes: IXmlElement[] = ornamentsNode.elements("wavy-line");
-              if (wavyLineNodes !== undefined) {
-                /* As mentioned elsewhere, the wavy-line is technically an ornament element, but is specified and behaves
-                   very much like a continuous expression, so makes more sense to interpret as an expression in our model.
-                */
-               for (const wavyLineNode of wavyLineNodes) {
-                  const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
-                  if (expressionReader) {
-                    //Read placement from the wavy line node
-                  expressionReader.readExpressionParameters(
-                    wavyLineNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
-                  );
-                  expressionReader.addWavyLine(
-                    wavyLineNode, this.currentMeasure, currentFraction, previousFraction
-                  );
-                  }
-               }
-              }
-            }
-          }
-
-          // check notehead/color
-          let noteheadColorXml: string;
-          const noteheadNode: IXmlElement = xmlNode.element("notehead");
-          if (noteheadNode) {
-            const colorAttr: Attr = noteheadNode.attribute("color");
-            if (colorAttr) {
-              noteheadColorXml = this.parseXmlColor(colorAttr.value);
+              tremoloStrokes = this.getTremoloStrokes(ornamentsNode);
+              this.getWavyLines(ornamentsNode, xmlNode, currentFraction, previousFraction);
             }
           }
 
-          let noteColorXml: string;
-          const noteColorAttr: Attr = xmlNode.attribute("color");
-          if (noteColorAttr) { // can be undefined
-            noteColorXml = this.parseXmlColor(noteColorAttr.value);
-            if (!noteheadColorXml) {
-              noteheadColorXml = noteColorXml;
-            }
-            if (!stemColorXml) {
-              stemColorXml = noteColorXml;
-            }
-          }
-
-          let musicTimestamp: Fraction = currentFraction.clone();
-          if (isChord) {
-            musicTimestamp = previousFraction.clone();
-          }
+          const musicTimestamp: Fraction = isChord ? previousFraction.clone() : currentFraction.clone();
           this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry(
             musicTimestamp,
             this.inSourceMeasureInstrumentIndex + noteStaff - 1,
@@ -375,7 +347,7 @@ export class InstrumentReader {
             || (!isGraceNote && lastNoteWasGrace)
           ) {
             this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, this.activeKey,
-                                                        this.ActiveRhythm, isGraceNote, graceNoteSlash, graceSlur);
+              this.ActiveRhythm, isGraceNote, graceNoteSlash, graceSlur);
           }
           if (!isGraceNote && !isChord) {
             previousFraction = currentFraction.clone();
@@ -396,11 +368,15 @@ export class InstrumentReader {
             this.currentStaffEntry.Timestamp.Equals(new Fraction(0, 1)) && !this.currentStaffEntry.hasNotes()
           );
           this.saveAbstractInstructionList(this.instrument.Staves.length, beginOfMeasure);
-          if (this.openChordSymbolContainers.length !== 0) {
-            this.currentStaffEntry.ChordContainers = this.openChordSymbolContainers;
-            // TODO handle multiple chords on one note/staffentry
-            this.openChordSymbolContainers = [];
-          }
+          // this if block handles harmony/chords on the next note/staffentry element, so it assumes that a
+          //   harmony is given before the staff entry, but when a harmony is given after a staff entry element with a backup node,
+          //   it is put on the next note/staffentry and the last chord item is never parsed at all.
+          //   see PR #1342
+          // if (this.openChordSymbolContainers.length !== 0) {
+          //   this.currentStaffEntry.ChordContainers = this.openChordSymbolContainers;
+          //   // TODO handle multiple chords on one note/staffentry
+          //   this.openChordSymbolContainers = [];
+          // }
           if (this.activeRhythm) {
             // (*) this.musicSheet.SheetPlaybackSetting.Rhythm = this.activeRhythm.Rhythm;
           }
@@ -430,87 +406,6 @@ export class InstrumentReader {
             }
           }
           lastNoteWasGrace = isGraceNote;
-        } else if (xmlNode.name === "attributes") {
-          const divisionsNode: IXmlElement = xmlNode.element("divisions");
-          if (divisionsNode) {
-            this.divisions = parseInt(divisionsNode.value, 10);
-            if (isNaN(this.divisions)) {
-              const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
-                                                                      "Invalid divisions value at Instrument: ");
-              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
-              this.divisions = this.readDivisionsFromNotes();
-              if (this.divisions > 0) {
-                this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
-              } else {
-                divisionsException = true;
-                throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
-              }
-            }
-          }
-          if (
-            !xmlNode.element("divisions") &&
-            this.divisions === 0 &&
-            this.currentXmlMeasureIndex === 0
-          ) {
-            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError", "Invalid divisions value at Instrument: ");
-            this.divisions = this.readDivisionsFromNotes();
-            if (this.divisions > 0) {
-              this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
-            } else {
-              divisionsException = true;
-              throw new MusicSheetReadingException(errorMsg + this.instrument.Name);
-            }
-          }
-          this.addAbstractInstruction(xmlNode, octavePlusOne, previousNode, currentFraction.clone());
-          if (currentFraction.Equals(new Fraction(0, 1)) &&
-            this.isAttributesNodeAtBeginOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode)) {
-            this.saveAbstractInstructionList(this.instrument.Staves.length, true);
-          }
-          if (this.isAttributesNodeAtEndOfMeasure(this.xmlMeasureList[this.currentXmlMeasureIndex], xmlNode, currentFraction)) {
-            this.saveClefInstructionAtEndOfMeasure();
-          }
-          const staffDetailsNodes: IXmlElement[] = xmlNode.elements("staff-details"); // there can be multiple, even if redundant. see #1041
-          for (const staffDetailsNode of staffDetailsNodes) {
-            const staffLinesNode: IXmlElement = staffDetailsNode.element("staff-lines");
-            if (staffLinesNode) {
-              let staffNumber: number = 1;
-              const staffNumberAttr: Attr = staffDetailsNode.attribute("number");
-              if (staffNumberAttr) {
-                staffNumber = parseInt(staffNumberAttr.value, 10);
-              }
-              this.instrument.Staves[staffNumber - 1].StafflineCount = parseInt(staffLinesNode.value, 10);
-            }
-          }
-          // check multi measure rest
-          const measureStyle: IXmlElement = xmlNode.element("measure-style");
-          if (measureStyle) {
-            const multipleRest: IXmlElement = measureStyle.element("multiple-rest");
-            if (multipleRest) {
-              // TODO: save multirest per staff info a dictionary, to display a partial multirest if multirest values across staffs differ.
-              //   this makes the code bulkier though, and for now we only draw multirests if the staffs have the same multirest lengths.
-              // if (!currentMeasure.multipleRestMeasuresPerStaff) {
-              //   currentMeasure.multipleRestMeasuresPerStaff = new Dictionary<number, number>();
-              // }
-              const multipleRestValueXml: string = multipleRest.value;
-              let multipleRestNumber: number = 0;
-              try {
-                multipleRestNumber = Number.parseInt(multipleRestValueXml, 10);
-                if (currentMeasure.multipleRestMeasures !== undefined && multipleRestNumber !== currentMeasure.multipleRestMeasures) {
-                  // different multi-rest values in same measure for different staffs
-                  currentMeasure.multipleRestMeasures = 0; // for now, ignore multirest here. TODO: take minimum
-                  // currentMeasure.multipleRestMeasuresPerStaff.setValue(this.currentStaff?.Id, multipleRestNumber);
-                  //   issue: currentStaff can be undefined for first measure
-                } else {
-                  currentMeasure.multipleRestMeasures = multipleRestNumber;
-                  this.currentMultirestStartMeasure = currentMeasure;
-                  this.followingMultirestMeasures = multipleRestNumber + 1; // will be decremented at the start of the loop
-                }
-              } catch (e) {
-                console.log("multirest parse error: " + e);
-              }
-            }
-          }
-
         } else if (xmlNode.name === "forward") {
           const forFraction: number = parseInt(xmlNode.element("duration").value, 10);
           currentFraction.Add(new Fraction(forFraction, 4 * this.divisions));
@@ -597,8 +492,13 @@ export class InstrumentReader {
             log.debug("InstrumentReader.readTempoInstruction", e);
           }
         } else if (xmlNode.name === "harmony") {
+          const noteStaff: number = this.getNoteStaff(xmlNode);
+          this.currentStaff = this.instrument.Staves[noteStaff - 1];
           // new chord, could be second chord on same staffentry/note
-          this.openChordSymbolContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey));
+          const musicTimestamp: Fraction = currentFraction.clone();
+          this.currentStaffEntry = this.currentMeasure.findOrCreateStaffEntry(
+              musicTimestamp, this.inSourceMeasureInstrumentIndex + noteStaff - 1, this.currentStaff).staffEntry;
+          this.currentStaffEntry.ChordContainers.push(ChordSymbolReader.readChordSymbol(xmlNode, this.musicSheet, this.activeKey));
         }
       }
       for (const j in this.voiceGeneratorsDict) {
@@ -652,6 +552,32 @@ export class InstrumentReader {
     return true;
   }
 
+  private getStemDirectionAndColors(xmlNode: IXmlElement): [StemDirectionType, string, string] {
+    let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
+    let stemColorXml: string;
+    const stemNode: IXmlElement = xmlNode.element("stem");
+    if (stemNode) {
+      stemDirectionXml = this.getStemDirectionType(stemNode);
+
+      const stemColorAttr: Attr = stemNode.attribute("color");
+      if (stemColorAttr) { // can be null, maybe also undefined
+        stemColorXml = this.parseXmlColor(stemColorAttr.value);
+      }
+    }
+
+    // check notehead/color
+    let noteheadColorXml: string = this.getNoteHeadColorXml(xmlNode);
+    const noteColorXml: string = this.getNoteColorXml(xmlNode);
+
+    if (noteColorXml && !noteheadColorXml) {
+      noteheadColorXml = noteColorXml;
+    }
+    if (noteColorXml && !stemColorXml) {
+      stemColorXml = noteColorXml;
+    }
+    return [stemDirectionXml, stemColorXml, noteheadColorXml];
+  }
+
   /** Parse a color in XML format. Can be #ARGB or #RGB format, colors as byte hex values.
    *  @return color in Vexflow format #[A]RGB or undefined for invalid xmlColorString
    */
@@ -1442,4 +1368,108 @@ export class InstrumentReader {
     }
     return divisionsFromNote;
   }
+
+  private getCueNoteAndNoteTypeXml(xmlNode: IXmlElement): [boolean, NoteType] {
+    const cueNode: IXmlElement = xmlNode.element("cue");
+    let isCueNote: boolean = false;
+    if (cueNode) {
+      isCueNote = true;
+    }
+
+    const typeNode: IXmlElement = xmlNode.element("type");
+    let noteTypeXml: NoteType = NoteType.UNDEFINED;
+    if (typeNode) {
+      const sizeAttr: Attr = typeNode.attribute("size");
+      if (sizeAttr?.value === "cue") {
+        isCueNote = true;
+      }
+      noteTypeXml = NoteTypeHandler.StringToNoteType(typeNode.value);
+    }
+    return [isCueNote, noteTypeXml];
+  }
+
+  private getStemDirectionType(stemNode: IXmlElement): StemDirectionType {
+    switch (stemNode.value) {
+      case "down":
+        return StemDirectionType.Down;
+      case "up":
+        return StemDirectionType.Up;
+      case "double":
+        return StemDirectionType.Double;
+      case "none":
+        return StemDirectionType.None;
+      default:
+        return StemDirectionType.Undefined;
+    }
+  }
+
+  private getNoteHeadColorXml(xmlNode: IXmlElement): string | null {
+    const noteheadNode: IXmlElement = xmlNode.element("notehead");
+    if (noteheadNode) {
+      const colorAttr: Attr = noteheadNode.attribute("color");
+      if (colorAttr) {
+        return this.parseXmlColor(colorAttr.value);
+      }
+    }
+    return null;
+  }
+
+  private getNoteColorXml(xmlNode: IXmlElement): string | null {
+    const noteColorAttr: Attr = xmlNode.attribute("color");
+    if (noteColorAttr) { // can be undefined
+      return this.parseXmlColor(noteColorAttr.value);
+    }
+    return null;
+  }
+
+  private getTremoloStrokes(ornamentsNode: IXmlElement): number {
+    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;
+        }
+      }
+      // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
+    }
+    return 0;
+  }
+
+  private getWavyLines(ornamentsNode: IXmlElement, xmlNode: IXmlElement, currentFraction: Fraction, previousFraction: Fraction): void {
+    const wavyLineNodes: IXmlElement[] = ornamentsNode.elements("wavy-line");
+    if (!wavyLineNodes) {
+      return;
+    }
+    /* As mentioned elsewhere, the wavy-line is technically an ornament element, but is specified and behaves
+        very much like a continuous expression, so makes more sense to interpret as an expression in our model.
+    */
+    for (const wavyLineNode of wavyLineNodes) {
+      const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
+      if (expressionReader) {
+        //Read placement from the wavy line node
+        expressionReader.readExpressionParameters(
+          wavyLineNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
+        );
+        expressionReader.addWavyLine(
+          wavyLineNode, this.currentMeasure, currentFraction, previousFraction
+        );
+      }
+    }
+  }
+
+  private getNoteStaff(xmlNode: IXmlElement): number {
+    let noteStaff: number = 1;
+    if (this.instrument.Staves.length > 1) {
+      if (xmlNode.element("staff")) {
+        noteStaff = parseInt(xmlNode.element("staff").value, 10);
+        if (isNaN(noteStaff)) {
+          log.debug("InstrumentReader.readNextXmlMeasure.get staff number");
+          noteStaff = 1;
+        }
+      }
+    }
+    return noteStaff;
+  }
 }

+ 1 - 0
src/MusicalScore/VoiceData/Note.ts

@@ -54,6 +54,7 @@ export class Note {
     /** The amount of notes the tuplet of this note (if there is one) replaces. */
     private normalNotes: number;
     private isRestFlag: boolean;
+    public IsWholeMeasureRest: boolean;
     /**
      * The untransposed (!!!) source data.
      */

+ 3 - 3
src/MusicalScore/VoiceData/SourceMeasure.ts

@@ -602,6 +602,9 @@ export class SourceMeasure {
     }
 
     public canBeReducedToMultiRest(): boolean {
+        if (this.firstRepetitionInstructions.length > 0 || this.lastRepetitionInstructions.length > 0) {
+            return false;
+        }
         let allRestsOrInvisible: boolean = true;
         let visibleLyrics: boolean = false;
         for (const container of this.verticalSourceStaffEntryContainers) {
@@ -618,9 +621,6 @@ export class SourceMeasure {
                 if (staffEntry.ParentStaff.hasLyrics) {
                     visibleLyrics = true;
                 }
-                if (this.firstRepetitionInstructions.length > 0 || this.lastRepetitionInstructions.length > 0) {
-                    return false;
-                }
                 for (const voiceEntry of staffEntry.VoiceEntries) {
                     for (const note of voiceEntry.Notes) {
                         if (!note.isRest()) {

+ 16 - 15
src/OpenSheetMusicDisplay/Cursor.ts

@@ -255,21 +255,22 @@ export class Cursor implements IPlaybackListener {
 
       musicSystem = multiRestGMeasure.ParentMusicSystem;
     } else {
-          // get all staff entries inside the current voice entry
-          const gseArr: VexFlowStaffEntry[] = voiceEntries.map(ve => this.getStaffEntryFromVoiceEntry(ve));
-          // sort them by x position and take the leftmost entry
-          const gse: VexFlowStaffEntry =
-                gseArr.sort((a, b) => a?.PositionAndShape?.AbsolutePosition?.x <= b?.PositionAndShape?.AbsolutePosition?.x ? -1 : 1 )[0];
-          if (gse) {
-            x = gse.PositionAndShape.AbsolutePosition.x;
-            musicSystem = gse.parentMeasure.ParentMusicSystem;
-          }
-          // debug: change color of notes under cursor (needs re-render)
-          // for (const gve of gse.graphicalVoiceEntries) {
-          //   for (const note of gve.notes) {
-          //     note.sourceNote.NoteheadColor = "#0000FF";
-          //   }
-          // }
+      // get all staff entries inside the current voice entry
+      const gseArr: VexFlowStaffEntry[] = voiceEntries.map(ve => this.getStaffEntryFromVoiceEntry(ve));
+      // sort them by x position and take the leftmost entry
+      const gse: VexFlowStaffEntry =
+            gseArr.sort((a, b) => a?.PositionAndShape?.AbsolutePosition?.x <= b?.PositionAndShape?.AbsolutePosition?.x ? -1 : 1 )[0];
+      if (gse) {
+        x = gse.PositionAndShape.AbsolutePosition.x;
+        musicSystem = gse.parentMeasure.ParentMusicSystem;
+      }
+
+      // debug: change color of notes under cursor (needs re-render)
+      // for (const gve of gse.graphicalVoiceEntries) {
+      //   for (const note of gve.notes) {
+      //     note.sourceNote.NoteheadColor = "#0000FF";
+      //   }
+      // }
     }
     if (!musicSystem) {
       return;

+ 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.4-audio-extended"; // getter: this.Version
+    private version: string = "1.7.5-audio-extended"; // getter: this.Version
     // at release, bump version and change to -release, afterwards to -dev again
 
     /**

+ 13 - 3
src/VexFlowPatch/src/tabnote.js

@@ -434,12 +434,22 @@ export class TabNote extends StemmableNote {
       const y = ys[i] + this.render_options.y_shift;
       const glyph = this.glyphs[i];
 
+      let currentGlyphWidth = glyph.getWidth();
+      if (currentGlyphWidth === 0 && glyph.text && glyph.text.toString() && glyph.text.toString().length) {
+        // above: glyph.text?.toString()?.length would be shorter, but fails appveyor build
+        // VexflowPatch: workaround for generateImages script -> SVG export
+        currentGlyphWidth = glyph.text.toString().length * 7;
+      }
       // Center the fret text beneath the notation note head
-      const note_glyph_width = this.glyph.getWidth();
-      const tab_x = x + (note_glyph_width / 2) - (glyph.getWidth() / 2);
+      let note_glyph_width = this.glyph.getWidth();
+      if (note_glyph_width === 0) {
+        // VexflowPatch: correct positioning after VexflowPatch change above
+        note_glyph_width = currentGlyphWidth;
+      }
+      const tab_x = x + (note_glyph_width / 2) - (currentGlyphWidth / 2);
 
       // FIXME: Magic numbers.
-      ctx.clearRect(tab_x - 2, y - 3, glyph.getWidth() + 4, 6);
+      ctx.clearRect(tab_x - 2, y - 3, currentGlyphWidth + 4, 6);
 
       if (glyph.code) {
         Glyph.renderGlyph(ctx, tab_x, y,

+ 3 - 1
test/Util/generateImages_browserless.mjs

@@ -319,7 +319,7 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
         const isTestFlatBeams = sampleFilename.startsWith("test_drum_tuplet_beams");
         const isTestEndClefStaffEntryBboxes = sampleFilename.startsWith("test_end_measure_clefs_staffentry_bbox");
         const isTestPageBottomMargin0 = sampleFilename.includes("PageBottomMargin0");
-        osmdInstance.EngravingRules.loadDefaultValues();
+        osmdInstance.EngravingRules.loadDefaultValues(); // note this may also be executed in setOptions below via drawingParameters default
         if (isTestEndClefStaffEntryBboxes) {
             drawBoundingBoxString = "VexFlowStaffEntry";
         } else {
@@ -340,6 +340,8 @@ async function generateSampleImage (sampleFilename, directory, osmdInstance, osm
             pageFormat: pageFormat, // reset by drawingparameters default,
             ...makeSkyBottomLineOptions()
         });
+        // note that loadDefaultValues() may be executed in setOptions with drawingParameters default
+        //osmdInstance.EngravingRules.RenderSingleHorizontalStaffline = true; // to use this option here, place it after setOptions(), see above
         osmdInstance.EngravingRules.AlwaysSetPreferredSkyBottomLineBackendAutomatically = false; // this would override the command line options (--plain etc)
         includeSkyBottomLine = options.skyBottomLine ? options.skyBottomLine : false; // apparently es6 doesn't have ?? operator
         osmdInstance.drawSkyLine = includeSkyBottomLine; // if includeSkyBottomLine, draw skyline and bottomline, else not

+ 138 - 0
test/data/test_chord_symbols_backup_node_timestamp_1270.musicxml

@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!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>OSMD Tests</work-title>
+  </work>
+  <movement-title>test_chord_symbols_backup_node_timestamp_1270</movement-title>
+  <identification>
+    <encoding>
+      <software>Dorico 4.2.0.1092</software>
+      <encoding-date>2022-11-09</encoding-date>
+    </encoding>
+  </identification>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+    </score-part>
+  </part-list>
+  <part id="P1">
+    <measure number="1">
+      <attributes>
+        <divisions>4</divisions>
+        <key number="1">
+          <fifths>0</fifths>
+          <mode>none</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <note>
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+      <backup>
+        <duration>4</duration>
+      </backup>
+      <harmony>
+        <root>
+          <root-step>C</root-step>
+          <root-alter>0</root-alter>
+        </root>
+        <kind text="">major</kind>
+      </harmony>
+      <forward>
+        <duration>4</duration>
+      </forward>
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+      <backup>
+        <duration>4</duration>
+      </backup>
+      <harmony>
+        <root>
+          <root-step>G</root-step>
+          <root-alter>0</root-alter>
+        </root>
+        <kind text="">major</kind>
+      </harmony>
+      <forward>
+        <duration>4</duration>
+      </forward>
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+      <backup>
+        <duration>4</duration>
+      </backup>
+      <harmony>
+        <root>
+          <root-step>A</root-step>
+          <root-alter>0</root-alter>
+        </root>
+        <kind text="m">minor</kind>
+      </harmony>
+      <forward>
+        <duration>4</duration>
+      </forward>
+      <note>
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+      <backup>
+        <duration>4</duration>
+      </backup>
+      <harmony>
+        <root>
+          <root-step>F</root-step>
+          <root-alter>0</root-alter>
+        </root>
+        <kind text="">major</kind>
+      </harmony>
+      <forward>
+        <duration>4</duration>
+      </forward>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+      </barline>
+    </measure>
+  </part>
+</score-partwise>

+ 109 - 0
test/data/test_multiple_rest_measures_repeat_2_measures.musicxml

@@ -0,0 +1,109 @@
+<?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_multiple_rest_measures_repeat_2_measures</work-title>
+    </work>
+  <identification>
+    <creator type="composer">Composer</creator>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-02-16</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">repeat-test</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="231.90">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>596.88</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>
+        <measure-style>
+          <multiple-rest>2</multiple-rest>
+          </measure-style>
+        </attributes>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+    <measure number="2" width="181.22">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        <repeat direction="backward"/>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 116 - 0
test/data/test_multiple_rest_measures_repeat_3_measures.musicxml

@@ -0,0 +1,116 @@
+<?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_multiple_rest_measures_repeat_3_measures</work-title>
+    </work>
+  <identification>
+    <creator type="composer">Composer</creator>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-02-16</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">repeat-test</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="231.90">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>596.88</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>
+        <measure-style>
+          <multiple-rest>3</multiple-rest>
+          </measure-style>
+        </attributes>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      </measure>
+	<measure number="2" width="181.22">
+	  <note>
+		<rest measure="yes"/>
+		<duration>4</duration>
+		<voice>1</voice>
+		</note>
+	  </measure>
+    <measure number="3" width="181.22">
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        <repeat direction="backward"/>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 738 - 0
test/data/test_notations_nodes_dorico_say_something.musicxml

@@ -0,0 +1,738 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!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_notations_nodes_dorico_say_something</work-title>
+  </work>
+  <identification>
+    <encoding>
+      <software>Dorico 4.2.0.1092</software>
+      <encoding-date>2022-10-18</encoding-date>
+    </encoding>
+  </identification>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Soprano 1</part-name>
+    </score-part>
+    <score-part id="P2">
+      <part-name>Mezzo-soprano</part-name>
+    </score-part>
+    <score-part id="P3">
+      <part-name>Alto</part-name>
+    </score-part>
+    <score-part id="P4">
+      <part-name>Bass</part-name>
+    </score-part>
+  </part-list>
+  <part id="P1">
+    <measure number="1">
+      <attributes>
+        <divisions>4</divisions>
+        <key number="1">
+          <fifths>3</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>6</beats>
+          <beat-type>8</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <sound tempo="75"/>
+      <direction placement="above" directive="yes">
+        <direction-type>
+          <words font-style="normal" font-weight="bold"> = 50</words>
+        </direction-type>
+        <direction-type>
+          <metronome>
+            <beat-unit>quarter</beat-unit>
+            <beat-unit-dot/>
+            <per-minute>50</per-minute>
+          </metronome>
+        </direction-type>
+        <staff>1</staff>
+      </direction>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+      <direction placement="above">
+        <direction-type>
+          <dynamics>
+            <p/>
+          </dynamics>
+        </direction-type>
+        <staff>1</staff>
+      </direction>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">begin</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>Say</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <lyric number="1" placement="below">
+          <syllabic>begin</syllabic>
+          <text>some</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">end</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>thing,</text>
+        </lyric>
+      </note>
+    </measure>
+    <measure number="2">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">begin</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>I’m</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <lyric number="1" placement="below">
+          <syllabic>begin</syllabic>
+          <text>gi</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">end</beam>
+        <lyric number="1" placement="below">
+          <syllabic>end</syllabic>
+          <text>ving</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">begin</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>up</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>on</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">end</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>you</text>
+        </lyric>
+      </note>
+    </measure>
+    <measure number="3">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="4">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="5">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">begin</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>I’ll</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        <beam number="1">continue</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>be</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</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">begin</beam>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>the</text>
+        </lyric>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</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>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>one,</text>
+          <extend/>
+        </lyric>
+      </note>
+    </measure>
+  </part>
+  <part id="P2">
+    <measure number="1">
+      <attributes>
+        <divisions>4</divisions>
+        <key number="1">
+          <fifths>3</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>6</beats>
+          <beat-type>8</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="2">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="3">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+      <direction placement="above">
+        <direction-type>
+          <dynamics>
+            <p/>
+          </dynamics>
+        </direction-type>
+        <staff>1</staff>
+      </direction>
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+        <notations>
+          <slur type="start" number="1"/>
+        </notations>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>ooh</text>
+          <extend/>
+        </lyric>
+      </note>
+    </measure>
+    <measure number="4">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>G</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="5">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+        <notations>
+          <slur type="stop" number="1"/>
+        </notations>
+      </note>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+    </measure>
+  </part>
+  <part id="P3">
+    <measure number="1">
+      <attributes>
+        <divisions>4</divisions>
+        <key number="1">
+          <fifths>3</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>6</beats>
+          <beat-type>8</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+        </clef>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="2">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="3">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+      <direction placement="above">
+        <direction-type>
+          <dynamics>
+            <p/>
+          </dynamics>
+        </direction-type>
+        <staff>1</staff>
+      </direction>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+        <notations>
+          <slur type="start" number="1"/>
+        </notations>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>ooh</text>
+          <extend/>
+        </lyric>
+      </note>
+    </measure>
+    <measure number="4">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>B</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="5">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+        <notations>
+          <slur type="stop" number="1"/>
+        </notations>
+      </note>
+      <note>
+        <rest/>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <staff>1</staff>
+      </note>
+    </measure>
+  </part>
+  <part id="P4">
+    <measure number="1">
+      <attributes>
+        <divisions>4</divisions>
+        <key number="1">
+          <fifths>3</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>6</beats>
+          <beat-type>8</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>F</sign>
+          <line>4</line>
+        </clef>
+      </attributes>
+      <direction placement="above">
+        <direction-type>
+          <dynamics>
+            <p/>
+          </dynamics>
+        </direction-type>
+        <staff>1</staff>
+      </direction>
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>down</stem>
+        <staff>1</staff>
+        <notations>
+          <slur type="start" number="1"/>
+        </notations>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>hmm</text>
+          <extend/>
+        </lyric>
+      </note>
+    </measure>
+    <measure number="2">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>D</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>down</stem>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="3">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>A</step>
+          <octave>2</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="4">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>6</duration>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>down</stem>
+        <staff>1</staff>
+        <notations>
+          <tied type="start"/>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>E</step>
+          <octave>3</octave>
+        </pitch>
+        <duration>4</duration>
+        <tie type="stop"/>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <staff>1</staff>
+        <notations>
+          <tied type="stop"/>
+        </notations>
+        <notations>
+          <slur type="stop" number="1"/>
+        </notations>
+      </note>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <staff>1</staff>
+      </note>
+    </measure>
+    <measure number="5">
+      <attributes>
+        <divisions>4</divisions>
+      </attributes>
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>down</stem>
+        <staff>1</staff>
+        <lyric number="1" placement="below">
+          <syllabic>single</syllabic>
+          <text>hmm</text>
+          <extend/>
+        </lyric>
+      </note>
+    </measure>
+	</part>
+</score-partwise>