Prechádzať zdrojové kódy

Merge branch 'release/0.3.1'

Sebastian Haas 7 rokov pred
rodič
commit
b57494c959
42 zmenil súbory, kde vykonal 784 pridanie a 1007 odobranie
  1. 2 2
      .appveyor.yml
  2. 1 0
      .travis.yml
  3. 19 0
      CHANGELOG.md
  4. 3 1
      demo/index.js
  5. 11 2
      external/vexflow/vexflow.d.ts
  6. 1 1
      package.json
  7. 12 0
      src/Common/DataObjects/Fraction.ts
  8. 0 21
      src/Common/Logging.ts
  9. 4 9
      src/MusicalScore/Graphical/AccidentalCalculator.ts
  10. 2 2
      src/MusicalScore/Graphical/EngravingRules.ts
  11. 10 30
      src/MusicalScore/Graphical/GraphicalMusicSheet.ts
  12. 5 19
      src/MusicalScore/Graphical/GraphicalNote.ts
  13. 60 116
      src/MusicalScore/Graphical/GraphicalStaffEntry.ts
  14. 2 4
      src/MusicalScore/Graphical/GraphicalStaffEntryLink.ts
  15. 22 0
      src/MusicalScore/Graphical/GraphicalVoiceEntry.ts
  16. 93 216
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  17. 4 9
      src/MusicalScore/Graphical/MusicSystemBuilder.ts
  18. 4 7
      src/MusicalScore/Graphical/StaffMeasure.ts
  19. 71 38
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  20. 2 2
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts
  21. 13 16
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts
  22. 158 67
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  23. 24 13
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  24. 14 14
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  25. 11 0
      src/MusicalScore/Graphical/VexFlow/VexFlowVoiceEntry.ts
  26. 7 2
      src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts
  27. 2 2
      src/MusicalScore/MusicParts/MusicPartManagerIterator.ts
  28. 4 4
      src/MusicalScore/MusicSheet.ts
  29. 2 2
      src/MusicalScore/MusicSource/Repetition.ts
  30. 14 14
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  31. 7 7
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  32. 147 253
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  33. 2 2
      src/MusicalScore/SubInstrument.ts
  34. 2 2
      src/MusicalScore/VoiceData/Expressions/InstantaniousDynamicExpression.ts
  35. 7 1
      src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts
  36. 0 54
      src/MusicalScore/VoiceData/Note.ts
  37. 1 1
      src/MusicalScore/VoiceData/SourceMeasure.ts
  38. 15 11
      src/MusicalScore/VoiceData/SourceStaffEntry.ts
  39. 22 60
      src/MusicalScore/VoiceData/Tie.ts
  40. 1 1
      src/MusicalScore/VoiceData/VoiceEntry.ts
  41. 2 1
      src/OpenSheetMusicDisplay/AJAX.ts
  42. 1 1
      webpack.dev.js

+ 2 - 2
.appveyor.yml

@@ -3,8 +3,8 @@ environment:
   matrix:
     - nodejs_version: "6"
     # - nodejs_version: "7"
-    # - nodejs_version: "8" 
-    - nodejs_version: "9"
+    - nodejs_version: "8" 
+    # - nodejs_version: "10"
 platform:
   # - x86
   - x64

+ 1 - 0
.travis.yml

@@ -2,6 +2,7 @@ sudo: false
 language: node_js
 node_js:
 - '6'
+- '8'
 notifications:
   email: false
   slack:

+ 19 - 0
CHANGELOG.md

@@ -1,3 +1,22 @@
+<a name="0.3.1"></a>
+## [0.3.1](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.3.0...0.3.1) (2018-06-25)
+
+
+### Bug Fixes
+
+* Fixed a bug where vexflow does not support having `8va` and the `bass` type ([ad0630d](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/ad0630d))
+* Added fix in repetition comparer (from [#251](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/251)) ([9c7e164](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/9c7e164))
+* Fixed UTF-8 encoding in AJAX loader. Fixes [#252](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/252) [#254](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/254)
+* Fixed ajax loader for mxl using old mimetype. ([ae8c5e1](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/ae8c5e1))
+* Fixed a little bug at the extra instruction measure ([da0ba9d](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/da0ba9d))
+* Fixed tie drawing bug - now all ties of a chord are drawn. ([4021833](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/4021833))
+* Fixed whole notes not being in the middle of a measure, see [#260](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/260)) ([ee2f706](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/ee2f706))
+
+### Features
+* Improved ghostnote positioning and a ghost note converter ([6dc0460](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/6dc0460))
+* Added `GraphicalVoiceEntry` for displaying lyrics ([471ee19](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/471ee19))
+
+
 ## [0.3.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0...0.3.0) (2018-05-03)
 
 

+ 3 - 1
demo/index.js

@@ -9,6 +9,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
     // The available demos
         demos = {
             "Beethoven - An die ferne Geliebte": "Beethoven_AnDieFerneGeliebte.xml",
+            "NinskaBanja_LoosMeasures.xml": "NinskaBanja_LoosMeasures.xml",
+            "NiskaBanja_DoesNotRender": "NiskaBanja_DoesNotRender.xml",
             "M. Clementi - Sonatina Op.36 No.1 Pt.1": "MuzioClementi_SonatinaOpus36No1_Part1.xml",
             "M. Clementi - Sonatina Op.36 No.1 Pt.2": "MuzioClementi_SonatinaOpus36No1_Part2.xml",
             "M. Clementi - Sonatina Op.36 No.3 Pt.1": "MuzioClementi_SonatinaOpus36No3_Part1.xml",
@@ -82,7 +84,6 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         select.onchange = selectOnChange;
 
         // Pre-select default music piece
-        select.value = "MuzioClementi_SonatinaOpus36No1_Part1.xml";
 
         custom.appendChild(document.createTextNode("Custom"));
 
@@ -187,6 +188,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 return openSheetMusicDisplay.render();
             },
             function(e) {
+                console.warn(e.stack);
                 error("Error reading sheet: " + e);
             }
         ).then(

+ 11 - 2
external/vexflow/vexflow.d.ts

@@ -40,6 +40,8 @@ declare namespace Vex {
             public setStave(stave: Stave);
 
             public getBoundingBox(): BoundingBox;
+
+            public getAttribute(arg: string): string;
         }
 
         export class Voice {
@@ -55,9 +57,9 @@ declare namespace Vex {
 
             public setStave(stave: Stave): Voice;
 
-            public addTickables(notes: StaveNote[]): Voice;
+            public addTickables(tickables: Tickable[]): Voice;
 
-            public addTickable(note: StaveNote): Voice;
+            public addTickable(tickable: Tickable): Voice;
 
             public setMode(mode: any): Voice;
 
@@ -74,6 +76,13 @@ declare namespace Vex {
         export class StemmableNote extends Note {
             public getStemDirection(): number;
             public setStemDirection(direction: number): StemmableNote;
+            public x_shift: number;
+            public getAbsoluteX(): number;
+        }
+
+        export class GhostNote extends StemmableNote {
+            constructor(note_struct: any);
+            public setStave(stave): void;
         }
 
         export class StaveNote extends StemmableNote {

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "opensheetmusicdisplay",
-  "version": "0.3.0",
+  "version": "0.3.1",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
   "main": "build/opensheetmusicdisplay.min.js",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",

+ 12 - 0
src/Common/DataObjects/Fraction.ts

@@ -170,6 +170,12 @@ export class Fraction {
   //   this.setRealValue();
   // }
 
+  /**
+   * Adds a Fraction to this Fraction.
+   * Attention: This changes the already existing Fraction, which might be referenced elsewhere!
+   * Use Fraction.plus() for creating a new Fraction object being the sum of two Fractions.
+   * @param fraction the Fraction to add.
+   */
   public Add(fraction: Fraction): void {
     // normally should check if denominator or fraction.denominator is 0 but in our case
     // a zero denominator doesn't make sense
@@ -181,6 +187,12 @@ export class Fraction {
     this.setRealValue();
   }
 
+  /**
+   * Subtracts a Fraction from this Fraction.
+   * Attention: This changes the already existing Fraction, which might be referenced elsewhere!
+   * Use Fraction.minus() for creating a new Fraction object being the difference of two Fractions.
+   * @param fraction the Fraction to subtract.
+   */
   public Sub(fraction: Fraction): void {
     // normally should check if denominator or fraction.denominator is 0 but in our case
     // a zero denominator doesn't make sense

+ 0 - 21
src/Common/Logging.ts

@@ -1,21 +0,0 @@
-/* tslint:disable:no-console */
-
-/**
- * Class for logging messages, mainly for debugging purposes.
- * It should be refactored soon, when an external logging framework
- * will be chosen (probably log4js).
- */
-export class Logging {
-    public static debug(...args: any[]): void {
-        console.debug("[opensheetmusicdisplay] ", args.join(" "));
-    }
-    public static log(...args: any[]): void {
-        console.log("[opensheetmusicdisplay] ", args.join(" "));
-    }
-    public static error(...args: any[]): void {
-        console.error("[opensheetmusicdisplay] ", args.join(" "));
-    }
-    public static warn(...args: any[]): void {
-        console.warn("[opensheetmusicdisplay] ", args.join(" "));
-    }
-}

+ 4 - 9
src/MusicalScore/Graphical/AccidentalCalculator.ts

@@ -1,25 +1,20 @@
-import {IGraphicalSymbolFactory} from "../Interfaces/IGraphicalSymbolFactory";
 import {AccidentalEnum} from "../../Common/DataObjects/Pitch";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {GraphicalNote} from "./GraphicalNote";
 import {Pitch} from "../../Common/DataObjects/Pitch";
 import {NoteEnum} from "../../Common/DataObjects/Pitch";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
+import { MusicSheetCalculator } from "./MusicSheetCalculator";
 
 /**
  * Compute the accidentals for notes according to the current key instruction
  */
 export class AccidentalCalculator {
-    private symbolFactory: IGraphicalSymbolFactory;
     private keySignatureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private currentAlterationsComparedToKeyInstructionList: number[] = [];
     private currentInMeasureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private activeKeyInstruction: KeyInstruction;
 
-    constructor(symbolFactory: IGraphicalSymbolFactory) {
-        this.symbolFactory = symbolFactory;
-    }
-
     public get ActiveKeyInstruction(): KeyInstruction {
         return this.activeKeyInstruction;
     }
@@ -77,7 +72,7 @@ export class AccidentalCalculator {
                 } else {
                     this.currentInMeasureNoteAlterationsDict.remove(pitchKey);
                 }
-                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                MusicSheetCalculator.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
             }
         } else {
             if (pitch.Accidental !== AccidentalEnum.NONE) {
@@ -85,11 +80,11 @@ export class AccidentalCalculator {
                     this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
                 }
                 this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
-                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                MusicSheetCalculator.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
             } else {
                 if (isInCurrentAlterationsToKeyList) {
                     this.currentAlterationsComparedToKeyInstructionList.splice(this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey), 1);
-                    this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                    MusicSheetCalculator.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
                 }
             }
         }

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

@@ -1,6 +1,6 @@
 import {PagePlacementEnum} from "./GraphicalMusicPage";
 //import {MusicSymbol} from "./MusicSymbol";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 export class EngravingRules {
     private static rules: EngravingRules;
@@ -315,7 +315,7 @@ export class EngravingRules {
             //        + 7 * FontInfo.Info.getBoundingBox(MusicSymbol.SHARP).width;
             //}
         } catch (ex) {
-            Logging.log("EngravingRules()", ex);
+            log.info("EngravingRules()", ex);
         }
 
     }

+ 10 - 30
src/MusicalScore/Graphical/GraphicalMusicSheet.ts

@@ -16,9 +16,8 @@ import {Fraction} from "../../Common/DataObjects/Fraction";
 import {GraphicalNote} from "./GraphicalNote";
 import {Instrument} from "../Instrument";
 import {BoundingBox} from "./BoundingBox";
-import {Note} from "../VoiceData/Note";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
@@ -513,7 +512,7 @@ export class GraphicalMusicSheet {
             if (closest === undefined) {
                 closest = note;
             } else {
-                if (note.parentStaffEntry.relInMeasureTimestamp === undefined) {
+                if (note.parentVoiceEntry.parentStaffEntry.relInMeasureTimestamp === undefined) {
                     continue;
                 }
                 const deltaNew: number = this.CalculateDistance(note.PositionAndShape.AbsolutePosition, clickPosition);
@@ -635,7 +634,7 @@ export class GraphicalMusicSheet {
         try {
             return this.GetClickableLabel(positionOnMusicSheet);
         } catch (ex) {
-            Logging.log("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
+            log.info("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
         }
 
         return undefined;
@@ -649,7 +648,7 @@ export class GraphicalMusicSheet {
             }
             return entry.getAbsoluteTimestamp();
         } catch (ex) {
-            Logging.log(
+            log.info(
                 "GraphicalMusicSheet.tryGetTimeStampFromPosition",
                 "positionOnMusicSheet: " + positionOnMusicSheet, ex
             );
@@ -681,7 +680,7 @@ export class GraphicalMusicSheet {
                 }
             }
         } catch (ex) {
-            Logging.log("GraphicalMusicSheet.getStaffEntry", ex);
+            log.info("GraphicalMusicSheet.getStaffEntry", ex);
         }
 
         return staffEntry;
@@ -879,19 +878,6 @@ export class GraphicalMusicSheet {
         return graphicalMeasure.findGraphicalStaffEntryFromTimestamp(sourceStaffEntry.Timestamp);
     }
 
-    public GetGraphicalNoteFromSourceNote(note: Note, containingGse: GraphicalStaffEntry): GraphicalNote {
-        for (let idx: number = 0, len: number = containingGse.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = containingGse.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
-                if (graphicalNote.sourceNote === note) {
-                    return graphicalNote;
-                }
-            }
-        }
-        return undefined;
-    }
-
     private CalculateDistance(pt1: PointF2D, pt2: PointF2D): number {
         const deltaX: number = pt1.x - pt2.x;
         const deltaY: number = pt1.y - pt2.y;
@@ -900,24 +886,18 @@ export class GraphicalMusicSheet {
 
     /**
      * Return the longest StaffEntry duration from a GraphicalVerticalContainer.
-     * @param index
+     * @param index the index of the vertical container
      * @returns {Fraction}
      */
     private getLongestStaffEntryDuration(index: number): Fraction {
         let maxLength: Fraction = new Fraction(0, 1);
-        for (let idx: number = 0, len: number = this.verticalGraphicalStaffEntryContainers[index].StaffEntries.length; idx < len; ++idx) {
-            const graphicalStaffEntry: GraphicalStaffEntry = this.verticalGraphicalStaffEntryContainers[index].StaffEntries[idx];
+        for (const graphicalStaffEntry of this.verticalGraphicalStaffEntryContainers[index].StaffEntries) {
             if (graphicalStaffEntry === undefined) {
                 continue;
             }
-            for (let idx2: number = 0, len2: number = graphicalStaffEntry.notes.length; idx2 < len2; ++idx2) {
-                const graphicalNotes: GraphicalNote[] = graphicalStaffEntry.notes[idx2];
-                for (let idx3: number = 0, len3: number = graphicalNotes.length; idx3 < len3; ++idx3) {
-                    const note: GraphicalNote = graphicalNotes[idx3];
-                    if (maxLength.lt(note.graphicalNoteLength)) {
-                        maxLength = note.graphicalNoteLength;
-                    }
-                }
+            const maxLengthInStaffEntry: Fraction = graphicalStaffEntry.findStaffEntryMaxNoteLength();
+            if (maxLength.lt(maxLengthInStaffEntry)) {
+                maxLength = maxLengthInStaffEntry;
             }
         }
         return maxLength;

+ 5 - 19
src/MusicalScore/Graphical/GraphicalNote.ts

@@ -4,28 +4,24 @@ import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {Pitch} from "../../Common/DataObjects/Pitch";
-import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
 import {GraphicalObject} from "./GraphicalObject";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {BoundingBox} from "./BoundingBox";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
 
 /**
  * The graphical counterpart of a [[Note]]
  */
 export class GraphicalNote extends GraphicalObject {
-    constructor(note: Note, parent: GraphicalStaffEntry, graphicalNoteLength: Fraction = undefined) {
+    constructor(note: Note, parent: GraphicalVoiceEntry, graphicalNoteLength: Fraction = undefined) {
         super();
         this.sourceNote = note;
-        this.parentStaffEntry = parent;
+        this.parentVoiceEntry = parent;
         this.PositionAndShape = new BoundingBox(this, parent.PositionAndShape);
         if (graphicalNoteLength !== undefined) {
             this.graphicalNoteLength = graphicalNoteLength;
         } else {
-            if (note.NoteTie !== undefined) {
-                this.graphicalNoteLength = note.calculateNoteLengthWithoutTie();
-            } else {
-                this.graphicalNoteLength = note.Length;
-            }
+            this.graphicalNoteLength = note.Length;
         }
 
         this.numberOfDots = this.calculateNumberOfNeededDots(this.graphicalNoteLength);
@@ -33,19 +29,9 @@ export class GraphicalNote extends GraphicalObject {
 
     public sourceNote: Note;
     public graphicalNoteLength: Fraction;
-    public parentStaffEntry: GraphicalStaffEntry;
+    public parentVoiceEntry: GraphicalVoiceEntry;
     public numberOfDots: number;
 
-    public get ParentList(): GraphicalNote[] {
-        for (let idx: number = 0, len: number = this.parentStaffEntry.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.parentStaffEntry.notes[idx];
-            if (graphicalNotes.indexOf(this) !== -1) {
-                return graphicalNotes;
-            }
-        }
-        return undefined;
-    }
-
     public Transpose(keyInstruction: KeyInstruction, activeClef: ClefInstruction, halfTones: number, octaveEnum: OctaveEnum): Pitch {
         let transposedPitch: Pitch = this.sourceNote.Pitch;
         if (MusicSheetCalculator.transposeCalculator !== undefined) {

+ 60 - 116
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -15,6 +15,8 @@ import {GraphicalLyricEntry} from "./GraphicalLyricEntry";
 import {AbstractGraphicalInstruction} from "./AbstractGraphicalInstruction";
 import {GraphicalStaffEntryLink} from "./GraphicalStaffEntryLink";
 import {CollectionUtil} from "../../Util/CollectionUtil";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
+import { MusicSheetCalculator } from "./MusicSheetCalculator";
 
 /**
  * The graphical counterpart of a [[SourceStaffEntry]].
@@ -23,7 +25,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     constructor(parentMeasure: StaffMeasure, sourceStaffEntry: SourceStaffEntry = undefined, staffEntryParent: GraphicalStaffEntry = undefined) {
         super();
         this.parentMeasure = parentMeasure;
-        this.notes = [];
+        this.graphicalVoiceEntries = [];
         this.graceStaffEntriesBefore = [];
         this.graceStaffEntriesAfter = [];
         this.sourceStaffEntry = sourceStaffEntry;
@@ -46,7 +48,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     public relInMeasureTimestamp: Fraction;
     public sourceStaffEntry: SourceStaffEntry;
     public parentMeasure: StaffMeasure;
-    public notes: GraphicalNote[][];
+    public graphicalVoiceEntries: GraphicalVoiceEntry[];
     public graceStaffEntriesBefore: GraphicalStaffEntry[];
     public graceStaffEntriesAfter: GraphicalStaffEntry[];
     public staffEntryParent: GraphicalStaffEntry;
@@ -90,13 +92,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      */
     public findEndTieGraphicalNoteFromNote(tieNote: Note): GraphicalNote {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
+        for (const gve of this.graphicalVoiceEntries) {
+            for (const graphicalNote of gve.notes) {
                 const note: Note = graphicalNote.sourceNote;
-                if (note.Pitch !== undefined && note.Pitch.FundamentalNote === tieNote.Pitch.FundamentalNote
-                    && note.Pitch.Octave === tieNote.Pitch.Octave && note.getAbsoluteTimestamp().Equals(tieNote.getAbsoluteTimestamp())) {
+                if (!note.isRest()
+                    && note.Pitch.FundamentalNote === tieNote.Pitch.FundamentalNote
+                    && note.Pitch.Octave === tieNote.Pitch.Octave
+                    && note.getAbsoluteTimestamp().Equals(tieNote.getAbsoluteTimestamp())) {
                     return graphicalNote;
                 }
             }
@@ -111,34 +113,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      */
     public findEndTieGraphicalNoteFromNoteWithStartingSlur(tieNote: Note, slur: Slur): GraphicalNote {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
-                const note: Note = graphicalNote.sourceNote;
-                if (note.NoteTie !== undefined && note.NoteSlurs.indexOf(slur) !== -1) {
-                    return graphicalNote;
-                }
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry !== tieNote.ParentVoiceEntry) {
+                continue;
             }
-        }
-        return undefined;
-    }
-
-    /**
-     * Search through all GraphicalNotes to find the suitable one for an EndSlurNote (that 's also an EndTieNote).
-     * @param tieNote
-     * @returns {any}
-     */
-    public findEndTieGraphicalNoteFromNoteWithEndingSlur(tieNote: Note): GraphicalNote {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
+            for (const graphicalNote of gve.notes) {
                 const note: Note = graphicalNote.sourceNote;
-                if (
-                    note.Pitch !== undefined && note.Pitch.FundamentalNote === tieNote.Pitch.FundamentalNote
-                    && note.Pitch.Octave === tieNote.Pitch.Octave && this.getAbsoluteTimestamp().Equals(tieNote.getAbsoluteTimestamp())
-                ) {
+                if (note.NoteTie !== undefined && note.NoteSlurs.indexOf(slur) !== -1) {
                     return graphicalNote;
                 }
             }
@@ -147,10 +128,11 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
 
     public findGraphicalNoteFromGraceNote(graceNote: Note): GraphicalNote {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry !== graceNote.ParentVoiceEntry) {
+                continue;
+            }
+            for (const graphicalNote of gve.notes) {
                 if (graphicalNote.sourceNote === graceNote) {
                     return graphicalNote;
                 }
@@ -159,12 +141,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return undefined;
     }
 
-    public findGraphicalNoteFromNote(baseNote: Note): GraphicalNote {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
-                if (graphicalNote.sourceNote === baseNote && this.getAbsoluteTimestamp().Equals(baseNote.getAbsoluteTimestamp())) {
+    public findGraphicalNoteFromNote(note: Note): GraphicalNote {
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry !== note.ParentVoiceEntry) {
+                continue;
+            }
+            for (const graphicalNote of gve.notes) {
+                if (graphicalNote.sourceNote === note && this.getAbsoluteTimestamp().Equals(note.getAbsoluteTimestamp())) {
                     return graphicalNote;
                 }
             }
@@ -173,46 +156,24 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
 
     public getGraphicalNoteDurationFromVoice(voice: Voice): Fraction {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            if (graphicalNotes[0].sourceNote.ParentVoiceEntry.ParentVoice === voice) {
-                return graphicalNotes[0].graphicalNoteLength;
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry.ParentVoice !== voice) {
+                continue;
             }
+            return gve.notes[0].graphicalNoteLength;
         }
         return new Fraction(0, 1);
     }
 
     /**
-     * Find the Linked GraphicalNotes which belong exclusively to the StaffEntry (in case of Linked StaffEntries).
-     * @param notLinkedNotes
-     */
-    public findLinkedNotes(notLinkedNotes: GraphicalNote[]): void {
-        if (this.sourceStaffEntry !== undefined && this.sourceStaffEntry.Link !== undefined) {
-            for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-                const graphicalNotes: GraphicalNote[] = this.notes[idx];
-                for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                    const graphicalNote: GraphicalNote = graphicalNotes[idx2];
-                    if (graphicalNote.parentStaffEntry === this) {
-                        notLinkedNotes.push(graphicalNote);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
      * Find the [[StaffEntry]]'s [[GraphicalNote]]s that correspond to the given [[VoiceEntry]]'s [[Note]]s.
      * @param voiceEntry
      * @returns {any}
      */
     public findVoiceEntryGraphicalNotes(voiceEntry: VoiceEntry): GraphicalNote[] {
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
-                if (graphicalNote.sourceNote.ParentVoiceEntry === voiceEntry) {
-                    return graphicalNotes;
-                }
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry === voiceEntry) {
+                return gve.notes;
             }
         }
         return undefined;
@@ -241,10 +202,8 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      */
     public findStaffEntryMinNoteLength(): Fraction {
         let minLength: Fraction = new Fraction(Number.MAX_VALUE, 1);
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
+        for (const gve of this.graphicalVoiceEntries) {
+            for (const graphicalNote of gve.notes) {
                 const calNoteLen: Fraction = graphicalNote.graphicalNoteLength;
                 if (calNoteLen.lt(minLength) && calNoteLen.GetExpandedNumerator() > 0) {
                     minLength = calNoteLen;
@@ -256,10 +215,8 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
 
     public findStaffEntryMaxNoteLength(): Fraction {
         let maxLength: Fraction = new Fraction(0, 1);
-        for (let idx: number = 0, len: number = this.notes.length; idx < len; ++idx) {
-            const graphicalNotes: GraphicalNote[] = this.notes[idx];
-            for (let idx2: number = 0, len2: number = graphicalNotes.length; idx2 < len2; ++idx2) {
-                const graphicalNote: GraphicalNote = graphicalNotes[idx2];
+        for (const gve of this.graphicalVoiceEntries) {
+            for (const graphicalNote of gve.notes) {
                 const calNoteLen: Fraction = graphicalNote.graphicalNoteLength;
                 if (maxLength.lt(calNoteLen)  && calNoteLen.GetExpandedNumerator() > 0) {
                     maxLength = calNoteLen;
@@ -274,21 +231,17 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @param voiceEntry
      * @returns {GraphicalNote[]}
      */
-    public findOrCreateGraphicalNotesListFromVoiceEntry(voiceEntry: VoiceEntry): GraphicalNote[] {
-        let graphicalNotes: GraphicalNote[];
-        if (this.notes.length === 0) {
-            graphicalNotes = [];
-            this.notes.push(graphicalNotes);
-        } else {
-            for (let i: number = 0; i < this.notes.length; i++) {
-                if (this.notes[i][0].sourceNote.ParentVoiceEntry.ParentVoice === voiceEntry.ParentVoice) {
-                    return this.notes[i];
-                }
+    public findOrCreateGraphicalVoiceEntry(voiceEntry: VoiceEntry): GraphicalVoiceEntry {
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve.parentVoiceEntry === voiceEntry) {
+                return gve;
             }
-            graphicalNotes = [];
-            this.notes.push(graphicalNotes);
         }
-        return graphicalNotes;
+        // if not found in list, create new one and add to list:
+        const graphicalVoiceEntry: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, this);
+        this.graphicalVoiceEntries.push(graphicalVoiceEntry);
+
+        return graphicalVoiceEntry;
     }
 
     /**
@@ -296,26 +249,17 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @param graphicalNote
      * @returns {GraphicalNote[]}
      */
-    public findOrCreateGraphicalNotesListFromGraphicalNote(graphicalNote: GraphicalNote): GraphicalNote[] {
-        let graphicalNotes: GraphicalNote[];
-        const tieStartSourceStaffEntry: SourceStaffEntry = graphicalNote.sourceNote.ParentStaffEntry;
-        if (this.sourceStaffEntry !== tieStartSourceStaffEntry) {
-            graphicalNotes = this.findOrCreateGraphicalNotesListFromVoiceEntry(graphicalNote.sourceNote.ParentVoiceEntry);
-        } else {
-            if (this.notes.length === 0) {
-                graphicalNotes = [];
-                this.notes.push(graphicalNotes);
-            } else {
-                for (let i: number = 0; i < this.notes.length; i++) {
-                    if (this.notes[i][0].sourceNote.ParentVoiceEntry.ParentVoice === graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice) {
-                        return this.notes[i];
-                    }
-                }
-                graphicalNotes = [];
-                this.notes.push(graphicalNotes);
+    public findOrCreateGraphicalVoiceEntryFromGraphicalNote(graphicalNote: GraphicalNote): GraphicalVoiceEntry {
+        for (const gve of this.graphicalVoiceEntries) {
+            if (gve === graphicalNote.parentVoiceEntry) {
+                return gve;
             }
         }
-        return graphicalNotes;
+        // if not found in list, create new one and add to list:
+        const graphicalVoiceEntry: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(graphicalNote.sourceNote.ParentVoiceEntry, this);
+        this.graphicalVoiceEntries.push(graphicalVoiceEntry);
+
+        return graphicalVoiceEntry;
     }
 
     /**
@@ -325,7 +269,8 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @param graphicalNotes
      * @param graphicalNote
      */
-    public addGraphicalNoteToListAtCorrectYPosition(graphicalNotes: GraphicalNote[], graphicalNote: GraphicalNote): void {
+    public addGraphicalNoteToListAtCorrectYPosition(gve: GraphicalVoiceEntry, graphicalNote: GraphicalNote): void {
+        const graphicalNotes: GraphicalNote[] = gve.notes;
         if (graphicalNotes.length === 0 ||
             graphicalNote.PositionAndShape.RelativePosition.y < CollectionUtil.last(graphicalNotes).PositionAndShape.RelativePosition.Y) {
             graphicalNotes.push(graphicalNote);
@@ -343,11 +288,10 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         }
     }
 
-    // FIXME: implement
     public hasOnlyRests(): boolean {
         const hasOnlyRests: boolean = true;
-        for (const graphicalNotes of this.notes) {
-            for (const graphicalNote of graphicalNotes) {
+        for (const gve of this.graphicalVoiceEntries) {
+            for (const graphicalNote of gve.notes) {
                 const note: Note = graphicalNote.sourceNote;
                 if (!note.isRest()) {
                     return false;

+ 2 - 4
src/MusicalScore/Graphical/GraphicalStaffEntryLink.ts

@@ -41,10 +41,8 @@ export class GraphicalStaffEntryLink {
             const notes: GraphicalNote[] = [];
             for (let idx: number = 0, len: number = this.graphicalLinkedStaffEntries.length; idx < len; ++idx) {
                 const graphicalLinkedStaffEntry: GraphicalStaffEntry = this.graphicalLinkedStaffEntries[idx];
-                for (let idx2: number = 0, len2: number = graphicalLinkedStaffEntry.notes.length; idx2 < len2; ++idx2) {
-                    const graphicalNotes: GraphicalNote[] = graphicalLinkedStaffEntry.notes[idx2];
-                    for (let idx3: number = 0, len3: number = graphicalNotes.length; idx3 < len3; ++idx3) {
-                        const graphicalNote: GraphicalNote = graphicalNotes[idx3];
+                for (const gve of graphicalLinkedStaffEntry.graphicalVoiceEntries) {
+                    for (const graphicalNote of gve.notes) {
                         if (graphicalNote.sourceNote.ParentStaffEntry.Link !== undefined
                             && graphicalNote.sourceNote.ParentVoiceEntry === this.staffEntryLink.GetVoiceEntry) {
                             notes.push(graphicalNote);

+ 22 - 0
src/MusicalScore/Graphical/GraphicalVoiceEntry.ts

@@ -0,0 +1,22 @@
+import {GraphicalObject} from "./GraphicalObject";
+import { VoiceEntry } from "../VoiceData/VoiceEntry";
+import { BoundingBox } from "./BoundingBox";
+import { GraphicalNote } from "./GraphicalNote";
+import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
+
+/**
+ * The graphical counterpart of a [[VoiceEntry]].
+ */
+export class GraphicalVoiceEntry extends GraphicalObject {
+    constructor(parentVoiceEntry: VoiceEntry, parentStaffEntry: GraphicalStaffEntry) {
+        super();
+        this.parentVoiceEntry = parentVoiceEntry;
+        this.parentStaffEntry = parentStaffEntry;
+        this.PositionAndShape = new BoundingBox(this, parentStaffEntry ? parentStaffEntry.PositionAndShape : undefined);
+        this.notes = [];
+    }
+
+    public parentVoiceEntry: VoiceEntry;
+    public parentStaffEntry: GraphicalStaffEntry;
+    public notes: GraphicalNote[];
+}

+ 93 - 216
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -47,18 +47,20 @@ import {AccidentalCalculator} from "./AccidentalCalculator";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {Staff} from "../VoiceData/Staff";
 import {OctaveShift} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
-import {CollectionUtil} from "../../Util/CollectionUtil";
 import {GraphicalLyricEntry} from "./GraphicalLyricEntry";
 import {GraphicalLyricWord} from "./GraphicalLyricWord";
 import {GraphicalLine} from "./GraphicalLine";
 import {Label} from "../Label";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
+import { VerticalSourceStaffEntryContainer } from "../VoiceData/VerticalSourceStaffEntryContainer";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
  */
 export abstract class MusicSheetCalculator {
+    public static symbolFactory: IGraphicalSymbolFactory;
     public static transposeCalculator: ITransposeCalculator;
     protected static textMeasurer: ITextMeasurer;
 
@@ -72,11 +74,7 @@ export abstract class MusicSheetCalculator {
 
     protected graphicalMusicSheet: GraphicalMusicSheet;
     protected rules: EngravingRules;
-    protected symbolFactory: IGraphicalSymbolFactory;
-
-    constructor(symbolFactory: IGraphicalSymbolFactory) {
-        this.symbolFactory = symbolFactory;
-    }
+    //protected symbolFactory: IGraphicalSymbolFactory;
 
     public static get TextMeasurer(): ITextMeasurer {
         return MusicSheetCalculator.textMeasurer;
@@ -90,21 +88,6 @@ export abstract class MusicSheetCalculator {
         return this.graphicalMusicSheet.LeadSheet;
     }
 
-    private static addTieToTieTimestampsDict(tieTimestampListDict: Dictionary<Tie, Fraction[]>, note: Note): void {
-        note.NoteTie.initializeBoolList();
-        const tieTimestampList: Fraction[] = [];
-        for (let m: number = 0; m < note.NoteTie.Fractions.length; m++) {
-            let musicTimestamp: Fraction;
-            if (m === 0) {
-                musicTimestamp = Fraction.plus(note.calculateNoteLengthWithoutTie(), note.getAbsoluteTimestamp());
-            } else {
-                musicTimestamp = Fraction.plus(tieTimestampList[m - 1], note.NoteTie.Fractions[m - 1]);
-            }
-            tieTimestampList.push(musicTimestamp);
-        }
-        tieTimestampListDict.setValue(note.NoteTie, tieTimestampList);
-    }
-
     private static setMeasuresMinStaffEntriesWidth(measures: StaffMeasure[], minimumStaffEntriesWidth: number): void {
         for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
             const measure: StaffMeasure = measures[idx];
@@ -120,7 +103,7 @@ export abstract class MusicSheetCalculator {
     }
 
     /**
-     * Build the 2D [[GraphicalMeasure]] ist needed for the [[MusicSheetCalculator]].
+     * Build the 2D [[GraphicalMeasure]] list needed for the [[MusicSheetCalculator]].
      * Internally it creates [[GraphicalMeasure]]s, [[GraphicalStaffEntry]]'s and [[GraphicalNote]]s.
      */
     public prepareGraphicalMusicSheet(): void {
@@ -153,10 +136,7 @@ export abstract class MusicSheetCalculator {
         const openOctaveShifts: OctaveShiftParams[] = [];
 
         // TieList - timestampsArray
-        const tieTimestampListDictList: Dictionary<Tie, Fraction[]>[] = [];
         for (let i: number = 0; i < completeNumberOfStaves; i++) {
-            const tieTimestampListDict: Dictionary<Tie, Fraction[]> = new Dictionary<Tie, Fraction[]>();
-            tieTimestampListDictList.push(tieTimestampListDict);
             openOctaveShifts.push(undefined);
         }
 
@@ -167,7 +147,6 @@ export abstract class MusicSheetCalculator {
                 sourceMeasure,
                 accidentalCalculators,
                 lyricWords,
-                tieTimestampListDictList,
                 openOctaveShifts,
                 activeClefs
             );
@@ -509,16 +488,18 @@ export abstract class MusicSheetCalculator {
                 systemMaxCount += maxPosition;
                 additionalPageLength += maxPosition;
             }
-
-            systemMaxCount -= this.rules.BetweenStaffDistance;
-            let systemIterator: MusicSystem = staffLine.ParentMusicSystem.NextSystem;
-            while (systemIterator !== undefined) {
-                systemIterator.PositionAndShape.RelativePosition.y += systemMaxCount;
-                systemIterator = systemIterator.NextSystem;
-                additionalPageLength += systemMaxCount;
+            // Special care for single line systems
+            if (systemMaxCount !== 0) {
+                systemMaxCount -= this.rules.BetweenStaffDistance;
+                let systemIterator: MusicSystem = staffLine.ParentMusicSystem.NextSystem;
+                while (systemIterator !== undefined) {
+                    systemIterator.PositionAndShape.RelativePosition.y += systemMaxCount;
+                    systemIterator = systemIterator.NextSystem;
+                    additionalPageLength += systemMaxCount;
+                }
+                staffLine.ParentMusicSystem.Parent.PositionAndShape.BorderBottom += additionalPageLength;
+                // Update the instrument labels
             }
-            staffLine.ParentMusicSystem.Parent.PositionAndShape.BorderBottom += additionalPageLength;
-            // Update the instrument labels
             staffLine.ParentMusicSystem.setMusicSystemLabelsYPosition();
             /**
              * HACK END
@@ -611,7 +592,7 @@ export abstract class MusicSheetCalculator {
      */
     protected handleStaffEntryLink(graphicalStaffEntry: GraphicalStaffEntry,
                                    staffEntryLinks: StaffEntryLink[]): void {
-        Logging.debug("handleStaffEntryLink not implemented");
+        log.debug("handleStaffEntryLink not implemented");
     }
 
     /**
@@ -655,7 +636,7 @@ export abstract class MusicSheetCalculator {
 
         // build the MusicSystems
         const musicSystemBuilder: MusicSystemBuilder = new MusicSystemBuilder();
-        musicSystemBuilder.initialize(this.graphicalMusicSheet, visibleMeasureList, numberOfStaffLines, this.symbolFactory);
+        musicSystemBuilder.initialize(this.graphicalMusicSheet, visibleMeasureList, numberOfStaffLines);
         musicSystemBuilder.buildMusicSystems();
 
         // check for Measures with only WholeRestNotes and correct their X-Position (middle of Measure)
@@ -779,22 +760,22 @@ export abstract class MusicSheetCalculator {
     }
 
     protected updateSkyBottomLine(staffLine: StaffLine): void {
-        //Logging.debug("updateSkyBottomLine not implemented");
+        //log.debug("updateSkyBottomLine not implemented");
         return;
     }
 
     protected calculateSkyBottomLine(staffLine: StaffLine): void {
-        //Logging.debug("calculateSkyBottomLine not implemented");
+        //log.debug("calculateSkyBottomLine not implemented");
         return;
     }
 
     protected calculateMarkedAreas(): void {
-        //Logging.debug("calculateMarkedAreas not implemented");
+        //log.debug("calculateMarkedAreas not implemented");
         return;
     }
 
     protected calculateComments(): void {
-        //Logging.debug("calculateComments not implemented");
+        //log.debug("calculateComments not implemented");
         return;
     }
 
@@ -924,12 +905,12 @@ export abstract class MusicSheetCalculator {
 
     protected handleVoiceEntry(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry,
                                accidentalCalculator: AccidentalCalculator, openLyricWords: LyricWord[],
-                               tieTimestampListDict: Dictionary<Tie, Fraction[]>, activeClef: ClefInstruction,
+                               activeClef: ClefInstruction,
                                openTuplets: Tuplet[], openBeams: Beam[],
                                octaveShiftValue: OctaveEnum, grace: boolean = false, linkedNotes: Note[] = undefined,
                                sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
         this.calculateStemDirectionFromVoices(voiceEntry);
-        const graphicalNotes: GraphicalNote[] = graphicalStaffEntry.findOrCreateGraphicalNotesListFromVoiceEntry(voiceEntry);
+        const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
         for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
             const note: Note = voiceEntry.Notes[idx];
             if (sourceStaffEntry !== undefined && sourceStaffEntry.Link !== undefined && linkedNotes !== undefined && linkedNotes.indexOf(note) > -1) {
@@ -937,18 +918,15 @@ export abstract class MusicSheetCalculator {
             }
             let graphicalNote: GraphicalNote;
             if (grace) {
-                graphicalNote = this.symbolFactory.createGraceNote(note, graphicalStaffEntry, activeClef, octaveShiftValue);
+                graphicalNote = MusicSheetCalculator.symbolFactory.createGraceNote(note, gve, activeClef, octaveShiftValue);
             } else {
-                graphicalNote = this.symbolFactory.createNote(note, graphicalStaffEntry, activeClef, octaveShiftValue, undefined);
-            }
-            if (note.NoteTie !== undefined) {
-                MusicSheetCalculator.addTieToTieTimestampsDict(tieTimestampListDict, note);
+                graphicalNote = MusicSheetCalculator.symbolFactory.createNote(note, gve, activeClef, octaveShiftValue, undefined);
             }
             if (note.Pitch !== undefined) {
                 this.checkNoteForAccidental(graphicalNote, accidentalCalculator, activeClef, octaveShiftValue, grace);
             }
             this.resetYPositionForLeadSheet(graphicalNote.PositionAndShape);
-            graphicalStaffEntry.addGraphicalNoteToListAtCorrectYPosition(graphicalNotes, graphicalNote);
+            graphicalStaffEntry.addGraphicalNoteToListAtCorrectYPosition(gve, graphicalNote);
             graphicalNote.PositionAndShape.calculateBoundingBox();
             if (!this.leadSheet) {
                 if (note.NoteBeam !== undefined) {
@@ -977,93 +955,24 @@ export abstract class MusicSheetCalculator {
     protected handleVoiceEntryGraceNotes(graceEntries: VoiceEntry[], graphicalGraceEntries: GraphicalStaffEntry[], graphicalStaffEntry: GraphicalStaffEntry,
                                          accidentalCalculator: AccidentalCalculator, activeClef: ClefInstruction,
                                          octaveShiftValue: OctaveEnum, lyricWords: LyricWord[],
-                                         tieTimestampListDict: Dictionary<Tie, Fraction[]>,
                                          tuplets: Tuplet[], beams: Beam[]): void {
         if (graceEntries !== undefined) {
             for (let idx: number = 0, len: number = graceEntries.length; idx < len; ++idx) {
                 const graceVoiceEntry: VoiceEntry = graceEntries[idx];
-                const graceStaffEntry: GraphicalStaffEntry = this.symbolFactory.createGraceStaffEntry(
+                const graceStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createGraceStaffEntry(
                     graphicalStaffEntry,
                     graphicalStaffEntry.parentMeasure
                 );
                 graphicalGraceEntries.push(graceStaffEntry);
                 this.handleVoiceEntry(
                     graceVoiceEntry, graceStaffEntry, accidentalCalculator, lyricWords,
-                    tieTimestampListDict, activeClef, tuplets,
+                    activeClef, tuplets,
                     beams, octaveShiftValue, true
                 );
             }
         }
     }
 
-    protected handleOpenTies(measure: StaffMeasure, beams: Beam[], tieTimestampListDict: Dictionary<Tie, Fraction[]>,
-                             activeClef: ClefInstruction, octaveShiftParams: OctaveShiftParams): void {
-        CollectionUtil.removeDictElementIfTrue( this, tieTimestampListDict,
-                                                function (thisPointer: MusicSheetCalculator, openTie: Tie, tieTimestamps: Fraction[]): boolean {
-            // for (let m: number = tieTimestampListDict.size() - 1; m >= 0; m--) {
-            //     let keyValuePair: KeyValuePair<Tie, Fraction[]> = tieTimestampListDict.ElementAt(m);
-            //     let openTie: Tie = keyValuePair.Key;
-            //    let tieTimestamps: Fraction[] = keyValuePair.Value;
-            let absoluteTimestamp: Fraction = undefined;
-
-            let removeTie: boolean = false;
-            for (let k: number = 0; k < tieTimestamps.length; k++) {
-                if (!openTie.NoteHasBeenCreated[k]) {
-                    absoluteTimestamp = tieTimestamps[k];
-                    if (Fraction.plus(measure.parentSourceMeasure.AbsoluteTimestamp, measure.parentSourceMeasure.Duration).lte(absoluteTimestamp)) {
-                        continue;
-                    }
-                    let graphicalStaffEntry: GraphicalStaffEntry = undefined;
-                    if (absoluteTimestamp !== undefined) {
-                        for (let idx: number = 0, len: number = measure.staffEntries.length; idx < len; ++idx) {
-                            const gse: GraphicalStaffEntry = measure.staffEntries[idx];
-                            if (gse.getAbsoluteTimestamp().Equals(absoluteTimestamp)) {
-                                graphicalStaffEntry = gse;
-                                break;
-                            }
-                        }
-                        if (graphicalStaffEntry === undefined) {
-                            graphicalStaffEntry = thisPointer.createStaffEntryForTieNote(measure, absoluteTimestamp, openTie);
-                        }
-                    }
-                    if (graphicalStaffEntry !== undefined) {
-                        let octaveShiftValue: OctaveEnum = OctaveEnum.NONE;
-                        if (octaveShiftParams !== undefined) {
-                            if (octaveShiftParams.getAbsoluteStartTimestamp.lte(graphicalStaffEntry.getAbsoluteTimestamp()) &&
-                                graphicalStaffEntry.getAbsoluteTimestamp().lte(octaveShiftParams.getAbsoluteEndTimestamp)) {
-                                octaveShiftValue = octaveShiftParams.getOpenOctaveShift.Type;
-                            }
-                        }
-                        const isLastTieNote: boolean = k === tieTimestamps.length - 1;
-                        const tieFraction: Fraction = openTie.Fractions[k];
-                        // GraphicalNote points to tieStartNote, but must get the correct Length (eg the correct Fraction of tieStartNote's Length)
-                        const tiedGraphicalNote: GraphicalNote = thisPointer.symbolFactory.createNote(openTie.Start, graphicalStaffEntry, activeClef,
-                                                                                                      octaveShiftValue, tieFraction);
-
-                        const graphicalNotes: GraphicalNote[] =
-                            graphicalStaffEntry.findOrCreateGraphicalNotesListFromGraphicalNote(tiedGraphicalNote);
-                        graphicalStaffEntry.addGraphicalNoteToListAtCorrectYPosition(graphicalNotes, tiedGraphicalNote);
-
-                        thisPointer.handleTiedGraphicalNote(tiedGraphicalNote, beams, activeClef, octaveShiftValue, graphicalStaffEntry, tieFraction,
-                                                            openTie, isLastTieNote);
-
-                        const tieStartNote: Note = openTie.Start;
-                        if (isLastTieNote && tieStartNote.ParentVoiceEntry.Articulations.length === 1 &&
-                            tieStartNote.ParentVoiceEntry.Articulations[0] === ArticulationEnum.fermata) {
-                            thisPointer.symbolFactory.addFermataAtTiedEndNote(tieStartNote, graphicalStaffEntry);
-                        }
-                        openTie.NoteHasBeenCreated[k] = true;
-                        if (openTie.allGraphicalNotesHaveBeenCreated()) {
-                            removeTie = true;
-                            //tieTimestampListDict.remove(openTie);
-                        }
-                    }
-                }
-            }
-            return removeTie;
-        });
-    }
-
     protected resetYPositionForLeadSheet(psi: BoundingBox): void {
         if (this.leadSheet) {
             psi.RelativePosition = new PointF2D(psi.RelativePosition.x, 0.0);
@@ -1074,9 +983,8 @@ export abstract class MusicSheetCalculator {
         graphicalStaffEntry.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
         if (!this.leadSheet) {
             const isGraceStaffEntry: boolean = graphicalStaffEntry.staffEntryParent !== undefined;
-            const graphicalStaffEntryNotes: GraphicalNote[][] = graphicalStaffEntry.notes;
-            for (let idx4: number = 0, len4: number = graphicalStaffEntryNotes.length; idx4 < len4; ++idx4) {
-                const graphicalNotes: GraphicalNote[] = graphicalStaffEntryNotes[idx4];
+            for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
+                const graphicalNotes: GraphicalNote[] = gve.notes;
                 if (graphicalNotes.length === 0) {
                     continue;
                 }
@@ -1134,8 +1042,8 @@ export abstract class MusicSheetCalculator {
                         const measure: StaffMeasure = staffLine.Measures[idx4];
                         if (measure.staffEntries.length === 1) {
                             const gse: GraphicalStaffEntry = measure.staffEntries[0];
-                            if (gse.notes.length > 0 && gse.notes[0].length > 0) {
-                                const graphicalNote: GraphicalNote = gse.notes[0][0];
+                            if (gse.graphicalVoiceEntries.length > 0 && gse.graphicalVoiceEntries[0].notes.length === 1) {
+                                const graphicalNote: GraphicalNote = gse.graphicalVoiceEntries[0].notes[0];
                                 if (graphicalNote.sourceNote.Pitch === undefined && (new Fraction(1, 2)).lt(graphicalNote.sourceNote.Length)) {
                                     this.layoutMeasureWithWholeRest(graphicalNote, gse, measure);
                                 }
@@ -1148,18 +1056,18 @@ export abstract class MusicSheetCalculator {
     }
 
     protected optimizeRestNotePlacement(graphicalStaffEntry: GraphicalStaffEntry, measure: StaffMeasure): void {
-        if (graphicalStaffEntry.notes.length === 0) {
+        if (graphicalStaffEntry.graphicalVoiceEntries.length === 0) {
             return;
         }
-        const voice1Notes: GraphicalNote[] = graphicalStaffEntry.notes[0];
+        const voice1Notes: GraphicalNote[] = graphicalStaffEntry.graphicalVoiceEntries[0].notes;
         if (voice1Notes.length === 0) {
             return;
         }
         const voice1Note1: GraphicalNote = voice1Notes[0];
         const voice1Note1IsRest: boolean = voice1Note1.sourceNote.Pitch === undefined;
-        if (graphicalStaffEntry.notes.length === 2) {
+        if (graphicalStaffEntry.graphicalVoiceEntries.length === 2) {
             let voice2Note1IsRest: boolean = false;
-            const voice2Notes: GraphicalNote[] = graphicalStaffEntry.notes[1];
+            const voice2Notes: GraphicalNote[] = graphicalStaffEntry.graphicalVoiceEntries[1].notes;
             if (voice2Notes.length > 0) {
                 const voice2Note1: GraphicalNote = voice2Notes[0];
                 voice2Note1IsRest = voice2Note1.sourceNote.Pitch === undefined;
@@ -1174,10 +1082,10 @@ export abstract class MusicSheetCalculator {
             const staffEntryIndex: number = measure.staffEntries.indexOf(graphicalStaffEntry);
             const previousStaffEntry: GraphicalStaffEntry = measure.staffEntries[staffEntryIndex - 1];
             const nextStaffEntry: GraphicalStaffEntry = measure.staffEntries[staffEntryIndex + 1];
-            if (previousStaffEntry.notes.length === 1) {
-                const previousNote: GraphicalNote = previousStaffEntry.notes[0][0];
-                if (previousNote.sourceNote.NoteBeam !== undefined && nextStaffEntry.notes.length === 1) {
-                    const nextNote: GraphicalNote = nextStaffEntry.notes[0][0];
+            if (previousStaffEntry.graphicalVoiceEntries.length === 1) {
+                const previousNote: GraphicalNote = previousStaffEntry.graphicalVoiceEntries[0].notes[0];
+                if (previousNote.sourceNote.NoteBeam !== undefined && nextStaffEntry.graphicalVoiceEntries.length === 1) {
+                    const nextNote: GraphicalNote = nextStaffEntry.graphicalVoiceEntries[0].notes[0];
                     if (nextNote.sourceNote.NoteBeam !== undefined && previousNote.sourceNote.NoteBeam === nextNote.sourceNote.NoteBeam) {
                         this.calculateRestNotePlacementWithinGraphicalBeam(
                             graphicalStaffEntry, voice1Note1, previousNote,
@@ -1319,26 +1227,13 @@ export abstract class MusicSheetCalculator {
 
     private handleTie(tie: Tie, startGraphicalStaffEntry: GraphicalStaffEntry, staffIndex: number, measureIndex: number): void {
         let startGse: GraphicalStaffEntry = startGraphicalStaffEntry;
+        let startNote: GraphicalNote = startGse.findEndTieGraphicalNoteFromNote(tie.StartNote);
         let endGse: GraphicalStaffEntry = undefined;
-        let startNote: GraphicalNote = undefined;
         let endNote: GraphicalNote = undefined;
-        for (let i: number = 0; i < tie.Fractions.length; i++) {
-            let verticalGraphicalStaffEntryContainer: VerticalGraphicalStaffEntryContainer;
-            let endTimestamp: Fraction;
-            const startContainerIndex: number = startGraphicalStaffEntry.parentVerticalContainer.Index;
-            if (i === 0) {
-                endTimestamp = Fraction.plus(startGraphicalStaffEntry.getAbsoluteTimestamp(), tie.Start.calculateNoteLengthWithoutTie());
-            } else {
-                endTimestamp = Fraction.plus(startGse.getAbsoluteTimestamp(), tie.Fractions[i - 1]);
-            }
-            verticalGraphicalStaffEntryContainer = this.graphicalMusicSheet.GetVerticalContainerFromTimestamp(endTimestamp, startContainerIndex + 1);
-            if (verticalGraphicalStaffEntryContainer !== undefined) {
-                endGse = verticalGraphicalStaffEntryContainer.StaffEntries[staffIndex];
-                startNote = startGse.findEndTieGraphicalNoteFromNote(tie.Start);
-                if (endGse !== undefined) {
-                    endNote = endGse.findEndTieGraphicalNoteFromNote(tie.Start);
-                }
-            }
+        for (let i: number = 1; i < tie.Notes.length; i++) {
+            startNote = startGse.findEndTieGraphicalNoteFromNote(tie.Notes[i - 1]);
+            endGse = this.graphicalMusicSheet.GetGraphicalFromSourceStaffEntry(tie.Notes[i].ParentStaffEntry);
+            endNote = endGse.findEndTieGraphicalNoteFromNote(tie.Notes[i]);
             if (startNote !== undefined && endNote !== undefined && endGse !== undefined) {
                 const graphicalTie: GraphicalTie = this.createGraphicalTie(tie, startGse, endGse, startNote, endNote);
                 startGse.GraphicalTies.push(graphicalTie);
@@ -1346,13 +1241,7 @@ export abstract class MusicSheetCalculator {
                     this.staffEntriesWithGraphicalTies.push(startGse);
                 }
             }
-            if (endGse !== undefined) {
-                if (endGse.parentMeasure !== startGse.parentMeasure) {
-                    measureIndex++;
-                }
-                startGse = endGse;
-                endGse = this.graphicalMusicSheet.findNextGraphicalStaffEntry(staffIndex, measureIndex, startGse);
-            }
+            startGse = endGse;
         }
     }
 
@@ -1361,7 +1250,7 @@ export abstract class MusicSheetCalculator {
         const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
         if (firstSourceMeasure !== undefined) {
             for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
-                const accidentalCalculator: AccidentalCalculator = new AccidentalCalculator(this.symbolFactory);
+                const accidentalCalculator: AccidentalCalculator = new AccidentalCalculator();
                 accidentalCalculators.push(accidentalCalculator);
                 if (firstSourceMeasure.FirstInstructionsStaffEntries[i] !== undefined) {
                     for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
@@ -1403,7 +1292,6 @@ export abstract class MusicSheetCalculator {
 
     private createGraphicalMeasuresForSourceMeasure(sourceMeasure: SourceMeasure, accidentalCalculators: AccidentalCalculator[],
                                                     openLyricWords: LyricWord[],
-                                                    tieTimestampListDictList: Dictionary<Tie, Fraction[]>[],
                                                     openOctaveShifts: OctaveShiftParams[], activeClefs: ClefInstruction[]): StaffMeasure[] {
         this.initStaffMeasuresCreation();
         const verticalMeasureList: StaffMeasure[] = [];
@@ -1412,7 +1300,7 @@ export abstract class MusicSheetCalculator {
         const staffEntryLinks: StaffEntryLink[] = [];
         for (let staffIndex: number = 0; staffIndex < sourceMeasure.CompleteNumberOfStaves; staffIndex++) {
             const measure: StaffMeasure = this.createGraphicalMeasure(
-                sourceMeasure, tieTimestampListDictList[staffIndex], openTuplets, openBeams,
+                sourceMeasure, openTuplets, openBeams,
                 accidentalCalculators[staffIndex], activeClefs, openOctaveShifts, openLyricWords, staffIndex, staffEntryLinks
             );
             this.staffMeasureCreatedCalculations(measure);
@@ -1422,12 +1310,12 @@ export abstract class MusicSheetCalculator {
         return verticalMeasureList;
     }
 
-    private createGraphicalMeasure(sourceMeasure: SourceMeasure, tieTimestampListDict: Dictionary<Tie, Fraction[]>, openTuplets: Tuplet[], openBeams: Beam[],
+    private createGraphicalMeasure(sourceMeasure: SourceMeasure, openTuplets: Tuplet[], openBeams: Beam[],
                                    accidentalCalculator: AccidentalCalculator, activeClefs: ClefInstruction[],
                                    openOctaveShifts: OctaveShiftParams[], openLyricWords: LyricWord[], staffIndex: number,
                                    staffEntryLinks: StaffEntryLink[]): StaffMeasure {
         const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
-        const measure: StaffMeasure = this.symbolFactory.createStaffMeasure(sourceMeasure, staff);
+        const measure: StaffMeasure = MusicSheetCalculator.symbolFactory.createStaffMeasure(sourceMeasure, staff);
         measure.hasError = sourceMeasure.getErrorInMeasure(staffIndex);
         if (sourceMeasure.FirstInstructionsStaffEntries[staffIndex] !== undefined) {
             for (let idx: number = 0, len: number = sourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions.length; idx < len; ++idx) {
@@ -1464,7 +1352,7 @@ export abstract class MusicSheetCalculator {
                         activeClefs[staffIndex] = <ClefInstruction>abstractNotationInstruction;
                     }
                 }
-                const graphicalStaffEntry: GraphicalStaffEntry = this.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
+                const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
                 if (measure.staffEntries.length > entryIndex) {
                     measure.addGraphicalStaffEntryAtTimestamp(graphicalStaffEntry);
                 } else {
@@ -1488,12 +1376,11 @@ export abstract class MusicSheetCalculator {
                     this.handleVoiceEntryGraceNotes(
                         voiceEntry.graceVoiceEntriesBefore, graphicalStaffEntry.graceStaffEntriesBefore, graphicalStaffEntry,
                         accidentalCalculator, activeClefs[staffIndex], octaveShiftValue, openLyricWords,
-                        tieTimestampListDict, openTuplets, openBeams
+                        openTuplets, openBeams
                     );
                     octaveShiftValue = this.handleVoiceEntry(
                         voiceEntry, graphicalStaffEntry,
                         accidentalCalculator, openLyricWords,
-                        tieTimestampListDict,
                         activeClefs[staffIndex], openTuplets,
                         openBeams, octaveShiftValue, false, linkedNotes,
                         sourceStaffEntry
@@ -1501,25 +1388,23 @@ export abstract class MusicSheetCalculator {
                     this.handleVoiceEntryGraceNotes(
                         voiceEntry.graceVoiceEntriesAfter, graphicalStaffEntry.graceStaffEntriesAfter, graphicalStaffEntry,
                         accidentalCalculator, activeClefs[staffIndex], octaveShiftValue, openLyricWords,
-                        tieTimestampListDict, openTuplets, openBeams
+                        openTuplets, openBeams
                     );
                 }
                 if (sourceStaffEntry.Instructions.length > 0) {
                     const clefInstruction: ClefInstruction = <ClefInstruction>sourceStaffEntry.Instructions[0];
-                    this.symbolFactory.createInStaffClef(graphicalStaffEntry, clefInstruction);
+                    MusicSheetCalculator.symbolFactory.createInStaffClef(graphicalStaffEntry, clefInstruction);
                 }
                 if (sourceStaffEntry.ChordContainer !== undefined) {
                     sourceStaffEntry.ParentStaff.ParentInstrument.HasChordSymbols = true;
-                    this.symbolFactory.createChordSymbol(sourceStaffEntry, graphicalStaffEntry, this.graphicalMusicSheet.ParentMusicSheet.Transpose);
+                    MusicSheetCalculator.symbolFactory.createChordSymbol(
+                        sourceStaffEntry,
+                        graphicalStaffEntry,
+                        this.graphicalMusicSheet.ParentMusicSheet.Transpose);
                 }
             }
         }
-        if (tieTimestampListDict.size() > 0) {
-            this.handleOpenTies(
-                measure, openBeams,
-                tieTimestampListDict, activeClefs[staffIndex], openOctaveShifts[staffIndex]
-            );
-        }
+
         accidentalCalculator.doCalculationsAtEndOfMeasure();
         if (sourceMeasure.LastInstructionsStaffEntries[staffIndex] !== undefined) {
             const lastStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
@@ -1537,19 +1422,26 @@ export abstract class MusicSheetCalculator {
                 openOctaveShifts[staffIndex] = undefined;
             }
         }
+        // if there are no staffEntries in this measure, create a rest for the whole measure:
         if (measure.staffEntries.length === 0) {
-            const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(undefined, staff);
-            const note: Note = new Note(undefined, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined);
-            const graphicalStaffEntry: GraphicalStaffEntry = this.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
+            const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(
+                new VerticalSourceStaffEntryContainer(  measure.parentSourceMeasure,
+                                                        measure.parentSourceMeasure.AbsoluteTimestamp,
+                                                        measure.parentSourceMeasure.CompleteNumberOfStaves),
+                staff);
+            const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
+            const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined);
+            voiceEntry.Notes.push(note);
+            const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
             measure.addGraphicalStaffEntry(graphicalStaffEntry);
-            graphicalStaffEntry.relInMeasureTimestamp = new Fraction(0, 1);
-            const graphicalNotes: GraphicalNote[] = [];
-            graphicalStaffEntry.notes.push(graphicalNotes);
-            const graphicalNote: GraphicalNote = this.symbolFactory.createNote(   note,
-                                                                                  graphicalStaffEntry,
-                                                                                  new ClefInstruction(),
-                                                                                  OctaveEnum.NONE, undefined);
-            graphicalNotes.push(graphicalNote);
+            graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
+            const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
+            graphicalStaffEntry.graphicalVoiceEntries.push(gve);
+            const graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote( note,
+                                                                                                gve,
+                                                                                                new ClefInstruction(),
+                                                                                                OctaveEnum.NONE, undefined);
+            gve.notes.push(graphicalNote);
         }
         return measure;
     }
@@ -1562,9 +1454,6 @@ export abstract class MusicSheetCalculator {
             pitch = graphicalNote.Transpose(
                 accidentalCalculator.ActiveKeyInstruction, activeClef, transpose, octaveEnum
             );
-            if (graphicalNote.sourceNote.NoteTie !== undefined) {
-                graphicalNote.sourceNote.NoteTie.BaseNoteYPosition = graphicalNote.PositionAndShape.RelativePosition.y;
-            }
         }
         graphicalNote.sourceNote.halfTone = pitch.getHalfTone();
         let scalingFactor: number = 1.0;
@@ -1574,19 +1463,6 @@ export abstract class MusicSheetCalculator {
         accidentalCalculator.checkAccidental(graphicalNote, pitch, grace, scalingFactor);
     }
 
-    // needed to disable linter, as it doesn't recognize the existing usage of this method.
-    // ToDo: check if a newer version doesn't have the problem.
-    /* tslint:disable:no-unused-variable */
-    private createStaffEntryForTieNote(measure: StaffMeasure, absoluteTimestamp: Fraction, openTie: Tie): GraphicalStaffEntry {
-        /* tslint:enable:no-unused-variable */
-        let graphicalStaffEntry: GraphicalStaffEntry;
-        graphicalStaffEntry = this.symbolFactory.createStaffEntry(openTie.Start.ParentStaffEntry, measure);
-        graphicalStaffEntry.relInMeasureTimestamp = Fraction.minus(absoluteTimestamp, measure.parentSourceMeasure.AbsoluteTimestamp);
-        this.resetYPositionForLeadSheet(graphicalStaffEntry.PositionAndShape);
-        measure.addGraphicalStaffEntryAtTimestamp(graphicalStaffEntry);
-        return graphicalStaffEntry;
-    }
-
     private updateSkyBottomLines(): void {
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
             const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
@@ -1605,9 +1481,10 @@ export abstract class MusicSheetCalculator {
             const measures: StaffMeasure[] = this.graphicalMusicSheet.MeasureList[idx];
             for (let idx2: number = 0, len2: number = measures.length; idx2 < len2; ++idx2) {
                 const measure: StaffMeasure = measures[idx2];
-                for (let idx3: number = 0, len3: number = measure.staffEntries.length; idx3 < len3; ++idx3) {
-                    const graphicalStaffEntry: GraphicalStaffEntry = measure.staffEntries[idx3];
-                    if (graphicalStaffEntry.parentMeasure !== undefined && graphicalStaffEntry.notes.length > 0 && graphicalStaffEntry.notes[0].length > 0) {
+                for (const graphicalStaffEntry of measure.staffEntries) {
+                    if (graphicalStaffEntry.parentMeasure !== undefined
+                        && graphicalStaffEntry.graphicalVoiceEntries.length > 0
+                        && graphicalStaffEntry.graphicalVoiceEntries[0].notes.length > 0) {
                         this.layoutVoiceEntries(graphicalStaffEntry);
                         this.layoutStaffEntry(graphicalStaffEntry);
                     }
@@ -1722,8 +1599,8 @@ export abstract class MusicSheetCalculator {
     }
 
     private calculateTwoRestNotesPlacementWithCollisionDetection(graphicalStaffEntry: GraphicalStaffEntry): void {
-        const firstRestNote: GraphicalNote = graphicalStaffEntry.notes[0][0];
-        const secondRestNote: GraphicalNote = graphicalStaffEntry.notes[1][0];
+        const firstRestNote: GraphicalNote = graphicalStaffEntry.graphicalVoiceEntries[0].notes[0];
+        const secondRestNote: GraphicalNote = graphicalStaffEntry.graphicalVoiceEntries[1].notes[0];
         secondRestNote.PositionAndShape.RelativePosition = new PointF2D(0.0, 2.5);
         graphicalStaffEntry.PositionAndShape.calculateAbsolutePositionsRecursiveWithoutTopelement();
         firstRestNote.PositionAndShape.computeNonOverlappingPositionWithMargin(
@@ -1739,12 +1616,12 @@ export abstract class MusicSheetCalculator {
     private calculateRestNotePlacementWithCollisionDetectionFromGraphicalNote(graphicalStaffEntry: GraphicalStaffEntry): void {
         let restNote: GraphicalNote;
         let graphicalNotes: GraphicalNote[];
-        if (graphicalStaffEntry.notes[0][0].sourceNote.Pitch === undefined) {
-            restNote = graphicalStaffEntry.notes[0][0];
-            graphicalNotes = graphicalStaffEntry.notes[1];
+        if (graphicalStaffEntry.graphicalVoiceEntries[0].notes[0].sourceNote.Pitch === undefined) {
+            restNote = graphicalStaffEntry.graphicalVoiceEntries[0].notes[0];
+            graphicalNotes = graphicalStaffEntry.graphicalVoiceEntries[1].notes;
         } else {
-            graphicalNotes = graphicalStaffEntry.notes[0];
-            restNote = graphicalStaffEntry.notes[1][0];
+            graphicalNotes = graphicalStaffEntry.graphicalVoiceEntries[0].notes;
+            restNote = graphicalStaffEntry.graphicalVoiceEntries[1].notes[0];
         }
         let collision: boolean = false;
         graphicalStaffEntry.PositionAndShape.calculateAbsolutePositionsRecursiveWithoutTopelement();
@@ -1791,10 +1668,10 @@ export abstract class MusicSheetCalculator {
                             const graphicalTies: GraphicalTie[] = staffEntry.GraphicalTies;
                             for (let idx7: number = 0, len7: number = graphicalTies.length; idx7 < len7; ++idx7) {
                                 const graphicalTie: GraphicalTie = graphicalTies[idx7];
-                                if (graphicalTie.StartNote !== undefined && graphicalTie.StartNote.parentStaffEntry === staffEntry) {
+                                if (graphicalTie.StartNote !== undefined && graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry === staffEntry) {
                                     const tieIsAtSystemBreak: boolean = (
-                                        graphicalTie.StartNote.parentStaffEntry.parentMeasure.ParentStaffLine !==
-                                        graphicalTie.EndNote.parentStaffEntry.parentMeasure.ParentStaffLine
+                                        graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine !==
+                                        graphicalTie.EndNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine
                                     );
                                     this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak);
                                 }

+ 4 - 9
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -16,7 +16,6 @@ import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
-import {IGraphicalSymbolFactory} from "../Interfaces/IGraphicalSymbolFactory";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {CollectionUtil} from "../../Util/CollectionUtil";
@@ -41,16 +40,13 @@ export class MusicSystemBuilder {
     private activeClefs: ClefInstruction[];
     private globalSystemIndex: number = 0;
     private leadSheet: boolean = false;
-    private symbolFactory: IGraphicalSymbolFactory;
 
     public initialize(
-        graphicalMusicSheet: GraphicalMusicSheet, measureList: StaffMeasure[][], numberOfStaffLines: number, symbolFactory: IGraphicalSymbolFactory
-    ): void {
+        graphicalMusicSheet: GraphicalMusicSheet, measureList: StaffMeasure[][], numberOfStaffLines: number): void {
         this.leadSheet = graphicalMusicSheet.LeadSheet;
         this.graphicalMusicSheet = graphicalMusicSheet;
         this.rules = this.graphicalMusicSheet.ParentMusicSheet.rules;
         this.measureList = measureList;
-        this.symbolFactory = symbolFactory;
         this.currentMusicPage = this.createMusicPage();
         this.currentPageHeight = 0.0;
         this.numberOfVisibleStaffLines = numberOfStaffLines;
@@ -257,7 +253,7 @@ export class MusicSystemBuilder {
      * @returns {MusicSystem}
      */
     private initMusicSystem(): MusicSystem {
-        const musicSystem: MusicSystem = this.symbolFactory.createMusicSystem(this.currentMusicPage, this.globalSystemIndex++);
+        const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.currentMusicPage, this.globalSystemIndex++);
         this.currentMusicPage.MusicSystems.push(musicSystem);
         return musicSystem;
     }
@@ -329,7 +325,7 @@ export class MusicSystemBuilder {
      */
     private addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
         if (musicSystem !== undefined) {
-            const staffLine: StaffLine = this.symbolFactory.createStaffLine(musicSystem, staff);
+            const staffLine: StaffLine = MusicSheetCalculator.symbolFactory.createStaffLine(musicSystem, staff);
             musicSystem.StaffLines.push(staffLine);
             const boundingBox: BoundingBox = staffLine.PositionAndShape;
             const relativePosition: PointF2D = new PointF2D();
@@ -613,7 +609,7 @@ export class MusicSystemBuilder {
     private addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
         const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
         const measures: StaffMeasure[] = [];
-        const measure: StaffMeasure = this.symbolFactory.createExtraStaffMeasure(currentSystem.StaffLines[visStaffIdx]);
+        const measure: StaffMeasure = MusicSheetCalculator.symbolFactory.createExtraStaffMeasure(currentSystem.StaffLines[visStaffIdx]);
         measures.push(measure);
         if (keyInstruction !== undefined) {
             measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
@@ -627,7 +623,6 @@ export class MusicSystemBuilder {
         const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
         measure.PositionAndShape.BorderRight = width;
         currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
-        measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
         return width;
     }
 

+ 4 - 7
src/MusicalScore/Graphical/StaffMeasure.ts

@@ -10,7 +10,6 @@ import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {Voice} from "../VoiceData/Voice";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
-import {GraphicalNote} from "./GraphicalNote";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {BoundingBox} from "./BoundingBox";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
@@ -261,12 +260,10 @@ export abstract class StaffMeasure extends GraphicalObject {
         for (let idx: number = 0, len: number = voices.length; idx < len; ++idx) {
             const voice: Voice = voices[idx];
             const voiceDuration: Fraction = new Fraction(0, 1);
-            for (let idx2: number = 0, len2: number = this.staffEntries.length; idx2 < len2; ++idx2) {
-                const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx2];
-                for (let idx3: number = 0, len3: number = graphicalStaffEntry.notes.length; idx3 < len3; ++idx3) {
-                    const graphicalNotes: GraphicalNote[] = graphicalStaffEntry.notes[idx3];
-                    if (graphicalNotes.length > 0 && graphicalNotes[0].sourceNote.ParentVoiceEntry.ParentVoice === voice) {
-                        voiceDuration.Add(graphicalNotes[0].graphicalNoteLength);
+            for (const graphicalStaffEntry of this.staffEntries) {
+                for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
+                    if (gve.parentVoiceEntry.ParentVoice === voice && gve.notes.length > 0) {
+                        voiceDuration.Add(gve.notes[0].graphicalNoteLength);
                     }
                 }
             }

+ 71 - 38
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -15,9 +15,10 @@ import {SystemLinesEnum} from "../SystemLinesEnum";
 import {FontStyles} from "../../../Common/Enums/FontStyles";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {OutlineAndFillStyleEnum, OUTLINE_AND_FILL_STYLE_DICT} from "../DrawingEnums";
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 import { ArticulationEnum, StemDirectionType } from "../../VoiceData/VoiceEntry";
 import { SystemLinePosition } from "../SystemLinePosition";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 
 /**
  * Helper class, which contains static methods which actually convert
@@ -137,56 +138,91 @@ export class VexFlowConverter {
         return acc;
     }
 
+    public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
+        // const frac: Fraction = notes[0].graphicalNoteLength;
+        return new Vex.Flow.GhostNote({
+            duration: VexFlowConverter.duration(frac, false),
+        });
+    }
+
     /**
-     * Convert a set of GraphicalNotes to a VexFlow StaveNote
-     * @param notes form a chord on the staff
+     * Convert a GraphicalVoiceEntry to a VexFlow StaveNote
+     * @param gve the GraphicalVoiceEntry which can hold a note or a chord on the staff belonging to one voice
      * @returns {Vex.Flow.StaveNote}
      */
-    public static StaveNote(notes: GraphicalNote[]): Vex.Flow.StaveNote {
+    public static StaveNote(gve: GraphicalVoiceEntry): Vex.Flow.StaveNote {
+        // VexFlow needs the notes ordered vertically in the other direction:
+        const notes: GraphicalNote[] = gve.notes.reverse();
+        const baseNote: GraphicalNote = notes[0];
+        if (baseNote.sourceNote.Pitch === undefined &&
+            new Fraction(1, 2).lt(baseNote.sourceNote.Length)) {
+                // test
+            }
         let keys: string[] = [];
         const accidentals: string[] = [];
-        const frac: Fraction = notes[0].graphicalNoteLength;
-        const isTuplet: boolean = notes[0].sourceNote.NoteTuplet !== undefined;
+        const frac: Fraction = baseNote.graphicalNoteLength;
+        const isTuplet: boolean = baseNote.sourceNote.NoteTuplet !== undefined;
         let duration: string = VexFlowConverter.duration(frac, isTuplet);
         let vfClefType: string = undefined;
-        let numDots: number = 0;
+        let numDots: number = baseNote.numberOfDots;
+        let alignCenter: boolean = false;
+        let xShift: number = 0;
         for (const note of notes) {
-            const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
-            if (pitch === undefined) { // if it is a rest:
-              keys = ["b/4"];
-              duration += "r";
-              break;
+            if (numDots < note.numberOfDots) {
+                numDots = note.numberOfDots;
             }
+            // if it is a rest:
+            if (note.sourceNote.isRest()) {
+                // if it is a full measure rest:
+                if (note.parentVoiceEntry.parentStaffEntry.parentMeasure.parentSourceMeasure.Duration.RealValue <= frac.RealValue) {
+                    duration = "w";
+                    numDots = 0;
+                    // If it's a whole rest we want it smack in the middle. Apparently there is still an issue in vexflow:
+                    // https://github.com/0xfe/vexflow/issues/579 The author reports that he needs to add some negative x shift
+                    // if the measure has no modifiers.
+                    alignCenter = true;
+                    xShift = -25; // TODO: Either replace by EngravingRules entry or find a way to make it dependent on the modifiers
+                }
+                keys = ["b/4"];
+                duration += "r";
+                break;
+            }
+
+            const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
             keys.push(pitch[0]);
             accidentals.push(pitch[1]);
             if (!vfClefType) {
                 const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
                 vfClefType = vfClef.type;
             }
-            if (numDots < note.numberOfDots) {
-                numDots = note.numberOfDots;
-            }
         }
+
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             duration += "d";
         }
 
         const vfnote: Vex.Flow.StaveNote = new Vex.Flow.StaveNote({
+            align_center: alignCenter,
             auto_stem: true,
             clef: vfClefType,
             duration: duration,
-            keys: keys,
+            keys: keys
         });
-        const wantedStemDirection: StemDirectionType = notes[0].sourceNote.ParentVoiceEntry.StemDirection;
-        switch (wantedStemDirection) {
-            case(StemDirectionType.Up):
-                vfnote.setStemDirection(Vex.Flow.Stem.UP);
-                break;
-            case (StemDirectionType.Down):
-                vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
-                break;
-            default:
-                break;
+
+        vfnote.x_shift = xShift;
+
+        if (gve.parentVoiceEntry !== undefined) {
+            const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.StemDirection;
+            switch (wantedStemDirection) {
+                case(StemDirectionType.Up):
+                    vfnote.setStemDirection(Vex.Flow.Stem.UP);
+                    break;
+                case (StemDirectionType.Down):
+                    vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
+                    break;
+                default:
+                    break;
+            }
         }
 
         for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
@@ -286,7 +322,7 @@ export class VexFlowConverter {
 
         // Make sure size is either "default" or "small"
         if (size !== "default" && size !== "small") {
-            Logging.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
+            log.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
             size = "default";
         }
 
@@ -307,7 +343,7 @@ export class VexFlowConverter {
                         break;
                     default:
                         type = "treble";
-                        Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
+                        log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
                 }
                 break;
 
@@ -325,7 +361,7 @@ export class VexFlowConverter {
                       break;
                   default:
                       type = "bass";
-                      Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
+                      log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
                 }
                 break;
 
@@ -346,7 +382,7 @@ export class VexFlowConverter {
                       break;
                   default:
                       type = "alto";
-                      Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
+                      log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
                 }
                 break;
 
@@ -362,14 +398,11 @@ export class VexFlowConverter {
             default:
         }
 
-        switch (clef.OctaveOffset) {
-            case 1:
-                annotation = "8va";
-                break;
-            case -1:
-                annotation = "8vb";
-                break;
-            default:
+        // annotations in vexflow don't allow bass and 8va. No matter the offset :(
+        if (clef.OctaveOffset === 1 && type !== "bass" ) {
+            annotation = "8va";
+        } else if (clef.OctaveOffset === -1) {
+            annotation = "8vb";
         }
         return { type, size, annotation };
     }

+ 2 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts

@@ -1,18 +1,18 @@
 import Vex = require("vexflow");
 import {GraphicalNote} from "../GraphicalNote";
 import {Note} from "../../VoiceData/Note";
-import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {VexFlowConverter} from "./VexFlowConverter";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 
 /**
  * The VexFlow version of a [[GraphicalNote]].
  */
 export class VexFlowGraphicalNote extends GraphicalNote {
-    constructor(note: Note, parent: GraphicalStaffEntry, activeClef: ClefInstruction,
+    constructor(note: Note, parent: GraphicalVoiceEntry, activeClef: ClefInstruction,
                 octaveShift: OctaveEnum = OctaveEnum.NONE,  graphicalNoteLength: Fraction = undefined) {
         super(note, parent, graphicalNoteLength);
         this.clef = activeClef;

+ 13 - 16
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -22,6 +22,9 @@ import {GraphicalChordSymbolContainer} from "../GraphicalChordSymbolContainer";
 import {GraphicalLabel} from "../GraphicalLabel";
 import {EngravingRules} from "../EngravingRules";
 import { TechnicalInstruction } from "../../VoiceData/Instructions/TechnicalInstruction";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
+import { VoiceEntry } from "../../VoiceData/VoiceEntry";
+import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 
 export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
     /**
@@ -86,6 +89,10 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
         return new VexFlowStaffEntry(<VexFlowMeasure>measure, undefined, <VexFlowStaffEntry>staffEntryParent);
     }
 
+    public createVoiceEntry(parentVoiceEntry: VoiceEntry, parentStaffEntry: GraphicalStaffEntry): GraphicalVoiceEntry {
+        return new VexFlowVoiceEntry(parentVoiceEntry, parentStaffEntry);
+    }
+
     /**
      * Create a Graphical Note for given note and clef and as part of graphicalStaffEntry.
      * @param note
@@ -95,34 +102,24 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      * @param octaveShift   The currently active octave transposition enum, needed for positioning the note vertically
      * @returns {GraphicalNote}
      */
-    public createNote(note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+    public createNote(note: Note, graphicalVoiceEntry: GraphicalVoiceEntry,
                       activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE,  graphicalNoteLength: Fraction = undefined): GraphicalNote {
-        // Creates the note:
-        const graphicalNote: GraphicalNote = new VexFlowGraphicalNote(note, graphicalStaffEntry, activeClef, octaveShift, graphicalNoteLength);
-        if (note.ParentVoiceEntry !== undefined) {
-            // Adds the note to the right (graphical) voice (mynotes)
-            const voiceID: number = note.ParentVoiceEntry.ParentVoice.VoiceId;
-            const mynotes: { [id: number]: GraphicalNote[]; } = (graphicalStaffEntry as VexFlowStaffEntry).graphicalNotes;
-            if (!(voiceID in mynotes)) {
-                mynotes[voiceID] = [];
-            }
-            mynotes[voiceID].push(graphicalNote);
-        }
-        return graphicalNote;
+        // Creates and returns the note:
+        return new VexFlowGraphicalNote(note, graphicalVoiceEntry, activeClef, octaveShift, graphicalNoteLength);
     }
 
     /**
      * Create a Graphical Grace Note (smaller head, stem...) for given note and clef and as part of graphicalStaffEntry.
      * @param note
      * @param numberOfDots
-     * @param graphicalStaffEntry
+     * @param graphicalVoiceEntry
      * @param activeClef
      * @param octaveShift
      * @returns {GraphicalNote}
      */
-    public createGraceNote(note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+    public createGraceNote(note: Note, graphicalVoiceEntry: GraphicalVoiceEntry,
                            activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE): GraphicalNote {
-        return new VexFlowGraphicalNote(note, graphicalStaffEntry, activeClef, octaveShift);
+        return new VexFlowGraphicalNote(note, graphicalVoiceEntry, activeClef, octaveShift);
     }
 
     /**

+ 158 - 67
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -14,12 +14,16 @@ import {GraphicalNote} from "../GraphicalNote";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import StaveConnector = Vex.Flow.StaveConnector;
 import StaveNote = Vex.Flow.StaveNote;
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {Tuplet} from "../../VoiceData/Tuplet";
-import { RepetitionInstructionEnum } from "../../VoiceData/Instructions/RepetitionInstruction";
-import { SystemLinePosition } from "../SystemLinePosition";
-import { StemDirectionType } from "../../VoiceData/VoiceEntry";
+import {RepetitionInstructionEnum} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {SystemLinePosition} from "../SystemLinePosition";
+import {StemDirectionType} from "../../VoiceData/VoiceEntry";
+import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
+import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
+import {Fraction} from "../../../Common/DataObjects/Fraction";
+import { Voice } from "../../VoiceData/Voice";
 
 export class VexFlowMeasure extends StaffMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -44,11 +48,11 @@ export class VexFlowMeasure extends StaffMeasure {
     // VexFlow StaveConnectors (vertical lines)
     private connectors: Vex.Flow.StaveConnector[] = [];
     // Intermediate object to construct beams
-    private beams: { [voiceID: number]: [Beam, VexFlowStaffEntry[]][]; } = {};
+    private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]][]; } = {};
     // VexFlow Beams
     private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
     // Intermediate object to construct tuplets
-    private tuplets: { [voiceID: number]: [Tuplet, VexFlowStaffEntry[]][]; } = {};
+    private tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
     // VexFlow Tuplets
     private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
 
@@ -274,18 +278,9 @@ export class VexFlowMeasure extends StaffMeasure {
      * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
      */
     public layoutSymbols(): void {
-        //this.stave.format();
+        // vexflow does the x-layout
     }
 
-    //public addGraphicalStaffEntry(entry: VexFlowStaffEntry): void {
-    //    super.addGraphicalStaffEntry(entry);
-    //}
-    //
-    //public addGraphicalStaffEntryAtTimestamp(entry: VexFlowStaffEntry): void {
-    //    super.addGraphicalStaffEntryAtTimestamp(entry);
-    //    // TODO
-    //}
-
     /**
      * Draw this measure on a VexFlow CanvasContext
      * @param ctx
@@ -342,17 +337,119 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     /**
+     * Returns all the voices that are present in this measure
+     */
+    public getVoicesWithinMeasure(): Voice[] {
+        const voices: Voice[] = [];
+        for (const gse of this.staffEntries) {
+           for (const gve of gse.graphicalVoiceEntries) {
+                if (voices.indexOf(gve.parentVoiceEntry.ParentVoice) === -1) {
+                    voices.push(gve.parentVoiceEntry.ParentVoice);
+                }
+            }
+        }
+        return voices;
+    }
+
+    /**
+     * Returns all the graphicalVoiceEntries of a given Voice.
+     * @param voice the voice for which the graphicalVoiceEntries shall be returned.
+     */
+    public getGraphicalVoiceEntriesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
+        const voiceEntries: GraphicalVoiceEntry[] = [];
+        for (const gse of this.staffEntries) {
+           for (const gve of gse.graphicalVoiceEntries) {
+                if (gve.parentVoiceEntry.ParentVoice === voice) {
+                    voiceEntries.push(gve);
+                }
+            }
+        }
+        return voiceEntries;
+    }
+
+    /**
+     * Finds the gaps between the existing notes within a measure.
+     * Problem here is, that the graphicalVoiceEntry does not exist yet and
+     * that Tied notes are not present in the normal voiceEntries.
+     * To handle this, calculation with absolute timestamps is needed.
+     * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
+     * @param voice the voice for which the ghost notes shall be searched.
+     */
+    private getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
+        let latestVoiceTimestamp: Fraction = undefined;
+        const gvEntries: GraphicalVoiceEntry[] = this.getGraphicalVoiceEntriesPerVoice(voice);
+        for (let idx: number = 0, len: number = gvEntries.length; idx < len; ++idx) {
+            const gve: GraphicalVoiceEntry = gvEntries[idx];
+            const gNotesStartTimestamp: Fraction = gve.notes[0].sourceNote.getAbsoluteTimestamp();
+            // find the voiceEntry end timestamp:
+            let gNotesEndTimestamp: Fraction = new Fraction();
+            for (const graphicalNote of gve.notes) {
+                const noteEnd: Fraction  = Fraction.plus(graphicalNote.sourceNote.getAbsoluteTimestamp(), graphicalNote.sourceNote.Length);
+                if (gNotesEndTimestamp < noteEnd) {
+                    gNotesEndTimestamp = noteEnd;
+                }
+            }
+
+            // check if this voice has just been found the first time:
+            if (latestVoiceTimestamp === undefined) {
+
+                // if this voice is new, check for a gap from measure start to the start of the current voice entry:
+                const gapFromMeasureStart: Fraction = Fraction.minus(gNotesStartTimestamp, this.parentSourceMeasure.AbsoluteTimestamp);
+                if (gapFromMeasureStart.RealValue > 0) {
+                    log.debug("Ghost Found at start");
+                    const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(gapFromMeasureStart);
+                    const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
+                    ghostGve.vfStaveNote = vfghost;
+                    gvEntries.splice(0, 0, ghostGve);
+                    idx++;
+                }
+            } else {
+                // get the length of the empty space between notes:
+                const inBetweenLength: Fraction = Fraction.minus(gNotesStartTimestamp, latestVoiceTimestamp);
+
+                if (inBetweenLength.RealValue > 0) {
+                    log.debug("Ghost Found in between");
+                    const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(inBetweenLength);
+                    const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
+                    ghostGve.vfStaveNote = vfghost;
+                    // add element before current element:
+                    gvEntries.splice(idx, 0, ghostGve);
+                    // and increase index, as we added an element:
+                    idx++;
+                }
+            }
+
+            // finally set the latest timestamp of this voice to the end timestamp of the longest note in the current voiceEntry:
+            latestVoiceTimestamp = gNotesEndTimestamp;
+        }
+
+        const measureEndTimestamp: Fraction = Fraction.plus(this.parentSourceMeasure.AbsoluteTimestamp, this.parentSourceMeasure.Duration);
+        const restLength: Fraction = Fraction.minus(measureEndTimestamp, latestVoiceTimestamp);
+        if (restLength.RealValue > 0) {
+            // fill the gap with a rest ghost note
+            // starting from lastFraction
+            // with length restLength:
+            log.debug("Ghost Found at end");
+            const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(restLength);
+            const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
+            ghostGve.vfStaveNote = vfghost;
+            gvEntries.push(ghostGve);
+        }
+        return gvEntries;
+    }
+
+    /**
      * Add a note to a beam
      * @param graphicalNote
      * @param beam
      */
     public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
         const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
-        let beams: [Beam, VexFlowStaffEntry[]][] = this.beams[voiceID];
+        let beams: [Beam, VexFlowVoiceEntry[]][] = this.beams[voiceID];
         if (beams === undefined) {
             beams = this.beams[voiceID] = [];
         }
-        let data: [Beam, VexFlowStaffEntry[]];
+        let data: [Beam, VexFlowVoiceEntry[]];
         for (const mybeam of beams) {
             if (mybeam[0] === beam) {
                 data = mybeam;
@@ -362,7 +459,7 @@ export class VexFlowMeasure extends StaffMeasure {
             data = [beam, []];
             beams.push(data);
         }
-        const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
+        const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
         if (data[1].indexOf(parent) < 0) {
             data[1].push(parent);
         }
@@ -371,11 +468,11 @@ export class VexFlowMeasure extends StaffMeasure {
     public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
         const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
         tuplet = graphicalNote.sourceNote.NoteTuplet;
-        let tuplets: [Tuplet, VexFlowStaffEntry[]][] = this.tuplets[voiceID];
+        let tuplets: [Tuplet, VexFlowVoiceEntry[]][] = this.tuplets[voiceID];
         if (tuplets === undefined) {
             tuplets = this.tuplets[voiceID] = [];
         }
-        let currentTupletBuilder: [Tuplet, VexFlowStaffEntry[]];
+        let currentTupletBuilder: [Tuplet, VexFlowVoiceEntry[]];
         for (const t of tuplets) {
             if (t[0] === tuplet) {
                 currentTupletBuilder = t;
@@ -385,7 +482,7 @@ export class VexFlowMeasure extends StaffMeasure {
             currentTupletBuilder = [tuplet, []];
             tuplets.push(currentTupletBuilder);
         }
-        const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
+        const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
         if (currentTupletBuilder[1].indexOf(parent) < 0) {
             currentTupletBuilder[1].push(parent);
         }
@@ -407,11 +504,18 @@ export class VexFlowMeasure extends StaffMeasure {
                 }
                 for (const beam of this.beams[voiceID]) {
                     const notes: Vex.Flow.StaveNote[] = [];
-                    const staffEntries: VexFlowStaffEntry[] = beam[1];
-                    const autoStemBeam: boolean = staffEntries[0].graphicalNotes[voiceID][0].sourceNote.
-                                                    ParentVoiceEntry.StemDirection === StemDirectionType.Undefined;
-                    for (const entry of staffEntries) {
-                        const note: Vex.Flow.StaveNote = (<VexFlowStaffEntry>entry).vfNotes[voiceID];
+                    const psBeam: Beam = beam[0];
+                    const voiceEntries: VexFlowVoiceEntry[] = beam[1];
+
+                    let autoStemBeam: boolean = true;
+                    for (const gve of voiceEntries) {
+                        if (gve.parentVoiceEntry.ParentVoice === psBeam.Notes[0].ParentVoiceEntry.ParentVoice) {
+                            autoStemBeam = gve.parentVoiceEntry.StemDirection === StemDirectionType.Undefined;
+                        }
+                    }
+
+                    for (const entry of voiceEntries) {
+                        const note: Vex.Flow.StaveNote = ((<VexFlowVoiceEntry>entry).vfStaveNote as StaveNote);
                         if (note !== undefined) {
                           notes.push(note);
                         }
@@ -424,7 +528,7 @@ export class VexFlowMeasure extends StaffMeasure {
                         //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
                         // }
                     } else {
-                        Logging.log("Warning! Beam with no notes!");
+                        log.debug("Warning! Beam with no notes!");
                     }
                 }
             }
@@ -447,9 +551,9 @@ export class VexFlowMeasure extends StaffMeasure {
                 }
                 for (const tupletBuilder of this.tuplets[voiceID]) {
                     const tupletStaveNotes: Vex.Flow.StaveNote[] = [];
-                    const tupletStaffEntries: VexFlowStaffEntry[] = tupletBuilder[1];
-                    for (const tupletStaffEntry of tupletStaffEntries) {
-                      tupletStaveNotes.push((tupletStaffEntry).vfNotes[voiceID]);
+                    const tupletVoiceEntries: VexFlowVoiceEntry[] = tupletBuilder[1];
+                    for (const tupletVoiceEntry of tupletVoiceEntries) {
+                      tupletStaveNotes.push(((tupletVoiceEntry).vfStaveNote as StaveNote));
                     }
                     if (tupletStaveNotes.length > 1) {
                       const notesOccupied: number = 2;
@@ -459,7 +563,7 @@ export class VexFlowMeasure extends StaffMeasure {
                                                             num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
                                                           }));
                     } else {
-                        Logging.log("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
+                        log.debug("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
                     }
                 }
             }
@@ -471,39 +575,30 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     public staffMeasureCreatedCalculations(): void {
-        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
-            const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
-
-            // create vex flow Notes:
-            const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
-            for (const voiceID in gnotes) {
-                if (gnotes.hasOwnProperty(voiceID)) {
-                    const vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
-                    (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
-                }
+        for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
+            // create vex flow Stave Notes:
+            for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
+                (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
             }
         }
 
         this.finalizeBeams();
         this.finalizeTuplets();
 
-        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
-            const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
-            const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
-            // create vex flow voices and add tickables to it:
-            const vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = this.vfVoices;
-            for (const voiceID in gnotes) {
-                if (gnotes.hasOwnProperty(voiceID)) {
-                    if (!(voiceID in vfVoices)) {
-                        vfVoices[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);
-                    }
+        const voices: Voice[] = this.getVoicesWithinMeasure();
 
-                    vfVoices[voiceID].addTickable(graphicalStaffEntry.vfNotes[voiceID]);
-                }
+        for (const voice of voices) {
+            // 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);
+
+            const restFilledEntries: GraphicalVoiceEntry[] =  this.getRestFilledVexFlowStaveNotesPerVoice(voice);
+            // create vex flow voices and add tickables to it:
+            for (const voiceEntry of restFilledEntries) {
+                this.vfVoices[voice.VoiceId].addTickable((voiceEntry as VexFlowVoiceEntry).vfStaveNote);
             }
         }
         this.createArticulations();
@@ -513,13 +608,11 @@ export class VexFlowMeasure extends StaffMeasure {
         for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
             const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
 
-            // create vex flow Notes:
-            const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
-            for (const voiceID in gnotes) {
-                if (gnotes.hasOwnProperty(voiceID)) {
-                    const vfnote: StaveNote = (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID];
-                    VexFlowConverter.generateArticulations(vfnote, gnotes[voiceID][0].sourceNote.ParentVoiceEntry.Articulations);
-                }
+            // create vex flow articulation:
+            const graphicalVoiceEntries: GraphicalVoiceEntry[] = graphicalStaffEntry.graphicalVoiceEntries;
+            for (const gve of graphicalVoiceEntries) {
+                const vfStaveNote: StaveNote = ((gve as VexFlowVoiceEntry).vfStaveNote as StaveNote);
+                VexFlowConverter.generateArticulations(vfStaveNote, gve.notes[0].sourceNote.ParentVoiceEntry.Articulations);
             }
         }
     }
@@ -561,7 +654,5 @@ export class VexFlowMeasure extends StaffMeasure {
 
         this.beginInstructionsWidth = beginInstructionsWidth / unitInPixels;
         this.endInstructionsWidth = endInstructionsWidth / unitInPixels;
-        //this.beginInstructionsWidth =  (this.stave.getNoteStartX() - this.stave.getX()) / unitInPixels;
-        //this.endInstructionsWidth = (this.stave.getX() + this.stave.getWidth() - this.stave.getNoteEndX()) / unitInPixels;
     }
 }

+ 24 - 13
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -24,7 +24,7 @@ import {VexFlowMeasure} from "./VexFlowMeasure";
 import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
 
 import Vex = require("vexflow");
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
@@ -35,8 +35,10 @@ import {GraphicalLyricWord} from "../GraphicalLyricWord";
 import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
+
   constructor() {
-    super(new VexFlowGraphicalSymbolFactory());
+    super();
+    MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
     MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
   }
 
@@ -97,7 +99,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             }
         }
         if (voices.length === 0) {
-            Logging.warn("Found a measure with no voices... Continuing anyway.", mvoices);
+            log.warn("Found a measure with no voices... Continuing anyway.", mvoices);
             continue;
         }
         formatter.joinVoices(voices);
@@ -218,37 +220,46 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
      */
   protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
     const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
+    const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
+
     let vfStartNote: Vex.Flow.StaveNote = undefined;
+    let startNoteIndexInTie: number = 0;
     if (startNote !== undefined) {
       vfStartNote = startNote.vfnote[0];
+      startNoteIndexInTie = startNote.vfnote[1];
     }
 
-    const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
     let vfEndNote: Vex.Flow.StaveNote = undefined;
+    let endNoteIndexInTie: number = 0;
     if (endNote !== undefined) {
       vfEndNote = endNote.vfnote[0];
+      endNoteIndexInTie = endNote.vfnote[1];
     }
 
-
     if (tieIsAtSystemBreak) {
       // split tie into two ties:
       const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-        first_note: vfStartNote,
+        first_indices: [startNoteIndexInTie],
+        first_note: vfStartNote
       });
-      const measure1: VexFlowMeasure = (startNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
+      const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
       measure1.vfTies.push(vfTie1);
 
       const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-        last_note: vfEndNote,
+        last_indices: [endNoteIndexInTie],
+        last_note: vfEndNote
       });
-      const measure2: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
+      const measure2: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
       measure2.vfTies.push(vfTie2);
     } else {
+      // normal case
       const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
+        first_indices: [startNoteIndexInTie],
         first_note: vfStartNote,
-        last_note: vfEndNote,
+        last_indices: [endNoteIndexInTie],
+        last_note: vfEndNote
       });
-      const measure: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
+      const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
       measure.vfTies.push(vfTie);
     }
   }
@@ -316,7 +327,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    * @param openBeams a list of all currently open beams
    */
   protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
-    (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
+    (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
   }
 
     protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
@@ -399,6 +410,6 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
    * @param openTuplets a list of all currently open tuplets
    */
   protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
-    (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
+    (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
   }
 }

+ 14 - 14
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -1,39 +1,39 @@
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {SourceStaffEntry} from "../../VoiceData/SourceStaffEntry";
-import {GraphicalNote} from "../GraphicalNote";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
+import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
         super(measure, sourceStaffEntry, staffEntryParent);
     }
 
-    // The Graphical Notes belonging to this StaffEntry, sorted by voiceID
-    public graphicalNotes: { [voiceID: number]: GraphicalNote[]; } = {};
-    // The corresponding VexFlow.StaveNotes
-    public vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = {};
-
     /**
      * Calculates the staff entry positions from the VexFlow stave information and the tickabels inside the staff.
      * This is needed in order to set the OSMD staff entries (which are almost the same as tickables) to the correct positionts.
      * It is also needed to be done after formatting!
      */
     public calculateXPosition(): void {
-        const vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = this.vfNotes;
         const stave: Vex.Flow.Stave = (this.parentMeasure as VexFlowMeasure).getVFStave();
         let tickablePosition: number = 0;
         let numberOfValidTickables: number = 0;
-        for (const voiceId in vfNotes) {
-            if (vfNotes.hasOwnProperty(voiceId)) {
-                const tickable: Vex.Flow.StaveNote = vfNotes[voiceId];
-                // This will let the tickable know how to calculate it's bounding box
-                tickable.setStave(stave);
+        for (const gve of this.graphicalVoiceEntries) {
+            const tickable: Vex.Flow.StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
+            // This will let the tickable know how to calculate it's bounding box
+            tickable.setStave(stave);
+            // The middle of the tickable is also the OSMD BoundingBox center
+            if (tickable.getAttribute("type") === "StaveNote") {
                 // The middle of the tickable is also the OSMD BoundingBox center
-                const staveNote: Vex.Flow.StaveNote = (<Vex.Flow.StaveNote>tickable);
+                const staveNote: Vex.Flow.StaveNote = tickable as Vex.Flow.StaveNote;
                 tickablePosition += staveNote.getNoteHeadEndX() - staveNote.getGlyphWidth() / 2;
-                numberOfValidTickables++;
+            } else {
+                console.log(tickable);
+                const ghostNote: Vex.Flow.GhostNote = tickable;
+                // That's basically the same as the StaveNote does.
+                tickablePosition = ghostNote.getAbsoluteX() + ghostNote.x_shift;
             }
+            numberOfValidTickables++;
         }
         tickablePosition = tickablePosition / numberOfValidTickables;
         // Calculate parent absolute position and reverse calculate the relative position

+ 11 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowVoiceEntry.ts

@@ -0,0 +1,11 @@
+import { VoiceEntry } from "../../VoiceData/VoiceEntry";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
+import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
+
+export class VexFlowVoiceEntry extends GraphicalVoiceEntry {
+    constructor(parentVoiceEntry: VoiceEntry, parentStaffEntry: GraphicalStaffEntry) {
+        super(parentVoiceEntry, parentStaffEntry);
+    }
+
+    public vfStaveNote: Vex.Flow.StemmableNote;
+}

+ 7 - 2
src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts

@@ -13,6 +13,8 @@ import {Staff} from "../VoiceData/Staff";
 import {StaffLine} from "../Graphical/StaffLine";
 import {StaffMeasure} from "../Graphical/StaffMeasure";
 import { TechnicalInstruction } from "../VoiceData/Instructions/TechnicalInstruction";
+import { GraphicalVoiceEntry } from "../Graphical/GraphicalVoiceEntry";
+import { VoiceEntry } from "../VoiceData/VoiceEntry";
 
 export interface IGraphicalSymbolFactory {
 
@@ -28,15 +30,18 @@ export interface IGraphicalSymbolFactory {
 
     createGraceStaffEntry(staffEntryParent: GraphicalStaffEntry, measure: StaffMeasure): GraphicalStaffEntry;
 
+    createVoiceEntry(parentVoiceEntry: VoiceEntry, parentStaffEntry: GraphicalStaffEntry): GraphicalVoiceEntry;
+
     createNote(
-        note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+        note: Note,
+        graphicalVoiceEntry: GraphicalVoiceEntry,
         activeClef: ClefInstruction,
         octaveShift: OctaveEnum,
         graphicalNoteLength: Fraction): GraphicalNote;
 
     createGraceNote(
         note: Note,
-        graphicalStaffEntry: GraphicalStaffEntry,
+        graphicalVoiceEntry: GraphicalVoiceEntry,
         activeClef: ClefInstruction,
         octaveShift: OctaveEnum): GraphicalNote;
 

+ 2 - 2
src/MusicalScore/MusicParts/MusicPartManagerIterator.ts

@@ -14,7 +14,7 @@ import {ContinuousDynamicExpression} from "../VoiceData/Expressions/ContinuousEx
 import {InstantaniousDynamicExpression} from "../VoiceData/Expressions/InstantaniousDynamicExpression";
 import {MultiTempoExpression} from "../VoiceData/Expressions/MultiTempoExpression";
 import {AbstractExpression} from "../VoiceData/Expressions/AbstractExpression";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 export class MusicPartManagerIterator {
     constructor(manager: MusicPartManager, startTimestamp?: Fraction, endTimestamp?: Fraction) {
@@ -47,7 +47,7 @@ export class MusicPartManagerIterator {
             }
             this.currentTempoChangingExpression = this.activeTempoExpression;
         } catch (err) {
-            Logging.log("MusicPartManagerIterator: " + err);
+            log.info("MusicPartManagerIterator: " + err);
         }
 
     }

+ 4 - 4
src/MusicalScore/MusicSheet.ts

@@ -16,7 +16,7 @@ import {EngravingRules} from "./Graphical/EngravingRules";
 import {NoteState} from "./Graphical/DrawingEnums";
 import {Note} from "./VoiceData/Note";
 import {VoiceEntry} from "./VoiceData/VoiceEntry";
-import {Logging} from "../Common/Logging";
+import * as log from "loglevel";
 
 // FIXME Andrea: Commented out some unnecessary/not-ported-yet code, have a look at (*)
 
@@ -400,7 +400,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
     //        }
     //        return repetitions;
     //    } catch (ex) {
-    //        Logging.log("MusicSheet.IRepetitions get: ", ex);
+    //        log.info("MusicSheet.IRepetitions get: ", ex);
     //        return undefined;
     //    }
     //
@@ -423,7 +423,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         try {
             return this.getFirstSourceMeasure().MeasureNumber;
         } catch (ex) {
-            Logging.log("MusicSheet.FirstMeasureNumber: ", ex);
+            log.info("MusicSheet.FirstMeasureNumber: ", ex);
             return 0;
         }
 
@@ -432,7 +432,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         try {
             return this.getLastSourceMeasure().MeasureNumber;
         } catch (ex) {
-            Logging.log("MusicSheet.LastMeasureNumber: ", ex);
+            log.info("MusicSheet.LastMeasureNumber: ", ex);
             return 0;
         }
 

+ 2 - 2
src/MusicalScore/MusicSource/Repetition.ts

@@ -4,7 +4,7 @@ import {Fraction} from "../../Common/DataObjects/Fraction";
 import {MusicSheet} from "../MusicSheet";
 import {RepetitionInstruction} from "../VoiceData/Instructions/RepetitionInstruction";
 import {PartListEntry} from "./PartListEntry";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 export class Repetition extends PartListEntry /*implements IRepetition*/ {
     constructor(musicSheet: MusicSheet, virtualOverallRepetition: boolean) {
@@ -84,7 +84,7 @@ export class Repetition extends PartListEntry /*implements IRepetition*/ {
                     this.numberOfEndings = endingNumber;
                 }
             } catch (err) {
-                Logging.error("Repetition: Exception.", err);
+                log.error("Repetition: Exception.", err);
             }
 
         }

+ 14 - 14
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -17,7 +17,7 @@ import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction";
 import {KeyEnum} from "../VoiceData/Instructions/KeyInstruction";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
 import { RepetitionInstructionReader } from "./MusicSymbolModules/RepetitionInstructionReader";
@@ -139,7 +139,7 @@ export class InstrumentReader {
             if (xmlNode.element("staff") !== undefined) {
               noteStaff = parseInt(xmlNode.element("staff").value, 10);
               if (isNaN(noteStaff)) {
-                Logging.debug("InstrumentReader.readNextXmlMeasure.get staff number");
+                log.debug("InstrumentReader.readNextXmlMeasure.get staff number");
                 noteStaff = 1;
               }
             }
@@ -172,13 +172,13 @@ export class InstrumentReader {
             } else {
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid Note Duration.");
               this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-              Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
               continue;
             }
           }
 
           const restNote: boolean = xmlNode.element("rest") !== undefined;
-          //Logging.log("New note found!", noteDivisions, noteDuration.toString(), restNote);
+          //log.info("New note found!", noteDivisions, noteDuration.toString(), restNote);
           const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
           let musicTimestamp: Fraction = currentFraction.clone();
           if (isChord) {
@@ -189,7 +189,7 @@ export class InstrumentReader {
             this.inSourceMeasureInstrumentIndex + noteStaff - 1,
             this.currentStaff
           ).staffEntry;
-          //Logging.log("currentStaffEntry", this.currentStaffEntry, this.currentMeasure.VerticalSourceStaffEntryContainers.length);
+          //log.info("currentStaffEntry", this.currentStaffEntry, this.currentMeasure.VerticalSourceStaffEntryContainers.length);
 
           if (!this.currentVoiceGenerator.hasVoiceEntry() || (!isChord && !isGraceNote && !lastNoteWasGrace) || (!lastNoteWasGrace && isGraceNote)) {
             this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, !restNote);
@@ -255,7 +255,7 @@ export class InstrumentReader {
             if (isNaN(this.divisions)) {
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
                                                                       "Invalid divisions value at Instrument: ");
-              Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
               this.divisions = this.readDivisionsFromNotes();
               if (this.divisions > 0) {
                 this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
@@ -380,7 +380,7 @@ export class InstrumentReader {
       }
       const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MeasureError", "Error while reading Measure.");
       this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-      Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
+      log.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
     }
 
     this.previousMeasure = this.currentMeasure;
@@ -589,7 +589,7 @@ export class InstrumentReader {
             );
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             line = 2;
-            Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+            log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
           }
 
         }
@@ -617,7 +617,7 @@ export class InstrumentReader {
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             clefEnum = ClefEnum.G;
             line = 2;
-            Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, e);
+            log.debug("InstrumentReader.addAbstractInstruction", errorMsg, e);
           }
 
         }
@@ -665,7 +665,7 @@ export class InstrumentReader {
           );
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           key = 0;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
       }
@@ -684,7 +684,7 @@ export class InstrumentReader {
           );
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           keyEnum = KeyEnum.major;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
       }
@@ -754,7 +754,7 @@ export class InstrumentReader {
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           num = 4;
           denom = 4;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
         if ((num === 4 && denom === 4) || (num === 2 && denom === 2)) {
@@ -978,7 +978,7 @@ export class InstrumentReader {
   //        );
   //        this.musicSheet.SheetErrors.pushTemp(errorMsg);
   //        directionStaffNumber = 1;
-  //        logging.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
+  //        log.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
   //      }
   //
   //    }
@@ -1012,7 +1012,7 @@ export class InstrumentReader {
               try {
                 noteDuration = parseInt(durationNode.value, 10);
               } catch (ex) {
-                Logging.debug("InstrumentReader.readDivisionsFromNotes", ex);
+                log.debug("InstrumentReader.readDivisionsFromNotes", ex);
                 continue;
               }
 

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

@@ -6,7 +6,7 @@ import {IXmlElement} from "../../Common/FileIO/Xml";
 import {Instrument} from "../Instrument";
 import {ITextTranslation} from "../Interfaces/ITextTranslation";
 import {MusicSheetReadingException} from "../Exceptions";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
 import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction";
@@ -63,7 +63,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         try {
             return this._createMusicSheet(root, path);
         } catch (e) {
-            Logging.log("MusicSheetReader.CreateMusicSheet", e);
+            log.info("MusicSheetReader.CreateMusicSheet", e);
         }
     }
 
@@ -498,7 +498,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                 const filenameSplits: string[] = filename.split(".", 1);
                 this.musicSheet.Title = new Label(filenameSplits[0]);
             } catch (ex) {
-                Logging.log("MusicSheetReader.pushSheetLabels: ", ex);
+                log.info("MusicSheetReader.pushSheetLabels: ", ex);
             }
 
         }
@@ -747,7 +747,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                                                 const result: number = <number>parseFloat(instrumentElement.value);
                                                 subInstrument.volume = result / 127.0;
                                             } catch (ex) {
-                                                Logging.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
+                                                log.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
                                             }
 
                                         } else if (instrumentElement.name === "pan") {
@@ -755,18 +755,18 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                                                 const result: number = <number>parseFloat(instrumentElement.value);
                                                 subInstrument.pan = result / 64.0;
                                             } catch (ex) {
-                                                Logging.debug("ExpressionReader.readExpressionParameters", "read pan", ex);
+                                                log.debug("ExpressionReader.readExpressionParameters", "read pan", ex);
                                             }
 
                                         }
                                     } catch (ex) {
-                                        Logging.log("MusicSheetReader.createInstrumentGroups midi settings: ", ex);
+                                        log.info("MusicSheetReader.createInstrumentGroups midi settings: ", ex);
                                     }
 
                                 }
                             }
                         } catch (ex) {
-                            Logging.log("MusicSheetReader.createInstrumentGroups: ", ex);
+                            log.info("MusicSheetReader.createInstrumentGroups: ", ex);
                         }
 
                     }

+ 147 - 253
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -1,34 +1,29 @@
-import {Instrument} from "../Instrument";
-import {LinkedVoice} from "../VoiceData/LinkedVoice";
-import {Voice} from "../VoiceData/Voice";
-import {MusicSheet} from "../MusicSheet";
-import {VoiceEntry} from "../VoiceData/VoiceEntry";
-import {Note} from "../VoiceData/Note";
-import {SourceMeasure} from "../VoiceData/SourceMeasure";
-import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
-import {Beam} from "../VoiceData/Beam";
-import {Tie} from "../VoiceData/Tie";
-import {Tuplet} from "../VoiceData/Tuplet";
-import {Fraction} from "../../Common/DataObjects/Fraction";
-//import {MusicSymbolModuleFactory} from "./InstrumentReader";
-import {IXmlElement} from "../../Common/FileIO/Xml";
-import {ITextTranslation} from "../Interfaces/ITextTranslation";
-import {ArticulationEnum} from "../VoiceData/VoiceEntry";
-import {Slur} from "../VoiceData/Expressions/ContinuousExpressions/Slur";
-import {LyricsEntry} from "../VoiceData/Lyrics/LyricsEntry";
-import {LyricsReader} from "../ScoreIO/MusicSymbolModules/LyricsReader";
-import {MusicSheetReadingException} from "../Exceptions";
-import {AccidentalEnum} from "../../Common/DataObjects/Pitch";
-import {NoteEnum} from "../../Common/DataObjects/Pitch";
-import {Staff} from "../VoiceData/Staff";
-import {StaffEntryLink} from "../VoiceData/StaffEntryLink";
-import {VerticalSourceStaffEntryContainer} from "../VoiceData/VerticalSourceStaffEntryContainer";
-import {Logging} from "../../Common/Logging";
-import {Pitch} from "../../Common/DataObjects/Pitch";
-import {IXmlAttribute} from "../../Common/FileIO/Xml";
-import {CollectionUtil} from "../../Util/CollectionUtil";
-import Dictionary from "typescript-collections/dist/lib/Dictionary";
-import {ArticulationReader} from "./MusicSymbolModules/ArticulationReader";
+import { Instrument } from "../Instrument";
+import { LinkedVoice } from "../VoiceData/LinkedVoice";
+import { Voice } from "../VoiceData/Voice";
+import { MusicSheet } from "../MusicSheet";
+import { VoiceEntry } from "../VoiceData/VoiceEntry";
+import { Note } from "../VoiceData/Note";
+import { SourceMeasure } from "../VoiceData/SourceMeasure";
+import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
+import { Beam } from "../VoiceData/Beam";
+import { Tie } from "../VoiceData/Tie";
+import { Tuplet } from "../VoiceData/Tuplet";
+import { Fraction } from "../../Common/DataObjects/Fraction";
+import { IXmlElement } from "../../Common/FileIO/Xml";
+import { ITextTranslation } from "../Interfaces/ITextTranslation";
+import { LyricsReader } from "../ScoreIO/MusicSymbolModules/LyricsReader";
+import { MusicSheetReadingException } from "../Exceptions";
+import { AccidentalEnum } from "../../Common/DataObjects/Pitch";
+import { NoteEnum } from "../../Common/DataObjects/Pitch";
+import { Staff } from "../VoiceData/Staff";
+import { StaffEntryLink } from "../VoiceData/StaffEntryLink";
+import { VerticalSourceStaffEntryContainer } from "../VoiceData/VerticalSourceStaffEntryContainer";
+import * as log from "loglevel";
+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
@@ -50,7 +45,7 @@ export class VoiceGenerator {
   }
 
   // private slurReader: SlurReader;
-    private lyricsReader: LyricsReader;
+  private lyricsReader: LyricsReader;
   private articulationReader: ArticulationReader;
   private musicSheet: MusicSheet;
   private voice: Voice;
@@ -114,15 +109,15 @@ export class VoiceGenerator {
               measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean): Note {
     this.currentStaffEntry = parentStaffEntry;
     this.currentMeasure = parentMeasure;
-    //Logging.debug("read called:", restNote);
+    //log.debug("read called:", restNote);
     try {
       this.currentNote = restNote
         ? this.addRestNote(noteDuration)
         : this.addSingleNote(noteNode, noteDuration, graceNote, chord, guitarPro);
 
       if (this.lyricsReader !== undefined && noteNode.elements("lyric") !== undefined) {
-          this.lyricsReader.addLyricEntry(noteNode.elements("lyric"), this.currentVoiceEntry);
-          this.voice.Parent.HasLyrics = true;
+        this.lyricsReader.addLyricEntry(noteNode.elements("lyric"), this.currentVoiceEntry);
+        this.voice.Parent.HasLyrics = true;
       }
       let hasTupletCommand: boolean = false;
       const notationNode: IXmlElement = noteNode.element("notations");
@@ -156,8 +151,8 @@ export class VoiceGenerator {
         const openTieDict: { [_: number]: Tie; } = this.openTieDict;
         for (const key in openTieDict) {
           if (openTieDict.hasOwnProperty(key)) {
-                        const tie: Tie = openTieDict[key];
-                        if (Fraction.plus(tie.Start.ParentStaffEntry.Timestamp, tie.Start.Length).lt(this.currentStaffEntry.Timestamp)) {
+            const tie: Tie = openTieDict[key];
+            if (Fraction.plus(tie.StartNote.ParentStaffEntry.Timestamp, tie.Duration).lt(this.currentStaffEntry.Timestamp)) {
               delete openTieDict[key];
             }
           }
@@ -169,10 +164,10 @@ export class VoiceGenerator {
         this.handleTimeModificationNode(noteNode);
       }
     } catch (err) {
-            const errorMsg: string = ITextTranslation.translateText(
+      const errorMsg: string = ITextTranslation.translateText(
         "ReaderErrorMessages/NoteError", "Ignored erroneous Note."
       );
-            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+      this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
     }
 
     return this.currentNote;
@@ -189,11 +184,11 @@ export class VoiceGenerator {
       && this.currentVoiceEntry.graceVoiceEntriesBefore !== undefined
       && this.currentVoiceEntry.graceVoiceEntriesBefore.length > 0
     ) {
-        const voice: Voice = this.currentVoiceEntry.ParentVoice;
-        const horizontalIndex: number = this.currentMeasure.VerticalSourceStaffEntryContainers.indexOf(this.currentStaffEntry.VerticalContainerParent);
-        const verticalIndex: number = this.currentStaffEntry.VerticalContainerParent.StaffEntries.indexOf(this.currentStaffEntry);
-        const previousStaffEntry: SourceStaffEntry = this.currentMeasure.getPreviousSourceStaffEntryFromIndex(verticalIndex, horizontalIndex);
-        if (previousStaffEntry !== undefined) {
+      const voice: Voice = this.currentVoiceEntry.ParentVoice;
+      const horizontalIndex: number = this.currentMeasure.VerticalSourceStaffEntryContainers.indexOf(this.currentStaffEntry.VerticalContainerParent);
+      const verticalIndex: number = this.currentStaffEntry.VerticalContainerParent.StaffEntries.indexOf(this.currentStaffEntry);
+      const previousStaffEntry: SourceStaffEntry = this.currentMeasure.getPreviousSourceStaffEntryFromIndex(verticalIndex, horizontalIndex);
+      if (previousStaffEntry !== undefined) {
         let previousVoiceEntry: VoiceEntry = undefined;
         for (let idx: number = 0, len: number = previousStaffEntry.VoiceEntries.length; idx < len; ++idx) {
           const voiceEntry: VoiceEntry = previousStaffEntry.VoiceEntries[idx];
@@ -245,12 +240,12 @@ export class VoiceGenerator {
   }
 
   public checkOpenTies(): void {
-    const openTieDict: {[key: number]: Tie} = this.openTieDict;
+    const openTieDict: { [key: number]: Tie } = this.openTieDict;
     for (const key in openTieDict) {
       if (openTieDict.hasOwnProperty(key)) {
-                const tie: Tie = openTieDict[key];
-                if (Fraction.plus(tie.Start.ParentStaffEntry.Timestamp, tie.Start.Length)
-            .lt(tie.Start.ParentStaffEntry.VerticalContainerParent.ParentMeasure.Duration)) {
+        const tie: Tie = openTieDict[key];
+        if (Fraction.plus(tie.StartNote.ParentStaffEntry.Timestamp, tie.Duration)
+          .lt(tie.StartNote.ParentStaffEntry.VerticalContainerParent.ParentMeasure.Duration)) {
           delete openTieDict[key];
         }
       }
@@ -298,10 +293,10 @@ export class VoiceGenerator {
       case "maxima":
         return new Fraction(8, 1);
       default: {
-                const errorMsg: string = ITextTranslation.translateText(
+        const errorMsg: string = ITextTranslation.translateText(
           "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
         );
-                throw new MusicSheetReadingException(errorMsg);
+        throw new MusicSheetReadingException(errorMsg);
       }
     }
   }
@@ -336,7 +331,7 @@ export class VoiceGenerator {
    * @returns {Note}
    */
   private addSingleNote(node: IXmlElement, noteDuration: Fraction, graceNote: boolean, chord: boolean, guitarPro: boolean): Note {
-    //Logging.debug("addSingleNote called");
+    //log.debug("addSingleNote called");
     let noteAlter: AccidentalEnum = AccidentalEnum.NONE;
     let noteStep: NoteEnum = NoteEnum.C;
     let noteOctave: number = 0;
@@ -353,21 +348,21 @@ export class VoiceGenerator {
               if (pitchElement.name === "step") {
                 noteStep = NoteEnum[pitchElement.value];
                 if (noteStep === undefined) {
-                    const errorMsg: string = ITextTranslation.translateText(
+                  const errorMsg: string = ITextTranslation.translateText(
                     "ReaderErrorMessages/NotePitchError",
                     "Invalid pitch while reading note."
                   );
-                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                    throw new MusicSheetReadingException(errorMsg, undefined);
+                  this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                  throw new MusicSheetReadingException(errorMsg, undefined);
                 }
               } else if (pitchElement.name === "alter") {
                 noteAlter = parseInt(pitchElement.value, 10);
                 if (isNaN(noteAlter)) {
-                                    const errorMsg: string = ITextTranslation.translateText(
+                  const errorMsg: string = ITextTranslation.translateText(
                     "ReaderErrorMessages/NoteAlterationError", "Invalid alteration while reading note."
                   );
-                                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                                    throw new MusicSheetReadingException(errorMsg, undefined);
+                  this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                  throw new MusicSheetReadingException(errorMsg, undefined);
                 }
 
               } else if (pitchElement.name === "octave") {
@@ -381,7 +376,7 @@ export class VoiceGenerator {
                 }
               }
             } catch (ex) {
-              Logging.log("VoiceGenerator.addSingleNote read Step: ", ex.message);
+              log.info("VoiceGenerator.addSingleNote read Step: ", ex.message);
             }
 
           }
@@ -403,7 +398,7 @@ export class VoiceGenerator {
           }
         }
       } catch (ex) {
-        Logging.log("VoiceGenerator.addSingleNote: ", ex);
+        log.info("VoiceGenerator.addSingleNote: ", ex);
       }
     }
 
@@ -536,8 +531,8 @@ export class VoiceGenerator {
       const verticalIndex: number = beamLastNoteStaffEntry.VerticalContainerParent.StaffEntries.indexOf(beamLastNoteStaffEntry);
       if (horizontalIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length - 1) {
         const nextStaffEntry: SourceStaffEntry = this.currentMeasure
-                                                             .VerticalSourceStaffEntryContainers[horizontalIndex + 1]
-                                                             .StaffEntries[verticalIndex];
+          .VerticalSourceStaffEntryContainers[horizontalIndex + 1]
+          .StaffEntries[verticalIndex];
         if (nextStaffEntry !== undefined) {
           for (let idx: number = 0, len: number = nextStaffEntry.VoiceEntries.length; idx < len; ++idx) {
             const voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
@@ -569,11 +564,11 @@ export class VoiceGenerator {
           note.Length = this.getNoteDurationFromType(type);
           note.Length.Numerator = 1;
         } catch (e) {
-                    const errorMsg: string = ITextTranslation.translateText(
+          const errorMsg: string = ITextTranslation.translateText(
             "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
           );
-                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                    throw new MusicSheetReadingException(errorMsg, e);
+          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+          throw new MusicSheetReadingException(errorMsg, e);
         }
 
       }
@@ -581,8 +576,8 @@ export class VoiceGenerator {
     const graceNode: IXmlElement = node.element("grace");
     if (graceNode !== undefined && graceNode.attributes()) {
       if (graceNode.attribute("slash")) {
-                const slash: string = graceNode.attribute("slash").value;
-                if (slash === "yes") {
+        const slash: string = graceNode.attribute("slash").value;
+        if (slash === "yes") {
           note.GraceNoteSlash = true;
         }
       }
@@ -627,10 +622,10 @@ export class VoiceGenerator {
       }
       const tupletNodeListArr: IXmlElement[] = tupletNodeList;
       for (let idx: number = 0, len: number = tupletNodeListArr.length; idx < len; ++idx) {
-                const tupletNode: IXmlElement = tupletNodeListArr[idx];
-                if (tupletNode !== undefined && tupletNode.attributes()) {
-                    const type: string = tupletNode.attribute("type").value;
-                    if (type === "start") {
+        const tupletNode: IXmlElement = tupletNodeListArr[idx];
+        if (tupletNode !== undefined && tupletNode.attributes()) {
+          const type: string = tupletNode.attribute("type").value;
+          if (type === "start") {
             let tupletNumber: number = 1;
             if (tupletNode.attribute("number")) {
               tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
@@ -639,11 +634,11 @@ export class VoiceGenerator {
             if (timeModNode !== undefined) {
               tupletLabelNumber = parseInt(timeModNode.value, 10);
               if (isNaN(tupletLabelNumber)) {
-                                const errorMsg: string = ITextTranslation.translateText(
+                const errorMsg: string = ITextTranslation.translateText(
                   "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
                 );
-                                this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                                throw new MusicSheetReadingException(errorMsg, undefined);
+                this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                throw new MusicSheetReadingException(errorMsg, undefined);
               }
 
             }
@@ -670,13 +665,13 @@ export class VoiceGenerator {
             }
             const tuplet: Tuplet = this.tupletDict[tupletNumber];
             if (tuplet !== undefined) {
-                            const subnotelist: Note[] = [];
-                            subnotelist.push(this.currentNote);
-                            tuplet.Notes.push(subnotelist);
-                            tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
-                            this.currentNote.NoteTuplet = tuplet;
-                            delete this.tupletDict[tupletNumber];
-                            if (Object.keys(this.tupletDict).length === 0) {
+              const subnotelist: Note[] = [];
+              subnotelist.push(this.currentNote);
+              tuplet.Notes.push(subnotelist);
+              tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
+              this.currentNote.NoteTuplet = tuplet;
+              delete this.tupletDict[tupletNumber];
+              if (Object.keys(this.tupletDict).length === 0) {
                 this.openTupletNumber = 0;
               } else if (Object.keys(this.tupletDict).length > 1) {
                 this.openTupletNumber--;
@@ -686,16 +681,16 @@ export class VoiceGenerator {
         }
       }
     } else if (tupletNodeList[0] !== undefined) {
-            const n: IXmlElement = tupletNodeList[0];
-            if (n.hasAttributes) {
-                const type: string = n.attribute("type").value;
-                let tupletnumber: number = 1;
-                if (n.attribute("number")) {
+      const n: IXmlElement = tupletNodeList[0];
+      if (n.hasAttributes) {
+        const type: string = n.attribute("type").value;
+        let tupletnumber: number = 1;
+        if (n.attribute("number")) {
           tupletnumber = parseInt(n.attribute("number").value, 10);
         }
-                const noTupletNumbering: boolean = isNaN(tupletnumber);
+        const noTupletNumbering: boolean = isNaN(tupletnumber);
 
-                if (type === "start") {
+        if (type === "start") {
           let tupletLabelNumber: number = 0;
           let timeModNode: IXmlElement = node.element("time-modification");
           if (timeModNode !== undefined) {
@@ -704,11 +699,11 @@ export class VoiceGenerator {
           if (timeModNode !== undefined) {
             tupletLabelNumber = parseInt(timeModNode.value, 10);
             if (isNaN(tupletLabelNumber)) {
-                            const errorMsg: string = ITextTranslation.translateText(
+              const errorMsg: string = ITextTranslation.translateText(
                 "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
               );
-                            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                            throw new MusicSheetReadingException(errorMsg);
+              this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+              throw new MusicSheetReadingException(errorMsg);
             }
 
           }
@@ -732,17 +727,17 @@ export class VoiceGenerator {
           }
           const tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
           if (tuplet !== undefined) {
-                        const subnotelist: Note[] = [];
-                        subnotelist.push(this.currentNote);
-                        tuplet.Notes.push(subnotelist);
-                        tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
-                        this.currentNote.NoteTuplet = tuplet;
-                        if (Object.keys(this.tupletDict).length === 0) {
+            const subnotelist: Note[] = [];
+            subnotelist.push(this.currentNote);
+            tuplet.Notes.push(subnotelist);
+            tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
+            this.currentNote.NoteTuplet = tuplet;
+            if (Object.keys(this.tupletDict).length === 0) {
               this.openTupletNumber = 0;
             } else if (Object.keys(this.tupletDict).length > 1) {
               this.openTupletNumber--;
             }
-                        delete this.tupletDict[tupletnumber];
+            delete this.tupletDict[tupletnumber];
           }
         }
       }
@@ -758,34 +753,34 @@ export class VoiceGenerator {
     if (this.tupletDict[this.openTupletNumber] !== undefined) {
       try {
         // Tuplet should already be created
-                const tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
-                const notes: Note[] = CollectionUtil.last(tuplet.Notes);
-                const lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;
-                let noteList: Note[];
-                if (lastTupletVoiceEntry.Timestamp.Equals(this.currentVoiceEntry.Timestamp)) {
+        const tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
+        const notes: Note[] = CollectionUtil.last(tuplet.Notes);
+        const lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;
+        let noteList: Note[];
+        if (lastTupletVoiceEntry.Timestamp.Equals(this.currentVoiceEntry.Timestamp)) {
           noteList = notes;
         } else {
           noteList = [];
           tuplet.Notes.push(noteList);
           tuplet.Fractions.push(this.getTupletNoteDurationFromType(noteNode));
         }
-                noteList.push(this.currentNote);
-                this.currentNote.NoteTuplet = tuplet;
+        noteList.push(this.currentNote);
+        this.currentNote.NoteTuplet = tuplet;
       } catch (ex) {
-                const errorMsg: string = ITextTranslation.translateText(
+        const errorMsg: string = ITextTranslation.translateText(
           "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
         );
-                this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                throw ex;
+        this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+        throw ex;
       }
 
     } else if (this.currentVoiceEntry.Notes.length > 0) {
-            const firstNote: Note = this.currentVoiceEntry.Notes[0];
-            if (firstNote.NoteTuplet !== undefined) {
-                const tuplet: Tuplet = firstNote.NoteTuplet;
-                const notes: Note[] = CollectionUtil.last(tuplet.Notes);
-                notes.push(this.currentNote);
-                this.currentNote.NoteTuplet = tuplet;
+      const firstNote: Note = this.currentVoiceEntry.Notes[0];
+      if (firstNote.NoteTuplet !== undefined) {
+        const tuplet: Tuplet = firstNote.NoteTuplet;
+        const notes: Note[] = CollectionUtil.last(tuplet.Notes);
+        notes.push(this.currentNote);
+        this.currentNote.NoteTuplet = tuplet;
       }
     }
   }
@@ -793,144 +788,43 @@ export class VoiceGenerator {
   private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction): void {
     if (tieNodeList !== undefined) {
       if (tieNodeList.length === 1) {
-                const tieNode: IXmlElement = tieNodeList[0];
-                if (tieNode !== undefined && tieNode.attributes()) {
-                    const type: string = tieNode.attribute("type").value;
-                    try {
+        const tieNode: IXmlElement = tieNodeList[0];
+        if (tieNode !== undefined && tieNode.attributes()) {
+          const type: string = tieNode.attribute("type").value;
+          try {
             if (type === "start") {
-                            const num: number = this.findCurrentNoteInTieDict(this.currentNote);
-                            if (num < 0) {
+              const num: number = this.findCurrentNoteInTieDict(this.currentNote);
+              if (num < 0) {
                 delete this.openTieDict[num];
               }
-                            const newTieNumber: number = this.getNextAvailableNumberForTie();
-                            const tie: Tie = new Tie(this.currentNote);
-                            this.openTieDict[newTieNumber] = tie;
-                            if (this.currentNote.NoteBeam !== undefined) {
-                if (this.currentNote.NoteBeam.Notes[0] === this.currentNote) {
-                  tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
-                } else {
-                  for (let idx: number = 0, len: number = this.currentNote.NoteBeam.Notes.length; idx < len; ++idx) {
-                                        const note: Note = this.currentNote.NoteBeam.Notes[idx];
-                                        if (note.NoteTie !== undefined && note.NoteTie !== tie && note.NoteTie.BeamStartTimestamp !== undefined) {
-                      tie.BeamStartTimestamp = note.NoteTie.BeamStartTimestamp;
-                      break;
-                    }
-                  }
-                  if (this.currentNote === CollectionUtil.last(this.currentNote.NoteBeam.Notes)) {
-                    tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
-                  }
-                }
-              }
+              const newTieNumber: number = this.getNextAvailableNumberForTie();
+              const tie: Tie = new Tie(this.currentNote);
+              this.openTieDict[newTieNumber] = tie;
             } else if (type === "stop") {
-                            const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
-                            const tie: Tie = this.openTieDict[tieNumber];
-                            if (tie !== undefined) {
-                                const tieStartNote: Note = tie.Start;
-                                tieStartNote.NoteTie = tie;
-                                tieStartNote.Length.Add(this.currentNote.Length);
-                                tie.Fractions.push(this.currentNote.Length);
-                                if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
+              const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
+              const tie: Tie = this.openTieDict[tieNumber];
+              if (tie !== undefined) {
+                tie.AddNote(this.currentNote);
+                if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
                   maxTieNoteFraction = Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length);
                 }
-                                const i: number = this.currentVoiceEntry.Notes.indexOf(this.currentNote);
-                                if (i !== -1) {
-                  this.currentVoiceEntry.Notes.splice(i, 1);
-                }
-                                if (
-                  this.currentVoiceEntry.Articulations.length === 1
-                  && this.currentVoiceEntry.Articulations[0] === ArticulationEnum.fermata
-                  && tieStartNote.ParentVoiceEntry.Articulations[ArticulationEnum.fermata] === undefined
-                ) {
-                  tieStartNote.ParentVoiceEntry.Articulations.push(ArticulationEnum.fermata);
-                }
-                                if (this.currentNote.NoteBeam !== undefined) {
-                                    const noteBeamIndex: number = this.currentNote.NoteBeam.Notes.indexOf(this.currentNote);
-                                    if (noteBeamIndex === 0 && tie.BeamStartTimestamp === undefined) {
-                    tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
-                  }
-                                    const noteBeam: Beam = this.currentNote.NoteBeam;
-                                    noteBeam.Notes[noteBeamIndex] = tieStartNote;
-                                    tie.TieBeam = noteBeam;
-                }
-                                if (this.currentNote.NoteTuplet !== undefined) {
-                                    const noteTupletIndex: number = this.currentNote.NoteTuplet.getNoteIndex(this.currentNote);
-                                    const index: number = this.currentNote.NoteTuplet.Notes[noteTupletIndex].indexOf(this.currentNote);
-                                    const noteTuplet: Tuplet = this.currentNote.NoteTuplet;
-                                    noteTuplet.Notes[noteTupletIndex][index] = tieStartNote;
-                                    tie.TieTuplet = noteTuplet;
-                }
-                                for (let idx: number = 0, len: number = this.currentNote.NoteSlurs.length; idx < len; ++idx) {
-                                    const slur: Slur = this.currentNote.NoteSlurs[idx];
-                                    if (slur.StartNote === this.currentNote) {
-                    slur.StartNote = tie.Start;
-                    slur.StartNote.NoteSlurs.push(slur);
-                  }
-                                    if (slur.EndNote === this.currentNote) {
-                    slur.EndNote = tie.Start;
-                    slur.EndNote.NoteSlurs.push(slur);
-                  }
-                }
-                                const lyricsEntries: Dictionary<number, LyricsEntry> = this.currentVoiceEntry.LyricsEntries;
-                                for (const lyricsEntry in lyricsEntries) {
-                  if (lyricsEntries.hasOwnProperty(lyricsEntry)) {
-                                        const val: LyricsEntry = this.currentVoiceEntry.LyricsEntries[lyricsEntry];
-                                        if (!tieStartNote.ParentVoiceEntry.LyricsEntries.hasOwnProperty(lyricsEntry)) {
-                      tieStartNote.ParentVoiceEntry.LyricsEntries[lyricsEntry] = val;
-                      val.Parent = tieStartNote.ParentVoiceEntry;
-                    }
-                  }
-                }
-                                delete this.openTieDict[tieNumber];
+                delete this.openTieDict[tieNumber];
               }
             }
           } catch (err) {
-                        const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
-                        this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
+            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           }
 
         }
       } else if (tieNodeList.length === 2) {
-                const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
-                if (tieNumber >= 0) {
-                    const tie: Tie = this.openTieDict[tieNumber];
-                    const tieStartNote: Note = tie.Start;
-                    tieStartNote.Length.Add(this.currentNote.Length);
-                    tie.Fractions.push(this.currentNote.Length);
-                    if (this.currentNote.NoteBeam !== undefined) {
-                        const noteBeamIndex: number = this.currentNote.NoteBeam.Notes.indexOf(this.currentNote);
-                        if (noteBeamIndex === 0 && tie.BeamStartTimestamp === undefined) {
-              tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
-            }
-                        const noteBeam: Beam = this.currentNote.NoteBeam;
-                        noteBeam.Notes[noteBeamIndex] = tieStartNote;
-                        tie.TieBeam = noteBeam;
-          }
-                    for (let idx: number = 0, len: number = this.currentNote.NoteSlurs.length; idx < len; ++idx) {
-                        const slur: Slur = this.currentNote.NoteSlurs[idx];
-                        if (slur.StartNote === this.currentNote) {
-              slur.StartNote = tie.Start;
-              slur.StartNote.NoteSlurs.push(slur);
-            }
-                        if (slur.EndNote === this.currentNote) {
-              slur.EndNote = tie.Start;
-              slur.EndNote.NoteSlurs.push(slur);
-            }
-          }
-
-                    this.currentVoiceEntry.LyricsEntries.forEach((key: number, value: LyricsEntry): void => {
-            if (!tieStartNote.ParentVoiceEntry.LyricsEntries.containsKey(key)) {
-              tieStartNote.ParentVoiceEntry.LyricsEntries.setValue(key, value);
-              value.Parent = tieStartNote.ParentVoiceEntry;
-            }
-          });
-                    if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
+        const tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
+        if (tieNumber >= 0) {
+          const tie: Tie = this.openTieDict[tieNumber];
+          tie.AddNote(this.currentNote);
+          if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
             maxTieNoteFraction = Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length);
           }
-          // delete currentNote from Notes:
-                    const i: number = this.currentVoiceEntry.Notes.indexOf(this.currentNote);
-                    if (i !== -1) {
-            this.currentVoiceEntry.Notes.splice(i, 1);
-          }
         }
       }
     }
@@ -941,17 +835,17 @@ export class VoiceGenerator {
    * @returns {number}
    */
   private getNextAvailableNumberForTie(): number {
-        const keys: string[] = Object.keys(this.openTieDict);
-        if (keys.length === 0) {
+    const keys: string[] = Object.keys(this.openTieDict);
+    if (keys.length === 0) {
       return 1;
     }
-        keys.sort((a, b) => (+a - +b)); // FIXME Andrea: test
-        for (let i: number = 0; i < keys.length; i++) {
+    keys.sort((a, b) => (+a - +b)); // FIXME Andrea: test
+    for (let i: number = 0; i < keys.length; i++) {
       if ("" + (i + 1) !== keys[i]) {
         return i + 1;
       }
     }
-        return +(keys[keys.length - 1]) + 1;
+    return +(keys[keys.length - 1]) + 1;
   }
 
   /**
@@ -960,16 +854,16 @@ export class VoiceGenerator {
    * @returns {number}
    */
   private findCurrentNoteInTieDict(candidateNote: Note): number {
-        const openTieDict: { [_: number]: Tie; } = this.openTieDict;
-        for (const key in openTieDict) {
+    const openTieDict: { [_: number]: Tie; } = this.openTieDict;
+    for (const key in openTieDict) {
       if (openTieDict.hasOwnProperty(key)) {
-                const tie: Tie = openTieDict[key];
-                if (tie.Start.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Start.Pitch.Octave === candidateNote.Pitch.Octave) {
+        const tie: Tie = openTieDict[key];
+        if (tie.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Pitch.Octave === candidateNote.Pitch.Octave) {
           return +key;
         }
       }
     }
-        return -1;
+    return -1;
   }
 
   /**
@@ -979,15 +873,15 @@ export class VoiceGenerator {
    */
   private getTupletNoteDurationFromType(xmlNode: IXmlElement): Fraction {
     if (xmlNode.element("type") !== undefined) {
-            const typeNode: IXmlElement = xmlNode.element("type");
-            if (typeNode !== undefined) {
-                const type: string = typeNode.value;
-                try {
+      const typeNode: IXmlElement = xmlNode.element("type");
+      if (typeNode !== undefined) {
+        const type: string = typeNode.value;
+        try {
           return this.getNoteDurationFromType(type);
         } catch (e) {
-                    const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid note duration.");
-                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                    throw new MusicSheetReadingException("", e);
+          const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid note duration.");
+          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+          throw new MusicSheetReadingException("", e);
         }
 
       }

+ 2 - 2
src/MusicalScore/SubInstrument.ts

@@ -1,6 +1,6 @@
 import {Instrument} from "./Instrument";
 import {MidiInstrument} from "./VoiceData/Instructions/ClefInstruction";
-import {Logging} from "../Common/Logging";
+import * as log from "loglevel";
 
 export class SubInstrument {
 
@@ -112,7 +112,7 @@ export class SubInstrument {
                 }
             }
         } catch (e) {
-            Logging.error("Error parsing MIDI Instrument. Default to Grand Piano.");
+            log.error("Error parsing MIDI Instrument. Default to Grand Piano.");
         }
         return "unnamed";
     }

+ 2 - 2
src/MusicalScore/VoiceData/Expressions/InstantaniousDynamicExpression.ts

@@ -3,7 +3,7 @@ import {MultiExpression} from "./MultiExpression";
 import {DynamicExpressionSymbolEnum} from "./DynamicExpressionSymbolEnum";
 //import {ArgumentOutOfRangeException} from "../../Exceptions";
 import {InvalidEnumArgumentException} from "../../Exceptions";
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 
 export class InstantaniousDynamicExpression extends AbstractExpression {
     constructor(dynamicExpression: string, soundDynamics: number, placement: PlacementEnum, staffNumber: number) {
@@ -143,7 +143,7 @@ export class InstantaniousDynamicExpression extends AbstractExpression {
         //    length += FontInfo.Info.getBoundingBox(symbol).Width;
         //}
         //return length;
-        Logging.debug("[Andrea] instantaniousDynamicExpression: not implemented: calculateLength!");
+        log.debug("[Andrea] instantaniousDynamicExpression: not implemented: calculateLength!");
         return 0.0;
     }
 

+ 7 - 1
src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts

@@ -126,10 +126,16 @@ export class RepetitionInstruction /*implements IComparable*/ {
             this.measureIndex !== other.measureIndex
             || this.type !== other.type
             || this.alignment !== other.alignment
-            || this.endingIndices.length !== other.endingIndices.length
         ) {
             return false;
         }
+        if (this.endingIndices === other.endingIndices) {
+            return true;
+        }
+        if (this.endingIndices === undefined || other.endingIndices === undefined ||
+            this.endingIndices.length !== other.endingIndices.length) {
+            return false;
+        }
         for (let i: number = 0; i < this.endingIndices.length; i++) {
             if (this.endingIndices[i] !== other.endingIndices[i]) {
                 return false;

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

@@ -107,29 +107,6 @@ export class Note {
         return this.Pitch === undefined;
     }
 
-    public calculateNoteLengthWithoutTie(): Fraction {
-        const withoutTieLength: Fraction = this.length.clone();
-        if (this.tie !== undefined) {
-            for (const fraction of this.tie.Fractions) {
-                withoutTieLength.Sub(fraction);
-            }
-        }
-        return withoutTieLength;
-    }
-    public calculateNoteOriginalLength(originalLength: Fraction = this.length): Fraction {
-        if (this.tie !== undefined) {
-            originalLength = this.calculateNoteLengthWithoutTie();
-        }
-        if (this.tuplet !== undefined) {
-            return this.length;
-        }
-        if (originalLength.Numerator > 1) {
-            const exp: number = Math.floor(Math.log(originalLength.Denominator) / Math.LN2) - this.calculateNumberOfNeededDots(originalLength);
-            originalLength.Denominator = Math.pow(2, exp);
-            originalLength.Numerator = 1;
-        }
-        return originalLength;
-    }
     public ToString(): string {
         if (this.pitch !== undefined) {
             return this.Pitch.ToString() + ", length: " + this.length.toString();
@@ -156,37 +133,6 @@ export class Note {
         }
         return false;
     }
-
-    //public calculateTailSymbol(): number {
-    //    let length: number = this.Length.RealValue;
-    //    if (this.NoteTuplet) {
-    //        length = this.NoteTuplet.Fractions[this.NoteTuplet.getNoteIndex(this)].RealValue;
-    //    }
-    //    if (length < 0.25 && length >= 0.125) {
-    //        return 8;
-    //    } else if (length < 0.125 && length >= 0.0625) {
-    //        return 16;
-    //    } else if (length < 0.0625 && length >= 0.03125) {
-    //        return 32;
-    //    } else {
-    //        return 64;
-    //    }
-    //}
-
-    /**
-     * Return the number of dots needed to represent the given [[Fraction]].
-     * @param fraction
-     * @returns {number}
-     */
-    private calculateNumberOfNeededDots(fraction: Fraction = this.length): number {
-        // FIXME (Andrea) Test if correct
-        if (this.tuplet === undefined) {
-            return Math.floor(Math.log(fraction.Numerator) / Math.LN2);
-        } else {
-            return 0;
-        }
-    }
-
 }
 
 export enum Appearance {

+ 1 - 1
src/MusicalScore/VoiceData/SourceMeasure.ts

@@ -214,7 +214,7 @@ export class SourceMeasure extends BaseIdClass {
                 }
             }
         }
-        //Logging.debug("created new container: ", staffEntry, this.verticalSourceStaffEntryContainers);
+        //log.debug("created new container: ", staffEntry, this.verticalSourceStaffEntryContainers);
         return {createdNewContainer: true, staffEntry: staffEntry};
     }
 

+ 15 - 11
src/MusicalScore/VoiceData/SourceStaffEntry.ts

@@ -197,11 +197,7 @@ export class SourceStaffEntry {
             const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
                 const note: Note = voiceEntry.Notes[idx2];
-                if (note.NoteTie !== undefined) {
-                    if (note.calculateNoteLengthWithoutTie().lt(duration)) {
-                        duration = note.calculateNoteLengthWithoutTie();
-                    }
-                } else if (note.Length.lt(duration)) {
+                if (note.Length.lt(duration)) {
                     duration = note.Length;
                 }
             }
@@ -216,14 +212,22 @@ export class SourceStaffEntry {
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
                 const note: Note = voiceEntry.Notes[idx2];
                 if (note.NoteTie !== undefined) {
-                    if (duration < note.calculateNoteLengthWithoutTie()) {
-                        duration = note.calculateNoteLengthWithoutTie();
-                        for (let idx3: number = 0, len3: number = note.NoteTie.Fractions.length; idx3 < len3; ++idx3) {
-                            const fraction: Fraction = note.NoteTie.Fractions[idx3];
-                            duration.Add(fraction);
+                    // only add notes from this and after this sse!!
+                    const tieRestDuration: Fraction = Fraction.createFromFraction(note.Length);
+                    let addFollowingNotes: boolean = false;
+                    for (const n of note.NoteTie.Notes) {
+                        if (n === note) {
+                            addFollowingNotes = true;
+                            continue;
                         }
+                        if (addFollowingNotes) {
+                            tieRestDuration.Add(n.Length);
+                        }
+                    }
+                    if (duration.lt(note.NoteTie.Duration)) {
+                        duration = note.NoteTie.Duration;
                     }
-                } else if (duration < note.Length) {
+                } else if (duration.lt(note.Length)) {
                     duration = note.Length;
                 }
             }

+ 22 - 60
src/MusicalScore/VoiceData/Tie.ts

@@ -1,8 +1,7 @@
 import {Note} from "./Note";
-import {Beam} from "./Beam";
-import {Fraction} from "../../Common/DataObjects/Fraction";
-import {Tuplet} from "./Tuplet";
 import {BaseIdClass} from "../../Util/BaseIdClass";
+import { Fraction } from "../../Common/DataObjects/Fraction";
+import { Pitch } from "../../Common/DataObjects/Pitch";
 
 /**
  * A [[Tie]] connects two notes of the same pitch and name, indicating that they have to be played as a single note.
@@ -11,70 +10,33 @@ export class Tie extends BaseIdClass {
 
     constructor(note: Note) {
         super();
-        this.start = note;
+        this.AddNote(note);
     }
 
-    private start: Note;
-    private tieBeam: Beam;
-    private beamStartTimestamp: Fraction;
-    private tieTuplet: Tuplet;
-    private fractions: Fraction[] = [];
-    private noteHasBeenCreated: boolean[] = [];
-    private baseNoteYPosition: number;
+    private notes: Note[] = [];
 
-    public get Start(): Note {
-        return this.start;
+    public get Notes(): Note[] {
+        return this.notes;
     }
-    public set Start(value: Note) {
-        this.start = value;
-    }
-    public get TieBeam(): Beam {
-        return this.tieBeam;
-    }
-    public set TieBeam(value: Beam) {
-        this.tieBeam = value;
-    }
-    public get BeamStartTimestamp(): Fraction {
-        return this.beamStartTimestamp;
-    }
-    public set BeamStartTimestamp(value: Fraction) {
-        this.beamStartTimestamp = value;
-    }
-    public get TieTuplet(): Tuplet {
-        return this.tieTuplet;
-    }
-    public set TieTuplet(value: Tuplet) {
-        this.tieTuplet = value;
-    }
-    public get Fractions(): Fraction[] {
-        return this.fractions;
-    }
-    public set Fractions(value: Fraction[]) {
-        this.fractions = value;
-    }
-    public get NoteHasBeenCreated(): boolean[] {
-        return this.noteHasBeenCreated;
-    }
-    public set NoteHasBeenCreated(value: boolean[]) {
-        this.noteHasBeenCreated = value;
-    }
-    public get BaseNoteYPosition(): number {
-        return this.baseNoteYPosition;
-    }
-    public set BaseNoteYPosition(value: number) {
-        this.baseNoteYPosition = value;
-    }
-    public initializeBoolList(): void {
-        this.noteHasBeenCreated = new Array(this.fractions.length);
+
+    public get StartNote(): Note {
+        return this.notes[0];
     }
-    public allGraphicalNotesHaveBeenCreated(): boolean {
-        for (const b of this.noteHasBeenCreated) {
-            if (!b) {
-                return false;
-            }
+
+    public get Duration(): Fraction {
+        const duration: Fraction = new Fraction();
+        for (const note of this.notes) {
+            duration.Add(note.Length);
         }
+        return duration;
+    }
 
-        return true;
+    public get Pitch(): Pitch {
+        return this.StartNote.Pitch;
     }
 
+    public AddNote(note: Note): void {
+        this.notes.push(note);
+        note.NoteTie = this;
+    }
 }

+ 1 - 1
src/MusicalScore/VoiceData/VoiceEntry.ts

@@ -159,7 +159,7 @@ export class VoiceEntry {
             return;
         }
         const baseNote: Note = this.notes[0];
-        const baselength: Fraction = baseNote.calculateNoteLengthWithoutTie();
+        const baselength: Fraction = baseNote.Length;
         const baseVoice: Voice = voiceEntryWithOrnament.ParentVoice;
         const baseTimestamp: Fraction = voiceEntryWithOrnament.Timestamp;
         let currentTimestamp: Fraction = Fraction.createFromFraction(baseTimestamp);

+ 2 - 1
src/OpenSheetMusicDisplay/AJAX.ts

@@ -11,6 +11,7 @@ export class AJAX {
      */
     public static ajax(url: string): Promise<string> {
         let xhttp: XMLHttpRequest;
+        const mimeType: string = url.indexOf(".mxl") > -1 ? "text/plain; charset=x-user-defined" : "application/xml";
         if (XMLHttpRequest) {
             xhttp = new XMLHttpRequest();
         } else if (ActiveXObject) {
@@ -32,7 +33,7 @@ export class AJAX {
                     }
                 }
             };
-            xhttp.overrideMimeType("text/plain; charset=x-user-defined");
+            xhttp.overrideMimeType(mimeType);
             xhttp.open("GET", url, true);
             xhttp.send();
         });

+ 1 - 1
webpack.dev.js

@@ -2,6 +2,6 @@ var merge = require('webpack-merge')
 var common = require('./webpack.common.js')
 
 module.exports = merge(common, {
-    devtool: 'source-map',
+    devtool: 'inline-source-map',
     mode: 'development'
 })