Преглед изворни кода

Merge Feature/Slurs (#359)

Squashed commits:

* Added slur reader

* Coded first version of calculateSlurs() in VexFlowMusicSheetCalculator. Added class VexFlowSlur in the equally named ts-file.
TODO: write correct call procedure for calculateSlurs(). Formally it was in MusicSheetCalculator but now it immediately generates VexFlow slurs and saves them to the VexFlow staffline.

* Added class definition of Vex.Flow.Curve in vexflow.d.ts
Added createVexFlowCurve(...) in VexFlowSlur.ts
Construct curve in check for Endnote of slur in calculateSlurs() in VexFlowMusicSheetCalculator.ts
Added drawSlurs(...:VexFlowStaffline) in VexFlowMusicSheetDrawer.ts
Closed Slurs work fine. Open slurs still throw an error (like in "An die ferne Geliebte" - Beethoven)

* Problem: in vexflow.d.ts in the definition of the class Stave (derived from Modifier), not only the interface of the function "public static get Position()" is given but also the return values defined.
Apparently in vexflow.d.ts only definitions without bodies are allowed.
I commented the function in vexflow.d.ts and added in VexFlowMeasure.ts the Enum StavePositionEnum that now handles the numbers and keywords. This makes sense, as the function was only used in this file.

* Corrected the position for removing VexFlowSlur from array vfSlurs

* First Version working by design:
store all slurs to be drawed to the VexFlowStaffline.
use vfOpenSlurs to store slurs that start/end in different stafflines
Moved createVexFlowCurve() to drawSlurs in VexFlowMusicSheetDrawer.ts in oder not to draw curves with undefined endpoints and run into an error.

* added translated ts-file GraphicalCurve.ts for generating points of a curve

* work in progress: files Matrix2D (rotation matrix), Graphical Slur and Sorter for Graphical Slur, translated from c# and syntax corrected (except Graphical Slur)

* Reworked GraphicalSlur.ts:
Old positioning routines have been removed or commented as they will be replaced with calculations using the bounding box and the skyline. Therefore "PositionAndShape" has been used for the moment for calculation (the "else" case of the old if statements)

* Added Comments from .cs files and structured code by newlines. In GraphicalSlur.ts there is also commented old redundant code.

* Implemented CalculatingSlurs function in VexFlowMusicSheetCalculator.ts using GraphicalSlurs. Code runs and seems correct in Debugger. At the moment no rendering of the Slurs is available, so they do not appear on the sheet in the browser.

* Added method for rendering bezier curve on context.
Fixed methods with ref parameters.
Fixed typo.
Added comments.

* Removed unecessary <number> casts.

* Slurs are drawn in a first version. But: Grace notes are wrong again. Curves are not correct.

* fixed curve rendering for slurs.

* Excluded drawing of crossed slurs (from one staffline to another) as the calculation is still missing for them.
Fixed placement issues for two voices.

* Fixed import bug
sschmidTU пре 6 година
родитељ
комит
1123251d5a
26 измењених фајлова са 1614 додато и 160 уклоњено
  1. 21 10
      external/vexflow/vexflow.d.ts
  2. 11 11
      src/Common/DataObjects/Fraction.ts
  3. 58 0
      src/Common/DataObjects/Matrix2D.ts
  4. 3 3
      src/Common/DataObjects/Pitch.ts
  5. 49 0
      src/MusicalScore/Graphical/GraphicalCurve.ts
  6. 2 2
      src/MusicalScore/Graphical/GraphicalMusicSheet.ts
  7. 1 1
      src/MusicalScore/Graphical/GraphicalNote.ts
  8. 805 0
      src/MusicalScore/Graphical/GraphicalSlur.ts
  9. 21 0
      src/MusicalScore/Graphical/GraphicalSlurSorter.ts
  10. 9 0
      src/MusicalScore/Graphical/GraphicalStaffEntry.ts
  11. 8 8
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  12. 17 0
      src/MusicalScore/Graphical/StaffLine.ts
  13. 26 0
      src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts
  14. 27 1
      src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts
  15. 2 0
      src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts
  16. 21 10
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  17. 283 62
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  18. 63 9
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  19. 75 0
      src/MusicalScore/Graphical/VexFlow/VexFlowSlur.ts
  20. 10 0
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffLine.ts
  21. 2 2
      src/MusicalScore/MusicSheet.ts
  22. 8 7
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  23. 2 2
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  24. 59 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/SlurReader.ts
  25. 30 31
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  26. 1 1
      src/Util/PSMath.ts

+ 21 - 10
external/vexflow/vexflow.d.ts

@@ -225,16 +225,16 @@ declare namespace Vex {
         }
 
         export class StaveModifier extends Modifier {
-            public static get Position() {
-                return {
-                    LEFT: 1,
-                    RIGHT: 2,
-                    ABOVE: 3,
-                    BELOW: 4,
-                    BEGIN: 5,
-                    END: 6,
-                };
-            }
+            // public static get Position() {
+            //     return {
+            //         LEFT: 1,
+            //         RIGHT: 2,
+            //         ABOVE: 3,
+            //         BELOW: 4,
+            //         BEGIN: 5,
+            //         END: 6,
+            //     };
+            // }
 
             public getPosition(): number;
 
@@ -321,6 +321,15 @@ declare namespace Vex {
             public draw(): void;
         }
 
+        // interface for class Curve to draw slurs. The options are set to undefined
+        export class Curve {
+            constructor(from: StemmableNote, to: StemmableNote, options: any);
+            
+            public setContext(ctx: RenderContext): Curve;
+
+            public draw(): void;
+        }
+
         export class RenderContext {
             public scale(x: number, y: number): RenderContext;
             public fillRect(x: number, y: number, width: number, height: number): RenderContext
@@ -329,8 +338,10 @@ declare namespace Vex {
             public beginPath(): RenderContext;
             public moveTo(x, y): RenderContext;
             public lineTo(x, y): RenderContext;
+            public bezierCurveTo(cp1_x: number, cp1_y: number, cp2_x: number, cp2_y: number, end_x: number, end_y: number): RenderContext;
             public closePath(): RenderContext;
             public stroke(): RenderContext;
+            public fill(): RenderContext;
             public save(): RenderContext;
             public restore(): RenderContext;
             public lineWidth: number;

+ 11 - 11
src/Common/DataObjects/Fraction.ts

@@ -261,7 +261,7 @@ export class Fraction {
   //        return true;
   //    if (ReferenceEquals(f, undefined))
   //        return false;
-  //    return <number>this.numerator * f.denominator === <number>f.numerator * this.denominator;
+  //    return this.numerator * f.denominator === f.numerator * this.denominator;
   //}
 
   private setRealValue(): void {
@@ -291,20 +291,20 @@ export class Fraction {
       }
     }
     if (this.denominator > Fraction.maximumAllowedNumber) {
-      const factor: number = <number>this.denominator / Fraction.maximumAllowedNumber;
-      this.numerator = <number>Math.round(this.numerator / factor);
-      this.denominator = <number>Math.round(this.denominator / factor);
+      const factor: number = this.denominator / Fraction.maximumAllowedNumber;
+      this.numerator = Math.round(this.numerator / factor);
+      this.denominator = Math.round(this.denominator / factor);
     }
     if (this.numerator > Fraction.maximumAllowedNumber) {
-      const factor: number = <number>this.numerator / Fraction.maximumAllowedNumber;
-      this.numerator = <number>Math.round(this.numerator / factor);
-      this.denominator = <number>Math.round(this.denominator / factor);
+      const factor: number = this.numerator / Fraction.maximumAllowedNumber;
+      this.numerator = Math.round(this.numerator / factor);
+      this.denominator = Math.round(this.denominator / factor);
     }
   }
 
 
   //private static equals(f1: Fraction, f2: Fraction): boolean {
-  //    return <number>f1.numerator * f2.denominator === <number>f2.numerator * f1.denominator;
+  //    return f1.numerator * f2.denominator === f2.numerator * f1.denominator;
   //}
   //
   //public static ApproximateFractionFromValue(value: number, epsilonForPrecision: number): Fraction {
@@ -317,9 +317,9 @@ export class Fraction {
   //        }
   //        else {
   //            d++;
-  //            n = <number>Math.round(value * d);
+  //            n = Math.round(value * d);
   //        }
-  //        fraction = n / <number>d;
+  //        fraction = n / d;
   //    }
   //    return new Fraction(n, d);
   //}
@@ -330,7 +330,7 @@ export class Fraction {
   //}
 
   //public static getFraction(value: number, denominatorPrecision: number): Fraction {
-  //    let numerator: number = <number>Math.round(value / (1.0 / denominatorPrecision));
+  //    let numerator: number = Math.round(value / (1.0 / denominatorPrecision));
   //    return new Fraction(numerator, denominatorPrecision);
   //}
   //public static fractionMin(f1: Fraction, f2: Fraction): Fraction {

+ 58 - 0
src/Common/DataObjects/Matrix2D.ts

@@ -0,0 +1,58 @@
+import { PointF2D } from "./PointF2D";
+
+export class Matrix2D {
+    private matrix: number[][];
+
+    constructor() {
+        this.matrix = [];
+        for (let i: number = 0; i < 2; i++) {
+            this.matrix[i] = [];
+            for (let j: number = 0; j < 2; j++) {
+                this.matrix[i][j] = 0;
+            }
+        }
+    }
+
+    public static getRotationMatrix(angle: number): Matrix2D {
+        const rotation: Matrix2D = new Matrix2D();
+        const cos: number = Math.cos(angle);
+        const sin: number = Math.sin(angle);
+        rotation.matrix[0][0] = cos;
+        rotation.matrix[0][1] = -sin;
+        rotation.matrix[1][0] = sin;
+        rotation.matrix[1][1] = cos;
+        return rotation;
+    }
+
+    public scalarMultiplication(scalar: number): void {
+        for (let i: number = 0; i < 2; i++) {
+            for (let j: number = 0; j < 2; j++) {
+                this.matrix[i][j] *= scalar;
+            }
+        }
+    }
+
+    public getTransposeMatrix(): Matrix2D {
+        const transpose: Matrix2D = new Matrix2D();
+        for (let i: number = 0; i < 2; i++) {
+            for (let j: number = 0; j < 2; j++) {
+                transpose.matrix[i][j] = this.matrix[j][i];
+            }
+        }
+        return transpose;
+    }
+
+    public vectorMultiplication(point: PointF2D): PointF2D {
+        const result: PointF2D = new PointF2D();
+        result.x = point.x * this.matrix[0][0] + point.y * this.matrix[0][1];
+        result.y = point.x * this.matrix[1][0] + point.y * this.matrix[1][1];
+        return result;
+    }
+
+    // public get Matrix(index: number): number[] {
+    //     return this.matrix[index];
+    // }
+    // public set Matrix(index: number, value: number[]): void {
+    //     this.matrix[index] = value;
+    // }
+}

+ 3 - 3
src/Common/DataObjects/Pitch.ts

@@ -132,7 +132,7 @@ export class Pitch {
     }
 
     public static fromHalftone(halftone: number): Pitch {
-        const octave: number = <number>Math.floor(<number>halftone / 12) - Pitch.octXmlDiff;
+        const octave: number = Math.floor(halftone / 12) - Pitch.octXmlDiff;
         const halftoneInOctave: number = halftone % 12;
         let fundamentalNote: NoteEnum = <NoteEnum>halftoneInOctave;
         let accidental: AccidentalEnum = AccidentalEnum.NONE;
@@ -140,11 +140,11 @@ export class Pitch {
             fundamentalNote = <NoteEnum>(halftoneInOctave - 1);
             accidental = AccidentalEnum.SHARP;
         }
-        return new Pitch(fundamentalNote, <number>octave, accidental);
+        return new Pitch(fundamentalNote, octave, accidental);
     }
 
     public static ceiling(halftone: number): NoteEnum {
-        halftone = <number>(halftone) % 12;
+        halftone = (halftone) % 12;
         let fundamentalNote: NoteEnum = <NoteEnum>halftone;
         if (this.pitchEnumValues.indexOf(fundamentalNote) === -1) {
             fundamentalNote = <NoteEnum>(halftone + 1);

+ 49 - 0
src/MusicalScore/Graphical/GraphicalCurve.ts

@@ -0,0 +1,49 @@
+import { PointF2D } from "../../Common/DataObjects/PointF2D";
+
+export class GraphicalCurve {
+    private static bezierCurveStepSize: number = 1000;
+    private static tPow3: number[];
+    private static oneMinusTPow3: number[];
+    private static bezierFactorOne: number[];
+    private static bezierFactorTwo: number[];
+
+    // Pre-calculate Curve-independend factors, to be used later in the Slur- and TieCurvePoints calculation.
+    constructor() {
+        GraphicalCurve.tPow3 = new Array(GraphicalCurve.bezierCurveStepSize);
+        GraphicalCurve.oneMinusTPow3 = new Array(GraphicalCurve.bezierCurveStepSize);
+        GraphicalCurve.bezierFactorOne = new Array(GraphicalCurve.bezierCurveStepSize);
+        GraphicalCurve.bezierFactorTwo = new Array(GraphicalCurve.bezierCurveStepSize);
+        for (let i: number = 0; i < GraphicalCurve.bezierCurveStepSize; i++) {
+            const t: number =  i / GraphicalCurve.bezierCurveStepSize;
+
+            GraphicalCurve.tPow3[i] = Math.pow(t, 3);
+            GraphicalCurve.oneMinusTPow3[i] = Math.pow((1 - t), 3);
+            GraphicalCurve.bezierFactorOne[i] = 3 * Math.pow((1 - t), 2) * t;
+            GraphicalCurve.bezierFactorTwo[i] = 3 * (1 - t) * Math.pow(t, 2);
+        }
+    }
+
+    public bezierStartPt: PointF2D;
+    public bezierStartControlPt: PointF2D;
+    public bezierEndControlPt: PointF2D;
+    public bezierEndPt: PointF2D;
+
+    /**
+     *
+     * @param relativePosition
+     */
+    public calculateCurvePointAtIndex(relativePosition: number): PointF2D {
+        const index: number =  Math.round(relativePosition);
+        if (index < 0 || index >= GraphicalCurve.bezierCurveStepSize) {
+            return new PointF2D();
+        }
+
+        return new PointF2D(  (GraphicalCurve.oneMinusTPow3[index] * this.bezierStartPt.x
+            + GraphicalCurve.bezierFactorOne[index] * this.bezierStartControlPt.x
+            + GraphicalCurve.bezierFactorTwo[index] * this.bezierEndControlPt.x
+            + GraphicalCurve.tPow3[index] * this.bezierEndPt.x)
+            ,                 (GraphicalCurve.oneMinusTPow3[index] * this.bezierStartPt.y
+            + GraphicalCurve.bezierFactorOne[index] * this.bezierStartControlPt.y
+            + GraphicalCurve.bezierFactorTwo[index] * this.bezierEndControlPt.y + GraphicalCurve.tPow3[index] * this.bezierEndPt.y));
+    }
+}

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

@@ -726,7 +726,7 @@ export class GraphicalMusicSheet {
 
     public findClosestLeftStaffEntry(fractionalIndex: number, searchOnlyVisibleEntries: boolean): GraphicalStaffEntry {
         let foundEntry: GraphicalStaffEntry = undefined;
-        let leftIndex: number = <number>Math.floor(fractionalIndex);
+        let leftIndex: number = Math.floor(fractionalIndex);
         leftIndex = Math.min(this.VerticalGraphicalStaffEntryContainers.length - 1, leftIndex);
         for (let i: number = leftIndex; i >= 0; i--) {
             foundEntry = this.getStaffEntry(i);
@@ -745,7 +745,7 @@ export class GraphicalMusicSheet {
 
     public findClosestRightStaffEntry(fractionalIndex: number, returnOnlyVisibleEntries: boolean): GraphicalStaffEntry {
         let foundEntry: GraphicalStaffEntry = undefined;
-        const rightIndex: number = <number>Math.max(0, Math.ceil(fractionalIndex));
+        const rightIndex: number = Math.max(0, Math.ceil(fractionalIndex));
         for (let i: number = rightIndex; i < this.VerticalGraphicalStaffEntryContainers.length; i++) {
             foundEntry = this.getStaffEntry(i);
             if (foundEntry !== undefined) {

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

@@ -52,7 +52,7 @@ export class GraphicalNote extends GraphicalObject {
       if (this.sourceNote === undefined || this.sourceNote.NoteTuplet === undefined) {
         while (product < expandedNumerator) {
           num++;
-          product = <number>Math.pow(2, num);
+          product = Math.pow(2, num);
         }
       }
       return Math.min(3, num - 1);

+ 805 - 0
src/MusicalScore/Graphical/GraphicalSlur.ts

@@ -0,0 +1,805 @@
+
+import { PointF2D } from "../../Common/DataObjects/PointF2D";
+import { GraphicalNote } from "./GraphicalNote";
+import { GraphicalCurve } from "./GraphicalCurve";
+import { Slur } from "../VoiceData/Expressions/ContinuousExpressions/Slur";
+import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
+import { EngravingRules } from "./EngravingRules";
+import { StaffLine } from "./StaffLine";
+import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
+import { Matrix2D } from "../../Common/DataObjects/Matrix2D";
+import { LinkedVoice } from "../VoiceData/LinkedVoice";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
+import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
+
+export class GraphicalSlur extends GraphicalCurve {
+    // private intersection: PointF2D;
+
+    constructor(slur: Slur) {
+        super();
+        this.slur = slur;
+    }
+
+    public slur: Slur;
+    public staffEntries: GraphicalStaffEntry[] = [];
+    public placement: PlacementEnum;
+    public graceStart: boolean;
+    public graceEnd: boolean;
+
+    /**
+     *
+     * @param rules
+     */
+    public calculateCurve(rules: EngravingRules): void {
+
+        // single GraphicalSlur means a single Curve, eg each GraphicalSlurObject is meant to be on the same StaffLine
+        // a Slur can span more than one GraphicalSlurObjects
+        const startStaffEntry: GraphicalStaffEntry = this.staffEntries[0];
+        const endStaffEntry: GraphicalStaffEntry = this.staffEntries[this.staffEntries.length - 1];
+
+        // where the Slur (not the graphicalObject) starts and ends (could belong to another StaffLine)
+        let slurStartNote: GraphicalNote = startStaffEntry.findGraphicalNoteFromNote(this.slur.StartNote);
+        if (slurStartNote === undefined && this.graceStart) {
+            slurStartNote = startStaffEntry.findGraphicalNoteFromGraceNote(this.slur.StartNote);
+        }
+        if (slurStartNote === undefined) {
+            slurStartNote = startStaffEntry.findEndTieGraphicalNoteFromNoteWithStartingSlur(this.slur.StartNote, this.slur);
+        }
+        let slurEndNote: GraphicalNote = endStaffEntry.findGraphicalNoteFromNote(this.slur.EndNote);
+        if (slurEndNote === undefined && this.graceEnd) {
+            slurEndNote = endStaffEntry.findGraphicalNoteFromGraceNote(this.slur.EndNote);
+        }
+
+        const staffLine: StaffLine = startStaffEntry.parentMeasure.ParentStaffLine;
+        const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
+
+        this.calculatePlacement(skyBottomLineCalculator, staffLine);
+
+        // the Start- and End Reference Points for the Sky-BottomLine
+        const startEndPoints: {startX: number, startY: number, endX: number, endY: number} =
+            this.calculateStartAndEnd(slurStartNote, slurEndNote, staffLine, rules, skyBottomLineCalculator);
+
+        const startX: number = startEndPoints.startX;
+        const endX: number = startEndPoints.endX;
+        let startY: number = startEndPoints.startY;
+        let endY: number = startEndPoints.endY;
+        const minAngle: number = rules.SlurTangentMinAngle;
+        const maxAngle: number = rules.SlurTangentMaxAngle;
+        let start: PointF2D, end: PointF2D;
+        let points: PointF2D[];
+
+        if (this.placement === PlacementEnum.Above) {
+            startY -= rules.SlurNoteHeadYOffset;
+            endY -= rules.SlurNoteHeadYOffset;
+            start = new PointF2D(startX, startY);
+            end = new PointF2D(endX, endY);
+            const startUpperRight: PointF2D = new PointF2D(this.staffEntries[0].parentMeasure.PositionAndShape.RelativePosition.x
+                                                           + this.staffEntries[0].PositionAndShape.RelativePosition.x,
+                                                           startY);
+            if (slurStartNote !== undefined) {
+                    startUpperRight.x += this.staffEntries[0].PositionAndShape.BorderRight;
+            } else  {
+                    // continuing Slur from previous StaffLine - must start after last Instruction of first Measure
+                    startUpperRight.x = this.staffEntries[0].parentMeasure.beginInstructionsWidth;
+            }
+
+            // must also add the GraceStaffEntry's ParentStaffEntry Position
+            if (this.graceStart) {
+                startUpperRight.x += endStaffEntry.PositionAndShape.RelativePosition.x;
+            }
+
+            const endUpperLeft: PointF2D = new PointF2D(this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.RelativePosition.x
+                                                        + this.staffEntries[this.staffEntries.length - 1].PositionAndShape.RelativePosition.x,
+                                                        endY);
+            if (slurEndNote !== undefined) {
+                    endUpperLeft.x += this.staffEntries[this.staffEntries.length - 1].PositionAndShape.BorderLeft;
+            } else {
+                    // Slur continues to next StaffLine - must reach the end of current StaffLine
+                    endUpperLeft.x = this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.RelativePosition.x
+                    + this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.Size.width;
+            }
+
+            // must also add the GraceStaffEntry's ParentStaffEntry Position
+            if (this.graceEnd) {
+                endUpperLeft.x += endStaffEntry.staffEntryParent.PositionAndShape.RelativePosition.x;
+            }
+
+            // SkyLinePointsList between firstStaffEntry startUpperRightPoint and lastStaffentry endUpperLeftPoint
+            points = this.calculateTopPoints(startUpperRight, endUpperLeft, staffLine, skyBottomLineCalculator);
+
+            if (points.length === 0) {
+                const pointF: PointF2D = new PointF2D((endUpperLeft.x - startUpperRight.x) / 2 + startUpperRight.x,
+                                                      (endUpperLeft.y - startUpperRight.y) / 2 + startUpperRight.y);
+                points.push(pointF);
+            }
+
+            // Angle between original x-Axis and Line from Start-Point to End-Point
+            const startEndLineAngleRadians: number = (Math.atan((endY - startY) / (endX - startX)));
+
+            // translate origin at Start (positiveY from Bottom to Top => change sign for Y)
+            const start2: PointF2D = new PointF2D(0, 0);
+            let end2: PointF2D = new PointF2D(endX - startX, -(endY - startY));
+
+            // and Rotate at new Origin startEndLineAngle degrees
+                // clockwise/counterclockwise Rotation
+                // after Rotation end2.Y must be 0
+                // Inverse of RotationMatrix = TransposeMatrix of RotationMatrix
+            let rotationMatrix: Matrix2D, transposeMatrix: Matrix2D;
+            rotationMatrix = Matrix2D.getRotationMatrix(startEndLineAngleRadians);
+            transposeMatrix = rotationMatrix.getTransposeMatrix();
+            end2 = rotationMatrix.vectorMultiplication(end2);
+            const transformedPoints: PointF2D[] = this.calculateTranslatedAndRotatedPointListAbove(points, startX, startY, rotationMatrix);
+
+            // calculate tangent Lines maximum Slopes between StartPoint and EndPoint to all Points in SkyLine
+                // and tangent Lines characteristica
+            const leftLineSlope: number = this.calculateMaxLeftSlope(transformedPoints, start2, end2);
+            const rightLineSlope: number = this.calculateMaxRightSlope(transformedPoints, start2, end2);
+            const leftLineD: number = start2.y - start2.x * leftLineSlope;
+            const rightLineD: number = end2.y - end2.x * rightLineSlope;
+
+            // calculate IntersectionPoint of the 2 Lines
+                // if same Slope, then Point.X between Start and End and Point.Y fixed
+            const intersectionPoint: PointF2D = new PointF2D();
+            let sameSlope: boolean = false;
+            if (Math.abs(Math.abs(leftLineSlope) - Math.abs(rightLineSlope)) < 0.0001) {
+                intersectionPoint.x = end2.x / 2;
+                intersectionPoint.y = 0;
+                sameSlope = true;
+            } else {
+                intersectionPoint.x = (rightLineD - leftLineD) / (leftLineSlope - rightLineSlope);
+                intersectionPoint.y = leftLineSlope * intersectionPoint.x + leftLineD;
+            }
+
+            // calculate tangent Lines Angles
+                // (using the calculated Slopes and the Ratio from the IntersectionPoint's distance to the MaxPoint in the SkyLine)
+            const leftAngle: number = minAngle;
+            const rightAngle: number = -minAngle;
+            // if the calculated Slopes (left and right) are equal, then Angles have fixed values
+            if (!sameSlope) {
+                this.calculateAngles(leftAngle, rightAngle, leftLineSlope, rightLineSlope, maxAngle);
+            }
+
+            // calculate Curve's Control Points
+            const controlPoints: {leftControlPoint: PointF2D, rightControlPoint: PointF2D} =
+                this.calculateControlPoints(end2.x, leftAngle, rightAngle, transformedPoints);
+
+            let leftControlPoint: PointF2D = controlPoints.leftControlPoint;
+            let rightControlPoint: PointF2D = controlPoints.rightControlPoint;
+
+            // transform ControlPoints to original Coordinate System
+                // (rotate back and translate back)
+            leftControlPoint = transposeMatrix.vectorMultiplication(leftControlPoint);
+            leftControlPoint.x += startX;
+            leftControlPoint.y = -leftControlPoint.y + startY;
+            rightControlPoint = transposeMatrix.vectorMultiplication(rightControlPoint);
+            rightControlPoint.x += startX;
+            rightControlPoint.y = -rightControlPoint.y + startY;
+
+            /* for DEBUG only */
+            // this.intersection = transposeMatrix.vectorMultiplication(intersectionPoint);
+            // this.intersection.x += startX;
+            // this.intersection.y = -this.intersection.y + startY;
+            /* for DEBUG only */
+
+            // set private members
+            this.bezierStartPt = start;
+            this.bezierStartControlPt = leftControlPoint;
+            this.bezierEndControlPt = rightControlPoint;
+            this.bezierEndPt = end;
+
+            // calculate CurvePoints
+            const length: number = staffLine.SkyLine.length;
+            const startIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(this.bezierStartPt.x, length);
+            const endIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(this.bezierEndPt.x, length);
+            const distance: number = this.bezierEndPt.x - this.bezierStartPt.x;
+            const samplingUnit: number = skyBottomLineCalculator.SamplingUnit;
+            for (let i: number = startIndex; i < endIndex; i++) {
+                // get the right distance ratio and index on the curve
+                const diff: number = i / samplingUnit - this.bezierStartPt.x;
+                const curvePoint: PointF2D = this.calculateCurvePointAtIndex(Math.abs(diff) / distance);
+
+                // update left- and rightIndex for better accuracy
+                let index: number = skyBottomLineCalculator.getLeftIndexForPointX(curvePoint.x, length);
+                // update SkyLine with final slur curve:
+                if (index >= startIndex) {
+                    staffLine.SkyLine[index] = Math.min(staffLine.SkyLine[index], curvePoint.y);
+                }
+                index++;
+                if (index < length) {
+                    staffLine.SkyLine[index] = Math.min(staffLine.SkyLine[index], curvePoint.y);
+                }
+            }
+        } else {
+            startY += rules.SlurNoteHeadYOffset;
+            endY += rules.SlurNoteHeadYOffset;
+            start = new PointF2D(startX, startY);
+            end = new PointF2D(endX, endY);
+
+            // firstStaffEntry startLowerRightPoint and lastStaffentry endLowerLeftPoint
+            const startLowerRight: PointF2D = new PointF2D(this.staffEntries[0].parentMeasure.PositionAndShape.RelativePosition.x
+                                                           + this.staffEntries[0].PositionAndShape.RelativePosition.x,
+                                                           startY);
+            if (slurStartNote !== undefined) {
+                startLowerRight.x += this.staffEntries[0].PositionAndShape.BorderRight;
+            } else {
+                // continuing Slur from previous StaffLine - must start after last Instruction of first Measure
+                startLowerRight.x = this.staffEntries[0].parentMeasure.beginInstructionsWidth;
+            }
+
+            // must also add the GraceStaffEntry's ParentStaffEntry Position
+            if (this.graceStart) {
+                startLowerRight.x += endStaffEntry.PositionAndShape.RelativePosition.x;
+            }
+            const endLowerLeft: PointF2D = new PointF2D(this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.RelativePosition.x
+                                                        + this.staffEntries[this.staffEntries.length - 1].PositionAndShape.RelativePosition.x,
+                                                        endY);
+            if (slurEndNote !== undefined) {
+                endLowerLeft.x += this.staffEntries[this.staffEntries.length - 1].PositionAndShape.BorderLeft;
+            } else {
+                // Slur continues to next StaffLine - must reach the end of current StaffLine
+                endLowerLeft.x = this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.RelativePosition.x
+                    + this.staffEntries[this.staffEntries.length - 1].parentMeasure.PositionAndShape.Size.width;
+            }
+
+            // must also add the GraceStaffEntry's ParentStaffEntry Position
+            if (this.graceEnd) {
+                endLowerLeft.x += endStaffEntry.staffEntryParent.PositionAndShape.RelativePosition.x;
+            }
+
+            // BottomLinePointsList between firstStaffEntry startLowerRightPoint and lastStaffentry endLowerLeftPoint
+            points = this.calculateBottomPoints(startLowerRight, endLowerLeft, staffLine, skyBottomLineCalculator);
+
+            if (points.length === 0) {
+                const pointF: PointF2D = new PointF2D((endLowerLeft.x - startLowerRight.x) / 2 + startLowerRight.x,
+                                                      (endLowerLeft.y - startLowerRight.y) / 2 + startLowerRight.y);
+                points.push(pointF);
+            }
+
+            // Angle between original x-Axis and Line from Start-Point to End-Point
+            const startEndLineAngleRadians: number = Math.atan((endY - startY) / (endX - startX));
+            // translate origin at Start
+            const start2: PointF2D = new PointF2D(0, 0);
+            let end2: PointF2D = new PointF2D(endX - startX, endY - startY);
+
+            // and Rotate at new Origin startEndLineAngle degrees
+            // clockwise/counterclockwise Rotation
+            // after Rotation end2.Y must be 0
+            // Inverse of RotationMatrix = TransposeMatrix of RotationMatrix
+            let rotationMatrix: Matrix2D, transposeMatrix: Matrix2D;
+            rotationMatrix = Matrix2D.getRotationMatrix(-startEndLineAngleRadians);
+            transposeMatrix = rotationMatrix.getTransposeMatrix();
+            end2 = rotationMatrix.vectorMultiplication(end2);
+            const transformedPoints: PointF2D[] = this.calculateTranslatedAndRotatedPointListBelow(points, startX, startY, rotationMatrix);
+
+            // calculate tangent Lines maximum Slopes between StartPoint and EndPoint to all Points in BottomLine
+            // and tangent Lines characteristica
+            const leftLineSlope: number = this.calculateMaxLeftSlope(transformedPoints, start2, end2);
+            const rightLineSlope: number = this.calculateMaxRightSlope(transformedPoints, start2, end2);
+            const leftLineD: number = start2.y - start2.x * leftLineSlope;
+            const rightLineD: number = end2.y - end2.x * rightLineSlope;
+
+            // calculate IntersectionPoint of the 2 Lines
+            // if same Slope, then Point.X between Start and End and Point.Y fixed
+            const intersectionPoint: PointF2D = new PointF2D();
+            let sameSlope: boolean = false;
+            if (Math.abs(Math.abs(leftLineSlope) - Math.abs(rightLineSlope)) < 0.0001) {
+                intersectionPoint.x = end2.x / 2;
+                intersectionPoint.y = 0;
+                sameSlope = true;
+            } else {
+                intersectionPoint.x = (rightLineD - leftLineD) / (leftLineSlope - rightLineSlope);
+                intersectionPoint.y = leftLineSlope * intersectionPoint.x + leftLineD;
+            }
+
+            // calculate tangent Lines Angles
+            // (using the calculated Slopes and the Ratio from the IntersectionPoint's distance to the MaxPoint in the SkyLine)
+            const leftAngle: number = minAngle;
+            const rightAngle: number = -minAngle;
+            // if the calculated Slopes (left and right) are equal, then Angles have fixed values
+            if (!sameSlope) {
+                this.calculateAngles(leftAngle, rightAngle, leftLineSlope, rightLineSlope, maxAngle);
+            }
+
+            // calculate Curve's Control Points
+            const controlPoints: {leftControlPoint: PointF2D, rightControlPoint: PointF2D} =
+                this.calculateControlPoints(end2.x, leftAngle, rightAngle, transformedPoints);
+            let leftControlPoint: PointF2D = controlPoints.leftControlPoint;
+            let rightControlPoint: PointF2D = controlPoints.rightControlPoint;
+
+            // transform ControlPoints to original Coordinate System
+            // (rotate back and translate back)
+            leftControlPoint = transposeMatrix.vectorMultiplication(leftControlPoint);
+            leftControlPoint.x += startX;
+            leftControlPoint.y += startY;
+            rightControlPoint = transposeMatrix.vectorMultiplication(rightControlPoint);
+            rightControlPoint.x += startX;
+            rightControlPoint.y += startY;
+
+            // set private members
+            this.bezierStartPt = start;
+            this.bezierStartControlPt = leftControlPoint;
+            this.bezierEndControlPt = rightControlPoint;
+            this.bezierEndPt = end;
+
+            /* for DEBUG only */
+            // this.intersection = transposeMatrix.vectorMultiplication(intersectionPoint);
+            // this.intersection.x += startX;
+            // this.intersection.y += startY;
+            /* for DEBUG only */
+
+            // calculate CurvePoints
+            const length: number = staffLine.BottomLine.length;
+            const startIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(this.bezierStartPt.x, length);
+            const endIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(this.bezierEndPt.x, length);
+            const distance: number = this.bezierEndPt.x - this.bezierStartPt.x;
+            const samplingUnit: number = skyBottomLineCalculator.SamplingUnit;
+            for (let i: number = startIndex; i < endIndex; i++) {
+                // get the right distance ratio and index on the curve
+                const diff: number = i / samplingUnit - this.bezierStartPt.x;
+                const curvePoint: PointF2D = this.calculateCurvePointAtIndex(Math.abs(diff) / distance);
+
+                // update left- and rightIndex for better accuracy
+                let index: number = skyBottomLineCalculator.getLeftIndexForPointX(curvePoint.x, length);
+                // update BottomLine with final slur curve:
+                if (index >= startIndex) {
+                    staffLine.BottomLine[index] = Math.max(staffLine.BottomLine[index], curvePoint.y);
+                }
+                index++;
+                if (index < length) {
+                    staffLine.BottomLine[index] = Math.max(staffLine.BottomLine[index], curvePoint.y);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * This method calculates the Start and End Positions of the Slur Curve.
+     * @param slurStartNote
+     * @param slurEndNote
+     * @param staffLine
+     * @param startX
+     * @param startY
+     * @param endX
+     * @param endY
+     * @param rules
+     * @param skyBottomLineCalculator
+     */
+    private calculateStartAndEnd(   slurStartNote: GraphicalNote,
+                                    slurEndNote: GraphicalNote,
+                                    staffLine: StaffLine,
+                                    rules: EngravingRules,
+                                    skyBottomLineCalculator: SkyBottomLineCalculator): {startX: number, startY: number, endX: number, endY: number} {
+        let startX: number = 0;
+        let startY: number = 0;
+        let endX: number = 0;
+        let endY: number = 0;
+
+        if (slurStartNote !== undefined) {
+            // must be relative to StaffLine
+            startX = slurStartNote.PositionAndShape.RelativePosition.x + slurStartNote.parentVoiceEntry.parentStaffEntry.PositionAndShape.RelativePosition.x
+                                            + slurStartNote.parentVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
+
+            // If Slur starts on a Gracenote
+            if (this.graceStart) {
+                startX += slurStartNote.parentVoiceEntry.parentStaffEntry.staffEntryParent.PositionAndShape.RelativePosition.x;
+            }
+
+            //const first: GraphicalNote = slurStartNote.parentVoiceEntry.notes[0];
+
+            // Determine Start/End Point coordinates with the VoiceEntry of the Start/EndNote of the slur
+            const slurStartVE: GraphicalVoiceEntry = slurStartNote.parentVoiceEntry;
+
+            if (this.placement === PlacementEnum.Above) {
+                startY = slurStartVE.PositionAndShape.RelativePosition.y + slurStartVE.PositionAndShape.BorderTop;
+            } else {
+                startY = slurStartVE.PositionAndShape.RelativePosition.y + slurStartVE.PositionAndShape.BorderBottom;
+            }
+
+            // if (first.NoteStem !== undefined && first.NoteStem.Direction === StemEnum.StemUp && this.placement === PlacementEnum.Above) {
+            //     startX += first.NoteStem.PositionAndShape.RelativePosition.x;
+            //     startY = skyBottomLineCalculator.getSkyLineMinAtPoint(staffLine, startX);
+            // } else {
+            //     const last: GraphicalNote = <GraphicalNote>slurStartNote[slurEndNote.parentVoiceEntry.notes.length - 1];
+            //     if (last.NoteStem !== undefined && last.NoteStem.Direction === StemEnum.StemDown && this.placement === PlacementEnum.Below) {
+            //         startX += last.NoteStem.PositionAndShape.RelativePosition.x;
+            //         startY = skyBottomLineCalculator.getBottomLineMaxAtPoint(staffLine, startX);
+            //     } else {
+            //     }
+            // }
+        } else {
+            startX = staffLine.Measures[0].beginInstructionsWidth;
+        }
+
+        if (slurEndNote !== undefined) {
+            endX = slurEndNote.PositionAndShape.RelativePosition.x + slurEndNote.parentVoiceEntry.parentStaffEntry.PositionAndShape.RelativePosition.x
+                + slurEndNote.parentVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
+
+            // If Slur ends in a Gracenote
+            if (this.graceEnd) {
+                endX += slurEndNote.parentVoiceEntry.parentStaffEntry.staffEntryParent.PositionAndShape.RelativePosition.x;
+            }
+
+            const slurEndVE: GraphicalVoiceEntry = slurEndNote.parentVoiceEntry;
+            if (this.placement === PlacementEnum.Above) {
+                endY = slurEndVE.PositionAndShape.RelativePosition.y + slurEndVE.PositionAndShape.BorderTop;
+            } else {
+                endY = slurEndVE.PositionAndShape.RelativePosition.y + slurEndVE.PositionAndShape.BorderBottom;
+            }
+
+            // const first: GraphicalNote = <GraphicalNote>slurEndNote.parentVoiceEntry.notes[0];
+            // if (first.NoteStem !== undefined && first.NoteStem.Direction === StemEnum.StemUp && this.placement === PlacementEnum.Above) {
+            //     endX += first.NoteStem.PositionAndShape.RelativePosition.x;
+            //     endY = skyBottomLineCalculator.getSkyLineMinAtPoint(staffLine, endX);
+            // } else {
+            //     const last: GraphicalNote = <GraphicalNote>slurEndNote.parentVoiceEntry.notes[slurEndNote.parentVoiceEntry.notes.length - 1];
+            //     if (last.NoteStem !== undefined && last.NoteStem.Direction === StemEnum.StemDown && this.placement === PlacementEnum.Below) {
+            //         endX += last.NoteStem.PositionAndShape.RelativePosition.x;
+            //         endY = skyBottomLineCalculator.getBottomLineMaxAtPoint(staffLine, endX);
+            //     } else {
+            //         if (this.placement === PlacementEnum.Above) {
+            //             const highestNote: GraphicalNote = last;
+            //             endY = highestNote.PositionAndShape.RelativePosition.y;
+            //             if (highestNote.NoteHead !== undefined) {
+            //                 endY += highestNote.NoteHead.PositionAndShape.BorderMarginTop;
+            //             } else { endY += highestNote.PositionAndShape.BorderTop; }
+            //         } else {
+            //             const lowestNote: GraphicalNote = first;
+            //             endY = lowestNote.parentVoiceEntry
+            //             lowestNote.PositionAndShape.RelativePosition.y;
+            //             if (lowestNote.NoteHead !== undefined) {
+            //                 endY += lowestNote.NoteHead.PositionAndShape.BorderMarginBottom;
+            //             } else { endY += lowestNote.PositionAndShape.BorderBottom; }
+            //         }
+            //     }
+            // }
+        } else {
+            endX = staffLine.PositionAndShape.Size.width;
+        }
+
+        // if GraphicalSlur breaks over System, then the end/start of the curve is at the corresponding height with the known start/end
+        if (slurStartNote === undefined && slurEndNote === undefined) {
+            startY = 0;
+            endY = 0;
+        }
+        if (slurStartNote === undefined) {
+            startY = endY;
+        }
+        if (slurEndNote === undefined) {
+            endY = startY;
+        }
+
+        // if two slurs start/end at the same GraphicalNote, then the second gets an offset
+        if (this.slur.startNoteHasMoreStartingSlurs() && this.slur.isSlurLonger()) {
+            if (this.placement === PlacementEnum.Above) {
+                startY -= rules.SlursStartingAtSameStaffEntryYOffset;
+            } else { startY += rules.SlursStartingAtSameStaffEntryYOffset; }
+        }
+        if (this.slur.endNoteHasMoreEndingSlurs() && this.slur.isSlurLonger()) {
+            if (this.placement === PlacementEnum.Above) {
+                endY -= rules.SlursStartingAtSameStaffEntryYOffset;
+            } else { endY += rules.SlursStartingAtSameStaffEntryYOffset; }
+        }
+
+        return {startX, startY, endX, endY};
+    }
+
+    /**
+     * This method calculates the placement of the Curve.
+     * @param skyBottomLineCalculator
+     * @param staffLine
+     */
+    private calculatePlacement(skyBottomLineCalculator: SkyBottomLineCalculator, staffLine: StaffLine): void {
+        // old version: when lyrics are given place above:
+        // if ( !this.slur.StartNote.ParentVoiceEntry.LyricsEntries.isEmpty || (this.slur.EndNote !== undefined
+        //                                     && !this.slur.EndNote.ParentVoiceEntry.LyricsEntries.isEmpty) ) {
+        //     this.placement = PlacementEnum.Above;
+        //     return;
+        // }
+
+        // if any StaffEntry belongs to a Measure with multiple Voices, than
+        // if Slur's Start- or End-Note belongs to a LinkedVoice Below else Above
+        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
+            const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
+            if (graphicalStaffEntry.parentMeasure.hasMultipleVoices()) {
+                if (this.slur.StartNote.ParentVoiceEntry.ParentVoice instanceof LinkedVoice ||
+                    this.slur.EndNote.ParentVoiceEntry.ParentVoice instanceof LinkedVoice) {
+                    this.placement = PlacementEnum.Below;
+                } else { this.placement = PlacementEnum.Above; }
+                return;
+            }
+        }
+
+        // when lyrics are given place above:
+        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
+            const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
+            if (graphicalStaffEntry.LyricsEntries.length > 0) {
+                this.placement = PlacementEnum.Above;
+                return;
+            }
+        }
+        const startStaffEntry: GraphicalStaffEntry = this.staffEntries[0];
+        const endStaffEntry: GraphicalStaffEntry = this.staffEntries[this.staffEntries.length - 1];
+
+        // Deactivated: single Voice, opposite to StemDirection
+        // if (startStaffEntry.hasStem() && endStaffEntry.hasStem() && startStaffEntry.getStemDirection() === endStaffEntry.getStemDirection()) {
+        //     this.placement = (startStaffEntry.getStemDirection() === StemDirectionType.Up) ? PlacementEnum.Below : PlacementEnum.Above;
+        // } else {
+
+        // Placement at the side with the minimum border
+        let sX: number = startStaffEntry.PositionAndShape.BorderLeft + startStaffEntry.PositionAndShape.RelativePosition.x
+                    + startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
+        let eX: number = endStaffEntry.PositionAndShape.BorderRight + endStaffEntry.PositionAndShape.RelativePosition.x
+                    + endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
+
+        if (this.graceStart) {
+            sX += endStaffEntry.PositionAndShape.RelativePosition.x;
+        }
+        if (this.graceEnd) {
+            eX += endStaffEntry.staffEntryParent.PositionAndShape.RelativePosition.x;
+        }
+
+        // get SkyBottomLine borders
+        let minAbove: number = skyBottomLineCalculator.getSkyLineMinInRange(sX, eX);
+        let maxBelow: number = skyBottomLineCalculator.getBottomLineMaxInRange(sX, eX);
+
+        // get lowest and highest placed NoteHead
+        const notesMinY: number = Math.min(startStaffEntry.PositionAndShape.BorderTop,
+                                           endStaffEntry.PositionAndShape.BorderTop);
+        const notesMaxY: number = Math.max(startStaffEntry.PositionAndShape.BorderBottom,
+                                           endStaffEntry.PositionAndShape.BorderBottom);
+
+        // get lowest and highest placed NoteHead
+        minAbove = notesMinY - minAbove;
+        maxBelow = maxBelow - notesMaxY;
+
+        if (Math.abs(maxBelow) > Math.abs(minAbove)) {
+            this.placement = PlacementEnum.Above;
+        } else { this.placement = PlacementEnum.Below; }
+        //}
+    }
+
+    /**
+     * This method calculates the Points between Start- and EndPoint (case above).
+     * @param start
+     * @param end
+     * @param staffLine
+     * @param skyBottomLineCalculator
+     */
+    private calculateTopPoints(start: PointF2D, end: PointF2D, staffLine: StaffLine, skyBottomLineCalculator: SkyBottomLineCalculator): PointF2D[] {
+        const points: PointF2D[] = [];
+        let startIndex: number = skyBottomLineCalculator.getRightIndexForPointX(start.x, staffLine.SkyLine.length);
+        let endIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(end.x, staffLine.SkyLine.length);
+
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        if (endIndex >= staffLine.SkyLine.length) {
+            endIndex = staffLine.SkyLine.length - 1;
+        }
+
+        for (let i: number = startIndex; i < endIndex; i++) {
+            const point: PointF2D = new PointF2D((0.5 + i) / skyBottomLineCalculator.SamplingUnit, staffLine.SkyLine[i]);
+            points.push(point);
+        }
+
+        return points;
+    }
+
+    /**
+     * This method calculates the Points between Start- and EndPoint (case below).
+     * @param start
+     * @param end
+     * @param staffLine
+     * @param skyBottomLineCalculator
+     */
+    private calculateBottomPoints(start: PointF2D, end: PointF2D, staffLine: StaffLine, skyBottomLineCalculator: SkyBottomLineCalculator): PointF2D[] {
+        const points: PointF2D[] = [];
+
+        // get BottomLine indices
+        let startIndex: number = skyBottomLineCalculator.getRightIndexForPointX(start.x, staffLine.BottomLine.length);
+        let endIndex: number = skyBottomLineCalculator.getLeftIndexForPointX(end.x, staffLine.BottomLine.length);
+        if (startIndex < 0) {
+            startIndex = 0;
+        }
+        if (endIndex >= staffLine.BottomLine.length) {
+            endIndex = staffLine.BottomLine.length - 1;
+        }
+
+        for (let i: number = startIndex; i < endIndex; i++) {
+            const point: PointF2D = new PointF2D((0.5 + i) / skyBottomLineCalculator.SamplingUnit, staffLine.BottomLine[i]);
+            points.push(point);
+        }
+
+        return points;
+    }
+
+    /**
+     * This method calculates the maximum slope between StartPoint and BetweenPoints.
+     * @param points
+     * @param start
+     * @param end
+     */
+    private calculateMaxLeftSlope(points: PointF2D[], start: PointF2D, end: PointF2D): number {
+        let slope: number = -Number.MAX_VALUE;
+        const x: number = start.x;
+        const y: number = start.y;
+
+        for (let i: number = 0; i < points.length; i++) {
+            if (Math.abs(points[i].y - Number.MAX_VALUE) < 0.0001 || Math.abs(points[i].y - (-Number.MAX_VALUE)) < 0.0001) {
+                continue;
+            }
+            slope = Math.max(slope, (points[i].y - y) / (points[i].x - x));
+        }
+
+        // in case all Points don't have a meaningful value or the slope between Start- and EndPoint is just bigger
+        slope = Math.max(slope, Math.abs(end.y - y) / (end.x - x));
+
+        return slope;
+    }
+
+    /**
+     * This method calculates the maximum slope between EndPoint and BetweenPoints.
+     * @param points
+     * @param start
+     * @param end
+     */
+    private calculateMaxRightSlope(points: PointF2D[], start: PointF2D, end: PointF2D): number {
+        let slope: number = Number.MAX_VALUE;
+        const x: number = end.x;
+        const y: number = end.y;
+
+        for (let i: number = 0; i < points.length; i++) {
+            if (Math.abs(points[i].y - Number.MAX_VALUE) < 0.0001 || Math.abs(points[i].y - (-Number.MAX_VALUE)) < 0.0001) {
+                continue;
+            }
+            slope = Math.min(slope, (y - points[i].y) / (x - points[i].x));
+        }
+
+        // in case no Point has a meaningful value or the slope between Start- and EndPoint is just smaller
+        slope = Math.min(slope, (y - start.y) / (x - start.x));
+
+        return slope;
+    }
+
+    /**
+     * This method returns the maximum (meaningful) points.Y.
+     * @param points
+     */
+    private getPointListMaxY(points: PointF2D[]): number {
+        let max: number = -Number.MAX_VALUE;
+
+        for (let idx: number = 0, len: number = points.length; idx < len; ++idx) {
+            const point: PointF2D = points[idx];
+            if (Math.abs(point.y - (-Number.MAX_VALUE)) < 0.0001 || Math.abs(point.y - Number.MAX_VALUE) < 0.0001) {
+                continue;
+            }
+            max = Math.max(max, point.y);
+        }
+
+        return max;
+    }
+
+    /**
+     * This method calculates the translated and rotated PointsList (case above).
+     * @param points
+     * @param startX
+     * @param startY
+     * @param rotationMatrix
+     */
+    private calculateTranslatedAndRotatedPointListAbove(points: PointF2D[], startX: number, startY: number, rotationMatrix: Matrix2D): PointF2D[] {
+        const transformedPoints: PointF2D[] = [];
+        for (let i: number = 0; i < points.length; i++) {
+            if (Math.abs(points[i].y - Number.MAX_VALUE) < 0.0001 || Math.abs(points[i].y - (-Number.MAX_VALUE)) < 0.0001) {
+                continue;
+            }
+
+            let point: PointF2D = new PointF2D(points[i].x - startX, -(points[i].y - startY));
+            point = rotationMatrix.vectorMultiplication(point);
+            transformedPoints.push(point);
+        }
+
+        return transformedPoints;
+    }
+
+    /**
+     * This method calculates the translated and rotated PointsList (case below).
+     * @param points
+     * @param startX
+     * @param startY
+     * @param rotationMatrix
+     */
+    private calculateTranslatedAndRotatedPointListBelow(points: PointF2D[], startX: number, startY: number, rotationMatrix: Matrix2D): PointF2D[] {
+        const transformedPoints: PointF2D[] = [];
+        for (let i: number = 0; i < points.length; i++) {
+            if (Math.abs(points[i].y - Number.MAX_VALUE) < 0.0001 || Math.abs(points[i].y - (-Number.MAX_VALUE)) < 0.0001) {
+                continue;
+            }
+            let point: PointF2D = new PointF2D(points[i].x - startX, points[i].y - startY);
+            point = rotationMatrix.vectorMultiplication(point);
+            transformedPoints.push(point);
+        }
+
+        return transformedPoints;
+    }
+
+    /**
+     * This method calculates the HeightWidthRatio between the MaxYpoint (from the points between StartPoint and EndPoint)
+     * and the X-distance from StartPoint to EndPoint.
+     * @param endX
+     * @param points
+     */
+    private calculateHeightWidthRatio(endX: number, points: PointF2D[]): number {
+        if (points.length === 0) {
+            return 0;
+        }
+
+        // in case of negative points
+        const max: number = Math.max(0, this.getPointListMaxY(points));
+
+        return max / endX;
+    }
+
+    /**
+     * This method calculates the 2 ControlPoints of the SlurCurve.
+     * @param endX
+     * @param leftAngle
+     * @param rightAngle
+     * @param points
+     */
+    private calculateControlPoints(endX: number,
+                                   leftAngle: number, rightAngle: number, points: PointF2D[]): { leftControlPoint: PointF2D, rightControlPoint: PointF2D } {
+        // calculate HeightWidthRatio between the MaxYpoint (from the points between StartPoint and EndPoint)
+            // and the X-distance from StartPoint to EndPoint
+            // use this HeightWidthRatio to get a "normalized" Factor (based on tested parameters)
+            // this Factor denotes the Length of the TangentLine of the Curve (a proportion of the X-distance from StartPoint to EndPoint)
+            // finally from this Length and the calculated Angles we get the coordinates of the Control Points
+        const heightWidthRatio: number = this.calculateHeightWidthRatio(endX, points);
+        const factor: number = GraphicalSlur.k * heightWidthRatio + GraphicalSlur.d;
+
+        const relativeLength: number = endX * factor;
+        const leftControlPoint: PointF2D = new PointF2D();
+        leftControlPoint.x = relativeLength * Math.cos(leftAngle * GraphicalSlur.degreesToRadiansFactor);
+        leftControlPoint.y = relativeLength * Math.sin(leftAngle * GraphicalSlur.degreesToRadiansFactor);
+
+        const rightControlPoint: PointF2D = new PointF2D();
+        rightControlPoint.x = endX - (relativeLength * Math.cos(rightAngle * GraphicalSlur.degreesToRadiansFactor));
+        rightControlPoint.y = -(relativeLength * Math.sin(rightAngle * GraphicalSlur.degreesToRadiansFactor));
+        return {leftControlPoint, rightControlPoint};
+    }
+
+    /**
+     * This method calculates the angles for the Curve's Tangent Lines.
+     * @param leftAngle
+     * @param rightAngle
+     * @param leftLineSlope
+     * @param rightLineSlope
+     * @param maxAngle
+     */
+    private calculateAngles(leftAngle: number, rightAngle: number, leftLineSlope: number, rightLineSlope: number, maxAngle: number): void {
+        // calculate Angles from the calculated Slopes, adding also a given angle
+        const angle: number = 20;
+
+        let calculatedLeftAngle: number = Math.atan(leftLineSlope) / GraphicalSlur.degreesToRadiansFactor;
+        if (leftLineSlope > 0) {
+            calculatedLeftAngle += angle;
+        } else {
+            calculatedLeftAngle -= angle;
+        }
+
+        let calculatedRightAngle: number = Math.atan(rightLineSlope) / GraphicalSlur.degreesToRadiansFactor;
+        if (rightLineSlope < 0) {
+            calculatedRightAngle -= angle;
+        } else {
+            calculatedRightAngle += angle;
+        }
+
+        // +/- 80 is the max/min allowed Angle
+        leftAngle = Math.min(Math.max(leftAngle, calculatedLeftAngle), maxAngle);
+        rightAngle = Math.max(Math.min(rightAngle, calculatedRightAngle), -maxAngle);
+    }
+
+    private static degreesToRadiansFactor: number = Math.PI / 180;
+    private static k: number = 0.9;
+    private static d: number = 0.2;
+}

+ 21 - 0
src/MusicalScore/Graphical/GraphicalSlurSorter.ts

@@ -0,0 +1,21 @@
+import { Fraction } from "../../Common/DataObjects/Fraction";
+import { GraphicalSlur } from "./GraphicalSlur";
+
+export interface GraphicalSlurSorterKeyValuePair {
+    key: Fraction;
+    value: GraphicalSlur;
+}
+
+export class GraphicalSlurSorter {
+    public Compare (x: GraphicalSlurSorterKeyValuePair, y: GraphicalSlurSorterKeyValuePair ): number {
+        if (x.key > y.key) {
+            return 1;
+        }
+
+        if (y.key > x.key) {
+            return -1;
+        }
+
+        return 0;
+    }
+}

+ 9 - 0
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -109,6 +109,9 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      */
     public findEndTieGraphicalNoteFromNoteWithStartingSlur(tieNote: Note, slur: Slur): GraphicalNote {
+        if (tieNote === undefined) {
+            return undefined;
+        }
         for (const gve of this.graphicalVoiceEntries) {
             if (gve.parentVoiceEntry !== tieNote.ParentVoiceEntry) {
                 continue;
@@ -124,6 +127,9 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
 
     public findGraphicalNoteFromGraceNote(graceNote: Note): GraphicalNote {
+        if (graceNote === undefined) {
+            return undefined;
+        }
         for (const gve of this.graphicalVoiceEntries) {
             if (gve.parentVoiceEntry !== graceNote.ParentVoiceEntry) {
                 continue;
@@ -138,6 +144,9 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
 
     public findGraphicalNoteFromNote(note: Note): GraphicalNote {
+        if (note === undefined) {
+            return undefined;
+        }
         for (const gve of this.graphicalVoiceEntries) {
             if (gve.parentVoiceEntry !== note.ParentVoiceEntry) {
                 continue;

+ 8 - 8
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -146,26 +146,26 @@ export abstract class MusicSheetDrawer {
         const screenPosition: PointF2D = this.applyScreenTransformation(graphicalLabel.PositionAndShape.AbsolutePosition);
         const heightInPixel: number = this.calculatePixelDistance(label.fontHeight);
         const widthInPixel: number = heightInPixel * this.textMeasurer.computeTextWidthToHeightRatio(label.text, label.font, label.fontStyle);
-        const bitmapWidth: number = <number>Math.ceil(widthInPixel);
-        const bitmapHeight: number = <number>Math.ceil(heightInPixel * 1.2);
+        const bitmapWidth: number = Math.ceil(widthInPixel);
+        const bitmapHeight: number = Math.ceil(heightInPixel * 1.2);
         switch (label.textAlignment) {
             case TextAlignment.LeftTop:
                 break;
             case TextAlignment.LeftCenter:
-                screenPosition.y -= <number>bitmapHeight / 2;
+                screenPosition.y -= bitmapHeight / 2;
                 break;
             case TextAlignment.LeftBottom:
                 screenPosition.y -= bitmapHeight;
                 break;
             case TextAlignment.CenterTop:
-                screenPosition.x -= <number>bitmapWidth / 2;
+                screenPosition.x -= bitmapWidth / 2;
                 break;
             case TextAlignment.CenterCenter:
-                screenPosition.x -= <number>bitmapWidth / 2;
-                screenPosition.y -= <number>bitmapHeight / 2;
+                screenPosition.x -= bitmapWidth / 2;
+                screenPosition.y -= bitmapHeight / 2;
                 break;
             case TextAlignment.CenterBottom:
-                screenPosition.x -= <number>bitmapWidth / 2;
+                screenPosition.x -= bitmapWidth / 2;
                 screenPosition.y -= bitmapHeight;
                 break;
             case TextAlignment.RightTop:
@@ -173,7 +173,7 @@ export abstract class MusicSheetDrawer {
                 break;
             case TextAlignment.RightCenter:
                 screenPosition.x -= bitmapWidth;
-                screenPosition.y -= <number>bitmapHeight / 2;
+                screenPosition.y -= bitmapHeight / 2;
                 break;
             case TextAlignment.RightBottom:
                 screenPosition.x -= bitmapWidth;

+ 17 - 0
src/MusicalScore/Graphical/StaffLine.ts

@@ -11,6 +11,7 @@ import {PointF2D} from "../../Common/DataObjects/PointF2D";
 import {GraphicalLabel} from "./GraphicalLabel";
 import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
 import { GraphicalOctaveShift } from "./GraphicalOctaveShift";
+import { GraphicalSlur } from "./GraphicalSlur";
 
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
@@ -27,6 +28,9 @@ export abstract class StaffLine extends GraphicalObject {
     protected lyricsDashes: GraphicalLabel[] = [];
     protected abstractExpressions: GraphicalObject[] = [];
 
+    // For displaying Slurs
+    protected graphicalSlurs: GraphicalSlur[] = [];
+
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super();
         this.parentMusicSystem = parentSystem;
@@ -116,6 +120,19 @@ export abstract class StaffLine extends GraphicalObject {
         this.octaveShifts = value;
     }
 
+    // get all Graphical Slurs of a staffline
+    public get GraphicalSlurs(): GraphicalSlur[] {
+        return this.graphicalSlurs;
+    }
+
+    /**
+     * Add a given Graphical Slur to the staffline
+     * @param gSlur
+     */
+    public addSlurToStaffline(gSlur: GraphicalSlur): void {
+        this.graphicalSlurs.push(gSlur);
+    }
+
     public addActivitySymbolClickArea(): void {
         const activitySymbol: StaffLineActivitySymbol = new StaffLineActivitySymbol(this);
         const staffLinePsi: BoundingBox = this.PositionAndShape;

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

@@ -84,6 +84,32 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.canvasRenderingCtx.strokeStyle = oldStyle;
     }
 
+    public renderCurve(points: PointF2D[]): void {
+        this.ctx.beginPath();
+        this.ctx.moveTo(points[0].x, points[0].y);
+        this.ctx.bezierCurveTo(
+            points[1].x,
+            points[1].y,
+            points[2].x,
+            points[2].y,
+            points[3].x,
+            points[3].y
+            );
+        this.ctx.lineTo(points[7].x, points[7].y);
+        this.ctx.bezierCurveTo(
+            points[6].x,
+            points[6].y,
+            points[5].x,
+            points[5].y,
+            points[4].x,
+            points[4].y
+            );
+        this.ctx.lineTo(points[0].x, points[0].y);
+        //this.ctx.stroke();
+        this.ctx.closePath();
+        this.ctx.fill();
+    }
+
     private ctx: Vex.Flow.CanvasContext;
     private canvasRenderingCtx: CanvasRenderingContext2D;
 }

+ 27 - 1
src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts

@@ -9,6 +9,8 @@ import {PointF2D} from "../../../Common/DataObjects/PointF2D";
 
 export class SvgVexFlowBackend extends VexFlowBackend {
 
+    private ctx: Vex.Flow.SVGContext;
+
     public getBackendType(): number {
         return Vex.Flow.Renderer.Backends.SVG;
     }
@@ -84,5 +86,29 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         this.ctx.restore();
     }
 
-    private ctx: Vex.Flow.SVGContext;
+    public renderCurve(points: PointF2D[]): void {
+        this.ctx.beginPath();
+        this.ctx.moveTo(points[0].x, points[0].y);
+        this.ctx.bezierCurveTo(
+            points[1].x,
+            points[1].y,
+            points[2].x,
+            points[2].y,
+            points[3].x,
+            points[3].y
+            );
+        this.ctx.lineTo(points[7].x, points[7].y);
+        this.ctx.bezierCurveTo(
+            points[6].x,
+            points[6].y,
+            points[5].x,
+            points[5].y,
+            points[4].x,
+            points[4].y
+            );
+        this.ctx.lineTo(points[0].x, points[0].y);
+        //this.ctx.stroke();
+        this.ctx.closePath();
+        this.ctx.fill();
+    }
 }

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

@@ -56,6 +56,8 @@ export abstract class VexFlowBackend {
 
   public abstract renderLine(start: PointF2D, stop: PointF2D, color: string, lineWidth: number): void;
 
+  public abstract renderCurve(points: PointF2D[]): void;
+
   public abstract getBackendType(): number;
 
   protected renderer: Vex.Flow.Renderer;

+ 21 - 10
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -20,8 +20,8 @@ import * as log from "loglevel";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {Tuplet} from "../../VoiceData/Tuplet";
 import { RepetitionInstructionEnum, RepetitionInstruction, AlignmentType } from "../../VoiceData/Instructions/RepetitionInstruction";
-import { SystemLinePosition } from "../SystemLinePosition";
-import { StemDirectionType } from "../../VoiceData/VoiceEntry";
+import {SystemLinePosition} from "../SystemLinePosition";
+import {StemDirectionType} from "../../VoiceData/VoiceEntry";
 import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
 import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
@@ -209,7 +209,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
      */
     public addMeasureNumber(): void {
         const text: string = this.MeasureNumber.toString();
-        const position: number = Vex.Flow.StaveModifier.Position.ABOVE;
+        const position: number = StavePositionEnum.ABOVE;  //Vex.Flow.StaveModifier.Position.ABOVE;
         const options: any = {
             justification: 1,
             shift_x: 0,
@@ -381,7 +381,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
            for (const gve of gse.graphicalVoiceEntries) {
                 if (voices.indexOf(gve.parentVoiceEntry.ParentVoice) === -1) {
                     voices.push(gve.parentVoiceEntry.ParentVoice);
-    }
+                }
             }
         }
         return voices;
@@ -648,10 +648,10 @@ export class VexFlowMeasure extends GraphicalMeasure {
 
             // add a vexFlow voice for this voice:
             this.vfVoices[voice.VoiceId] = new Vex.Flow.Voice({
-                            beat_value: this.parentSourceMeasure.Duration.Denominator,
-                            num_beats: this.parentSourceMeasure.Duration.Numerator,
-                            resolution: Vex.Flow.RESOLUTION,
-                        }).setMode(Vex.Flow.Voice.Mode.SOFT);
+                        beat_value: this.parentSourceMeasure.Duration.Denominator,
+                        num_beats: this.parentSourceMeasure.Duration.Numerator,
+                        resolution: Vex.Flow.RESOLUTION,
+                    }).setMode(Vex.Flow.Voice.Mode.SOFT);
 
             const restFilledEntries: GraphicalVoiceEntry[] =  this.getRestFilledVexFlowStaveNotesPerVoice(voice);
             // create vex flow voices and add tickables to it:
@@ -747,9 +747,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
         let vfEndInstructionsWidth: number = 0;
         const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();
         for (const mod of modifiers) {
-            if (mod.getPosition() === Vex.Flow.StaveModifier.Position.BEGIN) {
+            if (mod.getPosition() === StavePositionEnum.BEGIN) {  //Vex.Flow.StaveModifier.Position.BEGIN) {
                 vfBeginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
-            } else if (mod.getPosition() === Vex.Flow.StaveModifier.Position.END) {
+            } else if (mod.getPosition() === StavePositionEnum.END) { //Vex.Flow.StaveModifier.Position.END) {
                 vfEndInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
             }
         }
@@ -758,3 +758,14 @@ export class VexFlowMeasure extends GraphicalMeasure {
         this.endInstructionsWidth = vfEndInstructionsWidth / unitInPixels;
     }
 }
+
+// Gives the position of the Stave - replaces the function get Position() in the description of class StaveModifier in vexflow.d.ts
+// The latter gave an error because function cannot be defined in the class descriptions in vexflow.d.ts
+export enum StavePositionEnum {
+    LEFT = 1,
+    RIGHT = 2,
+    ABOVE = 3,
+    BELOW = 4,
+    BEGIN = 5,
+    END = 6
+}

+ 283 - 62
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -33,6 +33,12 @@ 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";
+// import { VexFlowStaffLine } from "./VexFlowStaffLine";
+// import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
+*/
 import { EngravingRules } from "../EngravingRules";
 import { InstantaneousDynamicExpression } from "../../VoiceData/Expressions/InstantaneousDynamicExpression";
 import { PointF2D } from "../../../Common/DataObjects/PointF2D";
@@ -40,6 +46,7 @@ import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneo
 import { SkyBottomLineCalculator } from "../SkyBottomLineCalculator";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { Staff } from "../../VoiceData/Staff";
+import { GraphicalSlur } from "../GraphicalSlur";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 
@@ -58,7 +65,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     }
   }
 
-  protected formatMeasures(): void {
+    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
@@ -129,18 +136,18 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             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);
               // 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;
-          }
+            }
         }
     }
 
@@ -148,7 +155,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       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;
@@ -188,9 +195,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           if (lyricsEntry.ParentLyricWord) {
             if (lyricsEntry.GetLyricsEntry.SyllableIndex > 0) { // syllables after first
               // give a little more spacing for dash between syllables
-              minLyricsSpacing = EngravingRules.Rules.BetweenSyllabelMinimumDistance;
-            }
+            minLyricsSpacing = EngravingRules.Rules.BetweenSyllabelMinimumDistance;
           }
+        }
 
           const lyricsBbox: BoundingBox = lyricsEntry.GraphicalLabel.PositionAndShape;
           const lyricsLabelHalfWidth: number = lyricsBbox.Size.width / 2;
@@ -200,13 +207,13 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
           if (lastLyricEntryDict[j] !== undefined) {
             if (lastLyricEntryDict[j].extend) {
               // TODO handle extend of last entry (extend is stored in lyrics entry of preceding syllable)
-            }
+        }
 
             const spaceNeededByLyrics: number =
               lastLyricEntryDict[j].labelHalfWidth + lyricsLabelHalfWidth + minLyricsSpacing;
 
             const staffEntrySpacing: number = staffEntryXPosition - lastLyricEntryDict[j].staffEntryXPosition;
-            // get factor of how much we need to stretch the measure to space the current lyric with the last one
+        // get factor of how much we need to stretch the measure to space the current lyric with the last one
             const elongationFactorMeasureWidthForCurrentLabels: number = spaceNeededByLyrics / staffEntrySpacing;
             elongationFactorMeasureWidth = Math.max(elongationFactorMeasureWidth, elongationFactorMeasureWidthForCurrentLabels);
           }
@@ -226,9 +233,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             staffEntryXPosition: staffEntryXPosition,
             text: lyricsEntry.GetLyricsEntry.Text,
           };
-        }
       }
     }
+    }
     return oldMinimumStaffEntriesWidth * elongationFactorMeasureWidth;
   }
 
@@ -277,12 +284,12 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     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.
+          // 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.
@@ -475,13 +482,13 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     }
 }
 
-  /**
-   * 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;
@@ -495,7 +502,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     if (octaveShift.ParentEndMultiExpression !== undefined) {
         endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
                                                                                            staffIndex);
-    }
+  }
     let startMeasure: GraphicalMeasure = undefined;
     if (octaveShift.ParentEndMultiExpression !== undefined) {
       startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentStartMultiExpression.SourceMeasureParent,
@@ -589,48 +596,48 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
   }
 
-    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);
+  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);
+              }
+
+              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;
+
+                  if (graphicalLyricWord.isFilled()) {
+                      lyricWords.splice(index, 1);
+                      this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
+                  }
+              }
+          }
+      });
   }
 
-                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;
-
-                    if (graphicalLyricWord.isFilled()) {
-                        lyricWords.splice(index, 1);
-                        this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
-                    }
-                }
-            }
-        });
-    }
-
   protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
     return;
   }
@@ -671,4 +678,218 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
   protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
     (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
   }
+
+  /**
+   * Find the Index of the item of the array of all VexFlow Slurs that holds a specified slur
+   * @param gSlurs
+   * @param slur
+   */
+  public findIndexGraphicalSlurFromSlur(gSlurs: GraphicalSlur[], slur: Slur): number {
+    for (let slurIndex: number = 0; slurIndex < gSlurs.length; slurIndex++) {
+        if (gSlurs[slurIndex].slur === slur) {
+            return slurIndex;
+        }
+    }
+    return -1;
+  }
+  /* VexFlow Version - for later use
+  public findIndexVFSlurFromSlur(vfSlurs: VexFlowSlur[], slur: Slur): number {
+        for (let slurIndex: number = 0; slurIndex < vfSlurs.length; slurIndex++) {
+            if (vfSlurs[slurIndex].vfSlur === slur) {
+                return slurIndex;
+            }
+        }
+  }
+  */
+
+  // Generate all Graphical Slurs and attach them to the staffline
+  protected calculateSlurs(): void {
+    const openSlursDict: { [staffId: number]: GraphicalSlur[]; } = {};
+    for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
+      openSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
+    }
+
+    /* VexFlow Version - for later use
+    // Generate an empty dictonary to index an array of VexFlowSlur classes
+    const vfOpenSlursDict: { [staffId: number]: VexFlowSlur[]; } = {}; //VexFlowSlur[]; } = {};
+    // use first SourceMeasure to get all graphical measures to know how many staves are currently visible in this musicsheet
+    // foreach stave: create an empty array. It can later hold open slurs.
+    // Measure how many staves are visible and reserve space for them.
+    for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
+        vfOpenSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
+    }
+    */
+
+    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;
+                }
+
+                /* 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);
+                                        }
+                                        */
+                                    }
+                                }
+                            }
+                        }
+                        // 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) {
+                for (const gSlur of staffLine.GraphicalSlurs) {
+                    // crossed slurs will be handled later:
+                    if (gSlur.slur.isCrossed()) {
+                        continue;
+                    }
+                    gSlur.calculateCurve(this.rules);
+                }
+            }
+        }
+    }
+  }
 }

+ 63 - 9
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -12,11 +12,14 @@ 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 { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
+import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
+import { VexFlowStaffLine } from "./VexFlowStaffLine";
 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 log = require("loglevel");
@@ -74,6 +77,57 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         return unitDistance * unitInPixels;
     }
 
+    protected drawStaffLine(staffLine: StaffLine): void {
+        super.drawStaffLine(staffLine);
+        const  absolutePos: PointF2D = staffLine.PositionAndShape.AbsolutePosition;
+        this.drawSlurs(staffLine as VexFlowStaffLine, absolutePos);
+    }
+
+    private drawSlurs(vfstaffLine: VexFlowStaffLine, absolutePos: PointF2D): void {
+        for (const graphicalSlur of vfstaffLine.GraphicalSlurs) {
+            // don't draw crossed slurs, as their curve calculation is not implemented yet:
+            if (graphicalSlur.slur.isCrossed()) {
+                continue;
+            }
+            this.drawSlur(graphicalSlur, absolutePos);
+        }
+    }
+
+    private drawSlur(graphicalSlur: GraphicalSlur, abs: PointF2D): void {
+        const curvePointsInPixels: PointF2D[] = [];
+        // 1) create inner or original curve:
+        const p1: PointF2D = new PointF2D(graphicalSlur.bezierStartPt.x + abs.x, graphicalSlur.bezierStartPt.y + abs.y);
+        const p2: PointF2D = new PointF2D(graphicalSlur.bezierStartControlPt.x + abs.x, graphicalSlur.bezierStartControlPt.y + abs.y);
+        const p3: PointF2D = new PointF2D(graphicalSlur.bezierEndControlPt.x + abs.x, graphicalSlur.bezierEndControlPt.y + abs.y);
+        const p4: PointF2D = new PointF2D(graphicalSlur.bezierEndPt.x + abs.x, graphicalSlur.bezierEndPt.y + abs.y);
+
+        // put screen transformed points into array
+        curvePointsInPixels.push(this.applyScreenTransformation(p1));
+        curvePointsInPixels.push(this.applyScreenTransformation(p2));
+        curvePointsInPixels.push(this.applyScreenTransformation(p3));
+        curvePointsInPixels.push(this.applyScreenTransformation(p4));
+
+        // 2) create second outer curve to create a thickness for the curve:
+        if (graphicalSlur.placement === PlacementEnum.Above) {
+            p1.y -= 0.05;
+            p2.y -= 0.3;
+            p3.y -= 0.3;
+            p4.y -= 0.05;
+        } else {
+            p1.y += 0.05;
+            p2.y += 0.3;
+            p3.y += 0.3;
+            p4.y += 0.05;
+        }
+
+        // put screen transformed points into array
+        curvePointsInPixels.push(this.applyScreenTransformation(p1));
+        curvePointsInPixels.push(this.applyScreenTransformation(p2));
+        curvePointsInPixels.push(this.applyScreenTransformation(p3));
+        curvePointsInPixels.push(this.applyScreenTransformation(p4));
+        this.backend.renderCurve(curvePointsInPixels);
+    }
+
     protected drawMeasure(measure: VexFlowMeasure): void {
         measure.setAbsoluteCoordinates(
             measure.PositionAndShape.AbsolutePosition.x * unitInPixels,
@@ -84,8 +138,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);
@@ -100,20 +154,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
@@ -131,7 +185,7 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             if (line[i] !== currentValue) {
                 indices.push(i);
                 currentValue = line[i];
-            }
+    }
         }
 
         const absolute: PointF2D = startPosition;

+ 75 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowSlur.ts

@@ -0,0 +1,75 @@
+import Vex = require("vexflow");
+import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
+
+export interface ICurveOptions {
+    spacing: number;
+    thickness: number;
+    x_shift: number;
+    y_shift: number;
+    position: CurvePositionEnum;
+    position_end: CurvePositionEnum;
+    invert: boolean;
+    cps: [{ x: number, y: number }, { x: number, y: number }];
+}
+
+export enum CurvePositionEnum {
+    NEAR_HEAD = 1,
+    NEAR_TOP = 2,
+}
+
+export class VexFlowSlur {
+
+    constructor(parentslur: Slur) {
+        this.parentSlur = parentslur;
+    }
+
+    /**
+     * Copy constructor: generate a VexFlowSlur from an existing one
+     */
+    public static createFromVexflowSlur(vfSlur: VexFlowSlur): VexFlowSlur {
+        return new VexFlowSlur(vfSlur.parentSlur);
+    }
+
+    public get vfSlur(): Slur {
+        return this.parentSlur;
+    }
+
+    private parentSlur: Slur;
+
+    public vfStartNote: Vex.Flow.StemmableNote = undefined;
+    public vfEndNote: Vex.Flow.StemmableNote = undefined;
+
+    public vfCurve: Vex.Flow.Curve;
+
+    public curve_Options(): ICurveOptions {
+        return {
+            cps: [{ x: 0, y: 10 }, { x: 0, y: 10 }],
+            invert: false,
+            position: CurvePositionEnum.NEAR_TOP,
+            position_end: CurvePositionEnum.NEAR_TOP,
+            spacing: 2,
+            thickness: 2,
+            x_shift: 0,
+            y_shift: 10
+        };
+    }
+
+    // public createVexFlowCurve(): void {
+    //     if (this.voiceentrySlurStart !== undefined || this.voiceentrySlurEnd !== undefined) {
+    //         this.vfCurve = new Vex.Flow.Curve( (this.voiceentrySlurStart as VexFlowVoiceEntry).vfStaveNote,
+    //                                            (this.voiceentrySlurEnd as VexFlowVoiceEntry).vfStaveNote,
+    //                                            this.curve_Options()
+    //                                         );
+    //     }
+    // }
+    public createVexFlowCurve(): void {
+            this.vfCurve = new Vex.Flow.Curve( this.vfStartNote,
+                                               this.vfEndNote,
+                                               undefined//this.curve_Options()
+                                            );
+    }
+}
+
+
+
+

+ 10 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowStaffLine.ts

@@ -1,9 +1,19 @@
 import {StaffLine} from "../StaffLine";
 import {MusicSystem} from "../MusicSystem";
 import {Staff} from "../../VoiceData/Staff";
+import { VexFlowSlur } from "./VexFlowSlur";
 
 export class VexFlowStaffLine extends StaffLine {
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super(parentSystem, parentStaff);
     }
+
+    protected slursInVFStaffLine: VexFlowSlur[] = [];
+
+    public get SlursInVFStaffLine(): VexFlowSlur[] {
+        return this.slursInVFStaffLine;
+    }
+    public addVFSlurToVFStaffline(vfSlur: VexFlowSlur): void {
+        this.slursInVFStaffLine.push(vfSlur);
+    }
 }

+ 2 - 2
src/MusicalScore/MusicSheet.ts

@@ -457,11 +457,11 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
     //        let oldValue: Object = 0;
     //        if (parameter === undefined) { // FIXME MusicSheetParameters.MusicSheetTranspose) {
     //            oldValue = this.Transpose;
-    //            this.Transpose = <number>value;
+    //            this.Transpose = value;
     //        }
     //        if (parameter === undefined) { // FIXME MusicSheetParameters.StartTempoInBPM) {
     //            oldValue = this.UserStartTempoInBPM;
-    //            this.UserStartTempoInBPM = <number>value;
+    //            this.UserStartTempoInBPM = value;
     //        }
     //        if (parameter === undefined) { // FIXME MusicSheetParameters.HighlightErrors) {
     //            oldValue = value;

+ 8 - 7
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -22,6 +22,7 @@ import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
 import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
 import { RepetitionInstructionReader } from "./MusicSymbolModules/RepetitionInstructionReader";
+import { SlurReader } from "./MusicSymbolModules/SlurReader";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 // FIXME: The following classes are missing
@@ -61,13 +62,13 @@ export class InstrumentReader {
         this.activeClefsHaveBeenInitialized[i] = false;
       }
       this.createExpressionGenerators(instrument.Staves.length);
-      // (*) this.slurReader = MusicSymbolModuleFactory.createSlurReader(this.musicSheet);
+      this.slurReader = new SlurReader(this.musicSheet);
   }
 
   private repetitionInstructionReader: RepetitionInstructionReader;
   private xmlMeasureList: IXmlElement[];
   private musicSheet: MusicSheet;
-  private slurReader: any; // (*) SlurReader;
+  private slurReader: SlurReader;
   private instrument: Instrument;
   private voiceGeneratorsDict: { [n: number]: VoiceGenerator; } = {};
   private staffMainVoiceGeneratorDict: { [staffId: number]: VoiceGenerator } = {};
@@ -271,7 +272,7 @@ export class InstrumentReader {
              expressionReader.read(
                xmlNode, this.currentMeasure, previousFraction
              );
-            }
+          }
           }
           lastNoteWasGrace = isGraceNote;
         } else if (xmlNode.name === "attributes") {
@@ -329,15 +330,15 @@ export class InstrumentReader {
           }
         } else if (xmlNode.name === "direction") {
           const directionTypeNode: IXmlElement = xmlNode.element("direction-type");
-          //(*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
+          // (*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
           let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
           if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
             relativePositionInMeasure /= this.activeRhythm.Rhythm.RealValue;
           }
           let handeled: boolean = false;
           if (this.repetitionInstructionReader !== undefined) {
-            handeled = this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode,
-                                                                                                       relativePositionInMeasure);
+            handeled = this.repetitionInstructionReader.handleRepetitionInstructionsFromWordsOrSymbols( directionTypeNode,
+                                                                                                        relativePositionInMeasure);
           }
           if (!handeled) {
            let expressionReader: ExpressionReader = this.expressionReaders[0];
@@ -393,7 +394,7 @@ export class InstrumentReader {
          const reader: ExpressionReader = this.expressionReaders[i];
          if (reader !== undefined) {
            reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
-         }
+      }
         }
       }
     } catch (e) {

+ 2 - 2
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -744,7 +744,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                                             subInstrument.fixedKey = Math.max(0, parseInt(instrumentElement.value, 10));
                                         } else if (instrumentElement.name === "volume") {
                                             try {
-                                                const result: number = <number>parseFloat(instrumentElement.value);
+                                                const result: number = parseFloat(instrumentElement.value);
                                                 subInstrument.volume = result / 127.0;
                                             } catch (ex) {
                                                 log.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
@@ -752,7 +752,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
 
                                         } else if (instrumentElement.name === "pan") {
                                             try {
-                                                const result: number = <number>parseFloat(instrumentElement.value);
+                                                const result: number = parseFloat(instrumentElement.value);
                                                 subInstrument.pan = result / 64.0;
                                             } catch (ex) {
                                                 log.debug("ExpressionReader.readExpressionParameters", "read pan", ex);

+ 59 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/SlurReader.ts

@@ -0,0 +1,59 @@
+import { MusicSheet } from "../../MusicSheet";
+import { IXmlElement, IXmlAttribute } from "../../../Common/FileIO/Xml";
+import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
+import { Note } from "../../VoiceData/Note";
+import * as log from "loglevel";
+import { ITextTranslation } from "../../Interfaces/ITextTranslation";
+
+export class SlurReader {
+    private musicSheet: MusicSheet;
+    private openSlurDict: { [_: number]: Slur; } = {};
+    constructor(musicSheet: MusicSheet) {
+        this.musicSheet = musicSheet;
+    }
+    public addSlur(slurNodes: IXmlElement[], currentNote: Note): void {
+        try {
+            if (slurNodes !== undefined) {
+                for (const slurNode of slurNodes) {
+                    if (slurNode.attributes().length > 0) {
+                        const type: string = slurNode.attribute("type").value;
+                        let slurNumber: number = 1;
+                        try {
+                            const slurNumberAttribute: IXmlAttribute = slurNode.attribute("number");
+                            if (slurNumberAttribute !== undefined) {
+                                slurNumber = parseInt(slurNode.attribute("number").value, 10);
+                            }
+                        } catch (ex) {
+                            log.debug("VoiceGenerator.addSlur number: ", ex);
+                        }
+
+                        if (type === "start") {
+                            let slur: Slur = this.openSlurDict[slurNumber];
+                            if (slur === undefined) {
+                                slur = new Slur();
+                                this.openSlurDict[slurNumber] = slur;
+                            }
+                            slur.StartNote = currentNote;
+                        } else if (type === "stop") {
+                            const slur: Slur = this.openSlurDict[slurNumber];
+                            if (slur !== undefined) {
+                                slur.EndNote = currentNote;
+                                // check if not already a slur with same notes has been given:
+                                if (!currentNote.checkForDoubleSlur(slur)) {
+                                    // if not, link slur to notes:
+                                    currentNote.NoteSlurs.push(slur);
+                                    const slurStartNote: Note = slur.StartNote;
+                                    slurStartNote.NoteSlurs.push(slur);
+                                }
+                                delete this.openSlurDict[slurNumber];
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (err) {
+            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/SlurError", "Error while reading slur.");
+            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+        }
+    }
+}

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

@@ -24,16 +24,12 @@ import { Pitch } from "../../Common/DataObjects/Pitch";
 import { IXmlAttribute } from "../../Common/FileIO/Xml";
 import { CollectionUtil } from "../../Util/CollectionUtil";
 import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader";
-
-/**
- * To be implemented
- */
-export type SlurReader = any;
+import { SlurReader } from "./MusicSymbolModules/SlurReader";
 
 export class VoiceGenerator {
   constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
     this.musicSheet = instrument.GetMusicSheet;
-    // this.slurReader = slurReader;
+    this.slurReader = slurReader;
     if (mainVoice !== undefined) {
       this.voice = new LinkedVoice(instrument, voiceId, mainVoice);
     } else {
@@ -44,7 +40,7 @@ export class VoiceGenerator {
     this.articulationReader = new ArticulationReader();
   }
 
-  // private slurReader: SlurReader;
+  private slurReader: SlurReader;
   private lyricsReader: LyricsReader;
   private articulationReader: ArticulationReader;
   private musicSheet: MusicSheet;
@@ -114,27 +110,30 @@ export class VoiceGenerator {
       this.currentNote = restNote
         ? this.addRestNote(noteDuration)
         : this.addSingleNote(noteNode, noteDuration, chord, guitarPro);
-
-      if (this.lyricsReader !== undefined && noteNode.elements("lyric") !== undefined) {
-        this.lyricsReader.addLyricEntry(noteNode.elements("lyric"), this.currentVoiceEntry);
+      // read lyrics
+      const lyricElements: IXmlElement[] = noteNode.elements("lyric");
+      if (this.lyricsReader !== undefined && lyricElements !== undefined) {
+        this.lyricsReader.addLyricEntry(lyricElements, this.currentVoiceEntry);
         this.voice.Parent.HasLyrics = true;
       }
       let hasTupletCommand: boolean = false;
       const notationNode: IXmlElement = noteNode.element("notations");
       if (notationNode !== undefined) {
-        // let articNode: IXmlElement = undefined;
-        // (*)
+        // read articulations
         if (this.articulationReader !== undefined) {
           this.readArticulations(notationNode, this.currentVoiceEntry);
         }
-        //let slurNodes: IXmlElement[] = undefined;
-        // (*)
-        //if (this.slurReader !== undefined && (slurNodes = notationNode.elements("slur")))
-        //    this.slurReader.addSlur(slurNodes, this.currentNote);
-        // check for Tuplets
-        const tupletNodeList: IXmlElement[] = notationNode.elements("tuplet");
-        if (tupletNodeList.length > 0) {
-          this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
+        // read slurs
+        const slurElements: IXmlElement[] = notationNode.elements("slur");
+        if (this.slurReader !== undefined &&
+            slurElements.length > 0 &&
+            !this.currentNote.ParentVoiceEntry.IsGrace) {
+          this.slurReader.addSlur(slurElements, this.currentNote);
+        }
+        // read Tuplets
+        const tupletElements: IXmlElement[] = notationNode.elements("tuplet");
+        if (tupletElements.length > 0) {
+          this.openTupletNumber = this.addTuplet(noteNode, tupletElements);
           hasTupletCommand = true;
         }
         // check for Arpeggios
@@ -423,26 +422,26 @@ export class VoiceGenerator {
                 this.handleOpenBeam();
               }
               this.openBeam = new Beam();
-          }
+            }
           this.lastBeamTag = currentBeamTag;
         }
         let sameVoiceEntry: boolean = false;
         if (this.openBeam === undefined) {
-          return;
-        }
+            return;
+          }
         for (let idx: number = 0, len: number = this.openBeam.Notes.length; idx < len; ++idx) {
-          const beamNote: Note = this.openBeam.Notes[idx];
-          if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
-            sameVoiceEntry = true;
+            const beamNote: Note = this.openBeam.Notes[idx];
+            if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
+              sameVoiceEntry = true;
+            }
           }
-        }
         if (!sameVoiceEntry) {
-          this.openBeam.addNoteToBeam(note);
-          if (currentBeamTag === "end" && beamNumber === 1) {
-            this.openBeam = undefined;
+            this.openBeam.addNoteToBeam(note);
+            if (currentBeamTag === "end" && beamNumber === 1) {
+              this.openBeam = undefined;
+            }
           }
         }
-      }
     } catch (e) {
       const errorMsg: string = ITextTranslation.translateText(
         "ReaderErrorMessages/BeamError", "Error while reading beam."

+ 1 - 1
src/Util/PSMath.ts

@@ -30,7 +30,7 @@ export class PSMath {
             sumWeigtedValues += values[i] * weight;
             sumWeights += weight;
         }
-        return <number>(sumWeigtedValues / sumWeights);
+        return sumWeigtedValues / sumWeights;
     }
 
 }