Explorar o código

Merge develop

Benjamin Giesinger %!s(int64=7) %!d(string=hai) anos
pai
achega
3dd0f3dd96
Modificáronse 77 ficheiros con 3994 adicións e 2227 borrados
  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=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:
 environment:
   matrix:
   matrix:
     - nodejs_version: "6"
     - 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:
 install:
-  - ps: Install-Product node $env:nodejs_version
+  - ps: Install-Product node $env:nodejs_version $env:platform
   - npm install
   - npm install
   - node --version
   - node --version
   - npm --version
   - npm --version
+  - npm run fix-memory-limit
 build_script:
 build_script:
   - npm run lint
   - npm run lint
   - npm run build
   - npm run build

+ 2 - 1
.travis.yml

@@ -1,7 +1,8 @@
 sudo: false
 sudo: false
 language: node_js
 language: node_js
 node_js:
 node_js:
-- '5'
+- '6'
+- '8'
 notifications:
 notifications:
   email: false
   email: false
   slack:
   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
 - Added Reset button for cursor for demo
 - Added Reset button for cursor for demo
 - Added more xml files for demo and testing
 - Added more xml files for demo and testing
 - Added unit tests for reading and calculating the xml files
 - Added unit tests for reading and calculating the xml files
 - Added logo as favicon and as img for demo site
 - Added logo as favicon and as img for demo site
+
 ### Changed
 ### Changed
 - html site layout of demo
 - html site layout of demo
+
 ### Bugfixes
 ### Bugfixes
 - Fixed cursor functionality in demo
 - Fixed cursor functionality in demo
 
 
@@ -92,7 +120,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Added
 ### Added
 - First public pre-release
 - 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.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.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
 [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/
 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.
 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
 #!/bin/bash
 
 
 # Prepare files to be published
 # Prepare files to be published
-npm run docs
 npm run build
 npm run build
+npm run docs
 
 
 # Clone github page
 # Clone github page
 git clone git@github.com:opensheetmusicdisplay/opensheetmusicdisplay.github.io.git
 git clone git@github.com:opensheetmusicdisplay/opensheetmusicdisplay.github.io.git
@@ -10,10 +10,12 @@ cd opensheetmusicdisplay.github.io
 git status
 git status
 
 
 # Copy class documentation
 # Copy class documentation
-rsync -a ../build/docs/* ./classdoc
+rsync -a ../build/docs/* ./classdoc/
 
 
 # Copy demo application
 # 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
 # Commit and push changes
 git status
 git status

+ 2 - 2
demo/index.html

@@ -25,9 +25,9 @@
     </div>
     </div>
     <div class="column">
     <div class="column">
         <h3 class="ui header">Render backend:</h3>
         <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="svg">SVG</option>
+            <option value="canvas">Canvas</option>>
         </select>
         </select>
     </div>
     </div>
     <div class="column">
     <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 */
 /*jslint browser:true */
 (function () {
 (function () {
     "use strict";
     "use strict";
-    var osmdObj;
+    var openSheetMusicDisplay;
     // The folder of the demo files
     // The folder of the demo files
     var folder = process.env.STATIC_FILES_SUBFOLDER ? process.env.STATIC_FILES_SUBFOLDER + "/" : "",
     var folder = process.env.STATIC_FILES_SUBFOLDER ? process.env.STATIC_FILES_SUBFOLDER + "/" : "",
     // The available demos
     // The available demos
         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.1": "MuzioClementi_SonatinaOpus36No1_Part1.xml",
             "M. Clementi - Sonatina Op.36 No.1 Pt.2": "MuzioClementi_SonatinaOpus36No1_Part2.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.1": "MuzioClementi_SonatinaOpus36No3_Part1.xml",
             "M. Clementi - Sonatina Op.36 No.3 Pt.2": "MuzioClementi_SonatinaOpus36No3_Part2.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",
             "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",
             "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",
             "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 - Elite Syncopations": "ScottJoplin_EliteSyncopations.xml",
             "S. Joplin - The Entertainer": "ScottJoplin_The_Entertainer.xml",
             "S. Joplin - The Entertainer": "ScottJoplin_The_Entertainer.xml",
             "ActorPreludeSample": "ActorPreludeSample.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",
             "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,
         zoom = 1.0,
@@ -80,6 +83,8 @@ import { OSMD } from '../src/OSMD/OSMD';
         }
         }
         select.onchange = selectOnChange;
         select.onchange = selectOnChange;
 
 
+        // Pre-select default music piece
+
         custom.appendChild(document.createTextNode("Custom"));
         custom.appendChild(document.createTextNode("Custom"));
 
 
         // Create zoom controls
         // Create zoom controls
@@ -93,8 +98,8 @@ import { OSMD } from '../src/OSMD/OSMD';
         };
         };
 
 
         // Create OSMD object and canvas
         // 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);
         document.body.appendChild(canvas);
 
 
         // Set resize event handler
         // Set resize event handler
@@ -106,7 +111,7 @@ import { OSMD } from '../src/OSMD/OSMD';
                 var width = document.body.clientWidth;
                 var width = document.body.clientWidth;
                 canvas.width = width;
                 canvas.width = width;
                 try {
                 try {
-                osmdObj.render();
+                openSheetMusicDisplay.render();
                 } catch (e) {}
                 } catch (e) {}
                 enable();
                 enable();
             }
             }
@@ -115,28 +120,28 @@ import { OSMD } from '../src/OSMD/OSMD';
         window.addEventListener("keydown", function(e) {
         window.addEventListener("keydown", function(e) {
             var event = window.event ? window.event : e;
             var event = window.event ? window.event : e;
             if (event.keyCode === 39) {
             if (event.keyCode === 39) {
-                osmdObj.cursor.next();
+                openSheetMusicDisplay.cursor.next();
             }
             }
         });
         });
         nextCursorBtn.addEventListener("click", function() {
         nextCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.next();
+            openSheetMusicDisplay.cursor.next();
         });
         });
         resetCursorBtn.addEventListener("click", function() {
         resetCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.reset();
+            openSheetMusicDisplay.cursor.reset();
         });
         });
         hideCursorBtn.addEventListener("click", function() {
         hideCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.hide();
+            openSheetMusicDisplay.cursor.hide();
         });
         });
         showCursorBtn.addEventListener("click", function() {
         showCursorBtn.addEventListener("click", function() {
-            osmdObj.cursor.show();
+            openSheetMusicDisplay.cursor.show();
         });
         });
 
 
         backendSelect.addEventListener("change", function(e) {
         backendSelect.addEventListener("change", function(e) {
             var value = e.target.value;
             var value = e.target.value;
             // clears the canvas element
             // clears the canvas element
             canvas.innerHTML = "";
             canvas.innerHTML = "";
-            osmdObj = new OSMD(canvas, false, value);
-            osmdObj.setLogLevel('info');
+            openSheetMusicDisplay = new OpenSheetMusicDisplay(canvas, false, value);
+            openSheetMusicDisplay.setLogLevel('info');
             selectOnChange();
             selectOnChange();
 
 
         });
         });
@@ -178,18 +183,19 @@ import { OSMD } from '../src/OSMD/OSMD';
             str = folder + select.value;
             str = folder + select.value;
         }
         }
         zoom = 1.0;
         zoom = 1.0;
-        osmdObj.load(str).then(
+        openSheetMusicDisplay.load(str).then(
             function() {
             function() {
-                return osmdObj.render();
+                return openSheetMusicDisplay.render();
             },
             },
             function(e) {
             function(e) {
+                console.warn(e.stack);
                 error("Error reading sheet: " + e);
                 error("Error reading sheet: " + e);
             }
             }
         ).then(
         ).then(
             function() {
             function() {
                 return onLoadingEnd(isCustom);
                 return onLoadingEnd(isCustom);
             }, function(e) {
             }, function(e) {
-                error("Error rendering sheet: " + e);
+                error("Error rendering sheet: " + process.env.DEBUG ? e.stack : e);
                 onLoadingEnd(isCustom);
                 onLoadingEnd(isCustom);
             }
             }
         );
         );
@@ -212,8 +218,8 @@ import { OSMD } from '../src/OSMD/OSMD';
     function scale() {
     function scale() {
         disable();
         disable();
         window.setTimeout(function(){
         window.setTimeout(function(){
-            osmdObj.zoom = zoom;
-            osmdObj.render();
+            openSheetMusicDisplay.zoom = zoom;
+            openSheetMusicDisplay.render();
             enable();
             enable();
         }, 0);
         }, 0);
     }
     }

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

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

+ 6 - 3
karma.conf.js

@@ -48,9 +48,10 @@ module.exports = function (config) {
             // webpack watches dependencies
             // webpack watches dependencies
 
 
             // copy parts of webpack configuration to use minimal effort here
             // 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: {
             module: {
-                loaders: common.module.loaders
+                rules: common.module.rules
             },
             },
             resolve: common.resolve
             resolve: common.resolve
         },
         },
@@ -73,7 +74,9 @@ module.exports = function (config) {
 
 
         // web server port
         // web server port
         port: 9876,
         port: 9876,
-
+        // timeout in ms:
+        browserNoActivityTimeout: 100000,
+        captureTimeout: 60000,
         // enable / disable colors in the output (reporters and logs)
         // enable / disable colors in the output (reporters and logs)
         colors: true,
         colors: true,
 
 

+ 21 - 22
package.json

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

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

@@ -126,6 +126,7 @@ export class Fraction {
   public set Denominator(value: number) {
   public set Denominator(value: number) {
     if (this.denominator !== value) {
     if (this.denominator !== value) {
       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) {
       if (this.numerator !== 0) {
         this.simplify();
         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 {
   public GetExpandedNumerator(): number {
     return this.wholeValue * this.denominator + this.numerator;
     return this.wholeValue * this.denominator + this.numerator;
   }
   }
@@ -170,7 +175,15 @@ export class Fraction {
   //   this.setRealValue();
   //   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 {
   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 +
     this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator +
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
     this.denominator = this.denominator * fraction.denominator;
     this.denominator = this.denominator * fraction.denominator;
@@ -179,7 +192,15 @@ export class Fraction {
     this.setRealValue();
     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 {
   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 -
     this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator -
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
       (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
     this.denominator = this.denominator * fraction.denominator;
     this.denominator = this.denominator * fraction.denominator;
@@ -187,7 +208,11 @@ export class Fraction {
     this.simplify();
     this.simplify();
     this.setRealValue();
     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 {
   public Quantize(maxAllowedDenominator: number): Fraction {
     if (this.denominator <= maxAllowedDenominator) {
     if (this.denominator <= maxAllowedDenominator) {
       return this;
       return this;
@@ -244,11 +269,14 @@ export class Fraction {
   }
   }
 
 
   private simplify(): void {
   private simplify(): void {
+    // don't simplify in case of a GraceNote (need it in order to set the right symbol)
     if (this.numerator === 0) {
     if (this.numerator === 0) {
       this.denominator = 1;
       this.denominator = 1;
       return;
       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));
     const i: number = Fraction.greatestCommonDenominator(Math.abs(this.numerator), Math.abs(this.denominator));
 
 
     this.numerator /= i;
     this.numerator /= i;

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

@@ -14,7 +14,8 @@ export enum AccidentalEnum {
     FLAT = -1,
     FLAT = -1,
     NONE = 0,
     NONE = 0,
     SHARP = 1,
     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.
 // 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 {AccidentalEnum} from "../../Common/DataObjects/Pitch";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {GraphicalNote} from "./GraphicalNote";
 import {GraphicalNote} from "./GraphicalNote";
 import {Pitch} from "../../Common/DataObjects/Pitch";
 import {Pitch} from "../../Common/DataObjects/Pitch";
 import {NoteEnum} from "../../Common/DataObjects/Pitch";
 import {NoteEnum} from "../../Common/DataObjects/Pitch";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
+import { MusicSheetCalculator } from "./MusicSheetCalculator";
 
 
 /**
 /**
  * Compute the accidentals for notes according to the current key instruction
  * Compute the accidentals for notes according to the current key instruction
  */
  */
 export class AccidentalCalculator {
 export class AccidentalCalculator {
-    private symbolFactory: IGraphicalSymbolFactory;
     private keySignatureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private keySignatureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private currentAlterationsComparedToKeyInstructionList: number[] = [];
     private currentAlterationsComparedToKeyInstructionList: number[] = [];
     private currentInMeasureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private currentInMeasureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private activeKeyInstruction: KeyInstruction;
     private activeKeyInstruction: KeyInstruction;
 
 
-    constructor(symbolFactory: IGraphicalSymbolFactory) {
-        this.symbolFactory = symbolFactory;
-    }
-
     public get ActiveKeyInstruction(): KeyInstruction {
     public get ActiveKeyInstruction(): KeyInstruction {
         return this.activeKeyInstruction;
         return this.activeKeyInstruction;
     }
     }
@@ -77,7 +72,7 @@ export class AccidentalCalculator {
                 } else {
                 } else {
                     this.currentInMeasureNoteAlterationsDict.remove(pitchKey);
                     this.currentInMeasureNoteAlterationsDict.remove(pitchKey);
                 }
                 }
-                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                MusicSheetCalculator.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
             }
             }
         } else {
         } else {
             if (pitch.Accidental !== AccidentalEnum.NONE) {
             if (pitch.Accidental !== AccidentalEnum.NONE) {
@@ -85,11 +80,11 @@ export class AccidentalCalculator {
                     this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
                     this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
                 }
                 }
                 this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
                 this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
-                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                MusicSheetCalculator.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
             } else {
             } else {
                 if (isInCurrentAlterationsToKeyList) {
                 if (isInCurrentAlterationsToKeyList) {
                     this.currentAlterationsComparedToKeyInstructionList.splice(this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey), 1);
                     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 {PagePlacementEnum} from "./GraphicalMusicPage";
 //import {MusicSymbol} from "./MusicSymbol";
 //import {MusicSymbol} from "./MusicSymbol";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 
 export class EngravingRules {
 export class EngravingRules {
     private static rules: EngravingRules;
     private static rules: EngravingRules;
@@ -315,7 +315,7 @@ export class EngravingRules {
             //        + 7 * FontInfo.Info.getBoundingBox(MusicSymbol.SHARP).width;
             //        + 7 * FontInfo.Info.getBoundingBox(MusicSymbol.SHARP).width;
             //}
             //}
         } catch (ex) {
         } 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);
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, staffHeight);
     }
     }
 
 
+    // FIXME: This should actually be called LyricsEntry or be a function
     public get GetLyricsEntry(): LyricsEntry {
     public get GetLyricsEntry(): LyricsEntry {
         return this.lyricsEntry;
         return this.lyricsEntry;
     }
     }

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

@@ -35,6 +35,7 @@ export class GraphicalLyricWord {
     }
     }
 
 
     private initialize(): void {
     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++) {
         for (let i: number = 0; i < this.lyricWord.Syllables.length; i++) {
             this.graphicalLyricsEntries.push(undefined);
             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 {GraphicalNote} from "./GraphicalNote";
 import {Instrument} from "../Instrument";
 import {Instrument} from "../Instrument";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
-import {Note} from "../VoiceData/Note";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
@@ -513,7 +512,7 @@ export class GraphicalMusicSheet {
             if (closest === undefined) {
             if (closest === undefined) {
                 closest = note;
                 closest = note;
             } else {
             } else {
-                if (note.parentStaffEntry.relInMeasureTimestamp === undefined) {
+                if (note.parentVoiceEntry.parentStaffEntry.relInMeasureTimestamp === undefined) {
                     continue;
                     continue;
                 }
                 }
                 const deltaNew: number = this.CalculateDistance(note.PositionAndShape.AbsolutePosition, clickPosition);
                 const deltaNew: number = this.CalculateDistance(note.PositionAndShape.AbsolutePosition, clickPosition);
@@ -635,7 +634,7 @@ export class GraphicalMusicSheet {
         try {
         try {
             return this.GetClickableLabel(positionOnMusicSheet);
             return this.GetClickableLabel(positionOnMusicSheet);
         } catch (ex) {
         } catch (ex) {
-            Logging.log("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
+            log.info("GraphicalMusicSheet.tryGetClickableObject", "positionOnMusicSheet: " + positionOnMusicSheet, ex);
         }
         }
 
 
         return undefined;
         return undefined;
@@ -649,7 +648,7 @@ export class GraphicalMusicSheet {
             }
             }
             return entry.getAbsoluteTimestamp();
             return entry.getAbsoluteTimestamp();
         } catch (ex) {
         } catch (ex) {
-            Logging.log(
+            log.info(
                 "GraphicalMusicSheet.tryGetTimeStampFromPosition",
                 "GraphicalMusicSheet.tryGetTimeStampFromPosition",
                 "positionOnMusicSheet: " + positionOnMusicSheet, ex
                 "positionOnMusicSheet: " + positionOnMusicSheet, ex
             );
             );
@@ -681,7 +680,7 @@ export class GraphicalMusicSheet {
                 }
                 }
             }
             }
         } catch (ex) {
         } catch (ex) {
-            Logging.log("GraphicalMusicSheet.getStaffEntry", ex);
+            log.info("GraphicalMusicSheet.getStaffEntry", ex);
         }
         }
 
 
         return staffEntry;
         return staffEntry;
@@ -879,19 +878,6 @@ export class GraphicalMusicSheet {
         return graphicalMeasure.findGraphicalStaffEntryFromTimestamp(sourceStaffEntry.Timestamp);
         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 {
     private CalculateDistance(pt1: PointF2D, pt2: PointF2D): number {
         const deltaX: number = pt1.x - pt2.x;
         const deltaX: number = pt1.x - pt2.x;
         const deltaY: number = pt1.y - pt2.y;
         const deltaY: number = pt1.y - pt2.y;
@@ -900,24 +886,18 @@ export class GraphicalMusicSheet {
 
 
     /**
     /**
      * Return the longest StaffEntry duration from a GraphicalVerticalContainer.
      * Return the longest StaffEntry duration from a GraphicalVerticalContainer.
-     * @param index
+     * @param index the index of the vertical container
      * @returns {Fraction}
      * @returns {Fraction}
      */
      */
     private getLongestStaffEntryDuration(index: number): Fraction {
     private getLongestStaffEntryDuration(index: number): Fraction {
         let maxLength: Fraction = new Fraction(0, 1);
         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) {
             if (graphicalStaffEntry === undefined) {
                 continue;
                 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;
         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 {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {OctaveEnum} from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {Pitch} from "../../Common/DataObjects/Pitch";
 import {Pitch} from "../../Common/DataObjects/Pitch";
-import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
 import {GraphicalObject} from "./GraphicalObject";
 import {GraphicalObject} from "./GraphicalObject";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
 
 
 /**
 /**
  * The graphical counterpart of a [[Note]]
  * The graphical counterpart of a [[Note]]
  */
  */
 export class GraphicalNote extends GraphicalObject {
 export class GraphicalNote extends GraphicalObject {
-    constructor(note: Note, parent: GraphicalStaffEntry, graphicalNoteLength: Fraction = undefined) {
+    constructor(note: Note, parent: GraphicalVoiceEntry, graphicalNoteLength: Fraction = undefined) {
         super();
         super();
         this.sourceNote = note;
         this.sourceNote = note;
-        this.parentStaffEntry = parent;
+        this.parentVoiceEntry = parent;
         this.PositionAndShape = new BoundingBox(this, parent.PositionAndShape);
         this.PositionAndShape = new BoundingBox(this, parent.PositionAndShape);
         if (graphicalNoteLength !== undefined) {
         if (graphicalNoteLength !== undefined) {
             this.graphicalNoteLength = graphicalNoteLength;
             this.graphicalNoteLength = graphicalNoteLength;
         } else {
         } else {
-            if (note.NoteTie !== undefined) {
-                this.graphicalNoteLength = note.calculateNoteLengthWithoutTie();
-            } else {
-                this.graphicalNoteLength = note.Length;
-            }
+            this.graphicalNoteLength = note.Length;
         }
         }
 
 
         this.numberOfDots = this.calculateNumberOfNeededDots(this.graphicalNoteLength);
         this.numberOfDots = this.calculateNumberOfNeededDots(this.graphicalNoteLength);
@@ -33,19 +29,9 @@ export class GraphicalNote extends GraphicalObject {
 
 
     public sourceNote: Note;
     public sourceNote: Note;
     public graphicalNoteLength: Fraction;
     public graphicalNoteLength: Fraction;
-    public parentStaffEntry: GraphicalStaffEntry;
+    public parentVoiceEntry: GraphicalVoiceEntry;
     public numberOfDots: number;
     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 {
     public Transpose(keyInstruction: KeyInstruction, activeClef: ClefInstruction, halfTones: number, octaveEnum: OctaveEnum): Pitch {
         let transposedPitch: Pitch = this.sourceNote.Pitch;
         let transposedPitch: Pitch = this.sourceNote.Pitch;
         if (MusicSheetCalculator.transposeCalculator !== undefined) {
         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 {Slur} from "../VoiceData/Expressions/ContinuousExpressions/Slur";
 import {Voice} from "../VoiceData/Voice";
 import {Voice} from "../VoiceData/Voice";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
-import {LinkedVoice} from "../VoiceData/LinkedVoice";
 import {GraphicalTie} from "./GraphicalTie";
 import {GraphicalTie} from "./GraphicalTie";
 import {GraphicalObject} from "./GraphicalObject";
 import {GraphicalObject} from "./GraphicalObject";
 import {StaffMeasure} from "./StaffMeasure";
 import {StaffMeasure} from "./StaffMeasure";
@@ -16,6 +15,8 @@ import {GraphicalLyricEntry} from "./GraphicalLyricEntry";
 import {AbstractGraphicalInstruction} from "./AbstractGraphicalInstruction";
 import {AbstractGraphicalInstruction} from "./AbstractGraphicalInstruction";
 import {GraphicalStaffEntryLink} from "./GraphicalStaffEntryLink";
 import {GraphicalStaffEntryLink} from "./GraphicalStaffEntryLink";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {CollectionUtil} from "../../Util/CollectionUtil";
+import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
+import { MusicSheetCalculator } from "./MusicSheetCalculator";
 
 
 /**
 /**
  * The graphical counterpart of a [[SourceStaffEntry]].
  * 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) {
     constructor(parentMeasure: StaffMeasure, sourceStaffEntry: SourceStaffEntry = undefined, staffEntryParent: GraphicalStaffEntry = undefined) {
         super();
         super();
         this.parentMeasure = parentMeasure;
         this.parentMeasure = parentMeasure;
-        this.notes = [];
+        this.graphicalVoiceEntries = [];
         this.graceStaffEntriesBefore = [];
         this.graceStaffEntriesBefore = [];
         this.graceStaffEntriesAfter = [];
         this.graceStaffEntriesAfter = [];
         this.sourceStaffEntry = sourceStaffEntry;
         this.sourceStaffEntry = sourceStaffEntry;
@@ -47,7 +48,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     public relInMeasureTimestamp: Fraction;
     public relInMeasureTimestamp: Fraction;
     public sourceStaffEntry: SourceStaffEntry;
     public sourceStaffEntry: SourceStaffEntry;
     public parentMeasure: StaffMeasure;
     public parentMeasure: StaffMeasure;
-    public notes: GraphicalNote[][];
+    public graphicalVoiceEntries: GraphicalVoiceEntry[];
     public graceStaffEntriesBefore: GraphicalStaffEntry[];
     public graceStaffEntriesBefore: GraphicalStaffEntry[];
     public graceStaffEntriesAfter: GraphicalStaffEntry[];
     public graceStaffEntriesAfter: GraphicalStaffEntry[];
     public staffEntryParent: GraphicalStaffEntry;
     public staffEntryParent: GraphicalStaffEntry;
@@ -69,6 +70,10 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return this.lyricsEntries;
         return this.lyricsEntries;
     }
     }
 
 
+    public set LyricsEntries(value: GraphicalLyricEntry[]) {
+        this.lyricsEntries = value;
+    }
+
     /**
     /**
      * Calculate the absolute Timestamp.
      * Calculate the absolute Timestamp.
      * @returns {Fraction}
      * @returns {Fraction}
@@ -87,13 +92,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      * @returns {any}
      */
      */
     public findEndTieGraphicalNoteFromNote(tieNote: Note): GraphicalNote {
     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;
                 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;
                     return graphicalNote;
                 }
                 }
             }
             }
@@ -108,34 +113,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @returns {any}
      * @returns {any}
      */
      */
     public findEndTieGraphicalNoteFromNoteWithStartingSlur(tieNote: Note, slur: Slur): GraphicalNote {
     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;
                 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;
                     return graphicalNote;
                 }
                 }
             }
             }
@@ -144,10 +128,11 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
     }
 
 
     public findGraphicalNoteFromGraceNote(graceNote: Note): GraphicalNote {
     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) {
                 if (graphicalNote.sourceNote === graceNote) {
                     return graphicalNote;
                     return graphicalNote;
                 }
                 }
@@ -156,12 +141,13 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return undefined;
         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;
                     return graphicalNote;
                 }
                 }
             }
             }
@@ -170,46 +156,24 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     }
     }
 
 
     public getGraphicalNoteDurationFromVoice(voice: Voice): Fraction {
     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);
         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.
      * Find the [[StaffEntry]]'s [[GraphicalNote]]s that correspond to the given [[VoiceEntry]]'s [[Note]]s.
      * @param voiceEntry
      * @param voiceEntry
      * @returns {any}
      * @returns {any}
      */
      */
     public findVoiceEntryGraphicalNotes(voiceEntry: VoiceEntry): GraphicalNote[] {
     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;
         return undefined;
@@ -232,26 +196,14 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
         return false;
         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.
      * Return the [[StaffEntry]]'s Minimum NoteLength.
      * @returns {Fraction}
      * @returns {Fraction}
      */
      */
     public findStaffEntryMinNoteLength(): Fraction {
     public findStaffEntryMinNoteLength(): Fraction {
         let minLength: Fraction = new Fraction(Number.MAX_VALUE, 1);
         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;
                 const calNoteLen: Fraction = graphicalNote.graphicalNoteLength;
                 if (calNoteLen.lt(minLength) && calNoteLen.GetExpandedNumerator() > 0) {
                 if (calNoteLen.lt(minLength) && calNoteLen.GetExpandedNumerator() > 0) {
                     minLength = calNoteLen;
                     minLength = calNoteLen;
@@ -263,10 +215,8 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
 
 
     public findStaffEntryMaxNoteLength(): Fraction {
     public findStaffEntryMaxNoteLength(): Fraction {
         let maxLength: Fraction = new Fraction(0, 1);
         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;
                 const calNoteLen: Fraction = graphicalNote.graphicalNoteLength;
                 if (maxLength.lt(calNoteLen)  && calNoteLen.GetExpandedNumerator() > 0) {
                 if (maxLength.lt(calNoteLen)  && calNoteLen.GetExpandedNumerator() > 0) {
                     maxLength = calNoteLen;
                     maxLength = calNoteLen;
@@ -281,21 +231,17 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
      * @param voiceEntry
      * @param voiceEntry
      * @returns {GraphicalNote[]}
      * @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
      * @param graphicalNote
      * @returns {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 graphicalNotes
      * @param graphicalNote
      * @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 ||
         if (graphicalNotes.length === 0 ||
             graphicalNote.PositionAndShape.RelativePosition.y < CollectionUtil.last(graphicalNotes).PositionAndShape.RelativePosition.Y) {
             graphicalNote.PositionAndShape.RelativePosition.y < CollectionUtil.last(graphicalNotes).PositionAndShape.RelativePosition.Y) {
             graphicalNotes.push(graphicalNote);
             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[] = [];
             const notes: GraphicalNote[] = [];
             for (let idx: number = 0, len: number = this.graphicalLinkedStaffEntries.length; idx < len; ++idx) {
             for (let idx: number = 0, len: number = this.graphicalLinkedStaffEntries.length; idx < len; ++idx) {
                 const graphicalLinkedStaffEntry: GraphicalStaffEntry = this.graphicalLinkedStaffEntries[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
                         if (graphicalNote.sourceNote.ParentStaffEntry.Link !== undefined
                             && graphicalNote.sourceNote.ParentVoiceEntry === this.staffEntryLink.GetVoiceEntry) {
                             && graphicalNote.sourceNote.ParentVoiceEntry === this.staffEntryLink.GetVoiceEntry) {
                             notes.push(graphicalNote);
                             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[];
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 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) {
         for (const measure of staffLine.Measures) {
             this.drawMeasure(measure);
             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 {
     // 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;
         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[] {
     public get StaffLines(): StaffLine[] {
         return this.staffLines;
         return this.staffLines;
     }
     }
@@ -242,14 +247,18 @@ export abstract class MusicSystem extends GraphicalObject {
                 continue;
                 continue;
             }
             }
             let firstStaffLine: StaffLine = undefined;
             let firstStaffLine: StaffLine = undefined;
+            let lastStaffLine: StaffLine = undefined;
             for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
             for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
                 const staffLine: StaffLine = this.staffLines[idx2];
                 const staffLine: StaffLine = this.staffLines[idx2];
                 if (staffLine.ParentStaff === instrument1.Staves[0]) {
                 if (staffLine.ParentStaff === instrument1.Staves[0]) {
                     firstStaffLine = staffLine;
                     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) {
             if (instrumentGroup.InstrumentalGroups.length < 1) {
                 continue;
                 continue;
@@ -274,11 +283,9 @@ export abstract class MusicSystem extends GraphicalObject {
                 );
                 );
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 this.labels.setValue(graphicalLabel, instrument);
                 this.labels.setValue(graphicalLabel, instrument);
-                //graphicalLabel.PositionAndShape.Parent = this.PositionAndShape;
-
                 // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
                 // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
                 // Y-Position will be calculated after the y-Spacing
                 // 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)
             // 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 {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
 import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
-import {IGraphicalSymbolFactory} from "../Interfaces/IGraphicalSymbolFactory";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {CollectionUtil} from "../../Util/CollectionUtil";
 import {CollectionUtil} from "../../Util/CollectionUtil";
@@ -41,16 +40,13 @@ export class MusicSystemBuilder {
     private activeClefs: ClefInstruction[];
     private activeClefs: ClefInstruction[];
     private globalSystemIndex: number = 0;
     private globalSystemIndex: number = 0;
     private leadSheet: boolean = false;
     private leadSheet: boolean = false;
-    private symbolFactory: IGraphicalSymbolFactory;
 
 
     public initialize(
     public initialize(
-        graphicalMusicSheet: GraphicalMusicSheet, measureList: StaffMeasure[][], numberOfStaffLines: number, symbolFactory: IGraphicalSymbolFactory
-    ): void {
+        graphicalMusicSheet: GraphicalMusicSheet, measureList: StaffMeasure[][], numberOfStaffLines: number): void {
         this.leadSheet = graphicalMusicSheet.LeadSheet;
         this.leadSheet = graphicalMusicSheet.LeadSheet;
         this.graphicalMusicSheet = graphicalMusicSheet;
         this.graphicalMusicSheet = graphicalMusicSheet;
         this.rules = this.graphicalMusicSheet.ParentMusicSheet.rules;
         this.rules = this.graphicalMusicSheet.ParentMusicSheet.rules;
         this.measureList = measureList;
         this.measureList = measureList;
-        this.symbolFactory = symbolFactory;
         this.currentMusicPage = this.createMusicPage();
         this.currentMusicPage = this.createMusicPage();
         this.currentPageHeight = 0.0;
         this.currentPageHeight = 0.0;
         this.numberOfVisibleStaffLines = numberOfStaffLines;
         this.numberOfVisibleStaffLines = numberOfStaffLines;
@@ -257,7 +253,7 @@ export class MusicSystemBuilder {
      * @returns {MusicSystem}
      * @returns {MusicSystem}
      */
      */
     private initMusicSystem(): 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);
         this.currentMusicPage.MusicSystems.push(musicSystem);
         return musicSystem;
         return musicSystem;
     }
     }
@@ -329,7 +325,7 @@ export class MusicSystemBuilder {
      */
      */
     private addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
     private addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
         if (musicSystem !== undefined) {
         if (musicSystem !== undefined) {
-            const staffLine: StaffLine = this.symbolFactory.createStaffLine(musicSystem, staff);
+            const staffLine: StaffLine = MusicSheetCalculator.symbolFactory.createStaffLine(musicSystem, staff);
             musicSystem.StaffLines.push(staffLine);
             musicSystem.StaffLines.push(staffLine);
             const boundingBox: BoundingBox = staffLine.PositionAndShape;
             const boundingBox: BoundingBox = staffLine.PositionAndShape;
             const relativePosition: PointF2D = new PointF2D();
             const relativePosition: PointF2D = new PointF2D();
@@ -613,7 +609,7 @@ export class MusicSystemBuilder {
     private addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
     private addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
         const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
         const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
         const measures: StaffMeasure[] = [];
         const measures: StaffMeasure[] = [];
-        const measure: StaffMeasure = this.symbolFactory.createExtraStaffMeasure(currentSystem.StaffLines[visStaffIdx]);
+        const measure: StaffMeasure = MusicSheetCalculator.symbolFactory.createExtraStaffMeasure(currentSystem.StaffLines[visStaffIdx]);
         measures.push(measure);
         measures.push(measure);
         if (keyInstruction !== undefined) {
         if (keyInstruction !== undefined) {
             measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
             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;
         const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
         measure.PositionAndShape.BorderRight = width;
         measure.PositionAndShape.BorderRight = width;
         currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
         currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
-        measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
         return width;
         return width;
     }
     }
 
 

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

@@ -8,6 +8,7 @@ import {StaffMeasure} from "./StaffMeasure";
 import {MusicSystem} from "./MusicSystem";
 import {MusicSystem} from "./MusicSystem";
 import {StaffLineActivitySymbol} from "./StaffLineActivitySymbol";
 import {StaffLineActivitySymbol} from "./StaffLineActivitySymbol";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
+import {GraphicalLabel} from "./GraphicalLabel";
 
 
 /**
 /**
  * A StaffLine contains the [[Measure]]s in one line of the music sheet
  * 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 parentStaff: Staff;
     protected skyLine: number[];
     protected skyLine: number[];
     protected bottomLine: number[];
     protected bottomLine: number[];
+    protected lyricLines: GraphicalLine[] = [];
+    protected lyricsDashes: GraphicalLabel[] = [];
 
 
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super();
         super();
@@ -44,6 +47,27 @@ export abstract class StaffLine extends GraphicalObject {
         this.staffLines = value;
         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 {
     public get ParentMusicSystem(): MusicSystem {
         return this.parentMusicSystem;
         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 {Fraction} from "../../Common/DataObjects/Fraction";
 import {Voice} from "../VoiceData/Voice";
 import {Voice} from "../VoiceData/Voice";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
 import {VoiceEntry} from "../VoiceData/VoiceEntry";
-import {GraphicalNote} from "./GraphicalNote";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {SystemLinesEnum} from "./SystemLinesEnum";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
 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) {
         for (let idx: number = 0, len: number = voices.length; idx < len; ++idx) {
             const voice: Voice = voices[idx];
             const voice: Voice = voices[idx];
             const voiceDuration: Fraction = new Fraction(0, 1);
             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 {FontStyles} from "../../../Common/Enums/FontStyles";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {OutlineAndFillStyleEnum, OUTLINE_AND_FILL_STYLE_DICT} from "../DrawingEnums";
 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
  * Helper class, which contains static methods which actually convert
@@ -135,47 +138,93 @@ export class VexFlowConverter {
         return acc;
         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}
      * @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[] = [];
         let keys: string[] = [];
         const accidentals: 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 duration: string = VexFlowConverter.duration(frac, isTuplet);
         let vfClefType: string = undefined;
         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) {
         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]);
             keys.push(pitch[0]);
             accidentals.push(pitch[1]);
             accidentals.push(pitch[1]);
             if (!vfClefType) {
             if (!vfClefType) {
                 const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
                 const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
                 vfClefType = vfClef.type;
                 vfClefType = vfClef.type;
             }
             }
-            if (numDots < note.numberOfDots) {
-                numDots = note.numberOfDots;
-            }
         }
         }
+
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             duration += "d";
             duration += "d";
         }
         }
 
 
         const vfnote: Vex.Flow.StaveNote = new Vex.Flow.StaveNote({
         const vfnote: Vex.Flow.StaveNote = new Vex.Flow.StaveNote({
+            align_center: alignCenter,
             auto_stem: true,
             auto_stem: true,
             clef: vfClefType,
             clef: vfClefType,
             duration: duration,
             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) {
         for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
             (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
             (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
             if (accidentals[i]) {
             if (accidentals[i]) {
@@ -185,10 +234,78 @@ export class VexFlowConverter {
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             vfnote.addDotToAll();
             vfnote.addDotToAll();
         }
         }
-
         return vfnote;
         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.
      * 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"
         // Make sure size is either "default" or "small"
         if (size !== "default" && size !== "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";
             size = "default";
         }
         }
 
 
@@ -226,7 +343,7 @@ export class VexFlowConverter {
                         break;
                         break;
                     default:
                     default:
                         type = "treble";
                         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;
                 break;
 
 
@@ -244,7 +361,7 @@ export class VexFlowConverter {
                       break;
                       break;
                   default:
                   default:
                       type = "bass";
                       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;
                 break;
 
 
@@ -265,7 +382,7 @@ export class VexFlowConverter {
                       break;
                       break;
                   default:
                   default:
                       type = "alto";
                       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;
                 break;
 
 
@@ -281,14 +398,11 @@ export class VexFlowConverter {
             default:
             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 };
         return { type, size, annotation };
     }
     }
@@ -345,21 +459,23 @@ export class VexFlowConverter {
      * @param lineType
      * @param lineType
      * @returns {any}
      * @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) {
         switch (lineType) {
             case SystemLinesEnum.SingleThin:
             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:
             case SystemLinesEnum.DoubleThin:
                 return Vex.Flow.StaveConnector.type.DOUBLE;
                 return Vex.Flow.StaveConnector.type.DOUBLE;
             case SystemLinesEnum.ThinBold:
             case SystemLinesEnum.ThinBold:
-                return Vex.Flow.StaveConnector.type.SINGLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.BoldThinDots:
             case SystemLinesEnum.BoldThinDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
             case SystemLinesEnum.DotsThinBold:
             case SystemLinesEnum.DotsThinBold:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.DotsBoldBoldDots:
             case SystemLinesEnum.DotsBoldBoldDots:
-                return Vex.Flow.StaveConnector.type.DOUBLE;
+                return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
             case SystemLinesEnum.None:
             case SystemLinesEnum.None:
                 return Vex.Flow.StaveConnector.type.NONE;
                 return Vex.Flow.StaveConnector.type.NONE;
             default:
             default:
@@ -434,3 +550,29 @@ export class VexFlowConverter {
         return ret;
         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 Vex = require("vexflow");
 import {GraphicalNote} from "../GraphicalNote";
 import {GraphicalNote} from "../GraphicalNote";
 import {Note} from "../../VoiceData/Note";
 import {Note} from "../../VoiceData/Note";
-import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {VexFlowConverter} from "./VexFlowConverter";
 import {VexFlowConverter} from "./VexFlowConverter";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 
 
 /**
 /**
  * The VexFlow version of a [[GraphicalNote]].
  * The VexFlow version of a [[GraphicalNote]].
  */
  */
 export class VexFlowGraphicalNote extends 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) {
                 octaveShift: OctaveEnum = OctaveEnum.NONE,  graphicalNoteLength: Fraction = undefined) {
         super(note, parent, graphicalNoteLength);
         super(note, parent, graphicalNoteLength);
         this.clef = activeClef;
         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 {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {GraphicalNote} from "../GraphicalNote";
 import {GraphicalNote} from "../GraphicalNote";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
 import {Pitch} from "../../../Common/DataObjects/Pitch";
-import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {GraphicalChordSymbolContainer} from "../GraphicalChordSymbolContainer";
 import {GraphicalChordSymbolContainer} from "../GraphicalChordSymbolContainer";
 import {GraphicalLabel} from "../GraphicalLabel";
 import {GraphicalLabel} from "../GraphicalLabel";
 import {EngravingRules} from "../EngravingRules";
 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 {
 export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
     /**
     /**
@@ -86,6 +89,10 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
         return new VexFlowStaffEntry(<VexFlowMeasure>measure, undefined, <VexFlowStaffEntry>staffEntryParent);
         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.
      * Create a Graphical Note for given note and clef and as part of graphicalStaffEntry.
      * @param note
      * @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
      * @param octaveShift   The currently active octave transposition enum, needed for positioning the note vertically
      * @returns {GraphicalNote}
      * @returns {GraphicalNote}
      */
      */
-    public createNote(note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+    public createNote(note: Note, graphicalVoiceEntry: GraphicalVoiceEntry,
                       activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE,  graphicalNoteLength: Fraction = undefined): GraphicalNote {
                       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.
      * Create a Graphical Grace Note (smaller head, stem...) for given note and clef and as part of graphicalStaffEntry.
      * @param note
      * @param note
      * @param numberOfDots
      * @param numberOfDots
-     * @param graphicalStaffEntry
+     * @param graphicalVoiceEntry
      * @param activeClef
      * @param activeClef
      * @param octaveShift
      * @param octaveShift
      * @returns {GraphicalNote}
      * @returns {GraphicalNote}
      */
      */
-    public createGraceNote(note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+    public createGraceNote(note: Note, graphicalVoiceEntry: GraphicalVoiceEntry,
                            activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE): GraphicalNote {
                            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;
         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.
      * Adds a clef change within a measure before the given staff entry.
@@ -185,4 +175,14 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
       graphicalChordSymbolContainer.PositionAndShape.calculateBoundingBox();
       graphicalChordSymbolContainer.PositionAndShape.calculateBoundingBox();
       graphicalStaffEntry.graphicalChordContainer = graphicalChordSymbolContainer;
       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 {VexFlowStaffLine} from "./VexFlowStaffLine";
 import { BoundingBox } from "../BoundingBox";
 import { BoundingBox } from "../BoundingBox";
 import { VexFlowMeasure } from "./VexFlowMeasure";
 import { VexFlowMeasure } from "./VexFlowMeasure";
+import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 
 
 /**
 /**
  * Class that defines a instrument bracket at the beginning of a line.
  * Class that defines a instrument bracket at the beginning of a line.
  */
  */
 export class VexFlowInstrumentBracket extends GraphicalObject {
 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();
         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 firstVexMeasure: VexFlowMeasure = firstVexFlowStaffLine.Measures[0] as VexFlowMeasure;
         const lastVexMeasure: VexFlowMeasure = lastVexFlowStaffLine.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
      * @param ctx Render Vexflow context
      */
      */
     public draw(ctx: Vex.Flow.RenderContext): void {
     public draw(ctx: Vex.Flow.RenderContext): void {
+        // Draw vexflow brace. This sets the positions inside the connector.
         this.vexflowConnector.setContext(ctx).draw();
         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
      * Adds a connector between two staves
      *
      *
@@ -35,8 +46,9 @@ export class VexFlowInstrumentBracket extends GraphicalObject {
      * @param {Stave} stave2: Second stave
      * @param {Stave} stave2: Second stave
      * @param {Flow.StaveConnector.type} type: Type of connector
      * @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)
         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 {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
 import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
 import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
 import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
-import {VexFlowConverter} from "./VexFlowConverter";
+import {VexFlowConverter, VexFlowRepetitionType, VexFlowBarlineType} from "./VexFlowConverter";
 import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
 import {Beam} from "../../VoiceData/Beam";
 import {Beam} from "../../VoiceData/Beam";
 import {GraphicalNote} from "../GraphicalNote";
 import {GraphicalNote} from "../GraphicalNote";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import StaveConnector = Vex.Flow.StaveConnector;
 import StaveConnector = Vex.Flow.StaveConnector;
 import StaveNote = Vex.Flow.StaveNote;
 import StaveNote = Vex.Flow.StaveNote;
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {Tuplet} from "../../VoiceData/Tuplet";
 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 {
 export class VexFlowMeasure extends StaffMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -33,17 +40,19 @@ export class VexFlowMeasure extends StaffMeasure {
     public formatVoices: (width: number) => void;
     public formatVoices: (width: number) => void;
     // The VexFlow Ties in the measure
     // The VexFlow Ties in the measure
     public vfTies: Vex.Flow.StaveTie[] = [];
     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;
     private stave: Vex.Flow.Stave;
     // VexFlow StaveConnectors (vertical lines)
     // VexFlow StaveConnectors (vertical lines)
     private connectors: Vex.Flow.StaveConnector[] = [];
     private connectors: Vex.Flow.StaveConnector[] = [];
     // Intermediate object to construct beams
     // Intermediate object to construct beams
-    private beams: { [voiceID: number]: [Beam, VexFlowStaffEntry[]][]; } = {};
+    private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]][]; } = {};
     // VexFlow Beams
     // VexFlow Beams
     private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
     private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
     // Intermediate object to construct tuplets
     // Intermediate object to construct tuplets
-    private tuplets: { [voiceID: number]: [Tuplet, VexFlowStaffEntry[]][]; } = {};
+    private tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
     // VexFlow Tuplets
     // VexFlow Tuplets
     private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
     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
      * @param line
-     * @returns {SystemLinesEnum} the x-width
+     * @returns the x-width in osmd units
      */
      */
     public getLineWidth(line: SystemLinesEnum): number {
     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:
             default:
                 return 0;
                 return 0;
         }
         }
@@ -146,6 +162,103 @@ export class VexFlowMeasure extends StaffMeasure {
         this.updateInstructionWidth();
         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.
      * Sets the overall x-width of the measure.
      * @param width
      * @param width
@@ -165,32 +278,15 @@ export class VexFlowMeasure extends StaffMeasure {
      * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
      * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
      */
      */
     public layoutSymbols(): void {
     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
      * Draw this measure on a VexFlow CanvasContext
      * @param ctx
      * @param ctx
      */
      */
     public draw(ctx: Vex.Flow.RenderContext): void {
     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
         // Draw stave lines
         this.stave.setContext(ctx).draw();
         this.stave.setContext(ctx).draw();
         // Draw all voices
         // Draw all voices
@@ -226,9 +322,120 @@ export class VexFlowMeasure extends StaffMeasure {
         for (const connector of this.connectors) {
         for (const connector of this.connectors) {
             connector.setContext(ctx).draw();
             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 {
     public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
         const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
         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) {
         if (beams === undefined) {
             beams = this.beams[voiceID] = [];
             beams = this.beams[voiceID] = [];
         }
         }
-        let data: [Beam, VexFlowStaffEntry[]];
+        let data: [Beam, VexFlowVoiceEntry[]];
         for (const mybeam of beams) {
         for (const mybeam of beams) {
             if (mybeam[0] === beam) {
             if (mybeam[0] === beam) {
                 data = mybeam;
                 data = mybeam;
@@ -252,7 +459,7 @@ export class VexFlowMeasure extends StaffMeasure {
             data = [beam, []];
             data = [beam, []];
             beams.push(data);
             beams.push(data);
         }
         }
-        const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
+        const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
         if (data[1].indexOf(parent) < 0) {
         if (data[1].indexOf(parent) < 0) {
             data[1].push(parent);
             data[1].push(parent);
         }
         }
@@ -261,11 +468,11 @@ export class VexFlowMeasure extends StaffMeasure {
     public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
     public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
         const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
         const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
         tuplet = graphicalNote.sourceNote.NoteTuplet;
         tuplet = graphicalNote.sourceNote.NoteTuplet;
-        let tuplets: [Tuplet, VexFlowStaffEntry[]][] = this.tuplets[voiceID];
+        let tuplets: [Tuplet, VexFlowVoiceEntry[]][] = this.tuplets[voiceID];
         if (tuplets === undefined) {
         if (tuplets === undefined) {
             tuplets = this.tuplets[voiceID] = [];
             tuplets = this.tuplets[voiceID] = [];
         }
         }
-        let currentTupletBuilder: [Tuplet, VexFlowStaffEntry[]];
+        let currentTupletBuilder: [Tuplet, VexFlowVoiceEntry[]];
         for (const t of tuplets) {
         for (const t of tuplets) {
             if (t[0] === tuplet) {
             if (t[0] === tuplet) {
                 currentTupletBuilder = t;
                 currentTupletBuilder = t;
@@ -275,7 +482,7 @@ export class VexFlowMeasure extends StaffMeasure {
             currentTupletBuilder = [tuplet, []];
             currentTupletBuilder = [tuplet, []];
             tuplets.push(currentTupletBuilder);
             tuplets.push(currentTupletBuilder);
         }
         }
-        const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
+        const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
         if (currentTupletBuilder[1].indexOf(parent) < 0) {
         if (currentTupletBuilder[1].indexOf(parent) < 0) {
             currentTupletBuilder[1].push(parent);
             currentTupletBuilder[1].push(parent);
         }
         }
@@ -297,20 +504,31 @@ export class VexFlowMeasure extends StaffMeasure {
                 }
                 }
                 for (const beam of this.beams[voiceID]) {
                 for (const beam of this.beams[voiceID]) {
                     const notes: Vex.Flow.StaveNote[] = [];
                     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) {
                         if (note !== undefined) {
                           notes.push(note);
                           notes.push(note);
                         }
                         }
                     }
                     }
                     if (notes.length > 1) {
                     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:
                         // just a test for coloring the notes:
                         // for (let note of notes) {
                         // for (let note of notes) {
                         //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
                         //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
                         // }
                         // }
                     } else {
                     } 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]) {
                 for (const tupletBuilder of this.tuplets[voiceID]) {
                     const tupletStaveNotes: Vex.Flow.StaveNote[] = [];
                     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) {
                     if (tupletStaveNotes.length > 1) {
                       const notesOccupied: number = 2;
                       const notesOccupied: number = 2;
@@ -345,7 +563,7 @@ export class VexFlowMeasure extends StaffMeasure {
                                                             num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
                                                             num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
                                                           }));
                                                           }));
                     } else {
                     } 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 {
     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.finalizeBeams();
         this.finalizeTuplets();
         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) {
         for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
             const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
             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;
         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
      * After re-running the formatting on the VexFlow Stave, update the
      * space needed by Instructions (in VexFlow: StaveModifiers)
      * space needed by Instructions (in VexFlow: StaveModifiers)
      */
      */
     private updateInstructionWidth(): void {
     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 {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {OctaveEnum} from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
-import {LyricsEntry} from "../../VoiceData/Lyrics/LyricsEntry";
 import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
 import {LyricWord} from "../../VoiceData/Lyrics/LyricsWord";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
 import {ArticulationEnum} from "../../VoiceData/VoiceEntry";
 import {Tuplet} from "../../VoiceData/Tuplet";
 import {Tuplet} from "../../VoiceData/Tuplet";
-import Dictionary from "typescript-collections/dist/lib/Dictionary";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
 import {VexFlowTextMeasurer} from "./VexFlowTextMeasurer";
 
 
 import Vex = require("vexflow");
 import Vex = require("vexflow");
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 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 {
 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 staffMeasures of this.graphicalMusicSheet.MeasureList) {
             for (const staffMeasure of staffMeasures) {
             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 {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {VexFlowBackend} from "./VexFlowBackend";
 import {VexFlowBackend} from "./VexFlowBackend";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
 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
  * 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.PositionAndShape.AbsolutePosition.y * unitInPixels
         );
         );
         measure.draw(this.backend.getContext());
         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
         // Draw the StaffEntries
         for (const staffEntry of measure.staffEntries) {
         for (const staffEntry of measure.staffEntries) {
@@ -82,16 +94,30 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
         if (staffEntry.graphicalChordContainer !== undefined) {
         if (staffEntry.graphicalChordContainer !== undefined) {
             this.drawLabel(staffEntry.graphicalChordContainer.GetGraphicalLabel, <number>GraphicalLayers.Notes);
             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 {
     protected drawInstrumentBrace(brace: GraphicalObject, system: MusicSystem): void {
         // Draw InstrumentBrackets at beginning of line
         // Draw InstrumentBrackets at beginning of line
-        const vexBrace: VexFlowInstrumentBracket = (brace as VexFlowInstrumentBracket);
+        const vexBrace: VexFlowInstrumentBrace = (brace as VexFlowInstrumentBrace);
         vexBrace.draw(this.backend.getContext());
         vexBrace.draw(this.backend.getContext());
     }
     }
 
 
     protected drawGroupBracket(bracket: GraphicalObject, system: MusicSystem): void {
     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 {StaffLine} from "../StaffLine";
 import {EngravingRules} from "../EngravingRules";
 import {EngravingRules} from "../EngravingRules";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
 import { VexFlowInstrumentBracket } from "./VexFlowInstrumentBracket";
+import { VexFlowInstrumentBrace } from "./VexFlowInstrumentBrace";
 
 
 export class VexFlowMusicSystem extends MusicSystem {
 export class VexFlowMusicSystem extends MusicSystem {
     constructor(parent: GraphicalMusicPage, id: number) {
     constructor(parent: GraphicalMusicPage, id: number) {
@@ -39,9 +40,13 @@ export class VexFlowMusicSystem extends MusicSystem {
      */
      */
     protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
     protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
                                musicSystem: MusicSystem, topMeasure: StaffMeasure, bottomMeasure: StaffMeasure = undefined): SystemLine {
                                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) {
         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);
         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.
         // You could write this in one line but the linter doesn't let me.
         const firstVexStaff: VexFlowStaffLine = (firstStaffLine as VexFlowStaffLine);
         const firstVexStaff: VexFlowStaffLine = (firstStaffLine as VexFlowStaffLine);
         const lastVexStaff: VexFlowStaffLine = (lastStaffLine 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);
         this.InstrumentBrackets.push(vexFlowBracket);
         return;
         return;
     }
     }
@@ -67,10 +72,18 @@ export class VexFlowMusicSystem extends MusicSystem {
      * The recursion depth informs about the current depth level (needed for positioning)
      * The recursion depth informs about the current depth level (needed for positioning)
      * @param firstStaffLine the upper staff line of the bracket to create
      * @param firstStaffLine the upper staff line of the bracket to create
      * @param lastStaffLine the lower staff line of the bracket to create
      * @param lastStaffLine the lower staff line of the bracket to create
-     * @param staffHeight
      * @param recursionDepth
      * @param recursionDepth
      */
      */
     protected createGroupBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine, recursionDepth: number): void {
     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;
         return;
     }
     }
 }
 }

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

@@ -1,36 +1,48 @@
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {VexFlowMeasure} from "./VexFlowMeasure";
 import {SourceStaffEntry} from "../../VoiceData/SourceStaffEntry";
 import {SourceStaffEntry} from "../../VoiceData/SourceStaffEntry";
-import {GraphicalNote} from "../GraphicalNote";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
+import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
         super(measure, sourceStaffEntry, staffEntryParent);
         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 {
 export class VexFlowStaffLine extends StaffLine {
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
     constructor(parentSystem: MusicSystem, parentStaff: Staff) {
         super(parentSystem, parentStaff);
         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 {
     public SetStaffAudible(staffId: number, audible: boolean): void {
         const staff: Staff = this.staves[staffId - 1];
         const staff: Staff = this.staves[staffId - 1];
         staff.audible = audible;
         staff.audible = audible;
+        // hack for now:
+        // activate all voices needed so that the staff notes will be played
         if (audible) {
         if (audible) {
             for (let idx: number = 0, len: number = staff.Voices.length; idx < len; ++idx) {
             for (let idx: number = 0, len: number = staff.Voices.length; idx < len; ++idx) {
                 const v: Voice = staff.Voices[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 {Staff} from "../VoiceData/Staff";
 import {StaffLine} from "../Graphical/StaffLine";
 import {StaffLine} from "../Graphical/StaffLine";
 import {StaffMeasure} from "../Graphical/StaffMeasure";
 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 {
 export interface IGraphicalSymbolFactory {
 
 
@@ -28,15 +30,18 @@ export interface IGraphicalSymbolFactory {
 
 
     createGraceStaffEntry(staffEntryParent: GraphicalStaffEntry, measure: StaffMeasure): GraphicalStaffEntry;
     createGraceStaffEntry(staffEntryParent: GraphicalStaffEntry, measure: StaffMeasure): GraphicalStaffEntry;
 
 
+    createVoiceEntry(parentVoiceEntry: VoiceEntry, parentStaffEntry: GraphicalStaffEntry): GraphicalVoiceEntry;
+
     createNote(
     createNote(
-        note: Note, graphicalStaffEntry: GraphicalStaffEntry,
+        note: Note,
+        graphicalVoiceEntry: GraphicalVoiceEntry,
         activeClef: ClefInstruction,
         activeClef: ClefInstruction,
         octaveShift: OctaveEnum,
         octaveShift: OctaveEnum,
         graphicalNoteLength: Fraction): GraphicalNote;
         graphicalNoteLength: Fraction): GraphicalNote;
 
 
     createGraceNote(
     createGraceNote(
         note: Note,
         note: Note,
-        graphicalStaffEntry: GraphicalStaffEntry,
+        graphicalVoiceEntry: GraphicalVoiceEntry,
         activeClef: ClefInstruction,
         activeClef: ClefInstruction,
         octaveShift: OctaveEnum): GraphicalNote;
         octaveShift: OctaveEnum): GraphicalNote;
 
 
@@ -48,6 +53,7 @@ export interface IGraphicalSymbolFactory {
         technicalInstruction: TechnicalInstruction,
         technicalInstruction: TechnicalInstruction,
         graphicalStaffEntry: GraphicalStaffEntry): void;
         graphicalStaffEntry: GraphicalStaffEntry): void;
 
 
+
     createInStaffClef(graphicalStaffEntry: GraphicalStaffEntry, clefInstruction: ClefInstruction): void;
     createInStaffClef(graphicalStaffEntry: GraphicalStaffEntry, clefInstruction: ClefInstruction): void;
 
 
     createChordSymbol(
     createChordSymbol(

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

@@ -13,9 +13,17 @@ export class MusicPartManager /*implements ISelectionListener*/ {
     private musicSheet: MusicSheet;
     private musicSheet: MusicSheet;
     private sheetStart: Fraction;
     private sheetStart: Fraction;
     private sheetEnd: Fraction;
     private sheetEnd: Fraction;
+
+    /**
+     * This method is called from CoreContainer when the user changes a Repetitions's userNumberOfRepetitions.
+     */
     public reInit(): void {
     public reInit(): void {
         this.init();
         this.init();
     }
     }
+
+    /**
+     * Main initialize method for MusicPartManager.
+     */
     public init(): void {
     public init(): void {
         this.parts = this.musicSheet.Repetitions.slice();
         this.parts = this.musicSheet.Repetitions.slice();
         this.sheetStart = this.musicSheet.SelectionStart = new Fraction(0, 1);
         this.sheetStart = this.musicSheet.SelectionStart = new Fraction(0, 1);
@@ -73,6 +81,8 @@ export class MusicPartManager /*implements ISelectionListener*/ {
         while (!iterator.EndReached) {
         while (!iterator.EndReached) {
             if (iterator.JumpOccurred || currentRepetition !== iterator.CurrentRepetition) {
             if (iterator.JumpOccurred || currentRepetition !== iterator.CurrentRepetition) {
                 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) {
                 if (iterator.backJumpOccurred) {
                     const jumpRep: Repetition = iterator.JumpResponsibleRepetition;
                     const jumpRep: Repetition = iterator.JumpResponsibleRepetition;
                     curTimestampTransform.nextBackJump = iterator.CurrentEnrolledTimestamp;
                     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 {InstantaniousDynamicExpression} from "../VoiceData/Expressions/InstantaniousDynamicExpression";
 import {MultiTempoExpression} from "../VoiceData/Expressions/MultiTempoExpression";
 import {MultiTempoExpression} from "../VoiceData/Expressions/MultiTempoExpression";
 import {AbstractExpression} from "../VoiceData/Expressions/AbstractExpression";
 import {AbstractExpression} from "../VoiceData/Expressions/AbstractExpression";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 
 export class MusicPartManagerIterator {
 export class MusicPartManagerIterator {
     constructor(manager: MusicPartManager, startTimestamp?: Fraction, endTimestamp?: Fraction) {
     constructor(manager: MusicPartManager, startTimestamp?: Fraction, endTimestamp?: Fraction) {
@@ -47,7 +47,7 @@ export class MusicPartManagerIterator {
             }
             }
             this.currentTempoChangingExpression = this.activeTempoExpression;
             this.currentTempoChangingExpression = this.activeTempoExpression;
         } catch (err) {
         } catch (err) {
-            Logging.log("MusicPartManagerIterator: " + err);
+            log.info("MusicPartManagerIterator: " + err);
         }
         }
 
 
     }
     }
@@ -128,6 +128,10 @@ export class MusicPartManagerIterator {
     public get JumpResponsibleRepetition(): Repetition {
     public get JumpResponsibleRepetition(): Repetition {
         return this.jumpResponsibleRepetition;
         return this.jumpResponsibleRepetition;
     }
     }
+
+    /**
+     * Creates a clone of this iterator which has the same actual position.
+     */
     public clone(): MusicPartManagerIterator {
     public clone(): MusicPartManagerIterator {
         const ret: MusicPartManagerIterator = new MusicPartManagerIterator(this.manager);
         const ret: MusicPartManagerIterator = new MusicPartManagerIterator(this.manager);
         ret.currentVoiceEntryIndex = this.currentVoiceEntryIndex;
         ret.currentVoiceEntryIndex = this.currentVoiceEntryIndex;
@@ -139,6 +143,11 @@ export class MusicPartManagerIterator {
         return ret;
         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[] {
     public CurrentVisibleVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
         if (this.currentVoiceEntries === undefined) {
@@ -159,6 +168,11 @@ export class MusicPartManagerIterator {
         return voiceEntries;
         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[] {
     public CurrentAudibleVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
         if (this.currentVoiceEntries === undefined) {
@@ -179,10 +193,20 @@ export class MusicPartManagerIterator {
         return voiceEntries;
         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[] {
     public getCurrentDynamicChangingExpressions(): DynamicsContainer[] {
         return this.currentDynamicChangingExpressions;
         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[] {
     public CurrentScoreFollowingVoiceEntries(instrument?: Instrument): VoiceEntry[] {
         const voiceEntries: VoiceEntry[] = [];
         const voiceEntries: VoiceEntry[] = [];
         if (this.currentVoiceEntries === undefined) {
         if (this.currentVoiceEntries === undefined) {
@@ -471,6 +495,7 @@ export class MusicPartManagerIterator {
             this.handleRepetitionsAtMeasureBegin();
             this.handleRepetitionsAtMeasureBegin();
             this.activateCurrentRhythmInstructions();
             this.activateCurrentRhythmInstructions();
         }
         }
+        // everything fine, no complications
         if (this.currentVoiceEntryIndex >= 0 && this.currentVoiceEntryIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length) {
         if (this.currentVoiceEntryIndex >= 0 && this.currentVoiceEntryIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length) {
             const currentContainer: VerticalSourceStaffEntryContainer = this.currentMeasure.VerticalSourceStaffEntryContainers[this.currentVoiceEntryIndex];
             const currentContainer: VerticalSourceStaffEntryContainer = this.currentMeasure.VerticalSourceStaffEntryContainers[this.currentVoiceEntryIndex];
             this.currentVoiceEntries = this.getVoiceEntries(currentContainer);
             this.currentVoiceEntries = this.getVoiceEntries(currentContainer);
@@ -491,11 +516,18 @@ export class MusicPartManagerIterator {
             this.recursiveMove();
             this.recursiveMove();
             return;
             return;
         }
         }
+        // we reached the end
         this.currentVerticalContainerInMeasureTimestamp = new Fraction();
         this.currentVerticalContainerInMeasureTimestamp = new Fraction();
         this.currentMeasure = undefined;
         this.currentMeasure = undefined;
         this.currentVoiceEntries = undefined;
         this.currentVoiceEntries = undefined;
         this.endReached = true;
         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 {
     private checkEntries(notesOnly: boolean): boolean {
         const tlist: VoiceEntry[] = this.CurrentVisibleVoiceEntries();
         const tlist: VoiceEntry[] = this.CurrentVisibleVoiceEntries();
         if (tlist.length > 0) {
         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 {NoteState} from "./Graphical/DrawingEnums";
 import {Note} from "./VoiceData/Note";
 import {Note} from "./VoiceData/Note";
 import {VoiceEntry} from "./VoiceData/VoiceEntry";
 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 (*)
 // 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.
  * This is the representation of a complete piece of sheet music.
  * It includes the contents of a MusicXML file after the reading.
  * 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>*/ {
 export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet>*/ {
     constructor() {
     constructor() {
         this.rules = EngravingRules.Rules;
         this.rules = EngravingRules.Rules;
         this.playbackSettings = new PlaybackSettings();
         this.playbackSettings = new PlaybackSettings();
         // FIXME?
         // FIXME?
+        // initialize SheetPlaybackSetting with default values
         this.playbackSettings.rhythm = new Fraction(4, 4, 0, false);
         this.playbackSettings.rhythm = new Fraction(4, 4, 0, false);
         this.userStartTempoInBPM = 100;
         this.userStartTempoInBPM = 100;
         this.pageWidth = 120;
         this.pageWidth = 120;
+        // create MusicPartManager
         this.MusicPartManager = new MusicPartManager(this);
         this.MusicPartManager = new MusicPartManager(this);
     }
     }
     public static defaultTitle: string = "[kein Titel]";
     public static defaultTitle: string = "[kein Titel]";
@@ -291,6 +295,10 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         }
         }
         return measures;
         return measures;
     }
     }
+    /**
+     * Returns the next SourceMeasure from a given SourceMeasure.
+     * @param measure
+     */
     public getNextSourceMeasure(measure: SourceMeasure): SourceMeasure {
     public getNextSourceMeasure(measure: SourceMeasure): SourceMeasure {
         const index: number = this.sourceMeasures.indexOf(measure);
         const index: number = this.sourceMeasures.indexOf(measure);
         if (index === this.sourceMeasures.length - 1) {
         if (index === this.sourceMeasures.length - 1) {
@@ -298,9 +306,15 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         }
         }
         return this.sourceMeasures[index + 1];
         return this.sourceMeasures[index + 1];
     }
     }
+    /**
+     * Returns the first SourceMeasure of MusicSheet.
+     */
     public getFirstSourceMeasure(): SourceMeasure {
     public getFirstSourceMeasure(): SourceMeasure {
         return this.sourceMeasures[0];
         return this.sourceMeasures[0];
     }
     }
+    /**
+     * Returns the last SourceMeasure of MusicSheet.
+     */
     public getLastSourceMeasure(): SourceMeasure {
     public getLastSourceMeasure(): SourceMeasure {
         return this.sourceMeasures[this.sourceMeasures.length - 1];
         return this.sourceMeasures[this.sourceMeasures.length - 1];
     }
     }
@@ -386,7 +400,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
     //        }
     //        }
     //        return repetitions;
     //        return repetitions;
     //    } catch (ex) {
     //    } catch (ex) {
-    //        Logging.log("MusicSheet.IRepetitions get: ", ex);
+    //        log.info("MusicSheet.IRepetitions get: ", ex);
     //        return undefined;
     //        return undefined;
     //    }
     //    }
     //
     //
@@ -409,7 +423,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         try {
         try {
             return this.getFirstSourceMeasure().MeasureNumber;
             return this.getFirstSourceMeasure().MeasureNumber;
         } catch (ex) {
         } catch (ex) {
-            Logging.log("MusicSheet.FirstMeasureNumber: ", ex);
+            log.info("MusicSheet.FirstMeasureNumber: ", ex);
             return 0;
             return 0;
         }
         }
 
 
@@ -418,7 +432,7 @@ export class MusicSheet /*implements ISettableMusicSheet, IComparable<MusicSheet
         try {
         try {
             return this.getLastSourceMeasure().MeasureNumber;
             return this.getLastSourceMeasure().MeasureNumber;
         } catch (ex) {
         } catch (ex) {
-            Logging.log("MusicSheet.LastMeasureNumber: ", ex);
+            log.info("MusicSheet.LastMeasureNumber: ", ex);
             return 0;
             return 0;
         }
         }
 
 

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

@@ -4,7 +4,7 @@ import {Fraction} from "../../Common/DataObjects/Fraction";
 import {MusicSheet} from "../MusicSheet";
 import {MusicSheet} from "../MusicSheet";
 import {RepetitionInstruction} from "../VoiceData/Instructions/RepetitionInstruction";
 import {RepetitionInstruction} from "../VoiceData/Instructions/RepetitionInstruction";
 import {PartListEntry} from "./PartListEntry";
 import {PartListEntry} from "./PartListEntry";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 
 
 export class Repetition extends PartListEntry /*implements IRepetition*/ {
 export class Repetition extends PartListEntry /*implements IRepetition*/ {
     constructor(musicSheet: MusicSheet, virtualOverallRepetition: boolean) {
     constructor(musicSheet: MusicSheet, virtualOverallRepetition: boolean) {
@@ -84,7 +84,7 @@ export class Repetition extends PartListEntry /*implements IRepetition*/ {
                     this.numberOfEndings = endingNumber;
                     this.numberOfEndings = endingNumber;
                 }
                 }
             } catch (err) {
             } 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 {KeyEnum} from "../VoiceData/Instructions/KeyInstruction";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
 import {ChordSymbolReader} from "./MusicSymbolModules/ChordSymbolReader";
 import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
 import {ExpressionReader} from "./MusicSymbolModules/ExpressionReader";
+import { RepetitionInstructionReader } from "./MusicSymbolModules/RepetitionInstructionReader";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 //import Dictionary from "typescript-collections/dist/lib/Dictionary";
 
 
 // FIXME: The following classes are missing
 // FIXME: The following classes are missing
-//type repetitionInstructionReader = any;
 //type ChordSymbolContainer = any;
 //type ChordSymbolContainer = any;
 //type SlurReader = any;
 //type SlurReader = any;
 //type RepetitionInstructionReader = 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
  * 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 {
 export class InstrumentReader {
 
 
   constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
   constructor(repetitionInstructionReader: RepetitionInstructionReader, xmlMeasureList: IXmlElement[], instrument: Instrument) {
-      // this.repetitionInstructionReader = repetitionInstructionReader;
+      this.repetitionInstructionReader = repetitionInstructionReader;
       this.xmlMeasureList = xmlMeasureList;
       this.xmlMeasureList = xmlMeasureList;
       this.musicSheet = instrument.GetMusicSheet;
       this.musicSheet = instrument.GetMusicSheet;
       this.instrument = instrument;
       this.instrument = instrument;
@@ -123,9 +119,9 @@ export class InstrumentReader {
     }
     }
     this.currentMeasure = currentMeasure;
     this.currentMeasure = currentMeasure;
     this.inSourceMeasureInstrumentIndex = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.instrument);
     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 currentFraction: Fraction = new Fraction(0, 1);
     let previousFraction: Fraction = new Fraction(0, 1);
     let previousFraction: Fraction = new Fraction(0, 1);
     let divisionsException: boolean = false;
     let divisionsException: boolean = false;
@@ -143,7 +139,7 @@ export class InstrumentReader {
             if (xmlNode.element("staff") !== undefined) {
             if (xmlNode.element("staff") !== undefined) {
               noteStaff = parseInt(xmlNode.element("staff").value, 10);
               noteStaff = parseInt(xmlNode.element("staff").value, 10);
               if (isNaN(noteStaff)) {
               if (isNaN(noteStaff)) {
-                Logging.debug("InstrumentReader.readNextXmlMeasure.get staff number");
+                log.debug("InstrumentReader.readNextXmlMeasure.get staff number");
                 noteStaff = 1;
                 noteStaff = 1;
               }
               }
             }
             }
@@ -176,13 +172,13 @@ export class InstrumentReader {
             } else {
             } else {
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid Note Duration.");
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid Note Duration.");
               this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
               this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-              Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
               continue;
               continue;
             }
             }
           }
           }
 
 
           const restNote: boolean = xmlNode.element("rest") !== undefined;
           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;
           const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
           let musicTimestamp: Fraction = currentFraction.clone();
           let musicTimestamp: Fraction = currentFraction.clone();
           if (isChord) {
           if (isChord) {
@@ -193,7 +189,7 @@ export class InstrumentReader {
             this.inSourceMeasureInstrumentIndex + noteStaff - 1,
             this.inSourceMeasureInstrumentIndex + noteStaff - 1,
             this.currentStaff
             this.currentStaff
           ).staffEntry;
           ).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)) {
           if (!this.currentVoiceGenerator.hasVoiceEntry() || (!isChord && !isGraceNote && !lastNoteWasGrace) || (!lastNoteWasGrace && isGraceNote)) {
             this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, !restNote);
             this.currentVoiceGenerator.createVoiceEntry(musicTimestamp, this.currentStaffEntry, !restNote);
@@ -241,7 +237,7 @@ export class InstrumentReader {
           }
           }
           const notationsNode: IXmlElement = xmlNode.element("notations");
           const notationsNode: IXmlElement = xmlNode.element("notations");
           if (notationsNode !== undefined && notationsNode.element("dynamics") !== undefined) {
           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) {
             if (expressionReader !== undefined) {
              expressionReader.readExpressionParameters(
              expressionReader.readExpressionParameters(
                xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
                xmlNode, this.instrument, this.divisions, currentFraction, previousFraction, this.currentMeasure.MeasureNumber, false
@@ -259,7 +255,7 @@ export class InstrumentReader {
             if (isNaN(this.divisions)) {
             if (isNaN(this.divisions)) {
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
               const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/DivisionError",
                                                                       "Invalid divisions value at Instrument: ");
                                                                       "Invalid divisions value at Instrument: ");
-              Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
+              log.debug("InstrumentReader.readNextXmlMeasure", errorMsg);
               this.divisions = this.readDivisionsFromNotes();
               this.divisions = this.readDivisionsFromNotes();
               if (this.divisions > 0) {
               if (this.divisions > 0) {
                 this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
                 this.musicSheet.SheetErrors.push(errorMsg + this.instrument.Name);
@@ -306,7 +302,7 @@ export class InstrumentReader {
             previousFraction = new Fraction(0, 1);
             previousFraction = new Fraction(0, 1);
           }
           }
         } else if (xmlNode.name === "direction") {
         } 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);
           //(*) MetronomeReader.readMetronomeInstructions(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
           let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
           let relativePositionInMeasure: number = Math.min(1, currentFraction.RealValue);
           if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
           if (this.activeRhythm !== undefined && this.activeRhythm.Rhythm !== undefined) {
@@ -319,7 +315,7 @@ export class InstrumentReader {
           }
           }
           if (!handeled) {
           if (!handeled) {
            let expressionReader: ExpressionReader = this.expressionReaders[0];
            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) {
            if (staffIndex < this.expressionReaders.length) {
              expressionReader = this.expressionReaders[staffIndex];
              expressionReader = this.expressionReaders[staffIndex];
            }
            }
@@ -337,15 +333,14 @@ export class InstrumentReader {
            }
            }
           }
           }
         } else if (xmlNode.name === "barline") {
         } 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") {
         } else if (xmlNode.name === "sound") {
           // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
           // (*) MetronomeReader.readTempoInstruction(xmlNode, this.musicSheet, this.currentXmlMeasureIndex);
         } else if (xmlNode.name === "harmony") {
         } else if (xmlNode.name === "harmony") {
@@ -370,7 +365,7 @@ export class InstrumentReader {
         }
         }
 
 
         for (let i: number = 0; i < this.expressionReaders.length; i++) {
         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) {
          if (reader !== undefined) {
            reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
            reader.checkForOpenExpressions(this.currentMeasure, currentFraction);
          }
          }
@@ -382,7 +377,7 @@ export class InstrumentReader {
       }
       }
       const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MeasureError", "Error while reading Measure.");
       const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MeasureError", "Error while reading Measure.");
       this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
       this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
-      Logging.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
+      log.debug("InstrumentReader.readNextXmlMeasure", errorMsg, e);
     }
     }
 
 
     this.previousMeasure = this.currentMeasure;
     this.previousMeasure = this.currentMeasure;
@@ -590,7 +585,7 @@ export class InstrumentReader {
             );
             );
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             line = 2;
             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);
             this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
             clefEnum = ClefEnum.G;
             clefEnum = ClefEnum.G;
             line = 2;
             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);
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           key = 0;
           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);
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           keyEnum = KeyEnum.major;
           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);
           this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
           num = 4;
           num = 4;
           denom = 4;
           denom = 4;
-          Logging.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
+          log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
         }
 
 
         if ((num === 4 && denom === 4) || (num === 2 && denom === 2)) {
         if ((num === 4 && denom === 4) || (num === 2 && denom === 2)) {
@@ -969,17 +964,17 @@ export class InstrumentReader {
   private readExpressionStaffNumber(xmlNode: IXmlElement): number {
   private readExpressionStaffNumber(xmlNode: IXmlElement): number {
    let directionStaffNumber: number = 1;
    let directionStaffNumber: number = 1;
    if (xmlNode.element("staff") !== undefined) {
    if (xmlNode.element("staff") !== undefined) {
-     let staffNode: IXmlElement = xmlNode.element("staff");
+     const staffNode: IXmlElement = xmlNode.element("staff");
      if (staffNode !== undefined) {
      if (staffNode !== undefined) {
        try {
        try {
          directionStaffNumber = parseInt(staffNode.value, 10);
          directionStaffNumber = parseInt(staffNode.value, 10);
        } catch (ex) {
        } catch (ex) {
-         let errorMsg: string = ITextTranslation.translateText(
+         const errorMsg: string = ITextTranslation.translateText(
            "ReaderErrorMessages/ExpressionStaffError", "Invalid Expression staff number -> set to default."
            "ReaderErrorMessages/ExpressionStaffError", "Invalid Expression staff number -> set to default."
          );
          );
          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
          this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
          directionStaffNumber = 1;
          directionStaffNumber = 1;
-         Logging.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
+         log.debug("InstrumentReader.readExpressionStaffNumber", errorMsg, ex);
        }
        }
 
 
      }
      }
@@ -1013,7 +1008,7 @@ export class InstrumentReader {
               try {
               try {
                 noteDuration = parseInt(durationNode.value, 10);
                 noteDuration = parseInt(durationNode.value, 10);
               } catch (ex) {
               } catch (ex) {
-                Logging.debug("InstrumentReader.readDivisionsFromNotes", ex);
+                log.debug("InstrumentReader.readDivisionsFromNotes", ex);
                 continue;
                 continue;
               }
               }
 
 

+ 28 - 33
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -6,7 +6,7 @@ import {IXmlElement} from "../../Common/FileIO/Xml";
 import {Instrument} from "../Instrument";
 import {Instrument} from "../Instrument";
 import {ITextTranslation} from "../Interfaces/ITextTranslation";
 import {ITextTranslation} from "../Interfaces/ITextTranslation";
 import {MusicSheetReadingException} from "../Exceptions";
 import {MusicSheetReadingException} from "../Exceptions";
-import {Logging} from "../../Common/Logging";
+import * as log from "loglevel";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {IXmlAttribute} from "../../Common/FileIO/Xml";
 import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
 import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
 import {RhythmSymbolEnum} 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 {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {Label} from "../Label";
 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*/ {
 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 repetitionInstructionReader: RepetitionInstructionReader;
     private repetitionCalculator: RepetitionCalculator;
     private repetitionCalculator: RepetitionCalculator;
-    // private afterSheetReadingModules: IAfterSheetReadingModule[];
+    private afterSheetReadingModules: IAfterSheetReadingModule[];
     private musicSheet: MusicSheet;
     private musicSheet: MusicSheet;
     private completeNumberOfStaves: number = 0;
     private completeNumberOfStaves: number = 0;
     private currentMeasure: SourceMeasure;
     private currentMeasure: SourceMeasure;
@@ -68,7 +63,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         try {
         try {
             return this._createMusicSheet(root, path);
             return this._createMusicSheet(root, path);
         } catch (e) {
         } 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) {
         if (this.repetitionInstructionReader !== undefined) {
             this.repetitionInstructionReader.removeRedundantInstructions();
             this.repetitionInstructionReader.removeRedundantInstructions();
             if (this.repetitionCalculator !== undefined) {
             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.checkForInstrumentWithNoVoice();
         this.musicSheet.fillStaffList();
         this.musicSheet.fillStaffList();
         //this.musicSheet.DefaultStartTempoInBpm = this.musicSheet.SheetPlaybackSetting.BeatsPerMinute;
         //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;
         return this.musicSheet;
     }
     }
@@ -194,7 +189,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         const instrumentDict: { [_: string]: Instrument; } = this.createInstrumentGroups(partList);
         const instrumentDict: { [_: string]: Instrument; } = this.createInstrumentGroups(partList);
         this.completeNumberOfStaves = this.getCompleteNumberOfStavesFromXml(partInst);
         this.completeNumberOfStaves = this.getCompleteNumberOfStavesFromXml(partInst);
         if (partInst.length !== 0) {
         if (partInst.length !== 0) {
-            // (*) this.repetitionInstructionReader.MusicSheet = this.musicSheet;
+            this.repetitionInstructionReader.MusicSheet = this.musicSheet;
             this.currentFraction = new Fraction(0, 1);
             this.currentFraction = new Fraction(0, 1);
             this.currentMeasure = undefined;
             this.currentMeasure = undefined;
             this.previousMeasure = undefined;
             this.previousMeasure = undefined;
@@ -220,7 +215,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                 currentInstrument.createStaves(instrumentNumberOfStaves);
                 currentInstrument.createStaves(instrumentNumberOfStaves);
                 instrumentReaders.push(new InstrumentReader(this.repetitionInstructionReader, xmlMeasureList, currentInstrument));
                 instrumentReaders.push(new InstrumentReader(this.repetitionInstructionReader, xmlMeasureList, currentInstrument));
                 if (this.repetitionInstructionReader !== undefined) {
                 if (this.repetitionInstructionReader !== undefined) {
-                    this.repetitionInstructionReader.XmlMeasureList[counter] = xmlMeasureList;
+                    this.repetitionInstructionReader.xmlMeasureList[counter] = xmlMeasureList;
                 }
                 }
                 counter++;
                 counter++;
             }
             }
@@ -503,7 +498,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                 const filenameSplits: string[] = filename.split(".", 1);
                 const filenameSplits: string[] = filename.split(".", 1);
                 this.musicSheet.Title = new Label(filenameSplits[0]);
                 this.musicSheet.Title = new Label(filenameSplits[0]);
             } catch (ex) {
             } 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);
                                                 const result: number = <number>parseFloat(instrumentElement.value);
                                                 subInstrument.volume = result / 127.0;
                                                 subInstrument.volume = result / 127.0;
                                             } catch (ex) {
                                             } catch (ex) {
-                                                Logging.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
+                                                log.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
                                             }
                                             }
 
 
                                         } else if (instrumentElement.name === "pan") {
                                         } else if (instrumentElement.name === "pan") {
@@ -760,18 +755,18 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
                                                 const result: number = <number>parseFloat(instrumentElement.value);
                                                 const result: number = <number>parseFloat(instrumentElement.value);
                                                 subInstrument.pan = result / 64.0;
                                                 subInstrument.pan = result / 64.0;
                                             } catch (ex) {
                                             } catch (ex) {
-                                                Logging.debug("ExpressionReader.readExpressionParameters", "read pan", ex);
+                                                log.debug("ExpressionReader.readExpressionParameters", "read pan", ex);
                                             }
                                             }
 
 
                                         }
                                         }
                                     } catch (ex) {
                                     } catch (ex) {
-                                        Logging.log("MusicSheetReader.createInstrumentGroups midi settings: ", ex);
+                                        log.info("MusicSheetReader.createInstrumentGroups midi settings: ", ex);
                                     }
                                     }
 
 
                                 }
                                 }
                             }
                             }
                         } catch (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
  * To be implemented
@@ -34,947 +31,861 @@ import Dictionary from "typescript-collections/dist/lib/Dictionary";
 export type SlurReader = any;
 export type SlurReader = any;
 
 
 export class VoiceGenerator {
 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 {
         } 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(
                 const errorMsg: string = ITextTranslation.translateText(
-                    "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
+                  "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
                 );
                 );
                 this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
                 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 {Instrument} from "./Instrument";
 import {MidiInstrument} from "./VoiceData/Instructions/ClefInstruction";
 import {MidiInstrument} from "./VoiceData/Instructions/ClefInstruction";
-import {Logging} from "../Common/Logging";
+import * as log from "loglevel";
 
 
 export class SubInstrument {
 export class SubInstrument {
 
 
@@ -93,6 +93,7 @@ export class SubInstrument {
     private parseMidiInstrument(instrumentType: string): string {
     private parseMidiInstrument(instrumentType: string): string {
         // FIXME: test this function
         // FIXME: test this function
         try {
         try {
+            // find the best match for the given instrumentType:
             if (instrumentType) {
             if (instrumentType) {
                 const tmpName: string = instrumentType.toLowerCase().trim();
                 const tmpName: string = instrumentType.toLowerCase().trim();
                 for (const key in SubInstrument.midiInstrument) {
                 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) {
             if (this.parentInstrument.Name) {
                 const tmpName: string = this.parentInstrument.Name.toLowerCase().trim();
                 const tmpName: string = this.parentInstrument.Name.toLowerCase().trim();
                 for (const key in SubInstrument.midiInstrument) {
                 for (const key in SubInstrument.midiInstrument) {
@@ -110,7 +112,7 @@ export class SubInstrument {
                 }
                 }
             }
             }
         } catch (e) {
         } catch (e) {
-            Logging.error("Error parsing MIDI Instrument. Default to Grand Piano.");
+            log.error("Error parsing MIDI Instrument. Default to Grand Piano.");
         }
         }
         return "unnamed";
         return "unnamed";
     }
     }

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

@@ -3,7 +3,7 @@ import {MultiExpression} from "./MultiExpression";
 import {DynamicExpressionSymbolEnum} from "./DynamicExpressionSymbolEnum";
 import {DynamicExpressionSymbolEnum} from "./DynamicExpressionSymbolEnum";
 //import {ArgumentOutOfRangeException} from "../../Exceptions";
 //import {ArgumentOutOfRangeException} from "../../Exceptions";
 import {InvalidEnumArgumentException} from "../../Exceptions";
 import {InvalidEnumArgumentException} from "../../Exceptions";
-import {Logging} from "../../../Common/Logging";
+import * as log from "loglevel";
 
 
 export class InstantaniousDynamicExpression extends AbstractExpression {
 export class InstantaniousDynamicExpression extends AbstractExpression {
     constructor(dynamicExpression: string, soundDynamics: number, placement: PlacementEnum, staffNumber: number) {
     constructor(dynamicExpression: string, soundDynamics: number, placement: PlacementEnum, staffNumber: number) {
@@ -143,7 +143,7 @@ export class InstantaniousDynamicExpression extends AbstractExpression {
         //    length += FontInfo.Info.getBoundingBox(symbol).Width;
         //    length += FontInfo.Info.getBoundingBox(symbol).Width;
         //}
         //}
         //return length;
         //return length;
-        Logging.debug("[Andrea] instantaniousDynamicExpression: not implemented: calculateLength!");
+        log.debug("[Andrea] instantaniousDynamicExpression: not implemented: calculateLength!");
         return 0.0;
         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";
 import {TextAlignment} from "../../../Common/Enums/TextAlignment";
 
 
 export class UnknownExpression extends AbstractExpression {
 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) {
     constructor(label: string, placementEnum: PlacementEnum, textAlignment: TextAlignment, staffNumber: number) {
         super();
         super();
         this.label = label;
         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.measureIndex = measureIndex;
-        this.endingIndices = endingIndices.slice();
+        if (endingIndices !== undefined) {
+            this.endingIndices = endingIndices.slice();
+        }
         this.type = type;
         this.type = type;
         this.alignment = alignment;
         this.alignment = alignment;
         this.parentRepetition = parentRepetition;
         this.parentRepetition = parentRepetition;
     }
     }
 
 
     public measureIndex: number;
     public measureIndex: number;
-    public endingIndices: number[];
+    public endingIndices: number[] = undefined;
     public type: RepetitionInstructionEnum;
     public type: RepetitionInstructionEnum;
     public alignment: AlignmentType;
     public alignment: AlignmentType;
     public parentRepetition: Repetition;
     public parentRepetition: Repetition;
@@ -123,10 +126,16 @@ export class RepetitionInstruction /*implements IComparable*/ {
             this.measureIndex !== other.measureIndex
             this.measureIndex !== other.measureIndex
             || this.type !== other.type
             || this.type !== other.type
             || this.alignment !== other.alignment
             || this.alignment !== other.alignment
-            || this.endingIndices.length !== other.endingIndices.length
         ) {
         ) {
             return false;
             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++) {
         for (let i: number = 0; i < this.endingIndices.length; i++) {
             if (this.endingIndices[i] !== other.endingIndices[i]) {
             if (this.endingIndices[i] !== other.endingIndices[i]) {
                 return false;
                 return false;

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

@@ -2,14 +2,17 @@ import {LyricWord} from "./LyricsWord";
 import {VoiceEntry} from "../VoiceEntry";
 import {VoiceEntry} from "../VoiceEntry";
 
 
 export class LyricsEntry {
 export class LyricsEntry {
-    constructor(text: string, word: LyricWord, parent: VoiceEntry) {
+    constructor(text: string, verseNumber: number, word: LyricWord, parent: VoiceEntry) {
         this.text = text;
         this.text = text;
         this.word = word;
         this.word = word;
         this.parent = parent;
         this.parent = parent;
+        this.verseNumber = verseNumber;
     }
     }
     private text: string;
     private text: string;
     private word: LyricWord;
     private word: LyricWord;
     private parent: VoiceEntry;
     private parent: VoiceEntry;
+    private verseNumber: number;
+    public extend: boolean;
 
 
     public get Text(): string {
     public get Text(): string {
         return this.text;
         return this.text;
@@ -26,4 +29,8 @@ export class LyricsEntry {
     public set Parent(value: VoiceEntry) {
     public set Parent(value: VoiceEntry) {
         this.parent = value;
         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;
         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 {
     public ToString(): string {
         if (this.pitch !== undefined) {
         if (this.pitch !== undefined) {
             return this.Pitch.ToString() + ", length: " + this.length.toString();
             return this.Pitch.ToString() + ", length: " + this.length.toString();
@@ -152,37 +133,6 @@ export class Note {
         }
         }
         return false;
         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 {
 export enum Appearance {

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

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

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

@@ -197,11 +197,7 @@ export class SourceStaffEntry {
             const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
             const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
                 const note: Note = voiceEntry.Notes[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;
                     duration = note.Length;
                 }
                 }
             }
             }
@@ -216,14 +212,22 @@ export class SourceStaffEntry {
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
             for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
                 const note: Note = voiceEntry.Notes[idx2];
                 const note: Note = voiceEntry.Notes[idx2];
                 if (note.NoteTie !== undefined) {
                 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;
                     duration = note.Length;
                 }
                 }
             }
             }

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

@@ -1,8 +1,7 @@
 import {Note} from "./Note";
 import {Note} from "./Note";
-import {Beam} from "./Beam";
-import {Fraction} from "../../Common/DataObjects/Fraction";
-import {Tuplet} from "./Tuplet";
 import {BaseIdClass} from "../../Util/BaseIdClass";
 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.
  * 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) {
     constructor(note: Note) {
         super();
         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 lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
     private arpeggiosNotesIndices: number[] = [];
     private arpeggiosNotesIndices: number[] = [];
     private ornamentContainer: OrnamentContainer;
     private ornamentContainer: OrnamentContainer;
+    private stemDirection: StemDirectionType = StemDirectionType.Undefined;
 
 
     public get ParentSourceStaffEntry(): SourceStaffEntry {
     public get ParentSourceStaffEntry(): SourceStaffEntry {
         return this.parentSourceStaffEntry;
         return this.parentSourceStaffEntry;
@@ -77,6 +78,13 @@ export class VoiceEntry {
         this.ornamentContainer = value;
         this.ornamentContainer = value;
     }
     }
 
 
+    public get StemDirection(): StemDirectionType {
+        return this.stemDirection;
+    }
+    public set StemDirection(value: StemDirectionType) {
+        this.stemDirection = value;
+    }
+
     public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
     public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
         switch (articulation) {
         switch (articulation) {
             case ArticulationEnum.accent:
             case ArticulationEnum.accent:
@@ -151,7 +159,7 @@ export class VoiceEntry {
             return;
             return;
         }
         }
         const baseNote: Note = this.notes[0];
         const baseNote: Note = this.notes[0];
-        const baselength: Fraction = baseNote.calculateNoteLengthWithoutTie();
+        const baselength: Fraction = baseNote.Length;
         const baseVoice: Voice = voiceEntryWithOrnament.ParentVoice;
         const baseVoice: Voice = voiceEntryWithOrnament.ParentVoice;
         const baseTimestamp: Fraction = voiceEntryWithOrnament.Timestamp;
         const baseTimestamp: Fraction = voiceEntryWithOrnament.Timestamp;
         let currentTimestamp: Fraction = Fraction.createFromFraction(baseTimestamp);
         let currentTimestamp: Fraction = Fraction.createFromFraction(baseTimestamp);
@@ -325,3 +333,10 @@ export enum ArticulationEnum {
     detachedlegato,
     detachedlegato,
     otherarticulation
     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> {
     public static ajax(url: string): Promise<string> {
         let xhttp: XMLHttpRequest;
         let xhttp: XMLHttpRequest;
+        const mimeType: string = url.indexOf(".mxl") > -1 ? "text/plain; charset=x-user-defined" : "application/xml";
         if (XMLHttpRequest) {
         if (XMLHttpRequest) {
             xhttp = new XMLHttpRequest();
             xhttp = new XMLHttpRequest();
         } else if (ActiveXObject) {
         } 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.open("GET", url, true);
             xhttp.send();
             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 {VoiceEntry} from "../MusicalScore/VoiceData/VoiceEntry";
 import {VexFlowStaffEntry} from "../MusicalScore/Graphical/VexFlow/VexFlowStaffEntry";
 import {VexFlowStaffEntry} from "../MusicalScore/Graphical/VexFlow/VexFlowStaffEntry";
 import {MusicSystem} from "../MusicalScore/Graphical/MusicSystem";
 import {MusicSystem} from "../MusicalScore/Graphical/MusicSystem";
-import {OSMD} from "./OSMD";
+import {OpenSheetMusicDisplay} from "./OpenSheetMusicDisplay";
 import {GraphicalMusicSheet} from "../MusicalScore/Graphical/GraphicalMusicSheet";
 import {GraphicalMusicSheet} from "../MusicalScore/Graphical/GraphicalMusicSheet";
 
 
 /**
 /**
  * A cursor which can iterate through the music sheet.
  * A cursor which can iterate through the music sheet.
  */
  */
 export class Cursor {
 export class Cursor {
-  constructor(container: HTMLElement, osmd: OSMD) {
+  constructor(container: HTMLElement, openSheetMusicDisplay: OpenSheetMusicDisplay) {
     this.container = container;
     this.container = container;
-    this.osmd = osmd;
+    this.openSheetMusicDisplay = openSheetMusicDisplay;
     const curs: HTMLElement = document.createElement("img");
     const curs: HTMLElement = document.createElement("img");
     curs.style.position = "absolute";
     curs.style.position = "absolute";
     curs.style.zIndex = "-1";
     curs.style.zIndex = "-1";
@@ -21,7 +21,7 @@ export class Cursor {
   }
   }
 
 
   private container: HTMLElement;
   private container: HTMLElement;
-  private osmd: OSMD;
+  private openSheetMusicDisplay: OpenSheetMusicDisplay;
   private manager: MusicPartManager;
   private manager: MusicPartManager;
   private iterator: MusicPartManagerIterator;
   private iterator: MusicPartManagerIterator;
   private graphic: GraphicalMusicSheet;
   private graphic: GraphicalMusicSheet;
@@ -44,11 +44,11 @@ export class Cursor {
     this.update();
     this.update();
     // Forcing the sheet to re-render is not necessary anymore,
     // Forcing the sheet to re-render is not necessary anymore,
     // since the cursor is an HTML element.
     // since the cursor is an HTML element.
-    // this.osmd.render();
+    // this.openSheetMusicDisplay.render();
   }
   }
 
 
   public update(): void {
   public update(): void {
-    // Warning! This should NEVER call this.osmd.render()
+    // Warning! This should NEVER call this.openSheetMusicDisplay.render()
     if (this.hidden) {
     if (this.hidden) {
       return;
       return;
     }
     }
@@ -90,10 +90,10 @@ export class Cursor {
 
 
     // This the current HTML Cursor:
     // This the current HTML Cursor:
     const cursorElement: HTMLImageElement = this.cursorElement;
     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) {
     if (newWidth !== cursorElement.width) {
       cursorElement.width = newWidth;
       cursorElement.width = newWidth;
       this.updateStyle(newWidth);
       this.updateStyle(newWidth);
@@ -113,7 +113,7 @@ export class Cursor {
     //this.graphic.Cursors.length = 0;
     //this.graphic.Cursors.length = 0;
     // Forcing the sheet to re-render is not necessary anymore
     // Forcing the sheet to re-render is not necessary anymore
     //if (!this.hidden) {
     //if (!this.hidden) {
-    //    this.osmd.render();
+    //    this.openSheetMusicDisplay.render();
     //}
     //}
     this.hidden = true;
     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 {AJAX} from "./AJAX";
 import * as log from "loglevel";
 import * as log from "loglevel";
 
 
-export class OSMD {
+export class OpenSheetMusicDisplay {
     /**
     /**
      * The easy way of displaying a MusicXML sheet music file
      * 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
      * @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
      * @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
         // Store container element
         if (typeof container === "string") {
         if (typeof container === "string") {
             // ID passed
             // ID passed
@@ -30,7 +30,7 @@ export class OSMD {
             this.container = <HTMLElement>container;
             this.container = <HTMLElement>container;
         }
         }
         if (!this.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") {
         if (backend === "svg") {
@@ -72,7 +72,7 @@ export class OSMD {
         this.reset();
         this.reset();
         if (typeof content === "string") {
         if (typeof content === "string") {
             const str: string = <string>content;
             const str: string = <string>content;
-            const self: OSMD = this;
+            const self: OpenSheetMusicDisplay = this;
             if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
             if (str.substr(0, 4) === "\x50\x4b\x03\x04") {
                 // This is a zip file, unpack it first
                 // This is a zip file, unpack it first
                 return MXLHelper.MXLtoXMLstring(str).then(
                 return MXLHelper.MXLtoXMLstring(str).then(
@@ -81,14 +81,19 @@ export class OSMD {
                     },
                     },
                     (err: any) => {
                     (err: any) => {
                         log.debug(err);
                         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") {
             if (str.substr(0, 5) === "<?xml") {
                 // Parse the string representing an xml file
                 // Parse the string representing an xml file
                 const parser: DOMParser = new DOMParser();
                 const parser: DOMParser = new DOMParser();
-                content = parser.parseFromString(str, "text/xml");
+                content = parser.parseFromString(str, "application/xml");
             } else if (str.length < 2083) {
             } else if (str.length < 2083) {
                 // Assume now "str" is a URL
                 // Assume now "str" is a URL
                 // Retrieve the file at the given URL
                 // Retrieve the file at the given URL
@@ -100,7 +105,7 @@ export class OSMD {
         }
         }
 
 
         if (!content || !(<any>content).nodeName) {
         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;
         const children: NodeList = (<Document>content).childNodes;
         let elem: Element;
         let elem: Element;
@@ -112,7 +117,7 @@ export class OSMD {
             }
             }
         }
         }
         if (!elem) {
         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 score: IXmlElement = new IXmlElement(elem);
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
@@ -129,7 +134,7 @@ export class OSMD {
      */
      */
     public render(): void {
     public render(): void {
         if (!this.graphic) {
         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;
         const width: number = this.container.offsetWidth;
         // Before introducing the following optimization (maybe irrelevant), tests
         // Before introducing the following optimization (maybe irrelevant), tests
@@ -208,7 +213,7 @@ export class OSMD {
      * Attach the appropriate handler to the window.onResize event
      * Attach the appropriate handler to the window.onResize event
      */
      */
     private autoResize(): void {
     private autoResize(): void {
-        const self: OSMD = this;
+        const self: OpenSheetMusicDisplay = this;
         this.handleResize(
         this.handleResize(
             () => {
             () => {
                 // empty
                 // empty

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

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

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

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

BIN=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">
 <score-partwise version="2.0">
   <work>
   <work>
     <work-number>K. 476</work-number>
     <work-number>K. 476</work-number>

+ 7 - 26
webpack.common.js

@@ -4,45 +4,26 @@ var webpack = require('webpack')
 
 
 module.exports = {
 module.exports = {
     entry: {
     entry: {
-        'osmd': './src/OSMD/OSMD.ts', // Main library
+        'opensheetmusicdisplay': './src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts', // Main library
         'demo': './demo/index.js' // Demo index
         'demo': './demo/index.js' // Demo index
     },
     },
     output: {
     output: {
         path: path.resolve(__dirname, 'build'),
         path: path.resolve(__dirname, 'build'),
-        filename: '[name].js'
+        filename: '[name].js',
+        library: 'opensheetmusicdisplay',
+        libraryTarget: 'umd'
     },
     },
     resolve: {
     resolve: {
         // Add '.ts' and '.tsx' as a resolvable extension.
         // Add '.ts' and '.tsx' as a resolvable extension.
-        extensions: ['.webpack.js', '.web.js', '.ts', '.tsx', '.js']
+        extensions: ['.ts', '.tsx', '.js']
     },
     },
     module: {
     module: {
-        loaders: [
+        rules: [
             // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
             // all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
             {
             {
                 test: /\.ts$/,
                 test: /\.ts$/,
                 loader: 'ts-loader',
                 loader: 'ts-loader',
                 exclude: /(node_modules|bower_components)/
                 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({
         new webpack.EnvironmentPlugin({
             STATIC_FILES_SUBFOLDER: false, // Set to other directory if NOT using webpack-dev-server
             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,
             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.
             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
         // add a demo page to the build folder
         new HtmlWebpackPlugin({
         new HtmlWebpackPlugin({
             template: 'demo/index.html',
             template: 'demo/index.html',
+            favicon: 'demo/favicon.ico',
             title: 'OpenSheetMusicDisplay Demo'
             title: 'OpenSheetMusicDisplay Demo'
         })
         })
     ],
     ],

+ 2 - 1
webpack.dev.js

@@ -2,5 +2,6 @@ var merge = require('webpack-merge')
 var common = require('./webpack.common.js')
 var common = require('./webpack.common.js')
 
 
 module.exports = merge(common, {
 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, {
 module.exports = merge(common, {
     output: {
     output: {
         filename: '[name].min.js',
         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: [
     plugins: [
-        new webpack.optimize.UglifyJsPlugin({
-            warnings: false,
-            beautify: false,
-            compress: true,
-            comments: false,
-            sourceMap: true,
-            parallel: true
-        }),
         // build optimization plugins
         // build optimization plugins
         new webpack.LoaderOptionsPlugin({
         new webpack.LoaderOptionsPlugin({
             minimize: true,
             minimize: true,

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio