瀏覽代碼

merge osmd-public (0.9.5+) into osmd-extended/master

sschmid 4 年之前
父節點
當前提交
87325df3de

+ 1 - 0
package.json

@@ -32,6 +32,7 @@
     "generate:current:singletest": "node test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/current png 0 0 ^Beethoven --osmdtestingsingle",
     "generate:blessed": "node ./test/Util/generateImages_browserless.js ../../build ./test/data ./visual_regression/blessed png 0 0 allSmall --osmdtesting",
     "test:visual": "bash ./test/Util/visual_regression.sh ./visual_regression",
+    "test:visual:build": "npm run build:webpack && npm run generate:current && npm run test:visual",
     "test:visual:singletest": "sh ./test/Util/visual_regression.sh ./visual_regression Beethoven",
     "fix-memory-limit": "cross-env NODE_OPTIONS=--max_old_space_size=4096"
   },

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

@@ -25,6 +25,8 @@ export enum AccidentalEnum {
     TRIPLEFLAT,
     QUARTERTONESHARP,
     QUARTERTONEFLAT,
+    THREEQUARTERSSHARP,
+    THREEQUARTERSFLAT,
 }
 
 // This class represents a musical note. The middle A (440 Hz) lies in the octave with the value 1.
@@ -66,15 +68,20 @@ export class Pitch {
         }
     }
 
-    /** This method goes x steps from a NoteEnum on a keyboard.
-     * E.g. Two steps to the left (-2) from a D is a B.
-     * Two steps to the right from an A is a C. */
-    public static stepFromNoteEnum(noteEnum: NoteEnum, step: number): [NoteEnum, number] {
+    /** Changes a note x lines/steps up (+) or down (-) from a NoteEnum on a staffline/keyboard (white keys).
+     * E.g. Two lines down (-2) from a D is a B.
+     * Two lines up from an A is a C.
+     *   (e.g. in the treble/violin clef, going one line up: E -> F (semitone), F -> G (2 semitones)).
+     * Returns new NoteEnum and the octave shift (e.g. -1 = new octave is one octave down). */
+    public static lineShiftFromNoteEnum(noteEnum: NoteEnum, lines: number): [NoteEnum, number] {
+        if (lines === 0) {
+            return [noteEnum, 0];
+        }
         const enums: NoteEnum[] = Pitch.pitchEnumValues;
         const originalIndex: number = enums.indexOf(noteEnum);
         let octaveShift: number = 0;
-        let newIndex: number = originalIndex + step % enums.length; // modulo only handles positive overflow
-        if (originalIndex + step > enums.length - 1) {
+        let newIndex: number = (originalIndex + lines) % enums.length; // modulo only handles positive overflow
+        if (originalIndex + lines > enums.length - 1) {
             octaveShift = 1;
         }
         if (newIndex < 0) {
@@ -208,14 +215,18 @@ export class Pitch {
                 return 2;
             case AccidentalEnum.DOUBLEFLAT:
                 return -2;
-            case AccidentalEnum.QUARTERTONESHARP:
-                return 0.5;
-            case AccidentalEnum.QUARTERTONEFLAT:
-                return -0.5;
             case AccidentalEnum.TRIPLESHARP: // very rare, in some classical pieces
                 return 3;
             case AccidentalEnum.TRIPLEFLAT:
                 return -3;
+            case AccidentalEnum.QUARTERTONESHARP:
+                return 0.5;
+            case AccidentalEnum.QUARTERTONEFLAT:
+                return -0.5;
+            case AccidentalEnum.THREEQUARTERSSHARP:
+                return 1.5;
+            case AccidentalEnum.THREEQUARTERSFLAT:
+                return -1.5;
             default:
                 throw new Error("Unhandled AccidentalEnum value");
                 // return 0;
@@ -235,14 +246,18 @@ export class Pitch {
                 return AccidentalEnum.DOUBLESHARP;
             case -2:
                 return AccidentalEnum.DOUBLEFLAT;
-            case 0.5:
-                return AccidentalEnum.QUARTERTONESHARP;
-            case -0.5:
-                return AccidentalEnum.QUARTERTONEFLAT;
             case 3:
                 return AccidentalEnum.TRIPLESHARP;
             case -3:
                 return AccidentalEnum.TRIPLEFLAT;
+            case 0.5:
+                return AccidentalEnum.QUARTERTONESHARP;
+            case -0.5:
+                return AccidentalEnum.QUARTERTONEFLAT;
+            case 1.5:
+                return AccidentalEnum.THREEQUARTERSSHARP;
+            case -1.5:
+                return AccidentalEnum.THREEQUARTERSFLAT;
             default:
                 if (halfTones > 0 && halfTones < 1) {
                     return AccidentalEnum.QUARTERTONESHARP;
@@ -276,7 +291,7 @@ export class Pitch {
                 acc = "##";
                 break;
             case AccidentalEnum.TRIPLESHARP:
-                acc = "++";
+                acc = "###";
                 break;
             case AccidentalEnum.DOUBLEFLAT:
                 acc = "bb";
@@ -290,6 +305,12 @@ export class Pitch {
             case AccidentalEnum.QUARTERTONEFLAT:
                 acc = "d";
                 break;
+            case AccidentalEnum.THREEQUARTERSSHARP:
+                acc = "++";
+                break;
+            case AccidentalEnum.THREEQUARTERSFLAT:
+                acc = "db";
+                break;
             default:
         }
         return acc;

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

@@ -67,7 +67,8 @@ export class EngravingRules {
     /** How many unique note positions a percussion score needs to have to not be rendered on one line. */
     public PercussionOneLineCutoff: number;
     public PercussionForceVoicesOneLineCutoff: number;
-    public PercussionOneLineUseXMLDisplayStep: boolean;
+    public PercussionUseXMLDisplayStep: boolean;
+    public PercussionXMLDisplayStepNoteValueShift: number;
     public PercussionOneLineXMLDisplayStepOctaveOffset: number;
     public BetweenKeySymbolsDistance: number;
     public KeyRightMargin: number;
@@ -287,6 +288,7 @@ export class EngravingRules {
     public ArticulationPlacementFromXML: boolean;
     /** Position of fingering label in relation to corresponding note (left, right supported, above, below experimental) */
     public FingeringPosition: PlacementEnum;
+    public FingeringPositionFromXML: boolean;
     public FingeringInsideStafflines: boolean;
     public FingeringLabelFontHeight: number;
     public FingeringOffsetX: number;
@@ -378,7 +380,8 @@ export class EngravingRules {
         this.ClefRightMargin = 0.75;
         this.PercussionOneLineCutoff = 3; // percussion parts with <3 unique note positions rendered on one line
         this.PercussionForceVoicesOneLineCutoff = 1;
-        this.PercussionOneLineUseXMLDisplayStep = true;
+        this.PercussionUseXMLDisplayStep = true;
+        this.PercussionXMLDisplayStepNoteValueShift = 0;
         this.PercussionOneLineXMLDisplayStepOctaveOffset = 0;
         this.BetweenKeySymbolsDistance = 0.2;
         this.KeyRightMargin = 0.75;
@@ -602,6 +605,7 @@ export class EngravingRules {
         this.RenderTimeSignatures = true;
         this.ArticulationPlacementFromXML = true;
         this.FingeringPosition = PlacementEnum.Left; // easier to get bounding box, and safer for vertical layout
+        this.FingeringPositionFromXML = true;
         this.FingeringInsideStafflines = false;
         this.FingeringLabelFontHeight = 1.7;
         this.FingeringOffsetX = 0.0;

+ 2 - 2
src/MusicalScore/Graphical/GraphicalVoiceEntry.ts

@@ -33,7 +33,7 @@ export class GraphicalVoiceEntry extends GraphicalObject {
      */
     public sort(): GraphicalNote[] {
         this.notes.sort((a, b) => {
-            return b.sourceNote.Pitch.getHalfTone() - a.sourceNote.Pitch.getHalfTone();
+            return (b.sourceNote.Pitch?.getHalfTone() ?? 0) - (a.sourceNote.Pitch?.getHalfTone() ?? 0);
         });
         // note that this is the reverse order of what vexflow needs
         return this.notes;
@@ -46,7 +46,7 @@ export class GraphicalVoiceEntry extends GraphicalObject {
      */
     public sortForVexflow(): GraphicalNote[] {
         this.notes.sort((a, b) => {
-            return a.sourceNote.Pitch.getHalfTone() - b.sourceNote.Pitch.getHalfTone();
+            return (a.sourceNote.Pitch?.getHalfTone() ?? 0) - (b.sourceNote.Pitch.getHalfTone() ?? 0);
         });
         return this.notes;
     }

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

@@ -113,6 +113,11 @@ export class SkyBottomLineCalculator {
                     tmpSkyLine[idx] = Math.max(this.findPreviousValidNumber(idx, tmpSkyLine), this.findNextValidNumber(idx, tmpSkyLine));
                 }
             }
+            for (let idx: number = 0; idx < tmpBottomLine.length; idx++) {
+                if (tmpBottomLine[idx] === undefined) {
+                    tmpBottomLine[idx] = Math.max(this.findPreviousValidNumber(idx, tmpBottomLine), this.findNextValidNumber(idx, tmpBottomLine));
+                }
+            }
 
             this.mSkyLine.push(...tmpSkyLine);
             this.mBottomLine.push(...tmpBottomLine);

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

@@ -423,7 +423,7 @@ export class VexFlowConverter {
         for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
             (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
             if (accidentals[i]) {
-                if (accidentals[i] === "++") { // triple sharp
+                if (accidentals[i] === "###") { // triple sharp
                     vfnote.addAccidental(i, new Vex.Flow.Accidental("##"));
                     vfnote.addAccidental(i, new Vex.Flow.Accidental("#"));
                     continue;

+ 6 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts

@@ -106,6 +106,9 @@ export class VexFlowGraphicalNote extends GraphicalNote {
      * This is for low-level rendering hacks and should be used with caution.
      */
     public getSVGId(): string {
+        if (!this.vfnote) {
+            return undefined; // e.g. MultiRestMeasure
+        }
         return this.vfnote[0].getAttribute("id");
     }
 
@@ -114,6 +117,9 @@ export class VexFlowGraphicalNote extends GraphicalNote {
      * This is for low-level rendering hacks and should be used with caution.
      */
     public getSVGGElement(): SVGGElement {
+        if (!this.vfnote) {
+            return undefined; // e.g. MultiRestMeasure
+        }
         return this.vfnote[0].getAttribute("el");
     }
 }

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

@@ -650,6 +650,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             last_indices: [endNoteIndexInTie],
             last_note: vfEndNote
           });
+          if (tie.Tie.TieDirection === PlacementEnum.Below) {
+            vfTie.setDirection(1); // + is down in vexflow
+          }
         }
 
         const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);

+ 2 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowTabMeasure.ts

@@ -26,9 +26,11 @@ export class VexFlowTabMeasure extends VexFlowMeasure {
         // Will be changed when repetitions will be implemented
         //this.beginInstructionsWidth = 20 / UnitInPixels;
         //this.endInstructionsWidth = 20 / UnitInPixels;
+        const stafflineCount: number = this.ParentStaff.StafflineCount ?? 6; // if undefined, 6 by default (same as Vexflow default)
         this.stave = new Vex.Flow.TabStave(0, 0, 0, {
             space_above_staff_ln: 0,
             space_below_staff_ln: 0,
+            num_lines: stafflineCount
         });
         // also see VexFlowMusicSheetDrawer.drawSheet() for some other vexflow default value settings (like default font scale)
         this.updateInstructionWidth();

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

@@ -89,10 +89,9 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
             return graphicalNote;
         }
         const currentPitchList: Array<Pitch> = this.staffPitchListMapping.getValue(staffIndex);
-        const xmlSingleStaffline: boolean = graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaff.StafflineCount === 1;
-        const positionByXml: boolean = this.rules.PercussionOneLineUseXMLDisplayStep &&
-            graphicalNote.sourceNote.displayStepUnpitched !== undefined &&
-            xmlSingleStaffline;
+        //const xmlSingleStaffline: boolean = graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaff.StafflineCount === 1;
+        const positionByXml: boolean = this.rules.PercussionUseXMLDisplayStep &&
+            graphicalNote.sourceNote.displayStepUnpitched !== undefined;
         if (currentPitchList.length > this.rules.PercussionOneLineCutoff && !positionByXml) {
             //Don't need to position notes. We aren't under the cutoff
             return graphicalNote;
@@ -102,9 +101,9 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
 
         let displayNote: NoteEnum = this.baseLineNote;
         let displayOctave: number = this.baseLineOctave;
-        if (this.rules.PercussionOneLineUseXMLDisplayStep
-            && graphicalNote.sourceNote.displayStepUnpitched !== undefined
-            && xmlSingleStaffline) {
+        if (this.rules.PercussionUseXMLDisplayStep
+            && graphicalNote.sourceNote.displayStepUnpitched !== undefined) {
+            //&& xmlSingleStaffline) {
             displayNote = graphicalNote.sourceNote.displayStepUnpitched;
             displayOctave = graphicalNote.sourceNote.displayOctaveUnpitched + this.rules.PercussionOneLineXMLDisplayStepOctaveOffset;
         }
@@ -115,7 +114,7 @@ export class VexflowStafflineNoteCalculator implements IStafflineNoteCalculator
             const pitchIndex: number = VexflowStafflineNoteCalculator.PitchIndexOf(currentPitchList, notePitch);
             if (pitchIndex > -1) {
                 const half: number = Math.ceil(currentPitchList.length / 2);
-                if (!this.rules.PercussionOneLineUseXMLDisplayStep) {
+                if (!this.rules.PercussionUseXMLDisplayStep) {
                     if (pitchIndex >= half) {
                         //position above
                         displayOctave = 2;

+ 18 - 6
src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts

@@ -7,14 +7,20 @@ import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
 import { Articulation } from "../../VoiceData/Articulation";
 import { Note } from "../../VoiceData/Note";
+import { EngravingRules } from "../../Graphical/EngravingRules";
 export class ArticulationReader {
+  private rules: EngravingRules;
+
+  constructor(rules: EngravingRules) {
+    this.rules = rules;
+  }
 
   private getAccEnumFromString(input: string): AccidentalEnum {
     switch (input) {
       case "sharp":
         return AccidentalEnum.SHARP;
       case "flat":
-          return AccidentalEnum.FLAT;
+        return AccidentalEnum.FLAT;
       case "natural":
         return AccidentalEnum.NATURAL;
       case "double-sharp":
@@ -23,14 +29,18 @@ export class ArticulationReader {
       case "double-flat":
       case "flat-flat":
         return AccidentalEnum.DOUBLEFLAT;
+      case "triple-sharp":
+        return AccidentalEnum.TRIPLESHARP;
+      case "triple-flat":
+        return AccidentalEnum.TRIPLEFLAT;
       case "quarter-sharp":
         return AccidentalEnum.QUARTERTONESHARP;
       case "quarter-flat":
         return AccidentalEnum.QUARTERTONEFLAT;
-      case "triple-sharp":
-          return AccidentalEnum.TRIPLESHARP;
-      case "triple-flat":
-        return AccidentalEnum.TRIPLEFLAT;
+      case "three-quarters-sharp":
+        return AccidentalEnum.THREEQUARTERSSHARP;
+      case "three-quarters-flat":
+        return AccidentalEnum.THREEQUARTERSFLAT;
       default:
         return AccidentalEnum.NONE;
     }
@@ -184,7 +194,9 @@ export class ArticulationReader {
     technicalInstruction.sourceNote = note;
     technicalInstruction.value = stringOrFingeringNode.value;
     const placement: Attr = stringOrFingeringNode.attribute("placement");
-    technicalInstruction.placement = this.getPlacement(placement);
+    if (this.rules.FingeringPositionFromXML) {
+      technicalInstruction.placement = this.getPlacement(placement);
+    }
     return technicalInstruction;
   }
 

+ 30 - 2
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -49,7 +49,7 @@ export class VoiceGenerator {
     this.instrument.Voices.push(this.voice); // apparently necessary for cursor.next(), for "cursor with hidden instrument" test
     this.staff.Voices.push(this.voice);
     this.lyricsReader = new LyricsReader(this.musicSheet);
-    this.articulationReader = new ArticulationReader();
+    this.articulationReader = new ArticulationReader(this.musicSheet.Rules);
   }
 
   public pluginManager: ReaderPluginManager; // currently only used in audio player
@@ -443,7 +443,11 @@ export class VoiceGenerator {
           if (displayStepElement) {
             noteStep = NoteEnum[displayStepElement.value.toUpperCase()];
             let octaveShift: number = 0;
-            [displayStepUnpitched, octaveShift] = Pitch.stepFromNoteEnum(noteStep, -3);
+            let noteValueShift: number = this.musicSheet.Rules.PercussionXMLDisplayStepNoteValueShift;
+            if (this.instrument.Staves[0].StafflineCount === 1) {
+              noteValueShift -= 3; // for percussion one line scores, we need to set the notes 3 lines lower
+            }
+            [displayStepUnpitched, octaveShift] = Pitch.lineShiftFromNoteEnum(noteStep, noteValueShift);
             displayOctaveUnpitched += octaveShift;
           }
         } else if (noteElement.name === "instrument") {
@@ -895,6 +899,8 @@ export class VoiceGenerator {
               const newTieNumber: number = this.getNextAvailableNumberForTie();
               const tie: Tie = new Tie(this.currentNote, tieType);
               this.openTieDict[newTieNumber] = tie;
+              tie.TieNumber = newTieNumber;
+              this.setTieDirections();
             } else if (type === "stop") {
               const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
               const tie: Tie = this.openTieDict[tieNumber];
@@ -919,6 +925,28 @@ export class VoiceGenerator {
     }
   }
 
+  // TODO do same for slurs, optimize.
+  /** Sets the directions of open ties: up for the top one, down for the others. */
+  private setTieDirections(): void {
+    const tieKeys: string[] = Object.keys(this.openTieDict);
+    let highestNote: Note = undefined;
+    for (const tieKey of tieKeys) {
+      const tie: Tie = this.openTieDict[tieKey];
+      const tieNote: Note = tie.Notes[0];
+      if (!highestNote || tieNote.Pitch.OperatorFundamentalGreaterThan(highestNote.Pitch)) {
+        highestNote = tieNote;
+      }
+    }
+    for (const tieKey of tieKeys) {
+      const tie: Tie = this.openTieDict[tieKey];
+      if (tie.Notes[0] === highestNote) {
+        tie.TieDirection = PlacementEnum.Above;
+      } else {
+        tie.TieDirection = PlacementEnum.Below;
+      }
+    }
+  }
+
   /**
    * Find the next free int (starting from 0) to use as key in TieDict.
    * @returns {number}

+ 3 - 0
src/MusicalScore/VoiceData/Tie.ts

@@ -2,6 +2,7 @@ import {Note} from "./Note";
 import { Fraction } from "../../Common/DataObjects/Fraction";
 import { Pitch } from "../../Common/DataObjects/Pitch";
 import { TieTypes } from "../../Common/Enums/";
+import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
 
 /**
  * A [[Tie]] connects two notes of the same pitch and name, indicating that they have to be played as a single note.
@@ -15,6 +16,8 @@ export class Tie {
 
     private notes: Note[] = [];
     private type: TieTypes;
+    public TieNumber: number = 1;
+    public TieDirection: PlacementEnum = PlacementEnum.Above;
 
     public get Notes(): Note[] {
         return this.notes;

+ 22 - 24
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -71,26 +71,27 @@ export class OpenSheetMusicDisplay {
 
     public cursor: Cursor;
     public zoom: number = 1.0;
-    private zoomUpdated: boolean = false;
+    protected zoomUpdated: boolean = false;
     /** Timeout in milliseconds used in osmd.load(string) when string is a URL. */
     public loadUrlTimeout: number = 5000;
 
-    private container: HTMLElement;
-    private backendType: BackendType;
-    private needBackendUpdate: boolean;
-    private sheet: MusicSheet;
-    private drawer: VexFlowMusicSheetDrawer;
-    private drawBoundingBox: string;
-    private drawSkyLine: boolean;
-    private drawBottomLine: boolean;
-    private graphic: GraphicalMusicSheet;
-    private renderingManager: SheetRenderingManager;
-    private interactionManager: AbstractDisplayInteractionManager;
-    private drawingParameters: DrawingParameters;
-    private rules: EngravingRules;
-    private autoResizeEnabled: boolean;
+    protected container: HTMLElement;
+    protected backendType: BackendType;
+    protected needBackendUpdate: boolean;
+    protected sheet: MusicSheet;
+    protected drawer: VexFlowMusicSheetDrawer;
+    protected drawBoundingBox: string;
+    protected drawSkyLine: boolean;
+    protected drawBottomLine: boolean;
+    protected graphic: GraphicalMusicSheet;
+    protected renderingManager: SheetRenderingManager;
+    protected interactionManager: AbstractDisplayInteractionManager;
+    protected drawingParameters: DrawingParameters;
+    protected rules: EngravingRules;
+    protected autoResizeEnabled: boolean;
     private resizeHandlerAttached: boolean;
-    private followCursor: boolean;
+    protected followCursor: boolean;
+    protected OnXMLRead: Function;
     public set PlaybackManager(manager: PlaybackManager) {
         if (this.renderingManager) {
             this.renderingManager.PlaybackManager = manager;
@@ -148,8 +149,6 @@ export class OpenSheetMusicDisplay {
         }
     }
 
-    private OnXMLRead: Function;
-
     /**
      * Load a MusicXML file
      * @param content is either the url of a file, or the root node of a MusicXML document, or the string content of a .xml/.mxl file
@@ -324,7 +323,7 @@ export class OpenSheetMusicDisplay {
         //console.log("[OSMD] render finished");
     }
 
-    private createOrRefreshRenderBackend(): void {
+    protected createOrRefreshRenderBackend(): void {
         // console.log("[OSMD] createOrRefreshRenderBackend()");
 
         // Remove old backends
@@ -435,8 +434,7 @@ export class OpenSheetMusicDisplay {
             return;
         }
         this.OnXMLRead = function(xml): string {return xml;};
-        if (options.onXMLRead)
-        {
+        if (options.onXMLRead) {
             this.OnXMLRead = options.onXMLRead;
         }
         if (options.drawingParameters) {
@@ -727,7 +725,7 @@ export class OpenSheetMusicDisplay {
      * Initialize this object to default values
      * FIXME: Probably unnecessary
      */
-    private reset(): void {
+    protected reset(): void {
         if (this.drawingParameters.drawCursors && this.cursor) {
             this.cursor.hide();
         }
@@ -739,7 +737,7 @@ export class OpenSheetMusicDisplay {
     /**
      * Attach the appropriate handler to the window.onResize event
      */
-    private autoResize(): void {
+    protected autoResize(): void {
 
         const self: OpenSheetMusicDisplay = this;
         this.handleResize(
@@ -774,7 +772,7 @@ export class OpenSheetMusicDisplay {
      * @param startCallback is the function called when resizing starts
      * @param endCallback is the function called when resizing (kind-of) ends
      */
-    private handleResize(startCallback: () => void, endCallback: () => void): void {
+    protected handleResize(startCallback: () => void, endCallback: () => void): void {
         let rtime: number;
         let timeout: number = undefined;
         const delta: number = 200;

+ 21 - 13
src/VexFlowPatch/src/staverepetition.js

@@ -119,25 +119,33 @@ export class Repetition extends StaveModifier {
       // Offset Coda text to right of stave beginning
       text_x = this.x + stave.options.vertical_bar_width;
       symbol_x = text_x + ctx.measureText(text).width + 12;
-    } else if (this.symbol_type === Repetition.type.TO_CODA) {
-      // text_x = x + this.x + this.x_shift + stave.options.vertical_bar_width;
-      // symbol_x = text_x + ctx.measureText(text).width + 12;
-
+    } else {
       // VexFlowPatch: fix placement, like for DS_AL_CODA
       this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
       // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
       text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
       symbol_x = text_x + ctx.measureText(text).width + 12;
-    } else if (this.symbol_type === Repetition.type.DS_AL_CODA) {
-      this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
-      // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
-      text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
-      symbol_x = text_x + ctx.measureText(text).width + 12;
-    } else {
-      // Offset Signo text to left stave end
-      symbol_x = this.x + x + stave.width - 5 + this.x_shift;
-      text_x = symbol_x - + ctx.measureText(text).width - 12;
     }
+    // earlier, we applied this to most elements individually, not necessary:
+    // } else if (this.symbol_type === Repetition.type.TO_CODA) {
+    //   // text_x = x + this.x + this.x_shift + stave.options.vertical_bar_width;
+    //   // symbol_x = text_x + ctx.measureText(text).width + 12;
+
+    //   // VexFlowPatch: fix placement, like for DS_AL_CODA
+    //   this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
+    //   // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
+    //   text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
+    //   symbol_x = text_x + ctx.measureText(text).width + 12;
+    // } else if (this.symbol_type === Repetition.type.DS_AL_CODA) {
+    //   this.x_shift = -(text_x + ctx.measureText(text).width + 12 + stave.options.vertical_bar_width + 12);
+    //   // TO_CODA and DS_AL_CODA draw in the next measure without this x_shift, not sure why not for other symbols.
+    //   text_x = this.x + this.x_shift + stave.options.vertical_bar_width;
+    //   symbol_x = text_x + ctx.measureText(text).width + 12;
+    // } else {
+    //   // Offset Signo text to left stave end
+    //   symbol_x = this.x + x + stave.width - 5 + this.x_shift;
+    //   text_x = symbol_x - + ctx.measureText(text).width - 12;
+    // }
 
     const y = stave.getYForTopText(stave.options.num_lines) + this.y_shift + 25;
     if (draw_coda) {

+ 1 - 1
test/MusicalScore/ScoreIO/Key_Test.ts

@@ -107,7 +107,7 @@ describe("MusicXML parser for element 'key'", () => {
         done();
       });
 
-      it("reads key signature B-major", (done: Mocha.Done) => {
+      it("reads key signature Bb-major", (done: Mocha.Done) => {
         const keyInstruction: KeyInstruction = getMusicSheetWithKey(-2, "major").getFirstSourceMeasure().getKeyInstruction(0);
         chai.expect(keyInstruction.Key).to.equal(-2);
         chai.expect(keyInstruction.Mode).to.equal(KeyModeEnum.major);

文件差異過大導致無法顯示
+ 2 - 0
test/data/test_percussion_display_step_from_xml.musicxml


+ 453 - 0
test/data/test_tabs_4_strings_bass_guitar.musicxml

@@ -0,0 +1,453 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
+<score-partwise version="3.0">
+  <work>
+    <work-title>Test - Tabs 4 Strings Bass Guitar</work-title>
+  </work>
+  <part-list>
+    <score-part id="P1">
+      <part-name>Bass Guitar</part-name>
+      <score-instrument id="P1-I3">
+        <instrument-name>Bass Guitar</instrument-name>
+      </score-instrument>
+      <midi-instrument id="P1-I3">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>80</volume>
+        <pan>0</pan>
+      </midi-instrument>
+    </score-part>
+  </part-list>
+  <part id="P1">
+    <measure number="1">
+      <attributes>
+        <divisions>24</divisions>
+        <key>
+          <fifths>2</fifths>
+          <mode>major</mode>
+        </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+        </time>
+        <staves>1</staves>
+        <clef number="1">
+          <sign>TAB</sign>
+          <line>5</line>
+        </clef>
+        <staff-details>
+          <staff-lines>4</staff-lines>
+          <staff-tuning line="1">
+            <tuning-step>E</tuning-step>
+            <tuning-octave>1</tuning-octave>
+          </staff-tuning>
+          <staff-tuning line="2">
+            <tuning-step>A</tuning-step>
+            <tuning-octave>1</tuning-octave>
+          </staff-tuning>
+          <staff-tuning line="3">
+            <tuning-step>D</tuning-step>
+            <tuning-octave>2</tuning-octave>
+          </staff-tuning>
+          <staff-tuning line="4">
+            <tuning-step>G</tuning-step>
+            <tuning-octave>2</tuning-octave>
+          </staff-tuning>
+        </staff-details>
+      </attributes>
+      <print>
+        <staff-layout number="2">
+          <staff-distance>70</staff-distance>
+        </staff-layout>
+        <measure-numbering>none</measure-numbering>
+      </print>
+      <direction placement="above">
+        <direction-type>
+          <metronome parentheses="yes">
+            <beat-unit>quarter</beat-unit>
+            <per-minute>70</per-minute>
+          </metronome>
+        </direction-type>
+      </direction>
+      <note>
+        <rest/>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <staff>1</staff>
+      </note>
+      <note>
+        <pitch>
+          <step>A</step>
+          <alter>0</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>21</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>E</step>
+          <alter>0</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>2</string>
+            <fret>21</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>E</step>
+          <alter>0</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>3</string>
+            <fret>22</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>B</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>4</string>
+            <fret>22</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>E</step>
+          <alter>0</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>17</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>C</step>
+          <alter>0</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>2</string>
+            <fret>2</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>18</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>D</step>
+          <alter>1</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>2</string>
+            <fret>1</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <alter>0</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>4</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>D</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>7</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>D</step>
+          <alter>0</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>19</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>18</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>16</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>A</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>14</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <chord/>
+        <pitch>
+          <step>A</step>
+          <alter>0</alter>
+          <octave>2</octave>
+        </pitch>
+        <duration>6</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>2</string>
+            <fret>7</fret>
+          </technical>
+        </notations>
+      </note>
+    </measure>
+    <measure number="2">
+      <note>
+        <pitch>
+          <step>C</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>18</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>F</step>
+          <alter>1</alter>
+          <octave>4</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>23</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>A</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <technical>
+            <string>1</string>
+            <fret>14</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>48</duration>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <tied type="start"/>
+          <technical>
+            <string>1</string>
+            <fret>16</fret>
+          </technical>
+        </notations>
+      </note>
+      <note>
+        <pitch>
+          <step>B</step>
+          <alter>0</alter>
+          <octave>3</octave>
+        </pitch>
+        <duration>12</duration>
+        <tie type="stop"/>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>none</stem>
+        <staff>1</staff>
+        <notations>
+          <tied type="stop"/>
+          <technical>
+            <string>1</string>
+            <fret>16</fret>
+          </technical>
+        </notations>
+      </note>
+    </measure>
+  </part>
+</score-partwise>

+ 160 - 0
test/data/test_tie_directions_up_down.musicxml

@@ -0,0 +1,160 @@
+<?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 - Tie directions - Up Down</work-title>
+    </work>
+  <identification>
+    <encoding>
+      <software>MuseScore 3.6.0</software>
+      <encoding-date>2021-04-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>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">Slurs direction</credit-words>
+    </credit>
+  <part-list>
+    <part-group type="start" number="1">
+      <group-symbol>brace</group-symbol>
+      </part-group>
+    <score-part id="P1">
+      <part-name>Piano</part-name>
+      <part-abbreviation>Pno.</part-abbreviation>
+      <score-instrument id="P1-I1">
+        <instrument-name>Piano</instrument-name>
+        </score-instrument>
+      <midi-device id="P1-I1" port="1"></midi-device>
+      <midi-instrument id="P1-I1">
+        <midi-channel>1</midi-channel>
+        <midi-program>1</midi-program>
+        <volume>78.7402</volume>
+        <pan>0</pan>
+        </midi-instrument>
+      </score-part>
+    </part-list>
+  <part id="P1">
+    <measure number="1" width="391.21">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>50.00</left-margin>
+            <right-margin>587.36</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>1</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="80.72" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        <notations>
+          <tied type="start"/>
+          <technical>
+            <fingering>1</fingering>
+            </technical>
+          </notations>
+        </note>
+      <note default-x="92.62" default-y="-10.00">
+        <chord/>
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="start"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        <notations>
+          <tied type="start"/>
+          <technical>
+            <fingering>2</fingering>
+            </technical>
+          </notations>
+        </note>
+      <note default-x="224.69" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="stop"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        <notations>
+          <tied type="stop"/>
+          <fermata type="upright" relative-y="5.00"/>
+          </notations>
+        </note>
+      <note default-x="236.59" default-y="-10.00">
+        <chord/>
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>2</duration>
+        <tie type="stop"/>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        <notations>
+          <tied type="stop"/>
+          </notations>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>

部分文件因文件數量過多而無法顯示