Browse Source

Merge develop

Benjamin Giesinger 7 years ago
parent
commit
3dd0f3dd96
77 changed files with 3994 additions and 2227 deletions
  1. 8 4
      .appveyor.yml
  2. 2 1
      .travis.yml
  3. 37 10
      CHANGELOG.md
  4. 1 1
      README.md
  5. 5 3
      bin/publish_gh_page.sh
  6. 2 2
      demo/index.html
  7. 32 26
      demo/index.js
  8. 97 6
      external/vexflow/vexflow.d.ts
  9. 6 3
      karma.conf.js
  10. 21 22
      package.json
  11. 29 1
      src/Common/DataObjects/Fraction.ts
  12. 2 1
      src/Common/DataObjects/Pitch.ts
  13. 0 21
      src/Common/Logging.ts
  14. 11 0
      src/Common/Strings/StringUtil.ts
  15. 4 9
      src/MusicalScore/Graphical/AccidentalCalculator.ts
  16. 2 2
      src/MusicalScore/Graphical/EngravingRules.ts
  17. 1 0
      src/MusicalScore/Graphical/GraphicalLyricEntry.ts
  18. 1 0
      src/MusicalScore/Graphical/GraphicalLyricWord.ts
  19. 10 30
      src/MusicalScore/Graphical/GraphicalMusicSheet.ts
  20. 5 19
      src/MusicalScore/Graphical/GraphicalNote.ts
  21. 75 124
      src/MusicalScore/Graphical/GraphicalStaffEntry.ts
  22. 2 4
      src/MusicalScore/Graphical/GraphicalStaffEntryLink.ts
  23. 22 0
      src/MusicalScore/Graphical/GraphicalVoiceEntry.ts
  24. 588 266
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  25. 13 0
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  26. 12 5
      src/MusicalScore/Graphical/MusicSystem.ts
  27. 4 9
      src/MusicalScore/Graphical/MusicSystemBuilder.ts
  28. 24 0
      src/MusicalScore/Graphical/StaffLine.ts
  29. 4 7
      src/MusicalScore/Graphical/StaffMeasure.ts
  30. 178 36
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  31. 2 2
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts
  32. 25 25
      src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts
  33. 14 0
      src/MusicalScore/Graphical/VexFlow/VexFlowInstrumentBrace.ts
  34. 20 8
      src/MusicalScore/Graphical/VexFlow/VexFlowInstrumentBracket.ts
  35. 307 110
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  36. 347 210
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  37. 28 2
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  38. 17 4
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts
  39. 32 20
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  40. 0 1
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffLine.ts
  41. 11 0
      src/MusicalScore/Graphical/VexFlow/VexFlowVoiceEntry.ts
  42. 2 0
      src/MusicalScore/Instrument.ts
  43. 8 0
      src/MusicalScore/Interfaces/IAfterSheetReadingModule.ts
  44. 9 3
      src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts
  45. 10 0
      src/MusicalScore/MusicParts/MusicPartManager.ts
  46. 34 2
      src/MusicalScore/MusicParts/MusicPartManagerIterator.ts
  47. 18 4
      src/MusicalScore/MusicSheet.ts
  48. 2 2
      src/MusicalScore/MusicSource/Repetition.ts
  49. 33 38
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  50. 28 33
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  51. 31 0
      src/MusicalScore/ScoreIO/MusicSymbolModuleFactory.ts
  52. 195 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts
  53. 140 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/LyricsReader.ts
  54. 99 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionCalculator.ts
  55. 380 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts
  56. 829 918
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  57. 4 2
      src/MusicalScore/SubInstrument.ts
  58. 2 2
      src/MusicalScore/VoiceData/Expressions/InstantaniousDynamicExpression.ts
  59. 0 4
      src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts
  60. 13 4
      src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts
  61. 8 1
      src/MusicalScore/VoiceData/Lyrics/LyricsEntry.ts
  62. 3 53
      src/MusicalScore/VoiceData/Note.ts
  63. 9 2
      src/MusicalScore/VoiceData/SourceMeasure.ts
  64. 15 11
      src/MusicalScore/VoiceData/SourceStaffEntry.ts
  65. 22 60
      src/MusicalScore/VoiceData/Tie.ts
  66. 16 1
      src/MusicalScore/VoiceData/VoiceEntry.ts
  67. 2 1
      src/OpenSheetMusicDisplay/AJAX.ts
  68. 11 11
      src/OpenSheetMusicDisplay/Cursor.ts
  69. 15 10
      src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
  70. 3 3
      test/Common/FileIO/Xml_Test.ts
  71. 30 30
      test/Common/OSMD/OSMD_Test.ts
  72. 1 1
      test/data/Beethoven_AnDieFerneGeliebte.xml
  73. BIN
      test/data/Cornelius_P_Christbaum_Opus_8_1_1865.mxl
  74. 1 1
      test/data/Mozart_DasVeilchen.xml
  75. 7 26
      webpack.common.js
  76. 2 1
      webpack.dev.js
  77. 11 9
      webpack.prod.js

+ 8 - 4
.appveyor.yml

@@ -2,14 +2,18 @@ image: Visual Studio 2017
 environment:
   matrix:
     - nodejs_version: "6"
-    - nodejs_version: "7"
-    - nodejs_version: "8"
-    - nodejs_version: "9"    
+    # - nodejs_version: "7"
+    - nodejs_version: "8" 
+    # - nodejs_version: "10"
+platform:
+  # - x86
+  - x64
 install:
-  - ps: Install-Product node $env:nodejs_version
+  - ps: Install-Product node $env:nodejs_version $env:platform
   - npm install
   - node --version
   - npm --version
+  - npm run fix-memory-limit
 build_script:
   - npm run lint
   - npm run build

+ 2 - 1
.travis.yml

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

+ 37 - 10
CHANGELOG.md

@@ -1,22 +1,50 @@
-# Change Log
-All notable changes to this project will be documented in this file.
+## [0.3.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0...0.3.0) (2018-05-03)
 
-The format is based on [Keep a Changelog](http://keepachangelog.com/) 
-and this project adheres to [Semantic Versioning](http://semver.org/).
 
-## [Unreleased]
-### Added
-### Changed
-### Bugfixes
+### Bug Fixes
+
+* **logging:** fixed problems connected to loglevel type definition changes ([eea535d](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/eea535d))
+* corrected begin instructions width (begin modifiers) to work also for extra instruction measures. ([1509a81](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/1509a81))
+* fixed a bug in stem calculation which made all stems up for a single voice. ([aeb670e](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/aeb670e))
+* fixed all broken file references in demo file selector. ([3659fec](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/3659fec))
+* fixed showing the staveconnector of multi-staved instruments at the end of the system. ([46ca911](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/46ca911))
+* refined position of Articulations (always close to note head). Remaining problem: Beam calculations change initial stem direction. Articulation positions need to be set after beams. ([22de162](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/22de162))
+* using backend select value already for initial OSMD constructor ([97aad81](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/97aad81))
+
+
+### Features
+
+* **Articulations:** Added ArticulationReader - articulations are read from xml ([f529f45](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/f529f45))
+* **clef:** Improved conversion of ClefInstructions to VexFlow clefs. Lines are now respected during ([473c85a](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/473c85a)), closes [#110](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/110)
+* **engraving:** allow to change system labels' right margin ([#131](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/131)) ([be03289](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/be03289))
+* implemented setting stem direction automatically from voice type (main or linked voice) ([e7f9e10](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/e7f9e10))
+* optional element mode in key signature ([e85117a](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/e85117a)), closes [#108](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/108)
+
+
+### Styles
+
+* moved linting from grunt to npm script ([8dafc27](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/8dafc27))
+
+
+### BREAKING CHANGES
+
+* Running `grunt lint` is no longer possible.
+
+
+## [0.2.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0...0.2.0) (2017-04-08)
+
+
+## [0.1.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0-beta.5...0.1.0) (2016-09-23)
 
-## [0.1.0] - 2016-09-23
 ### Added
 - Added Reset button for cursor for demo
 - Added more xml files for demo and testing
 - Added unit tests for reading and calculating the xml files
 - Added logo as favicon and as img for demo site
+
 ### Changed
 - html site layout of demo
+
 ### Bugfixes
 - Fixed cursor functionality in demo
 
@@ -92,7 +120,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Added
 - First public pre-release
 
-[Unreleased]: https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0-beta.5...HEAD
 [0.1.0-beta.5]: https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0-beta.4...0.1.0-beta.5
 [0.1.0-beta.4]: https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0-beta.3...0.1.0-beta.4
 [0.1.0-beta.3]: https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.1.0-beta.2...0.1.0-beta.3

+ 1 - 1
README.md

@@ -12,7 +12,7 @@
 
 https://www.opensheetmusicdisplay.org/
 
-OpenSheeMusicDisplay is the missing link between MusicXML and VexFlow. Built upon many years of experience in both sheet music interactivity and engraving, it is the perfect solution for app developers seeking to build digital sheet music services.
+OpenSheetMusicDisplay is the missing link between MusicXML and VexFlow. Built upon many years of experience in both sheet music interactivity and engraving, it is the perfect solution for app developers seeking to build digital sheet music services.
 
 MusicXML is the de facto standard for sharing sheet music on the internet. VexFlow is widely used for rendering sheet music. It features an extensive note sign library attributable to its open source nature.
 

+ 5 - 3
bin/publish_gh_page.sh

@@ -1,8 +1,8 @@
 #!/bin/bash
 
 # Prepare files to be published
-npm run docs
 npm run build
+npm run docs
 
 # Clone github page
 git clone git@github.com:opensheetmusicdisplay/opensheetmusicdisplay.github.io.git
@@ -10,10 +10,12 @@ cd opensheetmusicdisplay.github.io
 git status
 
 # Copy class documentation
-rsync -a ../build/docs/* ./classdoc
+rsync -a ../build/docs/* ./classdoc/
 
 # Copy demo application
-rsync -a ../build/demo/* ./demo
+rsync -a ../build/demo.min.js ./demo/
+rm -rf ./demo/sheets
+rsync -a ../test/data/* ./demo/sheets/
 
 # Commit and push changes
 git status

+ 2 - 2
demo/index.html

@@ -25,9 +25,9 @@
     </div>
     <div class="column">
         <h3 class="ui header">Render backend:</h3>
-        <select class="ui dropdown" id="backend-select" value="canvas">
-            <option value="canvas">Canvas</option>>
+        <select class="ui dropdown" id="backend-select" value="svg">
             <option value="svg">SVG</option>
+            <option value="canvas">Canvas</option>>
         </select>
     </div>
     <div class="column">

+ 32 - 26
demo/index.js

@@ -1,33 +1,36 @@
-import { OSMD } from '../src/OSMD/OSMD';
+import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay';
 
 /*jslint browser:true */
 (function () {
     "use strict";
-    var osmdObj;
+    var openSheetMusicDisplay;
     // The folder of the demo files
     var folder = process.env.STATIC_FILES_SUBFOLDER ? process.env.STATIC_FILES_SUBFOLDER + "/" : "",
     // The available demos
         demos = {
-            "Beethoven - AnDieFerneGeliebte": "Beethoven_AnDieFerneGeliebte.xml",
+            "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",
             "M. Clementi - Sonatina Op.36 No.3 Pt.2": "MuzioClementi_SonatinaOpus36No3_Part2.xml",
+            "J.S. Bach - Praeludium In C Dur BWV846 1": "JohannSebastianBach_PraeludiumInCDur_BWV846_1.xml",
             "J.S. Bach - Air": "JohannSebastianBach_Air.xml",
-            "G.P. Telemann - Sonata, TWV 40:102 - 1. Dolce": "TelemannWV40.102_Sonate-Nr.1.1-Dolce.xml",
             "C. Gounod - Meditation": "CharlesGounod_Meditation.xml",
-            "J.S. Bach - Praeludium In C Dur BWV846 1": "JohannSebastianBach_PraeludiumInCDur_BWV846_1.xml",
             "J. Haydn - Concertante Cello": "JosephHaydn_ConcertanteCello.xml",
+            "Mozart - An Chloe": "Mozart_AnChloe.xml",
+            "Mozart - Das Veilchen": "Mozart_DasVeilchen.xml",
+            "Mozart - Trio": "MozartTrio.mxl",
             "S. Joplin - Elite Syncopations": "ScottJoplin_EliteSyncopations.xml",
             "S. Joplin - The Entertainer": "ScottJoplin_The_Entertainer.xml",
             "ActorPreludeSample": "ActorPreludeSample.xml",
-            "an chloe - mozart": "an chloe - mozart.xml",
-            "das veilchen - mozart": "das veilchen - mozart.xml",
-            "Dichterliebe01": "Dichterliebe01.xml",
-            "mandoline - debussy": "mandoline - debussy.xml",
-            "MozartTrio": "MozartTrio.mxl",
-            "Cornelius P. Christbaum Opus 8.1": "Cornelius_P_Christbaum_Opus_8_1_1865.mxl",
+            "R. Schumann - Dichterliebe": "Dichterliebe01.xml",
+            "C. Debussy - Mandoline": "Debussy_Mandoline.xml",
             "France Levasseur - Parlez Mois": "Parlez-moi.mxl",
+            "Telemann - Sonate-Nr.1.1-Dolce": "TelemannWV40.102_Sonate-Nr.1.1-Dolce.xml",
+            "Telemann - Sonate-Nr.1.2-Allegro": "TelemannWV40.102_Sonate-Nr.1.2-Allegro-F-Dur.xml",
+            "Saltarello": "Saltarello.mxl",
         },
 
         zoom = 1.0,
@@ -80,6 +83,8 @@ import { OSMD } from '../src/OSMD/OSMD';
         }
         select.onchange = selectOnChange;
 
+        // Pre-select default music piece
+
         custom.appendChild(document.createTextNode("Custom"));
 
         // Create zoom controls
@@ -93,8 +98,8 @@ import { OSMD } from '../src/OSMD/OSMD';
         };
 
         // Create OSMD object and canvas
-        osmdObj = new OSMD(canvas, false);
-        osmdObj.setLogLevel('info');
+        openSheetMusicDisplay = new OpenSheetMusicDisplay(canvas, false, backendSelect.value);
+        openSheetMusicDisplay.setLogLevel('info');
         document.body.appendChild(canvas);
 
         // Set resize event handler
@@ -106,7 +111,7 @@ import { OSMD } from '../src/OSMD/OSMD';
                 var width = document.body.clientWidth;
                 canvas.width = width;
                 try {
-                osmdObj.render();
+                openSheetMusicDisplay.render();
                 } catch (e) {}
                 enable();
             }
@@ -115,28 +120,28 @@ import { OSMD } from '../src/OSMD/OSMD';
         window.addEventListener("keydown", function(e) {
             var event = window.event ? window.event : e;
             if (event.keyCode === 39) {
-                osmdObj.cursor.next();
+                openSheetMusicDisplay.cursor.next();
             }
         });
         nextCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.next();
+            openSheetMusicDisplay.cursor.next();
         });
         resetCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.reset();
+            openSheetMusicDisplay.cursor.reset();
         });
         hideCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.hide();
+            openSheetMusicDisplay.cursor.hide();
         });
         showCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.show();
+            openSheetMusicDisplay.cursor.show();
         });
 
         backendSelect.addEventListener("change", function(e) {
             var value = e.target.value;
             // clears the canvas element
             canvas.innerHTML = "";
-            osmdObj = new OSMD(canvas, false, value);
-            osmdObj.setLogLevel('info');
+            openSheetMusicDisplay = new OpenSheetMusicDisplay(canvas, false, value);
+            openSheetMusicDisplay.setLogLevel('info');
             selectOnChange();
 
         });
@@ -178,18 +183,19 @@ import { OSMD } from '../src/OSMD/OSMD';
             str = folder + select.value;
         }
         zoom = 1.0;
-        osmdObj.load(str).then(
+        openSheetMusicDisplay.load(str).then(
             function() {
-                return osmdObj.render();
+                return openSheetMusicDisplay.render();
             },
             function(e) {
+                console.warn(e.stack);
                 error("Error reading sheet: " + e);
             }
         ).then(
             function() {
                 return onLoadingEnd(isCustom);
             }, function(e) {
-                error("Error rendering sheet: " + e);
+                error("Error rendering sheet: " + process.env.DEBUG ? e.stack : e);
                 onLoadingEnd(isCustom);
             }
         );
@@ -212,8 +218,8 @@ import { OSMD } from '../src/OSMD/OSMD';
     function scale() {
         disable();
         window.setTimeout(function(){
-            osmdObj.zoom = zoom;
-            osmdObj.render();
+            openSheetMusicDisplay.zoom = zoom;
+            openSheetMusicDisplay.render();
             enable();
         }, 0);
     }

+ 97 - 6
external/vexflow/vexflow.d.ts

@@ -1,4 +1,7 @@
+
+
 declare namespace Vex {
+
     export module Flow {
         const RESOLUTION: any;
 
@@ -27,6 +30,18 @@ declare namespace Vex {
             public getW(): number;
 
             public getH(): number;
+
+            public draw(ctx: Vex.Flow.RenderContext): void;
+        }
+
+        export class Tickable {
+            public reset(): void;
+
+            public setStave(stave: Stave);
+
+            public getBoundingBox(): BoundingBox;
+
+            public getAttribute(arg: string): string;
         }
 
         export class Voice {
@@ -34,20 +49,43 @@ declare namespace Vex {
 
             public static Mode: any;
 
+            public context: RenderContext;
+
+            public tickables: Tickable[];
+
             public getBoundingBox(): BoundingBox;
 
             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;
 
             public draw(ctx: any, stave: Stave): void;
         }
 
-        export class StaveNote {
+        export class Note extends Tickable {
+        }
+
+        export class Stem {
+            public static UP: number;
+            public static DOWN: number;
+        }
+        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 {
             constructor(note_struct: any);
 
             public getNoteHeadBounds(): any;
@@ -56,10 +94,14 @@ declare namespace Vex {
 
             public getNoteHeadEndX(): number;
 
+            public getGlyphWidth(): number;
+
             public addAccidental(index: number, accidental: Accidental): StaveNote;
 
             public addAnnotation(index: number, annotation: Annotation): StaveNote;
 
+            public addModifier(index: number, modifier: Modifier): StaveNote;
+
             public setStyle(style: any): void;
 
             public addDotToAll(): void;
@@ -82,6 +124,10 @@ declare namespace Vex {
 
             public getX(): number;
 
+            public setBegBarType(type: any): Stave;
+
+            public setEndBarType(type: any): Stave;
+
             public addClef(clefSpec: string, size: any, annotation: any, position: any): void;
 
             public setEndClef(clefSpec: string, size: any, annotation: any): void;
@@ -96,12 +142,16 @@ declare namespace Vex {
 
             public getNoteStartX(): number;
 
+            public getModifierXShift(): number;
+
             public getNoteEndX(): number;
 
             public setNoteStartX(x: number): Stave;
 
             public setKeySignature(keySpec: any, cancelKeySpec: any, position: any): Stave;
 
+            public setText(text: string, position: number, options: any): void;
+
             public format(): void;
 
             public getSpacingBetweenLines(): number;
@@ -110,7 +160,10 @@ declare namespace Vex {
 
             public getLineForY(y: number): number;
 
+            public getYForLine(y: number): number;
+
             public getModifiers(pos: any, cat: any): Clef[]; // FIXME
+
             public setContext(ctx: RenderContext): Stave;
 
             public addModifier(mod: any, pos: any): void;
@@ -128,9 +181,31 @@ declare namespace Vex {
             public getWidth(): number;
 
             public getPadding(index: number): number;
+
+            public getPosition(): number;
+
+            public setPosition(position: number): Modifier;
         }
 
+
         export class StaveModifier extends Modifier {
+            public static get Position() {
+                return {
+                    LEFT: 1,
+                    RIGHT: 2,
+                    ABOVE: 3,
+                    BELOW: 4,
+                    BEGIN: 5,
+                    END: 6,
+                };
+            }
+
+            public getPosition(): number;
+
+        }
+
+        export class Repetition extends StaveModifier {
+            constructor(type: any, x: number, y_shift: number);
         }
 
         export class Clef extends StaveModifier {
@@ -159,13 +234,13 @@ declare namespace Vex {
 
             public resize(a: number, b: number): void;
 
-            public getContext(): CanvasContext|SVGContext;
+            public getContext(): CanvasContext | SVGContext;
         }
 
-        export class TimeSignature {
+        export class TimeSignature extends StaveModifier {
             constructor(timeSpec: string, customPadding?: any);
         }
-        export class KeySignature {
+        export class KeySignature extends StaveModifier {
             constructor(keySpec: string, cancelKeySpec: string, alterKeySpec?: string);
         }
 
@@ -177,6 +252,10 @@ declare namespace Vex {
             constructor(type: string);
         }
 
+        export class Articulation extends Modifier {
+            constructor(type: string);
+        }
+
         export class Beam {
             constructor(notes: StaveNote[], auto_stem: boolean);
 
@@ -221,6 +300,18 @@ declare namespace Vex {
 
             public setContext(ctx: RenderContext): StaveConnector;
 
+            public setXShift(shift: number): StaveConnector;
+
+            public top_stave: Stave;
+
+            public bottom_stave: Stave;
+
+            public thickness: number;
+
+            public width: number;
+
+            public x_shift: number;
+
             public draw(): void;
         }
     }

+ 6 - 3
karma.conf.js

@@ -48,9 +48,10 @@ module.exports = function (config) {
             // webpack watches dependencies
 
             // copy parts of webpack configuration to use minimal effort here
-            devtool: 'cheap-module-eval-source-map',
+            devtool: process.env.CI ? false : 'cheap-module-eval-source-map',
+            mode: process.env.CI ? 'production' : 'development',
             module: {
-                loaders: common.module.loaders
+                rules: common.module.rules
             },
             resolve: common.resolve
         },
@@ -73,7 +74,9 @@ module.exports = function (config) {
 
         // web server port
         port: 9876,
-
+        // timeout in ms:
+        browserNoActivityTimeout: 100000,
+        captureTimeout: 60000,
         // enable / disable colors in the output (reporters and logs)
         colors: true,
 

+ 21 - 22
package.json

@@ -1,11 +1,11 @@
 {
   "name": "opensheetmusicdisplay",
-  "version": "0.2.1",
+  "version": "0.3.0",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
-  "main": "dist/src/OSMD/OSMD.js",
-  "typings": "dist/src/OSMD/OSMD",
+  "main": "build/opensheetmusicdisplay.min.js",
+  "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
   "scripts": {
-    "docs": "typedoc --out ./build/docs --name OpenSheetMusicDisplay --module commonjs --target ES5 --mode file ./src/**/*.ts",
+    "docs": "typedoc --out ./build/docs --name OpenSheetMusicDisplay --module commonjs --target ES5 --ignoreCompilerErrors --mode file ./src",
     "eslint": "eslint .",
     "tslint": "tslint --project tsconfig.json \"src/**/*.ts\" \"test/**/*.ts\"",
     "lint": "npm-run-all eslint tslint",
@@ -16,22 +16,19 @@
     "build:doc": "cross-env STATIC_FILES_SUBFOLDER=sheets npm run build",
     "build:webpack": "webpack --progress --colors --config webpack.prod.js",
     "build:webpack-dev": "webpack --progress --colors --config webpack.dev.js",
-    "start": "webpack-dev-server --progress --colors --config webpack.dev.js"
+    "start": "webpack-dev-server --progress --colors --config webpack.dev.js",
+    "fix-memory-limit": "increase-memory-limit"
   },
   "pre-commit": [
     "lint"
   ],
   "files": [
-    "dist",
+    "build/dist/src",
+    "build/opensheetmusicdisplay.min.js",
     "AUTHORS",
     "CHANGELOG.md",
     "README.md",
-    "karma.conf.js",
-    "src",
-    "external",
-    "demo",
-    "tsconfig.json",
-    "tslint.json"
+    "external"
   ],
   "repository": {
     "type": "git",
@@ -51,11 +48,12 @@
   "homepage": "http://opensheetmusicdisplay.org",
   "dependencies": {
     "es6-promise": "^4.0.5",
+    "increase-memory-limit": "^1.0.6",
     "jszip": "^3.0.0",
     "loglevel": "^1.5.0",
     "shortid": "^2.2.6",
     "typescript-collections": "^1.1.2",
-    "vexflow": "^1.2.53"
+    "vexflow": "^1.2.84"
   },
   "devDependencies": {
     "@types/chai": "^4.0.3",
@@ -72,7 +70,7 @@
     "eslint-plugin-promise": "^3.6.0",
     "eslint-plugin-standard": "^3.0.1",
     "file-loader": "^1.1.8",
-    "html-webpack-plugin": "^2.30.1",
+    "html-webpack-plugin": "^3.1.0",
     "http-server": "^0.11.0",
     "jquery": "^3.2.1",
     "karma": "^2.0.0",
@@ -82,22 +80,23 @@
     "karma-firefox-launcher": "^1.0.0",
     "karma-mocha": "^1.1.1",
     "karma-mocha-reporter": "^2.0.4",
-    "karma-webpack": "^2.0.9",
+    "karma-webpack": "^3.0.0",
     "karma-xml2js-preprocessor": "^0.0.3",
     "mocha": "^4.1.0",
     "npm-run-all": "^4.1.2",
     "pre-commit": "^1.2.2",
-    "ts-loader": "^3.0.0",
+    "ts-loader": "^4.1.0",
     "tsify": "^3.0.0",
     "tslint": "^5.8.0",
     "tslint-loader": "^3.5.3",
-    "typedoc": "^0.10.0",
+    "typedoc": "^0.11.1",
     "typescript": "^2.6.1",
-    "uglifyjs-webpack-plugin": "^1.0.1",
-    "underscore-template-loader": "^0.8.0",
-    "webpack": "^3.5.5",
-    "webpack-dev-server": "2.7.1",
-    "webpack-merge": "^4.1.1",
+    "uglifyjs-webpack-plugin": "^1.2.4",
+    "underscore-template-loader": "^1.0.0",
+    "webpack": "^4.2.0",
+    "webpack-cli": "^2.0.13",
+    "webpack-dev-server": "3.1.1",
+    "webpack-merge": "^4.1.2",
     "webpack-visualizer-plugin": "^0.1.11"
   },
   "config": {

+ 29 - 1
src/Common/DataObjects/Fraction.ts

@@ -126,6 +126,7 @@ export class Fraction {
   public set Denominator(value: number) {
     if (this.denominator !== value) {
       this.denominator = value;
+      // don't simplify in case of a GraceNote (need it in order to set the right symbol)
       if (this.numerator !== 0) {
         this.simplify();
       }
@@ -144,6 +145,10 @@ export class Fraction {
     }
   }
 
+  /**
+   * Returns the unified numerator where the whole value will be expanded
+   * with the denominator and added to the existing numerator.
+   */
   public GetExpandedNumerator(): number {
     return this.wholeValue * this.denominator + this.numerator;
   }
@@ -170,7 +175,15 @@ 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
     this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator +
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
     this.denominator = this.denominator * fraction.denominator;
@@ -179,7 +192,15 @@ 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
     this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator -
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
     this.denominator = this.denominator * fraction.denominator;
@@ -187,7 +208,11 @@ export class Fraction {
     this.simplify();
     this.setRealValue();
   }
-
+  /**
+   * Brute Force quanization by searching incremental with the numerator until the denominator is
+   * smaller/equal than the desired one.
+   * @param maxAllowedDenominator
+   */
   public Quantize(maxAllowedDenominator: number): Fraction {
     if (this.denominator <= maxAllowedDenominator) {
       return this;
@@ -244,11 +269,14 @@ export class Fraction {
   }
 
   private simplify(): void {
+    // don't simplify in case of a GraceNote (need it in order to set the right symbol)
     if (this.numerator === 0) {
       this.denominator = 1;
       return;
     }
 
+    // normally should check if denominator or fraction.denominator is 0 but in our case a zero denominator
+    // doesn't make sense. Could probably be optimized
     const i: number = Fraction.greatestCommonDenominator(Math.abs(this.numerator), Math.abs(this.denominator));
 
     this.numerator /= i;

+ 2 - 1
src/Common/DataObjects/Pitch.ts

@@ -14,7 +14,8 @@ export enum AccidentalEnum {
     FLAT = -1,
     NONE = 0,
     SHARP = 1,
-    DOUBLESHARP = 2
+    DOUBLESHARP = 2,
+    NATURAL = 3
 }
 
 // This class represents a musical note. The middle A (440 Hz) lies in the octave with the value 1.

+ 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("[OSMD] ", args.join(" "));
-    }
-    public static log(...args: any[]): void {
-        console.log("[OSMD] ", args.join(" "));
-    }
-    public static error(...args: any[]): void {
-        console.error("[OSMD] ", args.join(" "));
-    }
-    public static warn(...args: any[]): void {
-        console.warn("[OSMD] ", args.join(" "));
-    }
-}

+ 11 - 0
src/Common/Strings/StringUtil.ts

@@ -0,0 +1,11 @@
+export class StringUtil {
+  public static StringContainsSeparatedWord(str: string, word: string): boolean {
+    if (str === word ||
+      str.search(" " + word) !== -1 ||
+      str.search(word + " ") !== -1 ||
+      str.search(word + ".") !== -1) {
+      return true;
+    }
+    return false;
+  }
+}

+ 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);
         }
 
     }

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

@@ -27,6 +27,7 @@ export class GraphicalLyricEntry {
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, staffHeight);
     }
 
+    // FIXME: This should actually be called LyricsEntry or be a function
     public get GetLyricsEntry(): LyricsEntry {
         return this.lyricsEntry;
     }

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

@@ -35,6 +35,7 @@ export class GraphicalLyricWord {
     }
 
     private initialize(): void {
+        // FIXME: This is actually not needed in Javascript as we have dynamic memory allication?
         for (let i: number = 0; i < this.lyricWord.Syllables.length; i++) {
             this.graphicalLyricsEntries.push(undefined);
         }

+ 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) {

+ 75 - 124
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -6,7 +6,6 @@ import {Note} from "../VoiceData/Note";
 import {Slur} from "../VoiceData/Expressions/ContinuousExpressions/Slur";
 import {Voice} from "../VoiceData/Voice";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
-import {LinkedVoice} from "../VoiceData/LinkedVoice";
 import {GraphicalTie} from "./GraphicalTie";
 import {GraphicalObject} from "./GraphicalObject";
 import {StaffMeasure} from "./StaffMeasure";
@@ -16,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]].
@@ -24,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;
@@ -47,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;
@@ -69,6 +70,10 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return this.lyricsEntries;
     }
 
+    public set LyricsEntries(value: GraphicalLyricEntry[]) {
+        this.lyricsEntries = value;
+    }
+
     /**
      * Calculate the absolute Timestamp.
      * @returns {Fraction}
@@ -87,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;
                 }
             }
@@ -108,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;
                 }
             }
@@ -144,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;
                 }
@@ -156,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;
                 }
             }
@@ -170,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;
@@ -232,26 +196,14 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return false;
     }
 
-    public getMainVoice(): Voice {
-        for (let idx: number = 0, len: number = this.sourceStaffEntry.VoiceEntries.length; idx < len; ++idx) {
-            const voiceEntry: VoiceEntry = this.sourceStaffEntry.VoiceEntries[idx];
-            if (!(voiceEntry.ParentVoice instanceof LinkedVoice)) {
-                return voiceEntry.ParentVoice;
-            }
-        }
-        return this.notes[0][0].sourceNote.ParentVoiceEntry.ParentVoice;
-    }
-
     /**
      * Return the [[StaffEntry]]'s Minimum NoteLength.
      * @returns {Fraction}
      */
     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;
@@ -263,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;
@@ -281,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;
     }
 
     /**
@@ -303,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;
     }
 
     /**
@@ -332,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);
@@ -349,4 +287,17 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
             }
         }
     }
+
+    public hasOnlyRests(): boolean {
+        const hasOnlyRests: boolean = true;
+        for (const gve of this.graphicalVoiceEntries) {
+            for (const graphicalNote of gve.notes) {
+                const note: Note = graphicalNote.sourceNote;
+                if (!note.isRest()) {
+                    return false;
+                }
+            }
+        }
+        return hasOnlyRests;
+    }
 }

+ 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[];
+}

File diff suppressed because it is too large
+ 588 - 266
src/MusicalScore/Graphical/MusicSheetCalculator.ts


+ 13 - 0
src/MusicalScore/Graphical/MusicSheetDrawer.ts

@@ -334,6 +334,19 @@ export abstract class MusicSheetDrawer {
         for (const measure of staffLine.Measures) {
             this.drawMeasure(measure);
         }
+
+        if (staffLine.LyricsDashes.length > 0) {
+            this.drawDashes(staffLine.LyricsDashes);
+        }
+    }
+
+    /**
+     * Draw all dashes to the canvas
+     * @param lyricsDashes Array of lyric dashes to be drawn
+     * @param layer Number of the layer that the lyrics should be drawn in
+     */
+    protected drawDashes(lyricsDashes: GraphicalLabel[]): void {
+        lyricsDashes.forEach(dash => this.drawLabel(dash, <number>GraphicalLayers.Notes));
     }
 
     // protected drawSlur(slur: GraphicalSlur, abs: PointF2D): void {

+ 12 - 5
src/MusicalScore/Graphical/MusicSystem.ts

@@ -57,6 +57,11 @@ export abstract class MusicSystem extends GraphicalObject {
         this.parent = value;
     }
 
+    public get NextSystem(): MusicSystem {
+        const idxInParent: number = this.Parent.MusicSystems.indexOf(this);
+        return idxInParent !== this.Parent.MusicSystems.length ? this.Parent.MusicSystems[idxInParent + 1] : undefined;
+    }
+
     public get StaffLines(): StaffLine[] {
         return this.staffLines;
     }
@@ -242,14 +247,18 @@ export abstract class MusicSystem extends GraphicalObject {
                 continue;
             }
             let firstStaffLine: StaffLine = undefined;
+            let lastStaffLine: StaffLine = undefined;
             for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
                 const staffLine: StaffLine = this.staffLines[idx2];
                 if (staffLine.ParentStaff === instrument1.Staves[0]) {
                     firstStaffLine = staffLine;
                 }
+                if (staffLine.ParentStaff === instrument2.Staves[0]) {
+                    lastStaffLine = staffLine;
+                }
             }
-            if (firstStaffLine !== undefined && firstStaffLine !== undefined) {
-                this.createGroupBracket(firstStaffLine, firstStaffLine, recursionDepth);
+            if (firstStaffLine !== undefined && lastStaffLine !== undefined) {
+                this.createGroupBracket(firstStaffLine, lastStaffLine, recursionDepth);
             }
             if (instrumentGroup.InstrumentalGroups.length < 1) {
                 continue;
@@ -274,11 +283,9 @@ export abstract class MusicSystem extends GraphicalObject {
                 );
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 this.labels.setValue(graphicalLabel, instrument);
-                //graphicalLabel.PositionAndShape.Parent = this.PositionAndShape;
-
                 // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
                 // Y-Position will be calculated after the y-Spacing
-                graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
+                // graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
             }
 
             // calculate maxLabelLength (needed for X-Spacing)

+ 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;
     }
 

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

@@ -8,6 +8,7 @@ import {StaffMeasure} from "./StaffMeasure";
 import {MusicSystem} from "./MusicSystem";
 import {StaffLineActivitySymbol} from "./StaffLineActivitySymbol";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
+import {GraphicalLabel} from "./GraphicalLabel";
 
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
@@ -20,6 +21,8 @@ export abstract class StaffLine extends GraphicalObject {
     protected parentStaff: Staff;
     protected skyLine: number[];
     protected bottomLine: number[];
+    protected lyricLines: GraphicalLine[] = [];
+    protected lyricsDashes: GraphicalLabel[] = [];
 
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super();
@@ -44,6 +47,27 @@ export abstract class StaffLine extends GraphicalObject {
         this.staffLines = value;
     }
 
+    public get NextStaffLine(): StaffLine {
+        const idxInParent: number = this.parentMusicSystem.StaffLines.indexOf(this);
+        return idxInParent !== this.parentMusicSystem.StaffLines.length ? this.parentMusicSystem.StaffLines[idxInParent + 1] : undefined;
+    }
+
+    public get LyricLines(): GraphicalLine[] {
+        return this.lyricLines;
+    }
+
+    public set LyricLines(value: GraphicalLine[]) {
+        this.lyricLines = value;
+    }
+
+    public get LyricsDashes(): GraphicalLabel[] {
+        return this.lyricsDashes;
+    }
+
+    public set LyricsDashes(value: GraphicalLabel[]) {
+        this.lyricsDashes = value;
+    }
+
     public get ParentMusicSystem(): MusicSystem {
         return this.parentMusicSystem;
     }

+ 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);
                     }
                 }
             }

+ 178 - 36
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -15,7 +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
@@ -135,47 +138,93 @@ 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
         });
 
+        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) {
             (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
             if (accidentals[i]) {
@@ -185,10 +234,78 @@ export class VexFlowConverter {
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             vfnote.addDotToAll();
         }
-
         return vfnote;
     }
 
+    public static generateArticulations(vfnote: Vex.Flow.StaveNote, articulations: ArticulationEnum[]): void {
+        // Articulations:
+        let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
+
+        if (vfnote.getStemDirection() === Vex.Flow.Stem.UP) {
+            vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
+        }
+
+        for (const articulation of articulations) {
+            // tslint:disable-next-line:switch-default
+            let vfArt: Vex.Flow.Articulation = undefined;
+            switch (articulation) {
+                case ArticulationEnum.accent: {
+                    vfArt = new Vex.Flow.Articulation("a>");
+                    break;
+                }
+                case ArticulationEnum.downbow: {
+                    vfArt = new Vex.Flow.Articulation("am");
+                    break;
+                }
+                case ArticulationEnum.fermata: {
+                    vfArt = new Vex.Flow.Articulation("a@a");
+                    vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
+                    break;
+                }
+                case ArticulationEnum.invertedfermata: {
+                    vfArt = new Vex.Flow.Articulation("a@u");
+                    vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
+                    break;
+                }
+                case ArticulationEnum.lefthandpizzicato: {
+                    vfArt = new Vex.Flow.Articulation("a+");
+                    break;
+                }
+                case ArticulationEnum.snappizzicato: {
+                    vfArt = new Vex.Flow.Articulation("ao");
+                    break;
+                }
+                case ArticulationEnum.staccatissimo: {
+                    vfArt = new Vex.Flow.Articulation("av");
+                    break;
+                }
+                case ArticulationEnum.staccato: {
+                    vfArt = new Vex.Flow.Articulation("a.");
+                    break;
+                }
+                case ArticulationEnum.tenuto: {
+                    vfArt = new Vex.Flow.Articulation("a-");
+                    break;
+                }
+                case ArticulationEnum.upbow: {
+                    vfArt = new Vex.Flow.Articulation("a|");
+                    break;
+                }
+                case ArticulationEnum.strongaccent: {
+                    vfArt = new Vex.Flow.Articulation("a^");
+                    break;
+                }
+                default: {
+                    break;
+                }
+            }
+            if (vfArt !== undefined) {
+                vfArt.setPosition(vfArtPosition);
+                vfnote.addModifier(0, vfArt);
+            }
+        }
+    }
+
     /**
      * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
      *
@@ -205,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";
         }
 
@@ -226,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;
 
@@ -244,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;
 
@@ -265,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;
 
@@ -281,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 };
     }
@@ -345,21 +459,23 @@ export class VexFlowConverter {
      * @param lineType
      * @returns {any}
      */
-    public static line(lineType: SystemLinesEnum): any {
-        // TODO Not all line types are correctly mapped!
+    public static line(lineType: SystemLinesEnum, linePosition: SystemLinePosition): any {
         switch (lineType) {
             case SystemLinesEnum.SingleThin:
-                return Vex.Flow.StaveConnector.type.SINGLE;
+                if (linePosition === SystemLinePosition.MeasureBegin) {
+                    return Vex.Flow.StaveConnector.type.SINGLE;
+                }
+                return Vex.Flow.StaveConnector.type.SINGLE_RIGHT;
             case SystemLinesEnum.DoubleThin:
                 return Vex.Flow.StaveConnector.type.DOUBLE;
             case SystemLinesEnum.ThinBold:
-                return Vex.Flow.StaveConnector.type.SINGLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.BoldThinDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
             case SystemLinesEnum.DotsThinBold:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.DotsBoldBoldDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.None:
                 return Vex.Flow.StaveConnector.type.NONE;
             default:
@@ -434,3 +550,29 @@ export class VexFlowConverter {
         return ret;
     }
 }
+
+export enum VexFlowRepetitionType {
+    NONE = 1,         // no coda or segno
+    CODA_LEFT = 2,    // coda at beginning of stave
+    CODA_RIGHT = 3,   // coda at end of stave
+    SEGNO_LEFT = 4,   // segno at beginning of stave
+    SEGNO_RIGHT = 5,  // segno at end of stave
+    DC = 6,           // D.C. at end of stave
+    DC_AL_CODA = 7,   // D.C. al coda at end of stave
+    DC_AL_FINE = 8,   // D.C. al Fine end of stave
+    DS = 9,           // D.S. at end of stave
+    DS_AL_CODA = 10,  // D.S. al coda at end of stave
+    DS_AL_FINE = 11,  // D.S. al Fine at end of stave
+    FINE = 12,        // Fine at end of stave
+}
+
+export enum VexFlowBarlineType {
+    SINGLE = 1,
+    DOUBLE = 2,
+    END = 3,
+    REPEAT_BEGIN = 4,
+    REPEAT_END = 5,
+    REPEAT_BOTH = 6,
+    NONE = 7,
+}
+

+ 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;

+ 25 - 25
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -16,12 +16,15 @@ import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {GraphicalNote} from "../GraphicalNote";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
-import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 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);
     }
 
     /**
@@ -150,14 +147,7 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
         return;
     }
 
-    /**
-     * Adds a technical instruction at the given staff entry.
-     * @param technicalInstruction
-     * @param graphicalStaffEntry
-     */
-    public createGraphicalTechnicalInstruction(technicalInstruction: TechnicalInstruction, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
-    }
+
 
     /**
      * Adds a clef change within a measure before the given staff entry.
@@ -185,4 +175,14 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
       graphicalChordSymbolContainer.PositionAndShape.calculateBoundingBox();
       graphicalStaffEntry.graphicalChordContainer = graphicalChordSymbolContainer;
     }
+
+    /**
+     * Adds a technical instruction at the given staff entry.
+     * @param technicalInstruction
+     * @param graphicalStaffEntry
+     */
+    public createGraphicalTechnicalInstruction(technicalInstruction: TechnicalInstruction, graphicalStaffEntry: GraphicalStaffEntry): void {
+        return;
+    }
+
 }

+ 14 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowInstrumentBrace.ts

@@ -0,0 +1,14 @@
+import Vex = require("vexflow");
+import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { VexFlowStaffLine } from "./VexFlowStaffLine";
+
+/**
+ * Class that defines a instrument bracket at the beginning of a line.
+ */
+export class VexFlowInstrumentBrace extends VexFlowInstrumentBracket {
+
+    constructor(firstVexFlowStaffLine: VexFlowStaffLine, lastVexFlowStaffLine: VexFlowStaffLine, depth: number = 0) {
+        super(firstVexFlowStaffLine, lastVexFlowStaffLine, depth);
+        this.vexflowConnector.setType(Vex.Flow.StaveConnector.type.BRACE);
+    }
+}

+ 20 - 8
src/MusicalScore/Graphical/VexFlow/VexFlowInstrumentBracket.ts

@@ -3,21 +3,21 @@ import {GraphicalObject} from "../GraphicalObject";
 import {VexFlowStaffLine} from "./VexFlowStaffLine";
 import { BoundingBox } from "../BoundingBox";
 import { VexFlowMeasure } from "./VexFlowMeasure";
+import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 
 /**
  * Class that defines a instrument bracket at the beginning of a line.
  */
 export class VexFlowInstrumentBracket extends GraphicalObject {
 
-    private vexflowConnector: Vex.Flow.StaveConnector;
+    protected vexflowConnector: Vex.Flow.StaveConnector;
 
-    constructor(firstVexFlowStaffLine: VexFlowStaffLine, lastVexFlowStaffLine: VexFlowStaffLine) {
+    constructor(firstVexFlowStaffLine: VexFlowStaffLine, lastVexFlowStaffLine: VexFlowStaffLine, depth: number = 0) {
         super();
-        // FIXME: B.Giesinger: Fill in sizes after calculation
-        this.boundingBox = new BoundingBox(this);
+        this.PositionAndShape = new BoundingBox(this, firstVexFlowStaffLine.ParentMusicSystem.PositionAndShape);
         const firstVexMeasure: VexFlowMeasure = firstVexFlowStaffLine.Measures[0] as VexFlowMeasure;
         const lastVexMeasure: VexFlowMeasure = lastVexFlowStaffLine.Measures[0] as VexFlowMeasure;
-        this.addConnector(firstVexMeasure.getVFStave(), lastVexMeasure.getVFStave(), Vex.Flow.StaveConnector.type.BRACE);
+        this.addConnector(firstVexMeasure.getVFStave(), lastVexMeasure.getVFStave(), Vex.Flow.StaveConnector.type.BRACKET, depth);
     }
 
     /**
@@ -25,9 +25,20 @@ export class VexFlowInstrumentBracket extends GraphicalObject {
      * @param ctx Render Vexflow context
      */
     public draw(ctx: Vex.Flow.RenderContext): void {
+        // Draw vexflow brace. This sets the positions inside the connector.
         this.vexflowConnector.setContext(ctx).draw();
+        // Set bounding box
+        const con: Vex.Flow.StaveConnector = this.vexflowConnector;
+        // First line in first stave
+        const topY: number = con.top_stave.getYForLine(0);
+        // Last line in last stave
+        const botY: number = con.bottom_stave.getYForLine(con.bottom_stave.getNumLines() - 1) + con.thickness;
+        // Set bounding box position and size in OSMD units
+        this.PositionAndShape.AbsolutePosition.x = (con.top_stave.getX() - 2 + con.x_shift) / unitInPixels;
+        this.PositionAndShape.AbsolutePosition.y = topY / unitInPixels;
+        this.PositionAndShape.Size.height = (botY - topY) / unitInPixels;
+        this.PositionAndShape.Size.width = 12 / unitInPixels; // width is always 12 -> vexflow implementation
     }
-
     /**
      * Adds a connector between two staves
      *
@@ -35,8 +46,9 @@ export class VexFlowInstrumentBracket extends GraphicalObject {
      * @param {Stave} stave2: Second stave
      * @param {Flow.StaveConnector.type} type: Type of connector
      */
-    private addConnector(stave1: Vex.Flow.Stave, stave2: Vex.Flow.Stave, type: any): void {
+    private addConnector(stave1: Vex.Flow.Stave, stave2: Vex.Flow.Stave, type: any, depth: number): void {
         this.vexflowConnector = new Vex.Flow.StaveConnector(stave1, stave2)
-        .setType(type);
+        .setType(type)
+        .setXShift(depth * -5);
     }
 }

+ 307 - 110
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -7,16 +7,23 @@ import {SystemLinesEnum} from "../SystemLinesEnum";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
 import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
-import {VexFlowConverter} from "./VexFlowConverter";
+import {VexFlowConverter, VexFlowRepetitionType, VexFlowBarlineType} from "./VexFlowConverter";
 import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 import {Beam} from "../../VoiceData/Beam";
 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 {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) {
@@ -33,17 +40,19 @@ export class VexFlowMeasure extends StaffMeasure {
     public formatVoices: (width: number) => void;
     // The VexFlow Ties in the measure
     public vfTies: Vex.Flow.StaveTie[] = [];
+    // The repetition instructions given as words or symbols (coda, dal segno..)
+    public vfRepetitionWords: Vex.Flow.Repetition[] = [];
 
-    // The VexFlow Stave (one measure in one line)
+    // The VexFlow Stave (= one measure in a staffline)
     private stave: Vex.Flow.Stave;
     // 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[]; } = {};
 
@@ -76,18 +85,25 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     /**
-     * returns the x-width of a given measure line.
+     * returns the x-width (in units) of a given measure line {SystemLinesEnum}.
      * @param line
-     * @returns {SystemLinesEnum} the x-width
+     * @returns the x-width in osmd units
      */
     public getLineWidth(line: SystemLinesEnum): number {
-        // FIXME: See values in VexFlow's stavebarline.js
-        const vfline: any = VexFlowConverter.line(line);
-        switch (vfline) {
-            case Vex.Flow.StaveConnector.type.SINGLE:
-                return 1.0 / unitInPixels;
-            case Vex.Flow.StaveConnector.type.DOUBLE:
-                return 3.0 / unitInPixels;
+        switch (line) {
+            // return 0 for the normal lines, as the line width will be considered at the updateInstructionWidth() method using the stavemodifiers.
+            // case SystemLinesEnum.SingleThin:
+            //     return 5.0 / unitInPixels;
+            // case SystemLinesEnum.DoubleThin:
+            //     return 5.0 / unitInPixels;
+            //     case SystemLinesEnum.ThinBold:
+            //     return 5.0 / unitInPixels;
+            // but just add a little extra space for repetitions (cosmetics):
+            case SystemLinesEnum.BoldThinDots:
+            case SystemLinesEnum.DotsThinBold:
+                return 10.0 / unitInPixels;
+            case SystemLinesEnum.DotsBoldBoldDots:
+                return 10.0 / unitInPixels;
             default:
                 return 0;
         }
@@ -146,6 +162,103 @@ export class VexFlowMeasure extends StaffMeasure {
         this.updateInstructionWidth();
     }
 
+    public addMeasureLine(lineType: SystemLinesEnum, linePosition: SystemLinePosition): void {
+        switch (linePosition) {
+            case SystemLinePosition.MeasureBegin:
+                switch (lineType) {
+                    case SystemLinesEnum.BoldThinDots:
+                        this.stave.setBegBarType(VexFlowBarlineType.REPEAT_BEGIN);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            case SystemLinePosition.MeasureEnd:
+                switch (lineType) {
+                    case SystemLinesEnum.DotsBoldBoldDots:
+                        this.stave.setEndBarType(VexFlowBarlineType.REPEAT_BOTH);
+                        break;
+                    case SystemLinesEnum.DotsThinBold:
+                        this.stave.setEndBarType(VexFlowBarlineType.REPEAT_END);
+                        break;
+                    case SystemLinesEnum.DoubleThin:
+                        this.stave.setEndBarType(VexFlowBarlineType.DOUBLE);
+                        break;
+                    case SystemLinesEnum.ThinBold:
+                        this.stave.setEndBarType(VexFlowBarlineType.END);
+                        break;
+                    default:
+                        break;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    /**
+     * Adds a measure number to the top left corner of the measure
+     * This method is not used currently in favor of the calculateMeasureNumberPlacement
+     * method in the MusicSheetCalculator.ts
+     */
+    public addMeasureNumber(): void {
+        const text: string = this.MeasureNumber.toString();
+        const position: number = Vex.Flow.StaveModifier.Position.ABOVE;
+        const options: any = {
+            justification: 1,
+            shift_x: 0,
+            shift_y: 0,
+          };
+
+        this.stave.setText(text, position, options);
+    }
+
+    public addWordRepetition(repetitionInstruction: RepetitionInstructionEnum): void {
+        let instruction: VexFlowRepetitionType = undefined;
+        let position: any = Vex.Flow.Modifier.Position.END;
+        switch (repetitionInstruction) {
+          case RepetitionInstructionEnum.Segno:
+            // create Segno Symbol:
+            instruction = VexFlowRepetitionType.SEGNO_LEFT;
+            position = Vex.Flow.Modifier.Position.BEGIN;
+            break;
+          case RepetitionInstructionEnum.Coda:
+            // create Coda Symbol:
+            instruction = VexFlowRepetitionType.CODA_LEFT;
+            position = Vex.Flow.Modifier.Position.BEGIN;
+            break;
+          case RepetitionInstructionEnum.DaCapo:
+            instruction = VexFlowRepetitionType.DC;
+            break;
+          case RepetitionInstructionEnum.DalSegno:
+            instruction = VexFlowRepetitionType.DS;
+            break;
+          case RepetitionInstructionEnum.Fine:
+            instruction = VexFlowRepetitionType.FINE;
+            break;
+          case RepetitionInstructionEnum.ToCoda:
+            //instruction = "To Coda";
+            break;
+          case RepetitionInstructionEnum.DaCapoAlFine:
+            instruction = VexFlowRepetitionType.DC_AL_FINE;
+            break;
+          case RepetitionInstructionEnum.DaCapoAlCoda:
+            instruction = VexFlowRepetitionType.DC_AL_CODA;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlFine:
+            instruction = VexFlowRepetitionType.DS_AL_FINE;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlCoda:
+            instruction = VexFlowRepetitionType.DS_AL_CODA;
+            break;
+          default:
+            break;
+        }
+        if (instruction !== undefined) {
+            this.stave.addModifier(new Vex.Flow.Repetition(instruction, 0, 0), position);
+        }
+    }
+
     /**
      * Sets the overall x-width of the measure.
      * @param width
@@ -165,32 +278,15 @@ 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
      */
     public draw(ctx: Vex.Flow.RenderContext): void {
-        // If this is the first stave in the vertical measure, call the format
-        // method to set the width of all the voices
-        if (this.formatVoices) {
-            // The width of the voices does not include the instructions (StaveModifiers)
-            this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
-        }
 
-        // Force the width of the Begin Instructions
-        this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
         // Draw stave lines
         this.stave.setContext(ctx).draw();
         // Draw all voices
@@ -226,9 +322,120 @@ export class VexFlowMeasure extends StaffMeasure {
         for (const connector of this.connectors) {
             connector.setContext(ctx).draw();
         }
+    }
+
+    public format(): void {
+        // If this is the first stave in the vertical measure, call the format
+        // method to set the width of all the voices
+        if (this.formatVoices) {
+            // The width of the voices does not include the instructions (StaveModifiers)
+            this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
+        }
+
+        // Force the width of the Begin Instructions
+        this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
+    }
+
+    /**
+     * 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;
+        }
 
-        // now we can finally set the vexflow x positions back into the osmd object model:
-        this.setStaffEntriesXPositions();
+        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;
     }
 
     /**
@@ -238,11 +445,11 @@ export class VexFlowMeasure extends StaffMeasure {
      */
     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;
@@ -252,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);
         }
@@ -261,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;
@@ -275,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);
         }
@@ -297,20 +504,31 @@ export class VexFlowMeasure extends StaffMeasure {
                 }
                 for (const beam of this.beams[voiceID]) {
                     const notes: Vex.Flow.StaveNote[] = [];
-                    for (const entry of beam[1]) {
-                        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);
                         }
                     }
                     if (notes.length > 1) {
-                        vfbeams.push(new Vex.Flow.Beam(notes, true));
+                        const vfBeam: Vex.Flow.Beam = new Vex.Flow.Beam(notes, autoStemBeam);
+                        vfbeams.push(vfBeam);
                         // just a test for coloring the notes:
                         // for (let note of notes) {
                         //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
                         // }
                     } else {
-                        Logging.log("Warning! Beam with no notes! Trying to ignore, but this is a serious problem.");
+                        log.debug("Warning! Beam with no notes!");
                     }
                 }
             }
@@ -333,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;
@@ -345,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.");
                     }
                 }
             }
@@ -357,39 +575,44 @@ 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();
 
+        const voices: Voice[] = this.getVoicesWithinMeasure();
+
+        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();
+    }
+
+    private createArticulations(): void {
         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);
-                    }
 
-                    vfVoices[voiceID].addTickable(graphicalStaffEntry.vfNotes[voiceID]);
-                }
+            // 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);
             }
         }
     }
@@ -413,49 +636,23 @@ export class VexFlowMeasure extends StaffMeasure {
         return this.stave;
     }
 
-    //private increaseBeginInstructionWidth(): void {
-    //    let modifiers: StaveModifier[] = this.stave.getModifiers();
-    //    let modifier: StaveModifier = modifiers[modifiers.length - 1];
-    //    //let padding: number = modifier.getCategory() === "keysignatures" ? modifier.getPadding(2) : 0;
-    //    let padding: number = modifier.getPadding(20);
-    //    let width: number = modifier.getWidth();
-    //    this.beginInstructionsWidth += (padding + width) / UnitInPixels;
-    //}
-    //
-    //private increaseEndInstructionWidth(): void {
-    //    let modifiers: StaveModifier[] = this.stave.getModifiers();
-    //    let modifier: StaveModifier = modifiers[modifiers.length - 1];
-    //    let padding: number = 0;
-    //    let width: number = modifier.getWidth();
-    //    this.endInstructionsWidth += (padding + width) / UnitInPixels;
-    //
-    //}
-
     /**
      * After re-running the formatting on the VexFlow Stave, update the
      * space needed by Instructions (in VexFlow: StaveModifiers)
      */
     private updateInstructionWidth(): void {
-        this.beginInstructionsWidth = (this.stave.getNoteStartX() - this.stave.getX()) / unitInPixels;
-        this.endInstructionsWidth = (this.stave.getX() + this.stave.getWidth() - this.stave.getNoteEndX()) / unitInPixels;
-    }
-
-    /**
-     * sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
-     * The positions are needed for cursor placement and mouse/tap interactions
-     */
-    private setStaffEntriesXPositions(): void {
-        for (let idx3: number = 0, len3: number = this.staffEntries.length; idx3 < len3; ++idx3) {
-            const gse: VexFlowStaffEntry = (<VexFlowStaffEntry> this.staffEntries[idx3]);
-            const measure: StaffMeasure = gse.parentMeasure;
-            const x: number =
-                gse.getX() -
-                measure.PositionAndShape.RelativePosition.x -
-                measure.ParentStaffLine.PositionAndShape.RelativePosition.x -
-                measure.parentMusicSystem.PositionAndShape.RelativePosition.x;
-            gse.PositionAndShape.RelativePosition.x = x;
-            gse.PositionAndShape.calculateAbsolutePosition();
-            gse.PositionAndShape.calculateAbsolutePositionsOfChildren();
+        let beginInstructionsWidth: number = 0;
+        let endInstructionsWidth: number = 0;
+        const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();
+        for (const mod of modifiers) {
+            if (mod.getPosition() === Vex.Flow.StaveModifier.Position.BEGIN) {
+                beginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
+            } else if (mod.getPosition() === Vex.Flow.StaveModifier.Position.END) {
+                endInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
+            }
         }
+
+        this.beginInstructionsWidth = beginInstructionsWidth / unitInPixels;
+        this.endInstructionsWidth = endInstructionsWidth / unitInPixels;
     }
 }

+ 347 - 210
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -16,263 +16,400 @@ import {Beam} from "../../VoiceData/Beam";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
-import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
 import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
 import {Tuplet} from "../../VoiceData/Tuplet";
-import Dictionary from "typescript-collections/dist/lib/Dictionary";
 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";
+import {GraphicalLyricEntry} from "../GraphicalLyricEntry";
+import {GraphicalLabel} from "../GraphicalLabel";
+import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
+import {GraphicalLyricWord} from "../GraphicalLyricWord";
+import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
-    constructor() {
-        super(new VexFlowGraphicalSymbolFactory());
-        MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+
+  constructor() {
+    super();
+    MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
+    MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer();
+  }
+
+  protected clearRecreatedObjects(): void {
+    super.clearRecreatedObjects();
+    for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
+      for (const staffMeasure of staffMeasures) {
+        (<VexFlowMeasure>staffMeasure).clean();
+      }
     }
+  }
 
-    protected clearRecreatedObjects(): void {
-        super.clearRecreatedObjects();
+    protected formatMeasures(): void {
         for (const staffMeasures of this.graphicalMusicSheet.MeasureList) {
             for (const staffMeasure of staffMeasures) {
-                (<VexFlowMeasure>staffMeasure).clean();
+                (<VexFlowMeasure>staffMeasure).format();
+                for (const staffEntry of staffMeasure.staffEntries) {
+                    (<VexFlowStaffEntry>staffEntry).calculateXPosition();
+                }
             }
         }
     }
 
-    //protected clearSystemsAndMeasures(): void {
-    //    for (let measure of measures) {
-    //
-    //    }
-    //}
-
-    /**
-     * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
-     * All staff entries are x-aligned throughout all vertically aligned staff measures.
-     * This method is called within calculateXLayout.
-     * The staff entries are aligned with minimum needed x distances.
-     * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
-     * @param measures
-     * @returns the minimum required x width of the source measure (=list of staff measures)
-     */
-    protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
-        // Finalize beams
-        /*for (let measure of measures) {
-            (measure as VexFlowMeasure).finalizeBeams();
-            (measure as VexFlowMeasure).finalizeTuplets();
-        }*/
-        // Format the voices
-        const allVoices: Vex.Flow.Voice[] = [];
-        const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({
-            align_rests: true,
-        });
-
-        for (const measure of measures) {
-            const mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
-            const voices: Vex.Flow.Voice[] = [];
-            for (const voiceID in mvoices) {
-                if (mvoices.hasOwnProperty(voiceID)) {
-                    voices.push(mvoices[voiceID]);
-                    allVoices.push(mvoices[voiceID]);
+  //protected clearSystemsAndMeasures(): void {
+  //    for (let measure of measures) {
+  //
+  //    }
+  //}
+
+  /**
+   * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
+   * All staff entries are x-aligned throughout all vertically aligned staff measures.
+   * This method is called within calculateXLayout.
+   * The staff entries are aligned with minimum needed x distances.
+   * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
+   * @param measures
+   * @returns the minimum required x width of the source measure (=list of staff measures)
+   */
+  protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
+    // Finalize beams
+    /*for (let measure of measures) {
+     (measure as VexFlowMeasure).finalizeBeams();
+     (measure as VexFlowMeasure).finalizeTuplets();
+     }*/
+    // Format the voices
+    const allVoices: Vex.Flow.Voice[] = [];
+    const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({align_rests: true,
+    });
+
+    for (const measure of measures) {
+        const mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
+        const voices: Vex.Flow.Voice[] = [];
+        for (const voiceID in mvoices) {
+            if (mvoices.hasOwnProperty(voiceID)) {
+                voices.push(mvoices[voiceID]);
+                allVoices.push(mvoices[voiceID]);
 
-                }
-            }
-            if (voices.length === 0) {
-                Logging.warn("Found a measure with no voices... Continuing anyway.", mvoices);
-                continue;
             }
-            formatter.joinVoices(voices);
         }
+        if (voices.length === 0) {
+            log.warn("Found a measure with no voices... Continuing anyway.", mvoices);
+            continue;
+        }
+        formatter.joinVoices(voices);
+    }
 
-        let width: number = 200;
-        if (allVoices.length > 0) {
-            const firstMeasure: VexFlowMeasure = measures[0] as VexFlowMeasure;
-            // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
-            // FIXME: a more relaxed formatting of voices
-            width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
-            for (const measure of measures) {
-                measure.minimumStaffEntriesWidth = width;
-                (measure as VexFlowMeasure).formatVoices = undefined;
+    let width: number = 200;
+    if (allVoices.length > 0) {
+        // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
+        // FIXME: a more relaxed formatting of voices
+        width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
+        // firstMeasure.formatVoices = (w: number) => {
+        //     formatter.format(allVoices, w);
+        // };
+        for (const measure of measures) {
+            measure.minimumStaffEntriesWidth = width;
+            if (measure !== measures[0]) {
+        (measure as VexFlowMeasure).formatVoices = undefined;
+            } else {
+                (measure as VexFlowMeasure).formatVoices = (w: number) => {
+                    formatter.format(allVoices, w);
+                };
             }
-            firstMeasure.formatVoices = (w: number) => {
-                formatter.format(allVoices, w);
-            };
         }
-
-        return width;
     }
-
-    protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
-                                 startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
-        return new GraphicalTie(tie, startNote, endNote);
+    return width;
+  }
+
+  protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
+                               startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
+    return new GraphicalTie(tie, startNote, endNote);
+  }
+
+
+  protected updateStaffLineBorders(staffLine: StaffLine): void {
+    return;
+  }
+
+  protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
+    (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
+  }
+
+  /**
+   * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
+   * Is Excecuted per voice entry of a staff entry.
+   * After that layoutStaffEntry is called.
+   * @param voiceEntry
+   * @param graphicalNotes
+   * @param graphicalStaffEntry
+   * @param hasPitchedNote
+   * @param isGraceStaffEntry
+   */
+  protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
+                             hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
+    return;
+  }
+
+  /**
+   * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
+   * This method is called after the voice entries are handled by layoutVoiceEntry().
+   * @param graphicalStaffEntry
+   */
+  protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
+    (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
+  }
+
+  /**
+   * calculates the y positions of the staff lines within a system and
+   * furthermore the y positions of the systems themselves.
+   */
+  protected calculateSystemYLayout(): void {
+    for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
+      const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
+      if (!this.leadSheet) {
+        let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
+          this.rules.TitleBottomDistance;
+        for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
+          const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
+          // calculate y positions of stafflines within system
+          let y: number = 0;
+          for (const line of musicSystem.StaffLines) {
+            line.PositionAndShape.RelativePosition.y = y;
+            y += 10;
+          }
+          // set y positions of systems using the previous system and a fixed distance.
+          musicSystem.PositionAndShape.BorderBottom = y + 0;
+          musicSystem.PositionAndShape.RelativePosition.x = this.rules.PageLeftMargin + this.rules.SystemLeftMargin;
+          musicSystem.PositionAndShape.RelativePosition.y = globalY;
+          globalY += y + 5;
+        }
+      }
     }
+  }
+
+  /**
+   * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
+   */
+  protected initStaffMeasuresCreation(): void {
+    return;
+  }
+
+  /**
+   * add here all given articulations to the VexFlowGraphicalStaffEntry and prepare them for rendering.
+   * @param articulations
+   * @param voiceEntry
+   * @param graphicalStaffEntry
+   */
+  protected layoutArticulationMarks(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+
+    return;
+  }
 
-
-    protected updateStaffLineBorders(staffLine: StaffLine): void {
-        return;
+    /**
+     * Calculate the shape (Bezier curve) for this tie.
+     * @param tie
+     * @param tieIsAtSystemBreak
+     */
+  protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
+    const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
+    const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
+
+    let vfStartNote: Vex.Flow.StaveNote = undefined;
+    let startNoteIndexInTie: number = 0;
+    if (startNote !== undefined) {
+      vfStartNote = startNote.vfnote[0];
+      startNoteIndexInTie = startNote.vfnote[1];
     }
 
-    protected calculateMeasureNumberPlacement(musicSystem: MusicSystem): void {
-        return;
+    let vfEndNote: Vex.Flow.StaveNote = undefined;
+    let endNoteIndexInTie: number = 0;
+    if (endNote !== undefined) {
+      vfEndNote = endNote.vfnote[0];
+      endNoteIndexInTie = endNote.vfnote[1];
     }
 
-    protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
-        (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
+    if (tieIsAtSystemBreak) {
+      // split tie into two ties:
+      const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
+        first_indices: [startNoteIndexInTie],
+        first_note: vfStartNote
+      });
+      const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
+      measure1.vfTies.push(vfTie1);
+
+      const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
+        last_indices: [endNoteIndexInTie],
+        last_note: vfEndNote
+      });
+      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_indices: [endNoteIndexInTie],
+        last_note: vfEndNote
+      });
+      const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
+      measure.vfTies.push(vfTie);
     }
+  }
 
     /**
-     * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
-     * Is Excecuted per voice entry of a staff entry.
-     * After that layoutStaffEntry is called.
-     * @param voiceEntry
-     * @param graphicalNotes
-     * @param graphicalStaffEntry
-     * @param hasPitchedNote
-     * @param isGraceStaffEntry
+     * Calculate a single OctaveShift for a [[MultiExpression]].
+     * @param sourceMeasure
+     * @param multiExpression
+     * @param measureIndex
+     * @param staffIndex
      */
-    protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
-                               hasPitchedNote: boolean, isGraceStaffEntry: boolean): void {
-        return;
-    }
+  protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
+    return;
+  }
 
     /**
-     * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
-     * This method is called after the voice entries are handled by layoutVoiceEntry().
-     * @param graphicalStaffEntry
+     * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
+     * @param repetitionInstruction
+     * @param measureIndex
      */
-    protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
-        (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
+  protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
+      // find first visible StaffLine
+      let uppermostMeasure: VexFlowMeasure = undefined;
+      const measures: VexFlowMeasure[]  = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
+      for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
+        const graphicalMeasure: VexFlowMeasure = measures[idx];
+        if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
+            uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
+            break;
+        }
+      }
+      // ToDo: feature/Repetitions
+      // now create corresponding graphical symbol or Text in VexFlow:
+      // use top measure and staffline for positioning.
+      if (uppermostMeasure !== undefined) {
+        uppermostMeasure.addWordRepetition(repetitionInstruction.type);
+      }
     }
 
+  protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
+    return;
+  }
+
     /**
-     * calculates the y positions of the staff lines within a system and
-     * furthermore the y positions of the systems themselves.
+     * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
+     * @param tiedGraphicalNote
+     * @param beams
+     * @param activeClef
+     * @param octaveShiftValue
+     * @param graphicalStaffEntry
+     * @param duration
+     * @param openTie
+     * @param isLastTieNote
      */
-    protected calculateSystemYLayout(): void {
-        for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
-            const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
-            if (!this.leadSheet) {
-                let globalY: number = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
-                    this.rules.TitleBottomDistance;
-                for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
-                    const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
-                    // calculate y positions of stafflines within system
-                    let y: number = 0;
-                    for (const line of musicSystem.StaffLines) {
-                        line.PositionAndShape.RelativePosition.y = y;
-                        y += 10;
+  protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
+                                    octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
+                                    openTie: Tie, isLastTieNote: boolean): void {
+    return;
+  }
+
+  /**
+   * Is called if a note is part of a beam.
+   * @param graphicalNote
+   * @param beam
+   * @param openBeams a list of all currently open beams
+   */
+  protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
+    (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
+  }
+
+    protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
+        voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
+            const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
+                                                                                     graphicalStaffEntry,
+                                                                                     this.rules.LyricsHeight,
+                                                                                     this.rules.StaffHeight);
+
+            graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
+
+            // create corresponding GraphicalLabel
+            const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
+            graphicalLabel.setLabelPositionAndShapeBorders();
+
+            if (lyricsEntry.Word !== undefined) {
+                const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
+                let index: number = lyricWords.indexOf(lyricsEntry.Word);
+                if (index === -1) {
+                    lyricWords.push(lyricsEntry.Word);
+                    index = lyricWords.indexOf(lyricsEntry.Word);
+  }
+
+                if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
+                    const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
+
+                    graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+                    graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+                    this.graphicalLyricWords.push(graphicalLyricWord);
+                } else {
+                    const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
+
+                    graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
+                    graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
+
+                    if (graphicalLyricWord.isFilled()) {
+                        lyricWords.splice(index, 1);
+                        this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
                     }
-                    // set y positions of systems using the previous system and a fixed distance.
-                    musicSystem.PositionAndShape.BorderBottom = y + 0;
-                    musicSystem.PositionAndShape.RelativePosition.x = this.rules.PageLeftMargin + this.rules.SystemLeftMargin;
-                    musicSystem.PositionAndShape.RelativePosition.y = globalY;
-                    globalY += y + 5;
                 }
             }
-        }
-    }
-
-    /**
-     * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
-     */
-    protected initStaffMeasuresCreation(): void {
-        return;
-    }
-
-    protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
-        const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
-        let vfStartNote: Vex.Flow.StaveNote = undefined;
-        if (startNote !== undefined) {
-            vfStartNote = startNote.vfnote[0];
-        }
-
-        const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
-        let vfEndNote: Vex.Flow.StaveNote = undefined;
-        if (endNote !== undefined) {
-            vfEndNote = endNote.vfnote[0];
-        }
-
-
-        if (tieIsAtSystemBreak) {
-            // split tie into two ties:
-            const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                first_note: vfStartNote,
-            });
-            const measure1: VexFlowMeasure = (startNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
-            measure1.vfTies.push(vfTie1);
-
-            const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                last_note : vfEndNote,
-            });
-            const measure2: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
-            measure2.vfTies.push(vfTie2);
-        } else {
-            const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
-                first_note: vfStartNote,
-                last_note : vfEndNote,
-            });
-            const measure: VexFlowMeasure = (endNote.parentStaffEntry.parentMeasure as VexFlowMeasure);
-            measure.vfTies.push(vfTie);
-        }
-    }
-
-    protected calculateSingleStaffLineLyricsPosition(staffLine: StaffLine, lyricVersesNumber: number[]): void {
-        return;
-    }
-
-    protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
-        return;
-    }
-
-    protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
-        return;
-    }
-
-    protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
-        return;
-    }
-
-    protected handleTiedGraphicalNote(  tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
-                                        octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
-                                        openTie: Tie, isLastTieNote: boolean): void {
-        return;
-    }
-
-    /**
-     * Is called if a note is part of a beam.
-     * @param graphicalNote
-     * @param beam
-     * @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);
-    }
-
-    protected handleVoiceEntryLyrics(lyricsEntries: Dictionary<number, LyricsEntry>, voiceEntry: VoiceEntry,
-                                     graphicalStaffEntry: GraphicalStaffEntry, openLyricWords: LyricWord[]): void {
-        return;
-    }
-
-    protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
-    }
-
-    protected handleVoiceEntryArticulations(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
-        return;
+        });
     }
 
-    /**
-     * Is called if a note is part of a tuplet.
-     * @param graphicalNote
-     * @param tuplet
-     * @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);
-    }
+  protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
+    return;
+  }
+
+  /**
+   * Add articulations to the given vexflow staff entry.
+   * @param articulations
+   * @param voiceEntry
+   * @param graphicalStaffEntry
+   */
+  protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
+                                          voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+
+    return;
+  }
+
+  /**
+   * Add technical instructions to the given vexflow staff entry.
+   * @param technicalInstructions
+   * @param voiceEntry
+   * @param staffEntry
+   */
+  protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
+                                                  voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
+    // uncomment this when implementing:
+    // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
+    return;
+  }
+
+  /**
+   * Is called if a note is part of a tuplet.
+   * @param graphicalNote
+   * @param tuplet
+   * @param openTuplets a list of all currently open tuplets
+   */
+  protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
+    (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
+  }
 }

+ 28 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -10,6 +10,8 @@ import {GraphicalLayers} from "../DrawingEnums";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {VexFlowBackend} from "./VexFlowBackend";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
+import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
 
 /**
  * This is a global constant which denotes the height in pixels of the space between two lines of the stave
@@ -70,6 +72,16 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
             measure.PositionAndShape.AbsolutePosition.y * unitInPixels
         );
         measure.draw(this.backend.getContext());
+        for (const voiceID in measure.vfVoices) {
+            if (measure.vfVoices.hasOwnProperty(voiceID)) {
+                const tickables: Vex.Flow.Tickable[] = measure.vfVoices[voiceID].tickables;
+                for (const tick of tickables) {
+                    if ((<any>tick).getAttribute("type") === "StaveNote" && process.env.DEBUG) {
+                        tick.getBoundingBox().draw(this.backend.getContext());
+                    }
+                }
+            }
+        }
 
         // Draw the StaffEntries
         for (const staffEntry of measure.staffEntries) {
@@ -82,16 +94,30 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         if (staffEntry.graphicalChordContainer !== undefined) {
             this.drawLabel(staffEntry.graphicalChordContainer.GetGraphicalLabel, <number>GraphicalLayers.Notes);
         }
+        if (staffEntry.LyricsEntries.length > 0) {
+            this.drawLyrics(staffEntry.LyricsEntries, <number>GraphicalLayers.Notes);
+        }
+    }
+
+    /**
+     * Draw all lyrics to the canvas
+     * @param lyricEntries Array of lyric entries to be drawn
+     * @param layer Number of the layer that the lyrics should be drawn in
+     */
+    private drawLyrics(lyricEntries: GraphicalLyricEntry[], layer: number): void {
+        lyricEntries.forEach(lyricsEntry => this.drawLabel(lyricsEntry.GraphicalLabel, layer));
     }
 
     protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {
         // Draw InstrumentBrackets at beginning of line
-        const vexBrace: VexFlowInstrumentBracket = (brace as VexFlowInstrumentBracket);
+        const vexBrace: VexFlowInstrumentBrace = (brace as VexFlowInstrumentBrace);
         vexBrace.draw(this.backend.getContext());
     }
 
     protected drawGroupBracket(bracket: GraphicalObject, system: MusicSystem): void {
-        // empty
+        // Draw InstrumentBrackets at beginning of line
+        const vexBrace: VexFlowInstrumentBracket = (bracket as VexFlowInstrumentBracket);
+        vexBrace.draw(this.backend.getContext());
     }
 
     /**

+ 17 - 4
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSystem.ts

@@ -10,6 +10,7 @@ import {VexFlowConverter} from "./VexFlowConverter";
 import {StaffLine} from "../StaffLine";
 import {EngravingRules} from "../EngravingRules";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
 
 export class VexFlowMusicSystem extends MusicSystem {
     constructor(parent: GraphicalMusicPage, id: number) {
@@ -39,9 +40,13 @@ export class VexFlowMusicSystem extends MusicSystem {
      */
     protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
                                musicSystem: MusicSystem, topMeasure: StaffMeasure, bottomMeasure: StaffMeasure = undefined): SystemLine {
-        // ToDo: create line in Vexflow
+        const vfMeasure: VexFlowMeasure = topMeasure as VexFlowMeasure;
+        vfMeasure.addMeasureLine(lineType, linePosition);
         if (bottomMeasure) {
-            (bottomMeasure as VexFlowMeasure).lineTo(topMeasure as VexFlowMeasure, VexFlowConverter.line(lineType));
+          // ToDo: feature/Repetitions
+          // create here the correct lines according to the given lineType.
+          (bottomMeasure as VexFlowMeasure).lineTo(topMeasure as VexFlowMeasure, VexFlowConverter.line(lineType, linePosition));
+          (bottomMeasure as VexFlowMeasure).addMeasureLine(lineType, linePosition);
         }
         return new SystemLine(lineType, linePosition, this, topMeasure, bottomMeasure);
     }
@@ -56,7 +61,7 @@ export class VexFlowMusicSystem extends MusicSystem {
         // You could write this in one line but the linter doesn't let me.
         const firstVexStaff: VexFlowStaffLine = (firstStaffLine as VexFlowStaffLine);
         const lastVexStaff: VexFlowStaffLine = (lastStaffLine as VexFlowStaffLine);
-        const vexFlowBracket: VexFlowInstrumentBracket = new VexFlowInstrumentBracket(firstVexStaff, lastVexStaff);
+        const vexFlowBracket: VexFlowInstrumentBrace = new VexFlowInstrumentBrace(firstVexStaff, lastVexStaff);
         this.InstrumentBrackets.push(vexFlowBracket);
         return;
     }
@@ -67,10 +72,18 @@ export class VexFlowMusicSystem extends MusicSystem {
      * The recursion depth informs about the current depth level (needed for positioning)
      * @param firstStaffLine the upper staff line of the bracket to create
      * @param lastStaffLine the lower staff line of the bracket to create
-     * @param staffHeight
      * @param recursionDepth
      */
     protected createGroupBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine, recursionDepth: number): void {
+        const firstVexStaff: VexFlowStaffLine = (firstStaffLine as VexFlowStaffLine);
+        const lastVexStaff: VexFlowStaffLine = (lastStaffLine as VexFlowStaffLine);
+        if (recursionDepth === 0) {
+            const vexFlowBracket: VexFlowInstrumentBracket = new VexFlowInstrumentBracket(firstVexStaff, lastVexStaff, recursionDepth);
+            this.GroupBrackets.push(vexFlowBracket);
+        } else {
+            const vexFlowBrace: VexFlowInstrumentBrace = new VexFlowInstrumentBrace(firstVexStaff, lastVexStaff, recursionDepth);
+            this.GroupBrackets.push(vexFlowBrace);
+        }
         return;
     }
 }

+ 32 - 20
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -1,36 +1,48 @@
 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; } = {};
-
     /**
-     *
-     * @returns {number} the x-position (in units) of this StaffEntry
+     * 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 getX(): number {
-        let x: number = 0;
-        let n: number = 0;
-        const vfNotes: { [voiceID: number]: Vex.Flow.StaveNote; } = this.vfNotes;
-        for (const voiceId in vfNotes) {
-            if (vfNotes.hasOwnProperty(voiceId)) {
-                x += (vfNotes[voiceId].getNoteHeadBeginX() + vfNotes[voiceId].getNoteHeadEndX()) / 2;
-                n += 1;
+    public calculateXPosition(): void {
+        const stave: Vex.Flow.Stave = (this.parentMeasure as VexFlowMeasure).getVFStave();
+        let tickablePosition: number = 0;
+        let numberOfValidTickables: number = 0;
+        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 = tickable as Vex.Flow.StaveNote;
+                tickablePosition += staveNote.getNoteHeadEndX() - staveNote.getGlyphWidth() / 2;
+            } 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++;
         }
-        if (n === 0) {
-            return 0;
-        }
-        return x / n / unitInPixels;
+        tickablePosition = tickablePosition / numberOfValidTickables;
+        // Calculate parent absolute position and reverse calculate the relative position
+        // All the modifiers signs, clefs, you name it have an offset in the measure. Therefore remove it.
+        // NOTE: Somehow vexflows shift is off by 25px.
+        const modifierOffset: number = stave.getModifierXShift() - (this.parentMeasure.MeasureNumber === 1 ? 25 : 0);
+        // const modifierOffset: number = 0;
+        // sets the vexflow x positions back into the bounding boxes of the staff entries in the osmd object model.
+        // The positions are needed for cursor placement and mouse/tap interactions
+        this.PositionAndShape.RelativePosition.x = (tickablePosition - stave.getNoteStartX() + modifierOffset) / unitInPixels;
     }
 }

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

@@ -5,6 +5,5 @@ import {Staff} from "../../VoiceData/Staff";
 export class VexFlowStaffLine extends StaffLine {
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super(parentSystem, parentStaff);
-
     }
 }

+ 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;
+}

+ 2 - 0
src/MusicalScore/Instrument.ts

@@ -173,6 +173,8 @@ export class Instrument extends InstrumentalGroup {
     public SetStaffAudible(staffId: number, audible: boolean): void {
         const staff: Staff = this.staves[staffId - 1];
         staff.audible = audible;
+        // hack for now:
+        // activate all voices needed so that the staff notes will be played
         if (audible) {
             for (let idx: number = 0, len: number = staff.Voices.length; idx < len; ++idx) {
                 const v: Voice = staff.Voices[idx];

+ 8 - 0
src/MusicalScore/Interfaces/IAfterSheetReadingModule.ts

@@ -0,0 +1,8 @@
+import {MusicSheet} from "../MusicSheet";
+/**
+ * Created by Matthias on 22.02.2017.
+ */
+
+export interface IAfterSheetReadingModule {
+  calculate(musicSheet: MusicSheet): void;
+}

+ 9 - 3
src/MusicalScore/Interfaces/IGraphicalSymbolFactory.ts

@@ -12,7 +12,9 @@ import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
 import {Staff} from "../VoiceData/Staff";
 import {StaffLine} from "../Graphical/StaffLine";
 import {StaffMeasure} from "../Graphical/StaffMeasure";
-import {TechnicalInstruction} from "../VoiceData/Instructions/TechnicalInstruction";
+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;
 
@@ -48,6 +53,7 @@ export interface IGraphicalSymbolFactory {
         technicalInstruction: TechnicalInstruction,
         graphicalStaffEntry: GraphicalStaffEntry): void;
 
+
     createInStaffClef(graphicalStaffEntry: GraphicalStaffEntry, clefInstruction: ClefInstruction): void;
 
     createChordSymbol(

+ 10 - 0
src/MusicalScore/MusicParts/MusicPartManager.ts

@@ -13,9 +13,17 @@ export class MusicPartManager /*implements ISelectionListener*/ {
     private musicSheet: MusicSheet;
     private sheetStart: Fraction;
     private sheetEnd: Fraction;
+
+    /**
+     * This method is called from CoreContainer when the user changes a Repetitions's userNumberOfRepetitions.
+     */
     public reInit(): void {
         this.init();
     }
+
+    /**
+     * Main initialize method for MusicPartManager.
+     */
     public init(): void {
         this.parts = this.musicSheet.Repetitions.slice();
         this.sheetStart = this.musicSheet.SelectionStart = new Fraction(0, 1);
@@ -73,6 +81,8 @@ export class MusicPartManager /*implements ISelectionListener*/ {
         while (!iterator.EndReached) {
             if (iterator.JumpOccurred || currentRepetition !== iterator.CurrentRepetition) {
                 currentRepetition = iterator.CurrentRepetition;
+                // if we are still in the same repetition but in a different repetition run, we remember
+                // that we have to jump backwards at this position
                 if (iterator.backJumpOccurred) {
                     const jumpRep: Repetition = iterator.JumpResponsibleRepetition;
                     curTimestampTransform.nextBackJump = iterator.CurrentEnrolledTimestamp;

+ 34 - 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);
         }
 
     }
@@ -128,6 +128,10 @@ export class MusicPartManagerIterator {
     public get JumpResponsibleRepetition(): Repetition {
         return this.jumpResponsibleRepetition;
     }
+
+    /**
+     * Creates a clone of this iterator which has the same actual position.
+     */
     public clone(): MusicPartManagerIterator {
         const ret: MusicPartManagerIterator = new MusicPartManagerIterator(this.manager);
         ret.currentVoiceEntryIndex = this.currentVoiceEntryIndex;
@@ -139,6 +143,11 @@ export class MusicPartManagerIterator {
         return ret;
     }
 
+    /**
+     * Returns the visible voice entries for the provided instrument of the current iterator position.
+     * @param instrument
+     * Returns: A List of voiceEntries. If there are no entries the List has a Count of 0 (it does not return null).
+     */
     public CurrentVisibleVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
@@ -159,6 +168,11 @@ export class MusicPartManagerIterator {
         return voiceEntries;
     }
 
+    /**
+     * Returns the visible voice entries for the provided instrument of the current iterator position.
+     * @param instrument
+     * Returns: A List of voiceEntries. If there are no entries the List has a Count of 0 (it does not return null).
+     */
     public CurrentAudibleVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
@@ -179,10 +193,20 @@ export class MusicPartManagerIterator {
         return voiceEntries;
     }
 
+    /**
+     * Returns the audible dynamics of the current iterator position.
+     * Returns: A List of Dynamics. If there are no entries the List has a Count of 0 (it does not return null).
+     */
     public getCurrentDynamicChangingExpressions(): DynamicsContainer[] {
         return this.currentDynamicChangingExpressions;
     }
 
+    /**
+     * Returns the score following voice entries for the provided instrument of the current iterator position.
+     * @param instrument
+     * Returns: A List of voiceEntries. If there are no entries the List has a Count of 0
+     * (it does not return null).
+     */
     public CurrentScoreFollowingVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
@@ -471,6 +495,7 @@ export class MusicPartManagerIterator {
             this.handleRepetitionsAtMeasureBegin();
             this.activateCurrentRhythmInstructions();
         }
+        // everything fine, no complications
         if (this.currentVoiceEntryIndex >= 0 && this.currentVoiceEntryIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length) {
             const currentContainer: VerticalSourceStaffEntryContainer = this.currentMeasure.VerticalSourceStaffEntryContainers[this.currentVoiceEntryIndex];
             this.currentVoiceEntries = this.getVoiceEntries(currentContainer);
@@ -491,11 +516,18 @@ export class MusicPartManagerIterator {
             this.recursiveMove();
             return;
         }
+        // we reached the end
         this.currentVerticalContainerInMeasureTimestamp = new Fraction();
         this.currentMeasure = undefined;
         this.currentVoiceEntries = undefined;
         this.endReached = true;
     }
+
+    /**
+     * helper function for moveToNextVisibleVoiceEntry and moveToPreviousVisibleVoiceEntry
+     * Get all entries and check if there is at least one valid entry in the list
+     * @param notesOnly
+     */
     private checkEntries(notesOnly: boolean): boolean {
         const tlist: VoiceEntry[] = this.CurrentVisibleVoiceEntries();
         if (tlist.length > 0) {

+ 18 - 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 (*)
 
@@ -27,15 +27,19 @@ export class PlaybackSettings {
 /**
  * This is the representation of a complete piece of sheet music.
  * It includes the contents of a MusicXML file after the reading.
+ * Notes: the musicsheet might not need the Rules, e.g. in the testframework. The EngravingRules Constructor
+ * fails when no FontInfo exists, which needs a TextMeasurer
  */
 export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet>*/ {
     constructor() {
         this.rules = EngravingRules.Rules;
         this.playbackSettings = new PlaybackSettings();
         // FIXME?
+        // initialize SheetPlaybackSetting with default values
         this.playbackSettings.rhythm = new Fraction(4, 4, 0, false);
         this.userStartTempoInBPM = 100;
         this.pageWidth = 120;
+        // create MusicPartManager
         this.MusicPartManager = new MusicPartManager(this);
     }
     public static defaultTitle: string = "[kein Titel]";
@@ -291,6 +295,10 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         }
         return measures;
     }
+    /**
+     * Returns the next SourceMeasure from a given SourceMeasure.
+     * @param measure
+     */
     public getNextSourceMeasure(measure: SourceMeasure): SourceMeasure {
         const index: number = this.sourceMeasures.indexOf(measure);
         if (index === this.sourceMeasures.length - 1) {
@@ -298,9 +306,15 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         }
         return this.sourceMeasures[index + 1];
     }
+    /**
+     * Returns the first SourceMeasure of MusicSheet.
+     */
     public getFirstSourceMeasure(): SourceMeasure {
         return this.sourceMeasures[0];
     }
+    /**
+     * Returns the last SourceMeasure of MusicSheet.
+     */
     public getLastSourceMeasure(): SourceMeasure {
         return this.sourceMeasures[this.sourceMeasures.length - 1];
     }
@@ -386,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;
     //    }
     //
@@ -409,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;
         }
 
@@ -418,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);
             }
 
         }

+ 33 - 38
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -17,14 +17,14 @@ 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 {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
+import { RepetitionInstructionReader } from "./MusicSymbolModules/RepetitionInstructionReader";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 // FIXME: The following classes are missing
-//type repetitionInstructionReader = any;
 //type ChordSymbolContainer = any;
 //type SlurReader = any;
 //type RepetitionInstructionReader = any;
@@ -43,10 +43,6 @@ import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
 //  }
 //}
 
-/**
- * To be implemented
- */
-export type RepetitionInstructionReader = any;
 
 /**
  * An InstrumentReader is used during the reading phase to keep parsing new measures from the MusicXML file
@@ -55,7 +51,7 @@ export type RepetitionInstructionReader = any;
 export class InstrumentReader {
 
   constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
-      // this.repetitionInstructionReader = repetitionInstructionReader;
+      this.repetitionInstructionReader = repetitionInstructionReader;
       this.xmlMeasureList = xmlMeasureList;
       this.musicSheet = instrument.GetMusicSheet;
       this.instrument = instrument;
@@ -123,9 +119,9 @@ export class InstrumentReader {
     }
     this.currentMeasure = currentMeasure;
     this.inSourceMeasureInstrumentIndex = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument);
-    // (*) if (this.repetitionInstructionReader !== undefined) {
-    //  this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
-    //}
+    if (this.repetitionInstructionReader !== undefined) {
+     this.repetitionInstructionReader.prepareReadingMeasure(currentMeasure, this.currentXmlMeasureIndex);
+    }
     let currentFraction: Fraction = new Fraction(0, 1);
     let previousFraction: Fraction = new Fraction(0, 1);
     let divisionsException: boolean = false;
@@ -143,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;
               }
             }
@@ -176,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) {
@@ -193,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);
@@ -241,7 +237,7 @@ export class InstrumentReader {
           }
           const notationsNode: IXmlElement = xmlNode.element("notations");
           if (notationsNode !== undefined && notationsNode.element("dynamics") !== undefined) {
-            let expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
+            const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
             if (expressionReader !== undefined) {
              expressionReader.readExpressionParameters(
                xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
@@ -259,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);
@@ -306,7 +302,7 @@ export class InstrumentReader {
             previousFraction = new Fraction(0, 1);
           }
         } else if (xmlNode.name === "direction") {
-          let directionTypeNode: IXmlElement = xmlNode.element("direction-type");
+          const directionTypeNode: IXmlElement = xmlNode.element("direction-type");
           //(*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
           let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
           if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
@@ -319,7 +315,7 @@ export class InstrumentReader {
           }
           if (!handeled) {
            let expressionReader: ExpressionReader = this.expressionReaders[0];
-           let staffIndex: number = this.readExpressionStaffNumber(xmlNode) - 1;
+           const staffIndex: number = this.readExpressionStaffNumber(xmlNode) - 1;
            if (staffIndex < this.expressionReaders.length) {
              expressionReader = this.expressionReaders[staffIndex];
            }
@@ -337,15 +333,14 @@ export class InstrumentReader {
            }
           }
         } else if (xmlNode.name === "barline") {
-
-          //if (this.repetitionInstructionReader !== undefined) {
-          //  let measureEndsSystem: boolean = false;
-          //  this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode, measureEndsSystem);
-          //  if (measureEndsSystem) {
-          //    this.currentMeasure.BreakSystemAfter = true;
-          //    this.currentMeasure.endsPiece = true;
-          //  }
-          //}
+          if (this.repetitionInstructionReader !== undefined) {
+           const measureEndsSystem: boolean = false;
+           this.repetitionInstructionReader.handleLineRepetitionInstructions(xmlNode, measureEndsSystem);
+           if (measureEndsSystem) {
+             this.currentMeasure.BreakSystemAfter = true;
+             this.currentMeasure.endsPiece = true;
+           }
+          }
         } else if (xmlNode.name === "sound") {
           // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
         } else if (xmlNode.name === "harmony") {
@@ -370,7 +365,7 @@ export class InstrumentReader {
         }
 
         for (let i: number = 0; i < this.expressionReaders.length; i++) {
-         let reader: ExpressionReader = this.expressionReaders[i];
+         const reader: ExpressionReader = this.expressionReaders[i];
          if (reader !== undefined) {
            reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
          }
@@ -382,7 +377,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;
@@ -590,7 +585,7 @@ export class InstrumentReader {
             );
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             line = 2;
-            Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+            log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
           }
 
         }
@@ -618,7 +613,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);
           }
 
         }
@@ -666,7 +661,7 @@ export class InstrumentReader {
           );
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           key = 0;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
       }
@@ -685,7 +680,7 @@ export class InstrumentReader {
           );
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           keyEnum = KeyEnum.major;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
       }
@@ -755,7 +750,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)) {
@@ -969,17 +964,17 @@ export class InstrumentReader {
   private readExpressionStaffNumber(xmlNode: IXmlElement): number {
    let directionStaffNumber: number = 1;
    if (xmlNode.element("staff") !== undefined) {
-     let staffNode: IXmlElement = xmlNode.element("staff");
+     const staffNode: IXmlElement = xmlNode.element("staff");
      if (staffNode !== undefined) {
        try {
          directionStaffNumber = parseInt(staffNode.value, 10);
        } catch (ex) {
-         let errorMsg: string = ITextTranslation.translateText(
+         const errorMsg: string = ITextTranslation.translateText(
            "ReaderErrorMessages/ExpressionStaffError", "Invalid Expression staff number -> set to default."
          );
          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
          directionStaffNumber = 1;
-         Logging.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
+         log.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
        }
 
      }
@@ -1013,7 +1008,7 @@ export class InstrumentReader {
               try {
                 noteDuration = parseInt(durationNode.value, 10);
               } catch (ex) {
-                Logging.debug("InstrumentReader.readDivisionsFromNotes", ex);
+                log.debug("InstrumentReader.readDivisionsFromNotes", ex);
                 continue;
               }
 

+ 28 - 33
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";
@@ -17,31 +17,26 @@ import {SubInstrument} from "../SubInstrument";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {Label} from "../Label";
-
-/**
- * To be implemented
- */
-type RepetitionInstructionReader = any;
-/**
- * To be implemented
- */
-type RepetitionCalculator = any;
+import {MusicSymbolModuleFactory} from "./MusicSymbolModuleFactory";
+import {IAfterSheetReadingModule} from "../Interfaces/IAfterSheetReadingModule";
+import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
+import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
 
 export class MusicSheetReader /*implements IMusicSheetReader*/ {
 
-    //constructor(afterSheetReadingModules: IAfterSheetReadingModule[]) {
-    //  if (afterSheetReadingModules === undefined) {
-    //    this.afterSheetReadingModules = [];
-    //  } else {
-    //    this.afterSheetReadingModules = afterSheetReadingModules;
-    //  }
-    //  this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
-    //  this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
-    //}
+    constructor(afterSheetReadingModules: IAfterSheetReadingModule[] = undefined) {
+     if (afterSheetReadingModules === undefined) {
+       this.afterSheetReadingModules = [];
+     } else {
+       this.afterSheetReadingModules = afterSheetReadingModules;
+     }
+     this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
+     this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
+    }
 
     private repetitionInstructionReader: RepetitionInstructionReader;
     private repetitionCalculator: RepetitionCalculator;
-    // private afterSheetReadingModules: IAfterSheetReadingModule[];
+    private afterSheetReadingModules: IAfterSheetReadingModule[];
     private musicSheet: MusicSheet;
     private completeNumberOfStaves: number = 0;
     private currentMeasure: SourceMeasure;
@@ -68,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);
         }
     }
 
@@ -176,16 +171,16 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         if (this.repetitionInstructionReader !== undefined) {
             this.repetitionInstructionReader.removeRedundantInstructions();
             if (this.repetitionCalculator !== undefined) {
-                this.repetitionCalculator.calculateRepetitions(this.musicSheet, this.repetitionInstructionReader.RepetitionInstructions);
+                this.repetitionCalculator.calculateRepetitions(this.musicSheet, this.repetitionInstructionReader.repetitionInstructions);
             }
         }
         this.musicSheet.checkForInstrumentWithNoVoice();
         this.musicSheet.fillStaffList();
         //this.musicSheet.DefaultStartTempoInBpm = this.musicSheet.SheetPlaybackSetting.BeatsPerMinute;
-        //for (let idx: number = 0, len: number = this.afterSheetReadingModules.length; idx < len; ++idx) {
-        //  let afterSheetReadingModule: IAfterSheetReadingModule = this.afterSheetReadingModules[idx];
-        //  afterSheetReadingModule.calculate(this.musicSheet);
-        //}
+        for (let idx: number = 0, len: number = this.afterSheetReadingModules.length; idx < len; ++idx) {
+         const afterSheetReadingModule: IAfterSheetReadingModule = this.afterSheetReadingModules[idx];
+         afterSheetReadingModule.calculate(this.musicSheet);
+        }
 
         return this.musicSheet;
     }
@@ -194,7 +189,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         const instrumentDict: { [_: string]: Instrument; } = this.createInstrumentGroups(partList);
         this.completeNumberOfStaves = this.getCompleteNumberOfStavesFromXml(partInst);
         if (partInst.length !== 0) {
-            // (*) this.repetitionInstructionReader.MusicSheet = this.musicSheet;
+            this.repetitionInstructionReader.MusicSheet = this.musicSheet;
             this.currentFraction = new Fraction(0, 1);
             this.currentMeasure = undefined;
             this.previousMeasure = undefined;
@@ -220,7 +215,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                 currentInstrument.createStaves(instrumentNumberOfStaves);
                 instrumentReaders.push(new InstrumentReader(this.repetitionInstructionReader, xmlMeasureList, currentInstrument));
                 if (this.repetitionInstructionReader !== undefined) {
-                    this.repetitionInstructionReader.XmlMeasureList[counter] = xmlMeasureList;
+                    this.repetitionInstructionReader.xmlMeasureList[counter] = xmlMeasureList;
                 }
                 counter++;
             }
@@ -503,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);
             }
 
         }
@@ -752,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") {
@@ -760,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);
                         }
 
                     }

+ 31 - 0
src/MusicalScore/ScoreIO/MusicSymbolModuleFactory.ts

@@ -0,0 +1,31 @@
+import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
+import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
+
+export class MusicSymbolModuleFactory {
+  public static createRepetitionInstructionReader(): RepetitionInstructionReader {
+    return new RepetitionInstructionReader();
+  }
+
+  public static createRepetitionCalculator(): RepetitionCalculator {
+    return new RepetitionCalculator();
+  }
+
+  /*
+   public static createExpressionGenerator(musicSheet: MusicSheet,
+   instrument: Instrument, staffNumber: number): ExpressionReader {
+   return new ExpressionReader(musicSheet, instrument, staffNumber);
+   }
+
+   public static createSlurReader(musicSheet: MusicSheet): SlurReader {
+   return new SlurReader(musicSheet);
+   }
+
+   public static createLyricsReader(musicSheet: MusicSheet): LyricsReader {
+   return new LyricsReader(musicSheet);
+   }
+
+   public static createArticulationReader(): ArticulationReader {
+   return new ArticulationReader();
+   }
+   */
+}

+ 195 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts

@@ -0,0 +1,195 @@
+import {ArticulationEnum, VoiceEntry} from "../../VoiceData/VoiceEntry";
+import {IXmlAttribute, IXmlElement} from "../../../Common/FileIO/Xml";
+import * as log from "loglevel";
+import {TechnicalInstruction, TechnicalInstructionType} from "../../VoiceData/Instructions/TechnicalInstruction";
+import {OrnamentContainer, OrnamentEnum} from "../../VoiceData/OrnamentContainer";
+import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
+import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
+export class ArticulationReader {
+
+  private getAccEnumFromString(input: string): AccidentalEnum {
+    switch (input) {
+      case "natural":
+        return AccidentalEnum.NATURAL;
+      case "sharp":
+        return AccidentalEnum.SHARP;
+      case "sharp-sharp":
+      case "double-sharp":
+        return AccidentalEnum.DOUBLESHARP;
+      case "flat":
+        return AccidentalEnum.FLAT;
+      case "flat-flat":
+        return AccidentalEnum.DOUBLEFLAT;
+      default:
+        return AccidentalEnum.NONE;
+    }
+  }
+
+  /**
+   * This method adds an Articulation Expression to the currentVoiceEntry.
+   * @param node
+   * @param currentVoiceEntry
+   */
+  public addArticulationExpression(node: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    if (node !== undefined && node.elements().length > 0) {
+      const childNotes: IXmlElement[] = node.elements();
+      for (let idx: number = 0, len: number = childNotes.length; idx < len; ++idx) {
+        const childNote: IXmlElement = childNotes[idx];
+        const name: string = childNote.name;
+        try {
+          // some Articulations appear in Xml separated with a "-" (eg strong-accent), we remove it for enum parsing
+          name.replace("-", "");
+          const articulationEnum: ArticulationEnum = ArticulationEnum[name];
+          if (VoiceEntry.isSupportedArticulation(articulationEnum)) {
+            // staccato should be first
+            if (name === "staccato") {
+              if (currentVoiceEntry.Articulations.length > 0 &&
+                currentVoiceEntry.Articulations[0] !== ArticulationEnum.staccato) {
+                currentVoiceEntry.Articulations.splice(0, 0, articulationEnum);
+              }
+            }
+
+            // don't add the same articulation twice
+            if (currentVoiceEntry.Articulations.indexOf(articulationEnum) === -1) {
+              currentVoiceEntry.Articulations.push(articulationEnum);
+            }
+          }
+        } catch (ex) {
+          const errorMsg: string = "Invalid note articulation.";
+          log.debug("addArticulationExpression", errorMsg, ex);
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * This method add a Fermata to the currentVoiceEntry.
+   * @param xmlNode
+   * @param currentVoiceEntry
+   */
+  public addFermata(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    // fermata appears as separate tag in XML
+    let articulationEnum: ArticulationEnum = ArticulationEnum.fermata;
+    if (xmlNode.attributes().length > 0 && xmlNode.attribute("type") !== undefined) {
+      if (xmlNode.attribute("type").value === "inverted") {
+        articulationEnum = ArticulationEnum.invertedfermata;
+      }
+    }
+    // add to VoiceEntry
+    currentVoiceEntry.Articulations.push(articulationEnum);
+  }
+
+  /**
+   * This method add a technical Articulation to the currentVoiceEntry.
+   * @param xmlNode
+   * @param currentVoiceEntry
+   */
+  public addTechnicalArticulations(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    let node: IXmlElement = xmlNode.element("up-bow");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.upbow) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.upbow);
+      }
+    }
+    node = xmlNode.element("down-bow");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.downbow) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.downbow);
+      }
+    }
+    node = xmlNode.element("open-string");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.naturalharmonic) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.naturalharmonic);
+      }
+    }
+    node = xmlNode.element("stopped");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.lefthandpizzicato) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.lefthandpizzicato);
+      }
+    }
+    node = xmlNode.element("snap-pizzicato");
+    if (node !== undefined) {
+      if (currentVoiceEntry.Articulations.indexOf(ArticulationEnum.snappizzicato) === -1) {
+        currentVoiceEntry.Articulations.push(ArticulationEnum.snappizzicato);
+      }
+    }
+    node = xmlNode.element("fingering");
+    if (node !== undefined) {
+      const currentTechnicalInstruction: TechnicalInstruction = new TechnicalInstruction();
+      currentTechnicalInstruction.type = TechnicalInstructionType.Fingering;
+      currentTechnicalInstruction.value = node.value;
+      currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
+    }
+  }
+
+  /**
+   * This method adds an Ornament to the currentVoiceEntry.
+   * @param ornamentsNode
+   * @param currentVoiceEntry
+   */
+  public addOrnament(ornamentsNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    if (ornamentsNode !== undefined) {
+      let ornament: OrnamentContainer = undefined;
+      let node: IXmlElement = ornamentsNode.element("trill-mark");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Trill);
+      }
+      node = ornamentsNode.element("turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Turn);
+      }
+      node = ornamentsNode.element("inverted-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.InvertedTurn);
+      }
+      node = ornamentsNode.element("delayed-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.DelayedTurn);
+      }
+      node = ornamentsNode.element("delayed-inverted-turn");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.DelayedInvertedTurn);
+      }
+      node = ornamentsNode.element("mordent");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.Mordent);
+      }
+      node = ornamentsNode.element("inverted-mordent");
+      if (node !== undefined) {
+        ornament = new OrnamentContainer(OrnamentEnum.InvertedMordent);
+      }
+      if (ornament !== undefined) {
+        const accidentalsList: IXmlElement[] = ornamentsNode.elements("accidental-mark");
+        if (accidentalsList !== undefined) {
+          let placement: PlacementEnum = PlacementEnum.Below;
+          let accidental: AccidentalEnum = AccidentalEnum.NONE;
+          const accidentalsListArr: IXmlElement[] = accidentalsList;
+          for (let idx: number = 0, len: number = accidentalsListArr.length; idx < len; ++idx) {
+            const accidentalNode: IXmlElement = accidentalsListArr[idx];
+            let text: string = accidentalNode.value;
+            accidental = this.getAccEnumFromString(text);
+            const placementAttr: IXmlAttribute = accidentalNode.attribute("placement");
+            if (accidentalNode.hasAttributes && placementAttr !== undefined) {
+              text = placementAttr.value;
+              if (text === "above") {
+                placement = PlacementEnum.Above;
+              } else if (text === "below") {
+                placement = PlacementEnum.Below;
+              }
+            }
+            if (placement === PlacementEnum.Above) {
+              ornament.AccidentalAbove = accidental;
+            } else if (placement === PlacementEnum.Below) {
+              ornament.AccidentalBelow = accidental;
+            }
+          }
+        }
+        // add this to currentVoiceEntry
+        currentVoiceEntry.OrnamentContainer = ornament;
+      }
+    }
+  }
+}

+ 140 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/LyricsReader.ts

@@ -0,0 +1,140 @@
+import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
+import {VoiceEntry} from "../../VoiceData/VoiceEntry";
+import {IXmlElement} from "../../../Common/FileIO/Xml";
+import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
+import {ITextTranslation} from "../../Interfaces/ITextTranslation";
+import {MusicSheet} from "../../MusicSheet";
+
+export class LyricsReader {
+    private openLyricWords: { [_: number]: LyricWord; } = {};
+    private currentLyricWord: LyricWord;
+    private musicSheet: MusicSheet;
+
+    constructor(musicSheet: MusicSheet) {
+        this.musicSheet = musicSheet;
+    }
+    /**
+     * This method adds a single LyricEntry to a VoiceEntry
+     * @param {IXmlElement[]} lyricNodeList
+     * @param {VoiceEntry} currentVoiceEntry
+     */
+    public addLyricEntry(lyricNodeList: IXmlElement[], currentVoiceEntry: VoiceEntry): void {
+        if (lyricNodeList !== undefined) {
+            const lyricNodeListArr: IXmlElement[] = lyricNodeList;
+            for (let idx: number = 0, len: number = lyricNodeListArr.length; idx < len; ++idx) {
+                const lyricNode: IXmlElement = lyricNodeListArr[idx];
+                try {
+                    let syllabic: string = "single"; // Single as default
+                    if (lyricNode.element("text") !== undefined) {
+                        let textNode: IXmlElement = lyricNode.element("text");
+                        if (lyricNode.element("syllabic") !== undefined) {
+                            syllabic = lyricNode.element("syllabic").value;
+                        }
+                        if (textNode !== undefined) {
+                            const text: string = textNode.value;
+                            // <elision> separates Multiple syllabels on a single LyricNote
+                            // "-" text indicating separated syllabel should be ignored
+                            // we calculate the Dash element much later
+                            if (lyricNode.element("elision") !== undefined && text === "-") {
+                                const lyricNodeChildren: IXmlElement[] = lyricNode.elements();
+                                let elisionIndex: number = 0;
+                                for (let i: number = 0; i < lyricNodeChildren.length; i++) {
+                                    const child: IXmlElement = lyricNodeChildren[i];
+                                    if (child.name === "elision") {
+                                        elisionIndex = i;
+                                        break;
+                                    }
+                                }
+                                let nextText: IXmlElement = undefined;
+                                let nextSyllabic: IXmlElement = undefined;
+                                // read the next nodes
+                                if (elisionIndex > 0) {
+                                    for (let i: number = elisionIndex; i < lyricNodeChildren.length; i++) {
+                                        const child: IXmlElement = lyricNodeChildren[i];
+                                        if (child.name === "text") {
+                                            nextText = child;
+                                        }
+                                        if (child.name === "syllabic") {
+                                            nextSyllabic = child;
+                                        }
+                                    }
+                                }
+                                if (nextText !== undefined && nextSyllabic !== undefined) {
+                                    textNode = nextText;
+                                    syllabic = "middle";
+                                }
+                            }
+                            let currentLyricVerseNumber: number = 1;
+                            if (lyricNode.attributes() !== undefined && lyricNode.attribute("number") !== undefined) {
+                                try {
+                                    currentLyricVerseNumber = parseInt(lyricNode.attribute("number").value, 10);
+                                } catch (err) {
+                                    try {
+                                        const result: string[] = lyricNode.attribute("number").value.toLowerCase().split("verse");
+                                        if (result.length > 1) {
+                                            currentLyricVerseNumber = parseInt(result[1], 10);
+                                        }
+                                    } catch (err) {
+                                        const errorMsg: string =
+                                        ITextTranslation.translateText("ReaderErrorMessages/LyricVerseNumberError", "Invalid lyric verse number");
+                                        this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                                        continue;
+                                    }
+                                }
+                            }
+                            let lyricsEntry: LyricsEntry = undefined;
+                            if (syllabic === "single" || syllabic === "end") {
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) { // word end given or some word still open
+                                    this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                    this.currentLyricWord.Syllables.push(lyricsEntry);
+                                    delete this.openLyricWords[currentLyricVerseNumber];
+                                    this.currentLyricWord = undefined;
+                                } else { // single syllable given or end given while no word has been started
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
+                                }
+                                lyricsEntry.extend = lyricNode.element("extend") !== undefined;
+                            } else if (syllabic === "begin") { // first finishing, if a word already is open (can only happen, when wrongly given)
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) {
+                                    delete this.openLyricWords[currentLyricVerseNumber];
+                                    this.currentLyricWord = undefined;
+                                }
+                                this.currentLyricWord = new LyricWord();
+                                this.openLyricWords[currentLyricVerseNumber] = this.currentLyricWord;
+                                lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                this.currentLyricWord.Syllables.push(lyricsEntry);
+                            } else if (syllabic === "middle") {
+                                if (this.openLyricWords[currentLyricVerseNumber] !== undefined) {
+                                    this.currentLyricWord = this.openLyricWords[currentLyricVerseNumber];
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, this.currentLyricWord, currentVoiceEntry);
+                                    this.currentLyricWord.Syllables.push(lyricsEntry);
+                                } else {
+                                    // in case the wrong syllabel information is given, create a single Entry and add it to currentVoiceEntry
+                                    lyricsEntry = new LyricsEntry(text, currentLyricVerseNumber, undefined, currentVoiceEntry);
+                                }
+                            }
+                            // add each LyricEntry to currentVoiceEntry
+                            if (lyricsEntry !== undefined) {
+                                // only add the lyric entry if not another entry has already been given:
+                                if (!currentVoiceEntry.LyricsEntries[currentLyricVerseNumber] !== undefined) {
+                                    currentVoiceEntry.LyricsEntries.setValue(currentLyricVerseNumber, lyricsEntry);
+                                }
+                                // save in currentInstrument the verseNumber (only once)
+                                if (!currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers[currentLyricVerseNumber] !== undefined) {
+                                    currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.push(currentLyricVerseNumber);
+                                }
+                            }
+                        }
+                    }
+                } catch (err) {
+                    const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/LyricError", "Error while reading lyric entry.");
+                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                    continue;
+                }
+            }
+            // Squash to unique numbers
+            currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers =
+            currentVoiceEntry.ParentVoice.Parent.LyricVersesNumbers.filter((lvn, index, self) => self.indexOf(lvn) === index);
+        }
+    }
+}

+ 99 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionCalculator.ts

@@ -0,0 +1,99 @@
+import {SourceMeasure} from "../../VoiceData/SourceMeasure";
+import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {RepetitionInstructionComparer} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {ArgumentOutOfRangeException} from "../../Exceptions";
+import {MusicSheet} from "../../MusicSheet";
+
+export class RepetitionCalculator {
+  private musicSheet: MusicSheet;
+  private repetitionInstructions: RepetitionInstruction[] = [];
+  private currentMeasure: SourceMeasure;
+  private currentMeasureIndex: number;
+
+  /**
+   * Is called when all repetition symbols have been read from xml.
+   * Creates the repetition instructions and adds them to the corresponding measure.
+   * Creates the logical repetition objects for iteration and playback.
+   * @param musicSheet
+   * @param repetitionInstructions
+   */
+  public calculateRepetitions(musicSheet: MusicSheet, repetitionInstructions: RepetitionInstruction[]): void {
+    this.musicSheet = <MusicSheet>musicSheet;
+    this.repetitionInstructions = repetitionInstructions;
+    const sourceMeasures: SourceMeasure[] = this.musicSheet.SourceMeasures;
+    for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[idx];
+      this.currentMeasureIndex = instruction.measureIndex;
+      this.currentMeasure = sourceMeasures[this.currentMeasureIndex];
+      this.handleRepetitionInstructions(instruction);
+    }
+
+    // if there are more than one instruction at measure begin or end,
+    // sort them according to the nesting of the repetitions:
+    for (let idx: number = 0, len: number = this.musicSheet.SourceMeasures.length; idx < len; ++idx) {
+      const measure: SourceMeasure = this.musicSheet.SourceMeasures[idx];
+      if (measure.FirstRepetitionInstructions.length > 1) {
+        measure.FirstRepetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+      }
+      if (measure.LastRepetitionInstructions.length > 1) {
+        measure.LastRepetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+      }
+    }
+  }
+
+  private handleRepetitionInstructions(currentRepetitionInstruction: RepetitionInstruction): boolean {
+    switch (currentRepetitionInstruction.type) {
+      case RepetitionInstructionEnum.StartLine:
+        this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.BackJumpLine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Ending:
+        // set ending start or end
+        if (currentRepetitionInstruction.alignment === AlignmentType.Begin) {  // ending start
+          this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        } else { // ending end
+          for (let idx: number = 0, len: number = currentRepetitionInstruction.endingIndices.length; idx < len; ++idx) {
+            this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+          }
+        }
+        break;
+      case RepetitionInstructionEnum.Segno:
+        this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Fine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.ToCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.Coda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapo:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegno:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegnoAlFine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapoAlFine:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DalSegnoAlCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.DaCapoAlCoda:
+        this.currentMeasure.LastRepetitionInstructions.push(currentRepetitionInstruction);
+        break;
+      case RepetitionInstructionEnum.None:
+        break;
+      default:
+        throw new ArgumentOutOfRangeException("currentRepetitionInstruction");
+    }
+    return true;
+  }
+}

+ 380 - 0
src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts

@@ -0,0 +1,380 @@
+import {MusicSheet} from "../../MusicSheet";
+import {IXmlElement} from "../../../Common/FileIO/Xml";
+import {SourceMeasure} from "../../VoiceData/SourceMeasure";
+import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {RepetitionInstructionComparer} from "../../VoiceData/Instructions/RepetitionInstruction";
+import {StringUtil} from "../../../Common/Strings/StringUtil";
+export class RepetitionInstructionReader {
+  /**
+   * A global list of all repetition instructions in the musicsheet.
+   */
+  public repetitionInstructions: RepetitionInstruction[];
+  public xmlMeasureList: IXmlElement[][];
+  private musicSheet: MusicSheet;
+  private currentMeasureIndex: number;
+
+  public set MusicSheet(value: MusicSheet) {
+    this.musicSheet = value;
+    this.xmlMeasureList = new Array(this.musicSheet.Instruments.length);
+    this.repetitionInstructions = [];
+  }
+
+  /**
+   * is called when starting reading an xml measure
+   * @param measure
+   * @param currentMeasureIndex
+   */
+  public prepareReadingMeasure(measure: SourceMeasure, currentMeasureIndex: number): void {
+    this.currentMeasureIndex = currentMeasureIndex;
+  }
+
+  public handleLineRepetitionInstructions(barlineNode: IXmlElement, pieceEndingDetected: boolean): void {
+    pieceEndingDetected = false;
+    if (barlineNode.elements().length > 0) {
+      let location: string = "";
+      let hasRepeat: boolean = false;
+      let direction: string = "";
+      let type: string = "";
+      let style: string = "";
+      const endingIndices: number[] = [];
+
+      // read barline style
+      const styleNode: IXmlElement = barlineNode.element("bar-style");
+
+      // if location is ommited in Xml, right is implied (from documentation)
+      if (styleNode !== undefined) {
+        style = styleNode.value;
+      }
+      if (barlineNode.attributes().length > 0 && barlineNode.attribute("location") !== undefined) {
+        location = barlineNode.attribute("location").value;
+      } else {
+        location = "right";
+      }
+      const barlineNodeElements: IXmlElement[] = barlineNode.elements();
+
+      // read repeat- or ending line information
+      for (let idx: number = 0, len: number = barlineNodeElements.length; idx < len; ++idx) {
+        const childNode: IXmlElement = barlineNodeElements[idx];
+        if ("repeat" === childNode.name && childNode.hasAttributes) {
+          hasRepeat = true;
+          direction = childNode.attribute("direction").value;
+        } else if ( "ending" === childNode.name && childNode.hasAttributes &&
+                    childNode.attribute("type") !== undefined && childNode.attribute("number") !== undefined) {
+          type = childNode.attribute("type").value;
+          const num: string = childNode.attribute("number").value;
+
+          // Parse the given ending indices:
+          // handle cases like: "1, 2" or "1 + 2" or even "1 - 3, 6"
+          const separatedEndingIndices: string[] = num.split("[,+]");
+          for (let idx2: number = 0, len2: number = separatedEndingIndices.length; idx2 < len2; ++idx2) {
+            const separatedEndingIndex: string = separatedEndingIndices[idx2];
+            const indices: string[] = separatedEndingIndex.match("[0-9]");
+
+            // check if possibly something like "1-3" is given..
+            if (separatedEndingIndex.search("-") !== -1 && indices.length === 2) {
+              const startIndex: number = parseInt(indices[0], 10);
+              const endIndex: number = parseInt(indices[1], 10);
+              for (let index: number = startIndex; index <= endIndex; index++) {
+                endingIndices.push(index);
+              }
+            } else {
+              for (let idx3: number = 0, len3: number = indices.length; idx3 < len3; ++idx3) {
+                const index: string = indices[idx3];
+                endingIndices.push(parseInt(index, 10));
+              }
+            }
+          }
+        }
+      }
+
+      // reset measure counter if not lastMeasure
+      if (style === "light-heavy" && endingIndices.length === 0 && !hasRepeat) {
+        pieceEndingDetected = true;
+      }
+      if (hasRepeat || endingIndices.length > 0) {
+        if (location === "left") {
+          if (type === "start") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
+                                                                                    AlignmentType.Begin, undefined, endingIndices);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+          if (direction === "forward") {
+            // start new Repetition
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.StartLine);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+        } else { // location right
+          if (type === "stop" || type === "discontinue") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending,
+                                                                                    AlignmentType.End, undefined, endingIndices);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+          if (direction === "backward") {
+            const newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.BackJumpLine);
+            this.addInstruction(this.repetitionInstructions, newInstruction);
+          }
+        }
+      }
+    }
+  }
+
+  public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean {
+    const wordsNode: IXmlElement = directionTypeNode.element("words");
+    if (wordsNode !== undefined) {
+      // must Trim string and ToLower before compare
+      const innerText: string = wordsNode.value.trim().toLowerCase();
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al fine") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlFine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al fine") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlFine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. c.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dacapo") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "da capo")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapo);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "d. s.") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dalsegno") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dal segno")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegno);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "tocoda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "to coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "a coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "a la coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.ToCoda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "fine")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition < 0.5) {
+          measureIndex--;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Fine);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "coda")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition > 0.5) {
+          measureIndex++;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+      if (StringUtil.StringContainsSeparatedWord(innerText, "segno")) {
+        let measureIndex: number = this.currentMeasureIndex;
+        if (relativeMeasurePosition > 0.5) {
+          measureIndex++;
+        }
+        const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
+        this.addInstruction(this.repetitionInstructions, newInstruction);
+        return true;
+      }
+    } else if (directionTypeNode.element("segno") !== undefined) {
+      let measureIndex: number = this.currentMeasureIndex;
+      if (relativeMeasurePosition > 0.5) {
+        measureIndex++;
+      }
+      const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno);
+      this.addInstruction(this.repetitionInstructions, newInstruction);
+      return true;
+    } else if (directionTypeNode.element("coda") !== undefined) {
+      let measureIndex: number = this.currentMeasureIndex;
+      if (relativeMeasurePosition > 0.5) {
+        measureIndex++;
+      }
+      const newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda);
+      this.addInstruction(this.repetitionInstructions, newInstruction);
+      return true;
+    }
+    return false;
+  }
+
+  public removeRedundantInstructions(): void {
+    let segnoCount: number = 0;
+    let codaCount: number = 0;
+    //const fineCount: number = 0;
+    let toCodaCount: number = 0;
+    let dalSegnaCount: number = 0;
+    for (let index: number = 0; index < this.repetitionInstructions.length; index++) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      switch (instruction.type) {
+        case RepetitionInstructionEnum.Coda:
+          if (toCodaCount > 0) {
+            if (this.findInstructionInPreviousMeasure(index, instruction.measureIndex, RepetitionInstructionEnum.ToCoda)) {
+              instruction.type = RepetitionInstructionEnum.None;
+            }
+          }
+          if (codaCount === 0 && toCodaCount === 0) {
+            instruction.type = RepetitionInstructionEnum.ToCoda;
+            instruction.alignment = AlignmentType.End;
+            instruction.measureIndex--;
+          }
+          break;
+        case RepetitionInstructionEnum.Segno:
+          if (segnoCount - dalSegnaCount > 0) { // two segnos in a row
+            let foundInstruction: boolean = false;
+            for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) {
+              const instr: RepetitionInstruction = this.repetitionInstructions[idx];
+              if (instruction.measureIndex - instr.measureIndex === 1) {
+                switch (instr.type) {
+                  case RepetitionInstructionEnum.BackJumpLine:
+                    if (toCodaCount - codaCount > 0) { // open toCoda existing
+                      instr.type = RepetitionInstructionEnum.DalSegnoAlCoda;
+                    } else {
+                      instr.type = RepetitionInstructionEnum.DalSegno;
+                    }
+                    instruction.type = RepetitionInstructionEnum.None;
+                    foundInstruction = true;
+                    break;
+                  case RepetitionInstructionEnum.DalSegno:
+                  case RepetitionInstructionEnum.DalSegnoAlFine:
+                  case RepetitionInstructionEnum.DalSegnoAlCoda:
+                    instruction.type = RepetitionInstructionEnum.None;
+                    foundInstruction = true;
+                    break;
+                  default:
+                    break;
+                }
+              }
+              if (foundInstruction) {
+                break;
+              }
+            }
+            if (foundInstruction) {
+              break;
+            }
+            // convert to dal segno instruction:
+            if (toCodaCount - codaCount > 0) { // open toCoda existing
+              instruction.type = RepetitionInstructionEnum.DalSegnoAlCoda;
+            } else {
+              instruction.type = RepetitionInstructionEnum.DalSegno;
+            }
+            instruction.alignment = AlignmentType.End;
+            instruction.measureIndex--;
+          }
+          break;
+        default:
+          break;
+      }
+
+      // check if this  instruction already exists or is otherwise redundant:
+      if (this.backwardSearchForPreviousIdenticalInstruction(index, instruction) || instruction.type === RepetitionInstructionEnum.None) {
+        this.repetitionInstructions.splice(index, 1);
+        index--;
+      } else {
+        switch (instruction.type) {
+          case RepetitionInstructionEnum.Fine:
+            //fineCount++;
+            break;
+          case RepetitionInstructionEnum.ToCoda:
+            toCodaCount++;
+            break;
+          case RepetitionInstructionEnum.Coda:
+            codaCount++;
+            break;
+          case RepetitionInstructionEnum.Segno:
+            segnoCount++;
+            break;
+          case RepetitionInstructionEnum.DalSegnoAlFine:
+          case RepetitionInstructionEnum.DalSegnoAlCoda:
+            dalSegnaCount++;
+            break;
+          default:
+            break;
+        }
+      }
+    }
+    this.repetitionInstructions.sort(RepetitionInstructionComparer.Compare);
+  }
+
+  private findInstructionInPreviousMeasure(currentInstructionIndex: number, currentMeasureIndex: number, searchedType: RepetitionInstructionEnum): boolean {
+    for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      if (currentMeasureIndex - instruction.measureIndex === 1 && instruction.type === searchedType) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private backwardSearchForPreviousIdenticalInstruction(currentInstructionIndex: number, currentInstruction: RepetitionInstruction): boolean {
+    for (let index: number = currentInstructionIndex - 1; index >= 0; index--) {
+      const instruction: RepetitionInstruction = this.repetitionInstructions[index];
+      if (instruction.equals(currentInstruction)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private addInstruction(currentRepetitionInstructions: RepetitionInstruction[], newInstruction: RepetitionInstruction): void {
+    let addInstruction: boolean = true;
+    for (let idx: number = 0, len: number = currentRepetitionInstructions.length; idx < len; ++idx) {
+      const repetitionInstruction: RepetitionInstruction = currentRepetitionInstructions[idx];
+      if (newInstruction.equals(repetitionInstruction)) {
+        addInstruction = false;
+        break;
+      }
+    }
+    if (addInstruction) {
+      currentRepetitionInstructions.push(newInstruction);
+    }
+  }
+}

+ 829 - 918
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -1,32 +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 {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 { 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
@@ -34,947 +31,861 @@ import Dictionary from "typescript-collections/dist/lib/Dictionary";
 export type SlurReader = any;
 
 export class VoiceGenerator {
-    constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
-        this.musicSheet = instrument.GetMusicSheet;
-        // this.slurReader = slurReader;
-        if (mainVoice !== undefined) {
-            this.voice = new LinkedVoice(instrument, voiceId, mainVoice);
-        } else {
-            this.voice = new Voice(instrument, voiceId);
+  constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
+    this.musicSheet = instrument.GetMusicSheet;
+    // this.slurReader = slurReader;
+    if (mainVoice !== undefined) {
+      this.voice = new LinkedVoice(instrument, voiceId, mainVoice);
+    } else {
+      this.voice = new Voice(instrument, voiceId);
+    }
+    instrument.Voices.push(this.voice);
+    this.lyricsReader = new LyricsReader(this.musicSheet);
+    this.articulationReader = new ArticulationReader();
+  }
+
+  // private slurReader: SlurReader;
+  private lyricsReader: LyricsReader;
+  private articulationReader: ArticulationReader;
+  private musicSheet: MusicSheet;
+  private voice: Voice;
+  private currentVoiceEntry: VoiceEntry;
+  private currentNote: Note;
+  private currentMeasure: SourceMeasure;
+  private currentStaffEntry: SourceStaffEntry;
+  private lastBeamTag: string = "";
+  private openBeam: Beam;
+  private openGraceBeam: Beam;
+  private openTieDict: { [_: number]: Tie; } = {};
+  private currentOctaveShift: number = 0;
+  private tupletDict: { [_: number]: Tuplet; } = {};
+  private openTupletNumber: number = 0;
+
+  public get GetVoice(): Voice {
+    return this.voice;
+  }
+
+  public get OctaveShift(): number {
+    return this.currentOctaveShift;
+  }
+
+  public set OctaveShift(value: number) {
+    this.currentOctaveShift = value;
+  }
+
+  /**
+   * Create new [[VoiceEntry]], add it to given [[SourceStaffEntry]] and if given so, to [[Voice]].
+   * @param musicTimestamp
+   * @param parentStaffEntry
+   * @param addToVoice
+   */
+  public createVoiceEntry(musicTimestamp: Fraction, parentStaffEntry: SourceStaffEntry, addToVoice: boolean): void {
+    this.currentVoiceEntry = new VoiceEntry(musicTimestamp.clone(), this.voice, parentStaffEntry);
+    if (addToVoice) {
+      this.voice.VoiceEntries.push(this.currentVoiceEntry);
+    }
+    if (parentStaffEntry.VoiceEntries.indexOf(this.currentVoiceEntry) === -1) {
+      parentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
+    }
+  }
+
+  /**
+   * Create [[Note]]s and handle Lyrics, Articulations, Beams, Ties, Slurs, Tuplets.
+   * @param noteNode
+   * @param noteDuration
+   * @param divisions
+   * @param restNote
+   * @param graceNote
+   * @param parentStaffEntry
+   * @param parentMeasure
+   * @param measureStartAbsoluteTimestamp
+   * @param maxTieNoteFraction
+   * @param chord
+   * @param guitarPro
+   * @returns {Note}
+   */
+  public read(noteNode: IXmlElement, noteDuration: Fraction, restNote: boolean, graceNote: boolean,
+              parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
+              measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean): Note {
+    this.currentStaffEntry = parentStaffEntry;
+    this.currentMeasure = parentMeasure;
+    //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;
+      }
+      let hasTupletCommand: boolean = false;
+      const notationNode: IXmlElement = noteNode.element("notations");
+      if (notationNode !== undefined) {
+        // let articNode: IXmlElement = undefined;
+        // (*)
+        if (this.articulationReader !== undefined) {
+          this.readArticulations(notationNode, this.currentVoiceEntry);
         }
-        instrument.Voices.push(this.voice);
-        //this.lyricsReader = MusicSymbolModuleFactory.createLyricsReader(this.musicSheet);
-        //this.articulationReader = MusicSymbolModuleFactory.createArticulationReader();
-    }
-
-    // private slurReader: SlurReader;
-    //private lyricsReader: LyricsReader;
-    //private articulationReader: ArticulationReader;
-    private musicSheet: MusicSheet;
-    private voice: Voice;
-    private currentVoiceEntry: VoiceEntry;
-    private currentNote: Note;
-    private currentMeasure: SourceMeasure;
-    private currentStaffEntry: SourceStaffEntry;
-    private lastBeamTag: string = "";
-    private openBeam: Beam;
-    private openGraceBeam: Beam;
-    private openTieDict: { [_: number]: Tie; } = {};
-    private currentOctaveShift: number = 0;
-    private tupletDict: { [_: number]: Tuplet; } = {};
-    private openTupletNumber: number = 0;
-
-    public get GetVoice(): Voice {
-        return this.voice;
-    }
-    public get OctaveShift(): number {
-        return this.currentOctaveShift;
-    }
-    public set OctaveShift(value: number) {
-        this.currentOctaveShift = value;
-    }
-
-    /**
-     * Create new [[VoiceEntry]], add it to given [[SourceStaffEntry]] and if given so, to [[Voice]].
-     * @param musicTimestamp
-     * @param parentStaffEntry
-     * @param addToVoice
-     */
-    public createVoiceEntry(musicTimestamp: Fraction, parentStaffEntry: SourceStaffEntry, addToVoice: boolean): void {
-        this.currentVoiceEntry = new VoiceEntry(musicTimestamp.clone(), this.voice, parentStaffEntry);
-        if (addToVoice) {
-            this.voice.VoiceEntries.push(this.currentVoiceEntry);
+        //let slurNodes: IXmlElement[] = undefined;
+        // (*)
+        //if (this.slurReader !== undefined && (slurNodes = notationNode.elements("slur")))
+        //    this.slurReader.addSlur(slurNodes, this.currentNote);
+        // check for Tuplets
+        const tupletNodeList: IXmlElement[] = notationNode.elements("tuplet");
+        if (tupletNodeList.length > 0) {
+          this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
+          hasTupletCommand = true;
         }
-        if (parentStaffEntry.VoiceEntries.indexOf(this.currentVoiceEntry) === -1) {
-            parentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
+        // check for Arpeggios
+        if (notationNode.element("arpeggiate") !== undefined && !graceNote) {
+          this.currentVoiceEntry.ArpeggiosNotesIndices.push(this.currentVoiceEntry.Notes.indexOf(this.currentNote));
         }
-    }
-
-    /**
-     * Create [[Note]]s and handle Lyrics, Articulations, Beams, Ties, Slurs, Tuplets.
-     * @param noteNode
-     * @param noteDuration
-     * @param divisions
-     * @param restNote
-     * @param graceNote
-     * @param parentStaffEntry
-     * @param parentMeasure
-     * @param measureStartAbsoluteTimestamp
-     * @param maxTieNoteFraction
-     * @param chord
-     * @param guitarPro
-     * @returns {Note}
-     */
-    public read(
-        noteNode: IXmlElement, noteDuration: Fraction, restNote: boolean, graceNote: boolean,
-        parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
-        measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean
-    ): Note {
-        this.currentStaffEntry = parentStaffEntry;
-        this.currentMeasure = parentMeasure;
-        //Logging.debug("read called:", restNote);
-        try {
-            this.currentNote = restNote
-                ? this.addRestNote(noteDuration)
-                : this.addSingleNote(noteNode, noteDuration, graceNote, chord, guitarPro);
-            // (*)
-            //if (this.lyricsReader !== undefined && noteNode.element("lyric") !== undefined) {
-            //    this.lyricsReader.addLyricEntry(noteNode, this.currentVoiceEntry);
-            //    this.voice.Parent.HasLyrics = true;
-            //}
-            let hasTupletCommand: boolean = false;
-            const notationNode: IXmlElement = noteNode.element("notations");
-            if (notationNode !== undefined) {
-                // let articNode: IXmlElement = undefined;
-                // (*)
-                //if (this.articulationReader !== undefined) {
-                //    this.readArticulations(notationNode, this.currentVoiceEntry);
-                //}
-                //let slurNodes: IXmlElement[] = undefined;
-                // (*)
-                //if (this.slurReader !== undefined && (slurNodes = notationNode.elements("slur")))
-                //    this.slurReader.addSlur(slurNodes, this.currentNote);
-                // check for Tuplets
-                const tupletNodeList: IXmlElement[] = notationNode.elements("tuplet");
-                if (tupletNodeList.length > 0) {
-                  this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
-                  hasTupletCommand = true;
-                }
-                // check for Arpeggios
-                if (notationNode.element("arpeggiate") !== undefined && !graceNote) {
-                    this.currentVoiceEntry.ArpeggiosNotesIndices.push(this.currentVoiceEntry.Notes.indexOf(this.currentNote));
-                }
-                // check for Ties - must be the last check
-                const tiedNodeList: IXmlElement[] = notationNode.elements("tied");
-                if (tiedNodeList.length > 0) {
-                    this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
-                }
-
-                // remove open ties, if there is already a gap between the last tie note and now.
-                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)) {
-                            delete openTieDict[key];
-                        }
-                    }
-                }
-            }
-            // time-modification yields tuplet in currentNote
-            // mustn't execute method, if this is the Note where the Tuplet has been created
-            if (noteNode.element("time-modification") !== undefined && !hasTupletCommand) {
-                this.handleTimeModificationNode(noteNode);
-            }
-        } catch (err) {
-            const errorMsg: string = ITextTranslation.translateText(
-                "ReaderErrorMessages/NoteError", "Ignored erroneous Note."
-            );
-            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+        // check for Ties - must be the last check
+        const tiedNodeList: IXmlElement[] = notationNode.elements("tied");
+        if (tiedNodeList.length > 0) {
+          this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
         }
 
-        return this.currentNote;
-    }
-
-    /**
-     * Handle the GraceNotes that appear before the Measure's End
-     * and aren't assigned to any normal (with [[VoiceEntries]]) [[SourceStaffEntry]]s yet.
-     */
-    public checkForOpenGraceNotes(): void {
-        if (
-            this.currentStaffEntry !== undefined
-            && this.currentStaffEntry.VoiceEntries.length === 0
-            && 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) {
-                let previousVoiceEntry: VoiceEntry = undefined;
-                for (let idx: number = 0, len: number = previousStaffEntry.VoiceEntries.length; idx < len; ++idx) {
-                    const voiceEntry: VoiceEntry = previousStaffEntry.VoiceEntries[idx];
-                    if (voiceEntry.ParentVoice === voice) {
-                        previousVoiceEntry = voiceEntry;
-                        previousVoiceEntry.graceVoiceEntriesAfter = [];
-                        for (let idx2: number = 0, len2: number = this.currentVoiceEntry.graceVoiceEntriesBefore.length; idx2 < len2; ++idx2) {
-                            const graceVoiceEntry: VoiceEntry = this.currentVoiceEntry.graceVoiceEntriesBefore[idx2];
-                            previousVoiceEntry.graceVoiceEntriesAfter.push(graceVoiceEntry);
-                        }
-                        this.currentVoiceEntry.graceVoiceEntriesBefore = [];
-                        this.currentStaffEntry = undefined;
-                        break;
-                    }
-                }
+        // remove open ties, if there is already a gap between the last tie note and now.
+        const openTieDict: { [_: number]: Tie; } = this.openTieDict;
+        for (const key in openTieDict) {
+          if (openTieDict.hasOwnProperty(key)) {
+            const tie: Tie = openTieDict[key];
+            if (Fraction.plus(tie.StartNote.ParentStaffEntry.Timestamp, tie.Duration).lt(this.currentStaffEntry.Timestamp)) {
+              delete openTieDict[key];
             }
+          }
         }
+      }
+      // time-modification yields tuplet in currentNote
+      // mustn't execute method, if this is the Note where the Tuplet has been created
+      if (noteNode.element("time-modification") !== undefined && !hasTupletCommand) {
+        this.handleTimeModificationNode(noteNode);
+      }
+    } catch (err) {
+      const errorMsg: string = ITextTranslation.translateText(
+        "ReaderErrorMessages/NoteError", "Ignored erroneous Note."
+      );
+      this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
     }
 
-    /**
-     * Create a new [[StaffEntryLink]] and sets the currenstStaffEntry accordingly.
-     * @param index
-     * @param currentStaff
-     * @param currentStaffEntry
-     * @param currentMeasure
-     * @returns {SourceStaffEntry}
-     */
-    public checkForStaffEntryLink(
-        index: number, currentStaff: Staff, currentStaffEntry: SourceStaffEntry, currentMeasure: SourceMeasure
-    ): SourceStaffEntry {
-        const staffEntryLink: StaffEntryLink = new StaffEntryLink(this.currentVoiceEntry);
-        staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
-        currentStaffEntry.Link = staffEntryLink;
-        const linkMusicTimestamp: Fraction = this.currentVoiceEntry.Timestamp.clone();
-        const verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = currentMeasure.getVerticalContainerByTimestamp(linkMusicTimestamp);
-        currentStaffEntry = verticalSourceStaffEntryContainer.StaffEntries[index];
-        if (currentStaffEntry === undefined) {
-            currentStaffEntry = new SourceStaffEntry(verticalSourceStaffEntryContainer, currentStaff);
-            verticalSourceStaffEntryContainer.StaffEntries[index] = currentStaffEntry;
-        }
-        currentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
-        staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
-        currentStaffEntry.Link = staffEntryLink;
-        return currentStaffEntry;
-    }
-    public checkForOpenBeam(): void {
-        if (this.openBeam !== undefined && this.currentNote !== undefined) {
-            this.handleOpenBeam();
-        }
-    }
-    public checkOpenTies(): void {
-        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)) {
-                    delete openTieDict[key];
-                }
+    return this.currentNote;
+  }
+
+  /**
+   * Handle the GraceNotes that appear before the Measure's End
+   * and aren't assigned to any normal (with [[VoiceEntries]]) [[SourceStaffEntry]]s yet.
+   */
+  public checkForOpenGraceNotes(): void {
+    if (
+      this.currentStaffEntry !== undefined
+      && this.currentStaffEntry.VoiceEntries.length === 0
+      && 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) {
+        let previousVoiceEntry: VoiceEntry = undefined;
+        for (let idx: number = 0, len: number = previousStaffEntry.VoiceEntries.length; idx < len; ++idx) {
+          const voiceEntry: VoiceEntry = previousStaffEntry.VoiceEntries[idx];
+          if (voiceEntry.ParentVoice === voice) {
+            previousVoiceEntry = voiceEntry;
+            previousVoiceEntry.graceVoiceEntriesAfter = [];
+            for (let idx2: number = 0, len2: number = this.currentVoiceEntry.graceVoiceEntriesBefore.length; idx2 < len2; ++idx2) {
+              const graceVoiceEntry: VoiceEntry = this.currentVoiceEntry.graceVoiceEntriesBefore[idx2];
+              previousVoiceEntry.graceVoiceEntriesAfter.push(graceVoiceEntry);
             }
+            this.currentVoiceEntry.graceVoiceEntriesBefore = [];
+            this.currentStaffEntry = undefined;
+            break;
+          }
         }
+      }
     }
-    public hasVoiceEntry(): boolean {
-        return this.currentVoiceEntry !== undefined;
-    }
-
-    /**
-     *
-     * @param type
-     * @returns {Fraction} - a Note's Duration from a given type (type must be valid).
-     */
-    public getNoteDurationFromType(type: string): Fraction {
-        switch (type) {
-            case "1024th":
-                return new Fraction(1, 1024);
-            case "512th":
-                return new Fraction(1, 512);
-            case "256th":
-                return new Fraction(1, 256);
-            case "128th":
-                return new Fraction(1, 128);
-            case "64th":
-                return new Fraction(1, 64);
-            case "32th":
-            case "32nd":
-                return new Fraction(1, 32);
-            case "16th":
-                return new Fraction(1, 16);
-            case "eighth":
-                return new Fraction(1, 8);
-            case "quarter":
-                return new Fraction(1, 4);
-            case "half":
-                return new Fraction(1, 2);
-            case "whole":
-                return new Fraction(1, 1);
-            case "breve":
-                return new Fraction(2, 1);
-            case "long":
-                return new Fraction(4, 1);
-            case "maxima":
-                return new Fraction(8, 1);
-            default: {
-                const errorMsg: string = ITextTranslation.translateText(
-                    "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
-                );
-                throw new MusicSheetReadingException(errorMsg);
-            }
-        }
+  }
+
+  /**
+   * Create a new [[StaffEntryLink]] and sets the currenstStaffEntry accordingly.
+   * @param index
+   * @param currentStaff
+   * @param currentStaffEntry
+   * @param currentMeasure
+   * @returns {SourceStaffEntry}
+   */
+  public checkForStaffEntryLink(index: number, currentStaff: Staff, currentStaffEntry: SourceStaffEntry, currentMeasure: SourceMeasure): SourceStaffEntry {
+    const staffEntryLink: StaffEntryLink = new StaffEntryLink(this.currentVoiceEntry);
+    staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
+    currentStaffEntry.Link = staffEntryLink;
+    const linkMusicTimestamp: Fraction = this.currentVoiceEntry.Timestamp.clone();
+    const verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = currentMeasure.getVerticalContainerByTimestamp(linkMusicTimestamp);
+    currentStaffEntry = verticalSourceStaffEntryContainer.StaffEntries[index];
+    if (currentStaffEntry === undefined) {
+      currentStaffEntry = new SourceStaffEntry(verticalSourceStaffEntryContainer, currentStaff);
+      verticalSourceStaffEntryContainer.StaffEntries[index] = currentStaffEntry;
     }
-    // (*)
-    //private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
-    //    let articNode: IXmlElement;
-    //    if ((articNode = notationNode.element("articulations")) !== undefined)
-    //        this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
-    //    let fermaNode: IXmlElement = undefined;
-    //    if ((fermaNode = notationNode.element("fermata")) !== undefined)
-    //        this.articulationReader.addFermata(fermaNode, currentVoiceEntry);
-    //    let tecNode: IXmlElement = undefined;
-    //    if ((tecNode = notationNode.element("technical")) !== undefined)
-    //        this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry);
-    //    let ornaNode: IXmlElement = undefined;
-    //    if ((ornaNode = notationNode.element("ornaments")) !== undefined)
-    //        this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
-    //}
-
-    /**
-     * Create a new [[Note]] and adds it to the currentVoiceEntry
-     * @param node
-     * @param noteDuration
-     * @param divisions
-     * @param graceNote
-     * @param chord
-     * @param guitarPro
-     * @returns {Note}
-     */
-    private addSingleNote(
-        node: IXmlElement, noteDuration: Fraction, graceNote: boolean, chord: boolean, guitarPro: boolean
-    ): Note {
-        //Logging.debug("addSingleNote called");
-        let noteAlter: AccidentalEnum = AccidentalEnum.NONE;
-        let noteStep: NoteEnum = NoteEnum.C;
-        let noteOctave: number = 0;
-        let playbackInstrumentId: string = undefined;
-        const xmlnodeElementsArr: IXmlElement[] = node.elements();
-        for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
-            const noteElement: IXmlElement = xmlnodeElementsArr[idx];
-            try {
-                if (noteElement.name === "pitch") {
-                    const noteElementsArr: IXmlElement[] = noteElement.elements();
-                    for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
-                        const pitchElement: IXmlElement = noteElementsArr[idx2];
-                        try {
-                            if (pitchElement.name === "step") {
-                                noteStep = NoteEnum[pitchElement.value];
-                                if (noteStep === undefined) {
-                                    const errorMsg: string = ITextTranslation.translateText(
-                                        "ReaderErrorMessages/NotePitchError",
-                                        "Invalid pitch while reading note."
-                                    );
-                                    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(
-                                        "ReaderErrorMessages/NoteAlterationError", "Invalid alteration while reading note."
-                                    );
-                                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                                    throw new MusicSheetReadingException(errorMsg, undefined);
-                                }
-
-                            } else if (pitchElement.name === "octave") {
-                                noteOctave = parseInt(pitchElement.value, 10);
-                                if (isNaN(noteOctave)) {
-                                    const errorMsg: string = ITextTranslation.translateText(
-                                        "ReaderErrorMessages/NoteOctaveError", "Invalid octave value while reading note."
-                                    );
-                                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                                    throw new MusicSheetReadingException(errorMsg, undefined);
-                                }
-                            }
-                        } catch (ex) {
-                            Logging.log("VoiceGenerator.addSingleNote read Step: ", ex.message);
-                        }
-
-                    }
-                } else if (noteElement.name === "unpitched") {
-                    const displayStep: IXmlElement = noteElement.element("display-step");
-                    if (displayStep !== undefined) {
-                        noteStep = NoteEnum[displayStep.value.toUpperCase()];
-                    }
-                    const octave: IXmlElement = noteElement.element("display-octave");
-                    if (octave !== undefined) {
-                        noteOctave = parseInt(octave.value, 10);
-                        if (guitarPro) {
-                            noteOctave += 1;
-                        }
-                    }
-                } else if (noteElement.name === "instrument") {
-                    if (noteElement.firstAttribute !== undefined) {
-                        playbackInstrumentId = noteElement.firstAttribute.value;
-                    }
-                }
-            } catch (ex) {
-                Logging.log("VoiceGenerator.addSingleNote: ", ex);
-            }
-        }
-
-        noteOctave -= Pitch.OctaveXmlDifference;
-        const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAlter);
-        const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
-        const note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
-        note.PlaybackInstrumentId = playbackInstrumentId;
-        if (!graceNote) {
-            this.currentVoiceEntry.Notes.push(note);
-        } else {
-            this.handleGraceNote(node, note);
-        }
-        if (node.elements("beam") && !chord) {
-            this.createBeam(node, note, graceNote);
-        }
-        return note;
-    }
-
-    /**
-     * Create a new rest note and add it to the currentVoiceEntry.
-     * @param noteDuration
-     * @param divisions
-     * @returns {Note}
-     */
-    private addRestNote(noteDuration: Fraction): Note {
-        const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
-        const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, undefined);
-        this.currentVoiceEntry.Notes.push(restNote);
-        if (this.openBeam !== undefined) {
-            this.openBeam.ExtendedNoteList.push(restNote);
+    currentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
+    staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
+    currentStaffEntry.Link = staffEntryLink;
+    return currentStaffEntry;
+  }
+
+  public checkForOpenBeam(): void {
+    if (this.openBeam !== undefined && this.currentNote !== undefined) {
+      this.handleOpenBeam();
+    }
+  }
+
+  public checkOpenTies(): void {
+    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.StartNote.ParentStaffEntry.Timestamp, tie.Duration)
+          .lt(tie.StartNote.ParentStaffEntry.VerticalContainerParent.ParentMeasure.Duration)) {
+          delete openTieDict[key];
         }
-        return restNote;
+      }
     }
+  }
+
+  public hasVoiceEntry(): boolean {
+    return this.currentVoiceEntry !== undefined;
+  }
+
+  /**
+   *
+   * @param type
+   * @returns {Fraction} - a Note's Duration from a given type (type must be valid).
+   */
+  public getNoteDurationFromType(type: string): Fraction {
+    switch (type) {
+      case "1024th":
+        return new Fraction(1, 1024);
+      case "512th":
+        return new Fraction(1, 512);
+      case "256th":
+        return new Fraction(1, 256);
+      case "128th":
+        return new Fraction(1, 128);
+      case "64th":
+        return new Fraction(1, 64);
+      case "32th":
+      case "32nd":
+        return new Fraction(1, 32);
+      case "16th":
+        return new Fraction(1, 16);
+      case "eighth":
+        return new Fraction(1, 8);
+      case "quarter":
+        return new Fraction(1, 4);
+      case "half":
+        return new Fraction(1, 2);
+      case "whole":
+        return new Fraction(1, 1);
+      case "breve":
+        return new Fraction(2, 1);
+      case "long":
+        return new Fraction(4, 1);
+      case "maxima":
+        return new Fraction(8, 1);
+      default: {
+        const errorMsg: string = ITextTranslation.translateText(
+          "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
+        );
+        throw new MusicSheetReadingException(errorMsg);
+      }
+    }
+  }
 
-    /**
-     * Handle the currentVoiceBeam.
-     * @param node
-     * @param note
-     * @param grace
-     */
-    private createBeam(node: IXmlElement, note: Note, grace: boolean): void {
-        try {
-            const beamNode: IXmlElement = node.element("beam");
-            let beamAttr: IXmlAttribute = undefined;
-            if (beamNode !== undefined && beamNode.hasAttributes) {
-                beamAttr = beamNode.attribute("number");
-            }
-            if (beamAttr !== undefined) {
-                const beamNumber: number = parseInt(beamAttr.value, 10);
-                const mainBeamNode: IXmlElement[] = node.elements("beam");
-                const currentBeamTag: string = mainBeamNode[0].value;
-                if (beamNumber === 1 && mainBeamNode !== undefined) {
-                    if (currentBeamTag === "begin" && this.lastBeamTag !== currentBeamTag) {
-                        if (grace) {
-                            if (this.openGraceBeam !== undefined) {
-                                this.handleOpenBeam();
-                            }
-                            this.openGraceBeam = new Beam();
-                        } else {
-                            if (this.openBeam !== undefined) {
-                                this.handleOpenBeam();
-                            }
-                            this.openBeam = new Beam();
-                        }
-                    }
-                    this.lastBeamTag = currentBeamTag;
+  private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+    const articNode: IXmlElement = notationNode.element("articulations");
+    if (articNode !== undefined) {
+      this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
+    }
+    const fermaNode: IXmlElement = notationNode.element("fermata");
+    if (fermaNode !== undefined) {
+      this.articulationReader.addFermata(fermaNode, currentVoiceEntry);
+    }
+    const tecNode: IXmlElement = notationNode.element("technical");
+    if (tecNode !== undefined) {
+      this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry);
+    }
+    const ornaNode: IXmlElement = notationNode.element("ornaments");
+    if (ornaNode !== undefined) {
+      this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
+    }
+  }
+
+  /**
+   * Create a new [[Note]] and adds it to the currentVoiceEntry
+   * @param node
+   * @param noteDuration
+   * @param divisions
+   * @param graceNote
+   * @param chord
+   * @param guitarPro
+   * @returns {Note}
+   */
+  private addSingleNote(node: IXmlElement, noteDuration: Fraction, graceNote: boolean, chord: boolean, guitarPro: boolean): Note {
+    //log.debug("addSingleNote called");
+    let noteAlter: AccidentalEnum = AccidentalEnum.NONE;
+    let noteStep: NoteEnum = NoteEnum.C;
+    let noteOctave: number = 0;
+    let playbackInstrumentId: string = undefined;
+    const xmlnodeElementsArr: IXmlElement[] = node.elements();
+    for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
+      const noteElement: IXmlElement = xmlnodeElementsArr[idx];
+      try {
+        if (noteElement.name === "pitch") {
+          const noteElementsArr: IXmlElement[] = noteElement.elements();
+          for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
+            const pitchElement: IXmlElement = noteElementsArr[idx2];
+            try {
+              if (pitchElement.name === "step") {
+                noteStep = NoteEnum[pitchElement.value];
+                if (noteStep === undefined) {
+                  const errorMsg: string = ITextTranslation.translateText(
+                    "ReaderErrorMessages/NotePitchError",
+                    "Invalid pitch while reading note."
+                  );
+                  this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                  throw new MusicSheetReadingException(errorMsg, undefined);
                 }
-                let sameVoiceEntry: boolean = false;
-                if (grace) {
-                    if (this.openGraceBeam === undefined) { return; }
-                    for (let idx: number = 0, len: number = this.openGraceBeam.Notes.length; idx < len; ++idx) {
-                        const beamNote: Note = this.openGraceBeam.Notes[idx];
-                        if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
-                            sameVoiceEntry = true;
-                        }
-                    }
-                    if (!sameVoiceEntry) {
-                        this.openGraceBeam.addNoteToBeam(note);
-                        if (currentBeamTag === "end" && beamNumber === 1) {
-                            this.openGraceBeam = undefined;
-                        }
-                    }
-                } else {
-                    if (this.openBeam === undefined) { return; }
-                    for (let idx: number = 0, len: number = this.openBeam.Notes.length; idx < len; ++idx) {
-                        const beamNote: Note = this.openBeam.Notes[idx];
-                        if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
-                            sameVoiceEntry = true;
-                        }
-                    }
-                    if (!sameVoiceEntry) {
-                        this.openBeam.addNoteToBeam(note);
-                        if (currentBeamTag === "end" && beamNumber === 1) {
-                            this.openBeam = undefined;
-                        }
-                    }
+              } else if (pitchElement.name === "alter") {
+                noteAlter = parseInt(pitchElement.value, 10);
+                if (isNaN(noteAlter)) {
+                  const errorMsg: string = ITextTranslation.translateText(
+                    "ReaderErrorMessages/NoteAlterationError", "Invalid alteration while reading note."
+                  );
+                  this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                  throw new MusicSheetReadingException(errorMsg, undefined);
                 }
-            }
-        } catch (e) {
-            const errorMsg: string = ITextTranslation.translateText(
-                "ReaderErrorMessages/BeamError", "Error while reading beam."
-            );
-            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-            throw new MusicSheetReadingException("", e);
-        }
-
-    }
 
-    /**
-     * Check for open [[Beam]]s at end of [[SourceMeasure]] and closes them explicity.
-     */
-    private handleOpenBeam(): void {
-        if (this.openBeam.Notes.length === 1) {
-            const beamNote: Note = this.openBeam.Notes[0];
-            beamNote.NoteBeam = undefined;
-            this.openBeam = undefined;
-            return;
-        }
-        if (this.currentNote === CollectionUtil.last(this.openBeam.Notes)) {
-            this.openBeam = undefined;
-        } else {
-            const beamLastNote: Note = CollectionUtil.last(this.openBeam.Notes);
-            const beamLastNoteStaffEntry: SourceStaffEntry = beamLastNote.ParentStaffEntry;
-            const horizontalIndex: number = this.currentMeasure.getVerticalContainerIndexByTimestamp(beamLastNoteStaffEntry.Timestamp);
-            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];
-                if (nextStaffEntry !== undefined) {
-                    for (let idx: number = 0, len: number = nextStaffEntry.VoiceEntries.length; idx < len; ++idx) {
-                        const voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
-                        if (voiceEntry.ParentVoice === this.voice) {
-                            const candidateNote: Note = voiceEntry.Notes[0];
-                            if (candidateNote.Length.lte(new Fraction(1, 8))) {
-                                this.openBeam.addNoteToBeam(candidateNote);
-                                this.openBeam = undefined;
-                            } else {
-                                this.openBeam = undefined;
-                            }
-                        }
-                    }
+              } else if (pitchElement.name === "octave") {
+                noteOctave = parseInt(pitchElement.value, 10);
+                if (isNaN(noteOctave)) {
+                  const errorMsg: string = ITextTranslation.translateText(
+                    "ReaderErrorMessages/NoteOctaveError", "Invalid octave value while reading note."
+                  );
+                  this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+                  throw new MusicSheetReadingException(errorMsg, undefined);
                 }
-            } else {
-                this.openBeam = undefined;
+              }
+            } catch (ex) {
+              log.info("VoiceGenerator.addSingleNote read Step: ", ex.message);
+            }
+
+          }
+        } else if (noteElement.name === "unpitched") {
+          const displayStep: IXmlElement = noteElement.element("display-step");
+          if (displayStep !== undefined) {
+            noteStep = NoteEnum[displayStep.value.toUpperCase()];
+          }
+          const octave: IXmlElement = noteElement.element("display-octave");
+          if (octave !== undefined) {
+            noteOctave = parseInt(octave.value, 10);
+            if (guitarPro) {
+              noteOctave += 1;
             }
+          }
+        } else if (noteElement.name === "instrument") {
+          if (noteElement.firstAttribute !== undefined) {
+            playbackInstrumentId = noteElement.firstAttribute.value;
+          }
         }
+      } catch (ex) {
+        log.info("VoiceGenerator.addSingleNote: ", ex);
+      }
     }
-    private handleGraceNote(node: IXmlElement, note: Note): void {
-        let graceChord: boolean = false;
-        let type: string = "";
-        if (node.elements("type")) {
-            const typeNode: IXmlElement[] = node.elements("type");
-            if (typeNode) {
-                type = typeNode[0].value;
-                try {
-                    note.Length = this.getNoteDurationFromType(type);
-                    note.Length.Numerator = 1;
-                } catch (e) {
-                    const errorMsg: string = ITextTranslation.translateText(
-                        "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
-                    );
-                    this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                    throw new MusicSheetReadingException(errorMsg, e);
-                }
 
+    noteOctave -= Pitch.OctaveXmlDifference;
+    const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAlter);
+    const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
+    const note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
+    note.PlaybackInstrumentId = playbackInstrumentId;
+    if (!graceNote) {
+      this.currentVoiceEntry.Notes.push(note);
+    } else {
+      this.handleGraceNote(node, note);
+    }
+    if (node.elements("beam") && !chord) {
+      this.createBeam(node, note, graceNote);
+    }
+    return note;
+  }
+
+  /**
+   * Create a new rest note and add it to the currentVoiceEntry.
+   * @param noteDuration
+   * @param divisions
+   * @returns {Note}
+   */
+  private addRestNote(noteDuration: Fraction): Note {
+    const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
+    const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, undefined);
+    this.currentVoiceEntry.Notes.push(restNote);
+    if (this.openBeam !== undefined) {
+      this.openBeam.ExtendedNoteList.push(restNote);
+    }
+    return restNote;
+  }
+
+  /**
+   * Handle the currentVoiceBeam.
+   * @param node
+   * @param note
+   * @param grace
+   */
+  private createBeam(node: IXmlElement, note: Note, grace: boolean): void {
+    try {
+      const beamNode: IXmlElement = node.element("beam");
+      let beamAttr: IXmlAttribute = undefined;
+      if (beamNode !== undefined && beamNode.hasAttributes) {
+        beamAttr = beamNode.attribute("number");
+      }
+      if (beamAttr !== undefined) {
+        const beamNumber: number = parseInt(beamAttr.value, 10);
+        const mainBeamNode: IXmlElement[] = node.elements("beam");
+        const currentBeamTag: string = mainBeamNode[0].value;
+        if (beamNumber === 1 && mainBeamNode !== undefined) {
+          if (currentBeamTag === "begin" && this.lastBeamTag !== currentBeamTag) {
+            if (grace) {
+              if (this.openGraceBeam !== undefined) {
+                this.handleOpenBeam();
+              }
+              this.openGraceBeam = new Beam();
+            } else {
+              if (this.openBeam !== undefined) {
+                this.handleOpenBeam();
+              }
+              this.openBeam = new Beam();
             }
+          }
+          this.lastBeamTag = currentBeamTag;
         }
-        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") {
-                    note.GraceNoteSlash = true;
-                }
+        let sameVoiceEntry: boolean = false;
+        if (grace) {
+          if (this.openGraceBeam === undefined) {
+            return;
+          }
+          for (let idx: number = 0, len: number = this.openGraceBeam.Notes.length; idx < len; ++idx) {
+            const beamNote: Note = this.openGraceBeam.Notes[idx];
+            if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
+              sameVoiceEntry = true;
             }
-        }
-        if (node.element("chord") !== undefined) {
-            graceChord = true;
-        }
-        let graceVoiceEntry: VoiceEntry = undefined;
-        if (!graceChord) {
-            graceVoiceEntry = new VoiceEntry(
-                new Fraction(0, 1), this.currentVoiceEntry.ParentVoice, this.currentStaffEntry
-            );
-            if (this.currentVoiceEntry.graceVoiceEntriesBefore === undefined) {
-                this.currentVoiceEntry.graceVoiceEntriesBefore = [];
+          }
+          if (!sameVoiceEntry) {
+            this.openGraceBeam.addNoteToBeam(note);
+            if (currentBeamTag === "end" && beamNumber === 1) {
+              this.openGraceBeam = undefined;
             }
-            this.currentVoiceEntry.graceVoiceEntriesBefore.push(graceVoiceEntry);
+          }
         } else {
-            if (
-                this.currentVoiceEntry.graceVoiceEntriesBefore !== undefined
-                && this.currentVoiceEntry.graceVoiceEntriesBefore.length > 0
-            ) {
-                graceVoiceEntry = CollectionUtil.last(this.currentVoiceEntry.graceVoiceEntriesBefore);
+          if (this.openBeam === undefined) {
+            return;
+          }
+          for (let idx: number = 0, len: number = this.openBeam.Notes.length; idx < len; ++idx) {
+            const beamNote: Note = this.openBeam.Notes[idx];
+            if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
+              sameVoiceEntry = true;
             }
+          }
+          if (!sameVoiceEntry) {
+            this.openBeam.addNoteToBeam(note);
+            if (currentBeamTag === "end" && beamNumber === 1) {
+              this.openBeam = undefined;
+            }
+          }
         }
-        if (graceVoiceEntry !== undefined) {
-            graceVoiceEntry.Notes.push(note);
-            note.ParentVoiceEntry = graceVoiceEntry;
-        }
+      }
+    } catch (e) {
+      const errorMsg: string = ITextTranslation.translateText(
+        "ReaderErrorMessages/BeamError", "Error while reading beam."
+      );
+      this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+      throw new MusicSheetReadingException("", e);
     }
 
-    /**
-     * Create a [[Tuplet]].
-     * @param node
-     * @param tupletNodeList
-     * @returns {number}
-     */
-    private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
-        if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
-            let timeModNode: IXmlElement = node.element("time-modification");
-            if (timeModNode !== undefined) {
-                timeModNode = timeModNode.element("actual-notes");
-            }
-            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") {
-                        let tupletNumber: number = 1;
-                        if (tupletNode.attribute("number")) {
-                            tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
-                        }
-                        let tupletLabelNumber: number = 0;
-                        if (timeModNode !== undefined) {
-                            tupletLabelNumber = parseInt(timeModNode.value, 10);
-                            if (isNaN(tupletLabelNumber)) {
-                                const errorMsg: string = ITextTranslation.translateText(
-                                    "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
-                                );
-                                this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                                throw new MusicSheetReadingException(errorMsg, undefined);
-                            }
-
-                        }
-                        const tuplet: Tuplet = new Tuplet(tupletLabelNumber);
-                        if (this.tupletDict[tupletNumber] !== undefined) {
-                            delete this.tupletDict[tupletNumber];
-                            if (Object.keys(this.tupletDict).length === 0) {
-                                this.openTupletNumber = 0;
-                            } else if (Object.keys(this.tupletDict).length > 1) {
-                                this.openTupletNumber--;
-                            }
-                        }
-                        this.tupletDict[tupletNumber] = tuplet;
-                        const subnotelist: Note[] = [];
-                        subnotelist.push(this.currentNote);
-                        tuplet.Notes.push(subnotelist);
-                        tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
-                        this.currentNote.NoteTuplet = tuplet;
-                        this.openTupletNumber = tupletNumber;
-                    } else if (type === "stop") {
-                        let tupletNumber: number = 1;
-                        if (tupletNode.attribute("number")) {
-                            tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
-                        }
-                        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) {
-                                this.openTupletNumber = 0;
-                            } else if (Object.keys(this.tupletDict).length > 1) {
-                                this.openTupletNumber--;
-                            }
-                        }
-                    }
-                }
-            }
-        } 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")) {
-                    tupletnumber = parseInt(n.attribute("number").value, 10);
-                }
-                const noTupletNumbering: boolean = isNaN(tupletnumber);
-
-                if (type === "start") {
-                    let tupletLabelNumber: number = 0;
-                    let timeModNode: IXmlElement = node.element("time-modification");
-                    if (timeModNode !== undefined) {
-                        timeModNode = timeModNode.element("actual-notes");
-                    }
-                    if (timeModNode !== undefined) {
-                        tupletLabelNumber = parseInt(timeModNode.value, 10);
-                        if (isNaN(tupletLabelNumber)) {
-                            const errorMsg: string = ITextTranslation.translateText(
-                                "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
-                            );
-                            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                            throw new MusicSheetReadingException(errorMsg);
-                        }
-
-                    }
-                    if (noTupletNumbering) {
-                        this.openTupletNumber++;
-                        tupletnumber = this.openTupletNumber;
-                    }
-                    let tuplet: Tuplet = this.tupletDict[tupletnumber];
-                    if (tuplet === undefined) {
-                        tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber);
-                    }
-                    const subnotelist: Note[] = [];
-                    subnotelist.push(this.currentNote);
-                    tuplet.Notes.push(subnotelist);
-                    tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
-                    this.currentNote.NoteTuplet = tuplet;
-                    this.openTupletNumber = tupletnumber;
-                } else if (type === "stop") {
-                    if (noTupletNumbering) {
-                        tupletnumber = this.openTupletNumber;
-                    }
-                    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) {
-                            this.openTupletNumber = 0;
-                        } else if (Object.keys(this.tupletDict).length > 1) {
-                            this.openTupletNumber--;
-                        }
-                        delete this.tupletDict[tupletnumber];
-                    }
-                }
+  }
+
+  /**
+   * Check for open [[Beam]]s at end of [[SourceMeasure]] and closes them explicity.
+   */
+  private handleOpenBeam(): void {
+    if (this.openBeam.Notes.length === 1) {
+      const beamNote: Note = this.openBeam.Notes[0];
+      beamNote.NoteBeam = undefined;
+      this.openBeam = undefined;
+      return;
+    }
+    if (this.currentNote === CollectionUtil.last(this.openBeam.Notes)) {
+      this.openBeam = undefined;
+    } else {
+      const beamLastNote: Note = CollectionUtil.last(this.openBeam.Notes);
+      const beamLastNoteStaffEntry: SourceStaffEntry = beamLastNote.ParentStaffEntry;
+      const horizontalIndex: number = this.currentMeasure.getVerticalContainerIndexByTimestamp(beamLastNoteStaffEntry.Timestamp);
+      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];
+        if (nextStaffEntry !== undefined) {
+          for (let idx: number = 0, len: number = nextStaffEntry.VoiceEntries.length; idx < len; ++idx) {
+            const voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
+            if (voiceEntry.ParentVoice === this.voice) {
+              const candidateNote: Note = voiceEntry.Notes[0];
+              if (candidateNote.Length.lte(new Fraction(1, 8))) {
+                this.openBeam.addNoteToBeam(candidateNote);
+                this.openBeam = undefined;
+              } else {
+                this.openBeam = undefined;
+              }
             }
+          }
         }
-        return this.openTupletNumber;
+      } else {
+        this.openBeam = undefined;
+      }
     }
+  }
+
+  private handleGraceNote(node: IXmlElement, note: Note): void {
+    let graceChord: boolean = false;
+    let type: string = "";
+    if (node.elements("type")) {
+      const typeNode: IXmlElement[] = node.elements("type");
+      if (typeNode) {
+        type = typeNode[0].value;
+        try {
+          note.Length = this.getNoteDurationFromType(type);
+          note.Length.Numerator = 1;
+        } catch (e) {
+          const errorMsg: string = ITextTranslation.translateText(
+            "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
+          );
+          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+          throw new MusicSheetReadingException(errorMsg, e);
+        }
 
-    /**
-     * This method handles the time-modification IXmlElement for the Tuplet case (tupletNotes not at begin/end of Tuplet).
-     * @param noteNode
-     */
-    private handleTimeModificationNode(noteNode: IXmlElement): void {
-        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)) {
-                    noteList = notes;
-                } else {
-                    noteList = [];
-                    tuplet.Notes.push(noteList);
-                    tuplet.Fractions.push(this.getTupletNoteDurationFromType(noteNode));
-                }
-                noteList.push(this.currentNote);
-                this.currentNote.NoteTuplet = tuplet;
-            } catch (ex) {
+      }
+    }
+    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") {
+          note.GraceNoteSlash = true;
+        }
+      }
+    }
+    if (node.element("chord") !== undefined) {
+      graceChord = true;
+    }
+    let graceVoiceEntry: VoiceEntry = undefined;
+    if (!graceChord) {
+      graceVoiceEntry = new VoiceEntry(
+        new Fraction(0, 1), this.currentVoiceEntry.ParentVoice, this.currentStaffEntry
+      );
+      if (this.currentVoiceEntry.graceVoiceEntriesBefore === undefined) {
+        this.currentVoiceEntry.graceVoiceEntriesBefore = [];
+      }
+      this.currentVoiceEntry.graceVoiceEntriesBefore.push(graceVoiceEntry);
+    } else {
+      if (
+        this.currentVoiceEntry.graceVoiceEntriesBefore !== undefined
+        && this.currentVoiceEntry.graceVoiceEntriesBefore.length > 0
+      ) {
+        graceVoiceEntry = CollectionUtil.last(this.currentVoiceEntry.graceVoiceEntriesBefore);
+      }
+    }
+    if (graceVoiceEntry !== undefined) {
+      graceVoiceEntry.Notes.push(note);
+      note.ParentVoiceEntry = graceVoiceEntry;
+    }
+  }
+
+  /**
+   * Create a [[Tuplet]].
+   * @param node
+   * @param tupletNodeList
+   * @returns {number}
+   */
+  private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
+    if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
+      let timeModNode: IXmlElement = node.element("time-modification");
+      if (timeModNode !== undefined) {
+        timeModNode = timeModNode.element("actual-notes");
+      }
+      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") {
+            let tupletNumber: number = 1;
+            if (tupletNode.attribute("number")) {
+              tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
+            }
+            let tupletLabelNumber: number = 0;
+            if (timeModNode !== undefined) {
+              tupletLabelNumber = parseInt(timeModNode.value, 10);
+              if (isNaN(tupletLabelNumber)) {
                 const errorMsg: string = ITextTranslation.translateText(
-                    "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
+                  "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
                 );
                 this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                throw ex;
-            }
+                throw new MusicSheetReadingException(errorMsg, undefined);
+              }
 
-        } 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 tuplet: Tuplet = new Tuplet(tupletLabelNumber);
+            if (this.tupletDict[tupletNumber] !== undefined) {
+              delete this.tupletDict[tupletNumber];
+              if (Object.keys(this.tupletDict).length === 0) {
+                this.openTupletNumber = 0;
+              } else if (Object.keys(this.tupletDict).length > 1) {
+                this.openTupletNumber--;
+              }
+            }
+            this.tupletDict[tupletNumber] = tuplet;
+            const subnotelist: Note[] = [];
+            subnotelist.push(this.currentNote);
+            tuplet.Notes.push(subnotelist);
+            tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
+            this.currentNote.NoteTuplet = tuplet;
+            this.openTupletNumber = tupletNumber;
+          } else if (type === "stop") {
+            let tupletNumber: number = 1;
+            if (tupletNode.attribute("number")) {
+              tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
+            }
+            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) {
+                this.openTupletNumber = 0;
+              } else if (Object.keys(this.tupletDict).length > 1) {
+                this.openTupletNumber--;
+              }
+            }
+          }
         }
-    }
-    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 {
-                        if (type === "start") {
-                            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);
-                                    }
-                                }
-                            }
-                        } 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))) {
-                                    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];
-                            }
-                        }
-                    } catch (err) {
-                        const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
-                        this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-                    }
+      }
+    } 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")) {
+          tupletnumber = parseInt(n.attribute("number").value, 10);
+        }
+        const noTupletNumbering: boolean = isNaN(tupletnumber);
+
+        if (type === "start") {
+          let tupletLabelNumber: number = 0;
+          let timeModNode: IXmlElement = node.element("time-modification");
+          if (timeModNode !== undefined) {
+            timeModNode = timeModNode.element("actual-notes");
+          }
+          if (timeModNode !== undefined) {
+            tupletLabelNumber = parseInt(timeModNode.value, 10);
+            if (isNaN(tupletLabelNumber)) {
+              const errorMsg: string = ITextTranslation.translateText(
+                "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
+              );
+              this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+              throw new MusicSheetReadingException(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))) {
-                        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); }
-                }
+          }
+          if (noTupletNumbering) {
+            this.openTupletNumber++;
+            tupletnumber = this.openTupletNumber;
+          }
+          let tuplet: Tuplet = this.tupletDict[tupletnumber];
+          if (tuplet === undefined) {
+            tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber);
+          }
+          const subnotelist: Note[] = [];
+          subnotelist.push(this.currentNote);
+          tuplet.Notes.push(subnotelist);
+          tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
+          this.currentNote.NoteTuplet = tuplet;
+          this.openTupletNumber = tupletnumber;
+        } else if (type === "stop") {
+          if (noTupletNumbering) {
+            tupletnumber = this.openTupletNumber;
+          }
+          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) {
+              this.openTupletNumber = 0;
+            } else if (Object.keys(this.tupletDict).length > 1) {
+              this.openTupletNumber--;
             }
+            delete this.tupletDict[tupletnumber];
+          }
         }
+      }
     }
-
-    /**
-     * Find the next free int (starting from 0) to use as key in TieDict.
-     * @returns {number}
-     */
-    private getNextAvailableNumberForTie(): number {
-        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++) {
-            if ("" + (i + 1) !== keys[i]) {
-                return i + 1;
-            }
+    return this.openTupletNumber;
+  }
+
+  /**
+   * This method handles the time-modification IXmlElement for the Tuplet case (tupletNotes not at begin/end of Tuplet).
+   * @param noteNode
+   */
+  private handleTimeModificationNode(noteNode: IXmlElement): void {
+    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)) {
+          noteList = notes;
+        } else {
+          noteList = [];
+          tuplet.Notes.push(noteList);
+          tuplet.Fractions.push(this.getTupletNoteDurationFromType(noteNode));
         }
-        return +(keys[keys.length - 1]) + 1;
+        noteList.push(this.currentNote);
+        this.currentNote.NoteTuplet = tuplet;
+      } catch (ex) {
+        const errorMsg: string = ITextTranslation.translateText(
+          "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
+        );
+        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;
+      }
     }
-
-    /**
-     * Search the tieDictionary for the corresponding candidateNote to the currentNote (same FundamentalNote && Octave).
-     * @param candidateNote
-     * @returns {number}
-     */
-    private findCurrentNoteInTieDict(candidateNote: Note): number {
-        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) {
-                    return +key;
+  }
+
+  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 {
+            if (type === "start") {
+              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;
+            } else if (type === "stop") {
+              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);
                 }
+                delete this.openTieDict[tieNumber];
+              }
             }
-        }
-        return -1;
-    }
-
-    /**
-     * Calculate the normal duration of a [[Tuplet]] note.
-     * @param xmlNode
-     * @returns {any}
-     */
-    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 {
-                    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);
-                }
+          } catch (err) {
+            const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
+            this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
+          }
 
-            }
         }
-        return undefined;
+      } else if (tieNodeList.length === 2) {
+        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);
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Find the next free int (starting from 0) to use as key in TieDict.
+   * @returns {number}
+   */
+  private getNextAvailableNumberForTie(): number {
+    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++) {
+      if ("" + (i + 1) !== keys[i]) {
+        return i + 1;
+      }
+    }
+    return +(keys[keys.length - 1]) + 1;
+  }
+
+  /**
+   * Search the tieDictionary for the corresponding candidateNote to the currentNote (same FundamentalNote && Octave).
+   * @param candidateNote
+   * @returns {number}
+   */
+  private findCurrentNoteInTieDict(candidateNote: Note): number {
+    const openTieDict: { [_: number]: Tie; } = this.openTieDict;
+    for (const key in openTieDict) {
+      if (openTieDict.hasOwnProperty(key)) {
+        const tie: Tie = openTieDict[key];
+        if (tie.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Pitch.Octave === candidateNote.Pitch.Octave) {
+          return +key;
+        }
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Calculate the normal duration of a [[Tuplet]] note.
+   * @param xmlNode
+   * @returns {any}
+   */
+  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 {
+          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);
+        }
+
+      }
     }
+    return undefined;
+  }
 }

+ 4 - 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 {
 
@@ -93,6 +93,7 @@ export class SubInstrument {
     private parseMidiInstrument(instrumentType: string): string {
         // FIXME: test this function
         try {
+            // find the best match for the given instrumentType:
             if (instrumentType) {
                 const tmpName: string = instrumentType.toLowerCase().trim();
                 for (const key in SubInstrument.midiInstrument) {
@@ -101,6 +102,7 @@ export class SubInstrument {
                     }
                 }
             }
+            // if the instrumentType didn't work, use the name:
             if (this.parentInstrument.Name) {
                 const tmpName: string = this.parentInstrument.Name.toLowerCase().trim();
                 for (const key in SubInstrument.midiInstrument) {
@@ -110,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;
     }
 

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

@@ -2,10 +2,6 @@ import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
 import {TextAlignment} from "../../../Common/Enums/TextAlignment";
 
 export class UnknownExpression extends AbstractExpression {
-    //constructor(label: string, placementEnum: PlacementEnum, staffNumber: number) {
-    //    this(label, placementEnum, OSMDTextAlignment.LeftBottom, staffNumber);
-    //
-    //}
     constructor(label: string, placementEnum: PlacementEnum, textAlignment: TextAlignment, staffNumber: number) {
         super();
         this.label = label;

+ 13 - 4
src/MusicalScore/VoiceData/Instructions/RepetitionInstruction.ts

@@ -41,16 +41,19 @@ export class RepetitionInstruction /*implements IComparable*/ {
 
      }
      */
-    constructor(measureIndex: number, endingIndices: number[], type: RepetitionInstructionEnum, alignment: AlignmentType, parentRepetition: Repetition) {
+    constructor(measureIndex: number, type: RepetitionInstructionEnum, alignment: AlignmentType = AlignmentType.End,
+                parentRepetition: Repetition = undefined, endingIndices: number[] = undefined) {
         this.measureIndex = measureIndex;
-        this.endingIndices = endingIndices.slice();
+        if (endingIndices !== undefined) {
+            this.endingIndices = endingIndices.slice();
+        }
         this.type = type;
         this.alignment = alignment;
         this.parentRepetition = parentRepetition;
     }
 
     public measureIndex: number;
-    public endingIndices: number[];
+    public endingIndices: number[] = undefined;
     public type: RepetitionInstructionEnum;
     public alignment: AlignmentType;
     public parentRepetition: Repetition;
@@ -123,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;

+ 8 - 1
src/MusicalScore/VoiceData/Lyrics/LyricsEntry.ts

@@ -2,14 +2,17 @@ import {LyricWord} from "./LyricsWord";
 import {VoiceEntry} from "../VoiceEntry";
 
 export class LyricsEntry {
-    constructor(text: string, word: LyricWord, parent: VoiceEntry) {
+    constructor(text: string, verseNumber: number, word: LyricWord, parent: VoiceEntry) {
         this.text = text;
         this.word = word;
         this.parent = parent;
+        this.verseNumber = verseNumber;
     }
     private text: string;
     private word: LyricWord;
     private parent: VoiceEntry;
+    private verseNumber: number;
+    public extend: boolean;
 
     public get Text(): string {
         return this.text;
@@ -26,4 +29,8 @@ export class LyricsEntry {
     public set Parent(value: VoiceEntry) {
         this.parent = value;
     }
+
+    public get VerseNumber(): number {
+        return this.verseNumber;
+    }
 }

+ 3 - 53
src/MusicalScore/VoiceData/Note.ts

@@ -103,29 +103,10 @@ export class Note {
         this.playbackInstrumentId = value;
     }
 
-    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 isRest(): boolean {
+        return this.Pitch === undefined;
     }
+
     public ToString(): string {
         if (this.pitch !== undefined) {
             return this.Pitch.ToString() + ", length: " + this.length.toString();
@@ -152,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 {

+ 9 - 2
src/MusicalScore/VoiceData/SourceMeasure.ts

@@ -1,7 +1,7 @@
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {VerticalSourceStaffEntryContainer} from "./VerticalSourceStaffEntryContainer";
 import {SourceStaffEntry} from "./SourceStaffEntry";
-import {RepetitionInstruction} from "./Instructions/RepetitionInstruction";
+import {RepetitionInstruction, RepetitionInstructionEnum} from "./Instructions/RepetitionInstruction";
 import {Staff} from "./Staff";
 import {VoiceEntry} from "./VoiceEntry";
 import {Voice} from "./Voice";
@@ -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};
     }
 
@@ -400,6 +400,9 @@ export class SourceMeasure extends BaseIdClass {
     public beginsWithLineRepetition(): boolean {
         for (let idx: number = 0, len: number = this.FirstRepetitionInstructions.length; idx < len; ++idx) {
             const instr: RepetitionInstruction = this.FirstRepetitionInstructions[idx];
+            if (instr.type === RepetitionInstructionEnum.StartLine) {
+                return true;
+            }
             if (instr.parentRepetition !== undefined && instr === instr.parentRepetition.startMarker && !instr.parentRepetition.FromWords) {
                 return true;
             }
@@ -414,6 +417,10 @@ export class SourceMeasure extends BaseIdClass {
     public endsWithLineRepetition(): boolean {
         for (let idx: number = 0, len: number = this.LastRepetitionInstructions.length; idx < len; ++idx) {
             const instruction: RepetitionInstruction = this.LastRepetitionInstructions[idx];
+            if (instruction.type === RepetitionInstructionEnum.BackJumpLine) {
+                return true;
+            }
+
             const rep: Repetition = instruction.parentRepetition;
             if (rep === undefined) {
                 continue;

+ 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;
+    }
 }

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

@@ -39,6 +39,7 @@ export class VoiceEntry {
     private lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
     private arpeggiosNotesIndices: number[] = [];
     private ornamentContainer: OrnamentContainer;
+    private stemDirection: StemDirectionType = StemDirectionType.Undefined;
 
     public get ParentSourceStaffEntry(): SourceStaffEntry {
         return this.parentSourceStaffEntry;
@@ -77,6 +78,13 @@ export class VoiceEntry {
         this.ornamentContainer = value;
     }
 
+    public get StemDirection(): StemDirectionType {
+        return this.stemDirection;
+    }
+    public set StemDirection(value: StemDirectionType) {
+        this.stemDirection = value;
+    }
+
     public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
         switch (articulation) {
             case ArticulationEnum.accent:
@@ -151,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);
@@ -325,3 +333,10 @@ export enum ArticulationEnum {
     detachedlegato,
     otherarticulation
 }
+
+export enum StemDirectionType {
+    Undefined = -1,
+    Up = 0,
+    Down = 1,
+    None = 2
+}

+ 2 - 1
src/OSMD/AJAX.ts → 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();
         });

+ 11 - 11
src/OSMD/Cursor.ts → src/OpenSheetMusicDisplay/Cursor.ts

@@ -3,16 +3,16 @@ import {MusicPartManager} from "../MusicalScore/MusicParts/MusicPartManager";
 import {VoiceEntry} from "../MusicalScore/VoiceData/VoiceEntry";
 import {VexFlowStaffEntry} from "../MusicalScore/Graphical/VexFlow/VexFlowStaffEntry";
 import {MusicSystem} from "../MusicalScore/Graphical/MusicSystem";
-import {OSMD} from "./OSMD";
+import {OpenSheetMusicDisplay} from "./OpenSheetMusicDisplay";
 import {GraphicalMusicSheet} from "../MusicalScore/Graphical/GraphicalMusicSheet";
 
 /**
  * A cursor which can iterate through the music sheet.
  */
 export class Cursor {
-  constructor(container: HTMLElement, osmd: OSMD) {
+  constructor(container: HTMLElement, openSheetMusicDisplay: OpenSheetMusicDisplay) {
     this.container = container;
-    this.osmd = osmd;
+    this.openSheetMusicDisplay = openSheetMusicDisplay;
     const curs: HTMLElement = document.createElement("img");
     curs.style.position = "absolute";
     curs.style.zIndex = "-1";
@@ -21,7 +21,7 @@ export class Cursor {
   }
 
   private container: HTMLElement;
-  private osmd: OSMD;
+  private openSheetMusicDisplay: OpenSheetMusicDisplay;
   private manager: MusicPartManager;
   private iterator: MusicPartManagerIterator;
   private graphic: GraphicalMusicSheet;
@@ -44,11 +44,11 @@ export class Cursor {
     this.update();
     // Forcing the sheet to re-render is not necessary anymore,
     // since the cursor is an HTML element.
-    // this.osmd.render();
+    // this.openSheetMusicDisplay.render();
   }
 
   public update(): void {
-    // Warning! This should NEVER call this.osmd.render()
+    // Warning! This should NEVER call this.openSheetMusicDisplay.render()
     if (this.hidden) {
       return;
     }
@@ -90,10 +90,10 @@ export class Cursor {
 
     // This the current HTML Cursor:
     const cursorElement: HTMLImageElement = this.cursorElement;
-    cursorElement.style.top = (y * 10.0 * this.osmd.zoom) + "px";
-    cursorElement.style.left = ((x - 1.5) * 10.0 * this.osmd.zoom) + "px";
-    cursorElement.height = (height * 10.0 * this.osmd.zoom);
-    const newWidth: number = 3 * 10.0 * this.osmd.zoom;
+    cursorElement.style.top = (y * 10.0 * this.openSheetMusicDisplay.zoom) + "px";
+    cursorElement.style.left = ((x - 1.5) * 10.0 * this.openSheetMusicDisplay.zoom) + "px";
+    cursorElement.height = (height * 10.0 * this.openSheetMusicDisplay.zoom);
+    const newWidth: number = 3 * 10.0 * this.openSheetMusicDisplay.zoom;
     if (newWidth !== cursorElement.width) {
       cursorElement.width = newWidth;
       this.updateStyle(newWidth);
@@ -113,7 +113,7 @@ export class Cursor {
     //this.graphic.Cursors.length = 0;
     // Forcing the sheet to re-render is not necessary anymore
     //if (!this.hidden) {
-    //    this.osmd.render();
+    //    this.openSheetMusicDisplay.render();
     //}
     this.hidden = true;
   }

+ 15 - 10
src/OSMD/OSMD.ts → src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -14,13 +14,13 @@ import {Promise} from "es6-promise";
 import {AJAX} from "./AJAX";
 import * as log from "loglevel";
 
-export class OSMD {
+export class OpenSheetMusicDisplay {
     /**
      * The easy way of displaying a MusicXML sheet music file
      * @param container is either the ID, or the actual "div" element which will host the music sheet
      * @autoResize automatically resize the sheet to full page width on window resize
      */
-    constructor(container: string|HTMLElement, autoResize: boolean = false, backend: string = "canvas") {
+    constructor(container: string|HTMLElement, autoResize: boolean = false, backend: string = "svg") {
         // Store container element
         if (typeof container === "string") {
             // ID passed
@@ -30,7 +30,7 @@ export class OSMD {
             this.container = <HTMLElement>container;
         }
         if (!this.container) {
-            throw new Error("Please pass a valid div container to OSMD");
+            throw new Error("Please pass a valid div container to OpenSheetMusicDisplay");
         }
 
         if (backend === "svg") {
@@ -72,7 +72,7 @@ export class OSMD {
         this.reset();
         if (typeof content === "string") {
             const str: string = <string>content;
-            const self: OSMD = this;
+            const self: OpenSheetMusicDisplay = this;
             if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
                 // This is a zip file, unpack it first
                 return MXLHelper.MXLtoXMLstring(str).then(
@@ -81,14 +81,19 @@ export class OSMD {
                     },
                     (err: any) => {
                         log.debug(err);
-                        throw new Error("OSMD: Invalid MXL file");
+                        throw new Error("OpenSheetMusicDisplay: Invalid MXL file");
                     }
                 );
             }
+            // Javascript loads strings as utf-16, which is wonderful BS if you want to parse UTF-8 :S
+            if (str.substr(0, 3) === "\uf7ef\uf7bb\uf7bf") {
+                // UTF with BOM detected, truncate first three bytes and pass along
+                return self.load(str.substr(3));
+            }
             if (str.substr(0, 5) === "<?xml") {
                 // Parse the string representing an xml file
                 const parser: DOMParser = new DOMParser();
-                content = parser.parseFromString(str, "text/xml");
+                content = parser.parseFromString(str, "application/xml");
             } else if (str.length < 2083) {
                 // Assume now "str" is a URL
                 // Retrieve the file at the given URL
@@ -100,7 +105,7 @@ export class OSMD {
         }
 
         if (!content || !(<any>content).nodeName) {
-            return Promise.reject(new Error("OSMD: The document which was provided is invalid"));
+            return Promise.reject(new Error("OpenSheetMusicDisplay: The document which was provided is invalid"));
         }
         const children: NodeList = (<Document>content).childNodes;
         let elem: Element;
@@ -112,7 +117,7 @@ export class OSMD {
             }
         }
         if (!elem) {
-            return Promise.reject(new Error("OSMD: Document is not a valid 'partwise' MusicXML"));
+            return Promise.reject(new Error("OpenSheetMusicDisplay: Document is not a valid 'partwise' MusicXML"));
         }
         const score: IXmlElement = new IXmlElement(elem);
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
@@ -129,7 +134,7 @@ export class OSMD {
      */
     public render(): void {
         if (!this.graphic) {
-            throw new Error("OSMD: Before rendering a music sheet, please load a MusicXML file");
+            throw new Error("OpenSheetMusicDisplay: Before rendering a music sheet, please load a MusicXML file");
         }
         const width: number = this.container.offsetWidth;
         // Before introducing the following optimization (maybe irrelevant), tests
@@ -208,7 +213,7 @@ export class OSMD {
      * Attach the appropriate handler to the window.onResize event
      */
     private autoResize(): void {
-        const self: OSMD = this;
+        const self: OpenSheetMusicDisplay = this;
         this.handleResize(
             () => {
                 // empty

+ 3 - 3
test/Common/FileIO/Xml_Test.ts

@@ -1,6 +1,6 @@
 import {IXmlElement} from "../../../src/Common/FileIO/Xml";
 import {TestUtils} from "../../Util/TestUtils";
-import {OSMD} from "../../../src/OSMD/OSMD";
+import {OpenSheetMusicDisplay} from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 
 // Test XML simple document
 const xmlTestData: string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\
@@ -48,8 +48,8 @@ describe("XML interface", () => {
             // Load the xml file content
             const score: Document = TestUtils.getScore(scoreName);
             const div: HTMLElement = document.createElement("div");
-            const osmd: OSMD = new OSMD(div);
-            osmd.load(score);
+            const openSheetMusicDisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+            openSheetMusicDisplay.load(score);
             done();
         }).timeout(3000);
     }

+ 30 - 30
test/Common/OSMD/OSMD_Test.ts

@@ -1,14 +1,14 @@
 import chai = require("chai");
-import {OSMD} from "../../../src/OSMD/OSMD";
+import {OpenSheetMusicDisplay} from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 import {TestUtils} from "../../Util/TestUtils";
 
 
-describe("OSMD Main Export", () => {
+describe("OpenSheetMusicDisplay Main Export", () => {
     let container1: HTMLElement;
 
     it("no container", (done: MochaDone) => {
         chai.expect(() => {
-            return new OSMD(undefined);
+            return new OpenSheetMusicDisplay(undefined);
         }).to.throw(/container/);
         done();
     });
@@ -16,7 +16,7 @@ describe("OSMD Main Export", () => {
     it("container", (done: MochaDone) => {
         const div: HTMLElement = document.createElement("div");
         chai.expect(() => {
-            return new OSMD(div);
+            return new OpenSheetMusicDisplay(div);
         }).to.not.throw(Error);
         done();
     });
@@ -24,10 +24,10 @@ describe("OSMD Main Export", () => {
     it("load MXL from string", (done: MochaDone) => {
         const mxl: string = TestUtils.getMXL("MozartTrio.mxl");
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(mxl).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(mxl).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 done();
             },
             done
@@ -37,8 +37,8 @@ describe("OSMD Main Export", () => {
     it("load invalid MXL from string", (done: MochaDone) => {
         const mxl: string = "\x50\x4b\x03\x04";
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(mxl).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(mxl).then(
             (_: {}) => {
                 done(new Error("Corrupted MXL appears to be loaded correctly"));
             },
@@ -56,10 +56,10 @@ describe("OSMD Main Export", () => {
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         const xml: string = new XMLSerializer().serializeToString(score);
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(xml).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(xml).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 done();
             },
             done
@@ -69,10 +69,10 @@ describe("OSMD Main Export", () => {
     it("load XML Document", (done: MochaDone) => {
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(score).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(score).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 done();
             },
             done
@@ -82,10 +82,10 @@ describe("OSMD Main Export", () => {
     it("load MXL Document by URL", (done: MochaDone) => {
         const url: string = "base/test/data/MozartTrio.mxl";
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(url).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(url).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 done();
             },
             done
@@ -95,8 +95,8 @@ describe("OSMD Main Export", () => {
     it("load MXL Document by invalid URL", (done: MochaDone) => {
         const url: string = "https://www.google.com";
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(url).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(url).then(
             (_: {}) => {
                 done(new Error("Invalid URL appears to be loaded correctly"));
             },
@@ -113,8 +113,8 @@ describe("OSMD Main Export", () => {
     it("load invalid XML string", (done: MochaDone) => {
         const xml: string = "<?xml";
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
-        osmd.load(xml).then(
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(xml).then(
             (_: {}) => {
                 done(new Error("Corrupted XML appears to be loaded correctly"));
             },
@@ -130,9 +130,9 @@ describe("OSMD Main Export", () => {
 
     it("render without loading", (done: MochaDone) => {
         const div: HTMLElement = document.createElement("div");
-        const osmd: OSMD = new OSMD(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
         chai.expect(() => {
-            return osmd.render();
+            return opensheetmusicdisplay.render();
         }).to.throw(/load/);
         done();
     });
@@ -150,11 +150,11 @@ describe("OSMD Main Export", () => {
     it("test width 500", (done: MochaDone) => {
         const div: HTMLElement = container1;
         div.style.width = "500px";
-        const osmd: OSMD = new OSMD(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
-        osmd.load(score).then(
+        opensheetmusicdisplay.load(score).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 chai.expect(div.offsetWidth).to.equal(500);
                 done();
             },
@@ -165,11 +165,11 @@ describe("OSMD Main Export", () => {
     it("test width 200", (done: MochaDone) => {
         const div: HTMLElement = container1;
         div.style.width = "200px";
-        const osmd: OSMD = new OSMD(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
-        osmd.load(score).then(
+        opensheetmusicdisplay.load(score).then(
             (_: {}) => {
-                osmd.render();
+                opensheetmusicdisplay.render();
                 chai.expect(div.offsetWidth).to.equal(200);
                 done();
             },

+ 1 - 1
test/data/Beethoven_AnDieFerneGeliebte.xml

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <score-partwise version="2.0">
   <work>
     <work-number>Op. 98</work-number>

BIN
test/data/Cornelius_P_Christbaum_Opus_8_1_1865.mxl


+ 1 - 1
test/data/Mozart_DasVeilchen.xml

@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" encoding="UTF-8"?>
 <score-partwise version="2.0">
   <work>
     <work-number>K. 476</work-number>

+ 7 - 26
webpack.common.js

@@ -4,45 +4,26 @@ var webpack = require('webpack')
 
 module.exports = {
     entry: {
-        'osmd': './src/OSMD/OSMD.ts', // Main library
+        'opensheetmusicdisplay': './src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts', // Main library
         'demo': './demo/index.js' // Demo index
     },
     output: {
         path: path.resolve(__dirname, 'build'),
-        filename: '[name].js'
+        filename: '[name].js',
+        library: 'opensheetmusicdisplay',
+        libraryTarget: 'umd'
     },
     resolve: {
         // Add '.ts' and '.tsx' as a resolvable extension.
-        extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js']
+        extensions: ['.ts', '.tsx', '.js']
     },
     module: {
-        loaders: [
+        rules: [
             // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
             {
                 test: /\.ts$/,
                 loader: 'ts-loader',
                 exclude: /(node_modules|bower_components)/
-            },
-            // FIXME: TSLint loader is horribly slow therefore check only at beginning
-            // https://github.com/wbuchwalter/tslint-loader/issues/76
-            // // ts lint loader. will pre-lint the ts files
-            // {
-            //     test: /\.ts$/,
-            //     enforce: 'pre',
-            //     loader: 'tslint-loader',
-            //     options: {
-            //         typeCheck: true
-            //     }
-            // },
-            // For html loader generation
-            {
-                test: /\.html$/,
-                loader: 'underscore-template-loader'
-            },
-            {
-                test: /\.(jpg|jpeg|gif|png|ico)$/,
-                exclude: /node_modules/,
-                loader: 'file-loader?name=img/[path][name].[ext]&context=./app/images'
             }
         ]
     },
@@ -53,13 +34,13 @@ module.exports = {
         }),
         new webpack.EnvironmentPlugin({
             STATIC_FILES_SUBFOLDER: false, // Set to other directory if NOT using webpack-dev-server
-            NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
             DEBUG: false,
             DRAW_BOUNDING_BOX_ELEMENT: false //  Specifies the element to draw bounding boxes for (e.g. 'GraphicalLabels'). If 'all', bounding boxes are drawn for all elements.
         }),
         // add a demo page to the build folder
         new HtmlWebpackPlugin({
             template: 'demo/index.html',
+            favicon: 'demo/favicon.ico',
             title: 'OpenSheetMusicDisplay Demo'
         })
     ],

+ 2 - 1
webpack.dev.js

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

+ 11 - 9
webpack.prod.js

@@ -13,17 +13,19 @@ var pathsToClean = [
 module.exports = merge(common, {
     output: {
         filename: '[name].min.js',
-        path: path.resolve(__dirname, 'build')
+        path: path.resolve(__dirname, 'build'),
+        library: 'opensheetmusicdisplay',
+        libraryTarget: 'umd'
+    },
+    mode: 'production',
+    optimization: {
+        minimize: true
+        // splitChunks: {
+        //     chunks: 'all',
+        //     name: false
+        // }
     },
     plugins: [
-        new webpack.optimize.UglifyJsPlugin({
-            warnings: false,
-            beautify: false,
-            compress: true,
-            comments: false,
-            sourceMap: true,
-            parallel: true
-        }),
         // build optimization plugins
         new webpack.LoaderOptionsPlugin({
             minimize: true,

Some files were not shown because too many files changed in this diff