소스 검색

merge osmd-public 1.8.6: improve lyrics spacing, fix multiline subtitle and composer, crescendo startX in multi-instrument scores, etc

note that you should run npm install again to update to karma-webpack 5.0.1.
see the OSMD changelog for ways to use the old subtitle + composer parsing and changing the lyrics spacing.
sschmidTU 1 년 전
부모
커밋
e33ed4f51a
34개의 변경된 파일1903개의 추가작업 그리고 226개의 파일을 삭제
  1. 3 4
      package.json
  2. 15 0
      src/Common/DataObjects/Pitch.ts
  3. 0 102
      src/KarmaWebpackPatch/lib/karma-webpack/preprocessor.js
  4. 0 34
      src/KarmaWebpackPatch/lib/webpack/plugin.js
  5. 27 7
      src/MusicalScore/Graphical/EngravingRules.ts
  6. 2 0
      src/MusicalScore/Graphical/GraphicalLabel.ts
  7. 7 0
      src/MusicalScore/Graphical/GraphicalNote.ts
  8. 12 7
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  9. 2 1
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  10. 97 31
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  11. 8 0
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  12. 10 3
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  13. 3 0
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  14. 8 7
      src/MusicalScore/Graphical/VexFlow/VexFlowTextMeasurer.ts
  15. 2 0
      src/MusicalScore/Interfaces/ITextMeasurer.ts
  16. 132 0
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  17. 9 0
      src/MusicalScore/VoiceData/Note.ts
  18. 1 1
      src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
  19. 5 1
      src/VexFlowPatch/src/keysignature.js
  20. 191 0
      src/VexFlowPatch/src/timesignature.js
  21. 0 3
      test/data/Debussy_Mandoline.xml
  22. 0 3
      test/data/Land_der_Berge.musicxml
  23. 1 1
      test/data/OSMD_Function_Test_Labels.musicxml
  24. 1 1
      test/data/OSMD_Function_Test_Pedals.musicxml
  25. 0 3
      test/data/OSMD_function_test_chord_tests_various.musicxml
  26. 0 6
      test/data/OSMD_function_test_color.musicxml
  27. 0 6
      test/data/Schubert_An_die_Musik.xml
  28. 0 4
      test/data/test_beam_svg_double.musicxml
  29. 1 1
      test/data/test_octaveshift_notes_shifted_octave_shift_end.musicxml
  30. 139 0
      test/data/test_subtitle_composer_multiline_hanzi.musicxml
  31. 391 0
      test/data/test_wedge_cresc_dim_simultaneous_quartet.musicxml
  32. 477 0
      test/data/test_wedge_crescendo_multi-instrument.musicxml
  33. 193 0
      test/data/test_wedge_decrescendo_crescendo_stop_start.musicxml
  34. 166 0
      test/data/test_wedge_decrescendo_multi-instrument_startX.musicxml

+ 3 - 4
package.json

@@ -1,6 +1,6 @@
 {
   "name": "osmd-extended",
-  "version": "1.8.5",
+  "version": "1.8.6",
   "description": "Private / sponsor exclusive OSMD mirror/audio player.",
   "main": "build/opensheetmusicdisplay.min.js",
   "types": "build/dist/src/index.d.ts",
@@ -10,9 +10,8 @@
     "lint": "npm run eslint",
     "test": "karma start --single-run --no-auto-watch",
     "test:watch": "karma start --no-single-run --auto-watch --browsers ChromeNoSecurity",
-    "prebuild": "npm-run-all prebuildVexflow prebuildKarma prebuildSamplePlayer",
+    "prebuild": "npm-run-all prebuildVexflow prebuildSamplePlayer",
     "prebuildVexflow": "ncp src/VexFlowPatch/src/ node_modules/vexflow/src/",
-    "prebuildKarma": "ncp src/KarmaWebpackPatch/lib/ node_modules/karma-webpack/lib/",
     "prebuildSamplePlayer": "ncp src/SamplePlayerPatch/lib/ node_modules/sample-player/lib/",
     "prepare": "npm run build",
     "build": "npm-run-all lint build:webpack",
@@ -109,7 +108,7 @@
     "karma-firefox-launcher": "^2.1.2",
     "karma-mocha": "^2.0.1",
     "karma-mocha-reporter": "^2.2.5",
-    "karma-webpack": "^5.0.0",
+    "karma-webpack": "^5.0.1",
     "karma-xml2js-preprocessor": "^0.1.0",
     "mocha": "^9.1.3",
     "ncp": "^2.0.0",

+ 15 - 0
src/Common/DataObjects/Pitch.ts

@@ -437,6 +437,21 @@ export class Pitch {
         ", Note: " + this.fundamentalNote + ", octave: " + this.octave.toString();
     }
 
+    /** A short representation of the note like A4 (A, octave 4), Ab5 or C#4. */
+    public ToStringShort(octaveOffset: number = 0): string {
+        let accidentalString: string = Pitch.accidentalVexflow(this.accidental);
+        if (!accidentalString) {
+            accidentalString = "";
+        }
+        const octave: number = this.octave + octaveOffset;
+        return Pitch.getNoteEnumString(this.fundamentalNote) + accidentalString + octave;
+    }
+
+    /** A shortcut getter for ToStringShort that can be useful for debugging. */
+    public get ToStringShortGet(): string {
+        return this.ToStringShort(0); // note that a getter cannot have parameters.
+    }
+
     public OperatorEquals(p2: Pitch): boolean {
         const p1: Pitch = this;
         // if (ReferenceEquals(p1, p2)) {

+ 0 - 102
src/KarmaWebpackPatch/lib/karma-webpack/preprocessor.js

@@ -1,102 +0,0 @@
-const path = require('path');
-
-const glob = require('glob');
-const minimatch = require('minimatch');
-
-const { ensureWebpackFrameworkSet } = require('../karma/validation');
-const { hash } = require('../utils/hash');
-
-const KW_Controller = require('./controller');
-
-function getPathKey(filePath, withExtension = false) {
-  const pathParts = path.parse(filePath);
-  const key = `${pathParts.name}.${hash(filePath)}`;
-  return withExtension ? `${key}${pathParts.ext}` : key;
-}
-
-function configToWebpackEntries(config) {
-  const filteredPreprocessorsPatterns = [];
-  const { preprocessors } = config;
-
-  let files = [];
-  config.files.forEach((fileEntry, i) => {
-    // forcefully disable karma watch as we use webpack watch only
-    config.files[i].watched = false;
-    files = [...files, ...glob.sync(fileEntry.pattern)];
-  });
-
-  Object.keys(preprocessors).forEach((pattern) => {
-    if (preprocessors[pattern].includes('webpack')) {
-      filteredPreprocessorsPatterns.push(pattern);
-    }
-  });
-
-  const filteredFiles = [];
-  files.forEach((filePath) => {
-    filteredPreprocessorsPatterns.forEach((pattern) => {
-      if (minimatch(filePath, pattern)) {
-        filteredFiles.push(filePath);
-      }
-    });
-  });
-
-  const webpackEntries = {};
-  filteredFiles.forEach((filePath) => {
-    webpackEntries[getPathKey(filePath)] = filePath;
-  });
-
-  return webpackEntries;
-}
-
-function KW_Preprocessor(config, emitter) {
-  const controller = new KW_Controller();
-  config.__karmaWebpackController = controller;
-  ensureWebpackFrameworkSet(config);
-
-  // one time setup
-  if (controller.isActive === false) {
-    controller.updateWebpackOptions({
-      entry: configToWebpackEntries(config),
-      watch: config.autoWatch,
-    });
-
-    if (config.webpack.entry) {
-      console.warn(`
-karma-webpack does not currently support custom entries, if this is something you need,
-consider opening an issue.
-ignoring attempt to set the entry option...
-      `);
-      delete config.webpack.entry;
-    }
-
-    controller.updateWebpackOptions(config.webpack);
-    controller.karmaEmitter = emitter;
-  }
-
-  const normalize = (file) => file.replace(/\\/g, '/');
-
-  const transformPath =
-    config.webpack.transformPath ||
-    ((filepath) => {
-      // force *.js files by default
-      const info = path.parse(filepath);
-      return `${path.join(info.dir, info.name)}.js`;
-    });
-
-  return function processFile(content, file, done) {
-    controller.bundle().then(() => {
-      file.path = normalize(file.path); // eslint-disable-line no-param-reassign
-
-      const transformedFilePath = transformPath(getPathKey(file.path, true));
-      const bundleContent = controller.bundlesContent[transformedFilePath];
-
-      file.path = transformedFilePath;
-
-      done(null, bundleContent);
-    });
-  };
-}
-
-KW_Preprocessor.$inject = ['config', 'emitter'];
-
-module.exports = KW_Preprocessor;

+ 0 - 34
src/KarmaWebpackPatch/lib/webpack/plugin.js

@@ -1,34 +0,0 @@
-const fs = require('fs');
-const path = require('path');
-
-class KW_WebpackPlugin {
-  constructor(options) {
-    this.karmaEmitter = options.karmaEmitter;
-    this.controller = options.controller;
-  }
-
-  apply(compiler) {
-    this.compiler = compiler;
-
-    // webpack bundles are finished
-    compiler.hooks.done.tap('KW_WebpackPlugin', (stats) => {
-      // read generated file content and store for karma preprocessor
-      this.controller.bundlesContent = {};
-      stats.toJson().assets.forEach((webpackFileObj) => {
-        const filePath = path.resolve(
-          compiler.options.output.path,
-          webpackFileObj.name
-        );
-        this.controller.bundlesContent[webpackFileObj.name] = fs.readFileSync(
-          filePath,
-          'utf-8'
-        );
-      });
-
-      // karma refresh
-      this.karmaEmitter.refreshFiles();
-    });
-  }
-}
-
-module.exports = KW_WebpackPlugin;

+ 27 - 7
src/MusicalScore/Graphical/EngravingRules.ts

@@ -36,6 +36,12 @@ export class EngravingRules {
     public SheetAuthorHeight: number;
     public SheetCopyrightHeight: number;
     public SheetCopyrightMargin: number;
+    /** Whether to use the (deprecated) OSMD < 1.8.6 way of parsing and displaying subtitles and composer,
+     * which did not read multiple lines from XML credit-words tags.
+     * Option will probably be removed soon.
+     * @deprecated
+     */
+    public SheetComposerSubtitleUseLegacyParsing: boolean;
     public CompactMode: boolean;
     public PagePlacementEnum: PagePlacementEnum;
     public PageHeight: number;
@@ -221,18 +227,25 @@ export class EngravingRules {
     public LyricsExtraXShiftForShortLyrics: number;
     /** Threshold of the lyric entry's width below which the x-shift is applied. Default 1.4. */
     public LyricsExtraXShiftForShortLyricsWidthThreshold: number;
-    /** Whether to enable x padding (to the right) for short notes, see LyricsXPaddingFactorForLongLyrics for the degree. */
-    public LyricsUseXPaddingForShortNotes: boolean;
+    /** Whether to enable x padding (to the right) for notes with long lyrics, see LyricsXPaddingFactorForLongLyrics for the degree.
+     * This helps avoid overlaps and shorten measures, because otherwise the whole measure needs to be stretched to avoid overlaps,
+     * see MaximumLyricsElongationFactor */
+    public LyricsUseXPaddingForLongLyrics: boolean;
     /** How much spacing/padding should be added after notes with long lyrics on short notes
      * (>4 characters on <8th note),
      * so that the measure doesn't need to be elongated too much to avoid lyrics collisions.
-     * Default 0.8 = 8 pixels */
+     * Default 1 = 10 pixels */
     public LyricsXPaddingFactorForLongLyrics: number;
     /** How wide a text needs to be to trigger lyrics padding for short notes.
-     * This is visual width, not number of characters, as e.g. 'zzz' is about as wide as 'iiii'.
+     * This is visual width, not number of characters, as e.g. 'zzz' is wider than 'iii'.
      * Default 3.3.
      */
     public LyricsXPaddingWidthThreshold: number;
+    /** Long notes need less padding than short ones, by default we use 0.7 less padding. */
+    public LyricsXPaddingReductionForLongNotes: number;
+    /** Last note in measure needs less padding because of measure bar and bar start/end padding. */
+    public LyricsXPaddingReductionForLastNoteInMeasure: number;
+    public LyricsXPaddingForLastNoteInMeasure: boolean;
     public VerticalBetweenLyricsDistance: number;
     public HorizontalBetweenLyricsDistance: number;
     public BetweenSyllableMaximumDistance: number;
@@ -395,6 +408,8 @@ export class EngravingRules {
     public RenderCopyright: boolean;
     public RenderPartNames: boolean;
     public RenderPartAbbreviations: boolean;
+    /** Whether two render system labels on page 2+. This doesn't affect the default endless PageFormat. */
+    public RenderSystemLabelsAfterFirstPage: boolean;
     public RenderFingerings: boolean;
     public RenderMeasureNumbers: boolean;
     public RenderMeasureNumbersOnlyAtSystemStart: boolean;
@@ -515,6 +530,7 @@ export class EngravingRules {
         this.SheetAuthorHeight = 2.0;
         this.SheetCopyrightHeight = 1.5;
         this.SheetCopyrightMargin = 2.0;
+        this.SheetComposerSubtitleUseLegacyParsing = false;
 
         // Staff sizing Variables
         this.CompactMode = false;
@@ -732,9 +748,12 @@ export class EngravingRules {
         this.LyricsYMarginToBottomLine = 0.2;
         this.LyricsExtraXShiftForShortLyrics = 0.5; // also see ChordSymbolExtraXShiftForShortChordSymbols, same principle
         this.LyricsExtraXShiftForShortLyricsWidthThreshold = 1.4; // width of '+': 1.12, 'II': 1.33 (benefits from x-shift), 'III': 1.99 (doesn't benefit)
-        this.LyricsUseXPaddingForShortNotes = true;
-        this.LyricsXPaddingFactorForLongLyrics = 0.8;
-        this.LyricsXPaddingWidthThreshold = 3.3;
+        this.LyricsUseXPaddingForLongLyrics = true;
+        this.LyricsXPaddingFactorForLongLyrics = 1.0;
+        this.LyricsXPaddingWidthThreshold = 1.7; // generateImages script with png might need more for 8th notes, e.g. Chloe
+        this.LyricsXPaddingReductionForLongNotes = 0.7;
+        this.LyricsXPaddingReductionForLastNoteInMeasure = 1.2;
+        this.LyricsXPaddingForLastNoteInMeasure = true;
         this.VerticalBetweenLyricsDistance = 0.5;
         this.HorizontalBetweenLyricsDistance = 0.2;
         this.BetweenSyllableMaximumDistance = 10.0;
@@ -843,6 +862,7 @@ export class EngravingRules {
         this.RenderCopyright = false;
         this.RenderPartNames = true;
         this.RenderPartAbbreviations = true;
+        this.RenderSystemLabelsAfterFirstPage = true;
         this.RenderFingerings = true;
         this.RenderMeasureNumbers = true;
         this.RenderMeasureNumbersOnlyAtSystemStart = false;

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

@@ -74,6 +74,8 @@ export class GraphicalLabel extends Clickable {
             MusicSheetCalculator.TextMeasurer.computeTextWidthToHeightRatio(
                line, this.Label.font, this.Label.fontStyle, this.label.fontFamily);
             const currWidth: number = this.Label.fontHeight * widthToHeightRatio;
+            // const currWidth: number = MusicSheetCalculator.TextMeasurer.computeTextWidth(
+            //     line, this.Label.font, this.Label.fontStyle, this.label.fontFamily);
             maxWidth = Math.max(maxWidth, currWidth);
             // here push only text and width of the text:
             this.TextLines.push({text: line, xOffset: 0, width: currWidth});

+ 7 - 0
src/MusicalScore/Graphical/GraphicalNote.ts

@@ -71,4 +71,11 @@ export class GraphicalNote extends GraphicalObject {
     public static FromNote(note: Note, rules: EngravingRules): GraphicalNote {
       return rules.NoteToGraphicalNoteMap.getValue(note.NoteToGraphicalNoteObjectId);
     }
+
+    public ToStringShort(octaveOffset: number = 0): string {
+      return this.sourceNote?.ToStringShort(octaveOffset);
+    }
+    public get ToStringShortGet(): string {
+      return this.ToStringShort(0);
+    }
 }

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

@@ -312,15 +312,16 @@ export abstract class MusicSheetCalculator {
         // const maxInstructionsLength: number = this.rules.MaxInstructionsConstValue;
         if (this.graphicalMusicSheet.MeasureList.length > 0) {
             /** list of vertically ordered measures belonging to one bar */
-            let measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[0];
-            let minimumStaffEntriesWidth: number = this.calculateMeasureXLayout(measures);
-            minimumStaffEntriesWidth = this.calculateMeasureWidthFromStaffEntries(measures, minimumStaffEntriesWidth);
-            MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minimumStaffEntriesWidth);
+            // let measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[0];
+            // let minimumStaffEntriesWidth: number = this.calculateMeasureXLayout(measures);
+            // minimumStaffEntriesWidth = this.calculateMeasureWidthFromStaffEntries(measures, minimumStaffEntriesWidth);
+            // MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minimumStaffEntriesWidth);
             // minLength = minimumStaffEntriesWidth * 1.2 + maxInstrNameLabelLength + maxInstructionsLength;
             let maxWidth: number = 0;
+            let measures: GraphicalMeasure[];
             for (let i: number = 0; i < this.graphicalMusicSheet.MeasureList.length; i++) {
                 measures = this.graphicalMusicSheet.MeasureList[i];
-                minimumStaffEntriesWidth = this.calculateMeasureXLayout(measures);
+                let minimumStaffEntriesWidth: number = this.calculateMeasureXLayout(measures);
                 minimumStaffEntriesWidth = this.calculateMeasureWidthFromStaffEntries(measures, minimumStaffEntriesWidth);
                 if (minimumStaffEntriesWidth > maxWidth) {
                     maxWidth = minimumStaffEntriesWidth;
@@ -1422,10 +1423,10 @@ export abstract class MusicSheetCalculator {
         const startCollideBox: BoundingBox =
             this.dynamicExpressionMap.get(graphicalContinuousDynamic.ContinuousDynamic.StartMultiExpression.AbsoluteTimestamp.RealValue);
         if (startCollideBox) {
-            startPosInStaffline.x = startCollideBox.RelativePosition.x + this.rules.WedgeHorizontalMargin;
             if ((startCollideBox.DataObject as any).ParentStaffLine === staffLine) {
                 // TODO the dynamicExpressionMap doesn't distinguish between staffLines, so we may react to a different staffline otherwise
                 //   so the more fundamental solution would be to fix dynamicExpressionMap mapping across stafflines.
+                startPosInStaffline.x = startCollideBox.RelativePosition.x + this.rules.WedgeHorizontalMargin;
                 startPosInStaffline.x += startCollideBox.BorderMarginRight;
             }
         }
@@ -2403,12 +2404,16 @@ export abstract class MusicSheetCalculator {
         }
         if (this.graphicalMusicSheet.Subtitle && this.rules.RenderTitle && this.rules.RenderSubtitle) {
             const subtitle: GraphicalLabel = this.graphicalMusicSheet.Subtitle;
-            //subtitle.PositionAndShape.Parent = firstStaffLine.PositionAndShape;
+            // subtitle.PositionAndShape.Parent = firstStaffLine.PositionAndShape;
             subtitle.PositionAndShape.Parent = page.PositionAndShape;
             const relative: PointF2D = new PointF2D();
             relative.x = this.graphicalMusicSheet.ParentMusicSheet.pageWidth / 2;
             //relative.x = firstStaffLine.PositionAndShape.RelativePosition.x + firstStaffLine.PositionAndShape.Size.width / 2; // half of first staffline width
             relative.y = this.rules.TitleTopDistance + this.rules.SheetTitleHeight + this.rules.SheetMinimumDistanceBetweenTitleAndSubtitle;
+            const lines: number = subtitle.TextLines?.length;
+            if (lines > 1) { // Don't want to affect existing behavior. but this doesn't check bboxes for clip
+                relative.y += subtitle.PositionAndShape.BorderBottom * (lines - 1) / (lines);
+            }
             subtitle.PositionAndShape.RelativePosition = relative;
             page.Labels.push(subtitle);
         }

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

@@ -327,7 +327,8 @@ export abstract class MusicSheetDrawer {
         for (const systemLine of musicSystem.SystemLines) {
             this.drawSystemLineObject(systemLine);
         }
-        if (musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0]) {
+        if (this.rules.RenderSystemLabelsAfterFirstPage ||
+            musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0]) {
             for (const label of musicSystem.Labels) {
                 label.SVGNode = this.drawLabel(label, <number>GraphicalLayers.Notes);
             }

+ 97 - 31
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -32,6 +32,7 @@ import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
 import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
 import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
+import { GraphicalMeasure } from "../GraphicalMeasure";
 
 /**
  * Helper class, which contains static methods which actually convert
@@ -467,53 +468,118 @@ export class VexFlowConverter {
             (vfnote as any).stagger_same_whole_notes = rules.StaggerSameWholeNotes;
             //   it would be nice to only save this once, not for every note, but has to be accessible in stavenote.js
             const lyricsEntries: GraphicalLyricEntry[] = gve.parentStaffEntry.LyricsEntries;
-            if (rules.RenderLyrics && rules.LyricsUseXPaddingForShortNotes && lyricsEntries.length > 0) {
+
+            let nextOrCloseNoteHasLyrics: boolean = true;
+            let extraExistingPadding: number = 0;
+            if (lyricsEntries.length > 0 &&
+                rules.RenderLyrics &&
+                rules.LyricsUseXPaddingForLongLyrics
+            ) { // if these conditions don't apply, we don't need the following calculation
+                // don't add padding if next note or close note (within quarter distance) has no lyrics
+                //   usually checking the last note is enough, but
+                //   sometimes you get e.g. a 16th with lyrics, one without lyrics, then one with lyrics again,
+                //   easily causing an overlap as well
+                //   the overlap is fixed by measure elongation, but leads to huge measures (see EngravingRule MaximumLyricsElongationFactor)
+                const startingGMeasure: GraphicalMeasure = gve.parentStaffEntry.parentMeasure;
+                const startingSEIndex: number = startingGMeasure.staffEntries.indexOf(gve.parentStaffEntry);
+                // const staffEntries: VoiceEntry[] = gve.parentVoiceEntry.ParentVoice.VoiceEntries;
+                //   unfortunately the voice entries apparently don't include rests, so they would be ignored
+                const staffEntriesToCheck: GraphicalStaffEntry [] = [];
+                for (let seIndex: number = startingSEIndex + 1; seIndex < startingGMeasure.staffEntries.length; seIndex++) {
+                    const se: GraphicalStaffEntry = startingGMeasure.staffEntries[seIndex];
+                    if (se.graphicalVoiceEntries[0]) {
+                        staffEntriesToCheck.push(se);
+                    }
+                }
+                // // also check next measure:
+                // //   problem: hard to get the next measure object here. (might need to put .nextMeasure into GraphicalMeasure)
+                // const stafflineMeasures: GraphicalMeasure[] = startingGMeasure.ParentStaffLine.Measures;
+                // const measureIndexInStaffline: number = stafflineMeasures.indexOf(startingGMeasure);
+                // if (measureIndexInStaffline + 1 < stafflineMeasures.length) {
+                //     const nextMeasure: GraphicalMeasure = stafflineMeasures[measureIndexInStaffline + 1];
+                //     for (const se of nextMeasure.staffEntries) {
+                //         staffEntriesToCheck.push(se);
+                //     }
+                // }
+                let totalDistanceFromFirstNote: Fraction;
+                let lastTimestamp: Fraction = gve.parentStaffEntry.relInMeasureTimestamp.clone();
+                for (const currentSE of staffEntriesToCheck) {
+                    const currentTimestamp: Fraction = currentSE.relInMeasureTimestamp.clone();
+                    totalDistanceFromFirstNote = Fraction.minus(currentTimestamp, gve.parentVoiceEntry.Timestamp);
+                    if (totalDistanceFromFirstNote.RealValue > 0.25) { // more than a quarter note distance: don't add padding
+                        nextOrCloseNoteHasLyrics = false;
+                        break;
+                    }
+                    if (currentSE.LyricsEntries.length > 0) {
+                        // nextOrCloseNoteHasLyrics = true;
+                        break;
+                    }
+                    const lastDistanceCovered: Fraction = Fraction.minus(currentTimestamp, lastTimestamp);
+                    extraExistingPadding += lastDistanceCovered.RealValue * 32; // for every 8th note in between (0.125), we need around 4 padding less (*4*8)
+                    lastTimestamp = currentTimestamp;
+                }
+                // if the for loop ends without breaking, we are at measure end and assume we need padding
+            }
+            if (rules.RenderLyrics &&
+                rules.LyricsUseXPaddingForLongLyrics &&
+                lyricsEntries.length > 0 &&
+                nextOrCloseNoteHasLyrics) {
                 // VexFlowPatch: add padding to the right for large lyrics,
                 //   so that measure doesn't need to be enlarged too much for spacing
 
                 let hasShortNotes: boolean = false;
-                let paddingMultiplier: number = 1;
+                let padding: number = 0;
                 for (const note of notes) {
                     if (note.sourceNote.Length.RealValue <= 0.125) { // 8th or shorter
                         hasShortNotes = true;
-                        if (note.sourceNote.Length.RealValue <= 0.0625) { // 16th or shorter
-                            paddingMultiplier = 1.7;
-                        }
+                        // if (note.sourceNote.Length.RealValue <= 0.0625) { // 16th or shorter
+                        //     padding += 0.0; // unnecessary by now. what rather needs more padding is eighth notes now.
+                        // }
                         break;
                     }
                 }
 
-                if (hasShortNotes) {
-                    let addPadding: boolean = false;
-                    for (const lyricsEntry of lyricsEntries) {
-                        const widthThreshold: number = rules.LyricsXPaddingWidthThreshold;
-                        // letters like i and l take less space, so we should use the visual width and not number of characters
-                        let currentLyricsWidth: number = lyricsEntry.GraphicalLabel.PositionAndShape.Size.width;
-                        if (lyricsEntry.hasDashFromLyricWord()) {
-                            currentLyricsWidth += 1.5;
+                let addPadding: boolean = false;
+                for (const lyricsEntry of lyricsEntries) {
+                    const widthThreshold: number = rules.LyricsXPaddingWidthThreshold;
+                    // letters like i and l take less space, so we should use the visual width and not number of characters
+                    let currentLyricsWidth: number = lyricsEntry.GraphicalLabel.PositionAndShape.Size.width;
+                    if (lyricsEntry.hasDashFromLyricWord()) {
+                        currentLyricsWidth += 0.5;
+                    }
+                    if (currentLyricsWidth > widthThreshold) {
+                        padding += currentLyricsWidth - widthThreshold;
+                        // if (currentLyricsWidth > 4) {
+                        //     padding *= 1.15; // only maybe needed if LyricsXPaddingFactorForLongLyrics < 1
+                        // }
+                        // check if we need padding because next staff entry also has long lyrics or it's the last note in the measure
+                        const currentStaffEntry: GraphicalStaffEntry = gve.parentStaffEntry;
+                        const measureStaffEntries: GraphicalStaffEntry[] = currentStaffEntry.parentMeasure.staffEntries;
+                        const currentStaffEntryIndex: number = measureStaffEntries.indexOf(currentStaffEntry);
+                        const isLastNoteInMeasure: boolean = currentStaffEntryIndex === measureStaffEntries.length - 1;
+                        if (isLastNoteInMeasure) {
+                            extraExistingPadding += rules.LyricsXPaddingReductionForLastNoteInMeasure; // need less padding
                         }
-                        if (currentLyricsWidth > widthThreshold) {
-                            paddingMultiplier *= currentLyricsWidth / widthThreshold;
-                            // check if we need padding because next staff entry also has long lyrics or it's the last note in the measure
-                            const currentStaffEntry: GraphicalStaffEntry = gve.parentStaffEntry;
-                            const measureStaffEntries: GraphicalStaffEntry[] = currentStaffEntry.parentMeasure.staffEntries;
-                            const currentStaffEntryIndex: number = measureStaffEntries.indexOf(currentStaffEntry);
-                            if (currentStaffEntryIndex === measureStaffEntries.length - 1) {
-                                // addPadding = true; // last note in the measure
-                                // probably unnecessary
-                            } else {
+                        if (!hasShortNotes) {
+                            extraExistingPadding += rules.LyricsXPaddingReductionForLongNotes; // quarter or longer notes need less padding
+                        }
+                        if (rules.LyricsXPaddingForLastNoteInMeasure || !isLastNoteInMeasure) {
+                            if (currentLyricsWidth > widthThreshold + extraExistingPadding) {
                                 addPadding = true;
+                                padding -= extraExistingPadding; // we don't need to add the e.g. 1.2 we already get from measure end padding
+                                // for last note in the measure, this is usually not necessary,
+                                //   but in rare samples with quite long text on the last note it is.
                             }
-                            break;
                         }
-                        // for situations unlikely to cause overlap we shouldn't add padding,
-                        //   e.g. Brooke West sample (OSMD Function Test Chord Symbols) - width ~3.1 in measure 11 on 'ling', no padding needed.
-                        //   though Beethoven - Geliebte has only 8ths in measure 2 and is still problematic,
-                        //   so unfortunately we can't just check if the next note is 16th or less.
-                    }
-                    if (addPadding) {
-                        (vfnote as any).paddingRight = 10 * rules.LyricsXPaddingFactorForLongLyrics * paddingMultiplier;
+                        break; // TODO take the max padding across verses
                     }
+                    // for situations unlikely to cause overlap we shouldn't add padding,
+                    //   e.g. Brooke West sample (OSMD Function Test Chord Symbols) - width ~3.1 in measure 11 on 'ling', no padding needed.
+                    //   though Beethoven - Geliebte has only 8ths in measure 2 and is still problematic,
+                    //   so unfortunately we can't just check if the next note is 16th or less.
+                }
+                if (addPadding) {
+                    (vfnote as any).paddingRight = 10 * rules.LyricsXPaddingFactorForLongLyrics * padding;
                 }
             }
         }

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

@@ -276,6 +276,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
                 if (modifier instanceof VF.KeySignature) {
                     modifier.setStyle({ fillStyle: "#00000000"}); // transparent. requires VexflowPatch
                     // instead of not rendering the key signature, technically, we render it, but with transparent color. this helps layout / x-alignment.
+
+                    // SVG compatibility: also set visibility="hidden".
+                    //   this helps make the key invisible instead of black in some systems like apps, outside the browser. (VexFlowPatch)
+                    (modifier as any).hidden = true;
                     break;
                 }
             }
@@ -305,6 +309,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
             // extends Element is missing from class StaveModifier in DefinitelyTyped definitions, so setStyle isn't found
             timeSig.setStyle({ fillStyle: "#00000000"}); // transparent. requires VexflowPatch
             // instead of not rendering the time signature, technically, we render it, but with transparent color. this helps layout / x-alignment.
+
+            // SVG compatibility: also set visibility="hidden".
+            //   this helps make the modifier invisible instead of black in some systems like apps, outside the browser. (VexFlowPatch)
+            (timeSig as any).hidden = true;
         }
         this.updateInstructionWidth();
     }

+ 10 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -395,8 +395,12 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
       const bBox: BoundingBox = container instanceof GraphicalLyricEntry ? container.GraphicalLabel.PositionAndShape : container.PositionAndShape;
       const labelWidth: number = bBox.Size.width;
-      const staffEntryXPosition: number = (staffEntry as VexFlowStaffEntry).PositionAndShape.RelativePosition.x;
-      let xPosition: number = staffEntryXPosition + bBox.BorderMarginLeft;
+      const vexStaffEntry: VexFlowStaffEntry = staffEntry as VexFlowStaffEntry;
+      // vexStaffEntry.calculateXPosition(false);
+      // const notePosition: number = (staffEntry.graphicalVoiceEntries[0] as VexFlowVoiceEntry).vfStaveNote.getBoundingBox().getX() / unitInPixels;
+      const staffEntryXPosition: number = vexStaffEntry.PositionAndShape.RelativePosition.x;
+      let xPosition: number = staffEntryXPosition + bBox.BorderLeft;
+      // vexStaffEntry.calculateXPosition();
       if (container instanceof GraphicalChordSymbolContainer && container.PositionAndShape.Parent.DataObject instanceof GraphicalMeasure) {
         // the parent is only the measure for whole measure rest notes with chord symbols,
         //   which should start near the beginning of the measure instead of the middle, where there is no desired staffEntry position.
@@ -416,6 +420,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       let currentSpacingToLastContainer: number; // undefined for first container in measure
       if (lastEntryDict[currentContainerIndex]) {
         currentSpacingToLastContainer = xPosition - lastEntryDict[currentContainerIndex].xPosition;
+        // currentSpacingToLastContainer = lastEntryDict[currentContainerIndex].bBox.Size.width;
       }
 
       let currentSpacingToMeasureEnd: number;
@@ -555,6 +560,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         continue;
       }
 
+      // (measure as VexFlowMeasure).format(); // needed to get vexflow bbox / x-position
       elongationFactorForMeasureWidth =
         this.calculateElongationFactorFromStaffEntries(
           measure.staffEntries,
@@ -565,7 +571,8 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
     }
     elongationFactorForMeasureWidth = Math.min(elongationFactorForMeasureWidth, this.rules.MaximumLyricsElongationFactor);
-    // TODO check when this is > 2.0. there seems to be an error here where this is unnecessarily > 2 in Beethoven Geliebte.
+    // console.log(`elongationFactor for measure ${measuresVertical[0]?.MeasureNumber}: ${elongationFactorForMeasureWidth}`);
+    // TODO check when this is > 2.0. See PR #1474
 
     const newMinimumStaffEntriesWidth: number = oldMinimumStaffEntriesWidth * elongationFactorForMeasureWidth;
 

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

@@ -76,6 +76,9 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
             }
         }
         this.PositionAndShape.RelativePosition.x -= lastBorderLeft;
+        // TODO sometimes subtracting lastBorderLeft fixes the x-position for lyrics spacing, sometimes it makes it wrong
+        //   e.g. wrong for Beethoven Geliebte measure 1 ("auf - dem", distance < width of "auf"), correct for measure 3 ("spä - hend")
+        //   this leads to a (lyrics) measure elongation of ~1.3 for measure 1, though it doesn't need any elongation (should be factor 1)
         this.PositionAndShape.calculateBoundingBox();
     }
 

+ 8 - 7
src/MusicalScore/Graphical/VexFlow/VexFlowTextMeasurer.ts

@@ -19,13 +19,6 @@ export class VexFlowTextMeasurer implements ITextMeasurer {
     public fontSizeStandard: number = this.fontSize;
     private rules: EngravingRules;
 
-    /**
-     *
-     * @param text
-     * @param font
-     * @param style
-     * @returns {number}
-     */
     public computeTextWidthToHeightRatio(text: string, font: Fonts, style: FontStyles,
                                          fontFamily: string = undefined,
                                          fontSize: number = this.fontSize): number {
@@ -33,6 +26,14 @@ export class VexFlowTextMeasurer implements ITextMeasurer {
         return this.context.measureText(text).width / fontSize;
     }
 
+    // public computeTextWidth(text: string, font: Fonts, style: FontStyles,
+    //     fontFamily: string = undefined,
+    //     fontSize: number = this.fontSize): number {
+    //     this.context.font = VexFlowConverter.font(fontSize, style, font, this.rules, fontFamily);
+    //     return this.context.measureText(text).width / 10.0;
+    //     // TODO this shifts the title text of sheets to the right for some reason, maybe because of bigger fontSize?
+    // }
+
     public setFontSize(fontSize: number = this.fontSizeStandard): number {
         this.fontSize = fontSize;
         return fontSize;

+ 2 - 0
src/MusicalScore/Interfaces/ITextMeasurer.ts

@@ -6,5 +6,7 @@ export interface ITextMeasurer {
     fontSizeStandard: number;
     computeTextWidthToHeightRatio(text: string, font: Fonts, style: FontStyles,
                                   fontFamily?: string, fontSize?: number): number;
+    // computeTextWidth(text: string, font: Fonts, style: FontStyles,
+    //                  fontFamily?: string, fontSize?: number): number;
     setFontSize(fontSize: number): number;
 }

+ 132 - 0
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -580,6 +580,138 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
     }
 
     private readTitleAndComposerFromCredits(root: IXmlElement): void {
+        if (this.rules.SheetComposerSubtitleUseLegacyParsing) {
+            this.readTitleAndComposerFromCreditsLegacy(root);
+            return;
+        }
+        const systemYCoordinates: number = this.computeSystemYCoordinates(root);
+        if (systemYCoordinates === 0) {
+            return;
+        }
+        // let largestTitleCreditSize: number = 1;
+        let finalTitle: string = undefined;
+        // let largestCreditYInfo: number = 0;
+        let finalSubtitle: string = undefined;
+        // let possibleTitle: string = undefined;
+        let finalComposer: string = undefined;
+        const creditElements: IXmlElement[] = root.elements("credit");
+        for (let idx: number = 0, len: number = creditElements.length; idx < len; ++idx) {
+            const credit: IXmlElement = creditElements[idx];
+            if (!credit.attribute("page")) {
+                return;
+            }
+            if (credit.attribute("page").value === "1") {
+                let creditChildren: IXmlElement[] = undefined;
+                if (credit) {
+                    let isSubtitle: boolean = false;
+                    let isComposer: boolean = false;
+                    const typeChild: IXmlElement = credit.element("credit-type");
+                    if (typeChild?.value === "subtitle") {
+                        isSubtitle = true;
+                    } else if (typeChild?.value === "composer") {
+                        isComposer = true;
+                    }
+                    let isSubtitleOrComposer: boolean = isSubtitle || isComposer;
+
+                    creditChildren = credit.elements("credit-words");
+                    for (const creditChild of creditChildren) {
+                        const creditChildValue: string = creditChild.value?.trim();
+                        if (creditChildValue === "Copyright ©") {
+                            continue; // this seems to be a MuseScore default, useless
+                        }
+                        const creditJustify: string = creditChild.attribute("justify")?.value;
+                        if (creditJustify === "right") {
+                            isComposer = true;
+                            isSubtitleOrComposer = true;
+                        } else if (creditJustify === "center" && finalTitle) {
+                            isSubtitle = true;
+                            isSubtitleOrComposer = true;
+                        }
+                        const creditY: string = creditChild.attribute("default-y")?.value;
+                        // eslint-disable-next-line no-null/no-null
+                        const creditYGiven: boolean = creditY !== undefined && creditY !== null;
+                        const creditYInfo: number = creditYGiven ? parseFloat(creditY) : Number.MIN_VALUE;
+                        if ((creditYGiven && creditYInfo > systemYCoordinates) || isSubtitleOrComposer) {
+                            if (!finalTitle && !isSubtitleOrComposer) {
+                                // only take largest font size label
+                                // const creditSize: string = creditChild.attribute("font-size")?.value;
+                                // if (creditSize) {
+                                //     const titleCreditSizeInt: number = parseFloat(creditSize);
+                                //     if (largestTitleCreditSize < titleCreditSizeInt) {
+                                //         largestTitleCreditSize = titleCreditSizeInt;
+                                //         finalTitle = creditChild.value;
+                                //     }
+                                // }
+                                finalTitle = creditChildValue;
+                                // if (!finalTitle) {
+                                //     finalTitle = creditChild.value;
+                                // } else {
+                                //     finalTitle += "\n" + creditChild.value;
+                                // }
+                            } else if (isComposer || creditJustify === "right") {
+                                if (!finalComposer) {
+                                    finalComposer = creditChildValue;
+                                } else {
+                                    finalComposer += "\n" + creditChildValue;
+                                }
+                            } else if (isSubtitle || creditJustify !== "right" && creditJustify !== "left") {
+                                // if (largestCreditYInfo < creditYInfo) {
+                                //     largestCreditYInfo = creditYInfo;
+                                //     if (possibleTitle) {
+                                //         finalSubtitle = possibleTitle;
+                                //         possibleTitle = creditChild.value;
+                                //     } else {
+                                //         possibleTitle = creditChild.value;
+                                //     }
+                                // } else {
+                                if (finalSubtitle) {
+                                    finalSubtitle += "\n" + creditChildValue;
+                                } else {
+                                    finalSubtitle = creditChildValue;
+                                }
+                                // }
+                            } else if (creditJustify === "left") {
+                                if (!this.musicSheet.Lyricist) {
+                                    this.musicSheet.Lyricist = new Label(creditChildValue);
+                                }
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (!this.musicSheet.Title && finalTitle) {
+            this.musicSheet.Title = new Label(this.trimString(finalTitle));
+        }
+        if (!this.musicSheet.Subtitle && finalSubtitle) {
+            this.musicSheet.Subtitle = new Label(this.trimString(finalSubtitle));
+        }
+        if (finalComposer) {
+            let overrideSheetComposer: boolean = false;
+            if (!this.musicSheet.Composer) {
+                overrideSheetComposer = true;
+            } else {
+                // check if we have more lines in existing composer label
+                //   we should only take the existing label if it has less lines,
+                //   since the credit labels are more likely to be the rendering intention than the metadata
+                const creditComposerLines: number = (finalComposer.match("\n") ?? []).length + 1;
+                const sheetComposerLines: number = (this.musicSheet.Composer.text.match("\n") ?? []).length + 1;
+                if (creditComposerLines >= sheetComposerLines) {
+                    overrideSheetComposer = true;
+                }
+            }
+            if (overrideSheetComposer) {
+                this.musicSheet.Composer = new Label(this.trimString(finalComposer));
+            }
+        }
+    }
+
+    /** @deprecated Old OSMD < 1.8.6 way of parsing composer + subtitles,
+     * ignores multiline composer + subtitles, uses XML identification tags instead.
+     * Will probably be removed soon.
+     */
+    private readTitleAndComposerFromCreditsLegacy(root: IXmlElement): void {
         const systemYCoordinates: number = this.computeSystemYCoordinates(root);
         if (systemYCoordinates === 0) {
             return;

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

@@ -114,6 +114,15 @@ export class Note {
      */
     public NoteToGraphicalNoteObjectId: number; // used with EngravingRules.NoteToGraphicalNoteMap
 
+    public ToStringShort(octaveOffset: number = 0): string {
+        if (!this.Pitch || this.isRest()) {
+            return "rest"; // Pitch is undefined for rest notes
+        }
+        return this.Pitch?.ToStringShort(octaveOffset);
+    }
+    public get ToStringShortGet(): string {
+        return this.ToStringShort(0);
+    }
     public get ParentVoiceEntry(): VoiceEntry {
         return this.voiceEntry;
     }

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

+ 5 - 1
src/VexFlowPatch/src/keysignature.js

@@ -315,7 +315,7 @@ export class KeySignature extends StaveModifier {
     this.setRendered();
 
     if (this.glyphs.length > 0) {
-        this.stave.context.openGroup("keysignature");
+        const group = this.stave.context.openGroup("keysignature");
         for (let i = 0; i < this.glyphs.length; i++) {
           const glyph = this.glyphs[i];
           const x = this.x + this.xPositions[i];
@@ -323,6 +323,10 @@ export class KeySignature extends StaveModifier {
           glyph.setContext(this.stave.context);
           glyph.renderToStave(x);
         }
+        if (this.hidden && group) {
+          // VexflowPatch: set visibility hidden, as in some systems (rendering SVG file) the key signature is rendered black even if alpha = 0
+          group.setAttribute("visibility", "hidden"); // group is undefined for CanvasContext, e.g. in SkybottomlineCalculator
+        }
         this.stave.context.closeGroup();
     }
   }

+ 191 - 0
src/VexFlowPatch/src/timesignature.js

@@ -0,0 +1,191 @@
+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
+//
+// ## Description
+// Implements time signatures glyphs for staffs
+// See tables.js for the internal time signatures
+// representation
+
+import { Vex } from './vex';
+import { Glyph } from './glyph';
+import { StaveModifier } from './stavemodifier';
+
+const assertIsValidFraction = (timeSpec) => {
+  const numbers = timeSpec.split('/').filter(number => number !== '');
+
+  if (numbers.length !== 2) {
+    throw new Vex.RERR(
+      'BadTimeSignature',
+      `Invalid time spec: ${timeSpec}. Must be in the form "<numerator>/<denominator>"`
+    );
+  }
+
+  numbers.forEach(number => {
+    if (isNaN(Number(number))) {
+      throw new Vex.RERR(
+        'BadTimeSignature', `Invalid time spec: ${timeSpec}. Must contain two valid numbers.`
+      );
+    }
+  });
+};
+
+export class TimeSignature extends StaveModifier {
+  static get CATEGORY() { return 'timesignatures'; }
+
+  static get glyphs() {
+    return {
+      'C': {
+        code: 'v41',
+        point: 40,
+        line: 2,
+      },
+      'C|': {
+        code: 'vb6',
+        point: 40,
+        line: 2,
+      },
+    };
+  }
+
+  constructor(timeSpec = null, customPadding = 15, validate_args = true) {
+    super();
+    this.setAttribute('type', 'TimeSignature');
+    this.validate_args = validate_args;
+
+    if (timeSpec === null) return;
+
+    const padding = customPadding;
+
+    this.point = 40;
+    this.topLine = 2;
+    this.bottomLine = 4;
+    this.setPosition(StaveModifier.Position.BEGIN);
+    this.setTimeSig(timeSpec);
+    this.setWidth(this.timeSig.glyph.getMetrics().width);
+    this.setPadding(padding);
+  }
+
+  getCategory() { return TimeSignature.CATEGORY; }
+
+  parseTimeSpec(timeSpec) {
+    if (timeSpec === 'C' || timeSpec === 'C|') {
+      const { line, code, point } = TimeSignature.glyphs[timeSpec];
+      return {
+        line,
+        num: false,
+        glyph: new Glyph(code, point),
+      };
+    }
+
+    if (this.validate_args) {
+      assertIsValidFraction(timeSpec);
+    }
+
+    const [topDigits, botDigits] = timeSpec
+      .split('/')
+      .map(number => number.split(''));
+
+    return {
+      num: true,
+      glyph: this.makeTimeSignatureGlyph(topDigits, botDigits),
+    };
+  }
+
+  makeTimeSignatureGlyph(topDigits, botDigits) {
+    const glyph = new Glyph('v0', this.point);
+    glyph.topGlyphs = [];
+    glyph.botGlyphs = [];
+
+    let topWidth = 0;
+    for (let i = 0; i < topDigits.length; ++i) {
+      const num = topDigits[i];
+      const topGlyph = new Glyph('v' + num, this.point);
+
+      glyph.topGlyphs.push(topGlyph);
+      topWidth += topGlyph.getMetrics().width;
+    }
+
+    let botWidth = 0;
+    for (let i = 0; i < botDigits.length; ++i) {
+      const num = botDigits[i];
+      const botGlyph = new Glyph('v' + num, this.point);
+
+      glyph.botGlyphs.push(botGlyph);
+      botWidth += botGlyph.getMetrics().width;
+    }
+
+    const width = topWidth > botWidth ? topWidth : botWidth;
+    const xMin = glyph.getMetrics().x_min;
+
+    glyph.getMetrics = () => ({
+      x_min: xMin,
+      x_max: xMin + width,
+      width,
+    });
+
+    const topStartX = (width - topWidth) / 2.0;
+    const botStartX = (width - botWidth) / 2.0;
+
+    const that = this;
+    glyph.renderToStave = function renderToStave(x) {
+      let start_x = x + topStartX;
+      for (let i = 0; i < this.topGlyphs.length; ++i) {
+        const glyph = this.topGlyphs[i];
+        Glyph.renderOutline(
+          this.context,
+          glyph.metrics.outline,
+          glyph.scale,
+          start_x + glyph.x_shift,
+          this.stave.getYForLine(that.topLine)
+        );
+        start_x += glyph.getMetrics().width;
+      }
+
+      start_x = x + botStartX;
+      for (let i = 0; i < this.botGlyphs.length; ++i) {
+        const glyph = this.botGlyphs[i];
+        that.placeGlyphOnLine(glyph, this.stave, glyph.line);
+        Glyph.renderOutline(
+          this.context,
+          glyph.metrics.outline,
+          glyph.scale,
+          start_x + glyph.x_shift,
+          this.stave.getYForLine(that.bottomLine)
+        );
+        start_x += glyph.getMetrics().width;
+      }
+    };
+
+    return glyph;
+  }
+
+  getTimeSig() {
+    return this.timeSig;
+  }
+
+  setTimeSig(timeSpec) {
+    this.timeSig = this.parseTimeSpec(timeSpec);
+    return this;
+  }
+
+  draw() {
+    if (!this.x) {
+      throw new Vex.RERR('TimeSignatureError', "Can't draw time signature without x.");
+    }
+
+    if (!this.stave) {
+      throw new Vex.RERR('TimeSignatureError', "Can't draw time signature without stave.");
+    }
+
+    this.setRendered();
+    this.timeSig.glyph.setStave(this.stave);
+    this.timeSig.glyph.setContext(this.stave.context);
+    this.placeGlyphOnLine(this.timeSig.glyph, this.stave, this.timeSig.line);
+    const group = this.stave.context.openGroup("timesignature");
+    this.timeSig.glyph.renderToStave(this.x);
+    if (this.hidden && group) {
+      // VexflowPatch: set visibility hidden, as in some systems (rendering SVG file) this is rendered black even if alpha = 0
+      group.setAttribute("visibility", "hidden"); // group is undefined for CanvasContext, e.g. in SkybottomlineCalculator
+    }
+    this.stave.context.closeGroup("timesignature");
+  }
+}

+ 0 - 3
test/data/Debussy_Mandoline.xml

@@ -59,9 +59,6 @@
     <lyric-font font-family="Times New Roman" font-size="10"/>
   </defaults>
   <credit page="1">
-    <credit-words default-x="680" default-y="79" font-size="9" halign="center" justify="right" letter-spacing="0.1" valign="bottom">Copyright © 2002 Recordare LLC</credit-words>
-  </credit>
-  <credit page="1">
     <credit-words default-x="680" default-y="1640" font-size="24" justify="center" letter-spacing="0.03" valign="top">Mandoline</credit-words>
   </credit>
   <credit page="1">

+ 0 - 3
test/data/Land_der_Berge.musicxml

@@ -58,9 +58,6 @@
   <credit page="1">
     <credit-words default-x="73.3138" default-y="2003.59" justify="left" valign="bottom" font-size="12">Paula von Preadović</credit-words>
     </credit>
-  <credit page="1">
-    <credit-words default-x="770.014" default-y="73.3138" justify="center" valign="bottom" font-size="8">public domain</credit-words>
-    </credit>
   <part-list>
     <score-part id="P1">
       <part-name>Singstimmen</part-name>

+ 1 - 1
test/data/OSMD_Function_Test_Labels.musicxml

@@ -63,7 +63,7 @@
     <credit-words default-x="56.6929" default-y="1526.67" justify="left" valign="bottom" font-size="12">Lyricist 1</credit-words>
     </credit>
   <credit page="1">
-    <credit-words default-x="595.44" default-y="113.386" justify="center" valign="bottom" font-size="8">2020</credit-words>
+    <credit-words default-x="595.44" default-y="113.386" justify="center" valign="bottom" font-size="8">Subtitle 2</credit-words>
     </credit>
   <part-list>
     <score-part id="P1">

+ 1 - 1
test/data/OSMD_Function_Test_Pedals.musicxml

@@ -46,7 +46,7 @@
     </credit>
   <credit page="1">
     <credit-type>composer</credit-type>
-    <credit-words default-x="1148.14" default-y="1411.09" justify="right" valign="top">Composer</credit-words>
+    <credit-words default-x="1148.14" default-y="1411.09" justify="right" valign="top">OSMD</credit-words>
     </credit>
   <part-list>
     <score-part id="P1">

+ 0 - 3
test/data/OSMD_function_test_chord_tests_various.musicxml

@@ -44,9 +44,6 @@
     <credit-words default-x="600" default-y="1640" justify="center" valign="top" font-family="Times New Roman" font-size="22.0128">Various Chord Tests
 </credit-words>
     </credit>
-  <credit page="1">
-    <credit-words default-x="600" default-y="114.286" justify="center" valign="bottom" font-size="8">Copyright © </credit-words>
-    </credit>
   <part-list>
     <score-part id="P1">
       <part-name>Piano</part-name>

+ 0 - 6
test/data/OSMD_function_test_color.musicxml

@@ -37,14 +37,8 @@
     <credit-words default-x="80" default-y="1522" justify="left" valign="bottom" font-size="12">Aloys Jeitteles</credit-words>
     </credit>
   <credit page="1">
-    <credit-words default-x="1280" default-y="1522" justify="right" valign="bottom" font-size="12">Ludwig van Beethoven</credit-words>
-    </credit>
-  <credit page="1">
     <credit-words default-x="680" default-y="1680" justify="center" valign="top" font-size="24">An die ferne Geliebte</credit-words>
     </credit>
-  <credit page="1">
-    <credit-words default-x="680" default-y="80" justify="center" valign="bottom" font-size="8">Copyright © 2002 Recordare LLC</credit-words>
-    </credit>
   <part-list>
     <score-part id="P1">
       <part-name>Voice</part-name>

+ 0 - 6
test/data/Schubert_An_die_Musik.xml

@@ -2,8 +2,6 @@
 <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
 <score-partwise version="3.1">
   <identification>
-    <rights>Franz Schubert: An die Musik
-(Mélisande on http://www.musescore.com)</rights>
     <encoding>
       <software>MuseScore 2.3.2</software>
       <encoding-date>2018-08-13</encoding-date>
@@ -45,10 +43,6 @@
   <credit page="1">
     <credit-words default-x="595.24" default-y="1626.98" justify="center" valign="top" font-family="Times New Roman" font-size="24">An die Musik</credit-words>
     </credit>
-  <credit page="1">
-    <credit-words default-x="595.24" default-y="113.379" justify="center" valign="bottom" font-size="8">Franz Schubert: An die Musik
-(Mélisande on http://www.musescore.com)</credit-words>
-    </credit>
   <part-list>
     <score-part id="P1">
       <part-name>Voice</part-name>

+ 0 - 4
test/data/test_beam_svg_double.musicxml

@@ -43,10 +43,6 @@
     <credit-type>title</credit-type>
     <credit-words default-x="600.24" default-y="1611.63" justify="center" valign="top" font-size="22">test_beam_svg_double</credit-words>
     </credit>
-  <credit page="1">
-    <credit-type>composer</credit-type>
-    <credit-words default-x="1114.75" default-y="1511.63" justify="right" valign="top">Composer</credit-words>
-    </credit>
   <part-list>
     <score-part id="P1">
       <part-name>Piano</part-name>

+ 1 - 1
test/data/test_octaveshift_notes_shifted_octave_shift_end.musicxml

@@ -46,7 +46,7 @@
     </credit>
   <credit page="1">
     <credit-type>composer</credit-type>
-    <credit-words default-x="1114.75" default-y="1590.19" justify="right" valign="top">scale test</credit-words>
+    <credit-words default-x="1114.75" default-y="1590.19" justify="right" valign="top">Hanon</credit-words>
     </credit>
   <part-list>
     <score-part id="P1">

+ 139 - 0
test/data/test_subtitle_composer_multiline_hanzi.musicxml

@@ -0,0 +1,139 @@
+<?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_subtitle_composer_multiline_hanzi</work-title>
+    </work>
+  <identification>
+    <creator type="composer">恩沛華</creator>
+    <creator type="lyricist">恩沛華,華人聖頌編輯委員會修,1992</creator>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2024-01-30</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>1696.75</page-height>
+      <page-width>1200.34</page-width>
+      <page-margins type="even">
+        <left-margin>86.0571</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>86.0571</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.17" default-y="1611.07" justify="center" valign="top" font-family="DFKai-SB" font-size="22">test_subtitle_composer_multiline_hanzi</credit-words>
+    </credit>
+  <credit page="1">
+    <credit-type>subtitle</credit-type>
+    <credit-words default-x="600.17" default-y="1568.22" justify="center" valign="top" font-family="DFKai-SB" font-size="8">拔萃
+</credit-words>
+    <credit-words font-family="Times New Roman">7.7.9.7.</credit-words>
+    </credit>
+  <credit page="1">
+    <credit-type>composer</credit-type>
+    <credit-words default-x="1114.28" default-y="1511.07" justify="right" valign="top" font-family="DFKai-SB">恩沛華
+</credit-words>
+    <credit-words>陳偉光編,</credit-words>
+    <credit-words font-family="Times New Roman">1992</credit-words>
+    </credit>
+  <credit page="1">
+    <credit-type>lyricist</credit-type>
+    <credit-words default-x="85.7143" default-y="1511.07" justify="left" valign="top" font-family="DFKai-SB">恩沛華,華人聖頌編輯委員會修,</credit-words>
+    <credit-words font-family="Times New Roman">1992</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="452.37">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>64.90</left-margin>
+            <right-margin>511.30</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>
+      <barline location="left">
+        <bar-style>heavy-light</bar-style>
+        <repeat direction="forward"/>
+        </barline>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>-3</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>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>1</voice>
+        <staff>1</staff>
+        </note>
+      <backup>
+        <duration>4</duration>
+        </backup>
+      <note>
+        <rest measure="yes"/>
+        <duration>4</duration>
+        <voice>5</voice>
+        <staff>2</staff>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 391 - 0
test/data/test_wedge_cresc_dim_simultaneous_quartet.musicxml

@@ -0,0 +1,391 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_wedge_cresc_dim_simultaneous_quartet</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2022-01-26</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_wedge_cresc_dim_simultaneous_quartet</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>bracket</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Violin I</part-name>
+      <score-instrument id="P1-I1">
+        <instrument-name>Violin</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>41</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P2">
+      <part-name>Violin II</part-name>
+      <score-instrument id="P2-I1">
+        <instrument-name>Violin</instrument-name>
+        </score-instrument>
+      <midi-device id="P2-I1" port="1"></midi-device>
+      <midi-instrument id="P2-I1">
+        <midi-channel>4</midi-channel>
+        <midi-program>41</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P3">
+      <part-name>Viola</part-name>
+      <score-instrument id="P3-I1">
+        <instrument-name>Viola</instrument-name>
+        </score-instrument>
+      <midi-device id="P3-I1" port="1"></midi-device>
+      <midi-instrument id="P3-I1">
+        <midi-channel>7</midi-channel>
+        <midi-program>42</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P4">
+      <part-name>Violoncello</part-name>
+      <score-instrument id="P4-I1">
+        <instrument-name>Violoncello</instrument-name>
+        </score-instrument>
+      <midi-device id="P4-I1" port="1"></midi-device>
+      <midi-instrument id="P4-I1">
+        <midi-channel>11</midi-channel>
+        <midi-program>43</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    <part-group type="stop" number="1"/>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="483.61">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>118.24</left-margin>
+            <right-margin>426.72</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-60.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="84.45" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        </note>
+      <note default-x="181.58" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        </note>
+      <note default-x="278.70" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        </note>
+      <note default-x="375.83" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>up</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="483.61">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>97.44</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-65.04"/>
+          </direction-type>
+        </direction>
+      <note default-x="84.45" default-y="-157.44">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="181.58" default-y="-157.44">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="278.70" default-y="-157.44">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="375.83" default-y="-157.44">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P3">
+    <measure number="1" width="483.61">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>97.44</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>C</sign>
+          <line>3</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-61.29"/>
+          </direction-type>
+        </direction>
+      <note default-x="84.45" default-y="-289.88">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="181.58" default-y="-289.88">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1" relative-x="-2.06"/>
+          </direction-type>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-61.29"/>
+          </direction-type>
+        </direction>
+      <note default-x="278.70" default-y="-289.88">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="375.83" default-y="-289.88">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <stem>down</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P4">
+    <measure number="1" width="483.61">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>97.44</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-60.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="83.49" default-y="-417.32">
+        <pitch>
+          <step>G</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1" relative-x="-4.09"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 477 - 0
test/data/test_wedge_crescendo_multi-instrument.musicxml

@@ -0,0 +1,477 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_wedge_crescendo_multi-instrument</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2024-01-09</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.5</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1828</page-height>
+      <page-width>1292</page-width>
+      <page-margins type="both">
+        <left-margin>92.3077</left-margin>
+        <right-margin>92.3077</right-margin>
+        <top-margin>92.3077</top-margin>
+        <bottom-margin>92.3077</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Times New Roman,serif" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="646" default-y="1735.69" justify="center" valign="top" font-family="ZapfChancery Light" font-size="29.94">test_wedge_crescendo_multi-instrument</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>bracket</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Violin I</part-name>
+      <part-abbreviation>Vln.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Violin I</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>41</midi-program>
+        <volume>78.7402</volume>
+        <pan>-67</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P2">
+      <part-name>Violin II</part-name>
+      <part-abbreviation>Vln.</part-abbreviation>
+      <score-instrument id="P2-I1">
+        <instrument-name>Violin II</instrument-name>
+        </score-instrument>
+      <midi-device id="P2-I1" port="1"></midi-device>
+      <midi-instrument id="P2-I1">
+        <midi-channel>4</midi-channel>
+        <midi-program>41</midi-program>
+        <volume>74.8031</volume>
+        <pan>67</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P3">
+      <part-name>Viola</part-name>
+      <part-abbreviation>Vla.</part-abbreviation>
+      <score-instrument id="P3-I1">
+        <instrument-name>Viola</instrument-name>
+        </score-instrument>
+      <midi-device id="P3-I1" port="1"></midi-device>
+      <midi-instrument id="P3-I1">
+        <midi-channel>7</midi-channel>
+        <midi-program>42</midi-program>
+        <volume>70.8661</volume>
+        <pan>-46</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P4">
+      <part-name>Cello</part-name>
+      <part-abbreviation>Vc.</part-abbreviation>
+      <score-instrument id="P4-I1">
+        <instrument-name>Violoncello (C2-C6)</instrument-name>
+        </score-instrument>
+      <midi-device id="P4-I1" port="1"></midi-device>
+      <midi-instrument id="P4-I1">
+        <midi-channel>11</midi-channel>
+        <midi-program>43</midi-program>
+        <volume>70.8661</volume>
+        <pan>44</pan>
+        </midi-instrument>
+      </score-part>
+    <part-group type="stop" number="1"/>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="600.32">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>85.68</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>-2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="above">
+        <direction-type>
+          <words relative-y="10.00" font-weight="bold" font-size="12.09">Andante</words>
+          </direction-type>
+        </direction>
+      <direction placement="above">
+        <direction-type>
+          <metronome parentheses="no" default-x="-37.68" default-y="14.10" relative-y="20.00">
+            <beat-unit>quarter</beat-unit>
+            <per-minute>72</per-minute>
+            </metronome>
+          </direction-type>
+        <sound tempo="72"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-63.55"/>
+          </direction-type>
+        </direction>
+      <note default-x="111.61" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      </measure>
+    <measure number="2" width="421.38">
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.50" default-y="-43.55" relative-y="-25.00">
+            <f/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="106.67"/>
+        </direction>
+      <note default-x="13.00" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <alter>-1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <note default-x="13.00" default-y="-15.00">
+        <chord/>
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="600.32">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>55.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>-2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-77.16"/>
+          </direction-type>
+        </direction>
+      <note default-x="111.61" default-y="-140.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>3</duration>
+        <voice>1</voice>
+        <type>quarter</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <note default-x="355.06" default-y="-140.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <notations>
+          <slur type="start" placement="below" number="1"/>
+          </notations>
+        </note>
+      <note default-x="436.22" default-y="-130.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        </note>
+      <note default-x="517.37" default-y="-115.00">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      </measure>
+    <measure number="2" width="421.38">
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.50" default-y="-57.16" relative-y="-25.00">
+            <f/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="106.67"/>
+        </direction>
+      <note default-x="13.00" default-y="-120.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P3">
+    <measure number="1" width="600.32">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>73.02</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>-2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>C</sign>
+          <line>3</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-63.11"/>
+          </direction-type>
+        </direction>
+      <note default-x="111.61" default-y="-233.02">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <slur type="start" placement="above" number="1"/>
+          </notations>
+        </note>
+      <note default-x="192.76" default-y="-248.02">
+        <pitch>
+          <step>F</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        </note>
+      <note default-x="273.91" default-y="-223.02">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        <notations>
+          <slur type="stop" number="1"/>
+          </notations>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      </measure>
+    <measure number="2" width="421.38">
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.50" default-y="-43.11" relative-y="-25.00">
+            <f/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="106.67"/>
+        </direction>
+      <note default-x="13.00" default-y="-248.02">
+        <pitch>
+          <step>F</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P4">
+    <measure number="1" width="600.32">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>55.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>2</divisions>
+        <key>
+          <fifths>-2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>3</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-68.11"/>
+          </direction-type>
+        </direction>
+      <note default-x="111.61" default-y="-333.02">
+        <pitch>
+          <step>B</step>
+          <alter>-1</alter>
+          <octave>2</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      </measure>
+    <measure number="2" width="421.38">
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.50" default-y="-48.11" relative-y="-25.00">
+            <f/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="106.67"/>
+        </direction>
+      <note default-x="13.00" default-y="-348.02">
+        <pitch>
+          <step>F</step>
+          <octave>2</octave>
+          </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <dot/>
+        <stem>up</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 193 - 0
test/data/test_wedge_decrescendo_crescendo_stop_start.musicxml

@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.1">
+  <work>
+    <work-title>test_wedge_decrescendo_crescendo_stop_start</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2023-06-17</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.5</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1828</page-height>
+      <page-width>1292</page-width>
+      <page-margins type="both">
+        <left-margin>78.1538</left-margin>
+        <right-margin>78.1538</right-margin>
+        <top-margin>78.1538</top-margin>
+        <bottom-margin>78.1538</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="Palatino,serif" font-size="10"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <credit page="1">
+    <credit-type>title</credit-type>
+    <credit-words default-x="646" default-y="1749.85" justify="center" valign="top" font-family="Edwin" font-size="22">test_wedge_decrescendo_crescendo_stop_start</credit-words>
+    </credit>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Voice</part-name>
+      <part-abbreviation>Vo.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Voice</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>53</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="295.48">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>558.86</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="20.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="89.48" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <accidental>sharp</accidental>
+        <stem>up</stem>
+        <notations>
+          <tied type="start"/>
+          </notations>
+        </note>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="20.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="191.58" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="stop"/>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <tied type="stop"/>
+          <tied type="start"/>
+          </notations>
+        </note>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      </measure>
+    <measure number="2" width="231.35">
+      <direction placement="above">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="20.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="16.50" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="stop"/>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <tied type="stop"/>
+          <tied type="start"/>
+          </notations>
+        </note>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="20.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="118.60" default-y="-50.00">
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="stop"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        <notations>
+          <tied type="stop"/>
+          </notations>
+        </note>
+      <direction placement="above">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

+ 166 - 0
test/data/test_wedge_decrescendo_multi-instrument_startX.musicxml

@@ -0,0 +1,166 @@
+<?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">
+  <movement-title>test_wedge_decrescendo_multi-instrument_startX</movement-title>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.2</software>
+      <encoding-date>2024-01-30</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</page-height>
+      <page-width>1200</page-width>
+      <page-margins type="both">
+        <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="11"/>
+    <lyric-font font-family="Edwin" font-size="10"/>
+    </defaults>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Violin</part-name>
+      <part-abbreviation>Vln.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Violin (solo)</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>41</midi-program>
+        <volume>78.7402</volume>
+        <pan>-46</pan>
+        </midi-instrument>
+      </score-part>
+    <score-part id="P2">
+      <part-name>Cello</part-name>
+      <part-abbreviation>Vlc.</part-abbreviation>
+      <score-instrument id="P2-I1">
+        <instrument-name>Violoncello</instrument-name>
+        </score-instrument>
+      <midi-device id="P2-I1" port="1"></midi-device>
+      <midi-instrument id="P2-I1">
+        <midi-channel>4</midi-channel>
+        <midi-program>43</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="393.21">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>71.69</left-margin>
+            <right-margin>563.68</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-63.64"/>
+          </direction-type>
+        </direction>
+      <note default-x="113.25" default-y="-45.00">
+        <pitch>
+          <step>D</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <notations>
+          <fermata type="upright" relative-y="5.00"/>
+          </notations>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  <part id="P2">
+    <measure number="1" width="393.21">
+      <print>
+        <staff-layout number="1">
+          <staff-distance>65.00</staff-distance>
+          </staff-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>2</fifths>
+          <mode>major</mode>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>F</sign>
+          <line>4</line>
+          </clef>
+        </attributes>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-60.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="113.25" default-y="-125.00">
+        <pitch>
+          <step>D</step>
+          <octave>3</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>whole</type>
+        <notations>
+          <fermata type="upright" relative-y="5.00"/>
+          </notations>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>