Browse Source

merge osmd-public (1.7.3): fix cursor.previous() infinite loop, ES2018/Android API28 compatibility, improve slur placement if not given in XML, pedals, etc

see 1.7.3 changelog:
https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/blob/develop/CHANGELOG.md
sschmidTU 2 years ago
parent
commit
43a84d0aa4

+ 1 - 1
package.json

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

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

@@ -777,8 +777,12 @@ export class EngravingRules {
     }
 
     public setPreferredSkyBottomLineBackendAutomatically(numberOfGraphicalMeasures: number = -1): void {
-        const vendor: string = globalThis.navigator?.vendor ?? "";
-        const userAgent: string = globalThis.navigator?.userAgent ?? "";
+        let vendor: string = "";
+        let userAgent: string = "";
+        if (typeof globalThis === "object") { // it looks like globalThis can be undefined and cause build issues in es2017 (e.g. Android API 28), see #1299
+            vendor = globalThis.navigator?.vendor ?? "";
+            userAgent = globalThis.navigator?.userAgent ?? "";
+        }
         let alwaysUsePlain: boolean = false;
         if (this.DisableWebGLInSafariAndIOS && (/apple/i).test(vendor)) { // doesn't apply to Chrome on MacOS
             alwaysUsePlain = true;

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

@@ -659,7 +659,7 @@ export class GraphicalSlur extends GraphicalCurve {
         //     return;
         // }
 
-        if (this.rules.SlurPlacementFromXML) {
+        if (this.rules.SlurPlacementFromXML && this.slur.PlacementXml !== PlacementEnum.NotYetDefined) {
             this.placement = this.slur.PlacementXml;
             return;
         }

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

@@ -1119,6 +1119,7 @@ export abstract class MusicSheetCalculator {
             return;
         }
         let currentTupletNumber: number = -1;
+        let currentTypeLength: Fraction = undefined;
         let consecutiveTupletCount: number = 0;
         let currentTuplet: Tuplet = undefined;
         let skipTuplet: Tuplet = undefined; // if set, ignore (further) handling of this tuplet
@@ -1134,6 +1135,7 @@ export abstract class MusicSheetCalculator {
                             currentTupletNumber = -1;
                             consecutiveTupletCount = 0;
                             currentTuplet = undefined;
+                            currentTypeLength = undefined;
                             continue;
                         }
                         if (firstNote.NoteTuplet === skipTuplet) {
@@ -1155,8 +1157,10 @@ export abstract class MusicSheetCalculator {
                                 }
                             }
                         }
-                        if (firstNote.NoteTuplet.TupletLabelNumber !== currentTupletNumber) {
+                        if (firstNote.NoteTuplet.TupletLabelNumber !== currentTupletNumber ||
+                            !typeLength.Equals(currentTypeLength)) {
                             currentTupletNumber = firstNote.NoteTuplet.TupletLabelNumber;
+                            currentTypeLength = typeLength;
                             consecutiveTupletCount = 0;
                         }
                         currentTuplet = firstNote.NoteTuplet;
@@ -1164,12 +1168,6 @@ export abstract class MusicSheetCalculator {
                         if (consecutiveTupletCount <= this.rules.TupletNumberMaxConsecutiveRepetitions) {
                             firstNote.NoteTuplet.RenderTupletNumber = true; // need to re-activate after re-render when it was set to false
                         }
-                        if (consecutiveTupletCount === this.rules.TupletNumberMaxConsecutiveRepetitions && this.rules.TupletNumberAlwaysDisableAfterFirstMax) {
-                            if (!disabledPerVoice[voice.VoiceId][currentTupletNumber]) {
-                                disabledPerVoice[voice.VoiceId][currentTupletNumber] = {};
-                            }
-                            disabledPerVoice[voice.VoiceId][currentTupletNumber][typeLength.RealValue] = true;
-                        }
                         if (consecutiveTupletCount > this.rules.TupletNumberMaxConsecutiveRepetitions) {
                             firstNote.NoteTuplet.RenderTupletNumber = false;
                             if (this.rules.TupletNumberAlwaysDisableAfterFirstMax) {

+ 2 - 1
src/MusicalScore/Graphical/SkyBottomLineBatchCalculator.ts

@@ -8,6 +8,7 @@ import { StaffLine } from "./StaffLine";
 import { VexFlowMeasure } from "./VexFlow/VexFlowMeasure";
 import { WebGLSkyBottomLineBatchCalculatorBackend } from "./WebGLSkyBottomLineBatchCalculatorBackend";
 import log from "loglevel";
+import { CollectionUtil } from "../../Util/CollectionUtil";
 
 interface IBatchEntry {
     skyBottomLineCalculator: SkyBottomLineCalculator;
@@ -46,7 +47,7 @@ export class SkyBottomLineBatchCalculator {
 
         this.batches = new Map<EngravingRules, IBatch>();
         for (const [rules, batchEntryArray] of batchEntryArrayList.entries()) {
-            const measures: VexFlowMeasure[] = batchEntryArray.map(entry => entry.measures).flat();
+            const measures: VexFlowMeasure[] = CollectionUtil.flat(batchEntryArray.map(entry => entry.measures));
             const backend: SkyBottomLineBatchCalculatorBackend = ((): SkyBottomLineBatchCalculatorBackend => {
                 if (preferredBackend === SkyBottomLineBatchCalculatorBackendType.Plain) {
                     return new PlainSkyBottomLineBatchCalculatorBackend(rules, measures).initialize();

+ 9 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -63,6 +63,7 @@ import { Pedal } from "../../VoiceData/Expressions/ContinuousExpressions/Pedal";
 import { VexFlowPedal } from "./VexFlowPedal";
 import { MusicSymbol } from "../MusicSymbol";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
+import { CollectionUtil } from "../../../Util/CollectionUtil";
 import { WavyLine } from "../../VoiceData/Expressions/ContinuousExpressions/WavyLine";
 import { VexflowVibratoBracket } from "./VexflowVibratoBracket";
 
@@ -1080,6 +1081,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       }
       // calculate GraphicalPedal and RelativePositions
       const graphicalPedal: VexFlowPedal = new VexFlowPedal(pedal, startStaffLine.PositionAndShape, false, openEnd);
+      graphicalPedal.setEndsStave(endMeasure, endTimeStamp); // unfortunately this can't already be checked in ExpressionReader
       // calculate RelativePosition
       let startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
       if (!startStaffEntry) { // fix for rendering range set
@@ -1093,6 +1095,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       if (!graphicalPedal.setStartNote(startStaffEntry)){
         return;
       }
+      graphicalPedal.setBeginsStave(graphicalPedal.startNote.isRest(), startTimeStamp);
 
       if (endStaffLine !== startStaffLine) {
         if(graphicalPedal.pedalSymbol === MusicSymbol.PEDAL_SYMBOL){
@@ -1105,6 +1108,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           const nextPedalFirstMeasure: GraphicalMeasure = endStaffLine.Measures[0];
           // pedal starts on the first measure
           const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape);
+          graphicalPedal.setEndsStave(endMeasure, endTimeStamp);
           const firstNote: GraphicalStaffEntry = nextPedalFirstMeasure.staffEntries[0];
           if(!nextPedal.setStartNote(firstNote)){
             return;
@@ -1143,6 +1147,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
               currentCount++;
               // pedal starts on the first measure
               const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape, true, nextOpenEnd);
+              graphicalPedal.setEndsStave(endMeasure, endTimeStamp);
               nextPedal.ChangeBegin = false;
               if(nextChangeEndFromParent){
                 nextPedal.ChangeEnd = pedal.ChangeEnd;
@@ -1156,7 +1161,10 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
               //If the end measure's staffline is the ending staffline, this endMeasure is the end of the pedal
               if (endMeasure.ParentStaffLine === nextPedalStaffline) {
                 nextPedalLastMeasure = endMeasure;
+                nextPedal.setEndMeasure(endMeasure);
                 lastNote = endStaffEntry;
+              } else {
+                nextPedal.setEndMeasure(nextPedalStaffline.Measures.last());
               }
               if(!nextPedal.setStartNote(firstNote)){
                 break;
@@ -1599,7 +1607,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   }
 
   protected calculateSkyBottomLines(): void {
-    const staffLines: StaffLine[] = this.musicSystems.map(musicSystem => musicSystem.StaffLines).flat();
+    const staffLines: StaffLine[] = CollectionUtil.flat(this.musicSystems.map(musicSystem => musicSystem.StaffLines));
     //const numMeasures: number = staffLines.map(staffLine => staffLine.Measures.length).reduce((a, b) => a + b, 0);
     let numMeasures: number = 0; // number of graphical measures that are rendered
     for (const staffline of staffLines) {

+ 23 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowPedal.ts

@@ -7,6 +7,7 @@ import { Pedal } from "../../VoiceData/Expressions/ContinuousExpressions/Pedal";
 import { MusicSymbol } from "../MusicSymbol";
 import { GraphicalMeasure } from "../GraphicalMeasure";
 import { VexFlowMeasure } from "./VexFlowMeasure";
+import { Fraction } from "../../../Common/DataObjects/Fraction";
 /**
  * The vexflow adaptation of a pedal marking
  */
@@ -129,12 +130,31 @@ export class VexFlowPedal extends GraphicalPedal {
         pedalMarking.setLine(this.line);
         pedalMarking.setCustomText(this.DepressText, this.ReleaseText);
         //If our end note is at the end of a stave, set that value
-        if(this.endVfVoiceEntry?.parentStaffEntry === this.endVfVoiceEntry?.parentStaffEntry?.parentMeasure?.staffEntries.last() ||
-        !this.endVfVoiceEntry){
-                (pedalMarking as any).EndsStave = true;
+        if(!this.endVfVoiceEntry ||
+            this.getPedal.EndsStave
+            //|| this.endVfVoiceEntry?.parentStaffEntry === this.endVfVoiceEntry?.parentStaffEntry?.parentMeasure?.staffEntries.last()
+            //   the above condition prevents the ability to stop BEFORE the last staff entry.
+            //   see test_pedal_stop_before_last_staffentry and OSMD Function test - Color, compare with Beethoven - Geliebte (pedal symbols vs lines)
+        ){
+            (pedalMarking as any).EndsStave = true;
+        }
+        if (this.getPedal.BeginsStave) {
+            (pedalMarking as any).BeginsStave = true;
         }
         (pedalMarking as any).ChangeBegin = this.ChangeBegin;
         (pedalMarking as any).ChangeEnd = this.ChangeEnd;
         return pedalMarking;
     }
+
+    public setEndsStave(endMeasure: GraphicalMeasure, endTimeStamp: Fraction): void {
+        if (endTimeStamp?.gte(endMeasure.parentSourceMeasure.Duration)) {
+            this.getPedal.EndsStave = true;
+        }
+    }
+
+    public setBeginsStave(isRest: boolean, startTimeStamp: Fraction): void {
+        if (isRest && startTimeStamp.RealValue === 0) {
+            this.getPedal.BeginsStave = true;
+        }
+    }
 }

+ 2 - 1
src/MusicalScore/Graphical/WebGLSkyBottomLineBatchCalculatorBackend.ts

@@ -11,6 +11,7 @@ import {
 import vertexShaderSource from "./Shaders/VertexShader.glsl";
 import fragmentShaderSource from "./Shaders/FragmentShader.glsl";
 import log from "loglevel";
+import { CollectionUtil } from "../../Util/CollectionUtil";
 
 // WebGL helper functions
 
@@ -58,7 +59,7 @@ function createVertexBuffer(gl: WebGLRenderingContext, program: WebGLShader, att
     }
 
     gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
-    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices.flat()), gl.STATIC_DRAW);
+    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(CollectionUtil.flat(vertices)), gl.STATIC_DRAW);
     gl.useProgram(program);
 
     const positionAttributeLocation: number = gl.getAttribLocation(program, attributeName);

+ 21 - 6
src/MusicalScore/MusicParts/MusicPartManagerIterator.ts

@@ -252,14 +252,13 @@ export class MusicPartManagerIterator {
     // move to previous
     public moveToPrevious(): void {
         // this.forwardJumpOccurred = this.backJumpOccurred = false;
-        if (this.frontReached) { return; }
+        if (this.frontReached) {
+            return;
+        }
         if (this.currentVoiceEntries) {
             this.currentVoiceEntries = [];
         }
-        if (this.currentTimeStamp.RealValue < this.musicSheet.SourceMeasures.length) {
-            this.recursiveMoveBack();
-            return;
-        }
+        this.recursiveMoveBack();
     }
 
     public moveToPreviousVisibleVoiceEntry(notesOnly: boolean): void {
@@ -274,13 +273,17 @@ export class MusicPartManagerIterator {
     public moveToNext(): void {
         this.forwardJumpOccurred = this.backJumpOccurred = false;
         if (this.endReached) { return; }
-        if (this.frontReached) { this.frontReached = false; }
+        if (this.frontReached) {
+            this.frontReached = false;
+            this.currentVoiceEntryIndex = -1;
+        }
         if (this.currentVoiceEntries) {
             this.currentVoiceEntries.length = 0;
         }
         this.recursiveMove();
         if (!this.currentMeasure) {
             this.currentTimeStamp = new Fraction(99999, 1);
+            this.currentMeasure = this.musicSheet.SourceMeasures.last();
         }
 
         if (this.CurrentTempoChangingExpression !== undefined) {
@@ -550,6 +553,12 @@ export class MusicPartManagerIterator {
             this.currentVerticalContainerInMeasureTimestamp = currentContainer.Timestamp;
             this.currentTimeStamp = Fraction.plus(this.currentMeasure.AbsoluteTimestamp, this.currentVerticalContainerInMeasureTimestamp);
             this.activateCurrentDynamicOrTempoInstructions();
+            // re-check endReached
+            const selectionEnd: Fraction = this.musicSheet.SelectionEnd;
+            if (selectionEnd && this.currentTimeStamp.lt(selectionEnd)) {
+                this.endReached = false;
+            }
+            this.currentMeasureIndex = this.musicSheet.SourceMeasures.indexOf(this.CurrentMeasure);
             return;
         }
         else if (this.currentVoiceEntryIndex === 0  && this.currentMeasureIndex !== 0) {
@@ -562,10 +571,16 @@ export class MusicPartManagerIterator {
             this.currentVoiceEntryIndex = m.VerticalSourceStaffEntryContainers.length-1;
             this.currentTimeStamp = currentContainer.Timestamp;
             this.activateCurrentDynamicOrTempoInstructions();
+            // re-check endReached
+            const selectionEnd: Fraction = this.musicSheet.SelectionEnd;
+            if (selectionEnd && this.currentTimeStamp.lt(selectionEnd)) {
+                this.endReached = false;
+            }
             return;
         }
         // we reached the beginning
         this.frontReached = true;
+        this.currentTimeStamp = new Fraction(-1, 1);
     }
 
     private recursiveMove(): void {

+ 2 - 1
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -553,7 +553,8 @@ export class InstrumentReader {
               expressionReader.readExpressionParameters(
                 xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, true
               );
-              expressionReader.addPedalMarking(xmlNode, this.currentMeasure, previousFraction.clone());
+              expressionReader.addPedalMarking(xmlNode, this.currentMeasure, currentFraction.clone());
+              // pedal end in OSMD and Vexflow means end BEFORE timestamp, so currentFraction instead of previousFraction needs to be used.
              }
              expressionReader.readExpressionParameters(
                xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false

+ 4 - 3
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -391,7 +391,7 @@ export class ExpressionReader {
                         break;
                         case "stop":
                             if (this.openPedal) {
-                                this.endOpenPedal(currentMeasure);
+                                this.endOpenPedal(currentMeasure, endTimestamp);
                             }
                         break;
                         case "change":
@@ -422,8 +422,9 @@ export class ExpressionReader {
             }
         }
     }
-    private endOpenPedal(currentMeasure: SourceMeasure): void {
-        this.createNewMultiExpressionIfNeeded(currentMeasure, -1);
+    private endOpenPedal(currentMeasure: SourceMeasure, endTimeStamp?: Fraction): void {
+        this.createNewMultiExpressionIfNeeded(currentMeasure, -1, endTimeStamp);
+        // unfortunately currentMeasure.Duration doesn't exist here yet, so we can't check pedal.EndsStave
         this.getMultiExpression.PedalEnd = this.openPedal;
         this.openPedal.ParentEndMultiExpression = this.getMultiExpression;
         this.openPedal = undefined;

+ 2 - 0
src/MusicalScore/VoiceData/Expressions/ContinuousExpressions/Pedal.ts

@@ -13,6 +13,8 @@ export class Pedal {
     public ParentEndMultiExpression: MultiExpression;
     public ChangeEnd: boolean = false;
     public ChangeBegin: boolean = false;
+    public EndsStave: boolean = false;
+    public BeginsStave: boolean = false;
 
     public get IsLine(): boolean {
         return this.line;

+ 13 - 2
src/OpenSheetMusicDisplay/Cursor.ts

@@ -214,7 +214,15 @@ export class Cursor implements IPlaybackListener {
 
     // TODO when measure draw range (drawUpToMeasureNumber) was changed, next/update can fail to move cursor. but of course it can be reset before.
 
-    const voiceEntries: VoiceEntry[] = iterator.CurrentVisibleVoiceEntries();
+    let voiceEntries: VoiceEntry[] = iterator.CurrentVisibleVoiceEntries();
+    if (iterator.FrontReached && voiceEntries.length === 0) {
+      // workaround: show position 0 instead of nothing when going before start of sheet.
+      //   The current cursor behavior before start of the sheet is not well defined, or at least a matter of preference.
+      //   There are reasonable alternatives, like highlighting the beginning (first vertical line or clef) of the measure.
+      iterator.moveToNext();
+      voiceEntries = iterator.CurrentVisibleVoiceEntries();
+      iterator.moveToPrevious();
+    }
     if (iterator.EndReached || !iterator.CurrentVoiceEntries || voiceEntries.length === 0) {
       return;
     }
@@ -441,7 +449,10 @@ export class Cursor implements IPlaybackListener {
    *  This is only necessary if using PageFormat (multiple pages).
    */
   public updateCurrentPage(): number {
-    const timestamp: Fraction = this.Iterator.currentTimeStamp;
+    let timestamp: Fraction = this.iterator.currentTimeStamp;
+    if (timestamp.RealValue < 0) {
+      timestamp = new Fraction(0, 0);
+    }
     for (const page of this.graphic.MusicPages) {
       const lastSystemTimestamp: Fraction = page.MusicSystems.last().GetSystemsLastTimeStamp();
       if (lastSystemTimestamp.gt(timestamp)) {

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

+ 7 - 0
src/Util/CollectionUtil.ts

@@ -60,6 +60,13 @@ export class CollectionUtil {
         return array[array.length - 1];
     }
 
+    /** Array.flat(), introduced in ES2019, polyfilled here to stick with ES2017 target in tsconfig.json.
+     *  Performance tests: https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/1299#issuecomment-1399062038
+     */
+    public static flat(array: any[]): any {
+        return [].concat(...array);
+    }
+
     /**
      * Iterates through a dictionary and calls iterationFunction.
      * If iterationFunction returns true the key gets stored.

+ 3 - 0
src/VexFlowPatch/src/pedalmarking.js

@@ -171,6 +171,9 @@ export class PedalMarking extends Element {
       if (note) {
         //default to note head begin
         x = note.getNoteHeadBeginX();
+        if (this.BeginsStave) {
+          x = note.getStave().getNoteStartX();
+        }
       } else {
         x = this.endStave.end_x + this.endStaveAddedWidth;
       }

+ 22 - 1
test/Common/OSMD/OSMD_Test.ts

@@ -2,7 +2,7 @@ import chai = require("chai");
 import { OpenSheetMusicDisplay } from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 import { TestUtils } from "../../Util/TestUtils";
 import { VoiceEntry, Instrument, Note, Staff, Voice, GraphicalStaffEntry, GraphicalNote,
-            Fraction, Pitch, AccidentalEnum, DrawingParametersEnum, IOSMDOptions } from "../../../src";
+            Fraction, Pitch, AccidentalEnum, DrawingParametersEnum, IOSMDOptions, Cursor } from "../../../src";
 
 describe("OpenSheetMusicDisplay Main Export", () => {
     let container1: HTMLElement;
@@ -314,6 +314,27 @@ describe("OpenSheetMusicDisplay Main Export", () => {
             ).catch(done);
         });
 
+        describe("next() and previous()", () => {
+            it("is able to advance past end and beginning of sheet", () => {
+                const cursor: Cursor = opensheetmusicdisplay.cursors[0];
+                chai.expect(cursor.NotesUnderCursor().length).to.greaterThanOrEqual(1);
+                cursor.previous(); // do previous from first timestamp in sheet ("beyond beginning")
+                chai.expect(cursor.NotesUnderCursor().length).to.equal(0);
+                cursor.next();
+                chai.expect(cursor.NotesUnderCursor().length).to.greaterThanOrEqual(1);
+                chai.expect(cursor.Iterator.currentTimeStamp.RealValue).to.equal(0);
+                // go past end of sheet
+                for (let i: number = 1; i <= 260; i++) {
+                    cursor.next(); // go past end of sheet: after 258 times in Clementi 36/1/1, the last timestamp is reached
+                }
+                chai.expect(cursor.Iterator.EndReached).to.equal(true);
+                // try to go back again after going beyond end of sheet
+                cursor.previous();
+                cursor.previous();
+                chai.expect(cursor.Iterator.EndReached).to.equal(false);
+            });
+        });
+
         describe("get AllVoicesUnderCursor", () => {
             it("retrieves all voices under cursor", () => {
                 const voiceEntries: VoiceEntry[] = opensheetmusicdisplay.cursors[0].VoicesUnderCursor();

+ 173 - 0
test/data/test_pedal_stop_before_last_staffentry.musicxml

@@ -0,0 +1,173 @@
+<?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_pedal_stop_before_last_staffentry</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-01-02</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="no"/>
+      <supports element="print" attribute="new-system" type="no"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">Pedal Demo</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="429.35">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>65.90</left-margin>
+            <right-margin>533.32</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        <staff-layout number="2">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <staves>2</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        <clef number="2">
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <staff>1</staff>
+        </note>
+      <note default-x="251.09" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="334.89" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <direction placement="below">
+        <direction-type>
+          <pedal type="start" line="yes" default-y="-65.00"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <note default-x="83.49" default-y="-130.00">
+        <pitch>
+          <step>C</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>2</staff>
+        </note>
+      <note default-x="167.29" default-y="-125.00">
+        <pitch>
+          <step>D</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <staff>2</staff>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <pedal type="stop" line="yes"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <note>
+        <rest/>
+        <duration>2</duration>
+        <voice>5</voice>
+        <type>half</type>
+        <staff>2</staff>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 232 - 0
test/data/test_pedal_systembreak.musicxml

@@ -0,0 +1,232 @@
+<?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_pedal_systembreak</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-01-02</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="no"/>
+      <supports element="print" attribute="new-system" type="no"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">Pedal Demo 2</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="962.67">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>65.90</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        <staff-layout number="2">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <staves>2</staves>
+        <clef number="1">
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        <clef number="2">
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <note default-x="86.99" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="305.46" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="523.93" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="742.40" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <direction placement="below">
+        <direction-type>
+          <pedal type="start" line="yes" default-y="-65.00"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <staff>2</staff>
+        </note>
+      </measure>
+    <measure number="2" width="335.09">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>15.90</left-margin>
+            <right-margin>677.58</right-margin>
+            </system-margins>
+          <system-distance>237.50</system-distance>
+          </system-layout>
+        <staff-layout number="2">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <note default-x="61.36" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="127.13" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="192.90" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <staff>1</staff>
+        </note>
+      <note default-x="258.67" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <staff>2</staff>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <pedal type="stop" line="yes"/>
+          </direction-type>
+        <staff>2</staff>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 219 - 0
test/data/test_slur_SlurPlacementFromXML_undefined_in_XML.musicxml

@@ -0,0 +1,219 @@
+<?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_slur_SlurPlacementFromXML_undefined_in_XML</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-01-20</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">test_slur_SlurPlacementFromXML_without_placement_value_in_xml</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>brace</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="978.57">
+      <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>4</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="80.72" default-y="5.00">
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <slur type="start" number="1"/>
+          </notations>
+        </note>
+      <note default-x="162.87" default-y="10.00">
+        <pitch>
+          <step>A</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      <note default-x="245.02" default-y="15.00">
+        <pitch>
+          <step>B</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="327.17" default-y="10.00">
+        <pitch>
+          <step>A</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        </note>
+      <note default-x="409.31" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <slur type="start" number="1"/>
+          </notations>
+        </note>
+      <note default-x="491.46" default-y="5.00">
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      <note default-x="573.61" default-y="15.00">
+        <pitch>
+          <step>B</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        <notations>
+          <articulations>
+            <staccato/>
+            </articulations>
+          </notations>
+        </note>
+      <note default-x="655.76" default-y="5.00">
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <articulations>
+            <staccato/>
+            </articulations>
+          </notations>
+        </note>
+      <note>
+        <rest/>
+        <duration>8</duration>
+        <voice>1</voice>
+        <type>half</type>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 451 - 0
test/data/test_tuplet_consecutive_respect_notelength.musicxml

@@ -0,0 +1,451 @@
+<?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_tuplet_consecutive_respect_notelength</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-01-20</encoding-date>
+      <supports element="accidental" type="yes"/>
+      <supports element="beam" type="yes"/>
+      <supports element="print" attribute="new-page" type="yes" value="yes"/>
+      <supports element="print" attribute="new-system" type="yes" value="yes"/>
+      <supports element="stem" type="yes"/>
+      </encoding>
+    </identification>
+  <defaults>
+    <scaling>
+      <millimeters>7</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1697.14</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="even">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>85.7143</left-margin>
+        <right-margin>85.7143</right-margin>
+        <top-margin>85.7143</top-margin>
+        <bottom-margin>85.7143</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Edwin" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">test_tuplet_consecutive_respect_notelength</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>brace</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="385.63">
+      <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>3</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="80.72" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="115.97" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="151.21" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="186.46" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="221.70" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="256.95" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="292.20" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          <normal-type>eighth</normal-type>
+          </time-modification>
+        <stem>down</stem>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        </note>
+      <note default-x="348.59" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          <normal-type>eighth</normal-type>
+          </time-modification>
+        <stem>down</stem>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      </measure>
+    <measure number="2" width="335.16">
+      <note default-x="13.00" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="48.60" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="84.19" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="119.79" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="155.38" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="190.98" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="226.57" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="262.17" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="297.76" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      </measure>
+    <measure number="3" width="257.78">
+      <note default-x="13.00" default-y="0.00">
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="55.18" default-y="0.00">
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="97.36" default-y="0.00">
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="139.53" default-y="5.00">
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 77 - 26
test/data/test_tuplet_consecutive_simple_alwaysdisable.musicxml

@@ -7,7 +7,7 @@
   <identification>
     <encoding>
       <software>MuseScore 3.6.2</software>
-      <encoding-date>2022-08-11</encoding-date>
+      <encoding-date>2023-01-20</encoding-date>
       <supports element="accidental" type="yes"/>
       <supports element="beam" type="yes"/>
       <supports element="print" attribute="new-page" type="yes" value="yes"/>
@@ -41,7 +41,7 @@
     </defaults>
   <credit page="1">
     <credit-type>title</credit-type>
-    <credit-words default-x="600" default-y="1611.43" justify="center" valign="top" font-size="22">tuplet_consecutive_test_simple</credit-words>
+    <credit-words default-x="600" default-y="1611.86" justify="center" valign="top" font-size="22">tuplet_consecutive_test_simple</credit-words>
     </credit>
   <part-list>
     <score-part id="P1">
@@ -60,11 +60,11 @@
       </score-part>
     </part-list>
   <part id="P1">
-    <measure number="1" width="529.40">
+    <measure number="1" width="547.15">
       <print>
         <system-layout>
           <system-margins>
-            <left-margin>65.90</left-margin>
+            <left-margin>64.90</left-margin>
             <right-margin>0.00</right-margin>
             </system-margins>
           <top-system-distance>170.00</top-system-distance>
@@ -126,7 +126,7 @@
           <tuplet type="start" bracket="no"/>
           </notations>
         </note>
-      <note default-x="139.34" default-y="-135.00">
+      <note default-x="134.81" default-y="-135.00">
         <pitch>
           <step>B</step>
           <octave>2</octave>
@@ -142,7 +142,7 @@
         <staff>2</staff>
         <beam number="1">continue</beam>
         </note>
-      <note default-x="195.20" default-y="-130.00">
+      <note default-x="186.12" default-y="-130.00">
         <pitch>
           <step>C</step>
           <octave>3</octave>
@@ -161,7 +161,7 @@
           <tuplet type="stop"/>
           </notations>
         </note>
-      <note default-x="251.06" default-y="-125.00">
+      <note default-x="237.44" default-y="-125.00">
         <pitch>
           <step>D</step>
           <octave>3</octave>
@@ -180,7 +180,7 @@
           <tuplet type="start" bracket="no"/>
           </notations>
         </note>
-      <note default-x="306.91" default-y="-120.00">
+      <note default-x="288.76" default-y="-120.00">
         <pitch>
           <step>E</step>
           <octave>3</octave>
@@ -196,7 +196,7 @@
         <staff>2</staff>
         <beam number="1">continue</beam>
         </note>
-      <note default-x="362.77" default-y="-125.00">
+      <note default-x="340.08" default-y="-125.00">
         <pitch>
           <step>D</step>
           <octave>3</octave>
@@ -215,19 +215,62 @@
           <tuplet type="stop"/>
           </notations>
         </note>
-      <note default-x="418.63" default-y="-120.00">
+      <note default-x="391.39" default-y="-120.00">
         <pitch>
           <step>E</step>
           <octave>3</octave>
           </pitch>
-        <duration>3</duration>
+        <duration>1</duration>
         <voice>5</voice>
-        <type>quarter</type>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
         <stem>down</stem>
         <staff>2</staff>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="442.71" default-y="-115.00">
+        <pitch>
+          <step>F</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <staff>2</staff>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="494.03" default-y="-120.00">
+        <pitch>
+          <step>E</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>5</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <staff>2</staff>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
         </note>
       </measure>
-    <measure number="2" width="433.27">
+    <measure number="2" width="416.52">
       <note default-x="13.00" default-y="-25.00">
         <pitch>
           <step>A</step>
@@ -248,6 +291,17 @@
           <step>F</step>
           <octave>3</octave>
           </pitch>
+        <duration>3</duration>
+        <voice>5</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        <staff>2</staff>
+        </note>
+      <note default-x="124.05" default-y="-110.00">
+        <pitch>
+          <step>G</step>
+          <octave>3</octave>
+          </pitch>
         <duration>1</duration>
         <voice>5</voice>
         <type>eighth</type>
@@ -262,9 +316,9 @@
           <tuplet type="start" bracket="no"/>
           </notations>
         </note>
-      <note default-x="72.35" default-y="-115.00">
+      <note default-x="180.98" default-y="-105.00">
         <pitch>
-          <step>F</step>
+          <step>A</step>
           <octave>3</octave>
           </pitch>
         <duration>1</duration>
@@ -278,9 +332,9 @@
         <staff>2</staff>
         <beam number="1">continue</beam>
         </note>
-      <note default-x="131.70" default-y="-115.00">
+      <note default-x="237.90" default-y="-110.00">
         <pitch>
-          <step>F</step>
+          <step>G</step>
           <octave>3</octave>
           </pitch>
         <duration>1</duration>
@@ -297,18 +351,15 @@
           <tuplet type="stop"/>
           </notations>
         </note>
-      <note>
-        <rest/>
-        <duration>3</duration>
-        <voice>5</voice>
-        <type>quarter</type>
-        <staff>2</staff>
-        </note>
-      <note>
-        <rest/>
+      <note default-x="294.82" default-y="-105.00">
+        <pitch>
+          <step>A</step>
+          <octave>3</octave>
+          </pitch>
         <duration>3</duration>
         <voice>5</voice>
         <type>quarter</type>
+        <stem>down</stem>
         <staff>2</staff>
         </note>
       <barline location="right">