瀏覽代碼

Feature/expressions (#410)

* feat(Added dynamic expressions classes and stubs): Ported methods from PS project and included in mo

#309, #346

* feat(Dynamic Expressions:): Wedges working now

Wedges are calculated and drawn. Unfortunatley the ported method is a mess and needs some
refactoring. Will be done in next commit.

#309, #338, #346

* feat(Expressions): Continuous expressions done. Alignment missing

Refactored the complete expressions part, moved almost all common parts in abstract class, split
skyline and calculation code, made drawing easier

#309 #338 #346 #401

* refactor(Expressions): Removed unnecessary arguments

Parent not needed anymore for calculating PSI

* feat(Expressions): Implemented Wedge squeezing

Wedges that are too close to other expressions are now squeezed a bit. Implmented also the "sff"
Expression which was missing. Changed text alignment for expression labels to be center.

#309 #338 #346 #401

* refactor(Expressions): Incorporated review

Moved calculations to SheetCalculator, Added more functionality to abstract classes, implemented
squeezable interface, remove unnecesary code
Benjamin Giesinger 6 年之前
父節點
當前提交
b21bb40f96
共有 29 個文件被更改,包括 1888 次插入681 次删除
  1. 1 0
      demo/index.js
  2. 34 0
      src/MusicalScore/Graphical/AbstractGraphicalExpression.ts
  3. 93 0
      src/MusicalScore/Graphical/AlignementManager.ts
  4. 10 0
      src/MusicalScore/Graphical/BoundingBox.ts
  5. 19 0
      src/MusicalScore/Graphical/EngravingRules.ts
  6. 340 0
      src/MusicalScore/Graphical/GraphicalContinuousDynamicExpression.ts
  7. 25 7
      src/MusicalScore/Graphical/GraphicalInstantaneousDynamicExpression.ts
  8. 9 13
      src/MusicalScore/Graphical/GraphicalInstantaneousTempoExpression.ts
  9. 1 0
      src/MusicalScore/Graphical/GraphicalLine.ts
  10. 7 0
      src/MusicalScore/Graphical/ISqueezable.ts
  11. 446 68
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  12. 17 10
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  13. 0 2
      src/MusicalScore/Graphical/SkyBottomLineCalculator.ts
  14. 11 3
      src/MusicalScore/Graphical/StaffLine.ts
  15. 26 0
      src/MusicalScore/Graphical/VexFlow/VexFlowContinuousDynamicExpression.ts
  16. 6 17
      src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts
  17. 0 4
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  18. 387 467
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  19. 59 43
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  20. 8 5
      src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts
  21. 9 3
      src/MusicalScore/VoiceData/Expressions/AbstractExpression.ts
  22. 3 4
      src/MusicalScore/VoiceData/Expressions/AbstractTempoExpression.ts
  23. 1 3
      src/MusicalScore/VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression.ts
  24. 13 13
      src/MusicalScore/VoiceData/Expressions/InstantaneousDynamicExpression.ts
  25. 1 3
      src/MusicalScore/VoiceData/Expressions/MoodExpression.ts
  26. 2 4
      src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts
  27. 10 0
      src/Util/CollectionUtil.ts
  28. 40 12
      test/data/OSMD_function_test_expressions.musicxml
  29. 310 0
      test/data/OSMD_function_test_expressions_overlap.musicxml

+ 1 - 0
demo/index.js

@@ -28,6 +28,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         "OSMD Function Test - Ornaments": "OSMD_function_test_Ornaments.xml",
         "OSMD Function Test - Accidentals": "OSMD_function_test_accidentals.musicxml",
         "OSMD Function Test - Expressions": "OSMD_function_test_expressions.musicxml",
+        "OSMD Function Test - Expressions Overlap": "OSMD_function_test_expressions_overlap.musicxml",
         "OSMD Function Test - NoteHeadShapes": "OSMD_function_test_noteHeadShapes.musicxml",
         "OSMD Function Test - Drumset": "OSMD_function_test_drumset.musicxml",
         "Schubert, F. - An Die Musik": "Schubert_An_die_Musik.xml",

+ 34 - 0
src/MusicalScore/Graphical/AbstractGraphicalExpression.ts

@@ -0,0 +1,34 @@
+import { GraphicalObject } from "./GraphicalObject";
+import { GraphicalLabel } from "./GraphicalLabel";
+import { StaffLine } from "./StaffLine";
+import { BoundingBox } from "./BoundingBox";
+import { AbstractExpression, PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
+import { EngravingRules } from "./EngravingRules";
+
+export abstract class AbstractGraphicalExpression extends GraphicalObject {
+    protected label: GraphicalLabel;
+    protected parentStaffLine: StaffLine;
+    /** Internal cache of read expression */
+    protected expression: AbstractExpression;
+    /** EngravingRules for positioning */
+    protected rules: EngravingRules = EngravingRules.Rules;
+
+    constructor(parentStaffline: StaffLine, expression: AbstractExpression) {
+        super();
+        this.expression = expression;
+        this.boundingBox = new BoundingBox(this, parentStaffline.PositionAndShape);
+        this.parentStaffLine = parentStaffline;
+        this.parentStaffLine.AbstractExpressions.push(this);
+    }
+
+    /** Graphical label of the expression if available */
+    get Label(): GraphicalLabel { return this.label; }
+    /** Staffline where the expression is attached to */
+    public get ParentStaffLine(): StaffLine { return this.parentStaffLine; }
+    public get SourceExpression(): AbstractExpression { return this.expression; }
+    public get Placement(): PlacementEnum { return this.expression.Placement; }
+
+    //#region abstract methods
+    public abstract updateSkyBottomLine(): void;
+    //#endregion
+}

+ 93 - 0
src/MusicalScore/Graphical/AlignementManager.ts

@@ -0,0 +1,93 @@
+import { StaffLine } from "./StaffLine";
+import { BoundingBox } from "./BoundingBox";
+import { VexFlowContinuousDynamicExpression } from "./VexFlow/VexFlowContinuousDynamicExpression";
+import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
+import { PointF2D } from "../../Common/DataObjects/PointF2D";
+import { EngravingRules } from "./EngravingRules";
+
+export class AlignmentManager {
+    private parentStaffline: StaffLine;
+    private rules: EngravingRules = EngravingRules.Rules;
+
+    constructor(staffline: StaffLine) {
+        this.parentStaffline = staffline;
+    }
+
+    public alignDynamicExpressions(): void {
+        // Find close expressions along the staffline. Group them into tuples
+        const groups: AbstractGraphicalExpression[][] = [];
+        let tmpList: AbstractGraphicalExpression[] = new Array<AbstractGraphicalExpression>();
+        for (let aeIdx: number = 0; aeIdx < this.parentStaffline.AbstractExpressions.length - 1; aeIdx++) {
+            const currentExpression: AbstractGraphicalExpression = this.parentStaffline.AbstractExpressions[aeIdx];
+            const nextExpression: AbstractGraphicalExpression = this.parentStaffline.AbstractExpressions[aeIdx + 1];
+            if (currentExpression.Placement === nextExpression.Placement) {
+                const dist: PointF2D = this.getDistance(currentExpression.PositionAndShape, nextExpression.PositionAndShape);
+                if (dist.x < this.rules.DynamicExpressionMaxDistance) {
+                    // Prevent last found expression to be added twice. e.g. p<f as three close expressions
+                    if (tmpList.indexOf(currentExpression) === -1) {
+                        tmpList.push(currentExpression);
+                    }
+                    tmpList.push(nextExpression);
+                } else {
+                    groups.push(tmpList);
+                    tmpList = new Array<AbstractGraphicalExpression>();
+                }
+            }
+        }
+        // If expressions are colliding at end, we need to add them too
+        groups.push(tmpList);
+
+        for (const aes of groups) {
+            if (aes.length > 0) {
+                // Get the median y position and shift all group members to that position
+                const centerYs: number[] = aes.map(expr => expr.PositionAndShape.Center.y);
+                const yIdeal: number = Math.max(...centerYs);
+                for (let exprIdx: number = 0; exprIdx < aes.length; exprIdx++) {
+                    const expr: AbstractGraphicalExpression = aes[exprIdx];
+                    const centerOffset: number = centerYs[exprIdx] - yIdeal;
+                    // FIXME: Expressions should not behave differently.
+                    if (expr instanceof VexFlowContinuousDynamicExpression) {
+                        (expr as VexFlowContinuousDynamicExpression).shiftYPosition(-centerOffset);
+                    } else {
+                        // TODO: The 0.8 are because the letters are a bit to far done
+                        expr.PositionAndShape.RelativePosition.y -= centerOffset * 0.8;
+                    }
+                    expr.PositionAndShape.calculateBoundingBox();
+                    // Squeeze wedges
+                    if (exprIdx < aes.length - 1 && exprIdx > 0 && (expr as VexFlowContinuousDynamicExpression).squeeze) {
+                        const nextExpression: AbstractGraphicalExpression = aes[exprIdx + 1];
+                        const prevExpression: AbstractGraphicalExpression = aes[exprIdx - 1];
+                        const overlapRight: PointF2D = this.getOverlap(expr.PositionAndShape, nextExpression.PositionAndShape);
+                        const overlapLeft: PointF2D = this.getOverlap(prevExpression.PositionAndShape, expr.PositionAndShape);
+                        (expr as VexFlowContinuousDynamicExpression).squeeze(-(overlapRight.x + this.rules.DynamicExpressionSpacer));
+                        (expr as VexFlowContinuousDynamicExpression).squeeze(overlapLeft.x + this.rules.DynamicExpressionSpacer);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Get distance between to bounding boxes
+     * @param a First bounding box
+     * @param b Second bounding box
+     */
+    private getDistance(a: BoundingBox, b: BoundingBox): PointF2D {
+        const rightBorderA: number = a.RelativePosition.x + a.BorderMarginRight;
+        const leftBorderB: number = b.RelativePosition.x + b.BorderMarginLeft;
+        const bottomBorderA: number = b.RelativePosition.y + a.BorderMarginBottom;
+        const topBorderB: number = b.RelativePosition.y + b.BorderMarginTop;
+        return new PointF2D(leftBorderB - rightBorderA,
+                            topBorderB - bottomBorderA);
+    }
+
+    /**
+     * Get overlap of two bounding boxes
+     * @param a First bounding box
+     * @param b Second bounding box
+     */
+    private getOverlap(a: BoundingBox, b: BoundingBox): PointF2D {
+        return new PointF2D((a.RelativePosition.x + a.BorderMarginRight) - (b.RelativePosition.x + b.BorderMarginLeft),
+                            (a.RelativePosition.y + a.BorderMarginBottom) - (b.RelativePosition.y + b.BorderMarginTop));
+    }
+}

+ 10 - 0
src/MusicalScore/Graphical/BoundingBox.ts

@@ -214,6 +214,16 @@ export class BoundingBox {
         return this.dataObject;
     }
 
+
+    /**
+     * Get the center of a bounding box
+     * @param boundingBox Bounding box to check
+     */
+    public get Center(): PointF2D {
+        return new PointF2D(this.RelativePosition.x + (this.BorderMarginRight + this.BorderMarginLeft),
+                            this.RelativePosition.y + (this.BorderMarginBottom + this.BorderMarginTop));
+    }
+
     public setAbsolutePositionFromParent(): void {
         if (this.parent !== undefined) {
             this.absolutePosition.x = this.parent.AbsolutePosition.x + this.relativePosition.x;

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

@@ -167,6 +167,8 @@ export class EngravingRules {
     private renderLyricist: boolean;
     private renderInstrumentNames: boolean;
     private renderFingerings: boolean;
+    private dynamicExpressionMaxDistance: number;
+    private dynamicExpressionSpacer: number;
     private fingeringPosition: PlacementEnum;
     private fingeringInsideStafflines: boolean;
 
@@ -319,6 +321,8 @@ export class EngravingRules {
         this.moodTextHeight = 2.3;
         this.unknownTextHeight = 2.0;
         this.continuousTempoTextHeight = 2.3;
+        this.dynamicExpressionMaxDistance = 2;
+        this.dynamicExpressionSpacer = 0.5;
 
         // Line Widths
         this.staffLineWidth = 0.12;
@@ -1072,6 +1076,21 @@ export class EngravingRules {
     public set ContinuousTempoTextHeight(value: number) {
         this.continuousTempoTextHeight = value;
     }
+    /** Distance of expressions inside a group */
+    public get DynamicExpressionMaxDistance(): number {
+        return this.dynamicExpressionMaxDistance;
+    }
+    public set DynamicExpressionMaxDistance(value: number) {
+        this.dynamicExpressionMaxDistance = value;
+    }
+    /** Space between expressions in a group */
+    public get DynamicExpressionSpacer(): number {
+        return this.dynamicExpressionSpacer;
+    }
+    public set DynamicExpressionSpacer(value: number) {
+        this.dynamicExpressionSpacer = value;
+    }
+
     public get UnknownTextHeight(): number {
         return this.unknownTextHeight;
     }

+ 340 - 0
src/MusicalScore/Graphical/GraphicalContinuousDynamicExpression.ts

@@ -0,0 +1,340 @@
+import { GraphicalLine } from "./GraphicalLine";
+import { StaffLine } from "./StaffLine";
+import { GraphicalMeasure } from "./GraphicalMeasure";
+import { ContDynamicEnum, ContinuousDynamicExpression } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
+import { PointF2D } from "../../Common/DataObjects/PointF2D";
+import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
+import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
+import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
+import { ISqueezable } from "./ISqueezable";
+import * as log from "loglevel";
+
+/**
+ * This class prepares the graphical elements for a continuous expression. It calculates the wedges and
+ * wrappings if they are split over system breaks.
+ */
+export class GraphicalContinuousDynamicExpression extends AbstractGraphicalExpression implements ISqueezable {
+    /** True if expression is split over system borders */
+    private isSplittedPart: boolean;
+    /** True if this expression should not be removed if re-rendered */
+    private notToBeRemoved: boolean;
+    /** Holds the line objects that can be drawn via implementation */
+    private lines: GraphicalLine[] = [];
+    private startMeasure: GraphicalMeasure;
+    private endMeasure: GraphicalMeasure;
+
+    /**
+     * Create a new instance of the GraphicalContinuousDynamicExpression
+     * @param continuousDynamic The continuous dynamic instruction read via ExpressionReader
+     * @param staffLine The staffline where the exoression is attached
+     */
+    constructor(continuousDynamic: ContinuousDynamicExpression, staffLine: StaffLine) {
+        super(staffLine, continuousDynamic);
+
+        this.isSplittedPart = false;
+        this.notToBeRemoved = false;
+    }
+
+    //#region Getter / Setter
+
+    /** The graphical measure where the parent continuous dynamic expression starts */
+    public get StartMeasure(): GraphicalMeasure { return this.startMeasure; }
+    public set StartMeasure(value: GraphicalMeasure) { this.startMeasure = value; }
+    /** The graphical measure where the parent continuous dynamic expression ends */
+    public get EndMeasure(): GraphicalMeasure { return this.endMeasure; }
+    public set EndMeasure(value: GraphicalMeasure) { this.endMeasure = value; }
+    /** The staff lin where the graphical dynamic expressions ends */
+    public get EndStaffLine(): StaffLine { return this.endMeasure ? this.endMeasure.ParentStaffLine : undefined; }
+    /**  Is true if this continuous expression is a wedge, that reaches over a system border and needs to be split into two. */
+    public get IsSplittedPart(): boolean { return this.isSplittedPart; }
+    public set IsSplittedPart(value: boolean) { this.isSplittedPart = value; }
+    /**  Is true if the dynamic is not a symbol but a text instruction. E.g. "decrescendo" */
+    public get IsVerbal(): boolean { return this.ContinuousDynamic.Label !== undefined && this.ContinuousDynamic.Label.length > 0; }
+    /** True if this expression should not be removed if re-rendered */
+    public get NotToBeRemoved(): boolean { return this.notToBeRemoved; }
+    public set NotToBeRemoved(value: boolean) { this.notToBeRemoved = value; }
+    /** Holds the line objects that can be drawn via implementation */
+    public get Lines(): GraphicalLine[] { return this.lines; }
+
+    public get ContinuousDynamic(): ContinuousDynamicExpression { return this.SourceExpression as ContinuousDynamicExpression; }
+    //#endregion
+
+    //#region Public methods
+
+    public updateSkyBottomLine(): void {
+        // update Sky-BottomLine
+        const skyBottomLineCalculator: SkyBottomLineCalculator = this.parentStaffLine.SkyBottomLineCalculator;
+        const left: number = this.IsVerbal ? this.label.PositionAndShape.RelativePosition.x + this.label.PositionAndShape.BorderMarginLeft : 0;
+        const right: number = this.IsVerbal ? this.label.PositionAndShape.RelativePosition.x + this.label.PositionAndShape.BorderMarginRight : 0;
+        if (!this.IsVerbal && this.lines.length < 2) {
+            log.warn("Not enough lines for SkyBottomLine calculation");
+        }
+        switch (this.Placement) {
+            case PlacementEnum.Above:
+                if (!this.IsVerbal) {
+                    skyBottomLineCalculator.updateSkyLineWithWedge(this.lines[0].Start, this.lines[0].End);
+                } else {
+                    const yValue: number = this.label.PositionAndShape.BorderMarginTop + this.label.PositionAndShape.RelativePosition.y;
+                    skyBottomLineCalculator.updateSkyLineInRange(left, right, yValue);
+                }
+                break;
+            case PlacementEnum.Below:
+                if (!this.IsVerbal) {
+                    skyBottomLineCalculator.updateBottomLineWithWedge(this.lines[1].Start, this.lines[1].End);
+                } else {
+                    const yValue: number = this.label.PositionAndShape.BorderMarginBottom + this.label.PositionAndShape.RelativePosition.y;
+                    skyBottomLineCalculator.updateBottomLineInRange(left, right, yValue);
+                }
+                break;
+            default:
+                log.error("Placement for GraphicalContinuousDynamicExpression is unknown");
+        }
+    }
+
+    /**
+     * Calculate crescendo lines for (full).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeOpeningLength length of the opening
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createCrescendoLines(startX: number, endX: number, y: number,
+                                wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const lineStart: PointF2D = new PointF2D(startX, y);
+        const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
+        const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
+        this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
+    }
+
+    /**
+     * Calculate crescendo lines for system break (first part).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeMeasureEndOpeningLength length of opening at measure end
+     * @param wedgeOpeningLength length of the opening
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createFirstHalfCrescendoLines(startX: number, endX: number, y: number,
+                                         wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
+                                         wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const lineStart: PointF2D = new PointF2D(startX, y);
+        const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
+        const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
+        this.addWedgeLines(lineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
+    }
+
+
+    /**
+     * Calculate crescendo lines for system break (second part).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeMeasureBeginOpeningLength length of opening at measure start
+     * @param wedgeOpeningLength length of the opening
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createSecondHalfCresendoLines(startX: number, endX: number, y: number,
+                                         wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
+                                         wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
+                                         wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
+        const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
+        const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeOpeningLength / 2);
+        const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeOpeningLength / 2);
+        this.addDoubleLines(upperLineStart, lowerLineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
+    }
+
+    /**
+     * This method recalculates the Crescendo Lines (for all cases).
+     * @param startX left most starting point
+     * @param endX right most ending point
+     * @param y y placement
+     */
+    public recalculateCrescendoLines(startX: number, endX: number, y: number): void {
+        const isSecondHalfSplit: boolean = Math.abs(this.lines[0].Start.y - this.lines[1].Start.y) > 0.0001;
+        this.lines.clear();
+
+        if (isSecondHalfSplit) {
+            this.createSecondHalfCresendoLines(startX, endX, y);
+        } else if (this.isSplittedPart) {
+            this.createFirstHalfCrescendoLines(startX, endX, y);
+        } else {
+            this.createCrescendoLines(startX, endX, y);
+        }
+    }
+
+    /**
+     * Calculate diminuendo lines for system break (full).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeOpeningLength length of the opening
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createDiminuendoLines(startX: number, endX: number, y: number,
+                                 wedgeOpeningLength: number = this.rules.WedgeOpeningLength, wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const upperWedgeStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
+        const lowerWedgeStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
+        const wedgeEnd: PointF2D = new PointF2D(endX, y);
+        this.addWedgeLines(wedgeEnd, upperWedgeStart, lowerWedgeStart, wedgeLineWidth);
+    }
+
+    /**
+     * Calculate diminuendo lines for system break (first part).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeOpeningLength length of the opening
+     * @param wedgeMeasureEndOpeningLength length of opening at measure end
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createFirstHalfDiminuendoLines(startX: number, endX: number, y: number,
+                                          wedgeOpeningLength: number = this.rules.WedgeOpeningLength,
+                                          wedgeMeasureEndOpeningLength: number = this.rules.WedgeMeasureEndOpeningLength,
+                                          wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeOpeningLength / 2);
+        const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeOpeningLength / 2);
+        const upperLineEnd: PointF2D = new PointF2D(endX, y - wedgeMeasureEndOpeningLength / 2);
+        const lowerLineEnd: PointF2D = new PointF2D(endX, y + wedgeMeasureEndOpeningLength / 2);
+        this.addDoubleLines(upperLineStart, lowerLineStart, upperLineEnd, lowerLineEnd, wedgeLineWidth);
+    }
+
+    /**
+     * Calculate diminuendo lines for system break (second part).
+     * @param startX left most starting point
+     * @param endX right mist ending point
+     * @param y y placement
+     * @param wedgeMeasureBeginOpeningLength length of opening at measure start
+     * @param wedgeLineWidth line width of the wedge
+     */
+    public createSecondHalfDiminuendoLines(startX: number, endX: number, y: number,
+                                           wedgeMeasureBeginOpeningLength: number = this.rules.WedgeMeasureBeginOpeningLength,
+                                           wedgeLineWidth: number = this.rules.WedgeLineWidth): void {
+        const upperLineStart: PointF2D = new PointF2D(startX, y - wedgeMeasureBeginOpeningLength / 2);
+        const lowerLineStart: PointF2D = new PointF2D(startX, y + wedgeMeasureBeginOpeningLength / 2);
+        const lineEnd: PointF2D = new PointF2D(endX, y);
+        this.addWedgeLines(lineEnd, upperLineStart, lowerLineStart, wedgeLineWidth);
+    }
+
+    /**
+     * This method recalculates the diminuendo lines (for all cases).
+     * @param startX left most starting point
+     * @param endX right most ending point
+     * @param y y placement
+     */
+    public recalculateDiminuendoLines(startX: number, endX: number, yPosition: number): void {
+        const isFirstHalfSplit: boolean = Math.abs(this.lines[0].End.y - this.lines[1].End.y) > 0.0001;
+        this.lines.clear();
+        if (isFirstHalfSplit) {
+            this.createFirstHalfDiminuendoLines(startX, endX, yPosition);
+        } else if (this.isSplittedPart) {
+            this.createSecondHalfDiminuendoLines(startX, endX, yPosition);
+        } else {
+            this.createDiminuendoLines(startX, endX, yPosition);
+        }
+    }
+
+    /**
+     * Calculate the BoundingBox (as a box around the Wedge).
+     */
+    public calcPsi(): void {
+        if (this.IsVerbal) {
+            this.PositionAndShape.calculateBoundingBox();
+            return;
+        }
+        this.PositionAndShape.RelativePosition = this.lines[0].Start;
+        this.PositionAndShape.BorderMarginTop = this.lines[0].End.y - this.lines[0].Start.y;
+        this.PositionAndShape.BorderMarginBottom = this.lines[1].End.y - this.lines[1].Start.y;
+
+        if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            this.PositionAndShape.BorderMarginLeft = 0;
+            this.PositionAndShape.BorderMarginRight = this.lines[0].End.x - this.lines[0].Start.x;
+        } else {
+            this.PositionAndShape.BorderMarginLeft = this.lines[0].End.x - this.lines[0].Start.x;
+            this.PositionAndShape.BorderMarginRight = 0;
+        }
+    }
+
+    /**
+     * Clear Lines
+     */
+    public cleanUp(): void {
+        this.lines.clear();
+    }
+
+    /**
+     * Shift wedge in y position
+     * @param shift Number to shift
+     */
+    public shiftYPosition(shift: number): void {
+        if (this.IsVerbal) {
+            this.PositionAndShape.RelativePosition.y += shift;
+            this.PositionAndShape.calculateBoundingBox();
+        } else {
+            this.lines[0].Start.y += shift;
+            this.lines[0].End.y += shift;
+            this.lines[1].End.y += shift;
+        }
+    }
+
+    public squeeze(value: number): void {
+        if (this.IsVerbal) {
+            return;
+        }
+        if (this.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            if (value > 0) {
+                this.lines[0].Start.x += value;
+            } else {
+                this.lines[0].End.x += value;
+                this.lines[1].End.x += value;
+            }
+        } else {
+            if (value < 0) {
+                this.lines[0].Start.x += value;
+            } else {
+                this.lines[0].End.x += value;
+                this.lines[1].End.x += value;
+            }
+        }
+        this.calcPsi();
+    }
+
+    //#endregion
+
+    //#region Private methods
+
+    /**
+     * Create lines from points and add them to the memory
+     * @param wedgePoint start of the expression
+     * @param upperWedgeEnd end of the upper line
+     * @param lowerWedgeEnd end of lower line
+     * @param wedgeLineWidth line width
+     */
+    private addWedgeLines(wedgePoint: PointF2D, upperWedgeEnd: PointF2D, lowerWedgeEnd: PointF2D, wedgeLineWidth: number): void {
+        const upperLine: GraphicalLine = new GraphicalLine(wedgePoint, upperWedgeEnd, wedgeLineWidth);
+        const lowerLine: GraphicalLine = new GraphicalLine(wedgePoint, lowerWedgeEnd, wedgeLineWidth);
+
+        this.lines.push(upperLine);
+        this.lines.push(lowerLine);
+    }
+
+    /**
+     * Create top and bottom lines for continuing wedges
+     * @param upperLineStart start of the upper line
+     * @param upperLineEnd end of the upper line
+     * @param lowerLineStart start of the lower line
+     * @param lowerLineEnd end of lower line
+     * @param wedgeLineWidth line width
+     */
+    private addDoubleLines(upperLineStart: PointF2D, upperLineEnd: PointF2D, lowerLineStart: PointF2D, lowerLineEnd: PointF2D, wedgeLineWidth: number): void {
+        const upperLine: GraphicalLine = new GraphicalLine(upperLineStart, upperLineEnd, wedgeLineWidth);
+        const lowerLine: GraphicalLine = new GraphicalLine(lowerLineStart, lowerLineEnd, wedgeLineWidth);
+
+        this.lines.push(upperLine);
+        this.lines.push(lowerLine);
+    }
+
+    //#endregion
+}

+ 25 - 7
src/MusicalScore/Graphical/GraphicalInstantaneousDynamicExpression.ts

@@ -1,19 +1,37 @@
-import { GraphicalObject } from "./GraphicalObject";
 import { StaffLine } from "./StaffLine";
 import { InstantaneousDynamicExpression } from "../VoiceData/Expressions/InstantaneousDynamicExpression";
 import { GraphicalMeasure } from "./GraphicalMeasure";
-import { BoundingBox } from "./BoundingBox";
+import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
+import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
+import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
+import * as log from "loglevel";
 
-export class GraphicalInstantaneousDynamicExpression extends GraphicalObject {
+export class GraphicalInstantaneousDynamicExpression extends AbstractGraphicalExpression {
     protected mInstantaneousDynamicExpression: InstantaneousDynamicExpression;
-    protected mParentStaffLine: StaffLine;
     protected mMeasure: GraphicalMeasure;
 
     constructor(instantaneousDynamic: InstantaneousDynamicExpression, staffLine: StaffLine, measure: GraphicalMeasure) {
-        super();
-        this.boundingBox = new BoundingBox(this, staffLine.PositionAndShape);
+        super(staffLine, instantaneousDynamic);
         this.mInstantaneousDynamicExpression = instantaneousDynamic;
-        this.mParentStaffLine = staffLine;
         this.mMeasure = measure;
     }
+
+    public updateSkyBottomLine(): void {
+        const skyBottomLineCalculator: SkyBottomLineCalculator = this.parentStaffLine.SkyBottomLineCalculator;
+        const left: number = this.PositionAndShape.RelativePosition.x + this.PositionAndShape.BorderMarginLeft;
+        const right: number = this.PositionAndShape.RelativePosition.x + this.PositionAndShape.BorderMarginRight;
+        let yValue: number = 0;
+        switch (this.Placement) {
+            case PlacementEnum.Above:
+                yValue = this.PositionAndShape.RelativePosition.y + this.PositionAndShape.BorderMarginTop;
+                skyBottomLineCalculator.updateSkyLineInRange(left, right, yValue);
+                break;
+            case PlacementEnum.Below:
+                yValue = this.PositionAndShape.RelativePosition.y + this.PositionAndShape.BorderMarginBottom;
+                skyBottomLineCalculator.updateBottomLineInRange(left, right, yValue);
+                break;
+            default:
+                log.error("Placement for GraphicalInstantaneousDynamicExpression is unknown");
+        }
+    }
 }

+ 9 - 13
src/MusicalScore/Graphical/GraphicalInstantaneousTempoExpression.ts

@@ -1,25 +1,21 @@
-import { GraphicalObject } from "./GraphicalObject";
+
 import { StaffLine } from "./StaffLine";
 import { AbstractTempoExpression } from "../VoiceData/Expressions/AbstractTempoExpression";
 import { GraphicalLabel } from "./GraphicalLabel";
+import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
 
-export class GraphicalInstantaneousTempoExpression extends GraphicalObject {
-    protected mTempoExpresssion: AbstractTempoExpression;
-    protected mParentStaffLine: StaffLine;
-    protected mLabel: GraphicalLabel;
+export class GraphicalInstantaneousTempoExpression extends AbstractGraphicalExpression {
 
     constructor(tempoExpresssion: AbstractTempoExpression, label: GraphicalLabel) {
-        super();
-        // this.boundingBox = new BoundingBox(this, staffLine.PositionAndShape);
-        this.mTempoExpresssion = tempoExpresssion;
-        this.mLabel = label;
+        super((label.PositionAndShape.Parent.DataObject as StaffLine), tempoExpresssion);
+        this.label = label;
     }
 
-    public get InstantaneousTempoExpression(): AbstractTempoExpression {
-        return this.mTempoExpresssion;
+    public get GraphicalLabel(): GraphicalLabel {
+        return this.label;
     }
 
-    public get GraphicalLabel(): GraphicalLabel {
-        return this.mLabel;
+    public updateSkyBottomLine(): void {
+        // Not implemented
     }
 }

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

@@ -1,3 +1,4 @@
+
 import {OutlineAndFillStyleEnum} from "./DrawingEnums";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
 

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

@@ -0,0 +1,7 @@
+export interface ISqueezable {
+    /**
+     * Squeezes the wedge by the given amount.
+     * @param value Squeeze amount. Positive values squeeze from the left, negative from the right
+     */
+    squeeze(value: number): void;
+}

+ 446 - 68
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -1,52 +1,52 @@
-import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
-import {StaffLine} from "./StaffLine";
-import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
-import {EngravingRules} from "./EngravingRules";
-import {Tie} from "../VoiceData/Tie";
-import {Fraction} from "../../Common/DataObjects/Fraction";
-import {Note} from "../VoiceData/Note";
-import {MusicSheet} from "../MusicSheet";
-import {GraphicalMeasure} from "./GraphicalMeasure";
-import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
-import {LyricWord} from "../VoiceData/Lyrics/LyricsWord";
-import {SourceMeasure} from "../VoiceData/SourceMeasure";
-import {GraphicalMusicPage} from "./GraphicalMusicPage";
-import {GraphicalNote} from "./GraphicalNote";
-import {Beam} from "../VoiceData/Beam";
-import {OctaveEnum} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
-import {VoiceEntry, StemDirectionType} from "../VoiceData/VoiceEntry";
-import {OrnamentContainer} from "../VoiceData/OrnamentContainer";
-import {ArticulationEnum} from "../VoiceData/VoiceEntry";
-import {Tuplet} from "../VoiceData/Tuplet";
-import {MusicSystem} from "./MusicSystem";
-import {GraphicalTie} from "./GraphicalTie";
-import {RepetitionInstruction} from "../VoiceData/Instructions/RepetitionInstruction";
-import {MultiExpression} from "../VoiceData/Expressions/MultiExpression";
-import {StaffEntryLink} from "../VoiceData/StaffEntryLink";
-import {MusicSystemBuilder} from "./MusicSystemBuilder";
-import {MultiTempoExpression} from "../VoiceData/Expressions/MultiTempoExpression";
-import {Repetition} from "../MusicSource/Repetition";
-import {PointF2D} from "../../Common/DataObjects/PointF2D";
-import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
-import {BoundingBox} from "./BoundingBox";
-import {Instrument} from "../Instrument";
-import {GraphicalLabel} from "./GraphicalLabel";
-import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
-import {VerticalGraphicalStaffEntryContainer} from "./VerticalGraphicalStaffEntryContainer";
-import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
-import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
-import {TechnicalInstruction} from "../VoiceData/Instructions/TechnicalInstruction";
-import {Pitch} from "../../Common/DataObjects/Pitch";
-import {LinkedVoice} from "../VoiceData/LinkedVoice";
-import {ColDirEnum} from "./BoundingBox";
-import {IGraphicalSymbolFactory} from "../Interfaces/IGraphicalSymbolFactory";
-import {ITextMeasurer} from "../Interfaces/ITextMeasurer";
-import {ITransposeCalculator} from "../Interfaces/ITransposeCalculator";
-import {OctaveShiftParams} from "./OctaveShiftParams";
-import {AccidentalCalculator} from "./AccidentalCalculator";
-import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
-import {Staff} from "../VoiceData/Staff";
-import {OctaveShift} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
+import { StaffLine } from "./StaffLine";
+import { GraphicalMusicSheet } from "./GraphicalMusicSheet";
+import { EngravingRules } from "./EngravingRules";
+import { Tie } from "../VoiceData/Tie";
+import { Fraction } from "../../Common/DataObjects/Fraction";
+import { Note } from "../VoiceData/Note";
+import { MusicSheet } from "../MusicSheet";
+import { GraphicalMeasure } from "./GraphicalMeasure";
+import { ClefInstruction } from "../VoiceData/Instructions/ClefInstruction";
+import { LyricWord } from "../VoiceData/Lyrics/LyricsWord";
+import { SourceMeasure } from "../VoiceData/SourceMeasure";
+import { GraphicalMusicPage } from "./GraphicalMusicPage";
+import { GraphicalNote } from "./GraphicalNote";
+import { Beam } from "../VoiceData/Beam";
+import { OctaveEnum } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { VoiceEntry, StemDirectionType } from "../VoiceData/VoiceEntry";
+import { OrnamentContainer } from "../VoiceData/OrnamentContainer";
+import { ArticulationEnum } from "../VoiceData/VoiceEntry";
+import { Tuplet } from "../VoiceData/Tuplet";
+import { MusicSystem } from "./MusicSystem";
+import { GraphicalTie } from "./GraphicalTie";
+import { RepetitionInstruction } from "../VoiceData/Instructions/RepetitionInstruction";
+import { MultiExpression } from "../VoiceData/Expressions/MultiExpression";
+import { StaffEntryLink } from "../VoiceData/StaffEntryLink";
+import { MusicSystemBuilder } from "./MusicSystemBuilder";
+import { MultiTempoExpression } from "../VoiceData/Expressions/MultiTempoExpression";
+import { Repetition } from "../MusicSource/Repetition";
+import { PointF2D } from "../../Common/DataObjects/PointF2D";
+import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
+import { BoundingBox } from "./BoundingBox";
+import { Instrument } from "../Instrument";
+import { GraphicalLabel } from "./GraphicalLabel";
+import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
+import { VerticalGraphicalStaffEntryContainer } from "./VerticalGraphicalStaffEntryContainer";
+import { KeyInstruction } from "../VoiceData/Instructions/KeyInstruction";
+import { AbstractNotationInstruction } from "../VoiceData/Instructions/AbstractNotationInstruction";
+import { TechnicalInstruction } from "../VoiceData/Instructions/TechnicalInstruction";
+import { Pitch } from "../../Common/DataObjects/Pitch";
+import { LinkedVoice } from "../VoiceData/LinkedVoice";
+import { ColDirEnum } from "./BoundingBox";
+import { IGraphicalSymbolFactory } from "../Interfaces/IGraphicalSymbolFactory";
+import { ITextMeasurer } from "../Interfaces/ITextMeasurer";
+import { ITransposeCalculator } from "../Interfaces/ITransposeCalculator";
+import { OctaveShiftParams } from "./OctaveShiftParams";
+import { AccidentalCalculator } from "./AccidentalCalculator";
+import { MidiInstrument } from "../VoiceData/Instructions/ClefInstruction";
+import { Staff } from "../VoiceData/Staff";
+import { OctaveShift } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import * as log from "loglevel";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import { GraphicalLyricEntry } from "./GraphicalLyricEntry";
@@ -62,6 +62,10 @@ import { GraphicalInstantaneousTempoExpression } from "./GraphicalInstantaneousT
 import { InstantaneousTempoExpression, TempoEnum } from "../VoiceData/Expressions/InstantaneousTempoExpression";
 import { ContinuousTempoExpression } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousTempoExpression";
 import { FontStyles } from "../../Common/Enums/FontStyles";
+import { AbstractTempoExpression } from "../VoiceData/Expressions/AbstractTempoExpression";
+import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
+import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
+import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -75,7 +79,6 @@ export abstract class MusicSheetCalculator {
     protected staffEntriesWithOrnaments: GraphicalStaffEntry[] = [];
     protected staffEntriesWithChordSymbols: GraphicalStaffEntry[] = [];
     protected staffLinesWithLyricWords: StaffLine[] = [];
-    protected staffLinesWithGraphicalExpressions: StaffLine[] = [];
 
     protected graphicalLyricWords: GraphicalLyricWord[] = [];
 
@@ -123,7 +126,7 @@ export abstract class MusicSheetCalculator {
         this.staffEntriesWithOrnaments = [];
         this.staffEntriesWithChordSymbols = [];
         this.staffLinesWithLyricWords = [];
-        this.staffLinesWithGraphicalExpressions = [];
+        // this.staffLinesWithGraphicalExpressions = [];
 
         this.graphicalMusicSheet.Initialize();
         const measureList: GraphicalMeasure[][] = this.graphicalMusicSheet.MeasureList;
@@ -739,10 +742,10 @@ export abstract class MusicSheetCalculator {
         if (!this.leadSheet) {
             // calculate all Instantaneous/Continuous Dynamics Expressions
             this.calculateDynamicExpressions();
-            // place neighbouring DynamicExpressions at the same height
-            this.optimizeStaffLineDynamicExpressionsPositions();
             // calculate all Mood and Unknown Expression
             this.calculateMoodAndUnknownExpressions();
+            // Calculate the alignment of close expressions
+            this.calculateExpressionAlignements();
             // calculate all OctaveShifts
             this.calculateOctaveShifts();
             // calcualte RepetitionInstructions (Dal Segno, Coda, etc)
@@ -832,13 +835,6 @@ export abstract class MusicSheetCalculator {
         return;
     }
 
-    /**
-     * Iterate through all the [[StaffLine]]s in order to check for possible optimizations in the placement of the [[GraphicalExpression]]s.
-     */
-    protected optimizeStaffLineDynamicExpressionsPositions(): void {
-        return;
-    }
-
     protected calculateChordSymbols(): void {
         return;
     }
@@ -887,6 +883,370 @@ export abstract class MusicSheetCalculator {
         return;
     }
 
+
+    /**
+     * This method calculates the RelativePosition of a single verbal GraphicalContinuousDynamic.
+     * @param graphicalContinuousDynamic Graphical continous dynamic to be calculated
+     * @param startPosInStaffline Starting point in staff line
+     */
+    protected calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic: GraphicalContinuousDynamicExpression,
+                                                        startPosInStaffline: PointF2D): void {
+        // if ContinuousDynamicExpression is given from words
+        const graphLabel: GraphicalLabel = graphicalContinuousDynamic.Label;
+        const left: number = startPosInStaffline.x + graphLabel.PositionAndShape.BorderMarginLeft;
+        const right: number = startPosInStaffline.x + graphLabel.PositionAndShape.BorderMarginRight;
+        // placement always below the currentStaffLine, with the exception of Voice Instrument (-> above)
+        const placement: PlacementEnum = graphicalContinuousDynamic.ContinuousDynamic.Placement;
+        const staffLine: StaffLine = graphicalContinuousDynamic.ParentStaffLine;
+        const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
+
+        let drawingHeight: number;
+        if (placement === PlacementEnum.Below) {
+            drawingHeight = skyBottomLineCalculator.getBottomLineMaxInRange(left, right);    // Bottom line
+            graphLabel.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, drawingHeight - graphLabel.PositionAndShape.BorderMarginTop);
+        } else {
+            drawingHeight = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
+            graphLabel.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, drawingHeight - graphLabel.PositionAndShape.BorderMarginBottom);
+        }
+    }
+
+   /**
+    * This method calculates the RelativePosition of a single GraphicalContinuousDynamic.
+    * @param graphicalContinuousDynamic Graphical continous dynamic to be calculated
+    * @param startPosInStaffline Starting point in staff line
+    */
+    public calculateGraphicalContinuousDynamic(graphicalContinuousDynamic: GraphicalContinuousDynamicExpression, startPosInStaffline: PointF2D): void {
+        const staffIndex: number = graphicalContinuousDynamic.ParentStaffLine.ParentStaff.idInMusicSheet;
+        // TODO: Previously the staffIndex was passed down. BUT you can (and this function actually does this) get it from
+        // the musicSystem OR from the ParentStaffLine. Is this the same index?
+        // const staffIndex: number = musicSystem.StaffLines.indexOf(staffLine);
+
+        // We know we have an end measure because otherwise we won't be called
+        const endMeasure: GraphicalMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(
+            graphicalContinuousDynamic.ContinuousDynamic.EndMultiExpression.SourceMeasureParent, staffIndex);
+        if (!endMeasure) {
+            log.warn("Not working");
+            return;
+        }
+
+        graphicalContinuousDynamic.EndMeasure = endMeasure;
+        const endStaffLine: StaffLine = endMeasure.ParentStaffLine;
+        const endAbsoluteTimestamp: Fraction = Fraction.createFromFraction(graphicalContinuousDynamic.ContinuousDynamic.EndMultiExpression.AbsoluteTimestamp);
+
+        const endPosInStaffLine: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
+            endAbsoluteTimestamp, staffIndex, endStaffLine, endStaffLine.isPartOfMultiStaffInstrument(), 0);
+
+        const staffLine: StaffLine = graphicalContinuousDynamic.ParentStaffLine;
+        //currentMusicSystem and currentStaffLine
+        const musicSystem: MusicSystem = staffLine.ParentMusicSystem;
+        const currentStaffLineIndex: number = musicSystem.StaffLines.indexOf(staffLine);
+        const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
+        // let expressionIndex: number;
+
+        // placement always below the currentStaffLine, with the exception of Voice Instrument (-> above)
+        const placement: PlacementEnum = graphicalContinuousDynamic.ContinuousDynamic.Placement;
+
+        // if ContinuousDynamicExpression is given from wedge
+        let secondGraphicalContinuousDynamic: GraphicalContinuousDynamicExpression = undefined;
+
+        // check if Expression spreads over the same StaffLine or not
+        const sameStaffLine: boolean = endStaffLine !== undefined && staffLine === endStaffLine;
+
+        // last length check
+        if (sameStaffLine && endPosInStaffLine.x - startPosInStaffline.x < this.rules.WedgeMinLength) {
+            endPosInStaffLine.x = startPosInStaffline.x + this.rules.WedgeMinLength;
+        }
+
+        // Upper staff wedge always starts at the given position and the lower staff wedge always starts at the begin of measure
+        const upperStartX: number = startPosInStaffline.x;
+        const lowerStartX: number = endStaffLine.Measures[0].beginInstructionsWidth + this.rules.WedgeHorizontalMargin;
+        let upperEndX: number = 0;
+        let lowerEndX: number = 0;
+
+        if (!sameStaffLine) {
+            upperEndX = staffLine.PositionAndShape.Size.width;
+            lowerEndX = endPosInStaffLine.x;
+
+            // must create a new Wedge
+            secondGraphicalContinuousDynamic = new GraphicalContinuousDynamicExpression(graphicalContinuousDynamic.ContinuousDynamic, endStaffLine);
+            secondGraphicalContinuousDynamic.IsSplittedPart = true;
+            graphicalContinuousDynamic.IsSplittedPart = true;
+        } else {
+            upperEndX = endPosInStaffLine.x;
+        }
+
+        // the Height of the Expression's placement
+        let idealY: number = 0;
+        let secondIdealY: number = 0;
+
+        if (placement === PlacementEnum.Below) {
+            // can be a single Staff Instrument or an Instrument with 2 Staves
+            let nextStaffLineIndex: number = 0;
+            if (currentStaffLineIndex < musicSystem.StaffLines.length - 1) {
+                nextStaffLineIndex = currentStaffLineIndex + 1;
+            }
+
+            // check, maybe currentStaffLine is the last of the MusicSystem (and it has a ContinuousDynamicExpression with placement below)
+            if (nextStaffLineIndex > currentStaffLineIndex) {
+                // currentStaffLine isn't the last of the MusicSystem
+                const nextStaffLine: StaffLine = musicSystem.StaffLines[nextStaffLineIndex];
+
+                const distanceBetweenStaffLines: number = nextStaffLine.PositionAndShape.RelativePosition.y -
+                    staffLine.PositionAndShape.RelativePosition.y -
+                    this.rules.StaffHeight;
+
+                // ideal Height is exactly between the two StaffLines
+                idealY = this.rules.StaffHeight + distanceBetweenStaffLines / 2;
+            } else {
+                // currentStaffLine is the MusicSystem's last
+                idealY = this.rules.WedgePlacementBelowY;
+            }
+
+            // must consider the upperWedge starting/ending tip for the comparison with the BottomLine
+            idealY -= this.rules.WedgeOpeningLength / 2;
+            if (!sameStaffLine) {
+                // Set the value for the splitted y position to the ideal position before we check and modify it with
+                // the skybottom calculator detection
+                secondIdealY = idealY;
+            }
+            // must check BottomLine for possible collisions within the Length of the Expression
+            // find the corresponding max value for the given Length
+            let maxBottomLineValueForExpressionLength: number = skyBottomLineCalculator.getBottomLineMaxInRange(upperStartX, upperEndX);
+
+            // if collisions, then set the Height accordingly
+            if (maxBottomLineValueForExpressionLength > idealY) {
+                idealY = maxBottomLineValueForExpressionLength;
+            }
+
+            // special case - wedge must be drawn within the boundaries of a crossedBeam
+            const withinCrossedBeam: boolean = false;
+
+            if (currentStaffLineIndex < musicSystem.StaffLines.length - 1) {
+                // find GraphicalStaffEntries closest to wedge's xPositions
+                const closestToEndStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperEndX);
+                const closestToStartStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperStartX);
+
+                if (closestToStartStaffEntry && closestToEndStaffEntry) {
+                    // must check both StaffLines
+                    const startVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToStartStaffEntry.parentVerticalContainer;
+                    // const endVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToEndStaffEntry.parentVerticalContainer;
+                    if (startVerticalContainer) {
+                        // TODO: Needs to be implemented?
+                        // withinCrossedBeam = areStaffEntriesWithinCrossedBeam(startVerticalContainer,
+                        // endVerticalContainer, currentStaffLineIndex, nextStaffLineIndex);
+                    }
+
+                    if (withinCrossedBeam) {
+                        const nextStaffLine: StaffLine = musicSystem.StaffLines[nextStaffLineIndex];
+                        const nextStaffLineMinSkyLineValue: number = nextStaffLine.SkyBottomLineCalculator.getSkyLineMinInRange(upperStartX, upperEndX);
+                        const distanceBetweenStaffLines: number = nextStaffLine.PositionAndShape.RelativePosition.y -
+                            staffLine.PositionAndShape.RelativePosition.y;
+                        const relativeSkyLineHeight: number = distanceBetweenStaffLines + nextStaffLineMinSkyLineValue;
+
+                        if (relativeSkyLineHeight - this.rules.WedgeOpeningLength > this.rules.StaffHeight) {
+                            idealY = relativeSkyLineHeight - this.rules.WedgeVerticalMargin;
+                        } else {
+                            idealY = this.rules.StaffHeight + this.rules.WedgeOpeningLength;
+                        }
+
+                        graphicalContinuousDynamic.NotToBeRemoved = true;
+                    }
+                }
+            }
+
+            // do the same in case of a Wedge ending at another StaffLine
+            if (!sameStaffLine) {
+                maxBottomLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getBottomLineMaxInRange(lowerStartX, lowerEndX);
+
+                if (maxBottomLineValueForExpressionLength > secondIdealY) {
+                    secondIdealY = maxBottomLineValueForExpressionLength;
+                }
+
+                secondIdealY += this.rules.WedgeOpeningLength / 2;
+                secondIdealY += this.rules.WedgeVerticalMargin;
+            }
+
+            if (!withinCrossedBeam) {
+                idealY += this.rules.WedgeOpeningLength / 2;
+                idealY += this.rules.WedgeVerticalMargin;
+            }
+
+        } else if (placement === PlacementEnum.Above) {
+            // single Staff Instrument (eg Voice)
+            if (staffLine.ParentStaff.ParentInstrument.Staves.length === 1) {
+                // single Staff Voice Instrument
+                idealY = this.rules.WedgePlacementAboveY;
+            } else {
+                // Staff = not the first Staff of a 2-staved Instrument
+                let previousStaffLineIndex: number = 0;
+                if (currentStaffLineIndex > 0) {
+                    previousStaffLineIndex = currentStaffLineIndex - 1;
+                }
+
+                const previousStaffLine: StaffLine = musicSystem.StaffLines[previousStaffLineIndex];
+                const distanceBetweenStaffLines: number = staffLine.PositionAndShape.RelativePosition.y -
+                    previousStaffLine.PositionAndShape.RelativePosition.y -
+                    this.rules.StaffHeight;
+
+                // ideal Height is exactly between the two StaffLines
+                idealY = -distanceBetweenStaffLines / 2;
+            }
+
+            // must consider the upperWedge starting/ending tip for the comparison with the SkyLine
+            idealY += this.rules.WedgeOpeningLength / 2;
+            if (!sameStaffLine) {
+                secondIdealY = idealY;
+            }
+
+            // must check SkyLine for possible collisions within the Length of the Expression
+            // find the corresponding min value for the given Length
+            let minSkyLineValueForExpressionLength: number = skyBottomLineCalculator.getSkyLineMinInRange(upperStartX, upperEndX);
+
+            // if collisions, then set the Height accordingly
+            if (minSkyLineValueForExpressionLength < idealY) {
+                idealY = minSkyLineValueForExpressionLength;
+            }
+            const withinCrossedBeam: boolean = false;
+
+            // special case - wedge must be drawn within the boundaries of a crossedBeam
+            if (staffLine.ParentStaff.ParentInstrument.Staves.length > 1 && currentStaffLineIndex > 0) {
+                // find GraphicalStaffEntries closest to wedge's xPositions
+                const closestToStartStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperStartX);
+                const closestToEndStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperEndX);
+
+                if (closestToStartStaffEntry && closestToEndStaffEntry) {
+                    // must check both StaffLines
+                    const startVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToStartStaffEntry.parentVerticalContainer;
+                    // const endVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToEndStaffEntry.parentVerticalContainer;
+                    const formerStaffLineIndex: number = currentStaffLineIndex - 1;
+                    if (startVerticalContainer) {
+                        // withinCrossedBeam = this.areStaffEntriesWithinCrossedBeam(startVerticalContainer,
+                        // endVerticalContainer, currentStaffLineIndex, formerStaffLineIndex);
+                    }
+
+                    if (withinCrossedBeam) {
+                        const formerStaffLine: StaffLine = musicSystem.StaffLines[formerStaffLineIndex];
+                        const formerStaffLineMaxBottomLineValue: number = formerStaffLine.SkyBottomLineCalculator.
+                                                                          getBottomLineMaxInRange(upperStartX, upperEndX);
+                        const distanceBetweenStaffLines: number = staffLine.PositionAndShape.RelativePosition.y -
+                            formerStaffLine.PositionAndShape.RelativePosition.y;
+                        const relativeSkyLineHeight: number = distanceBetweenStaffLines - formerStaffLineMaxBottomLineValue;
+                        idealY = (relativeSkyLineHeight - this.rules.StaffHeight) / 2 + this.rules.StaffHeight;
+                    }
+                }
+            }
+
+            // do the same in case of a Wedge ending at another StaffLine
+            if (!sameStaffLine) {
+                minSkyLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getSkyLineMinInRange(lowerStartX, lowerEndX);
+
+                if (minSkyLineValueForExpressionLength < secondIdealY) {
+                    secondIdealY = minSkyLineValueForExpressionLength;
+                }
+
+                secondIdealY -= this.rules.WedgeOpeningLength / 2;
+            }
+
+            if (!withinCrossedBeam) {
+                idealY -= this.rules.WedgeOpeningLength / 2;
+                idealY -= this.rules.WedgeVerticalMargin;
+            }
+            if (!sameStaffLine) {
+                secondIdealY -= this.rules.WedgeVerticalMargin;
+            }
+        }
+
+        // now we have the correct placement Height for the Expression
+        // the idealY is calculated relative to the currentStaffLine
+
+        // Crescendo (point to the left, opening to the right)
+        graphicalContinuousDynamic.Lines.clear();
+        if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
+            if (sameStaffLine) {
+                graphicalContinuousDynamic.createCrescendoLines(upperStartX, upperEndX, idealY);
+                graphicalContinuousDynamic.calcPsi();
+            } else {
+                // two different Wedges
+                graphicalContinuousDynamic.createFirstHalfCrescendoLines(upperStartX, upperEndX, idealY);
+                graphicalContinuousDynamic.calcPsi();
+
+                secondGraphicalContinuousDynamic.createSecondHalfCresendoLines(lowerStartX, lowerEndX, secondIdealY);
+                secondGraphicalContinuousDynamic.calcPsi();
+            }
+        } else if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
+            if (sameStaffLine) {
+                graphicalContinuousDynamic.createDiminuendoLines(upperStartX, upperEndX, idealY);
+                graphicalContinuousDynamic.calcPsi();
+            } else {
+                graphicalContinuousDynamic.createFirstHalfDiminuendoLines(upperStartX, upperEndX, idealY);
+                graphicalContinuousDynamic.calcPsi();
+
+                secondGraphicalContinuousDynamic.createSecondHalfCresendoLines(lowerStartX, lowerEndX, secondIdealY);
+                secondGraphicalContinuousDynamic.calcPsi();
+            }
+        } //End Diminuendo
+    }
+
+    /**
+     * This method calculates the RelativePosition of a single GraphicalInstantaneousDynamicExpression.
+     * @param graphicalInstantaneousDynamic Dynamic expression to be calculated
+     * @param startPosInStaffline Starting point in staff line
+     */
+    protected calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic: GraphicalInstantaneousDynamicExpression,
+                                                               startPosInStaffline: PointF2D): void {
+        // get Margin Dimensions
+        const staffLine: StaffLine = graphicalInstantaneousDynamic.ParentStaffLine;
+        const left: number = startPosInStaffline.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginLeft;
+        const right: number = startPosInStaffline.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginRight;
+        const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
+        let yPosition: number = 0;
+
+        // calculate yPosition according to Placement
+        if (graphicalInstantaneousDynamic.Placement === PlacementEnum.Above) {
+            const skyLineValue: number = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
+
+            // if StaffLine part of multiStafff Instrument and not the first one, ideal yPosition middle of distance between Staves
+            if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== staffLine.ParentStaff.ParentInstrument.Staves[0]) {
+                const formerStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) - 1];
+                const difference: number = staffLine.PositionAndShape.RelativePosition.y -
+                    formerStaffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
+
+                // take always into account the size of the Dynamic
+                if (skyLineValue > -difference / 2) {
+                    yPosition = -difference / 2;
+                } else {
+                    yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
+                }
+            } else {
+                yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
+            }
+
+            graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, yPosition);
+        } else if (graphicalInstantaneousDynamic.Placement === PlacementEnum.Below) {
+            const bottomLineValue: number = skyBottomLineCalculator.getBottomLineMaxInRange(left, right);
+            // if StaffLine part of multiStafff Instrument and not the last one, ideal yPosition middle of distance between Staves
+            const lastStaff: Staff = staffLine.ParentStaff.ParentInstrument.Staves[staffLine.ParentStaff.ParentInstrument.Staves.length - 1];
+            if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== lastStaff) {
+                const nextStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) + 1];
+                const difference: number = nextStaffLine.PositionAndShape.RelativePosition.y -
+                    staffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
+                const border: number = graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
+
+                // take always into account the size of the Dynamic
+                if (bottomLineValue + border < this.rules.StaffHeight + difference / 2) {
+                    yPosition = this.rules.StaffHeight + difference / 2;
+                } else {
+                    yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
+                }
+            } else {
+                yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
+            }
+
+            graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, yPosition);
+        }
+        graphicalInstantaneousDynamic.updateSkyBottomLine();
+    }
+
     protected calcGraphicalRepetitionEndingsRecursively(repetition: Repetition): void {
         return;
     }
@@ -1033,7 +1393,7 @@ export abstract class MusicSheetCalculator {
                     let alreadyAdded: boolean = false;
                     for (const expr of staffLine.AbstractExpressions) {
                         if (expr instanceof GraphicalInstantaneousTempoExpression &&
-                           (expr as GraphicalInstantaneousTempoExpression).InstantaneousTempoExpression.Label === entry.Expression.Label) {
+                            (expr.SourceExpression as AbstractTempoExpression).Label === entry.Expression.Label) {
                             alreadyAdded = true;
                         }
                     }
@@ -1043,13 +1403,17 @@ export abstract class MusicSheetCalculator {
                     }
 
                     const graphicalTempoExpr: GraphicalInstantaneousTempoExpression = new GraphicalInstantaneousTempoExpression(entry.Expression, graphLabel);
+                    if (graphicalTempoExpr.ParentStaffLine === undefined) {
+                        log.warn("Adding staffline didn't work");
+                        // I am actually fooling the linter her and use the created object. This method needs refactoring,
+                        // all graphical expression creations should be in one place and ahve basic stuff like labels, lines, ...
+                        // in their constructor
+                    }
                     // in case of metronome mark:
                     if ((entry.Expression as InstantaneousTempoExpression).Enum === TempoEnum.metronomeMark) {
                         // use smaller font:
                         graphLabel.Label.fontHeight = 1.2;
                     }
-
-                    staffLine.AbstractExpressions.push(graphicalTempoExpr);
                 } else if (entry.Expression instanceof ContinuousTempoExpression) {
                     // FIXME: Not yet implemented
                     // let alreadyAdded: boolean = false;
@@ -1704,6 +2068,20 @@ export abstract class MusicSheetCalculator {
         }
     }
 
+    /**
+     * Re-adjust the x positioning of expressions. Update the skyline afterwards
+     */
+    private calculateExpressionAlignements(): void {
+        for (const graphicalMusicPage of this.graphicalMusicSheet.MusicPages) {
+            for (const musicSystem of graphicalMusicPage.MusicSystems) {
+                for (const staffLine of musicSystem.StaffLines) {
+                    staffLine.AlignmentManager.alignDynamicExpressions();
+                    staffLine.AbstractExpressions.forEach(ae => ae.updateSkyBottomLine());
+                }
+            }
+        }
+    }
+
     private calculateBeams(): void {
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
             const musicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
@@ -2083,14 +2461,14 @@ export abstract class MusicSheetCalculator {
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
                 lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
-                // + startStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
+            // + startStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
             const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffEntry.PositionAndShape.RelativePosition.x +
                 endStaffEntry.PositionAndShape.BorderMarginRight;
-                // + endStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
-                // TODO maybe add half-width of following note.
-                // though we don't have the vexflow note's bbox yet and extend layouting is unconstrained,
-                // we have more room for spacing without it.
+            // + endStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
+            // TODO maybe add half-width of following note.
+            // though we don't have the vexflow note's bbox yet and extend layouting is unconstrained,
+            // we have more room for spacing without it.
             // needed in order to line up with the Label's text bottom line (is the y position of the underscore)
             startY -= lyricEntry.GraphicalLabel.PositionAndShape.Size.height / 4;
             // create a Line (as underscore after the LyricLabel's End)

+ 17 - 10
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -24,6 +24,7 @@ import {Instrument} from "../Instrument";
 import {MusicSymbolDrawingStyle, PhonicScoreModes} from "./DrawingMode";
 import {GraphicalObject} from "./GraphicalObject";
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
+import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
 
 /**
  * Draw a [[GraphicalMusicSheet]] (through the .drawSheet method)
@@ -429,17 +430,23 @@ export abstract class MusicSheetDrawer {
     //         drawLineAsVerticalRectangle(ending.Right, absolutePosition, <number>GraphicalLayers.Notes);
     //     this.drawLabel(ending.Label, <number>GraphicalLayers.Notes);
     // }
+
+    /**
+     * Draws an instantaneous dynamic expression (p, pp, f, ff, ...) to the canvas
+     * @param instantaneousDynamic GraphicalInstantaneousDynamicExpression to be drawn
+     */
     protected drawInstantaneousDynamic(instantaneousDynamic: GraphicalInstantaneousDynamicExpression): void {
-        // expression.ExpressionSymbols.forEach(function (expressionSymbol) {
-        //     let position: PointF2D = expressionSymbol.PositionAndShape.AbsolutePosition;
-        //     let symbol: MusicSymbol = expressionSymbol.GetSymbol;
-        //     drawSymbol(symbol, MusicSymbolDrawingStyle.Normal, position);
-        // });
-    }
-    // protected drawContinuousDynamic(expression: GraphicalContinuousDynamicExpression,
-    //     absolute: PointF2D): void {
-    //     throw new Error("not implemented");
-    // }
+        throw new Error("not implemented");
+    }
+
+    /**
+     * Draws a continuous dynamic expression (wedges) to the canvas
+     * @param expression GraphicalContinuousDynamicExpression to be drawn
+     */
+    protected drawContinuousDynamic(expression: GraphicalContinuousDynamicExpression): void {
+        throw new Error("not implemented");
+    }
+
     protected drawSymbol(symbol: MusicSymbol, symbolStyle: MusicSymbolDrawingStyle, position: PointF2D,
                          scalingFactor: number = 1, layer: number = <number>GraphicalLayers.Notes): void {
         //empty

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

@@ -157,7 +157,6 @@ export class SkyBottomLineCalculator {
 
     /**
      * This method updates the SkyLine for a given Wedge.
-     * @param  to update the SkyLine for
      * @param start Start point of the wedge
      * @param end End point of the wedge
      */
@@ -194,7 +193,6 @@ export class SkyBottomLineCalculator {
 
     /**
      * This method updates the BottomLine for a given Wedge.
-     * @param  to update the bottomline for
      * @param start Start point of the wedge
      * @param end End point of the wedge
      */

+ 11 - 3
src/MusicalScore/Graphical/StaffLine.ts

@@ -12,6 +12,8 @@ import {GraphicalLabel} from "./GraphicalLabel";
 import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
 import { GraphicalOctaveShift } from "./GraphicalOctaveShift";
 import { GraphicalSlur } from "./GraphicalSlur";
+import { AlignmentManager } from "./AlignementManager";
+import { AbstractGraphicalExpression } from "./AbstractGraphicalExpression";
 
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
@@ -24,9 +26,10 @@ export abstract class StaffLine extends GraphicalObject {
     protected parentStaff: Staff;
     protected octaveShifts: GraphicalOctaveShift[] = [];
     protected skyBottomLine: SkyBottomLineCalculator;
+    protected alignmentManager: AlignmentManager;
     protected lyricLines: GraphicalLine[] = [];
     protected lyricsDashes: GraphicalLabel[] = [];
-    protected abstractExpressions: GraphicalObject[] = [];
+    protected abstractExpressions: AbstractGraphicalExpression[] = [];
 
     // For displaying Slurs
     protected graphicalSlurs: GraphicalSlur[] = [];
@@ -37,6 +40,7 @@ export abstract class StaffLine extends GraphicalObject {
         this.parentStaff = parentStaff;
         this.boundingBox = new BoundingBox(this, parentSystem.PositionAndShape);
         this.skyBottomLine = new SkyBottomLineCalculator(this);
+        this.alignmentManager = new AlignmentManager(this);
     }
 
     public get Measures(): GraphicalMeasure[] {
@@ -64,11 +68,11 @@ export abstract class StaffLine extends GraphicalObject {
         return this.lyricLines;
     }
 
-    public get AbstractExpressions(): GraphicalObject[] {
+    public get AbstractExpressions(): AbstractGraphicalExpression[] {
         return this.abstractExpressions;
     }
 
-    public set AbstractExpressions(value: GraphicalObject[]) {
+    public set AbstractExpressions(value: AbstractGraphicalExpression[]) {
         this.abstractExpressions = value;
     }
 
@@ -100,6 +104,10 @@ export abstract class StaffLine extends GraphicalObject {
         this.parentStaff = value;
     }
 
+    public get AlignmentManager(): AlignmentManager {
+        return this.alignmentManager;
+    }
+
     public get SkyBottomLineCalculator(): SkyBottomLineCalculator {
         return this.skyBottomLine;
     }

+ 26 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowContinuousDynamicExpression.ts

@@ -0,0 +1,26 @@
+import { GraphicalContinuousDynamicExpression } from "../GraphicalContinuousDynamicExpression";
+import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
+import { StaffLine } from "../StaffLine";
+import { GraphicalLabel } from "../GraphicalLabel";
+import { Label } from "../../Label";
+import { TextAlignmentEnum } from "../../../Common/Enums/TextAlignment";
+import { FontStyles } from "../../../Common/Enums/FontStyles";
+
+/**
+ * This class extends the GraphicalContinuousDynamicExpression and creates all necessary methods for drawing
+ */
+export class VexFlowContinuousDynamicExpression extends GraphicalContinuousDynamicExpression {
+    constructor(continuousDynamic: ContinuousDynamicExpression, staffLine: StaffLine, textHeight?: number) {
+        super(continuousDynamic, staffLine);
+        if (this.IsVerbal) {
+            this.label = new GraphicalLabel(new Label(continuousDynamic.Label),
+                                            textHeight ? textHeight : this.rules.ContinuousDynamicTextHeight,
+                                            TextAlignmentEnum.LeftCenter,
+                                            this.PositionAndShape);
+
+            this.label.Label.fontStyle = FontStyles.Italic;
+            this.label.setLabelPositionAndShapeBorders();
+            this.PositionAndShape.calculateBoundingBox();
+        }
+    }
+}

+ 6 - 17
src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts

@@ -3,28 +3,21 @@ import { InstantaneousDynamicExpression, DynamicEnum } from "../../VoiceData/Exp
 import { GraphicalLabel } from "../GraphicalLabel";
 import { Label } from "../../Label";
 import { TextAlignmentEnum } from "../../../Common/Enums/TextAlignment";
-import { EngravingRules } from "../EngravingRules";
 import { FontStyles } from "../../../Common/Enums/FontStyles";
 import { StaffLine } from "../StaffLine";
 import { GraphicalMeasure } from "../GraphicalMeasure";
 
 export class VexFlowInstantaneousDynamicExpression extends GraphicalInstantaneousDynamicExpression {
-    private mLabel: GraphicalLabel;
-
     constructor(instantaneousDynamicExpression: InstantaneousDynamicExpression, staffLine: StaffLine, measure: GraphicalMeasure) {
         super(instantaneousDynamicExpression, staffLine, measure);
 
-        let labelAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterTop;
-        if (EngravingRules.Rules.CompactMode) {
-            labelAlignment = TextAlignmentEnum.LeftBottom;
-        }
-        this.mLabel = new GraphicalLabel(new Label(this.Expression, labelAlignment),
-                                         EngravingRules.Rules.ContinuousDynamicTextHeight,
-                                         labelAlignment,
-                                         this.PositionAndShape);
+        this.label = new GraphicalLabel(new Label(this.Expression),
+                                        this.rules.ContinuousDynamicTextHeight,
+                                        TextAlignmentEnum.CenterCenter,
+                                        this.PositionAndShape);
 
-        this.mLabel.Label.fontStyle = FontStyles.BoldItalic;
-        this.mLabel.setLabelPositionAndShapeBorders();
+        this.label.Label.fontStyle = FontStyles.BoldItalic;
+        this.label.setLabelPositionAndShapeBorders();
         this.PositionAndShape.calculateBoundingBox();
     }
 
@@ -35,8 +28,4 @@ export class VexFlowInstantaneousDynamicExpression extends GraphicalInstantaneou
     get Expression(): string {
         return DynamicEnum[this.mInstantaneousDynamicExpression.DynEnum];
     }
-
-    get Label(): GraphicalLabel {
-        return this.mLabel;
-    }
 }

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

@@ -26,7 +26,6 @@ import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
 import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Voice} from "../../VoiceData/Voice";
-import {VexFlowInstantaneousDynamicExpression} from "./VexFlowInstantaneousDynamicExpression";
 import {LinkedVoice} from "../../VoiceData/LinkedVoice";
 import {EngravingRules} from "../EngravingRules";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
@@ -52,8 +51,6 @@ export class VexFlowMeasure extends GraphicalMeasure {
     public vfTies: Vex.Flow.StaveTie[] = [];
     /** The repetition instructions given as words or symbols (coda, dal segno..) */
     public vfRepetitionWords: Vex.Flow.Repetition[] = [];
-    /** Instant dynamics */
-    public instantaneousDynamics: VexFlowInstantaneousDynamicExpression[] = [];
     /** The VexFlow Stave (= one measure in a staffline) */
     private stave: Vex.Flow.Stave;
     /** VexFlow StaveConnectors (vertical lines) */
@@ -93,7 +90,6 @@ export class VexFlowMeasure extends GraphicalMeasure {
         this.connectors = [];
         // Clean up instructions
         this.resetLayout();
-        this.instantaneousDynamics = [];
     }
 
     /**

+ 387 - 467
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -1,38 +1,37 @@
-import {MusicSheetCalculator} from "../MusicSheetCalculator";
-import {VexFlowGraphicalSymbolFactory} from "./VexFlowGraphicalSymbolFactory";
-import {GraphicalMeasure} from "../GraphicalMeasure";
-import {StaffLine} from "../StaffLine";
-import {VoiceEntry} from "../../VoiceData/VoiceEntry";
-import {GraphicalNote} from "../GraphicalNote";
-import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
-import {GraphicalTie} from "../GraphicalTie";
-import {Tie} from "../../VoiceData/Tie";
-import {SourceMeasure} from "../../VoiceData/SourceMeasure";
-import {MultiExpression} from "../../VoiceData/Expressions/MultiExpression";
-import {RepetitionInstruction} from "../../VoiceData/Instructions/RepetitionInstruction";
-import {Beam} from "../../VoiceData/Beam";
-import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
-import {OctaveEnum, OctaveShift} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
-import {Fraction} from "../../../Common/DataObjects/Fraction";
-import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
-import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
-import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
-import {Tuplet} from "../../VoiceData/Tuplet";
-import {VexFlowMeasure} from "./VexFlowMeasure";
-import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
+import { MusicSheetCalculator } from "../MusicSheetCalculator";
+import { VexFlowGraphicalSymbolFactory } from "./VexFlowGraphicalSymbolFactory";
+import { GraphicalMeasure } from "../GraphicalMeasure";
+import { StaffLine } from "../StaffLine";
+import { VoiceEntry } from "../../VoiceData/VoiceEntry";
+import { GraphicalNote } from "../GraphicalNote";
+import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
+import { GraphicalTie } from "../GraphicalTie";
+import { Tie } from "../../VoiceData/Tie";
+import { SourceMeasure } from "../../VoiceData/SourceMeasure";
+import { MultiExpression } from "../../VoiceData/Expressions/MultiExpression";
+import { RepetitionInstruction } from "../../VoiceData/Instructions/RepetitionInstruction";
+import { Beam } from "../../VoiceData/Beam";
+import { ClefInstruction } from "../../VoiceData/Instructions/ClefInstruction";
+import { OctaveEnum, OctaveShift } from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { Fraction } from "../../../Common/DataObjects/Fraction";
+import { LyricWord } from "../../VoiceData/Lyrics/LyricsWord";
+import { OrnamentContainer } from "../../VoiceData/OrnamentContainer";
+import { ArticulationEnum } from "../../VoiceData/VoiceEntry";
+import { Tuplet } from "../../VoiceData/Tuplet";
+import { VexFlowMeasure } from "./VexFlowMeasure";
+import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer";
 import Vex = require("vexflow");
 import * as log from "loglevel";
-import {unitInPixels} from "./VexFlowMusicSheetDrawer";
-import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
-import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
-import {GraphicalLyricEntry} from "../GraphicalLyricEntry";
-import {GraphicalLabel} from "../GraphicalLabel";
-import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
-import {GraphicalLyricWord} from "../GraphicalLyricWord";
-import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
+import { unitInPixels } from "./VexFlowMusicSheetDrawer";
+import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
+import { TechnicalInstruction } from "../../VoiceData/Instructions/TechnicalInstruction";
+import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
+import { GraphicalLabel } from "../GraphicalLabel";
+import { LyricsEntry } from "../../VoiceData/Lyrics/LyricsEntry";
+import { GraphicalLyricWord } from "../GraphicalLyricWord";
+import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
 import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
 import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
-import {BoundingBox} from "../BoundingBox";
 import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
 /* VexFlow Version - for later use
 // import { VexFlowSlur } from "./VexFlowSlur";
@@ -40,14 +39,12 @@ import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
 // import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 */
 import { EngravingRules } from "../EngravingRules";
-import { InstantaneousDynamicExpression } from "../../VoiceData/Expressions/InstantaneousDynamicExpression";
 import { PointF2D } from "../../../Common/DataObjects/PointF2D";
-import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneousDynamicExpression";
-import { SkyBottomLineCalculator } from "../SkyBottomLineCalculator";
-import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
-import { Staff } from "../../VoiceData/Staff";
 import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
 import { GraphicalSlur } from "../GraphicalSlur";
+import { BoundingBox } from "../BoundingBox";
+import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
+import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   /** space needed for a dash for lyrics spacing, calculated once */
@@ -68,17 +65,17 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     }
   }
 
-    protected formatMeasures(): void {
-      for (const verticalMeasureList of this.graphicalMusicSheet.MeasureList) {
-        const firstMeasure: VexFlowMeasure = verticalMeasureList[0] as VexFlowMeasure;
-        // first measure has formatting method as lambda function object, but formats all measures. TODO this could be refactored
-        firstMeasure.format();
-        for (const measure of verticalMeasureList) {
-          for (const staffEntry of measure.staffEntries) {
-            (<VexFlowStaffEntry>staffEntry).calculateXPosition();
-          }
+  protected formatMeasures(): void {
+    for (const verticalMeasureList of this.graphicalMusicSheet.MeasureList) {
+      const firstMeasure: VexFlowMeasure = verticalMeasureList[0] as VexFlowMeasure;
+      // first measure has formatting method as lambda function object, but formats all measures. TODO this could be refactored
+      firstMeasure.format();
+      for (const measure of verticalMeasureList) {
+        for (const staffEntry of measure.staffEntries) {
+          (<VexFlowStaffEntry>staffEntry).calculateXPosition();
         }
       }
+    }
   }
 
   //protected clearSystemsAndMeasures(): void {
@@ -109,56 +106,56 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter();
 
     for (const measure of measures) {
-        const mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
-        const voices: Vex.Flow.Voice[] = [];
-        for (const voiceID in mvoices) {
-            if (mvoices.hasOwnProperty(voiceID)) {
-                voices.push(mvoices[voiceID]);
-                allVoices.push(mvoices[voiceID]);
-            }
+      const mvoices: { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
+      const voices: Vex.Flow.Voice[] = [];
+      for (const voiceID in mvoices) {
+        if (mvoices.hasOwnProperty(voiceID)) {
+          voices.push(mvoices[voiceID]);
+          allVoices.push(mvoices[voiceID]);
         }
-        if (voices.length === 0) {
-            log.info("Found a measure with no voices. Continuing anyway.", mvoices);
-            continue;
-        }
-        // all voices that belong to one stave are collectively added to create a common context in VexFlow.
-        formatter.joinVoices(voices);
+      }
+      if (voices.length === 0) {
+        log.info("Found a measure with no voices. Continuing anyway.", mvoices);
+        continue;
+      }
+      // all voices that belong to one stave are collectively added to create a common context in VexFlow.
+      formatter.joinVoices(voices);
     }
 
     let minStaffEntriesWidth: number = 200;
     if (allVoices.length > 0) {
-        // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
-        // FIXME: a more relaxed formatting of voices
-        minStaffEntriesWidth = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
-        // firstMeasure.formatVoices = (w: number) => {
-        //     formatter.format(allVoices, w);
-        // };
-        MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minStaffEntriesWidth);
-        for (const measure of measures) {
-          if (measure === measures[0]) {
-            const vexflowMeasure: VexFlowMeasure = (measure as VexFlowMeasure);
-            // prepare format function for voices, will be called later for formatting measure again
-            vexflowMeasure.formatVoices = (w: number) => {
-                    formatter.format(allVoices, w);
-              // formatter.format(allVoices, w, {
-              //   align_rests: false, // TODO
-              //   // align_rests = true causes a Vexflow Exception for Mozart - An Chloe
-              //   // align_rests = false still aligns rests with beams according to Vexflow, but doesn't seem to do anything
-              // });
-                };
-            // format now for minimum width, calculateMeasureWidthFromLyrics later
-            vexflowMeasure.formatVoices(minStaffEntriesWidth * unitInPixels);
-          } else {
-            (measure as VexFlowMeasure).formatVoices = undefined;
-            }
+      // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
+      // FIXME: a more relaxed formatting of voices
+      minStaffEntriesWidth = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
+      // firstMeasure.formatVoices = (w: number) => {
+      //     formatter.format(allVoices, w);
+      // };
+      MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minStaffEntriesWidth);
+      for (const measure of measures) {
+        if (measure === measures[0]) {
+          const vexflowMeasure: VexFlowMeasure = (measure as VexFlowMeasure);
+          // prepare format function for voices, will be called later for formatting measure again
+          vexflowMeasure.formatVoices = (w: number) => {
+            formatter.format(allVoices, w);
+            // formatter.format(allVoices, w, {
+            //   align_rests: false, // TODO
+            //   // align_rests = true causes a Vexflow Exception for Mozart - An Chloe
+            //   // align_rests = false still aligns rests with beams according to Vexflow, but doesn't seem to do anything
+            // });
+          };
+          // format now for minimum width, calculateMeasureWidthFromLyrics later
+          vexflowMeasure.formatVoices(minStaffEntriesWidth * unitInPixels);
+        } else {
+          (measure as VexFlowMeasure).formatVoices = undefined;
         }
+      }
     }
 
     for (const graphicalMeasure of measures) {
       for (const staffEntry of graphicalMeasure.staffEntries) {
         // here the measure modifiers are not yet set, therefore the begin instruction width will be empty
         (<VexFlowStaffEntry>staffEntry).calculateXPosition();
-  }
+      }
     }
     // calculateMeasureWidthFromLyrics() will be called from MusicSheetCalculator after this
     return minStaffEntriesWidth;
@@ -314,7 +311,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
 
   protected updateStaffLineBorders(staffLine: StaffLine): void {
-      staffLine.SkyBottomLineCalculator.updateStaffLineBorders();
+    staffLine.SkyBottomLineCalculator.updateStaffLineBorders();
   }
 
   protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
@@ -350,15 +347,15 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    */
   protected calculateSystemYLayout(): void {
     for (const graphicalMusicPage of this.graphicalMusicSheet.MusicPages) {
-            for (const musicSystem of graphicalMusicPage.MusicSystems) {
-                this.optimizeDistanceBetweenStaffLines(musicSystem);
-          }
-
-          // set y positions of systems using the previous system and a fixed distance.
-            this.calculateMusicSystemsRelativePositions(graphicalMusicPage);
-        }
+      for (const musicSystem of graphicalMusicPage.MusicSystems) {
+        this.optimizeDistanceBetweenStaffLines(musicSystem);
       }
 
+      // set y positions of systems using the previous system and a fixed distance.
+      this.calculateMusicSystemsRelativePositions(graphicalMusicPage);
+    }
+  }
+
   /**
    * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
    */
@@ -379,11 +376,11 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     return;
   }
 
-    /**
-     * Calculate the shape (Bezier curve) for this tie.
-     * @param tie
-     * @param tieIsAtSystemBreak
-     */
+  /**
+   * Calculate the shape (Bezier curve) for this tie.
+   * @param tie
+   * @param tieIsAtSystemBreak
+   */
   protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
     const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
     const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
@@ -436,127 +433,50 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     const absoluteTimestamp: Fraction = multiExpression.AbsoluteTimestamp;
     const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
     const staffLine: StaffLine = measures[staffIndex].ParentStaffLine;
+    const startMeasure: GraphicalMeasure = measures[staffIndex];
 
-    if (multiExpression.InstantaneousDynamic) {
-        const instantaneousDynamic: InstantaneousDynamicExpression = multiExpression.InstantaneousDynamic;
-
-        const startPosInStaffline: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
-          absoluteTimestamp,
-          staffIndex,
-          staffLine,
-          staffLine.isPartOfMultiStaffInstrument());
-        if (Math.abs(startPosInStaffline.x) === 0) {
-          startPosInStaffline.x = measures[staffIndex].beginInstructionsWidth + this.rules.RhythmRightMargin;
-        }
-        const measure: GraphicalMeasure = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex];
-        const graphicalInstantaneousDynamic: VexFlowInstantaneousDynamicExpression = new VexFlowInstantaneousDynamicExpression(
-          instantaneousDynamic,
-          staffLine,
-          measure);
-        (measure as VexFlowMeasure).instantaneousDynamics.push(graphicalInstantaneousDynamic);
-        this.calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic, staffLine, startPosInStaffline);
-    }
-  }
+    const startPosInStaffline: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
+      absoluteTimestamp,
+      staffIndex,
+      staffLine,
+      staffLine.isPartOfMultiStaffInstrument());
 
-  public calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic: VexFlowInstantaneousDynamicExpression,
-                                                          staffLine: StaffLine,
-                                                          relative: PointF2D): void {
-    // // add to StaffLine and set PSI relations
-    staffLine.AbstractExpressions.push(graphicalInstantaneousDynamic);
-    staffLine.PositionAndShape.ChildElements.push(graphicalInstantaneousDynamic.PositionAndShape);
-    if (this.staffLinesWithGraphicalExpressions.indexOf(staffLine) === -1) {
-        this.staffLinesWithGraphicalExpressions.push(staffLine);
+    const dynamicStartPosition: PointF2D = startPosInStaffline;
+    if (startPosInStaffline.x <= 0) {
+      dynamicStartPosition.x = startMeasure.beginInstructionsWidth + this.rules.RhythmRightMargin;
     }
 
-    // get Margin Dimensions
-    const left: number = relative.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginLeft;
-    const right: number = relative.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginRight;
-    const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
-
-    // get possible previous Dynamic
-    let previousExpression: GraphicalInstantaneousDynamicExpression = undefined;
-    const expressionIndex: number = staffLine.AbstractExpressions.indexOf(graphicalInstantaneousDynamic);
-    if (expressionIndex > 0) {
-        previousExpression = (staffLine.AbstractExpressions[expressionIndex - 1] as GraphicalInstantaneousDynamicExpression);
+    if (multiExpression.InstantaneousDynamic) {
+      const graphicalInstantaneousDynamic: VexFlowInstantaneousDynamicExpression = new VexFlowInstantaneousDynamicExpression(
+        multiExpression.InstantaneousDynamic,
+        staffLine,
+        startMeasure);
+      this.calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic, dynamicStartPosition);
     }
-
-    // TODO: Not yet implemented
-    // // is previous a ContinuousDynamic?
-    // if (previousExpression && previousExpression instanceof GraphicalContinuousDynamicExpression)
-    // {
-    //     GraphicalContinuousDynamicExpression formerGraphicalContinuousDynamic =
-    //         (GraphicalContinuousDynamicExpression)previousExpression;
-
-    //     optimizeFormerContDynamicXPositionForInstDynamic(staffLine, skyBottomLineCalculator,
-    //                                                      graphicalInstantaneousDynamic,
-    //                                                      formerGraphicalContinuousDynamic, left, right);
-    // }
-    // // is previous a instantaneousDynamic?
-    // else
-    if (previousExpression && previousExpression instanceof GraphicalInstantaneousDynamicExpression) {
-        //const formerGraphicalInstantaneousDynamic: GraphicalInstantaneousDynamicExpression = previousExpression;
-
-        // optimizeFormerInstDynamicXPositionForInstDynamic(formerGraphicalInstantaneousDynamic,
-        //                                                  graphicalInstantaneousDynamic, ref relative, ref left, ref right);
-    }// End x-positioning overlap check
-
-    // calculate yPosition according to Placement
-    if (graphicalInstantaneousDynamic.InstantaneousDynamic.Placement === PlacementEnum.Above) {
-        const skyLineValue: number = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
-        let yPosition: number = 0;
-
-        // if StaffLine part of multiStafff Instrument and not the first one, ideal yPosition middle of distance between Staves
-        if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== staffLine.ParentStaff.ParentInstrument.Staves[0]) {
-            const formerStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) - 1];
-            const difference: number = staffLine.PositionAndShape.RelativePosition.y -
-                               formerStaffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
-
-            // take always into account the size of the Dynamic
-            if (skyLineValue > -difference / 2) {
-                yPosition = -difference / 2;
-            } else {
-                yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
-            }
-        } else {
-            yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
-        }
-
-        graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(relative.x, yPosition);
-        skyBottomLineCalculator.updateSkyLineInRange(left, right, yPosition + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop);
-    } else if (graphicalInstantaneousDynamic.InstantaneousDynamic.Placement === PlacementEnum.Below) {
-        const bottomLineValue: number = skyBottomLineCalculator.getBottomLineMaxInRange(left, right);
-        let yPosition: number = 0;
-
-        // if StaffLine part of multiStafff Instrument and not the last one, ideal yPosition middle of distance between Staves
-        const lastStaff: Staff = staffLine.ParentStaff.ParentInstrument.Staves[staffLine.ParentStaff.ParentInstrument.Staves.length - 1];
-        if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== lastStaff) {
-            const nextStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) + 1];
-            const difference: number = nextStaffLine.PositionAndShape.RelativePosition.y -
-                               staffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
-            const border: number = graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
-
-            // take always into account the size of the Dynamic
-            if (bottomLineValue + border < this.rules.StaffHeight + difference / 2) {
-                yPosition = this.rules.StaffHeight + difference / 2;
-            } else {
-                yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
-            }
-        } else {
-            yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
-        }
-
-        graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(relative.x, yPosition);
-        skyBottomLineCalculator.updateBottomLineInRange(left, right, yPosition + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom);
+    if (multiExpression.StartingContinuousDynamic) {
+      const continuousDynamic: ContinuousDynamicExpression = multiExpression.StartingContinuousDynamic;
+      const graphicalContinuousDynamic: VexFlowContinuousDynamicExpression = new VexFlowContinuousDynamicExpression(
+        multiExpression.StartingContinuousDynamic,
+        staffLine);
+      graphicalContinuousDynamic.StartMeasure = startMeasure;
+
+      if (!graphicalContinuousDynamic.IsVerbal && continuousDynamic.EndMultiExpression) {
+        this.calculateGraphicalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
+      } else if (graphicalContinuousDynamic.IsVerbal) {
+        this.calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
+      } else {
+        log.warn("This continous dynamic is not covered");
+      }
     }
-}
+  }
 
-    /**
-     * Calculate a single OctaveShift for a [[MultiExpression]].
-     * @param sourceMeasure
-     * @param multiExpression
-     * @param measureIndex
-     * @param staffIndex
-     */
+  /**
+   * Calculate a single OctaveShift for a [[MultiExpression]].
+   * @param sourceMeasure
+   * @param multiExpression
+   * @param measureIndex
+   * @param staffIndex
+   */
   protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
     // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
     const octaveShift: OctaveShift = multiExpression.OctaveShiftStart;
@@ -568,9 +488,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
     let endMeasure: GraphicalMeasure = undefined;
     if (octaveShift.ParentEndMultiExpression !== undefined) {
-        endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
-                                                                                           staffIndex);
-  }
+      endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
+                                                                                         staffIndex);
+    }
     let startMeasure: GraphicalMeasure = undefined;
     if (octaveShift.ParentEndMultiExpression !== undefined) {
       startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentStartMultiExpression.SourceMeasureParent,
@@ -578,76 +498,76 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     }
 
     if (endMeasure !== undefined) {
-        // calculate GraphicalOctaveShift and RelativePositions
-        const graphicalOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, startStaffLine.PositionAndShape);
-        startStaffLine.OctaveShifts.push(graphicalOctaveShift);
-
-        // calculate RelativePosition and Dashes
-        const startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
-        const endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
-
-        graphicalOctaveShift.setStartNote(startStaffEntry);
-
-        if (endMeasure.ParentStaffLine !== startMeasure.ParentStaffLine) {
-          graphicalOctaveShift.endsOnDifferentStaffLine = true;
-          const lastMeasure: GraphicalMeasure = startMeasure.ParentStaffLine.Measures[startMeasure.ParentStaffLine.Measures.length - 1];
-          const lastNote: GraphicalStaffEntry = lastMeasure.staffEntries[lastMeasure.staffEntries.length - 1];
-          graphicalOctaveShift.setEndNote(lastNote);
-
-          // Now finish the shift on the next line
-          const remainingOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, endMeasure.PositionAndShape);
-          endMeasure.ParentStaffLine.OctaveShifts.push(remainingOctaveShift);
-          const firstMeasure: GraphicalMeasure = endMeasure.ParentStaffLine.Measures[0];
-          const firstNote: GraphicalStaffEntry = firstMeasure.staffEntries[0];
-          remainingOctaveShift.setStartNote(firstNote);
-          remainingOctaveShift.setEndNote(endStaffEntry);
-        } else {
-          graphicalOctaveShift.setEndNote(endStaffEntry);
-        }
+      // calculate GraphicalOctaveShift and RelativePositions
+      const graphicalOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, startStaffLine.PositionAndShape);
+      startStaffLine.OctaveShifts.push(graphicalOctaveShift);
+
+      // calculate RelativePosition and Dashes
+      const startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
+      const endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
+
+      graphicalOctaveShift.setStartNote(startStaffEntry);
+
+      if (endMeasure.ParentStaffLine !== startMeasure.ParentStaffLine) {
+        graphicalOctaveShift.endsOnDifferentStaffLine = true;
+        const lastMeasure: GraphicalMeasure = startMeasure.ParentStaffLine.Measures[startMeasure.ParentStaffLine.Measures.length - 1];
+        const lastNote: GraphicalStaffEntry = lastMeasure.staffEntries[lastMeasure.staffEntries.length - 1];
+        graphicalOctaveShift.setEndNote(lastNote);
+
+        // Now finish the shift on the next line
+        const remainingOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, endMeasure.PositionAndShape);
+        endMeasure.ParentStaffLine.OctaveShifts.push(remainingOctaveShift);
+        const firstMeasure: GraphicalMeasure = endMeasure.ParentStaffLine.Measures[0];
+        const firstNote: GraphicalStaffEntry = firstMeasure.staffEntries[0];
+        remainingOctaveShift.setStartNote(firstNote);
+        remainingOctaveShift.setEndNote(endStaffEntry);
+      } else {
+        graphicalOctaveShift.setEndNote(endStaffEntry);
+      }
     } else {
       log.warn("End measure for octave shift is undefined! This should not happen!");
     }
   }
 
-    /**
-     * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
-     * @param repetitionInstruction
-     * @param measureIndex
-     */
+  /**
+   * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
+   * @param repetitionInstruction
+   * @param measureIndex
+   */
   protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
-      // find first visible StaffLine
-      let uppermostMeasure: VexFlowMeasure = undefined;
-      const measures: VexFlowMeasure[]  = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
-      for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
-        const graphicalMeasure: VexFlowMeasure = measures[idx];
-        if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
-            uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
-            break;
-        }
-      }
-      // ToDo: feature/Repetitions
-      // now create corresponding graphical symbol or Text in VexFlow:
-      // use top measure and staffline for positioning.
-      if (uppermostMeasure !== undefined) {
-        uppermostMeasure.addWordRepetition(repetitionInstruction);
+    // find first visible StaffLine
+    let uppermostMeasure: VexFlowMeasure = undefined;
+    const measures: VexFlowMeasure[] = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
+    for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
+      const graphicalMeasure: VexFlowMeasure = measures[idx];
+      if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
+        uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
+        break;
       }
     }
+    // ToDo: feature/Repetitions
+    // now create corresponding graphical symbol or Text in VexFlow:
+    // use top measure and staffline for positioning.
+    if (uppermostMeasure !== undefined) {
+      uppermostMeasure.addWordRepetition(repetitionInstruction);
+    }
+  }
 
   protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
     return;
   }
 
-    /**
-     * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
-     * @param tiedGraphicalNote
-     * @param beams
-     * @param activeClef
-     * @param octaveShiftValue
-     * @param graphicalStaffEntry
-     * @param duration
-     * @param openTie
-     * @param isLastTieNote
-     */
+  /**
+   * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
+   * @param tiedGraphicalNote
+   * @param beams
+   * @param activeClef
+   * @param octaveShiftValue
+   * @param graphicalStaffEntry
+   * @param duration
+   * @param openTie
+   * @param isLastTieNote
+   */
   protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
                                     octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
                                     openTie: Tie, isLastTieNote: boolean): void {
@@ -665,45 +585,45 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   }
 
   protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
-      voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
-          const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
-                                                                                   graphicalStaffEntry,
-                                                                                   this.rules.LyricsHeight,
-                                                                                   this.rules.StaffHeight);
-
-          graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
-
-          // create corresponding GraphicalLabel
-          const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
-          graphicalLabel.setLabelPositionAndShapeBorders();
-
-          if (lyricsEntry.Word !== undefined) {
-              const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
-              let index: number = lyricWords.indexOf(lyricsEntry.Word);
-              if (index === -1) {
-                  lyricWords.push(lyricsEntry.Word);
-                  index = lyricWords.indexOf(lyricsEntry.Word);
-              }
+    voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
+      const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
+                                                                               graphicalStaffEntry,
+                                                                               this.rules.LyricsHeight,
+                                                                               this.rules.StaffHeight);
+
+      graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
+
+      // create corresponding GraphicalLabel
+      const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
+      graphicalLabel.setLabelPositionAndShapeBorders();
+
+      if (lyricsEntry.Word !== undefined) {
+        const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
+        let index: number = lyricWords.indexOf(lyricsEntry.Word);
+        if (index === -1) {
+          lyricWords.push(lyricsEntry.Word);
+          index = lyricWords.indexOf(lyricsEntry.Word);
+        }
 
-              if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
-                  const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
+        if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
+          const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
 
-                  graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
-                  graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
-                  this.graphicalLyricWords.push(graphicalLyricWord);
-              } else {
-                  const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
+          graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+          graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+          this.graphicalLyricWords.push(graphicalLyricWord);
+        } else {
+          const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
 
-                  graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
-                  graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+          graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+          graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
 
-                  if (graphicalLyricWord.isFilled()) {
-                      lyricWords.splice(index, 1);
-                      this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
-                  }
-              }
+          if (graphicalLyricWord.isFilled()) {
+            lyricWords.splice(index, 1);
+            this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
           }
-      });
+        }
+      }
+    });
   }
 
   protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
@@ -754,9 +674,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    */
   public findIndexGraphicalSlurFromSlur(gSlurs: GraphicalSlur[], slur: Slur): number {
     for (let slurIndex: number = 0; slurIndex < gSlurs.length; slurIndex++) {
-        if (gSlurs[slurIndex].slur === slur) {
-            return slurIndex;
-        }
+      if (gSlurs[slurIndex].slur === slur) {
+        return slurIndex;
+      }
     }
     return -1;
   }
@@ -789,177 +709,177 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     */
 
     for (const gmPage of this.graphicalMusicSheet.MusicPages) {
-        for (const musicSystem  of gmPage.MusicSystems) {
-            for (const staffLine of musicSystem.StaffLines) {
-                // if a graphical slur reaches out of the last musicsystem, we have to create another graphical slur reaching into this musicsystem
-                // (one slur needs 2 graphical slurs)
-                const openGraphicalSlurs: GraphicalSlur[] = openSlursDict[staffLine.ParentStaff.idInMusicSheet];
-                for (let slurIndex: number = 0; slurIndex < openGraphicalSlurs.length; slurIndex++) {
-                  const oldGSlur: GraphicalSlur = openGraphicalSlurs[slurIndex];
-                  const newGSlur: GraphicalSlur = new GraphicalSlur(oldGSlur.slur); //Graphicalslur.createFromSlur(oldSlur);
-                  staffLine.addSlurToStaffline(newGSlur); // every VFSlur is added to the array in the VFStaffline!
-                  openGraphicalSlurs[slurIndex] = newGSlur;
-                }
+      for (const musicSystem of gmPage.MusicSystems) {
+        for (const staffLine of musicSystem.StaffLines) {
+          // if a graphical slur reaches out of the last musicsystem, we have to create another graphical slur reaching into this musicsystem
+          // (one slur needs 2 graphical slurs)
+          const openGraphicalSlurs: GraphicalSlur[] = openSlursDict[staffLine.ParentStaff.idInMusicSheet];
+          for (let slurIndex: number = 0; slurIndex < openGraphicalSlurs.length; slurIndex++) {
+            const oldGSlur: GraphicalSlur = openGraphicalSlurs[slurIndex];
+            const newGSlur: GraphicalSlur = new GraphicalSlur(oldGSlur.slur); //Graphicalslur.createFromSlur(oldSlur);
+            staffLine.addSlurToStaffline(newGSlur); // every VFSlur is added to the array in the VFStaffline!
+            openGraphicalSlurs[slurIndex] = newGSlur;
+          }
 
-                /* VexFlow Version - for later use
-                const vfOpenSlurs: VexFlowSlur[] = vfOpenSlursDict[staffLine.ParentStaff.idInMusicSheet];
-                const vfStaffLine: VexFlowStaffLine = <VexFlowStaffLine> staffLine;
-                for (let slurIndex: number = 0; slurIndex < vfOpenSlurs.length; slurIndex++) {
-                    const oldVFSlur: VexFlowSlur = vfOpenSlurs[slurIndex];
-                    const newVFSlur: VexFlowSlur = VexFlowSlur.createFromVexflowSlur(oldVFSlur);
-                    newVFSlur.vfStartNote = undefined;
-                    vfStaffLine.addVFSlurToVFStaffline(newVFSlur); // every VFSlur is added to the array in the VFStaffline!
-                    vfOpenSlurs[slurIndex] = newVFSlur;
-                }
-                */
-
-                // add reference of slur array to the VexFlowStaffline class
-                for (const graphicalMeasure of staffLine.Measures) {
-                    for (const graphicalStaffEntry of graphicalMeasure.staffEntries) {
-                        // for (var idx5: number = 0, len5 = graphicalStaffEntry.GraceStaffEntriesBefore.Count; idx5 < len5; ++idx5) {
-                        //     var graceStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.GraceStaffEntriesBefore[idx5];
-                        //     if (graceStaffEntry.Notes[0][0].SourceNote.NoteSlurs.Count > 0) {
-                        //         var graceNote: Note = graceStaffEntry.Notes[0][0].SourceNote;
-                        //         graceStaffEntry.RelInMeasureTimestamp = Fraction.createFromFraction(graphicalStaffEntry.RelInMeasureTimestamp);
-                        //         for (var idx6: number = 0, len6 = graceNote.NoteSlurs.Count; idx6 < len6; ++idx6) {
-                        //             var graceNoteSlur: Slur = graceNote.NoteSlurs[idx6];
-                        //             if (graceNoteSlur.StartNote == graceNote) {
-                        //                 var vfSlur: VexFlowSlur = new VexFlowSlur(graceNoteSlur);
-                        //                 vfSlur.GraceStart = true;
-                        //                 staffLine.GraphicalSlurs.Add(vfSlur);
-                        //                 openGraphicalSlurs[i].Add(vfSlur);
-                        //                 for (var j: number = graphicalStaffEntry.GraceStaffEntriesBefore.IndexOf(graceStaffEntry);
-                        //                     j < graphicalStaffEntry.GraceStaffEntriesBefore.Count; j++)
-                        //                        vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesBefore[j]);
-                        //             }
-                        //             if (graceNote == graceNoteSlur.EndNote) {
-                        //                 var vfSlur: VexFlowSlur = findGraphicalSlurFromSlur(openGraphicalSlurs[i], graceNoteSlur);
-                        //                 if (vfSlur != null) {
-                        //                     vfSlur.GraceEnd = true;
-                        //                     openGraphicalSlurs[i].Remove(vfSlur);
-                        //                     for (var j: number = 0; j <= graphicalStaffEntry.GraceStaffEntriesBefore.IndexOf(graceStaffEntry); j++)
-                        //                         vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesBefore[j]);
-                        //                 }
-                        //             }
-                        //         }
-                        //     }
-                        // }
-                        // loop over "normal" notes (= no gracenotes)
-                        for (const graphicalVoiceEntry of graphicalStaffEntry.graphicalVoiceEntries) {
-                            for (const graphicalNote of graphicalVoiceEntry.notes) {
-                                for (const slur of graphicalNote.sourceNote.NoteSlurs) {
-                                    // extra check for some MusicSheets that have openSlurs (because only the first Page is available -> Recordare files)
-                                    if (slur.EndNote === undefined || slur.StartNote === undefined) {
-                                      continue;
-                                    }
-                                    // add new VexFlowSlur to List
-                                    if (slur.StartNote === graphicalNote.sourceNote) {
-                                        if (graphicalNote.sourceNote.NoteTie !== undefined) {
-                                            if (graphicalNote.parentVoiceEntry.parentStaffEntry.getAbsoluteTimestamp() !==
-                                            graphicalNote.sourceNote.NoteTie.StartNote.getAbsoluteTimestamp()) {
-                                                break;
-                                            }
-                                        }
-
-                                        // Add a Graphical Slur to the staffline, if the recent note is the Startnote of a slur
-                                        const gSlur: GraphicalSlur = new GraphicalSlur(slur);
-                                        openGraphicalSlurs.push(gSlur);
-                                        staffLine.addSlurToStaffline(gSlur);
-
-                                        /* VexFlow Version - for later use
-                                        const vfSlur: VexFlowSlur = new VexFlowSlur(slur);
-                                        vfOpenSlurs.push(vfSlur); //add open... adding / removing is JUST DONE in the open... array
-                                        vfSlur.vfStartNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
-                                        vfStaffLine.addVFSlurToVFStaffline(vfSlur); // every VFSlur is added to the array in the VFStaffline!
-                                        */
-                                    }
-                                    if (slur.EndNote === graphicalNote.sourceNote) {
-                                        // Remove the Graphical Slur from the staffline if the note is the Endnote of a slur
-                                        const index: number = this.findIndexGraphicalSlurFromSlur(openGraphicalSlurs, slur);
-                                        if (index >= 0) {
-                                            // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
-                                            const gSlur: GraphicalSlur = openGraphicalSlurs[index];
-                                            if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
-                                              gSlur.staffEntries.push(graphicalStaffEntry);
-                                            }
-
-                                            openGraphicalSlurs.splice(index, 1);
-                                        }
-
-                                        /* VexFlow Version - for later use
-                                        const vfIndex: number = this.findIndexVFSlurFromSlur(vfOpenSlurs, slur);
-                                        if (vfIndex !== undefined) {
-                                            // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
-                                            const vfSlur: VexFlowSlur = vfOpenSlurs[vfIndex];
-                                            vfSlur.vfEndNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
-                                            vfSlur.createVexFlowCurve();
-                                            vfOpenSlurs.splice(vfIndex, 1);
-                                        }
-                                        */
-                                    }
-                                }
-                            }
+          /* VexFlow Version - for later use
+          const vfOpenSlurs: VexFlowSlur[] = vfOpenSlursDict[staffLine.ParentStaff.idInMusicSheet];
+          const vfStaffLine: VexFlowStaffLine = <VexFlowStaffLine> staffLine;
+          for (let slurIndex: number = 0; slurIndex < vfOpenSlurs.length; slurIndex++) {
+              const oldVFSlur: VexFlowSlur = vfOpenSlurs[slurIndex];
+              const newVFSlur: VexFlowSlur = VexFlowSlur.createFromVexflowSlur(oldVFSlur);
+              newVFSlur.vfStartNote = undefined;
+              vfStaffLine.addVFSlurToVFStaffline(newVFSlur); // every VFSlur is added to the array in the VFStaffline!
+              vfOpenSlurs[slurIndex] = newVFSlur;
+          }
+          */
+
+          // add reference of slur array to the VexFlowStaffline class
+          for (const graphicalMeasure of staffLine.Measures) {
+            for (const graphicalStaffEntry of graphicalMeasure.staffEntries) {
+              // for (var idx5: number = 0, len5 = graphicalStaffEntry.GraceStaffEntriesBefore.Count; idx5 < len5; ++idx5) {
+              //     var graceStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.GraceStaffEntriesBefore[idx5];
+              //     if (graceStaffEntry.Notes[0][0].SourceNote.NoteSlurs.Count > 0) {
+              //         var graceNote: Note = graceStaffEntry.Notes[0][0].SourceNote;
+              //         graceStaffEntry.RelInMeasureTimestamp = Fraction.createFromFraction(graphicalStaffEntry.RelInMeasureTimestamp);
+              //         for (var idx6: number = 0, len6 = graceNote.NoteSlurs.Count; idx6 < len6; ++idx6) {
+              //             var graceNoteSlur: Slur = graceNote.NoteSlurs[idx6];
+              //             if (graceNoteSlur.StartNote == graceNote) {
+              //                 var vfSlur: VexFlowSlur = new VexFlowSlur(graceNoteSlur);
+              //                 vfSlur.GraceStart = true;
+              //                 staffLine.GraphicalSlurs.Add(vfSlur);
+              //                 openGraphicalSlurs[i].Add(vfSlur);
+              //                 for (var j: number = graphicalStaffEntry.GraceStaffEntriesBefore.IndexOf(graceStaffEntry);
+              //                     j < graphicalStaffEntry.GraceStaffEntriesBefore.Count; j++)
+              //                        vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesBefore[j]);
+              //             }
+              //             if (graceNote == graceNoteSlur.EndNote) {
+              //                 var vfSlur: VexFlowSlur = findGraphicalSlurFromSlur(openGraphicalSlurs[i], graceNoteSlur);
+              //                 if (vfSlur != null) {
+              //                     vfSlur.GraceEnd = true;
+              //                     openGraphicalSlurs[i].Remove(vfSlur);
+              //                     for (var j: number = 0; j <= graphicalStaffEntry.GraceStaffEntriesBefore.IndexOf(graceStaffEntry); j++)
+              //                         vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesBefore[j]);
+              //                 }
+              //             }
+              //         }
+              //     }
+              // }
+              // loop over "normal" notes (= no gracenotes)
+              for (const graphicalVoiceEntry of graphicalStaffEntry.graphicalVoiceEntries) {
+                for (const graphicalNote of graphicalVoiceEntry.notes) {
+                  for (const slur of graphicalNote.sourceNote.NoteSlurs) {
+                    // extra check for some MusicSheets that have openSlurs (because only the first Page is available -> Recordare files)
+                    if (slur.EndNote === undefined || slur.StartNote === undefined) {
+                      continue;
+                    }
+                    // add new VexFlowSlur to List
+                    if (slur.StartNote === graphicalNote.sourceNote) {
+                      if (graphicalNote.sourceNote.NoteTie !== undefined) {
+                        if (graphicalNote.parentVoiceEntry.parentStaffEntry.getAbsoluteTimestamp() !==
+                          graphicalNote.sourceNote.NoteTie.StartNote.getAbsoluteTimestamp()) {
+                          break;
                         }
-                        // for (var idx5: number = 0, len5 = graphicalStaffEntry.GraceStaffEntriesAfter.Count; idx5 < len5; ++idx5) {
-                        //     var graceStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.GraceStaffEntriesAfter[idx5];
-                        //     if (graceStaffEntry.Notes[0][0].SourceNote.NoteSlurs.Count > 0) {
-                        //         var graceNote: Note = graceStaffEntry.Notes[0][0].SourceNote;
-                        //         graceStaffEntry.RelInMeasureTimestamp = Fraction.createFromFraction(graphicalStaffEntry.RelInMeasureTimestamp);
-                        //         for (var idx6: number = 0, len6 = graceNote.NoteSlurs.Count; idx6 < len6; ++idx6) {
-                        //             var graceNoteSlur: Slur = graceNote.NoteSlurs[idx6];
-                        //             if (graceNoteSlur.StartNote == graceNote) {
-                        //                 var vfSlur: VexFlowSlur = new VexFlowSlur(graceNoteSlur);
-                        //                 vfSlur.GraceStart = true;
-                        //                 staffLine.GraphicalSlurs.Add(vfSlur);
-                        //                 openGraphicalSlurs[i].Add(vfSlur);
-                        //                 for (var j: number = graphicalStaffEntry.GraceStaffEntriesAfter.IndexOf(graceStaffEntry);
-                        //                      j < graphicalStaffEntry.GraceStaffEntriesAfter.Count; j++)
-                        //                        vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesAfter[j]);
-                        //             }
-                        //             if (graceNote == graceNoteSlur.EndNote) {
-                        //                 var vfSlur: VexFlowSlur = findGraphicalSlurFromSlur(openGraphicalSlurs[i], graceNoteSlur);
-                        //                 if (vfSlur != null) {
-                        //                     vfSlur.GraceEnd = true;
-                        //                     openGraphicalSlurs[i].Remove(vfSlur);
-                        //                     vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry);
-                        //                     for (var j: number = 0; j <= graphicalStaffEntry.GraceStaffEntriesAfter.IndexOf(graceStaffEntry); j++)
-                        //                         vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesAfter[j]);
-                        //                 }
-                        //             }
-                        //         }
-                        //     }
-                        // }
-
-                        //add the present Staffentry to all open slurs that don't contain this Staffentry already
-                        for (const gSlur of openGraphicalSlurs) {
-                          if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
-                            gSlur.staffEntries.push(graphicalStaffEntry);
-                          }
+                      }
+
+                      // Add a Graphical Slur to the staffline, if the recent note is the Startnote of a slur
+                      const gSlur: GraphicalSlur = new GraphicalSlur(slur);
+                      openGraphicalSlurs.push(gSlur);
+                      staffLine.addSlurToStaffline(gSlur);
+
+                      /* VexFlow Version - for later use
+                      const vfSlur: VexFlowSlur = new VexFlowSlur(slur);
+                      vfOpenSlurs.push(vfSlur); //add open... adding / removing is JUST DONE in the open... array
+                      vfSlur.vfStartNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
+                      vfStaffLine.addVFSlurToVFStaffline(vfSlur); // every VFSlur is added to the array in the VFStaffline!
+                      */
+                    }
+                    if (slur.EndNote === graphicalNote.sourceNote) {
+                      // Remove the Graphical Slur from the staffline if the note is the Endnote of a slur
+                      const index: number = this.findIndexGraphicalSlurFromSlur(openGraphicalSlurs, slur);
+                      if (index >= 0) {
+                        // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
+                        const gSlur: GraphicalSlur = openGraphicalSlurs[index];
+                        if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
+                          gSlur.staffEntries.push(graphicalStaffEntry);
                         }
-                    } // loop over StaffEntries
-                } // loop over Measures
-            } // loop over StaffLines
 
-                // Attach vfSlur array to the vfStaffline to be drawn
-                //vfStaffLine.SlursInVFStaffLine = vfSlurs;
-        } // loop over MusicSystems
+                        openGraphicalSlurs.splice(index, 1);
+                      }
+
+                      /* VexFlow Version - for later use
+                      const vfIndex: number = this.findIndexVFSlurFromSlur(vfOpenSlurs, slur);
+                      if (vfIndex !== undefined) {
+                          // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
+                          const vfSlur: VexFlowSlur = vfOpenSlurs[vfIndex];
+                          vfSlur.vfEndNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
+                          vfSlur.createVexFlowCurve();
+                          vfOpenSlurs.splice(vfIndex, 1);
+                      }
+                      */
+                    }
+                  }
+                }
+              }
+              // for (var idx5: number = 0, len5 = graphicalStaffEntry.GraceStaffEntriesAfter.Count; idx5 < len5; ++idx5) {
+              //     var graceStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.GraceStaffEntriesAfter[idx5];
+              //     if (graceStaffEntry.Notes[0][0].SourceNote.NoteSlurs.Count > 0) {
+              //         var graceNote: Note = graceStaffEntry.Notes[0][0].SourceNote;
+              //         graceStaffEntry.RelInMeasureTimestamp = Fraction.createFromFraction(graphicalStaffEntry.RelInMeasureTimestamp);
+              //         for (var idx6: number = 0, len6 = graceNote.NoteSlurs.Count; idx6 < len6; ++idx6) {
+              //             var graceNoteSlur: Slur = graceNote.NoteSlurs[idx6];
+              //             if (graceNoteSlur.StartNote == graceNote) {
+              //                 var vfSlur: VexFlowSlur = new VexFlowSlur(graceNoteSlur);
+              //                 vfSlur.GraceStart = true;
+              //                 staffLine.GraphicalSlurs.Add(vfSlur);
+              //                 openGraphicalSlurs[i].Add(vfSlur);
+              //                 for (var j: number = graphicalStaffEntry.GraceStaffEntriesAfter.IndexOf(graceStaffEntry);
+              //                      j < graphicalStaffEntry.GraceStaffEntriesAfter.Count; j++)
+              //                        vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesAfter[j]);
+              //             }
+              //             if (graceNote == graceNoteSlur.EndNote) {
+              //                 var vfSlur: VexFlowSlur = findGraphicalSlurFromSlur(openGraphicalSlurs[i], graceNoteSlur);
+              //                 if (vfSlur != null) {
+              //                     vfSlur.GraceEnd = true;
+              //                     openGraphicalSlurs[i].Remove(vfSlur);
+              //                     vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry);
+              //                     for (var j: number = 0; j <= graphicalStaffEntry.GraceStaffEntriesAfter.IndexOf(graceStaffEntry); j++)
+              //                         vfSlur.StaffEntries.Add(<PsStaffEntry>graphicalStaffEntry.GraceStaffEntriesAfter[j]);
+              //                 }
+              //             }
+              //         }
+              //     }
+              // }
+
+              //add the present Staffentry to all open slurs that don't contain this Staffentry already
+              for (const gSlur of openGraphicalSlurs) {
+                if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
+                  gSlur.staffEntries.push(graphicalStaffEntry);
+                }
+              }
+            } // loop over StaffEntries
+          } // loop over Measures
+        } // loop over StaffLines
+
+        // Attach vfSlur array to the vfStaffline to be drawn
+        //vfStaffLine.SlursInVFStaffLine = vfSlurs;
+      } // loop over MusicSystems
     } // loop over MusicPages
 
     // order slurs that were saved to the Staffline
     for (const graphicalMusicPage of this.graphicalMusicSheet.MusicPages) {
         for (const musicSystem of graphicalMusicPage.MusicSystems) {
-            for (const staffLine of musicSystem.StaffLines) {
-                // Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
-                const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
-                for (const gSlur of sortedGSlurs) {
-                    // crossed slurs will be handled later:
-                    if (gSlur.slur.isCrossed()) {
-                        continue;
-                    }
-                    gSlur.calculateCurve(this.rules);
+          for (const staffLine of musicSystem.StaffLines) {
+            // Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
+            const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
+            for (const gSlur of sortedGSlurs) {
+                // crossed slurs will be handled later:
+                if (gSlur.slur.isCrossed()) {
+                    continue;
                 }
+                gSlur.calculateCurve(this.rules);
             }
+          }
         }
-    }
+      }
   }
 }

+ 59 - 43
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -1,29 +1,31 @@
 import Vex = require("vexflow");
-import {MusicSheetDrawer} from "../MusicSheetDrawer";
-import {RectangleF2D} from "../../../Common/DataObjects/RectangleF2D";
-import {VexFlowMeasure} from "./VexFlowMeasure";
-import {PointF2D} from "../../../Common/DataObjects/PointF2D";
-import {GraphicalLabel} from "../GraphicalLabel";
-import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
-import {MusicSystem} from "../MusicSystem";
-import {GraphicalObject} from "../GraphicalObject";
-import {GraphicalLayers} from "../DrawingEnums";
-import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
-import {VexFlowBackend} from "./VexFlowBackend";
-import {VexFlowOctaveShift} from "./VexFlowOctaveShift";
-import {VexFlowInstantaneousDynamicExpression} from "./VexFlowInstantaneousDynamicExpression";
+import { MusicSheetDrawer } from "../MusicSheetDrawer";
+import { RectangleF2D } from "../../../Common/DataObjects/RectangleF2D";
+import { VexFlowMeasure } from "./VexFlowMeasure";
+import { PointF2D } from "../../../Common/DataObjects/PointF2D";
+import { GraphicalLabel } from "../GraphicalLabel";
+import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer";
+import { MusicSystem } from "../MusicSystem";
+import { GraphicalObject } from "../GraphicalObject";
+import { GraphicalLayers } from "../DrawingEnums";
+import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
+import { VexFlowBackend } from "./VexFlowBackend";
+import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
+import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
 import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
 import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
 import { VexFlowStaffLine } from "./VexFlowStaffLine";
-import {StaffLine} from "../StaffLine";
-import {EngravingRules} from "../EngravingRules";
+import { StaffLine } from "../StaffLine";
+import { EngravingRules } from "../EngravingRules";
 import { GraphicalSlur } from "../GraphicalSlur";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
-import {GraphicalInstantaneousTempoExpression} from "../GraphicalInstantaneousTempoExpression";
-import {GraphicalInstantaneousDynamicExpression} from "../GraphicalInstantaneousDynamicExpression";
+import { GraphicalInstantaneousTempoExpression } from "../GraphicalInstantaneousTempoExpression";
+import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneousDynamicExpression";
 import log = require("loglevel");
-import {DrawingParameters} from "../DrawingParameters";
+import { GraphicalContinuousDynamicExpression } from "../GraphicalContinuousDynamicExpression";
+import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
+import { DrawingParameters } from "../DrawingParameters";
 
 /**
  * This is a global constant which denotes the height in pixels of the space between two lines of the stave
@@ -80,7 +82,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
     protected drawStaffLine(staffLine: StaffLine): void {
         super.drawStaffLine(staffLine);
-        const  absolutePos: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
+        const absolutePos: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
         this.drawSlurs(staffLine as VexFlowStaffLine, absolutePos);
     }
 
@@ -139,8 +141,8 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         // Draw the StaffEntries
         for (const staffEntry of measure.staffEntries) {
             this.drawStaffEntry(staffEntry);
-                    }
-                }
+        }
+    }
 
     // private drawPixel(coord: PointF2D): void {
     //     coord = this.applyScreenTransformation(coord);
@@ -155,20 +157,20 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         start = this.applyScreenTransformation(start);
         stop = this.applyScreenTransformation(stop);
         this.backend.renderLine(start, stop, color, lineWidth * unitInPixels);
-            }
+    }
 
     protected drawSkyLine(staffline: StaffLine): void {
         const startPosition: PointF2D = staffline.PositionAndShape.AbsolutePosition;
         const width: number = staffline.PositionAndShape.Size.width;
         this.drawSampledLine(staffline.SkyLine, startPosition, width);
-        }
+    }
 
     protected drawBottomLine(staffline: StaffLine): void {
         const startPosition: PointF2D = new PointF2D(staffline.PositionAndShape.AbsolutePosition.x,
                                                      staffline.PositionAndShape.AbsolutePosition.y);
         const width: number = staffline.PositionAndShape.Size.width;
         this.drawSampledLine(staffline.BottomLine, startPosition, width, "#0000FFFF");
-        }
+    }
 
     /**
      * Draw a line with a width and start point in a chosen color (used for skyline/bottom line debugging) from
@@ -186,7 +188,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             if (line[i] !== currentValue) {
                 indices.push(i);
                 currentValue = line[i];
-    }
+            }
         }
 
         const absolute: PointF2D = startPosition;
@@ -283,25 +285,25 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             // Draw InstantaniousDynamics
             if (abstractGraphicalExpression instanceof GraphicalInstantaneousDynamicExpression) {
                 this.drawInstantaneousDynamic((abstractGraphicalExpression as VexFlowInstantaneousDynamicExpression));
-            // Draw InstantaniousTempo
+                // Draw InstantaniousTempo
             } else if (abstractGraphicalExpression instanceof GraphicalInstantaneousTempoExpression) {
                 this.drawLabel((abstractGraphicalExpression as GraphicalInstantaneousTempoExpression).GraphicalLabel, GraphicalLayers.Notes);
-            // // Draw ContinuousDynamics
-            // } else if (abstractGraphicalExpression instanceof GraphicalContinuousDynamicExpression) {
-            // //     drawContinuousDynamic((GraphicalContinuousDynamicExpression)abstractGraphicalExpression, absolutePos);
-            // // Draw ContinuousTempo
-            // } else if (abstractGraphicalExpression instanceof GraphicalContinuousTempoExpression) {
-            //     this.drawLabel((abstractGraphicalExpression as GraphicalContinuousTempoExpression).GraphicalLabel, GraphicalLayers.Notes);
-            // // Draw Mood
-            // } else if (abstractGraphicalExpression instanceof GraphicalMoodExpression) {
-            //     GraphicalMoodExpression; graphicalMood = (GraphicalMoodExpression); abstractGraphicalExpression;
-            //     drawLabel(graphicalMood.GetGraphicalLabel, (int)GraphicalLayers.Notes);
-            // // Draw Unknown
-            // } else if (abstractGraphicalExpression instanceof GraphicalUnknownExpression) {
-            //     GraphicalUnknownExpression; graphicalUnknown =
-            //         (GraphicalUnknownExpression); abstractGraphicalExpression;
-            //     drawLabel(graphicalUnknown.GetGraphicalLabel, (int)GraphicalLayers.Notes);
-            // }
+                // Draw ContinuousDynamics
+            } else if (abstractGraphicalExpression instanceof GraphicalContinuousDynamicExpression) {
+                this.drawContinuousDynamic((abstractGraphicalExpression as VexFlowContinuousDynamicExpression));
+                // Draw ContinuousTempo
+                // } else if (abstractGraphicalExpression instanceof GraphicalContinuousTempoExpression) {
+                //     this.drawLabel((abstractGraphicalExpression as GraphicalContinuousTempoExpression).GraphicalLabel, GraphicalLayers.Notes);
+                // // Draw Mood
+                // } else if (abstractGraphicalExpression instanceof GraphicalMoodExpression) {
+                //     GraphicalMoodExpression; graphicalMood = (GraphicalMoodExpression); abstractGraphicalExpression;
+                //     drawLabel(graphicalMood.GetGraphicalLabel, (int)GraphicalLayers.Notes);
+                // // Draw Unknown
+                // } else if (abstractGraphicalExpression instanceof GraphicalUnknownExpression) {
+                //     GraphicalUnknownExpression; graphicalUnknown =
+                //         (GraphicalUnknownExpression); abstractGraphicalExpression;
+                //     drawLabel(graphicalUnknown.GetGraphicalLabel, (int)GraphicalLayers.Notes);
+                // }
             } else {
                 log.warn("Unkown type of expression!");
             }
@@ -312,6 +314,20 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         this.drawLabel((instantaneousDynamic as VexFlowInstantaneousDynamicExpression).Label, <number>GraphicalLayers.Notes);
     }
 
+    protected drawContinuousDynamic(graphicalExpression: VexFlowContinuousDynamicExpression): void {
+        if (graphicalExpression.IsVerbal) {
+            this.drawLabel(graphicalExpression.Label, <number>GraphicalLayers.Notes);
+        } else {
+            for (const line of graphicalExpression.Lines) {
+                const start: PointF2D = new PointF2D(graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.x + line.Start.x,
+                                                     graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.y + line.Start.y);
+                const end: PointF2D = new PointF2D(graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.x + line.End.x,
+                                                   graphicalExpression.ParentStaffLine.PositionAndShape.AbsolutePosition.y + line.End.y);
+                this.drawLine(start, end, "black", line.Width);
+            }
+        }
+    }
+
     /**
      * Renders a Label to the screen (e.g. Title, composer..)
      * @param graphicalLabel holds the label string, the text height in units and the font parameters
@@ -337,7 +353,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
      * @param alpha alpha value between 0 and 1
      */
     protected renderRectangle(rectangle: RectangleF2D, layer: number, styleId: number, alpha: number): void {
-       this.backend.renderRectangle(rectangle, styleId, alpha);
+        this.backend.renderRectangle(rectangle, styleId, alpha);
     }
 
     /**

+ 8 - 5
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -300,11 +300,14 @@ export class ExpressionReader {
             if (dynamicsNode.hasAttributes && dynamicsNode.attribute("default-x") !== undefined) {
                 this.directionTimestamp = Fraction.createFromFraction(inSourceMeasureCurrentFraction);
             }
-            const name: string = dynamicsNode.elements()[0].name;
-            if (name !== undefined) {
+            let expressionText: string = dynamicsNode.elements()[0].name;
+            if (expressionText === "other-dynamics") {
+                expressionText = dynamicsNode.elements()[0].value;
+            }
+            if (expressionText !== undefined) {
                 let dynamicEnum: DynamicEnum;
                 try {
-                    dynamicEnum = DynamicEnum[name];
+                    dynamicEnum = DynamicEnum[expressionText];
                 } catch (err) {
                     const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DynamicError", "Error while reading dynamic.");
                     this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
@@ -320,7 +323,7 @@ export class ExpressionReader {
                         this.openContinuousDynamicExpression.StartMultiExpression !== this.getMultiExpression) {
                         this.closeOpenContinuousDynamic();
                     }
-                    const instantaneousDynamicExpression: InstantaneousDynamicExpression = new InstantaneousDynamicExpression(name,
+                    const instantaneousDynamicExpression: InstantaneousDynamicExpression = new InstantaneousDynamicExpression(expressionText,
                                                                                                                               this.soundDynamic,
                                                                                                                               this.placement,
                                                                                                                               this.staffNumber);
@@ -328,7 +331,7 @@ export class ExpressionReader {
                     this.initialize();
                     if (this.activeInstantaneousDynamic !== undefined) {
                         this.activeInstantaneousDynamic.DynEnum = instantaneousDynamicExpression.DynEnum;
-                    } else { this.activeInstantaneousDynamic = new InstantaneousDynamicExpression(name, 0, PlacementEnum.NotYetDefined, 1); }
+                    } else { this.activeInstantaneousDynamic = new InstantaneousDynamicExpression(expressionText, 0, PlacementEnum.NotYetDefined, 1); }
                 }
             }
         }

+ 9 - 3
src/MusicalScore/VoiceData/Expressions/AbstractExpression.ts

@@ -1,7 +1,10 @@
 export class AbstractExpression {
-    //constructor() {
-    //
-    //}
+    protected placement: PlacementEnum;
+
+    constructor(placement: PlacementEnum) {
+        this.placement = placement;
+    }
+
     protected static isStringInStringList(stringList: Array<string>, inputString: string): boolean {
         for (let idx: number = 0, len: number = stringList.length; idx < len; ++idx) {
             const s: string = stringList[idx];
@@ -12,6 +15,9 @@ export class AbstractExpression {
         return false;
     }
 
+    /** Placement of the expression */
+    public get Placement(): PlacementEnum { return this.placement; }
+
     public static PlacementEnumFromString(placementString: string): PlacementEnum {
         switch (placementString.toLowerCase()) {
             case "above":

+ 3 - 4
src/MusicalScore/VoiceData/Expressions/AbstractTempoExpression.ts

@@ -1,17 +1,16 @@
-import {PlacementEnum} from "./AbstractExpression";
+import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
 import {MultiTempoExpression} from "./MultiTempoExpression";
 
-export abstract class AbstractTempoExpression {
+export abstract class AbstractTempoExpression extends AbstractExpression {
 
     constructor(label: string, placement: PlacementEnum, staffNumber: number, parentMultiTempoExpression: MultiTempoExpression) {
+        super(placement);
         this.label = label;
-        this.placement = placement;
         this.staffNumber = staffNumber;
         this.parentMultiTempoExpression = parentMultiTempoExpression;
     }
 
     protected label: string;
-    protected placement: PlacementEnum;
     protected staffNumber: number;
     protected parentMultiTempoExpression: MultiTempoExpression;
 

+ 1 - 3
src/MusicalScore/VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression.ts

@@ -4,10 +4,9 @@ import {Fraction} from "../../../../Common/DataObjects/Fraction";
 
 export class ContinuousDynamicExpression extends AbstractExpression {
     constructor(dynamicType: ContDynamicEnum, placement: PlacementEnum, staffNumber: number, label: string = "") {
-        super();
+        super(placement);
         this.dynamicType = dynamicType;
         this.label = label;
-        this.placement = placement;
         this.staffNumber = staffNumber;
         this.startVolume = -1;
         this.endVolume = -1;
@@ -24,7 +23,6 @@ export class ContinuousDynamicExpression extends AbstractExpression {
     private endMultiExpression: MultiExpression;
     private startVolume: number;
     private endVolume: number;
-    private placement: PlacementEnum;
     private staffNumber: number;
     private label: string;
 

+ 13 - 13
src/MusicalScore/VoiceData/Expressions/InstantaneousDynamicExpression.ts

@@ -7,10 +7,9 @@ import * as log from "loglevel";
 
 export class InstantaneousDynamicExpression extends AbstractExpression {
     constructor(dynamicExpression: string, soundDynamics: number, placement: PlacementEnum, staffNumber: number) {
-        super();
+        super(placement);
         this.dynamicEnum = DynamicEnum[dynamicExpression.toLowerCase()];
         this.soundDynamic = soundDynamics;
-        this.placement = placement;
         this.staffNumber = staffNumber;
     }
     public static dynamicToRelativeVolumeDict: { [_: string]: number; } = {
@@ -33,6 +32,7 @@ export class InstantaneousDynamicExpression extends AbstractExpression {
         "rf": 0.5,
         "rfz": 0.5,
         "sf": 0.5,
+        "sff": 0.5,
         "sffz": 0.5,
         "sfp": 0.5,
         "sfpp": 0.5,
@@ -43,13 +43,12 @@ export class InstantaneousDynamicExpression extends AbstractExpression {
     private static listInstantaneousDynamics: string[] =  [
         "pppppp", "ppppp", "pppp", "ppp", "pp", "p",
         "ffffff", "fffff", "ffff", "fff", "ff", "f",
-        "mf", "mp", "sf", "sp", "spp", "fp", "rf", "rfz", "sfz", "sffz", "fz",
+        "mf", "mp", "sf", "sff", "sp", "spp", "fp", "rf", "rfz", "sfz", "sffz", "fz",
     ];
 
     private multiExpression: MultiExpression;
     private dynamicEnum: DynamicEnum;
     private soundDynamic: number;
-    private placement: PlacementEnum;
     private staffNumber: number;
     private length: number;
 
@@ -165,13 +164,14 @@ export enum DynamicEnum {
     fffff = 12,
     ffffff = 13,
     sf = 14,
-    sfp = 15,
-    sfpp = 16,
-    fp = 17,
-    rf = 18,
-    rfz = 19,
-    sfz = 20,
-    sffz = 21,
-    fz = 22,
-    other = 23
+    sff = 15,
+    sfp = 16,
+    sfpp = 17,
+    fp = 18,
+    rf = 19,
+    rfz = 20,
+    sfz = 21,
+    sffz = 22,
+    fz = 23,
+    other = 24
 }

+ 1 - 3
src/MusicalScore/VoiceData/Expressions/MoodExpression.ts

@@ -2,9 +2,8 @@ import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
 
 export class MoodExpression extends AbstractExpression {
     constructor(label: string, placement: PlacementEnum, staffNumber: number) {
-        super();
+        super(placement);
         this.label = label;
-        this.placement = placement;
         this.staffNumber = staffNumber;
         this.setMoodType();
     }
@@ -47,7 +46,6 @@ export class MoodExpression extends AbstractExpression {
     private moodType: MoodEnum;
     private label: string;
     private staffNumber: number;
-    private placement: PlacementEnum;
 
     public static isInputStringMood(inputString: string): boolean {
         if (inputString === undefined) {

+ 2 - 4
src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts

@@ -2,10 +2,9 @@ import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
 import {TextAlignmentEnum} from "../../../Common/Enums/TextAlignment";
 
 export class UnknownExpression extends AbstractExpression {
-    constructor(label: string, placementEnum: PlacementEnum, textAlignment: TextAlignmentEnum, staffNumber: number) {
-        super();
+    constructor(label: string, placement: PlacementEnum, textAlignment: TextAlignmentEnum, staffNumber: number) {
+        super(placement);
         this.label = label;
-        this.placement = placementEnum;
         this.staffNumber = staffNumber;
         if (textAlignment === undefined) {
             textAlignment = TextAlignmentEnum.LeftBottom;
@@ -13,7 +12,6 @@ export class UnknownExpression extends AbstractExpression {
         this.textAlignment = textAlignment;
     }
     private label: string;
-    private placement: PlacementEnum;
     private textAlignment: TextAlignmentEnum;
     private staffNumber: number;
 

+ 10 - 0
src/Util/CollectionUtil.ts

@@ -2,7 +2,11 @@ import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 declare global {
     interface Array<T> {
+        /** Returns the last element from an array */
         last(): T;
+        /** Deletes all elements from an array */
+        clear(): void;
+        /** Returns true if the element is found in the array */
         contains(elem: T): boolean;
     }
 }
@@ -13,6 +17,12 @@ if (!Array.prototype.last) {
     };
 }
 
+if (!Array.prototype.clear) {
+    Array.prototype.clear = function<T>(): void {
+        this.length = 0;
+    };
+}
+
 if (!Array.prototype.contains) {
     Array.prototype.contains = function<T>(elem: T): boolean {
         return this.indexOf(elem) !== -1;

+ 40 - 12
test/data/OSMD_function_test_expressions.musicxml

@@ -7,7 +7,7 @@
   <identification>
     <encoding>
       <software>MuseScore 2.3.2</software>
-      <encoding-date>2018-08-22</encoding-date>
+      <encoding-date>2018-09-27</encoding-date>
       <supports element="accidental" type="yes"/>
       <supports element="beam" type="yes"/>
       <supports element="print" attribute="new-page" type="yes" value="yes"/>
@@ -63,7 +63,7 @@
       <print>
         <system-layout>
           <system-margins>
-            <left-margin>-0.00</left-margin>
+            <left-margin>0.00</left-margin>
             <right-margin>0.00</right-margin>
             </system-margins>
           <top-system-distance>170.00</top-system-distance>
@@ -93,7 +93,12 @@
         </direction>
       <direction placement="above">
         <direction-type>
-          <words default-y="40.00" relative-x="-12.75" relative-y="6.30" font-weight="bold">Adagio</words>
+          <words default-y="40.00" relative-x="-59.61" relative-y="12.55" font-weight="bold">Adagio</words>
+          </direction-type>
+        </direction>
+      <direction placement="above">
+        <direction-type>
+          <words default-y="40.00">Notenzeilentext</words>
           </direction-type>
         </direction>
       <direction placement="above">
@@ -216,7 +221,7 @@
     <measure number="3" width="199.74">
       <direction placement="above">
         <direction-type>
-          <dynamics default-x="6.58" default-y="-80.00" relative-x="-6.11" relative-y="105.93">
+          <dynamics default-x="6.58" default-y="-80.00" relative-y="96.00">
             <mf/>
             </dynamics>
           </direction-type>
@@ -276,7 +281,7 @@
     <measure number="4" width="199.74">
       <direction placement="above">
         <direction-type>
-          <dynamics default-x="3.29" default-y="-80.00" relative-x="1.02" relative-y="102.87">
+          <dynamics default-x="3.29" default-y="-80.00" relative-y="96.00">
             <fz/>
             </dynamics>
           </direction-type>
@@ -340,7 +345,7 @@
         </direction>
       <direction placement="below">
         <direction-type>
-          <wedge type="crescendo" number="1" default-y="-65.00"/>
+          <wedge type="crescendo" number="1" default-y="-93.24"/>
           </direction-type>
         </direction>
       <note default-x="12.00" default-y="-50.00">
@@ -393,13 +398,13 @@
       <print new-system="yes">
         <system-layout>
           <system-margins>
-            <left-margin>-0.00</left-margin>
-            <right-margin>0.00</right-margin>
+            <left-margin>0.00</left-margin>
+            <right-margin>-0.00</right-margin>
             </system-margins>
           <system-distance>150.00</system-distance>
           </system-layout>
         </print>
-      <note default-x="49.08" default-y="-50.00">
+      <note default-x="49.07" default-y="-50.00">
         <pitch>
           <step>C</step>
           <octave>6</octave>
@@ -429,6 +434,19 @@
         <type>quarter</type>
         <stem>up</stem>
         </note>
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="3.29" default-y="-80.00" relative-x="1.57" relative-y="-18.82">
+            <p/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="54.44"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-93.82" relative-x="25.10"/>
+          </direction-type>
+        </direction>
       <note default-x="205.86" default-y="-45.00">
         <pitch>
           <step>D</step>
@@ -514,7 +532,12 @@
         </note>
       <direction placement="below">
         <direction-type>
-          <dynamics default-x="6.58" default-y="-80.00" relative-y="6.00">
+          <wedge type="stop" number="1" relative-x="-9.70"/>
+          </direction-type>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.58" default-y="-80.00" relative-x="3.14" relative-y="-20.67">
             <ff/>
             </dynamics>
           </direction-type>
@@ -556,7 +579,7 @@
     <measure number="8" width="171.37">
       <direction placement="above">
         <direction-type>
-          <dynamics default-x="6.58" default-y="-80.00" relative-x="-2.04" relative-y="105.93">
+          <dynamics default-x="6.58" default-y="-80.00" relative-y="96.00">
             <sfz/>
             </dynamics>
           </direction-type>
@@ -574,7 +597,7 @@
     <measure number="9" width="171.37">
       <direction placement="above">
         <direction-type>
-          <words default-y="40.00" relative-x="2.46" relative-y="-16.02" font-weight="bold">Presto</words>
+          <words default-y="40.00" font-weight="bold">Presto</words>
           </direction-type>
         </direction>
       <note default-x="12.00" default-y="-15.00">
@@ -591,6 +614,11 @@
       <barline location="left">
         <ending number="1" type="start" default-y="30.00"/>
         </barline>
+      <direction placement="below">
+        <direction-type>
+          <words default-y="40.00" relative-x="1.57" relative-y="-111.38">crescendo</words>
+          </direction-type>
+        </direction>
       <note default-x="12.00" default-y="-30.00">
         <pitch>
           <step>G</step>

+ 310 - 0
test/data/OSMD_function_test_expressions_overlap.musicxml

@@ -0,0 +1,310 @@
+<?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">
+  <identification>
+    <encoding>
+      <software>MuseScore 2.3.2</software>
+      <encoding-date>2018-09-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.05556</millimeters>
+      <tenths>40</tenths>
+      </scaling>
+    <page-layout>
+      <page-height>1683.36</page-height>
+      <page-width>1190.88</page-width>
+      <page-margins type="even">
+        <left-margin>56.6929</left-margin>
+        <right-margin>56.6929</right-margin>
+        <top-margin>56.6929</top-margin>
+        <bottom-margin>113.386</bottom-margin>
+        </page-margins>
+      <page-margins type="odd">
+        <left-margin>56.6929</left-margin>
+        <right-margin>56.6929</right-margin>
+        <top-margin>56.6929</top-margin>
+        <bottom-margin>113.386</bottom-margin>
+        </page-margins>
+      </page-layout>
+    <word-font font-family="FreeSerif" font-size="10"/>
+    <lyric-font font-family="FreeSerif" font-size="11"/>
+    </defaults>
+  <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="614.02">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>70.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>2</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>
+          <dynamics default-x="6.58" default-y="-80.00">
+            <ppp/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="17.78"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-75.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="75.17" default-y="-40.00">
+        <pitch>
+          <step>E</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="75.17" default-y="-25.00">
+        <chord/>
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        </note>
+      <note default-x="142.33" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.58" default-y="-80.00" relative-x="-1.37" relative-y="-0.38">
+            <ff/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="124.44"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-75.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="209.49" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="276.64" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="diminuendo" number="1" default-y="-75.00"/>
+          </direction-type>
+        </direction>
+      <note default-x="343.80" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        </note>
+      <note default-x="410.95" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1"/>
+          </direction-type>
+        </direction>
+      <note default-x="478.11" default-y="-40.00">
+        <pitch>
+          <step>E</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="478.11" default-y="5.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="545.27" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        </note>
+      <note default-x="533.40" default-y="0.00">
+        <chord/>
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="545.27" default-y="5.00">
+        <chord/>
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>1</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      </measure>
+    <measure number="2" width="463.47">
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="6.58" default-y="-80.00" relative-x="-3.14" relative-y="-1.57">
+            <pp/>
+            </dynamics>
+          </direction-type>
+        <sound dynamics="36.67"/>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="crescendo" number="1" default-y="-78.14" relative-x="5.81"/>
+          </direction-type>
+        </direction>
+      <note default-x="12.00" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>up</stem>
+        </note>
+      <direction placement="below">
+        <direction-type>
+          <wedge type="stop" number="1" relative-x="1.57"/>
+          </direction-type>
+        </direction>
+      <direction placement="below">
+        <direction-type>
+          <dynamics default-x="3.29" default-y="-80.00" relative-x="-3.14" relative-y="-1.57">
+            <other-dynamics>sff</other-dynamics>
+            </dynamics>
+          </direction-type>
+        </direction>
+      <note default-x="232.26" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>4</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        </note>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
+      </measure>
+    </part>
+  </score-partwise>