浏览代码

fix(Pedals): show symbols instead of lines when given, pedal change (V), fix positioning, etc.

code by @fredmeister77
Simon 3 年之前
父节点
当前提交
1cd0bd603b

+ 107 - 64
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -1038,7 +1038,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         endStaffEntry = endMeasure.staffEntries[endMeasure.staffEntries.length - 1];
         // TODO can be undefined if no notes in end measure
       }
-      graphicalPedal.setStartNote(startStaffEntry);
+      if(!graphicalPedal.setStartNote(startStaffEntry)){
+        return;
+      }
 
       if (endStaffLine !== startStaffLine) {
         if(graphicalPedal.pedalSymbol === MusicSymbol.PEDAL_SYMBOL){
@@ -1052,7 +1054,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           // pedal starts on the first measure
           const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape);
           const firstNote: GraphicalStaffEntry = nextPedalFirstMeasure.staffEntries[0];
-          nextPedal.setStartNote(firstNote);
+          if(!nextPedal.setStartNote(firstNote)){
+            return;
+        }
           nextPedal.setEndNote(endStaffEntry);
           graphicalPedal.setEndMeasure(endMeasure);
           endStaffLine.Pedals.push(nextPedal);
@@ -1067,6 +1071,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           const lastNoteOfFirstShift: GraphicalStaffEntry = lastMeasureOfFirstShift.staffEntries[lastMeasureOfFirstShift.staffEntries.length - 1];
           graphicalPedal.setEndNote(lastNoteOfFirstShift);
           graphicalPedal.setEndMeasure(endMeasure);
+          graphicalPedal.ChangeEnd = false;
 
           const systemsInBetweenCount: number = endStaffLine.ParentMusicSystem.Id - startStaffLine.ParentMusicSystem.Id;
           if (systemsInBetweenCount > 0) {
@@ -1077,13 +1082,21 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
               const nextPedalStaffline: StaffLine = nextPedalMusicSystem.StaffLines[staffIndex];
               const nextPedalFirstMeasure: GraphicalMeasure = nextPedalStaffline.Measures[0];
               let nextOpenEnd: boolean = false;
+              let nextChangeEndFromParent: boolean = false;
               if (currentCount < systemsInBetweenCount) {
                 nextOpenEnd = true;
+              } else {
+                nextChangeEndFromParent = true;
               }
               currentCount++;
               // pedal starts on the first measure
               const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape, true, nextOpenEnd);
-
+              nextPedal.ChangeBegin = false;
+              if(nextChangeEndFromParent){
+                nextPedal.ChangeEnd = pedal.ChangeEnd;
+              } else {
+                nextPedal.ChangeEnd = false;
+              }
               let nextPedalLastMeasure: GraphicalMeasure = nextPedalStaffline.Measures[nextPedalStaffline.Measures.length - 1];
               const firstNote: GraphicalStaffEntry = nextPedalFirstMeasure.staffEntries[0];
               let lastNote: GraphicalStaffEntry = nextPedalLastMeasure.staffEntries[nextPedalLastMeasure.staffEntries.length - 1];
@@ -1093,8 +1106,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
                 nextPedalLastMeasure = endMeasure;
                 lastNote = endStaffEntry;
               }
-
-              nextPedal.setStartNote(firstNote);
+              if(!nextPedal.setStartNote(firstNote)){
+                break;
+              }
               nextPedal.setEndNote(lastNote);
               graphicalPedal.setEndMeasure(endMeasure);
               nextPedalStaffline.Pedals.push(nextPedal);
@@ -1301,8 +1315,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       //Just for shorthand. Easier readability below
       const PEDAL_STYLES_ENUM: any = Vex.Flow.PedalMarking.Styles;
       const pedalMarking: any = vfPedal.getPedalMarking();
-      //VF adds 3 to the line that is set for rendering pedal
-      const bottomLineYOffset: number = (pedalMarking.line + 3);
+      //VF adds 3 lines to whatever the pedal line is set to.
+      //VF also measures from the bottom line, whereas our bottom line is from the top staff line
+      const yLineForPedalMarking: number = (pedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
       //VF Uses a margin offset for rendering. Take this into account
       const pedalMarkingMarginXOffset: number = pedalMarking.render_options.text_margin_right / 10;
       //TODO: Most of this should be in the bounding box calculation
@@ -1315,39 +1330,55 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         startX -= 1;
       }
       let stopX: number = undefined;
+      let footroom: number = (parentStaffline.StaffLines.length - 1);
+      //Find the highest foot room in our staffline
+      for (const otherPedal of parentStaffline.Pedals) {
+        const vfOtherPedal: VexFlowPedal = otherPedal as VexFlowPedal;
+        const otherPedalMarking: any = vfOtherPedal.getPedalMarking();
+        const yLineForOtherPedalMarking: number = (otherPedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
+        footroom = Math.max(yLineForOtherPedalMarking, footroom);
+      }
       //We have the two seperate symbols, with two bounding boxes
       if (vfPedal.EndSymbolPositionAndShape) {
+        const symbolHalfHeight: number = pedalMarking.render_options.glyph_point_size / 20;
         //Width of the Ped. symbol
         stopX = startX + 3.4;
         const startX2: number = endBbox.AbsolutePosition.x - pedalMarkingMarginXOffset;
         //Width of * symbol
         const stopX2: number = startX2 + 1.5;
 
-        let footroom: number = parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX);
-        footroom = Math.max((vfPedal.startNote.getStave().options as any).bottom_text_position, footroom);
+        footroom = Math.max(parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX), footroom);
+        footroom = Math.max(yLineForPedalMarking + symbolHalfHeight * 2, footroom);
         const footroom2: number = parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX2, stopX2);
         //If Depress text is set, means we are not rendering the begin label (we are just rendering the end one)
         if (!vfPedal.DepressText) {
           footroom = Math.max(footroom, footroom2);
         }
-        if (startVfVoiceEntry.parentStaffEntry.parentMeasure !== endVfVoiceEntry.parentStaffEntry.parentMeasure && vfPedal.endNote) {
-          //TODO: look into using the vexflow setLine instead of modifying the whole stave if possible
-          footroom = Math.max((vfPedal.endNote.getStave().options as any).bottom_text_position, footroom);
-          (vfPedal.endNote.getStave().options as any).bottom_text_position = footroom;
-        }
-        (vfPedal.startNote.getStave().options as any).bottom_text_position = footroom;
-        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + bottomLineYOffset);
-        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX2, stopX2, footroom + bottomLineYOffset);
+        vfPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
+        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + symbolHalfHeight);
+        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX2, stopX2, footroom + symbolHalfHeight);
       } else {
-        switch (pedalMarking.style) {
-          case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
-          case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
-          case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
-            stopX = endBbox.AbsolutePosition.x + endBbox.BorderRight - pedalMarkingMarginXOffset;
-          break;
-          default:
-            stopX = endBbox.AbsolutePosition.x + endBbox.BorderLeft - pedalMarkingMarginXOffset;
-          break;
+        const bracketHeight: number = pedalMarking.render_options.bracket_height / 10;
+
+        if(pedalMarking.EndsStave){
+          if(endVfVoiceEntry){
+            stopX = endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.AbsolutePosition.x +
+              endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.Size.width - pedalMarkingMarginXOffset;
+
+          } else {
+            stopX = endBbox.AbsolutePosition.x + endBbox.Size.width;
+          }
+        } else {
+          switch (pedalMarking.style) {
+            case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
+            case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
+            case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
+              stopX = endBbox.AbsolutePosition.x + endBbox.BorderRight - pedalMarkingMarginXOffset;
+            break;
+            default:
+              stopX = endBbox.AbsolutePosition.x + endBbox.BorderLeft - pedalMarkingMarginXOffset;
+            break;
+          }
         }
         //Take into account in-staff clefs associated with the staff entry (they modify the bounding box position)
         const vfClefBefore: Vex.Flow.ClefNote = (endVfVoiceEntry?.parentStaffEntry as VexFlowStaffEntry)?.vfClefBefore;
@@ -1356,57 +1387,69 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           stopX += clefWidth;
         }
 
-        let footroom: number = parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX);
+        footroom = Math.max(parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX), footroom);
         if (footroom === Infinity) { // will cause Vexflow error
           return;
         }
         //Whatever is currently lower - the set render height of the begin vf stave, the set render height of the end vf stave,
         //or the bottom line. Use that as the render height of both staves
-        footroom = Math.max(footroom, (vfPedal.startNote.getStave().options as any).bottom_text_position);
-        if (startVfVoiceEntry.parentStaffEntry.parentMeasure !== endVfVoiceEntry?.parentStaffEntry.parentMeasure && vfPedal.endNote) {
-          footroom = Math.max(footroom, (vfPedal.endNote.getStave().options as any).bottom_text_position);
-          (vfPedal.endNote.getStave().options as any).bottom_text_position = footroom;
-        }
-        (vfPedal.startNote.getStave().options as any).bottom_text_position = footroom;
+        footroom = Math.max(footroom, yLineForPedalMarking + bracketHeight);
+        vfPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
         if (startX > stopX) { // TODO hotfix for skybottomlinecalculator after pedal no endNote fix
           const newStart: number = stopX;
           stopX = startX;
           startX = newStart;
         }
-        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + bottomLineYOffset);
-        //This list will contain only previously processed Pedals - aka, those previous in the sheet music
-        for (const otherPedal of parentStaffline.Pedals) {
-          const vfOtherPedal: VexFlowPedal = otherPedal as VexFlowPedal;
-          //If there is a pedal that ends on our begin measure, we need to update its begin measure to render at the new height
-          //So we don't get lopsided pedal brackets
-          if (vfOtherPedal.endVfVoiceEntry?.parentStaffEntry.parentMeasure === startVfVoiceEntry.parentStaffEntry.parentMeasure) {
-            //Since we've already checked for max height for this stave above, we are certain that this pedal will not render higher than it should
-            //(e.g. colliding with stuff)
-            (vfOtherPedal.startNote.getStave().options as any).bottom_text_position = footroom;
-            const otherPedalMarking: any = vfOtherPedal.getPedalMarking();
-            const otherPedalMarkingMarginXOffset: number = otherPedalMarking.render_options.text_margin_right / 10;
-            const otherPedalStartX: number = vfOtherPedal.startVfVoiceEntry.PositionAndShape.AbsolutePosition.x - otherPedalMarkingMarginXOffset;
-            let otherPedalStopX: number = undefined;
-            switch (otherPedalMarking.style) {
-              case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
-              case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
-              case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
-                otherPedalStopX = vfOtherPedal.endVfVoiceEntry.PositionAndShape.AbsolutePosition.x +
-                                  vfOtherPedal.endVfVoiceEntry.PositionAndShape.BorderRight - otherPedalMarkingMarginXOffset;
-              break;
-              default:
-                otherPedalStopX = vfOtherPedal.endVfVoiceEntry.PositionAndShape.AbsolutePosition.x +
-                vfOtherPedal.endVfVoiceEntry.PositionAndShape.BorderLeft - otherPedalMarkingMarginXOffset;
-              break;
+        parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + bracketHeight);
+      }
+      //If our current pedal is below the other pedals in this staffline, set them all to this height
+      for (const otherPedal of parentStaffline.Pedals) {
+        const vfOtherPedal: VexFlowPedal = otherPedal as VexFlowPedal;
+        const otherPedalMarking: any = vfOtherPedal.getPedalMarking();
+        const yLineForOtherPedalMarking: number = (otherPedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
+        //Only do these changes if current footroom is higher
+        if(footroom > yLineForOtherPedalMarking) {
+          const otherPedalMarkingMarginXOffset: number = otherPedalMarking.render_options.text_margin_right / 10;
+          const otherPedalStartX: number = vfOtherPedal.startVfVoiceEntry.PositionAndShape.AbsolutePosition.x - otherPedalMarkingMarginXOffset;
+          let otherPedalStopX: number = undefined;
+          vfOtherPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
+          let otherPedalEndBBox: BoundingBox = vfOtherPedal.endVfVoiceEntry?.PositionAndShape;
+          if (!otherPedalEndBBox) {
+            otherPedalEndBBox = vfOtherPedal.endMeasure.PositionAndShape;
+          }
+          if (vfOtherPedal.EndSymbolPositionAndShape) {
+            const otherSymbolHalfHeight: number = pedalMarking.render_options.glyph_point_size / 20;
+            //Width of the Ped. symbol
+            otherPedalStopX = otherPedalStartX + 3.4;
+            const otherPedalStartX2: number = otherPedalEndBBox.AbsolutePosition.x - otherPedalMarkingMarginXOffset;
+            //Width of * symbol
+            const otherPedalStopX2: number = otherPedalStartX2 + 1.5;
+            parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX, otherPedalStopX, footroom + otherSymbolHalfHeight);
+            parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX2, otherPedalStopX2, footroom + otherSymbolHalfHeight);
+          } else {
+            const otherPedalBracketHeight: number = otherPedalMarking.render_options.bracket_height / 10;
+
+            if(otherPedalMarking.EndsStave){
+                otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.Size.width - otherPedalMarkingMarginXOffset;
+            } else {
+              switch (pedalMarking.style) {
+                case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
+                case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
+                case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
+                  otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.BorderRight - otherPedalMarkingMarginXOffset;
+                break;
+                default:
+                  otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.BorderLeft - otherPedalMarkingMarginXOffset;
+                break;
+              }
             }
             //Take into account in-staff clefs associated with the staff entry (they modify the bounding box position)
-            const otherPedalVfClefBefore: Vex.Flow.ClefNote = (vfOtherPedal.endVfVoiceEntry.parentStaffEntry as VexFlowStaffEntry).vfClefBefore;
-            if (otherPedalVfClefBefore) {
-              const clefWidth: number = otherPedalVfClefBefore.getWidth() / 10;
-              stopX += clefWidth;
+            const vfOtherClefBefore: Vex.Flow.ClefNote = (vfOtherPedal.endVfVoiceEntry?.parentStaffEntry as VexFlowStaffEntry)?.vfClefBefore;
+            if (vfOtherClefBefore) {
+              const otherClefWidth: number = vfOtherClefBefore.getWidth() / 10;
+              otherPedalStopX += otherClefWidth;
             }
-            const otherPedalBottomLineYOffset: number = (pedalMarking.line + 3);
-            parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX, otherPedalStopX, footroom + otherPedalBottomLineYOffset);
+            parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX, otherPedalStopX, footroom + otherPedalBracketHeight);
           }
         }
       }

+ 20 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowPedal.ts

@@ -7,7 +7,6 @@ import { Pedal } from "../../VoiceData/Expressions/ContinuousExpressions/Pedal";
 import { MusicSymbol } from "../MusicSymbol";
 import { GraphicalMeasure } from "../GraphicalMeasure";
 import { VexFlowMeasure } from "./VexFlowMeasure";
-
 /**
  * The vexflow adaptation of a pedal marking
  */
@@ -22,6 +21,9 @@ export class VexFlowPedal extends GraphicalPedal {
     public startVfVoiceEntry: VexFlowVoiceEntry;
     public endVfVoiceEntry: VexFlowVoiceEntry;
     public endMeasure: GraphicalMeasure;
+    public ChangeBegin: boolean = false;
+    public ChangeEnd: boolean = false;
+    private line: number = -3;
 
     public EndSymbolPositionAndShape: BoundingBox = undefined;
     /**
@@ -31,6 +33,8 @@ export class VexFlowPedal extends GraphicalPedal {
      */
     constructor(pedal: Pedal, parent: BoundingBox, openBegin: boolean = false, openEnd: boolean = false) {
         super(pedal, parent);
+        this.ChangeBegin = pedal.ChangeBegin;
+        this.ChangeEnd = pedal.ChangeEnd;
         switch (this.pedalSymbol) {
             case MusicSymbol.PEDAL_SYMBOL:
                 //This renders the pedal symbols in VF.
@@ -68,6 +72,9 @@ export class VexFlowPedal extends GraphicalPedal {
      * @param graphicalStaffEntry the staff entry that holds the start note
      */
     public setStartNote(graphicalStaffEntry: GraphicalStaffEntry): boolean {
+        if(!graphicalStaffEntry){
+            return false;
+        }
         for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
             const vve: VexFlowVoiceEntry = (gve as VexFlowVoiceEntry);
             if (vve?.vfStaveNote) {
@@ -106,6 +113,10 @@ export class VexFlowPedal extends GraphicalPedal {
     public CalculateBoundingBox(): void {
         //TODO?
     }
+
+    public setLine(line: number): void {
+        this.line = line;
+    }
     /**
      * Get the actual vexflow Pedal Marking used for drawing
      */
@@ -115,8 +126,15 @@ export class VexFlowPedal extends GraphicalPedal {
             (pedalMarking as any).setEndStave((this.endMeasure as VexFlowMeasure).getVFStave());
         }
         pedalMarking.setStyle(this.vfStyle);
-        pedalMarking.setLine(-1);
+        pedalMarking.setLine(this.line);
         pedalMarking.setCustomText(this.DepressText, this.ReleaseText);
+        //If our end note is at the end of a stave, set that value
+        if(this.endVfVoiceEntry?.parentStaffEntry === this.endVfVoiceEntry?.parentStaffEntry?.parentMeasure?.staffEntries.last() ||
+        !this.endVfVoiceEntry){
+                (pedalMarking as any).EndsStave = true;
+        }
+        (pedalMarking as any).ChangeBegin = this.ChangeBegin;
+        (pedalMarking as any).ChangeEnd = this.ChangeEnd;
         return pedalMarking;
     }
 }

+ 21 - 1
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -314,9 +314,17 @@ export class ExpressionReader {
                 try {
                     if (pedalNode.attribute("line")?.value === "yes") {
                         line = true;
+                    } else if (pedalNode.attribute("line")?.value === "no"){
+                        line = false;
+                        //No on line implies yes on sign
+                        sign = true;
                     }
                     if (pedalNode.attribute("sign")?.value === "yes") {
                         sign = true;
+                    } else if (pedalNode.attribute("sign")?.value === "no"){
+                        sign = false;
+                        //No on sign implies yes on line
+                        line = true;
                     }
                     switch (pedalNode.attribute("type").value) {
                         case "start":
@@ -337,7 +345,19 @@ export class ExpressionReader {
                             }
                         break;
                         case "change":
-                            //VF doesn't seem to support this V marking
+                            //Ignore non-line pedals
+                            if (this.openPedal && this.openPedal.IsLine) {
+                                this.openPedal.ChangeEnd = true;
+                                this.createNewMultiExpressionIfNeeded(currentMeasure);
+                                this.getMultiExpression.PedalEnd = this.openPedal;
+                                this.openPedal.ParentEndMultiExpression = this.getMultiExpression;
+
+                                this.createNewMultiExpressionIfNeeded(currentMeasure);
+                                this.openPedal = new Pedal(line, sign);
+                                this.openPedal.ChangeBegin = true;
+                                this.getMultiExpression.PedalStart = this.openPedal;
+                                this.openPedal.ParentStartMultiExpression = this.getMultiExpression;
+                            }
                         break;
                         case "continue":
                         break;

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

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

+ 366 - 316
src/VexFlowPatch/src/pedalmarking.js

@@ -1,317 +1,367 @@
-// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
-//
-// ## Description
-//
-// This file implements different types of pedal markings. These notation
-// elements indicate to the performer when to depress and release the a pedal.
-//
-// In order to create "Sostenuto", and "una corda" markings, you must set
-// custom text for the release/depress pedal markings.
-
-import { Vex } from './vex';
-import { Element } from './element';
-import { Glyph } from './glyph';
-
-// To enable logging for this class. Set `Vex.Flow.PedalMarking.DEBUG` to `true`.
-function L(...args) { if (PedalMarking.DEBUG) Vex.L('Vex.Flow.PedalMarking', args); }
-
-// Draws a pedal glyph with the provided `name` on a rendering `context`
-// at the coordinates `x` and `y. Takes into account the glyph data
-// coordinate shifts.
-function drawPedalGlyph(name, context, x, y, point) {
-  const glyph_data = PedalMarking.GLYPHS[name];
-  const glyph = new Glyph(glyph_data.code, point);
-  glyph.render(context, x + glyph_data.x_shift, y + glyph_data.y_shift);
-}
-
-export class PedalMarking extends Element {
-  // Glyph data
-  static get GLYPHS() {
-    return {
-      'pedal_depress': {
-        code: 'v36',
-        x_shift: -10,
-        y_shift: 0,
-      },
-      'pedal_release': {
-        code: 'v5d',
-        x_shift: -2,
-        y_shift: 3,
-      },
-    };
-  }
-
-  static get Styles() {
-    return {
-      TEXT: 1,
-      BRACKET: 2,
-      MIXED: 3,
-      MIXED_OPEN_END: 4,
-      BRACKET_OPEN_BEGIN: 5,
-      BRACKET_OPEN_END: 6,
-      BRACKET_OPEN_BOTH: 7
-    };
-  }
-
-  static get StylesString() {
-    return {
-      text: PedalMarking.Styles.TEXT,
-      bracket: PedalMarking.Styles.BRACKET,
-      mixed: PedalMarking.Styles.MIXED,
-      mixed_open_end: PedalMarking.Styles.MIXED_OPEN_END,
-      bracket_open_begin: PedalMarking.Styles.BRACKET_OPEN_BEGIN,
-      bracket_open_end: PedalMarking.Styles.BRACKET_OPEN_END,
-      bracket_open_both: PedalMarking.Styles.BRACKET_OPEN_BOTH
-    };
-  }
-
-  // Create a sustain pedal marking. Returns the defaults PedalMarking.
-  // Which uses the traditional "Ped" and "*"" markings.
-  static createSustain(notes) {
-    const pedal = new PedalMarking(notes);
-    return pedal;
-  }
-
-  // Create a sostenuto pedal marking
-  static createSostenuto(notes) {
-    const pedal = new PedalMarking(notes);
-    pedal.setStyle(PedalMarking.Styles.MIXED);
-    pedal.setCustomText('Sost. Ped.');
-    return pedal;
-  }
-
-  // Create an una corda pedal marking
-  static createUnaCorda(notes) {
-    const pedal = new PedalMarking(notes);
-    pedal.setStyle(PedalMarking.Styles.TEXT);
-    pedal.setCustomText('una corda', 'tre corda');
-    return pedal;
-  }
-
-  // ## Prototype Methods
-  constructor(notes) {
-    super();
-    this.setAttribute('type', 'PedalMarking');
-
-    this.notes = notes;
-    this.style = PedalMarking.TEXT;
-    this.line = 0;
-
-    // Custom text for the release/depress markings
-    this.custom_depress_text = '';
-    this.custom_release_text = '';
-
-    this.font = {
-      family: 'Times New Roman',
-      size: 12,
-      weight: 'italic bold',
-    };
-
-    this.render_options = {
-      bracket_height: 10,
-      text_margin_right: 6,
-      bracket_line_width: 1,
-      glyph_point_size: 40,
-      color: 'black',
-    };
-  }
-
-  setEndStave(stave) {
-    this.endStave = stave;
-  }
-
-  // Set custom text for the `depress`/`release` pedal markings. No text is
-  // set if the parameter is falsy.
-  setCustomText(depress, release) {
-    this.custom_depress_text = depress || '';
-    this.custom_release_text = release || '';
-    return this;
-  }
-
-  // Set the pedal marking style
-  setStyle(style) {
-    if (style < 1 && style > 3)  {
-      throw new Vex.RERR('InvalidParameter', 'The style must be one found in PedalMarking.Styles');
-    }
-
-    this.style = style;
-    return this;
-  }
-
-  // Set the staff line to render the markings on
-  setLine(line) { this.line = line; return this; }
-
-  // Draw the bracket based pedal markings
-  drawBracketed() {
-    const ctx = this.context;
-    let is_pedal_depressed = false;
-    let prev_x;
-    let prev_y;
-    const pedal = this;
-
-    // Iterate through each note
-    this.notes.forEach((note, index, notes) => {
-      // Each note triggers the opposite pedal action
-      is_pedal_depressed = !is_pedal_depressed;
-      // Get the initial coordinates for the note
-      let x = 0;
-      if (note) {
-        x = note.getNoteHeadBeginX();
-      }
-      if (!note) {
-        x = this.endStave.end_x;
-      } else if (!is_pedal_depressed) {
-        switch(pedal.style) {
-          case PedalMarking.Styles.BRACKET_OPEN_END:
-          case PedalMarking.Styles.BRACKET_OPEN_BOTH:
-          case PedalMarking.Styles.MIXED_OPEN_END:
-            x = note.getNoteHeadEndX();
-          break;
-          default:
-            x -= pedal.render_options.text_margin_right;
-          break;
-        }
-      } else {
-        switch(pedal.style) {
-          case PedalMarking.Styles.BRACKET_OPEN_BEGIN:
-          case PedalMarking.Styles.BRACKET_OPEN_BOTH:
-            x -= pedal.render_options.text_margin_right;
-          break;
-        }
-      }
-
-      let stave = this.endStave; // if !note
-      if (note) {
-        stave = note.getStave();
-      }
-      let y = stave.getYForBottomText(pedal.line + 3);
-      if (prev_y) { // compiler complains if we shorten this
-        if (prev_y > y) { // don't slope pedal marking upwards (nonstandard)
-          y = prev_y;
-        }
-      }
-
-      // Throw if current note is positioned before the previous note
-      if (x < prev_x) {
-        // TODO this unnecessarily throws for missing endNote fix
-        // throw new Vex.RERR(
-        //   'InvalidConfiguration', 'The notes provided must be in order of ascending x positions'
-        // );
-      }
-
-      // Determine if the previous or next note are the same
-      // as the current note. We need to keep track of this for
-      // when adjustments are made for the release+depress action
-      const next_is_same = notes[index + 1] === note;
-      const prev_is_same = notes[index - 1] === note;
-
-      let x_shift = 0;
-      if (is_pedal_depressed) {
-        // Adjustment for release+depress
-        x_shift =  prev_is_same ? 5 : 0;
-
-        if ((pedal.style === PedalMarking.Styles.MIXED || pedal.style === PedalMarking.Styles.MIXED_OPEN_END) && !prev_is_same) {
-          // For MIXED style, start with text instead of bracket
-          if (pedal.custom_depress_text) {
-            // If we have custom text, use instead of the default "Ped" glyph
-            const text_width = ctx.measureText(pedal.custom_depress_text).width;
-            ctx.fillText(pedal.custom_depress_text, x - (text_width / 2), y);
-            x_shift = (text_width / 2) + pedal.render_options.text_margin_right;
-          } else {
-            // Render the Ped glyph in position
-            drawPedalGlyph('pedal_depress', ctx, x, y, pedal.render_options.glyph_point_size);
-            x_shift = 20 + pedal.render_options.text_margin_right;
-          }
-        } else {
-          // Draw start bracket
-          ctx.beginPath();
-          if (pedal.style === PedalMarking.Styles.BRACKET_OPEN_BEGIN || pedal.style === PedalMarking.Styles.BRACKET_OPEN_BOTH) {
-            ctx.moveTo(x + x_shift, y);
-          } else {
-            ctx.moveTo(x, y - pedal.render_options.bracket_height);
-            ctx.lineTo(x + x_shift, y);
-          }
-          ctx.stroke();
-          ctx.closePath();
-        }
-      } else {
-        // Adjustment for release+depress
-        x_shift = next_is_same ? -5 : 0;
-
-        // Draw end bracket
-        ctx.beginPath();
-        ctx.moveTo(prev_x, prev_y);
-        ctx.lineTo(x + x_shift, y);
-        if (pedal.style !== PedalMarking.Styles.BRACKET_OPEN_END && pedal.style !== PedalMarking.Styles.MIXED_OPEN_END &&
-            pedal.style !== PedalMarking.Styles.BRACKET_OPEN_BOTH) {
-            ctx.lineTo(x, y - pedal.render_options.bracket_height);
-        }
-        ctx.stroke();
-        ctx.closePath();
-      }
-
-      // Store previous coordinates
-      prev_x = x + x_shift;
-      prev_y = y;
-    });
-  }
-
-  // Draw the text based pedal markings. This defaults to the traditional
-  // "Ped" and "*"" symbols if no custom text has been provided.
-  drawText() {
-    const ctx = this.context;
-    let is_pedal_depressed = false;
-    const pedal = this;
-
-    // The glyph point size
-    const point = pedal.render_options.glyph_point_size;
-
-    // Iterate through each note, placing glyphs or custom text accordingly
-    this.notes.forEach(note => {
-      is_pedal_depressed = !is_pedal_depressed;
-      const stave = note.getStave();
-      const x = note.getAbsoluteX();
-      const y = stave.getYForBottomText(pedal.line + 3);
-
-      let text_width = 0;
-      if (is_pedal_depressed) {
-        if (pedal.custom_depress_text) {
-          text_width = ctx.measureText(pedal.custom_depress_text).width;
-          ctx.fillText(pedal.custom_depress_text, x - (text_width / 2), y);
-        } else {
-          drawPedalGlyph('pedal_depress', ctx, x, y, point);
-        }
-      } else {
-        if (pedal.custom_release_text) {
-          text_width = ctx.measureText(pedal.custom_release_text).width;
-          ctx.fillText(pedal.custom_release_text, x - (text_width / 2), y);
-        } else {
-          drawPedalGlyph('pedal_release', ctx, x, y, point);
-        }
-      }
-    });
-  }
-
-  // Render the pedal marking in position on the rendering context
-  draw() {
-    const ctx = this.checkContext();
-    this.setRendered();
-
-    ctx.save();
-    ctx.setStrokeStyle(this.render_options.color);
-    ctx.setFillStyle(this.render_options.color);
-    ctx.setFont(this.font.family, this.font.size, this.font.weight);
-
-    L('Rendering Pedal Marking');
-
-    if (this.style === PedalMarking.Styles.BRACKET || this.style === PedalMarking.Styles.MIXED || this.style === PedalMarking.Styles.MIXED_OPEN_END ||
-        this.style === PedalMarking.Styles.BRACKET_OPEN_BEGIN || this.style === PedalMarking.Styles.BRACKET_OPEN_END || this.style === PedalMarking.Styles.BRACKET_OPEN_BOTH) {
-      ctx.setLineWidth(this.render_options.bracket_line_width);
-      this.drawBracketed();
-    } else if (this.style === PedalMarking.Styles.TEXT) {
-      this.drawText();
-    }
-
-    ctx.restore();
-  }
+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
+//
+// ## Description
+//
+// This file implements different types of pedal markings. These notation
+// elements indicate to the performer when to depress and release the a pedal.
+//
+// In order to create "Sostenuto", and "una corda" markings, you must set
+// custom text for the release/depress pedal markings.
+
+import { Vex } from './vex';
+import { Element } from './element';
+import { Glyph } from './glyph';
+import { StaveModifier } from './stavemodifier';
+
+// To enable logging for this class. Set `Vex.Flow.PedalMarking.DEBUG` to `true`.
+function L(...args) { if (PedalMarking.DEBUG) Vex.L('Vex.Flow.PedalMarking', args); }
+
+// Draws a pedal glyph with the provided `name` on a rendering `context`
+// at the coordinates `x` and `y. Takes into account the glyph data
+// coordinate shifts.
+function drawPedalGlyph(name, context, x, y, point) {
+  const glyph_data = PedalMarking.GLYPHS[name];
+  const glyph = new Glyph(glyph_data.code, point);
+  glyph.render(context, x + glyph_data.x_shift, y + glyph_data.y_shift);
+}
+
+export class PedalMarking extends Element {
+  // Glyph data
+  static get GLYPHS() {
+    return {
+      'pedal_depress': {
+        code: 'v36',
+        x_shift: -10,
+        y_shift: 0,
+      },
+      'pedal_release': {
+        code: 'v5d',
+        x_shift: -2,
+        y_shift: 3,
+      },
+    };
+  }
+
+  static get Styles() {
+    return {
+      TEXT: 1,
+      BRACKET: 2,
+      MIXED: 3,
+      MIXED_OPEN_END: 4,
+      BRACKET_OPEN_BEGIN: 5,
+      BRACKET_OPEN_END: 6,
+      BRACKET_OPEN_BOTH: 7
+    };
+  }
+
+  static get StylesString() {
+    return {
+      text: PedalMarking.Styles.TEXT,
+      bracket: PedalMarking.Styles.BRACKET,
+      mixed: PedalMarking.Styles.MIXED,
+      mixed_open_end: PedalMarking.Styles.MIXED_OPEN_END,
+      bracket_open_begin: PedalMarking.Styles.BRACKET_OPEN_BEGIN,
+      bracket_open_end: PedalMarking.Styles.BRACKET_OPEN_END,
+      bracket_open_both: PedalMarking.Styles.BRACKET_OPEN_BOTH
+    };
+  }
+
+  // Create a sustain pedal marking. Returns the defaults PedalMarking.
+  // Which uses the traditional "Ped" and "*"" markings.
+  static createSustain(notes) {
+    const pedal = new PedalMarking(notes);
+    return pedal;
+  }
+
+  // Create a sostenuto pedal marking
+  static createSostenuto(notes) {
+    const pedal = new PedalMarking(notes);
+    pedal.setStyle(PedalMarking.Styles.MIXED);
+    pedal.setCustomText('Sost. Ped.');
+    return pedal;
+  }
+
+  // Create an una corda pedal marking
+  static createUnaCorda(notes) {
+    const pedal = new PedalMarking(notes);
+    pedal.setStyle(PedalMarking.Styles.TEXT);
+    pedal.setCustomText('una corda', 'tre corda');
+    return pedal;
+  }
+
+  // ## Prototype Methods
+  constructor(notes) {
+    super();
+    this.setAttribute('type', 'PedalMarking');
+    this.EndsStave = false;
+    this.ChangeBegin = false;
+    this.ChangeEnd = false;
+    this.notes = notes;
+    this.style = PedalMarking.TEXT;
+    this.line = 0;
+
+    // Custom text for the release/depress markings
+    this.custom_depress_text = '';
+    this.custom_release_text = '';
+
+    this.font = {
+      family: 'Times New Roman',
+      size: 12,
+      weight: 'italic bold',
+    };
+
+    this.render_options = {
+      bracket_height: 10,
+      text_margin_right: 6,
+      bracket_line_width: 1,
+      glyph_point_size: 40,
+      color: 'black',
+    };
+  }
+
+  setEndStave(stave) {
+    this.endStave = stave;
+    this.endStaveAddedWidth = 0;
+    this.startMargin = 0;
+    this.endMargin = 0;
+    if(Array.isArray(this.endStave.modifiers)){
+      for(let i = 0; i < this.endStave.modifiers.length; i++){
+        let nextMod = this.endStave.modifiers[i];
+        if(nextMod && nextMod.position === StaveModifier.Position.END && nextMod.width){
+          this.endStaveAddedWidth += nextMod.width;
+        }
+      }
+    }
+  }
+
+  // Set custom text for the `depress`/`release` pedal markings. No text is
+  // set if the parameter is falsy.
+  setCustomText(depress, release) {
+    this.custom_depress_text = depress || '';
+    this.custom_release_text = release || '';
+    return this;
+  }
+
+  // Set the pedal marking style
+  setStyle(style) {
+    if (style < 1 && style > 3)  {
+      throw new Vex.RERR('InvalidParameter', 'The style must be one found in PedalMarking.Styles');
+    }
+
+    this.style = style;
+    return this;
+  }
+
+  // Set the staff line to render the markings on
+  setLine(line) { this.line = line; return this; }
+
+  // Draw the bracket based pedal markings
+  drawBracketed() {
+    const ctx = this.context;
+    let is_pedal_depressed = false;
+    let prev_x;
+    let prev_y;
+    const pedal = this;
+    // Iterate through each note
+    this.notes.forEach((note, index, notes) => {
+      // Each note triggers the opposite pedal action
+      is_pedal_depressed = !is_pedal_depressed;
+      // Get the initial coordinates for the note
+      let x = 0;
+      if (note) {
+        //default to note head begin
+        x = note.getNoteHeadBeginX();
+      } else {
+        x = this.endStave.end_x + this.endStaveAddedWidth;
+      }
+
+      //If this pedal doesn't end a stave...
+      if(!this.EndsStave){
+        if(note){
+          //pedal across a single note or just the end note
+          if(!is_pedal_depressed){
+            switch(pedal.style) {
+              case PedalMarking.Styles.BRACKET_OPEN_END:
+              case PedalMarking.Styles.BRACKET_OPEN_BOTH:
+              case PedalMarking.Styles.MIXED_OPEN_END:
+                x = note.getNoteHeadEndX();
+              break;
+              default:
+                if(this.ChangeEnd){
+                  //Start in the middle of the note
+                  x = note.getAbsoluteX();
+                } else {
+                  x = note.getNoteHeadBeginX() - pedal.render_options.text_margin_right;
+                  this.startMargin = -pedal.render_options.text_margin_right;
+                }
+              break;
+            }
+          } else if(this.ChangeBegin){
+            x = note.getAbsoluteX();
+          }
+        }
+      } else {
+        //Ends stave and we are at the end...
+        if(!is_pedal_depressed){
+          //IF we are the end, set the end to the stave end
+          if(note){
+            if(this.ChangeEnd){
+              //Start in the middle of the note
+              x = note.getAbsoluteX();
+            }  else {
+              x = note.getStave().end_x + this.endStaveAddedWidth - pedal.render_options.text_margin_right;
+            }
+          } else {
+            x = this.endStave.end_x + this.endStaveAddedWidth - pedal.render_options.text_margin_right;
+          }
+          
+          this.endMargin = -pedal.render_options.text_margin_right;
+        } else if (this.ChangeBegin){
+          x = note.getAbsoluteX();
+        }
+      }
+
+      let stave = this.endStave; // if !note
+      if (note) {
+        stave = note.getStave();
+      }
+      let y = stave.getYForBottomText(pedal.line + 3);
+      if (prev_y) { // compiler complains if we shorten this
+        if (prev_y > y) { // don't slope pedal marking upwards (nonstandard)
+          y = prev_y;
+        }
+      }
+
+      // Throw if current note is positioned before the previous note
+      if (x < prev_x) {
+        // TODO this unnecessarily throws for missing endNote fix
+        // throw new Vex.RERR(
+        //   'InvalidConfiguration', 'The notes provided must be in order of ascending x positions'
+        // );
+      }
+
+      // Determine if the previous or next note are the same
+      // as the current note. We need to keep track of this for
+      // when adjustments are made for the release+depress action
+      const next_is_same = notes[index + 1] === note;
+      const prev_is_same = notes[index - 1] === note;
+
+      let x_shift = 0;
+      if (is_pedal_depressed) {
+        // Adjustment for release+depress
+        x_shift =  prev_is_same ? 5 : 0;
+
+        if ((pedal.style === PedalMarking.Styles.MIXED || pedal.style === PedalMarking.Styles.MIXED_OPEN_END) && !prev_is_same) {
+          // For MIXED style, start with text instead of bracket
+          if (pedal.custom_depress_text) {
+            // If we have custom text, use instead of the default "Ped" glyph
+            const text_width = ctx.measureText(pedal.custom_depress_text).width;
+            ctx.fillText(pedal.custom_depress_text, x - (text_width / 2), y);
+            x_shift = (text_width / 2) + pedal.render_options.text_margin_right;
+          } else {
+            // Render the Ped glyph in position
+            drawPedalGlyph('pedal_depress', ctx, x, y, pedal.render_options.glyph_point_size);
+            x_shift = 20 + pedal.render_options.text_margin_right;
+          }
+        } else {
+          // Draw start bracket
+          ctx.beginPath();
+          if (pedal.style === PedalMarking.Styles.BRACKET_OPEN_BEGIN || pedal.style === PedalMarking.Styles.BRACKET_OPEN_BOTH) {
+            ctx.moveTo(x + x_shift, y);
+          } else {
+            if(this.ChangeBegin){
+              x += 5;
+            }
+            ctx.moveTo(x, y - pedal.render_options.bracket_height);
+            if(this.ChangeBegin){
+              x += 5;
+            }
+            ctx.lineTo(x + x_shift, y);
+          }
+          ctx.stroke();
+          ctx.closePath();
+        }
+      } else {
+        // Adjustment for release+depress
+        x_shift = next_is_same && !this.EndsStave ? -5 : 0;
+
+        // Draw end bracket
+        ctx.beginPath();
+        ctx.moveTo(prev_x, prev_y);
+        ctx.lineTo(x + x_shift, y);
+        if (pedal.style !== PedalMarking.Styles.BRACKET_OPEN_END && pedal.style !== PedalMarking.Styles.MIXED_OPEN_END &&
+            pedal.style !== PedalMarking.Styles.BRACKET_OPEN_BOTH) {
+            if(this.ChangeEnd){
+              x += 5;
+            }
+            ctx.lineTo(x, y - pedal.render_options.bracket_height);
+        }
+        ctx.stroke();
+        ctx.closePath();
+      }
+
+      // Store previous coordinates
+      prev_x = x + x_shift;
+      prev_y = y;
+    });
+  }
+
+  // Draw the text based pedal markings. This defaults to the traditional
+  // "Ped" and "*"" symbols if no custom text has been provided.
+  drawText() {
+    const ctx = this.context;
+    let is_pedal_depressed = false;
+    const pedal = this;
+
+    // The glyph point size
+    const point = pedal.render_options.glyph_point_size;
+
+    // Iterate through each note, placing glyphs or custom text accordingly
+    this.notes.forEach(note => {
+      is_pedal_depressed = !is_pedal_depressed;
+      const stave = note.getStave();
+      const x = note.getAbsoluteX();
+      const y = stave.getYForBottomText(pedal.line + 3);
+
+      let text_width = 0;
+      if (is_pedal_depressed) {
+        if (pedal.custom_depress_text) {
+          text_width = ctx.measureText(pedal.custom_depress_text).width;
+          ctx.fillText(pedal.custom_depress_text, x - (text_width / 2), y);
+        } else {
+          drawPedalGlyph('pedal_depress', ctx, x, y, point);
+        }
+      } else {
+        if (pedal.custom_release_text) {
+          text_width = ctx.measureText(pedal.custom_release_text).width;
+          ctx.fillText(pedal.custom_release_text, x - (text_width / 2), y);
+        } else {
+          drawPedalGlyph('pedal_release', ctx, x, y, point);
+        }
+      }
+    });
+  }
+
+  // Render the pedal marking in position on the rendering context
+  draw() {
+    const ctx = this.checkContext();
+    this.setRendered();
+
+    ctx.save();
+    ctx.setStrokeStyle(this.render_options.color);
+    ctx.setFillStyle(this.render_options.color);
+    ctx.setFont(this.font.family, this.font.size, this.font.weight);
+
+    L('Rendering Pedal Marking');
+
+    if (this.style === PedalMarking.Styles.BRACKET || this.style === PedalMarking.Styles.MIXED || this.style === PedalMarking.Styles.MIXED_OPEN_END ||
+        this.style === PedalMarking.Styles.BRACKET_OPEN_BEGIN || this.style === PedalMarking.Styles.BRACKET_OPEN_END || this.style === PedalMarking.Styles.BRACKET_OPEN_BOTH) {
+      ctx.setLineWidth(this.render_options.bracket_line_width);
+      this.drawBracketed();
+    } else if (this.style === PedalMarking.Styles.TEXT) {
+      this.drawText();
+    }
+
+    ctx.restore();
+  }
 }