Explorar o código

Merge branch 'release/0.5.1' into master

sschmidTU %!s(int64=6) %!d(string=hai) anos
pai
achega
f86929ef86
Modificáronse 49 ficheiros con 1746 adicións e 336 borrados
  1. 3 2
      .travis.yml
  2. 22 0
      CHANGELOG.md
  3. 3 1
      README.md
  4. 17 8
      demo/index.html
  5. 48 10
      demo/index.js
  6. 1 0
      external/vexflow/vexflow.d.ts
  7. 30 31
      karma.conf.js
  8. 15 13
      package.json
  9. 22 18
      src/Common/Enums/TextAlignment.ts
  10. 2 5
      src/Common/Strings/StringUtil.ts
  11. 134 1
      src/MusicalScore/Graphical/DrawingParameters.ts
  12. 115 16
      src/MusicalScore/Graphical/EngravingRules.ts
  13. 2 2
      src/MusicalScore/Graphical/GraphicalChordSymbolContainer.ts
  14. 13 13
      src/MusicalScore/Graphical/GraphicalLabel.ts
  15. 9 6
      src/MusicalScore/Graphical/GraphicalLyricEntry.ts
  16. 87 37
      src/MusicalScore/Graphical/MusicSheetCalculator.ts
  17. 17 22
      src/MusicalScore/Graphical/MusicSheetDrawer.ts
  18. 2 2
      src/MusicalScore/Graphical/MusicSystem.ts
  19. 14 13
      src/MusicalScore/Graphical/MusicSystemBuilder.ts
  20. 4 1
      src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts
  21. 7 3
      src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts
  22. 23 6
      src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts
  23. 3 3
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts
  24. 3 2
      src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts
  25. 12 0
      src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts
  26. 3 3
      src/MusicalScore/Label.ts
  27. 26 10
      src/MusicalScore/ScoreIO/InstrumentReader.ts
  28. 18 9
      src/MusicalScore/ScoreIO/MusicSheetReader.ts
  29. 7 2
      src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts
  30. 3 0
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionCalculator.ts
  31. 12 20
      src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts
  32. 25 10
      src/MusicalScore/ScoreIO/VoiceGenerator.ts
  33. 5 5
      src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts
  34. 9 0
      src/MusicalScore/VoiceData/Instructions/AbstractNotationInstruction.ts
  35. 9 0
      src/MusicalScore/VoiceData/Note.ts
  36. 2 1
      src/MusicalScore/VoiceData/NoteHead.ts
  37. 12 1
      src/MusicalScore/VoiceData/Tuplet.ts
  38. 11 8
      src/OpenSheetMusicDisplay/Cursor.ts
  39. 58 0
      src/OpenSheetMusicDisplay/OSMDOptions.ts
  40. 122 25
      src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts
  41. 2 1
      test/Common/FileIO/Xml_Test.ts
  42. 30 14
      test/Common/OSMD/OSMD_Test.ts
  43. 2 1
      test/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer_Test.ts
  44. 5 0
      test/Util/TestUtils.ts
  45. 765 8
      test/data/OSMD_function_test_all.xml
  46. 3 1
      tsconfig.json
  47. 0 1
      tslint.json
  48. 1 1
      webpack.prod.js
  49. 8 0
      webpack.sourcemap.js

+ 3 - 2
.travis.yml

@@ -1,10 +1,11 @@
-sudo: false
+sudo: required
+dist: trusty
 language: node_js
 language: node_js
 node_js:
 node_js:
 - '6'
 - '6'
 - '8'
 - '8'
 env:
 env:
-  - timeout=4000
+  - timeout=10000
 notifications:
 notifications:
   email: false
   email: false
   slack:
   slack:

+ 22 - 0
CHANGELOG.md

@@ -1,3 +1,25 @@
+<a name="0.5.1"></a>
+## [0.5.1](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.5.0...0.5.1) (2018-09-26)
+
+### Bug Fixes
+
+* **Bbox/Cursor:** fix bbox and cursor position for whole rests ([#383](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/383)) ([3d1894d](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/3d1894d)), closes [#380](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/380)
+* **Cursor:** Fix Cursor jumping incorrectly on Next() ([#377](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/377)) ([f43e305](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/f43e305)), closes [#376](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/376)
+* **DrawingParameters:** switching default and compact mode during runtime correctly renders each mode after re-render. ([96bf081](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/96bf081))
+* **Error handling:** Fix error loading empty score, improve error handling ([#367](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/367)) ([aa65792](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/aa65792)), closes [#358](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/358)
+* **Invisible notes:** do not display invisible notes and double rests. ([a6ac78c](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/a6ac78c))
+* **NoteHead:** info instead of warn for unsupported NoteHead, use triangle for "do" ([f1d4b47](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/f1d4b47))
+* **Zooming on mobile devices crashed OSMD:** Skip Bottomline Update if the zoom gets too much for vexflow ([#375](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/375)) ([c562a96](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/c562a96)), closes [#373](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/373)
+
+### Features
+
+* **Invisible notes:** Do not display invisible time signature. ([af0770a](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/af0770a))
+* **Invisible notes:** Do not display invisible notes. Invisible notes are parsed, with invisible (printObject) flag ([ccf860e](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/ccf860e))
+* **Options:** add options interface for osmd constructor ([#368](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/368)) ([9da9cb4](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/9da9cb4))
+* **Options:** implement compact mode, drawPartNames, drawTitle, drawSubtitle, drawComposer, drawLyricist ([9cec507](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/9cec507))
+* **Tuplets:** Read and display tuplet bracket setting from XML. Do not display tuplet ratios by default. ([f568e51](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/f568e51))
+* **Demo:** Add Re-render button, add column to layout ([#361](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/pull/361))
+
 <a name="0.5.0"></a>
 <a name="0.5.0"></a>
 # [0.5.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.3.1...0.5.0) (2018-09-05)
 # [0.5.0](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.3.1...0.5.0) (2018-09-05)
 
 

+ 3 - 1
README.md

@@ -10,7 +10,7 @@
 [![Code Climate](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay/badges/gpa.svg)](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Code Climate](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay/badges/gpa.svg)](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
 [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
 
 
-[osmd.org](https://osmd.org)
+[opensheetmusicdisplay.org](https://opensheetmusicdisplay.org/)
 
 
 OpenSheetMusicDisplay renders MusicXML sheet music in the browser. It 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 renders MusicXML sheet music in the browser. It 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.
 
 
@@ -22,6 +22,8 @@ Written in [TypeScript](https://www.typescriptlang.org) and released under [MIT
 
 
 See the [Wiki](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/wiki) for information about the source code and how to use OSMD.
 See the [Wiki](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/wiki) for information about the source code and how to use OSMD.
 
 
+Try the [Demo](https://opensheetmusicdisplay.github.io/demo/) to see what OSMD can do.
+
 
 
 <!--# <a name="license"></a>License
 <!--# <a name="license"></a>License
 The MIT License (MIT)
 The MIT License (MIT)

+ 17 - 8
demo/index.html

@@ -2,7 +2,8 @@
 <html lang="en">
 <html lang="en">
 <head>
 <head>
     <meta charset="utf-8">
     <meta charset="utf-8">
-
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    
     <title><%= htmlWebpackPlugin.options.title %></title>
     <title><%= htmlWebpackPlugin.options.title %></title>
     <meta name="description" content="A showcase for OpenSheetMusicDisplay.">
     <meta name="description" content="A showcase for OpenSheetMusicDisplay.">
     <meta name="author" content="OpenSheetMusicDisplay contributors">
     <meta name="author" content="OpenSheetMusicDisplay contributors">
@@ -18,14 +19,14 @@
     <img src="./favicon.ico?" class="ui image">
     <img src="./favicon.ico?" class="ui image">
     <%= htmlWebpackPlugin.options.title %>
     <%= htmlWebpackPlugin.options.title %>
 </h1>
 </h1>
-<div class="ui three column grid container">
+<div class="ui four column grid container">
     <div class="column">
     <div class="column">
         <h3 class="ui header">Select a sample:</h3>
         <h3 class="ui header">Select a sample:</h3>
-        <select class="ui selection dropdown" id="selectSample" style="width:320px; height:40%"></select>
+        <select class="ui selection dropdown" id="selectSample" style="width:320px; height:38%"></select>
     </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 selection dropdown" id="backend-select" value="svg" style="height:40%">
+        <select class="ui selection dropdown" id="backend-select" value="svg" style="height:38%">
             <option value="svg">SVG</option>
             <option value="svg">SVG</option>
             <option value="canvas">Canvas</option>>
             <option value="canvas">Canvas</option>>
         </select>
         </select>
@@ -81,11 +82,13 @@
             <option value="none">None</option>
             <option value="none">None</option>
             <option value="all">All</option>
             <option value="all">All</option>
             <option value="VexFlowMeasure">Measures</option>
             <option value="VexFlowMeasure">Measures</option>
-            <option value="VexFlowStaffEntry">Staff entries</option>
+            <option value="VexFlowGraphicalNote">GraphicalNotes</option>
+            <option value="VexFlowVoiceEntry">VoiceEntries</option>
+            <option value="VexFlowStaffEntry">StaffEntries</option>
             <option value="GraphicalLabel">Labels</option>
             <option value="GraphicalLabel">Labels</option>
-            <option value="VexFlowStaffLine">Staff lines</option>
-            <option value="SystemLine">System lines</option>
-            <option value="StaffLineActivitySymbol">Activity symbols</option>
+            <option value="VexFlowStaffLine">StaffLines</option>
+            <option value="SystemLine">SystemLines</option>
+            <option value="StaffLineActivitySymbol">ActivitySymbols</option>
         </select>
         </select>
     </div>
     </div>
     <div class="column">
     <div class="column">
@@ -105,6 +108,12 @@
             </div>
             </div>
         </div>
         </div>
     </div>
     </div>
+    <div class="column">
+        <h3 class="ui header">Debug controls:</h3>
+        <div class="ui vertical buttons">
+            <div class="ui button" id="debug-re-render-btn">Re-render</div>
+        </div>
+    </div>
 </div>
 </div>
 <table cellspacing="0" style="max-width:700px;">
 <table cellspacing="0" style="max-width:700px;">
     <tr id="error-tr">
     <tr id="error-tr">

+ 48 - 10
demo/index.js

@@ -57,7 +57,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
     resetCursorBtn,
     resetCursorBtn,
     showCursorBtn,
     showCursorBtn,
     hideCursorBtn,
     hideCursorBtn,
-    backendSelect;
+    backendSelect,
+    debugReRenderBtn;
 
 
     // Initialization code
     // Initialization code
     function init() {
     function init() {
@@ -79,6 +80,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         showCursorBtn = document.getElementById("show-cursor-btn");
         showCursorBtn = document.getElementById("show-cursor-btn");
         hideCursorBtn = document.getElementById("hide-cursor-btn");
         hideCursorBtn = document.getElementById("hide-cursor-btn");
         backendSelect = document.getElementById("backend-select");
         backendSelect = document.getElementById("backend-select");
+        debugReRenderBtn = document.getElementById("debug-re-render-btn");
+
 
 
         // Hide error
         // Hide error
         error();
         error();
@@ -113,12 +116,28 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             openSheetMusicDisplay.DrawSkyLine = !openSheetMusicDisplay.DrawSkyLine;
             openSheetMusicDisplay.DrawSkyLine = !openSheetMusicDisplay.DrawSkyLine;
         }
         }
 
 
-        bottomlineDebug .onclick = function() {
+        bottomlineDebug.onclick = function() {
             openSheetMusicDisplay.DrawBottomLine = !openSheetMusicDisplay.DrawBottomLine;
             openSheetMusicDisplay.DrawBottomLine = !openSheetMusicDisplay.DrawBottomLine;
         }
         }
 
 
+        debugReRenderBtn.onclick = function() {
+            rerender();
+        }
+
         // Create OSMD object and canvas
         // Create OSMD object and canvas
-        openSheetMusicDisplay = new OpenSheetMusicDisplay(canvas, false, backendSelect.value);
+        openSheetMusicDisplay = new OpenSheetMusicDisplay(canvas, {
+            autoResize: true,
+            backend: backendSelect.value,
+            drawingParameters: "default", // try compact (instead of default)
+            disableCursor: false,
+            drawPartNames: true, // try false
+            // drawTitle: false,
+            // drawSubtitle: false,
+
+            // tupletsBracketed: true,
+            // tripletsBracketed: true,
+            // tupletsRatioed: true, // unconventional; renders ratios for tuplets (3:2 instead of 3 for triplets)
+        });
         openSheetMusicDisplay.setLogLevel('info');
         openSheetMusicDisplay.setLogLevel('info');
         document.body.appendChild(canvas);
         document.body.appendChild(canvas);
 
 
@@ -141,7 +160,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 try {
                 try {
                     openSheetMusicDisplay.render();
                     openSheetMusicDisplay.render();
                 } catch (e) {
                 } catch (e) {
-                    console.warn(e.stack);
+                    errorLoadingOrRenderingSheet(e, "rendering");
                 }
                 }
                 enable();
                 enable();
             }
             }
@@ -163,7 +182,11 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             openSheetMusicDisplay.cursor.hide();
             openSheetMusicDisplay.cursor.hide();
         });
         });
         showCursorBtn.addEventListener("click", function() {
         showCursorBtn.addEventListener("click", function() {
-            openSheetMusicDisplay.cursor.show();
+            if (openSheetMusicDisplay.cursor) {
+                openSheetMusicDisplay.cursor.show();
+            } else {
+                console.info("Can't show cursor, as it was disabled (e.g. by drawingParameters).");
+            }
         });
         });
 
 
         backendSelect.addEventListener("change", function(e) {
         backendSelect.addEventListener("change", function(e) {
@@ -178,7 +201,6 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
     }
     }
 
 
     function Resize(startCallback, endCallback) {
     function Resize(startCallback, endCallback) {
-
       var rtime;
       var rtime;
       var timeout = false;
       var timeout = false;
       var delta = 200;
       var delta = 200;
@@ -225,20 +247,28 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 return openSheetMusicDisplay.render();
                 return openSheetMusicDisplay.render();
             },
             },
             function(e) {
             function(e) {
-                console.warn(e.stack);
-                error("Error reading sheet: " + e);
+                errorLoadingOrRenderingSheet(e, "rendering");
             }
             }
         ).then(
         ).then(
             function() {
             function() {
                 return onLoadingEnd(isCustom);
                 return onLoadingEnd(isCustom);
             }, function(e) {
             }, function(e) {
-                console.warn(e.stack);
-                error("Error rendering sheet: " + process.env.DEBUG ? e.stack : e);
+                errorLoadingOrRenderingSheet(e, "loading");
                 onLoadingEnd(isCustom);
                 onLoadingEnd(isCustom);
             }
             }
         );
         );
     }
     }
 
 
+    function errorLoadingOrRenderingSheet(e, loadingOrRenderingString) {
+        var errorString = "Error " + loadingOrRenderingString + " sheet: " + e;
+        // if (process.env.DEBUG) { // people may not set a debug environment variable for the demo.
+        // Always giving a StackTrace might give us more and better error reports.
+        // TODO for a release, StackTrace control could be reenabled
+        errorString += "\n" + "StackTrace: \n" + e.stack;
+        // }
+        console.warn(errorString);
+    }
+
     function onLoadingEnd(isCustom) {
     function onLoadingEnd(isCustom) {
         sampleLoaded = true;
         sampleLoaded = true;
         // Remove option from select
         // Remove option from select
@@ -262,6 +292,14 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         }, 0);
         }, 0);
     }
     }
 
 
+    function rerender() {
+        disable();
+        window.setTimeout(function(){
+            openSheetMusicDisplay.render();
+            enable();
+        }, 0);
+    }
+
     function error(errString) {
     function error(errString) {
         if (!errString) {
         if (!errString) {
             error_tr.style.display = "none";
             error_tr.style.display = "none";

+ 1 - 0
external/vexflow/vexflow.d.ts

@@ -96,6 +96,7 @@ declare namespace Vex {
             public x_shift: number;
             public x_shift: number;
             public getAbsoluteX(): number;
             public getAbsoluteX(): number;
             public addModifier(index: number, modifier: Modifier): StemmableNote;
             public addModifier(index: number, modifier: Modifier): StemmableNote;
+            public preFormatted: boolean;
         }
         }
 
 
         export class GhostNote extends StemmableNote {
         export class GhostNote extends StemmableNote {

+ 30 - 31
karma.conf.js

@@ -13,24 +13,22 @@ module.exports = function (config) {
         // list of files to exclude
         // list of files to exclude
         exclude: [],
         exclude: [],
 
 
-        files: [{
-            pattern: 'src/**/*.ts',
-            included: false
-        }, {
-            pattern: 'test/**/*.ts',
-            included: true
-        }, {
-            pattern: 'test/data/*.xml',
-            included: true
-        }, {
-            pattern: 'test/data/*.mxl.base64',
-            included: true
-        }, {
-            pattern: 'test/data/*.mxl',
-            included: false,
-            watched: false,
-            served: true
-        }],
+        files: [
+            {
+                pattern: 'test/**/*.ts',
+                included: true
+            }, {
+                pattern: 'test/data/*.xml',
+                included: true
+            }, {
+                pattern: 'test/data/*.mxl.base64',
+                included: true
+            }, {
+                pattern: 'test/data/*.mxl',
+                included: false,
+                watched: false,
+                served: true
+            }],
 
 
         // preprocess matching files before serving them to the browser
         // preprocess matching files before serving them to the browser
         // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
         // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
@@ -38,7 +36,6 @@ module.exports = function (config) {
             'test/data/*.xml': ['xml2js'],
             'test/data/*.xml': ['xml2js'],
             'test/data/*.mxl.base64': ['base64-to-js'],
             'test/data/*.mxl.base64': ['base64-to-js'],
             // add webpack as preprocessor
             // add webpack as preprocessor
-            'src/**/*.ts': ['webpack'],
             'test/**/*.ts': ['webpack']
             'test/**/*.ts': ['webpack']
         },
         },
 
 
@@ -48,23 +45,18 @@ 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: process.env.CI ? false : 'cheap-module-eval-source-map',
+            devtool: process.env.CI ? false : 'inline-source-map',
             mode: process.env.CI ? 'production' : 'development',
             mode: process.env.CI ? 'production' : 'development',
             module: {
             module: {
                 rules: common.module.rules
                 rules: common.module.rules
             },
             },
             resolve: common.resolve
             resolve: common.resolve
         },
         },
-        webpackMiddleware: {
-            // webpack-dev-middleware configuration
-            // i. e.
-            noInfo: true
-        },
 
 
         // Required for Firefox and Chorme to work
         // Required for Firefox and Chorme to work
         // see https://github.com/webpack-contrib/karma-webpack/issues/188
         // see https://github.com/webpack-contrib/karma-webpack/issues/188
         mime: {
         mime: {
-            'text/x-typescript': ['ts', 'tsx']
+            'text/x-typescript': ['ts']
         },
         },
 
 
         // test results reporter to use
         // test results reporter to use
@@ -75,19 +67,21 @@ module.exports = function (config) {
         // web server port
         // web server port
         port: 9876,
         port: 9876,
         // timeout in ms:
         // timeout in ms:
-        browserNoActivityTimeout: 100000,
+        browserNoActivityTimeout: 100000, // default 10000
+        browserDisconnectTimeout: 10000, // default 2000
+        browserDisconnectTolerance: 1, // default 0
         captureTimeout: 60000,
         captureTimeout: 60000,
         // enable / disable colors in the output (reporters and logs)
         // enable / disable colors in the output (reporters and logs)
         colors: true,
         colors: true,
 
 
         // level of logging
         // level of logging
         // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
         // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
-        logLevel: config.LOG_ERROR,
+        logLevel: config.LOG_INFO,
 
 
         client: {
         client: {
             captureConsole: true,
             captureConsole: true,
             mocha: {
             mocha: {
-                timeout: process.env.timeout || 2000
+                timeout: process.env.timeout || 6000
             }
             }
         },
         },
 
 
@@ -95,14 +89,19 @@ module.exports = function (config) {
         autoWatch: false,
         autoWatch: false,
 
 
         // start these browsers
         // start these browsers
-        browsers: [process.env.CI ? 'ChromeHeadlessNoSandbox' : 'ChromeHeadless'],
+        browsers: ['ChromeHeadlessNoSandbox'],
 
 
         // For security reasons, Google Chrome is unable to provide sandboxing
         // For security reasons, Google Chrome is unable to provide sandboxing
         // when it is running in container-based environments (e.g. CI).
         // when it is running in container-based environments (e.g. CI).
         customLaunchers: {
         customLaunchers: {
             ChromeHeadlessNoSandbox: {
             ChromeHeadlessNoSandbox: {
                 base: 'ChromeHeadless',
                 base: 'ChromeHeadless',
-                flags: ['--no-sandbox']
+                flags: ['--no-sandbox',
+                    '--disable-web-security']
+            },
+            ChromeNoSecurity: {
+                base: 'Chrome',
+                flags: ['--disable-web-security']
             }
             }
         },
         },
 
 

+ 15 - 13
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "opensheetmusicdisplay",
   "name": "opensheetmusicdisplay",
-  "version": "0.5.0",
+  "version": "0.5.1",
   "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": "build/opensheetmusicdisplay.min.js",
   "main": "build/opensheetmusicdisplay.min.js",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
@@ -10,12 +10,13 @@
     "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",
     "test": "karma start --single-run --no-auto-watch",
     "test": "karma start --single-run --no-auto-watch",
-    "test:watch": "karma start --no-single-run --auto-watch --browsers Chrome",
+    "test:watch": "karma start --no-single-run --auto-watch --browsers ChromeNoSecurity",
     "prepare": "npm run build",
     "prepare": "npm run build",
     "build": "npm-run-all lint build:webpack",
     "build": "npm-run-all lint build:webpack",
     "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",
+    "build:webpack-sourcemap": "webpack --progress --colors --config webpack.sourcemap.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"
     "fix-memory-limit": "increase-memory-limit"
   },
   },
@@ -47,7 +48,7 @@
   },
   },
   "homepage": "http://opensheetmusicdisplay.org",
   "homepage": "http://opensheetmusicdisplay.org",
   "dependencies": {
   "dependencies": {
-    "es6-promise": "^4.0.5",
+    "es6-promise": "^4.2.5",
     "increase-memory-limit": "^1.0.6",
     "increase-memory-limit": "^1.0.6",
     "jszip": "^3.0.0",
     "jszip": "^3.0.0",
     "loglevel": "^1.5.0",
     "loglevel": "^1.5.0",
@@ -59,18 +60,19 @@
     "@types/chai": "^4.0.3",
     "@types/chai": "^4.0.3",
     "@types/loglevel": "^1.4.29",
     "@types/loglevel": "^1.4.29",
     "@types/mocha": "^5.2.3",
     "@types/mocha": "^5.2.3",
+    "browserify": ">=10.x",
     "chai": "^4.1.0",
     "chai": "^4.1.0",
     "clean-webpack-plugin": "^0.1.18",
     "clean-webpack-plugin": "^0.1.18",
     "cross-env": "^5.1.3",
     "cross-env": "^5.1.3",
     "cz-conventional-changelog": "^2.0.0",
     "cz-conventional-changelog": "^2.0.0",
     "eslint": "^5.0.1",
     "eslint": "^5.0.1",
-    "eslint-config-standard": "^11.0.0",
+    "eslint-config-standard": "^12.0.0",
     "eslint-plugin-import": "^2.9.0",
     "eslint-plugin-import": "^2.9.0",
-    "eslint-plugin-node": "^6.0.1",
-    "eslint-plugin-promise": "^3.6.0",
-    "eslint-plugin-standard": "^3.0.1",
+    "eslint-plugin-node": "^7.0.1",
+    "eslint-plugin-promise": "^4.0.1",
+    "eslint-plugin-standard": "^4.0.0",
     "file-loader": "^1.1.8",
     "file-loader": "^1.1.8",
-    "html-webpack-plugin": "^3.1.0",
+    "html-webpack-plugin": "^3.2.0",
     "http-server": "^0.11.0",
     "http-server": "^0.11.0",
     "jquery": "^3.2.1",
     "jquery": "^3.2.1",
     "karma": "^3.0.0",
     "karma": "^3.0.0",
@@ -80,22 +82,22 @@
     "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": "^3.0.0",
+    "karma-webpack": "^3.0.4",
     "karma-xml2js-preprocessor": "^0.0.3",
     "karma-xml2js-preprocessor": "^0.0.3",
     "mocha": "^5.2.0",
     "mocha": "^5.2.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": "^4.1.0",
     "ts-loader": "^4.1.0",
-    "tsify": "^3.0.0",
+    "tsify": "^4.0.0",
     "tslint": "^5.8.0",
     "tslint": "^5.8.0",
     "tslint-loader": "^3.5.3",
     "tslint-loader": "^3.5.3",
     "typedoc": "^0.12.0",
     "typedoc": "^0.12.0",
     "typescript": "^2.6.1",
     "typescript": "^2.6.1",
-    "uglifyjs-webpack-plugin": "^1.2.4",
+    "uglifyjs-webpack-plugin": "^2.0.0",
     "underscore-template-loader": "^1.0.0",
     "underscore-template-loader": "^1.0.0",
-    "webpack": "^4.15.1",
+    "webpack": "^4.18.1",
     "webpack-cli": "^3.0.8",
     "webpack-cli": "^3.0.8",
-    "webpack-dev-server": "3.1.6",
+    "webpack-dev-server": "3.1.9",
     "webpack-merge": "^4.1.2",
     "webpack-merge": "^4.1.2",
     "webpack-visualizer-plugin": "^0.1.11"
     "webpack-visualizer-plugin": "^0.1.11"
   },
   },

+ 22 - 18
src/Common/Enums/TextAlignment.ts

@@ -1,12 +1,11 @@
 /**
 /**
- * The possible positioning of text on the sheet music
+ * The Alignment of a TextLabel.
+ * Specifically the label's position coordinates within the Bounding Box.
+ * For LeftBottom, the label's position is at the left bottom corner of its Bounding Box.
  * (used for example with title, composer, author, etc.)
  * (used for example with title, composer, author, etc.)
- * TODO this should be split into alignment and placement, e.g. <Left, Top> for LeftTop.
- * Right now "LeftTop" means left-aligned and top-placed. This is ambiguous for center,
- * which can be alignment or placement.
- * A function like "IsLeft" would be easier with the split.
+ * (see Show Bounding Box For -> Labels in the local demo)
  */
  */
-export enum TextAlignmentAndPlacement {
+export enum TextAlignmentEnum {
     LeftTop,
     LeftTop,
     LeftCenter,
     LeftCenter,
     LeftBottom,
     LeftBottom,
@@ -17,23 +16,28 @@ export enum TextAlignmentAndPlacement {
     RightCenter,
     RightCenter,
     RightBottom
     RightBottom
 }
 }
+/*
+ * TODO this could be split into two alignments, e.g. <Left, Top> for LeftTop.
+ * A function like IsLeft would be easier with the split.
+ * On the other hand, accessing these values will be more complex
+*/
 
 
 export class TextAlignment {
 export class TextAlignment {
-    public static IsLeft(textAlignment: TextAlignmentAndPlacement): boolean {
-        return textAlignment === TextAlignmentAndPlacement.LeftTop
-            || textAlignment === TextAlignmentAndPlacement.LeftCenter
-            || textAlignment === TextAlignmentAndPlacement.LeftBottom;
+    public static IsLeft(textAlignment: TextAlignmentEnum): boolean {
+        return textAlignment === TextAlignmentEnum.LeftTop
+            || textAlignment === TextAlignmentEnum.LeftCenter
+            || textAlignment === TextAlignmentEnum.LeftBottom;
     }
     }
 
 
-    public static IsCenterAligned(textAlignment: TextAlignmentAndPlacement): boolean {
-        return textAlignment === TextAlignmentAndPlacement.CenterTop
-            || textAlignment === TextAlignmentAndPlacement.CenterCenter
-            || textAlignment === TextAlignmentAndPlacement.CenterBottom;
+    public static IsCenterAligned(textAlignment: TextAlignmentEnum): boolean {
+        return textAlignment === TextAlignmentEnum.CenterTop
+            || textAlignment === TextAlignmentEnum.CenterCenter
+            || textAlignment === TextAlignmentEnum.CenterBottom;
     }
     }
 
 
-    public static IsRight(textAlignment: TextAlignmentAndPlacement): boolean {
-        return textAlignment === TextAlignmentAndPlacement.RightTop
-            || textAlignment === TextAlignmentAndPlacement.RightCenter
-            || textAlignment === TextAlignmentAndPlacement.RightBottom;
+    public static IsRight(textAlignment: TextAlignmentEnum): boolean {
+        return textAlignment === TextAlignmentEnum.RightTop
+            || textAlignment === TextAlignmentEnum.RightCenter
+            || textAlignment === TextAlignmentEnum.RightBottom;
     }
     }
 }
 }

+ 2 - 5
src/Common/Strings/StringUtil.ts

@@ -1,9 +1,6 @@
 export class StringUtil {
 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) {
+  public static StringContainsSeparatedWord(str: string, wordRegExString: string): boolean {
+    if (new RegExp("( |^)" + wordRegExString + "([ .]|$)").test(str)) {
       return true;
       return true;
     }
     }
     return false;
     return false;

+ 134 - 1
src/MusicalScore/Graphical/DrawingParameters.ts

@@ -1,4 +1,17 @@
+import { EngravingRules } from "./EngravingRules";
+
+export enum DrawingParametersEnum {
+    allon = "allon",
+    compact = "compact",
+    default = "default",
+    leadsheet = "leadsheet",
+    preview = "preview",
+    thumbnail = "thumbnail",
+}
+
 export class DrawingParameters {
 export class DrawingParameters {
+    /** will set other settings if changed with set method */
+    private drawingParametersEnum: DrawingParametersEnum;
     public drawHighlights: boolean;
     public drawHighlights: boolean;
     public drawErrors: boolean;
     public drawErrors: boolean;
     public drawSelectionStartSymbol: boolean;
     public drawSelectionStartSymbol: boolean;
@@ -8,6 +21,46 @@ export class DrawingParameters {
     public drawScrollIndicator: boolean;
     public drawScrollIndicator: boolean;
     public drawComments: boolean;
     public drawComments: boolean;
     public drawMarkedAreas: boolean;
     public drawMarkedAreas: boolean;
+    public drawTitle: boolean = true;
+    public drawSubtitle: boolean = true;
+    public drawLyricist: boolean = true;
+    public drawComposer: boolean = true;
+    public drawCredits: boolean = true;
+    public drawPartNames: boolean = true;
+    /** Draw notes set to be invisible (print-object="no" in XML). */
+    public drawHiddenNotes: boolean = false;
+    public defaultColorNoteHead: string; // TODO not yet supported
+    public defaultColorStem: string; // TODO not yet supported
+
+    constructor(drawingParameters: DrawingParametersEnum = DrawingParametersEnum.default) {
+        this.DrawingParametersEnum = drawingParameters;
+    }
+
+    /** Sets drawing parameters enum and changes settings flags accordingly. */
+    public set DrawingParametersEnum(drawingParametersEnum: DrawingParametersEnum) {
+        this.drawingParametersEnum = drawingParametersEnum;
+        switch (drawingParametersEnum) {
+            case DrawingParametersEnum.allon:
+                this.setForAllOn();
+                break;
+            case DrawingParametersEnum.thumbnail:
+                this.setForThumbnail();
+                break;
+            case DrawingParametersEnum.leadsheet:
+                this.setForLeadsheet();
+                break;
+            case DrawingParametersEnum.compact:
+                this.setForCompactMode();
+                break;
+            case DrawingParametersEnum.default:
+            default:
+                this.setForDefault();
+        }
+    }
+
+    public get DrawingParametersEnum(): DrawingParametersEnum {
+        return this.drawingParametersEnum;
+    }
 
 
     public setForAllOn(): void {
     public setForAllOn(): void {
         this.drawHighlights = true;
         this.drawHighlights = true;
@@ -19,9 +72,22 @@ export class DrawingParameters {
         this.drawScrollIndicator = true;
         this.drawScrollIndicator = true;
         this.drawComments = true;
         this.drawComments = true;
         this.drawMarkedAreas = true;
         this.drawMarkedAreas = true;
+        this.DrawTitle = true;
+        this.DrawSubtitle = true;
+        this.DrawComposer = true;
+        this.DrawLyricist = true;
+        this.drawCredits = true;
+        this.DrawPartNames = true;
+        this.drawHiddenNotes = true;
+        EngravingRules.Rules.CompactMode = false;
     }
     }
 
 
-    public setForThumbmail(): void {
+    public setForDefault(): void {
+        this.setForAllOn();
+        this.drawHiddenNotes = false;
+    }
+
+    public setForThumbnail(): void {
         this.drawHighlights = false;
         this.drawHighlights = false;
         this.drawErrors = false;
         this.drawErrors = false;
         this.drawSelectionStartSymbol = false;
         this.drawSelectionStartSymbol = false;
@@ -31,6 +97,18 @@ export class DrawingParameters {
         this.drawScrollIndicator = false;
         this.drawScrollIndicator = false;
         this.drawComments = true;
         this.drawComments = true;
         this.drawMarkedAreas = true;
         this.drawMarkedAreas = true;
+        this.drawHiddenNotes = false;
+    }
+
+    public setForCompactMode(): void {
+        this.setForDefault();
+        EngravingRules.Rules.CompactMode = true;
+        this.DrawTitle = false;
+        this.DrawComposer = false;
+        this.DrawLyricist = false;
+        // this.DrawPartNames = true; // unnecessary
+        this.drawCredits = false;
+        this.drawHiddenNotes = false;
     }
     }
 
 
     public setForLeadsheet(): void {
     public setForLeadsheet(): void {
@@ -44,4 +122,59 @@ export class DrawingParameters {
         this.drawComments = true;
         this.drawComments = true;
         this.drawMarkedAreas = true;
         this.drawMarkedAreas = true;
     }
     }
+
+    //#region GETTER / SETTER
+    public get DrawTitle(): boolean {
+        return this.drawTitle;
+    }
+
+    /** Enable or disable drawing the Title of the piece. If disabled, will disable drawing Subtitle as well. */
+    public set DrawTitle(value: boolean) {
+        this.drawTitle = value;
+        EngravingRules.Rules.RenderTitle = value;
+        if (!value) { // don't draw subtitle if title isn't drawn
+            this.DrawSubtitle = false;
+        }
+    }
+
+    public get DrawSubtitle(): boolean {
+        return this.drawSubtitle;
+    }
+
+    /** Enable or disable drawing the Subtitle of the piece. If enabled, will enable drawing Title as well. */
+    public set DrawSubtitle(value: boolean) {
+        this.drawSubtitle = value;
+        EngravingRules.Rules.RenderSubtitle = value;
+        if (value) {
+            this.DrawTitle = true; // if subtitle is drawn, title needs to be drawn as well
+        }
+    }
+
+    public get DrawComposer(): boolean {
+        return this.drawComposer;
+    }
+
+    /** Enable or disable drawing a label for the Composer of the piece. */
+    public set DrawComposer(value: boolean) {
+        this.drawComposer = value;
+        EngravingRules.Rules.RenderComposer = value;
+    }
+
+    public get DrawLyricist(): boolean {
+        return this.drawLyricist;
+    }
+
+    public set DrawLyricist(value: boolean) {
+        this.drawLyricist = value;
+        EngravingRules.Rules.RenderLyricist = value;
+    }
+
+    public get DrawPartNames(): boolean {
+        return this.drawPartNames;
+    }
+
+    public set DrawPartNames(value: boolean) {
+        this.drawPartNames = value;
+        EngravingRules.Rules.RenderInstrumentNames = value;
+    }
 }
 }

+ 115 - 16
src/MusicalScore/Graphical/EngravingRules.ts

@@ -1,7 +1,7 @@
 import {PagePlacementEnum} from "./GraphicalMusicPage";
 import {PagePlacementEnum} from "./GraphicalMusicPage";
 //import {MusicSymbol} from "./MusicSymbol";
 //import {MusicSymbol} from "./MusicSymbol";
 import * as log from "loglevel";
 import * as log from "loglevel";
-import { TextAlignmentAndPlacement } from "../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
 
 
 export class EngravingRules {
 export class EngravingRules {
     private static rules: EngravingRules;
     private static rules: EngravingRules;
@@ -13,9 +13,11 @@ export class EngravingRules {
     private sheetMinimumDistanceBetweenTitleAndSubtitle: number;
     private sheetMinimumDistanceBetweenTitleAndSubtitle: number;
     private sheetComposerHeight: number;
     private sheetComposerHeight: number;
     private sheetAuthorHeight: number;
     private sheetAuthorHeight: number;
+    private compactMode: boolean;
     private pagePlacementEnum: PagePlacementEnum;
     private pagePlacementEnum: PagePlacementEnum;
     private pageHeight: number;
     private pageHeight: number;
     private pageTopMargin: number;
     private pageTopMargin: number;
+    private pageTopMarginNarrow: number;
     private pageBottomMargin: number;
     private pageBottomMargin: number;
     private pageLeftMargin: number;
     private pageLeftMargin: number;
     private pageRightMargin: number;
     private pageRightMargin: number;
@@ -69,7 +71,7 @@ export class EngravingRules {
     private distanceOffsetBetweenTwoHorizontallyCrossedWedges: number;
     private distanceOffsetBetweenTwoHorizontallyCrossedWedges: number;
     private wedgeMinLength: number;
     private wedgeMinLength: number;
     private distanceBetweenAdjacentDynamics: number;
     private distanceBetweenAdjacentDynamics: number;
-    private tempoChangeMeasureValitidy: number;
+    private tempoChangeMeasureValidity: number;
     private tempoContinousFactor: number;
     private tempoContinousFactor: number;
     private staccatoScalingFactor: number;
     private staccatoScalingFactor: number;
     private betweenDotsDistance: number;
     private betweenDotsDistance: number;
@@ -79,6 +81,18 @@ export class EngravingRules {
     private fingeringLabelFontHeight: number;
     private fingeringLabelFontHeight: number;
     private measureNumberLabelHeight: number;
     private measureNumberLabelHeight: number;
     private measureNumberLabelOffset: number;
     private measureNumberLabelOffset: number;
+    /** Whether tuplets should display ratio (3:2 instead of 3 for triplet). Default false. */
+    private tupletsRatioed: boolean;
+    /** Whether all tuplets should be bracketed (e.g. |--5--| instead of 5). Default false.
+     * If false, only tuplets given as bracketed in XML (bracket="yes") will be bracketed.
+     * (If not given in XML, bracketing is implementation-dependent according to standard)
+     */
+    private tupletsBracketed: boolean;
+    /** Whether all triplets should be bracketed. Overrides tupletsBracketed for triplets.
+     * If false, only triplets given as bracketed in XML (bracket="yes") will be bracketed.
+     * (Bracketing all triplets can be cluttering)
+     */
+    private tripletsBracketed: boolean;
     private tupletNumberLabelHeight: number;
     private tupletNumberLabelHeight: number;
     private tupletNumberYOffset: number;
     private tupletNumberYOffset: number;
     private labelMarginBorderFactor: number;
     private labelMarginBorderFactor: number;
@@ -88,7 +102,7 @@ export class EngravingRules {
     private repetitionEndingLabelYOffset: number;
     private repetitionEndingLabelYOffset: number;
     private repetitionEndingLineYLowerOffset: number;
     private repetitionEndingLineYLowerOffset: number;
     private repetitionEndingLineYUpperOffset: number;
     private repetitionEndingLineYUpperOffset: number;
-    private lyricsAlignmentStandard: TextAlignmentAndPlacement;
+    private lyricsAlignmentStandard: TextAlignmentEnum;
     private lyricsHeight: number;
     private lyricsHeight: number;
     private lyricsYOffsetToStaffHeight: number;
     private lyricsYOffsetToStaffHeight: number;
     private verticalBetweenLyricsDistance: number;
     private verticalBetweenLyricsDistance: number;
@@ -140,11 +154,17 @@ export class EngravingRules {
     private minNoteDistance: number;
     private minNoteDistance: number;
     private subMeasureXSpacingThreshold: number;
     private subMeasureXSpacingThreshold: number;
     private measureDynamicsMaxScalingFactor: number;
     private measureDynamicsMaxScalingFactor: number;
+    private wholeRestXShiftVexflow: number;
     private maxInstructionsConstValue: number;
     private maxInstructionsConstValue: number;
     private noteDistances: number[] = [1.0, 1.0, 1.3, 1.6, 2.0, 2.5, 3.0, 4.0];
     private noteDistances: number[] = [1.0, 1.0, 1.3, 1.6, 2.0, 2.5, 3.0, 4.0];
     private noteDistancesScalingFactors: number[] = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0];
     private noteDistancesScalingFactors: number[] = [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0];
     private durationDistanceDict: {[_: number]: number; } = {};
     private durationDistanceDict: {[_: number]: number; } = {};
     private durationScalingDistanceDict: {[_: number]: number; } = {};
     private durationScalingDistanceDict: {[_: number]: number; } = {};
+    private renderComposer: boolean;
+    private renderTitle: boolean;
+    private renderSubtitle: boolean;
+    private renderLyricist: boolean;
+    private renderInstrumentNames: boolean;
 
 
     constructor() {
     constructor() {
         // global variables
         // global variables
@@ -158,9 +178,11 @@ export class EngravingRules {
         this.sheetAuthorHeight = 2.0;
         this.sheetAuthorHeight = 2.0;
 
 
         // Staff sizing Variables
         // Staff sizing Variables
+        this.compactMode = false;
         this.pagePlacementEnum = PagePlacementEnum.Down;
         this.pagePlacementEnum = PagePlacementEnum.Down;
         this.pageHeight = 100001.0;
         this.pageHeight = 100001.0;
         this.pageTopMargin = 5.0;
         this.pageTopMargin = 5.0;
+        this.pageTopMarginNarrow = 0.0; // for compact mode
         this.pageBottomMargin = 5.0;
         this.pageBottomMargin = 5.0;
         this.pageLeftMargin = 5.0;
         this.pageLeftMargin = 5.0;
         this.pageRightMargin = 5.0;
         this.pageRightMargin = 5.0;
@@ -218,7 +240,7 @@ export class EngravingRules {
         this.graceNoteScalingFactor = 0.6;
         this.graceNoteScalingFactor = 0.6;
         this.graceNoteXOffset = 0.2;
         this.graceNoteXOffset = 0.2;
 
 
-        // GraceNote Variables
+        // Wedge Variables
         this.wedgeOpeningLength = 1.2;
         this.wedgeOpeningLength = 1.2;
         this.wedgeMeasureEndOpeningLength = 0.75;
         this.wedgeMeasureEndOpeningLength = 0.75;
         this.wedgeMeasureBeginOpeningLength = 0.75;
         this.wedgeMeasureBeginOpeningLength = 0.75;
@@ -230,8 +252,8 @@ export class EngravingRules {
         this.wedgeMinLength = 2.0;
         this.wedgeMinLength = 2.0;
         this.distanceBetweenAdjacentDynamics = 0.75;
         this.distanceBetweenAdjacentDynamics = 0.75;
 
 
-        // GraceNote Variables
-        this.tempoChangeMeasureValitidy = 4;
+        // Tempo Variables
+        this.tempoChangeMeasureValidity = 4;
         this.tempoContinousFactor = 0.7;
         this.tempoContinousFactor = 0.7;
 
 
         // various
         // various
@@ -241,15 +263,18 @@ export class EngravingRules {
         this.chordSymbolTextHeight = 2.0;
         this.chordSymbolTextHeight = 2.0;
         this.fingeringLabelFontHeight = 1.7;
         this.fingeringLabelFontHeight = 1.7;
 
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Tuplets, MeasureNumber and TupletNumber Labels
         this.measureNumberLabelHeight = 1.5 * EngravingRules.unit;
         this.measureNumberLabelHeight = 1.5 * EngravingRules.unit;
         this.measureNumberLabelOffset = 2;
         this.measureNumberLabelOffset = 2;
+        this.tupletsRatioed = false;
+        this.tupletsBracketed = false;
+        this.tripletsBracketed = false; // special setting for triplets, overrides tuplet setting (for triplets only)
         this.tupletNumberLabelHeight = 1.5 * EngravingRules.unit;
         this.tupletNumberLabelHeight = 1.5 * EngravingRules.unit;
         this.tupletNumberYOffset = 0.5;
         this.tupletNumberYOffset = 0.5;
         this.labelMarginBorderFactor = 0.1;
         this.labelMarginBorderFactor = 0.1;
         this.tupletVerticalLineLength = 0.5;
         this.tupletVerticalLineLength = 0.5;
 
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Slur and Tie variables
         this.bezierCurveStepSize = 1000;
         this.bezierCurveStepSize = 1000;
         this.calculateCurveParametersArrays();
         this.calculateCurveParametersArrays();
         this.tieGhostObjectWidth = 0.75;
         this.tieGhostObjectWidth = 0.75;
@@ -266,7 +291,7 @@ export class EngravingRules {
         this.slurTangentMaxAngle = 80.0;
         this.slurTangentMaxAngle = 80.0;
         this.slursStartingAtSameStaffEntryYOffset = 0.8;
         this.slursStartingAtSameStaffEntryYOffset = 0.8;
 
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Repetitions
         this.repetitionEndingLabelHeight = 2.0;
         this.repetitionEndingLabelHeight = 2.0;
         this.repetitionEndingLabelXOffset = 0.5;
         this.repetitionEndingLabelXOffset = 0.5;
         this.repetitionEndingLabelYOffset = 0.3;
         this.repetitionEndingLabelYOffset = 0.3;
@@ -274,7 +299,7 @@ export class EngravingRules {
         this.repetitionEndingLineYUpperOffset = 0.3;
         this.repetitionEndingLineYUpperOffset = 0.3;
 
 
         // Lyrics
         // Lyrics
-        this.lyricsAlignmentStandard = TextAlignmentAndPlacement.LeftBottom; // CenterBottom and LeftBottom tested, spacing-optimized
+        this.lyricsAlignmentStandard = TextAlignmentEnum.LeftBottom; // CenterBottom and LeftBottom tested, spacing-optimized
         this.lyricsHeight = 2.0; // actually size of lyrics
         this.lyricsHeight = 2.0; // actually size of lyrics
         this.lyricsYOffsetToStaffHeight = 3.0; // distance between lyrics and staff. could partly be even lower/dynamic
         this.lyricsYOffsetToStaffHeight = 3.0; // distance between lyrics and staff. could partly be even lower/dynamic
         this.verticalBetweenLyricsDistance = 0.5;
         this.verticalBetweenLyricsDistance = 0.5;
@@ -316,6 +341,14 @@ export class EngravingRules {
         this.minNoteDistance = 2.0;
         this.minNoteDistance = 2.0;
         this.subMeasureXSpacingThreshold = 35;
         this.subMeasureXSpacingThreshold = 35;
         this.measureDynamicsMaxScalingFactor = 2.5;
         this.measureDynamicsMaxScalingFactor = 2.5;
+        this.wholeRestXShiftVexflow = -2.5; // VexFlow draws rest notes too far to the right
+
+        // Render options (whether to render specific or invisible elements)
+        this.renderComposer = true;
+        this.renderTitle = true;
+        this.renderSubtitle = true;
+        this.renderLyricist = true;
+        this.renderInstrumentNames = true;
 
 
         this.populateDictionaries();
         this.populateDictionaries();
         try {
         try {
@@ -372,6 +405,12 @@ export class EngravingRules {
     public set PagePlacement(value: PagePlacementEnum) {
     public set PagePlacement(value: PagePlacementEnum) {
         this.pagePlacementEnum = value;
         this.pagePlacementEnum = value;
     }
     }
+    public get CompactMode(): boolean {
+        return this.compactMode;
+    }
+    public set CompactMode(value: boolean) {
+        this.compactMode = value;
+    }
     public get PageHeight(): number {
     public get PageHeight(): number {
         return this.pageHeight;
         return this.pageHeight;
     }
     }
@@ -384,6 +423,12 @@ export class EngravingRules {
     public set PageTopMargin(value: number) {
     public set PageTopMargin(value: number) {
         this.pageTopMargin = value;
         this.pageTopMargin = value;
     }
     }
+    public get PageTopMarginNarrow(): number {
+        return this.pageTopMarginNarrow;
+    }
+    public set PageTopMarginNarrow(value: number) {
+        this.pageTopMarginNarrow = value;
+    }
     public get PageBottomMargin(): number {
     public get PageBottomMargin(): number {
         return this.pageBottomMargin;
         return this.pageBottomMargin;
     }
     }
@@ -708,11 +753,11 @@ export class EngravingRules {
     public set DistanceBetweenAdjacentDynamics(value: number) {
     public set DistanceBetweenAdjacentDynamics(value: number) {
         this.distanceBetweenAdjacentDynamics = value;
         this.distanceBetweenAdjacentDynamics = value;
     }
     }
-    public get TempoChangeMeasureValitidy(): number {
-        return this.tempoChangeMeasureValitidy;
+    public get TempoChangeMeasureValidity(): number {
+        return this.tempoChangeMeasureValidity;
     }
     }
-    public set TempoChangeMeasureValitidy(value: number) {
-        this.tempoChangeMeasureValitidy = value;
+    public set TempoChangeMeasureValidity(value: number) {
+        this.tempoChangeMeasureValidity = value;
     }
     }
     public get TempoContinousFactor(): number {
     public get TempoContinousFactor(): number {
         return this.tempoContinousFactor;
         return this.tempoContinousFactor;
@@ -762,6 +807,24 @@ export class EngravingRules {
     public set MeasureNumberLabelOffset(value: number) {
     public set MeasureNumberLabelOffset(value: number) {
         this.measureNumberLabelOffset = value;
         this.measureNumberLabelOffset = value;
     }
     }
+    public get TupletsRatioed(): boolean {
+        return this.tupletsRatioed;
+    }
+    public set TupletsRatioed(value: boolean) {
+        this.tupletsRatioed = value;
+    }
+    public get TupletsBracketed(): boolean {
+        return this.tupletsBracketed;
+    }
+    public set TupletsBracketed(value: boolean) {
+        this.tupletsBracketed = value;
+    }
+    public get TripletsBracketed(): boolean {
+        return this.tripletsBracketed;
+    }
+    public set TripletsBracketed(value: boolean) {
+        this.tripletsBracketed = value;
+    }
     public get TupletNumberLabelHeight(): number {
     public get TupletNumberLabelHeight(): number {
         return this.tupletNumberLabelHeight;
         return this.tupletNumberLabelHeight;
     }
     }
@@ -816,10 +879,10 @@ export class EngravingRules {
     public set RepetitionEndingLineYUpperOffset(value: number) {
     public set RepetitionEndingLineYUpperOffset(value: number) {
         this.repetitionEndingLineYUpperOffset = value;
         this.repetitionEndingLineYUpperOffset = value;
     }
     }
-    public get LyricsAlignmentStandard(): TextAlignmentAndPlacement {
+    public get LyricsAlignmentStandard(): TextAlignmentEnum {
         return this.lyricsAlignmentStandard;
         return this.lyricsAlignmentStandard;
     }
     }
-    public set LyricsAlignmentStandard(value: TextAlignmentAndPlacement) {
+    public set LyricsAlignmentStandard(value: TextAlignmentEnum) {
         this.lyricsAlignmentStandard = value;
         this.lyricsAlignmentStandard = value;
     }
     }
     public get LyricsHeight(): number {
     public get LyricsHeight(): number {
@@ -1128,6 +1191,12 @@ export class EngravingRules {
     public set MeasureDynamicsMaxScalingFactor(value: number) {
     public set MeasureDynamicsMaxScalingFactor(value: number) {
         this.measureDynamicsMaxScalingFactor = value;
         this.measureDynamicsMaxScalingFactor = value;
     }
     }
+    public get WholeRestXShiftVexflow(): number {
+        return this.wholeRestXShiftVexflow;
+    }
+    public set WholeRestXShiftVexflow(value: number) {
+        this.wholeRestXShiftVexflow = value;
+    }
     public get MaxInstructionsConstValue(): number {
     public get MaxInstructionsConstValue(): number {
         return this.maxInstructionsConstValue;
         return this.maxInstructionsConstValue;
     }
     }
@@ -1152,6 +1221,36 @@ export class EngravingRules {
     public get DurationScalingDistanceDict(): {[_: number]: number; } {
     public get DurationScalingDistanceDict(): {[_: number]: number; } {
         return this.durationScalingDistanceDict;
         return this.durationScalingDistanceDict;
     }
     }
+    public get RenderComposer(): boolean {
+        return this.renderComposer;
+    }
+    public set RenderComposer(value: boolean) {
+        this.renderComposer = value;
+    }
+    public get RenderTitle(): boolean {
+        return this.renderTitle;
+    }
+    public set RenderTitle(value: boolean) {
+        this.renderTitle = value;
+    }
+    public get RenderSubtitle(): boolean {
+        return this.renderSubtitle;
+    }
+    public set RenderSubtitle(value: boolean) {
+        this.renderSubtitle = value;
+    }
+    public get RenderLyricist(): boolean {
+        return this.renderLyricist;
+    }
+    public set RenderLyricist(value: boolean) {
+        this.renderLyricist = value;
+    }
+    public get RenderInstrumentNames(): boolean {
+        return this.renderInstrumentNames;
+    }
+    public set RenderInstrumentNames(value: boolean) {
+        this.renderInstrumentNames = value;
+    }
 
 
     /**
     /**
      * This method maps NoteDurations to Distances and DistancesScalingFactors.
      * This method maps NoteDurations to Distances and DistancesScalingFactors.

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

@@ -1,4 +1,4 @@
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {Label} from "../Label";
 import {Label} from "../Label";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
@@ -23,7 +23,7 @@ export class GraphicalChordSymbolContainer extends GraphicalObject {
     }
     }
     private calculateLabel(textHeight: number, transposeHalftones: number): void {
     private calculateLabel(textHeight: number, transposeHalftones: number): void {
         const text: string = ChordSymbolContainer.calculateChordText(this.chordSymbolContainer, transposeHalftones);
         const text: string = ChordSymbolContainer.calculateChordText(this.chordSymbolContainer, transposeHalftones);
-        this.graphicalLabel = new GraphicalLabel(new Label(text), textHeight, TextAlignmentAndPlacement.CenterBottom, this.boundingBox);
+        this.graphicalLabel = new GraphicalLabel(new Label(text), textHeight, TextAlignmentEnum.CenterBottom, this.boundingBox);
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
         this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
     }
     }
 }
 }

+ 13 - 13
src/MusicalScore/Graphical/GraphicalLabel.ts

@@ -1,5 +1,5 @@
 import {Label} from "../Label";
 import {Label} from "../Label";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {Clickable} from "./Clickable";
 import {Clickable} from "./Clickable";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
 import {EngravingRules} from "./EngravingRules";
 import {EngravingRules} from "./EngravingRules";
@@ -18,7 +18,7 @@ export class GraphicalLabel extends Clickable {
      * @param alignment Alignement like left, right, top, ...
      * @param alignment Alignement like left, right, top, ...
      * @param parent Parent Bounding Box where the label is attached to
      * @param parent Parent Bounding Box where the label is attached to
      */
      */
-    constructor(label: Label, textHeight: number, alignment: TextAlignmentAndPlacement, parent: BoundingBox = undefined) {
+    constructor(label: Label, textHeight: number, alignment: TextAlignmentEnum, parent: BoundingBox = undefined) {
         super();
         super();
         this.label = label;
         this.label = label;
         this.boundingBox = new BoundingBox(this, parent);
         this.boundingBox = new BoundingBox(this, parent);
@@ -50,55 +50,55 @@ export class GraphicalLabel extends Clickable {
         const bbox: BoundingBox = this.PositionAndShape;
         const bbox: BoundingBox = this.PositionAndShape;
 
 
         switch (this.Label.textAlignment) {
         switch (this.Label.textAlignment) {
-            case TextAlignmentAndPlacement.CenterBottom:
+            case TextAlignmentEnum.CenterBottom:
                 bbox.BorderTop = -height;
                 bbox.BorderTop = -height;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = 0;
                 bbox.BorderBottom = 0;
                 bbox.BorderRight = width / 2;
                 bbox.BorderRight = width / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.CenterCenter:
+            case TextAlignmentEnum.CenterCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = width / 2;
                 bbox.BorderRight = width / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.CenterTop:
+            case TextAlignmentEnum.CenterTop:
                 bbox.BorderTop = 0;
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = height;
                 bbox.BorderBottom = height;
                 bbox.BorderRight = width / 2;
                 bbox.BorderRight = width / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.LeftBottom:
+            case TextAlignmentEnum.LeftBottom:
                 bbox.BorderTop = -height;
                 bbox.BorderTop = -height;
-                bbox.BorderLeft = -1;
+                bbox.BorderLeft = 0;
                 bbox.BorderBottom = 0;
                 bbox.BorderBottom = 0;
-                bbox.BorderRight = width - 1;
+                bbox.BorderRight = width;
                 break;
                 break;
-            case TextAlignmentAndPlacement.LeftCenter:
+            case TextAlignmentEnum.LeftCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = 0;
                 bbox.BorderLeft = 0;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = width;
                 bbox.BorderRight = width;
                 break;
                 break;
-            case TextAlignmentAndPlacement.LeftTop:
+            case TextAlignmentEnum.LeftTop:
                 bbox.BorderTop = 0;
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = 0;
                 bbox.BorderLeft = 0;
                 bbox.BorderBottom = height;
                 bbox.BorderBottom = height;
                 bbox.BorderRight = width;
                 bbox.BorderRight = width;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightBottom:
+            case TextAlignmentEnum.RightBottom:
                 bbox.BorderTop = -height;
                 bbox.BorderTop = -height;
                 bbox.BorderLeft = -width;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = 0;
                 bbox.BorderBottom = 0;
                 bbox.BorderRight = 0;
                 bbox.BorderRight = 0;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightCenter:
+            case TextAlignmentEnum.RightCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = -width;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = 0;
                 bbox.BorderRight = 0;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightTop:
+            case TextAlignmentEnum.RightTop:
                 bbox.BorderTop = 0;
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = -width;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = height;
                 bbox.BorderBottom = height;

+ 9 - 6
src/MusicalScore/Graphical/GraphicalLyricEntry.ts

@@ -5,7 +5,7 @@ import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
 import {Label} from "../Label";
 import {Label} from "../Label";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
 import {PointF2D} from "../../Common/DataObjects/PointF2D";
 import { EngravingRules } from "./EngravingRules";
 import { EngravingRules } from "./EngravingRules";
-import { TextAlignmentAndPlacement } from "../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
 
 
 /**
 /**
  * The graphical counterpart of a [[LyricsEntry]]
  * The graphical counterpart of a [[LyricsEntry]]
@@ -19,21 +19,24 @@ export class GraphicalLyricEntry {
     constructor(lyricsEntry: LyricsEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricsHeight: number, staffHeight: number) {
     constructor(lyricsEntry: LyricsEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricsHeight: number, staffHeight: number) {
         this.lyricsEntry = lyricsEntry;
         this.lyricsEntry = lyricsEntry;
         this.graphicalStaffEntry = graphicalStaffEntry;
         this.graphicalStaffEntry = graphicalStaffEntry;
-        let lyricsTextAlignment: TextAlignmentAndPlacement = EngravingRules.Rules.LyricsAlignmentStandard;
+        const lyricsTextAlignment: TextAlignmentEnum = EngravingRules.Rules.LyricsAlignmentStandard;
         // for small notes with long text, use center alignment
         // for small notes with long text, use center alignment
         // TODO use this, fix center+left alignment combination spacing
         // TODO use this, fix center+left alignment combination spacing
         if (lyricsEntry.Text.length >= 4
         if (lyricsEntry.Text.length >= 4
             && lyricsEntry.Parent.Notes[0].Length.Denominator > 4
             && lyricsEntry.Parent.Notes[0].Length.Denominator > 4
-            && lyricsTextAlignment === TextAlignmentAndPlacement.LeftBottom) {
-            lyricsTextAlignment = TextAlignmentAndPlacement.CenterBottom;
+            && lyricsTextAlignment === TextAlignmentEnum.LeftBottom) {
+            // lyricsTextAlignment = TextAlignmentAndPlacement.CenterBottom;
         }
         }
         this.graphicalLabel = new GraphicalLabel(
         this.graphicalLabel = new GraphicalLabel(
             new Label(lyricsEntry.Text),
             new Label(lyricsEntry.Text),
             lyricsHeight,
             lyricsHeight,
-            EngravingRules.Rules.LyricsAlignmentStandard,
+            lyricsTextAlignment,
             graphicalStaffEntry.PositionAndShape
             graphicalStaffEntry.PositionAndShape
         );
         );
-        this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0, staffHeight); // TODO gets reset later
+        this.graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0, staffHeight);
+        if (lyricsTextAlignment === TextAlignmentEnum.LeftBottom) {
+            this.graphicalLabel.PositionAndShape.RelativePosition.x -= 1; // make lyrics optically left-aligned
+        }
     }
     }
 
 
     public get LyricsEntry(): LyricsEntry {
     public get LyricsEntry(): LyricsEntry {

+ 87 - 37
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -31,7 +31,7 @@ import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
 import {Instrument} from "../Instrument";
 import {Instrument} from "../Instrument";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalLabel} from "./GraphicalLabel";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {VerticalGraphicalStaffEntryContainer} from "./VerticalGraphicalStaffEntryContainer";
 import {VerticalGraphicalStaffEntryContainer} from "./VerticalGraphicalStaffEntryContainer";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
@@ -161,7 +161,7 @@ export abstract class MusicSheetCalculator {
         }
         }
         this.handleStaffEntries();
         this.handleStaffEntries();
         this.calculateVerticalContainersList();
         this.calculateVerticalContainersList();
-        this.setIndecesToVerticalGraphicalContainers();
+        this.setIndicesToVerticalGraphicalContainers();
     }
     }
 
 
     /**
     /**
@@ -224,22 +224,34 @@ export abstract class MusicSheetCalculator {
         throw new Error("abstract, not implemented");
         throw new Error("abstract, not implemented");
     }
     }
 
 
-    /// <summary>
-    /// This method calculates the relative Positions of all MusicSystems.
-    /// </summary>
-    /// <param name="graphicalMusicPage"></param>
+    /** Calculates the relative Positions of all MusicSystems.
+     *
+     */
     protected calculateMusicSystemsRelativePositions(graphicalMusicPage: GraphicalMusicPage): void {
     protected calculateMusicSystemsRelativePositions(graphicalMusicPage: GraphicalMusicPage): void {
-        // xPosition is always fix
+        // xPosition is always fixed
         let relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin, 0);
         let relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin, 0);
 
 
+        if (EngravingRules.Rules.CompactMode) {
+            relativePosition.y += EngravingRules.Rules.PageTopMarginNarrow;
+        } else {
+            relativePosition.y += EngravingRules.Rules.PageTopMargin;
+        }
+
         // first System is handled extra
         // first System is handled extra
         const firstMusicSystem: MusicSystem = graphicalMusicPage.MusicSystems[0];
         const firstMusicSystem: MusicSystem = graphicalMusicPage.MusicSystems[0];
         if (graphicalMusicPage === graphicalMusicPage.Parent.MusicPages[0]) {
         if (graphicalMusicPage === graphicalMusicPage.Parent.MusicPages[0]) {
-            relativePosition.y = this.rules.PageTopMargin + this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
-                this.rules.TitleBottomDistance;
+            if (EngravingRules.Rules.RenderTitle) {
+                relativePosition.y += this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
+                    this.rules.TitleBottomDistance;
+            }
         } else {
         } else {
-            relativePosition.y = this.rules.PageTopMargin + this.rules.TitleTopDistance;
+            if (EngravingRules.Rules.RenderTitle) {
+                relativePosition.y += this.rules.PageTopMargin + this.rules.TitleTopDistance;
+            } else {
+                relativePosition.y = this.rules.PageTopMargin;
+            }
         }
         }
+
         firstMusicSystem.PositionAndShape.RelativePosition = relativePosition;
         firstMusicSystem.PositionAndShape.RelativePosition = relativePosition;
 
 
         for (let i: number = 1; i < graphicalMusicPage.MusicSystems.length; i++) {
         for (let i: number = 1; i < graphicalMusicPage.MusicSystems.length; i++) {
@@ -422,7 +434,7 @@ export abstract class MusicSheetCalculator {
     private calculateSingleMeasureNumberPlacement(measure: GraphicalMeasure, staffLine: StaffLine, musicSystem: MusicSystem): void {
     private calculateSingleMeasureNumberPlacement(measure: GraphicalMeasure, staffLine: StaffLine, musicSystem: MusicSystem): void {
         const labelNumber: string = measure.MeasureNumber.toString();
         const labelNumber: string = measure.MeasureNumber.toString();
         const graphicalLabel: GraphicalLabel = new GraphicalLabel(new Label(labelNumber), this.rules.MeasureNumberLabelHeight,
         const graphicalLabel: GraphicalLabel = new GraphicalLabel(new Label(labelNumber), this.rules.MeasureNumberLabelHeight,
-                                                                  TextAlignmentAndPlacement.LeftBottom);
+                                                                  TextAlignmentEnum.LeftBottom);
 
 
         const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
         const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
 
 
@@ -435,7 +447,7 @@ export abstract class MusicSheetCalculator {
             measure.PositionAndShape.RelativePosition.x - graphicalLabel.PositionAndShape.BorderMarginLeft;
             measure.PositionAndShape.RelativePosition.x - graphicalLabel.PositionAndShape.BorderMarginLeft;
         let relativeY: number;
         let relativeY: number;
 
 
-        // and the corresponding SkyLine indeces
+        // and the corresponding SkyLine indices
         let start: number = relativeX;
         let start: number = relativeX;
         let end: number = relativeX - graphicalLabel.PositionAndShape.BorderLeft + graphicalLabel.PositionAndShape.BorderMarginRight;
         let end: number = relativeX - graphicalLabel.PositionAndShape.BorderLeft + graphicalLabel.PositionAndShape.BorderMarginRight;
 
 
@@ -542,7 +554,8 @@ export abstract class MusicSheetCalculator {
                 if (this.leadSheet) {
                 if (this.leadSheet) {
                     position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
                     position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
                 }
                 }
-                lyricsEntryLabel.PositionAndShape.RelativePosition = new PointF2D(0, position);
+                const previousRelativeX: number = lyricsEntryLabel.PositionAndShape.RelativePosition.x;
+                lyricsEntryLabel.PositionAndShape.RelativePosition = new PointF2D(previousRelativeX, position);
                 maxPosition = Math.max(maxPosition, position);
                 maxPosition = Math.max(maxPosition, position);
             }
             }
         }
         }
@@ -550,9 +563,10 @@ export abstract class MusicSheetCalculator {
         // update BottomLine (on the whole StaffLine's length)
         // update BottomLine (on the whole StaffLine's length)
         if (lyricsStaffEntriesList.length > 0) {
         if (lyricsStaffEntriesList.length > 0) {
             const endX: number = staffLine.PositionAndShape.Size.width;
             const endX: number = staffLine.PositionAndShape.Size.width;
-            const startX: number = lyricsStaffEntriesList[0].PositionAndShape.RelativePosition.x +
+            let startX: number = lyricsStaffEntriesList[0].PositionAndShape.RelativePosition.x +
                 lyricsStaffEntriesList[0].PositionAndShape.BorderMarginLeft +
                 lyricsStaffEntriesList[0].PositionAndShape.BorderMarginLeft +
                 lyricsStaffEntriesList[0].parentMeasure.PositionAndShape.RelativePosition.x;
                 lyricsStaffEntriesList[0].parentMeasure.PositionAndShape.RelativePosition.x;
+            startX = startX > endX ? endX : startX;
             skyBottomLineCalculator.updateBottomLineInRange(startX, endX, maxPosition);
             skyBottomLineCalculator.updateBottomLineInRange(startX, endX, maxPosition);
         }
         }
         return lyricsStaffEntriesList;
         return lyricsStaffEntriesList;
@@ -896,17 +910,18 @@ export abstract class MusicSheetCalculator {
                              combinedString: string,
                              combinedString: string,
                              style: FontStyles,
                              style: FontStyles,
                              placement: PlacementEnum,
                              placement: PlacementEnum,
-                             fontHeight: number): GraphicalLabel {
-        const label: Label = new Label(combinedString);
+                             fontHeight: number,
+                             textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom): GraphicalLabel {
+        const label: Label = new Label(combinedString, textAlignment);
         label.fontHeight = fontHeight;
         label.fontHeight = fontHeight;
 
 
         // TODO_RR: TextHeight from first Entry
         // TODO_RR: TextHeight from first Entry
-        const graphLabel: GraphicalLabel = new GraphicalLabel(label, fontHeight, TextAlignmentAndPlacement.CenterBottom, staffLine.PositionAndShape);
+        const graphLabel: GraphicalLabel = new GraphicalLabel(label, fontHeight, label.textAlignment, staffLine.PositionAndShape);
         graphLabel.Label.fontStyle = style;
         graphLabel.Label.fontStyle = style;
         const marginFactor: number = 1.1;
         const marginFactor: number = 1.1;
 
 
         if (placement === PlacementEnum.Below) {
         if (placement === PlacementEnum.Below) {
-            graphLabel.Label.textAlignment = TextAlignmentAndPlacement.LeftTop;
+            graphLabel.Label.textAlignment = TextAlignmentEnum.LeftTop;
         }
         }
 
 
         graphLabel.setLabelPositionAndShapeBorders();
         graphLabel.setLabelPositionAndShapeBorders();
@@ -983,27 +998,36 @@ export abstract class MusicSheetCalculator {
                 instantaniousTempo.Placement = PlacementEnum.Above;
                 instantaniousTempo.Placement = PlacementEnum.Above;
 
 
                 // if an InstantaniousTempoExpression exists at the very beginning then
                 // if an InstantaniousTempoExpression exists at the very beginning then
-                // check if expression is positioned at ever first StaffEntry and
+                // check if expression is positioned at first ever StaffEntry and
                 // check if MusicSystem is first MusicSystem
                 // check if MusicSystem is first MusicSystem
                 if (staffLine.Measures[0].staffEntries.length > 0 &&
                 if (staffLine.Measures[0].staffEntries.length > 0 &&
                     Math.abs(relative.x - staffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x) === 0 &&
                     Math.abs(relative.x - staffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x) === 0 &&
                     staffLine.ParentMusicSystem === staffLine.ParentMusicSystem.Parent.MusicSystems[0]) {
                     staffLine.ParentMusicSystem === staffLine.ParentMusicSystem.Parent.MusicSystems[0]) {
                     const firstInstructionEntry: GraphicalStaffEntry = staffLine.Measures[0].FirstInstructionStaffEntry;
                     const firstInstructionEntry: GraphicalStaffEntry = staffLine.Measures[0].FirstInstructionStaffEntry;
                     if (firstInstructionEntry) {
                     if (firstInstructionEntry) {
-                        const lastIntruction: AbstractGraphicalInstruction = firstInstructionEntry.GraphicalInstructions.last();
-                        relative.x = lastIntruction.PositionAndShape.RelativePosition.x;
+                        const lastInstruction: AbstractGraphicalInstruction = firstInstructionEntry.GraphicalInstructions.last();
+                        relative.x = lastInstruction.PositionAndShape.RelativePosition.x;
+                    }
+                    if (EngravingRules.Rules.CompactMode) {
+                        relative.x = staffLine.PositionAndShape.RelativePosition.x +
+                            staffLine.Measures[0].PositionAndShape.RelativePosition.x;
                     }
                     }
                 }
                 }
             }
             }
 
 
             // const addAtLastList: GraphicalObject[] = [];
             // const addAtLastList: GraphicalObject[] = [];
             for (const entry of multiTempoExpression.EntriesList) {
             for (const entry of multiTempoExpression.EntriesList) {
+                let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
+                if (EngravingRules.Rules.CompactMode) {
+                    textAlignment = TextAlignmentEnum.LeftBottom;
+                }
                 const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
                 const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
                                                                        relative,
                                                                        relative,
                                                                        entry.label,
                                                                        entry.label,
                                                                        multiTempoExpression.getFontstyleOfFirstEntry(),
                                                                        multiTempoExpression.getFontstyleOfFirstEntry(),
                                                                        entry.Expression.Placement,
                                                                        entry.Expression.Placement,
-                                                                       EngravingRules.Rules.UnknownTextHeight);
+                                                                       EngravingRules.Rules.UnknownTextHeight,
+                                                                       textAlignment);
 
 
                 if (entry.Expression instanceof InstantaneousTempoExpression) {
                 if (entry.Expression instanceof InstantaneousTempoExpression) {
                     let alreadyAdded: boolean = false;
                     let alreadyAdded: boolean = false;
@@ -1098,10 +1122,23 @@ export abstract class MusicSheetCalculator {
                                openTuplets: Tuplet[], openBeams: Beam[],
                                openTuplets: Tuplet[], openBeams: Beam[],
                                octaveShiftValue: OctaveEnum, linkedNotes: Note[] = undefined,
                                octaveShiftValue: OctaveEnum, linkedNotes: Note[] = undefined,
                                sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
                                sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
+        let voiceEntryHasPrintableNotes: boolean = false;
+        for (const note of voiceEntry.Notes) {
+            if (note.PrintObject) {
+                voiceEntryHasPrintableNotes = true;
+                break;
+            }
+        }
+        if (!voiceEntryHasPrintableNotes) {
+            return; // do not create a GraphicalVoiceEntry without graphical notes in it, will cause problems
+        }
         this.calculateStemDirectionFromVoices(voiceEntry);
         this.calculateStemDirectionFromVoices(voiceEntry);
         const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
         const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
         for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
         for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
             const note: Note = voiceEntry.Notes[idx];
             const note: Note = voiceEntry.Notes[idx];
+            if (note === undefined || !note.PrintObject) {
+                continue;
+            }
             if (sourceStaffEntry !== undefined && sourceStaffEntry.Link !== undefined && linkedNotes !== undefined && linkedNotes.indexOf(note) > -1) {
             if (sourceStaffEntry !== undefined && sourceStaffEntry.Link !== undefined && linkedNotes !== undefined && linkedNotes.indexOf(note) > -1) {
                 continue;
                 continue;
             }
             }
@@ -1163,11 +1200,14 @@ export abstract class MusicSheetCalculator {
     }
     }
 
 
     protected maxInstrNameLabelLength(): number {
     protected maxInstrNameLabelLength(): number {
+        if (!EngravingRules.Rules.RenderInstrumentNames) {
+            return 0;
+        }
         let maxLabelLength: number = 0.0;
         let maxLabelLength: number = 0.0;
         for (const instrument of this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
         for (const instrument of this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
             if (instrument.Voices.length > 0 && instrument.Voices[0].Visible) {
             if (instrument.Voices.length > 0 && instrument.Voices[0].Visible) {
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                    instrument.NameLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentAndPlacement.LeftCenter);
+                    instrument.NameLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentEnum.LeftCenter);
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 maxLabelLength = Math.max(maxLabelLength, graphicalLabel.PositionAndShape.MarginSize.width);
                 maxLabelLength = Math.max(maxLabelLength, graphicalLabel.PositionAndShape.MarginSize.width);
             }
             }
@@ -1177,25 +1217,33 @@ export abstract class MusicSheetCalculator {
 
 
     protected calculateSheetLabelBoundingBoxes(): void {
     protected calculateSheetLabelBoundingBoxes(): void {
         const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
         const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
-        if (musicSheet.Title !== undefined) {
-            const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentAndPlacement.CenterBottom);
+        if (musicSheet.Title !== undefined && EngravingRules.Rules.RenderTitle) {
+            const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom);
             this.graphicalMusicSheet.Title = title;
             this.graphicalMusicSheet.Title = title;
             title.setLabelPositionAndShapeBorders();
             title.setLabelPositionAndShapeBorders();
+        } else if (!EngravingRules.Rules.RenderTitle) {
+            this.graphicalMusicSheet.Title = undefined; // clear label if rendering it was disabled after last render
         }
         }
-        if (musicSheet.Subtitle !== undefined) {
-            const subtitle: GraphicalLabel = new GraphicalLabel(musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentAndPlacement.CenterCenter);
+        if (musicSheet.Subtitle !== undefined && EngravingRules.Rules.RenderSubtitle) {
+            const subtitle: GraphicalLabel = new GraphicalLabel(musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter);
             this.graphicalMusicSheet.Subtitle = subtitle;
             this.graphicalMusicSheet.Subtitle = subtitle;
             subtitle.setLabelPositionAndShapeBorders();
             subtitle.setLabelPositionAndShapeBorders();
+        } else if (!EngravingRules.Rules.RenderSubtitle) {
+            this.graphicalMusicSheet.Subtitle = undefined;
         }
         }
-        if (musicSheet.Composer !== undefined) {
-            const composer: GraphicalLabel = new GraphicalLabel(musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentAndPlacement.RightCenter);
+        if (musicSheet.Composer !== undefined && EngravingRules.Rules.RenderComposer) {
+            const composer: GraphicalLabel = new GraphicalLabel(musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter);
             this.graphicalMusicSheet.Composer = composer;
             this.graphicalMusicSheet.Composer = composer;
             composer.setLabelPositionAndShapeBorders();
             composer.setLabelPositionAndShapeBorders();
+        } else if (!EngravingRules.Rules.RenderComposer) {
+            this.graphicalMusicSheet.Composer = undefined;
         }
         }
-        if (musicSheet.Lyricist !== undefined) {
-            const lyricist: GraphicalLabel = new GraphicalLabel(musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentAndPlacement.LeftCenter);
+        if (musicSheet.Lyricist !== undefined && EngravingRules.Rules.RenderLyricist) {
+            const lyricist: GraphicalLabel = new GraphicalLabel(musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter);
             this.graphicalMusicSheet.Lyricist = lyricist;
             this.graphicalMusicSheet.Lyricist = lyricist;
             lyricist.setLabelPositionAndShapeBorders();
             lyricist.setLabelPositionAndShapeBorders();
+        } else if (!EngravingRules.Rules.RenderLyricist) {
+            this.graphicalMusicSheet.Lyricist = undefined;
         }
         }
     }
     }
 
 
@@ -1452,7 +1500,7 @@ export abstract class MusicSheetCalculator {
         }
         }
     }
     }
 
 
-    private setIndecesToVerticalGraphicalContainers(): void {
+    private setIndicesToVerticalGraphicalContainers(): void {
         for (let i: number = 0; i < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length; i++) {
         for (let i: number = 0; i < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length; i++) {
             this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].Index = i;
             this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].Index = i;
         }
         }
@@ -1895,9 +1943,12 @@ export abstract class MusicSheetCalculator {
             // start- and End margins from the text Labels
             // start- and End margins from the text Labels
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
                 lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
                 lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+
             const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
             const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffentry.PositionAndShape.RelativePosition.x +
                 endStaffentry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
                 nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
                 nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
             const y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
             const y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
             let numberOfDashes: number = 1;
             let numberOfDashes: number = 1;
@@ -1984,7 +2035,7 @@ export abstract class MusicSheetCalculator {
      * @param {number} y
      * @param {number} y
      */
      */
     private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
     private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
-        const dash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentAndPlacement.CenterBottom);
+        const dash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
         dash.setLabelPositionAndShapeBorders();
         dash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(dash);
         staffLine.LyricsDashes.push(dash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2032,8 +2083,7 @@ export abstract class MusicSheetCalculator {
             // start- and End margins from the text Labels
             // start- and End margins from the text Labels
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
-                startStaffEntry.PositionAndShape.BorderMarginRight;
-                // + lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
+                lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
                 // + startStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
                 // + startStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
             const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
             const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffEntry.PositionAndShape.RelativePosition.x +
                 endStaffEntry.PositionAndShape.RelativePosition.x +
@@ -2099,7 +2149,7 @@ export abstract class MusicSheetCalculator {
      * @returns {number}
      * @returns {number}
      */
      */
     private calculateRightAndLeftDashesForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): number {
     private calculateRightAndLeftDashesForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): number {
-        const leftDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentAndPlacement.CenterBottom);
+        const leftDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
         leftDash.setLabelPositionAndShapeBorders();
         leftDash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(leftDash);
         staffLine.LyricsDashes.push(leftDash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2108,7 +2158,7 @@ export abstract class MusicSheetCalculator {
         leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
         leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
         const leftDashRelative: PointF2D = new PointF2D(startX, y);
         const leftDashRelative: PointF2D = new PointF2D(startX, y);
         leftDash.PositionAndShape.RelativePosition = leftDashRelative;
         leftDash.PositionAndShape.RelativePosition = leftDashRelative;
-        const rightDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentAndPlacement.CenterBottom);
+        const rightDash: GraphicalLabel = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom);
         rightDash.setLabelPositionAndShapeBorders();
         rightDash.setLabelPositionAndShapeBorders();
         staffLine.LyricsDashes.push(rightDash);
         staffLine.LyricsDashes.push(rightDash);
         rightDash.PositionAndShape.Parent = staffLine.PositionAndShape;
         rightDash.PositionAndShape.Parent = staffLine.PositionAndShape;

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

@@ -10,7 +10,7 @@ import {PointF2D} from "../../Common/DataObjects/PointF2D";
 import {GraphicalRectangle} from "./GraphicalRectangle";
 import {GraphicalRectangle} from "./GraphicalRectangle";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {Label} from "../Label";
 import {Label} from "../Label";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {ArgumentOutOfRangeException} from "../Exceptions";
 import {ArgumentOutOfRangeException} from "../Exceptions";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
 import {SelectionEndSymbol} from "./SelectionEndSymbol";
 import {SelectionEndSymbol} from "./SelectionEndSymbol";
@@ -24,7 +24,6 @@ import {Instrument} from "../Instrument";
 import {MusicSymbolDrawingStyle, PhonicScoreModes} from "./DrawingMode";
 import {MusicSymbolDrawingStyle, PhonicScoreModes} from "./DrawingMode";
 import {GraphicalObject} from "./GraphicalObject";
 import {GraphicalObject} from "./GraphicalObject";
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
-import { unitInPixels } from "./VexFlow/VexFlowMusicSheetDrawer";
 
 
 /**
 /**
  * Draw a [[GraphicalMusicSheet]] (through the .drawSheet method)
  * Draw a [[GraphicalMusicSheet]] (through the .drawSheet method)
@@ -39,7 +38,7 @@ import { unitInPixels } from "./VexFlow/VexFlowMusicSheetDrawer";
  * This class also includes the resizing and positioning of the symbols due to user interaction like zooming or panning.
  * This class also includes the resizing and positioning of the symbols due to user interaction like zooming or panning.
  */
  */
 export abstract class MusicSheetDrawer {
 export abstract class MusicSheetDrawer {
-    public drawingParameters: DrawingParameters = new DrawingParameters();
+    public drawingParameters: DrawingParameters;
     public splitScreenLineColor: number;
     public splitScreenLineColor: number;
     public midiPlaybackAvailable: boolean;
     public midiPlaybackAvailable: boolean;
     public drawableBoundingBoxElement: string = process.env.DRAW_BOUNDING_BOX_ELEMENT;
     public drawableBoundingBoxElement: string = process.env.DRAW_BOUNDING_BOX_ELEMENT;
@@ -53,14 +52,10 @@ export abstract class MusicSheetDrawer {
     private phonicScoreMode: PhonicScoreModes = PhonicScoreModes.Manual;
     private phonicScoreMode: PhonicScoreModes = PhonicScoreModes.Manual;
 
 
     constructor(textMeasurer: ITextMeasurer,
     constructor(textMeasurer: ITextMeasurer,
-                isPreviewImageDrawer: boolean = false) {
+                drawingParameters: DrawingParameters) {
         this.textMeasurer = textMeasurer;
         this.textMeasurer = textMeasurer;
         this.splitScreenLineColor = -1;
         this.splitScreenLineColor = -1;
-        if (isPreviewImageDrawer) {
-            this.drawingParameters.setForThumbmail();
-        } else {
-            this.drawingParameters.setForAllOn();
-        }
+        this.drawingParameters = drawingParameters;
     }
     }
 
 
     public set Mode(value: PhonicScoreModes) {
     public set Mode(value: PhonicScoreModes) {
@@ -150,36 +145,36 @@ export abstract class MusicSheetDrawer {
         const bitmapWidth: number = Math.ceil(widthInPixel);
         const bitmapWidth: number = Math.ceil(widthInPixel);
         const bitmapHeight: number = Math.ceil(heightInPixel * 1.2);
         const bitmapHeight: number = Math.ceil(heightInPixel * 1.2);
         switch (label.textAlignment) {
         switch (label.textAlignment) {
-            // the following have to match the Border settings in GraphicalLabel.setLabelPositionAndShapeBorders()
-            // TODO unify alignment shifts and our label/bbox position, which does not correspond to screenposition
-            case TextAlignmentAndPlacement.LeftTop:
+            // Adjust the OSMD-calculated positions to rendering coordinates
+            // These have to match the Border settings in GraphicalLabel.setLabelPositionAndShapeBorders()
+            // TODO isn't this a Vexflow-specific transformation that should be in VexflowMusicSheetDrawer?
+            case TextAlignmentEnum.LeftTop:
                 break;
                 break;
-            case TextAlignmentAndPlacement.LeftCenter:
+            case TextAlignmentEnum.LeftCenter:
                 screenPosition.y -= bitmapHeight / 2;
                 screenPosition.y -= bitmapHeight / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.LeftBottom:
+            case TextAlignmentEnum.LeftBottom:
                 screenPosition.y -= bitmapHeight;
                 screenPosition.y -= bitmapHeight;
-                screenPosition.x -= unitInPixels; // lyrics-specific to align with notes
                 break;
                 break;
-            case TextAlignmentAndPlacement.CenterTop:
+            case TextAlignmentEnum.CenterTop:
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.x -= bitmapWidth / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.CenterCenter:
+            case TextAlignmentEnum.CenterCenter:
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.y -= bitmapHeight / 2;
                 screenPosition.y -= bitmapHeight / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.CenterBottom:
+            case TextAlignmentEnum.CenterBottom:
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.y -= bitmapHeight;
                 screenPosition.y -= bitmapHeight;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightTop:
+            case TextAlignmentEnum.RightTop:
                 screenPosition.x -= bitmapWidth;
                 screenPosition.x -= bitmapWidth;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightCenter:
+            case TextAlignmentEnum.RightCenter:
                 screenPosition.x -= bitmapWidth;
                 screenPosition.x -= bitmapWidth;
                 screenPosition.y -= bitmapHeight / 2;
                 screenPosition.y -= bitmapHeight / 2;
                 break;
                 break;
-            case TextAlignmentAndPlacement.RightBottom:
+            case TextAlignmentEnum.RightBottom:
                 screenPosition.x -= bitmapWidth;
                 screenPosition.x -= bitmapWidth;
                 screenPosition.y -= bitmapHeight;
                 screenPosition.y -= bitmapHeight;
                 break;
                 break;
@@ -510,7 +505,7 @@ export abstract class MusicSheetDrawer {
 
 
             tmpRect = this.applyScreenTransformationForRect(tmpRect);
             tmpRect = this.applyScreenTransformationForRect(tmpRect);
             this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, 0.5);
             this.renderRectangle(tmpRect, <number>GraphicalLayers.Background, layer, 0.5);
-            this.renderLabel(new GraphicalLabel(new Label(dataObjectString), 0.8, TextAlignmentAndPlacement.CenterCenter),
+            this.renderLabel(new GraphicalLabel(new Label(dataObjectString), 0.8, TextAlignmentEnum.CenterCenter),
                              layer, tmpRect.width, tmpRect.height, tmpRect.height, new PointF2D(tmpRect.x, tmpRect.y + 12));
                              layer, tmpRect.width, tmpRect.height, tmpRect.height, new PointF2D(tmpRect.x, tmpRect.y + 12));
         }
         }
         layer++;
         layer++;

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

@@ -4,7 +4,7 @@ import {BoundingBox} from "./BoundingBox";
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {SourceMeasure} from "../VoiceData/SourceMeasure";
 import {SourceMeasure} from "../VoiceData/SourceMeasure";
 import {InstrumentalGroup} from "../InstrumentalGroup";
 import {InstrumentalGroup} from "../InstrumentalGroup";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {GraphicalMusicPage} from "./GraphicalMusicPage";
 import {GraphicalMusicPage} from "./GraphicalMusicPage";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalLabel} from "./GraphicalLabel";
 import {GraphicalMeasure} from "./GraphicalMeasure";
 import {GraphicalMeasure} from "./GraphicalMeasure";
@@ -279,7 +279,7 @@ export abstract class MusicSystem extends GraphicalObject {
             for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
             for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
                 const instrument: Instrument = instruments[idx];
                 const instrument: Instrument = instruments[idx];
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                    instrument.NameLabel, instrumentLabelTextHeight, TextAlignmentAndPlacement.LeftCenter, this.boundingBox
+                    instrument.NameLabel, instrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.boundingBox
                 );
                 );
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 this.labels.setValue(graphicalLabel, instrument);
                 this.labels.setValue(graphicalLabel, instrument);

+ 14 - 13
src/MusicalScore/Graphical/MusicSystemBuilder.ts

@@ -65,11 +65,13 @@ export class MusicSystemBuilder {
         // the first System - create also its Labels
         // the first System - create also its Labels
         this.currentSystemParams.currentSystem = this.initMusicSystem();
         this.currentSystemParams.currentSystem = this.initMusicSystem();
         this.layoutSystemStaves();
         this.layoutSystemStaves();
-        this.currentSystemParams.currentSystem.createMusicSystemLabel(
-            this.rules.InstrumentLabelTextHeight,
-            this.rules.SystemLabelsRightMargin,
-            this.rules.LabelMarginBorderFactor
-        );
+        if (EngravingRules.Rules.RenderInstrumentNames) {
+            this.currentSystemParams.currentSystem.createMusicSystemLabel(
+                this.rules.InstrumentLabelTextHeight,
+                this.rules.SystemLabelsRightMargin,
+                this.rules.LabelMarginBorderFactor
+            );
+        }
         this.currentPageHeight += this.currentSystemParams.currentSystem.PositionAndShape.RelativePosition.y;
         this.currentPageHeight += this.currentSystemParams.currentSystem.PositionAndShape.RelativePosition.y;
 
 
         let numberOfMeasures: number = 0;
         let numberOfMeasures: number = 0;
@@ -329,18 +331,17 @@ export class MusicSystemBuilder {
             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();
-            if (musicSystem.Parent.MusicSystems[0] === musicSystem && musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0]) {
+            if (musicSystem.Parent.MusicSystems[0] === musicSystem &&
+                musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0] &&
+                !EngravingRules.Rules.CompactMode) {
                 relativePosition.x = this.rules.FirstSystemMargin;
                 relativePosition.x = this.rules.FirstSystemMargin;
+                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
             } else {
             } else {
                 relativePosition.x = 0.0;
                 relativePosition.x = 0.0;
+                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
             }
             }
             relativePosition.y = relativeYPosition;
             relativePosition.y = relativeYPosition;
             boundingBox.RelativePosition = relativePosition;
             boundingBox.RelativePosition = relativePosition;
-            if (musicSystem.Parent.MusicSystems[0] === musicSystem && musicSystem.Parent === musicSystem.Parent.Parent.MusicPages[0]) {
-                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
-            } else {
-                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
-            }
             boundingBox.BorderLeft = 0.0;
             boundingBox.BorderLeft = 0.0;
             boundingBox.BorderTop = 0.0;
             boundingBox.BorderTop = 0.0;
             boundingBox.BorderBottom = this.rules.StaffHeight;
             boundingBox.BorderBottom = this.rules.StaffHeight;
@@ -488,7 +489,7 @@ export class MusicSystemBuilder {
             measure.addKeyAtBegin(currentKey, previousKey, currentClef);
             measure.addKeyAtBegin(currentKey, previousKey, currentClef);
             keyAdded = true;
             keyAdded = true;
         }
         }
-        if (currentRhythm !== undefined) {
+        if (currentRhythm !== undefined && currentRhythm.PrintObject) {
             measure.addRhythmAtBegin(currentRhythm);
             measure.addRhythmAtBegin(currentRhythm);
             rhythmAdded = true;
             rhythmAdded = true;
         }
         }
@@ -615,7 +616,7 @@ export class MusicSystemBuilder {
         if (keyInstruction !== undefined) {
         if (keyInstruction !== undefined) {
             measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
             measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
         }
         }
-        if (rhythmInstruction !== undefined) {
+        if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
             measure.addRhythmAtBegin(rhythmInstruction);
             measure.addRhythmAtBegin(rhythmInstruction);
         }
         }
         measure.PositionAndShape.BorderLeft = 0.0;
         measure.PositionAndShape.BorderLeft = 0.0;

+ 4 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -21,6 +21,8 @@ import { SystemLinePosition } from "../SystemLinePosition";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
 import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
 import { NoteHead, NoteHeadShape } from "../../VoiceData/NoteHead";
 import { NoteHead, NoteHeadShape } from "../../VoiceData/NoteHead";
+import { unitInPixels } from "./VexFlowMusicSheetDrawer";
+import { EngravingRules } from "../EngravingRules";
 
 
 /**
 /**
  * Helper class, which contains static methods which actually convert
  * Helper class, which contains static methods which actually convert
@@ -228,7 +230,8 @@ export class VexFlowConverter {
                     // https://github.com/0xfe/vexflow/issues/579 The author reports that he needs to add some negative x shift
                     // 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.
                     // if the measure has no modifiers.
                     alignCenter = true;
                     alignCenter = true;
-                    xShift = -25; // TODO: Either replace by EngravingRules entry or find a way to make it dependent on the modifiers
+                    xShift = EngravingRules.Rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
+                    // affects VexFlowStaffEntry.calculateXPosition()
                 }
                 }
                 duration += "r";
                 duration += "r";
                 break;
                 break;

+ 7 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowInstantaneousDynamicExpression.ts

@@ -2,7 +2,7 @@ import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneo
 import { InstantaneousDynamicExpression, DynamicEnum } from "../../VoiceData/Expressions/InstantaneousDynamicExpression";
 import { InstantaneousDynamicExpression, DynamicEnum } from "../../VoiceData/Expressions/InstantaneousDynamicExpression";
 import { GraphicalLabel } from "../GraphicalLabel";
 import { GraphicalLabel } from "../GraphicalLabel";
 import { Label } from "../../Label";
 import { Label } from "../../Label";
-import { TextAlignmentAndPlacement } from "../../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum } from "../../../Common/Enums/TextAlignment";
 import { EngravingRules } from "../EngravingRules";
 import { EngravingRules } from "../EngravingRules";
 import { FontStyles } from "../../../Common/Enums/FontStyles";
 import { FontStyles } from "../../../Common/Enums/FontStyles";
 import { StaffLine } from "../StaffLine";
 import { StaffLine } from "../StaffLine";
@@ -14,9 +14,13 @@ export class VexFlowInstantaneousDynamicExpression extends GraphicalInstantaneou
     constructor(instantaneousDynamicExpression: InstantaneousDynamicExpression, staffLine: StaffLine, measure: GraphicalMeasure) {
     constructor(instantaneousDynamicExpression: InstantaneousDynamicExpression, staffLine: StaffLine, measure: GraphicalMeasure) {
         super(instantaneousDynamicExpression, staffLine, measure);
         super(instantaneousDynamicExpression, staffLine, measure);
 
 
-        this.mLabel = new GraphicalLabel(new Label(this.Expression),
+        let labelAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterTop;
+        if (EngravingRules.Rules.CompactMode) {
+            labelAlignment = TextAlignmentEnum.LeftBottom;
+        }
+        this.mLabel = new GraphicalLabel(new Label(this.Expression, labelAlignment),
                                          EngravingRules.Rules.ContinuousDynamicTextHeight,
                                          EngravingRules.Rules.ContinuousDynamicTextHeight,
-                                         TextAlignmentAndPlacement.CenterTop,
+                                         labelAlignment,
                                          this.PositionAndShape);
                                          this.PositionAndShape);
 
 
         this.mLabel.Label.fontStyle = FontStyles.BoldItalic;
         this.mLabel.Label.fontStyle = FontStyles.BoldItalic;

+ 23 - 6
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -25,9 +25,10 @@ import {StemDirectionType} from "../../VoiceData/VoiceEntry";
 import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
 import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
 import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
 import {Fraction} from "../../../Common/DataObjects/Fraction";
-import { Voice } from "../../VoiceData/Voice";
-import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
-import { LinkedVoice } from "../../VoiceData/LinkedVoice";
+import {Voice} from "../../VoiceData/Voice";
+import {VexFlowInstantaneousDynamicExpression} from "./VexFlowInstantaneousDynamicExpression";
+import {LinkedVoice} from "../../VoiceData/LinkedVoice";
+import {EngravingRules} from "../EngravingRules";
 
 
 export class VexFlowMeasure extends GraphicalMeasure {
 export class VexFlowMeasure extends GraphicalMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -595,10 +596,16 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     }
                     }
                     if (tupletStaveNotes.length > 1) {
                     if (tupletStaveNotes.length > 1) {
                       const notesOccupied: number = 2;
                       const notesOccupied: number = 2;
+                      const tuplet: Tuplet = tupletBuilder[0];
+                      const bracketed: boolean = tuplet.Bracket ||
+                        (tuplet.TupletLabelNumber === 3 && EngravingRules.Rules.TripletsBracketed) ||
+                        (tuplet.TupletLabelNumber !== 3 && EngravingRules.Rules.TupletsBracketed);
                       vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
                       vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
                                                           {
                                                           {
+                                                            bracketed: bracketed,
                                                             notes_occupied: notesOccupied,
                                                             notes_occupied: notesOccupied,
-                                                            num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
+                                                            num_notes: tupletStaveNotes.length, //, location: -1, ratioed: true
+                                                            ratioed: EngravingRules.Rules.TupletsRatioed,
                                                           }));
                                                           }));
                     } else {
                     } else {
                         log.debug("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.");
@@ -625,11 +632,18 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     }
                     }
                     continue;
                     continue;
                 }
                 }
-                (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
+                if (gve.notes[0].sourceNote.PrintObject) {
+                    (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
+                } else {
+                    graceGVoiceEntriesBefore = []; // if note is not rendered, its grace notes might need to be removed
+                    continue;
+                }
                 if (graceGVoiceEntriesBefore.length > 0) {
                 if (graceGVoiceEntriesBefore.length > 0) {
                     const graceNotes: Vex.Flow.GraceNote[] = [];
                     const graceNotes: Vex.Flow.GraceNote[] = [];
                     for (let i: number = 0; i < graceGVoiceEntriesBefore.length; i++) {
                     for (let i: number = 0; i < graceGVoiceEntriesBefore.length; i++) {
-                        graceNotes.push(VexFlowConverter.StaveNote(graceGVoiceEntriesBefore[i]));
+                        if (graceGVoiceEntriesBefore[i].notes[0].sourceNote.PrintObject) {
+                            graceNotes.push(VexFlowConverter.StaveNote(graceGVoiceEntriesBefore[i]));
+                        }
                     }
                     }
                     const graceNoteGroup: Vex.Flow.GraceNoteGroup = new Vex.Flow.GraceNoteGroup(graceNotes, graceSlur);
                     const graceNoteGroup: Vex.Flow.GraceNoteGroup = new Vex.Flow.GraceNoteGroup(graceNotes, graceSlur);
                     (gve as VexFlowVoiceEntry).vfStaveNote.addModifier(0, graceNoteGroup.beamNotes());
                     (gve as VexFlowVoiceEntry).vfStaveNote.addModifier(0, graceNoteGroup.beamNotes());
@@ -644,6 +658,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
         const voices: Voice[] = this.getVoicesWithinMeasure();
         const voices: Voice[] = this.getVoicesWithinMeasure();
 
 
         for (const voice of voices) {
         for (const voice of voices) {
+            if (voice === undefined) {
+                continue;
+            }
             const isMainVoice: boolean = !(voice instanceof LinkedVoice);
             const isMainVoice: boolean = !(voice instanceof LinkedVoice);
 
 
             // add a vexFlow voice for this voice:
             // add a vexFlow voice for this voice:

+ 3 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -46,7 +46,7 @@ import { GraphicalInstantaneousDynamicExpression } from "../GraphicalInstantaneo
 import { SkyBottomLineCalculator } from "../SkyBottomLineCalculator";
 import { SkyBottomLineCalculator } from "../SkyBottomLineCalculator";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { Staff } from "../../VoiceData/Staff";
 import { Staff } from "../../VoiceData/Staff";
-import { TextAlignmentAndPlacement, TextAlignment } from "../../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
 import { GraphicalSlur } from "../GraphicalSlur";
 import { GraphicalSlur } from "../GraphicalSlur";
 
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
@@ -118,7 +118,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             }
             }
         }
         }
         if (voices.length === 0) {
         if (voices.length === 0) {
-            log.warn("Found a measure with no voices... Continuing anyway.", mvoices);
+            log.info("Found a measure with no voices. Continuing anyway.", mvoices);
             continue;
             continue;
         }
         }
         // all voices that belong to one stave are collectively added to create a common context in VexFlow.
         // all voices that belong to one stave are collectively added to create a common context in VexFlow.
@@ -194,7 +194,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         for (let j: number = 0; j < staffEntry.LyricsEntries.length; j++) {
         for (let j: number = 0; j < staffEntry.LyricsEntries.length; j++) {
           const lyricsEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[j];
           const lyricsEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[j];
           // const lyricsEntryText = lyricsEntry.LyricsEntry.Text; // for easier debugging
           // const lyricsEntryText = lyricsEntry.LyricsEntry.Text; // for easier debugging
-          const lyricAlignment: TextAlignmentAndPlacement = lyricsEntry.GraphicalLabel.Label.textAlignment;
+          const lyricAlignment: TextAlignmentEnum = lyricsEntry.GraphicalLabel.Label.textAlignment;
           let minLyricsSpacing: number = EngravingRules.Rules.HorizontalBetweenLyricsDistance;
           let minLyricsSpacing: number = EngravingRules.Rules.HorizontalBetweenLyricsDistance;
           // for quarter note in Vexflow, where spacing is halfed for each smaller note duration.
           // for quarter note in Vexflow, where spacing is halfed for each smaller note duration.
 
 

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

@@ -23,6 +23,7 @@ import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import {GraphicalInstantaneousTempoExpression} from "../GraphicalInstantaneousTempoExpression";
 import {GraphicalInstantaneousTempoExpression} from "../GraphicalInstantaneousTempoExpression";
 import {GraphicalInstantaneousDynamicExpression} from "../GraphicalInstantaneousDynamicExpression";
 import {GraphicalInstantaneousDynamicExpression} from "../GraphicalInstantaneousDynamicExpression";
 import log = require("loglevel");
 import log = require("loglevel");
+import {DrawingParameters} from "../DrawingParameters";
 
 
 /**
 /**
  * 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
@@ -37,8 +38,8 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
 
     constructor(element: HTMLElement,
     constructor(element: HTMLElement,
                 backend: VexFlowBackend,
                 backend: VexFlowBackend,
-                isPreviewImageDrawer: boolean = false) {
-        super(new VexFlowTextMeasurer(), isPreviewImageDrawer);
+                drawingParameters: DrawingParameters = new DrawingParameters()) {
+        super(new VexFlowTextMeasurer(), drawingParameters);
         this.backend = backend;
         this.backend = backend;
     }
     }
 
 

+ 12 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowStaffEntry.ts

@@ -3,6 +3,8 @@ import {VexFlowMeasure} from "./VexFlowMeasure";
 import {SourceStaffEntry} from "../../VoiceData/SourceStaffEntry";
 import {SourceStaffEntry} from "../../VoiceData/SourceStaffEntry";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
+import { Note } from "../../VoiceData/Note";
+import { EngravingRules } from "../EngravingRules";
 
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
@@ -27,8 +29,18 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
         for (const gve of this.graphicalVoiceEntries as VexFlowVoiceEntry[]) {
         for (const gve of this.graphicalVoiceEntries as VexFlowVoiceEntry[]) {
             if (gve.vfStaveNote) {
             if (gve.vfStaveNote) {
                 gve.vfStaveNote.setStave(stave);
                 gve.vfStaveNote.setStave(stave);
+                if (!gve.vfStaveNote.preFormatted) {
+                    continue;
+                }
                 gve.applyBordersFromVexflow();
                 gve.applyBordersFromVexflow();
                 this.PositionAndShape.RelativePosition.x = gve.vfStaveNote.getBoundingBox().x / unitInPixels;
                 this.PositionAndShape.RelativePosition.x = gve.vfStaveNote.getBoundingBox().x / unitInPixels;
+                const sourceNote: Note = gve.notes[0].sourceNote;
+                if (sourceNote.isRest() && sourceNote.Length.WholeValue === 1) { // whole rest
+                    this.PositionAndShape.RelativePosition.x +=
+                        EngravingRules.Rules.WholeRestXShiftVexflow - 0.1; // xShift from VexFlowConverter
+                    gve.PositionAndShape.BorderLeft = -0.7;
+                    gve.PositionAndShape.BorderRight = 0.7;
+                }
                 if (gve.PositionAndShape.BorderLeft < lastBorderLeft) {
                 if (gve.PositionAndShape.BorderLeft < lastBorderLeft) {
                     lastBorderLeft = gve.PositionAndShape.BorderLeft;
                     lastBorderLeft = gve.PositionAndShape.BorderLeft;
                 }
                 }

+ 3 - 3
src/MusicalScore/Label.ts

@@ -1,4 +1,4 @@
-import {TextAlignmentAndPlacement} from "../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../Common/Enums/TextAlignment";
 import {OSMDColor} from "../Common/DataObjects/OSMDColor";
 import {OSMDColor} from "../Common/DataObjects/OSMDColor";
 import {Fonts} from "../Common/Enums/Fonts";
 import {Fonts} from "../Common/Enums/Fonts";
 import {FontStyles} from "../Common/Enums/FontStyles";
 import {FontStyles} from "../Common/Enums/FontStyles";
@@ -9,7 +9,7 @@ import {FontStyles} from "../Common/Enums/FontStyles";
  */
  */
 export class Label {
 export class Label {
 
 
-    constructor(text: string = "", alignment: TextAlignmentAndPlacement = TextAlignmentAndPlacement.LeftBottom, font: Fonts = Fonts.TimesNewRoman) {
+    constructor(text: string = "", alignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom, font: Fonts = Fonts.TimesNewRoman) {
         this.text = text;
         this.text = text;
         this.textAlignment = alignment;
         this.textAlignment = alignment;
         this.font = font;
         this.font = font;
@@ -20,7 +20,7 @@ export class Label {
     public font: Fonts;
     public font: Fonts;
     public fontStyle: FontStyles;
     public fontStyle: FontStyles;
     public fontHeight: number;
     public fontHeight: number;
-    public textAlignment: TextAlignmentAndPlacement;
+    public textAlignment: TextAlignmentEnum;
 
 
     public ToString(): string {
     public ToString(): string {
         return this.text;
         return this.text;

+ 26 - 10
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -132,8 +132,13 @@ export class InstrumentReader {
       const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[this.currentXmlMeasureIndex].elements();
       const xmlMeasureListArr: IXmlElement[] = this.xmlMeasureList[this.currentXmlMeasureIndex].elements();
       for (const xmlNode of xmlMeasureListArr) {
       for (const xmlNode of xmlMeasureListArr) {
         if (xmlNode.name === "note") {
         if (xmlNode.name === "note") {
-          if (xmlNode.hasAttributes && xmlNode.attribute("print-object") && xmlNode.attribute("print-spacing")) {
-            continue;
+          let printObject: boolean = true;
+          if (xmlNode.hasAttributes && xmlNode.attribute("print-object") &&
+              xmlNode.attribute("print-object").value === "no") {
+              printObject = false; // note will not be rendered, but still parsed for Playback etc.
+              // if (xmlNode.attribute("print-spacing")) {
+              //   if (xmlNode.attribute("print-spacing").value === "yes" {
+              //     // TODO give spacing for invisible notes even when not displayed. might be hard with Vexflow formatting
           }
           }
           let noteStaff: number = 1;
           let noteStaff: number = 1;
           if (this.instrument.Staves.length > 1) {
           if (this.instrument.Staves.length > 1) {
@@ -259,7 +264,7 @@ export class InstrumentReader {
             xmlNode, noteDuration, restNote,
             xmlNode, noteDuration, restNote,
             this.currentStaffEntry, this.currentMeasure,
             this.currentStaffEntry, this.currentMeasure,
             measureStartAbsoluteTimestamp,
             measureStartAbsoluteTimestamp,
-            this.maxTieNoteFraction, isChord, guitarPro
+            this.maxTieNoteFraction, isChord, guitarPro, printObject
           );
           );
 
 
           const notationsNode: IXmlElement = xmlNode.element("notations");
           const notationsNode: IXmlElement = xmlNode.element("notations");
@@ -713,18 +718,27 @@ export class InstrumentReader {
       this.abstractInstructions.push([1, keyInstruction]);
       this.abstractInstructions.push([1, keyInstruction]);
     }
     }
     if (node.element("time") !== undefined) {
     if (node.element("time") !== undefined) {
-      let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
       const timeNode: IXmlElement = node.element("time");
       const timeNode: IXmlElement = node.element("time");
+      let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
+      let timePrintObject: boolean = true;
       if (timeNode !== undefined && timeNode.hasAttributes) {
       if (timeNode !== undefined && timeNode.hasAttributes) {
-        const firstAttr: IXmlAttribute = timeNode.firstAttribute;
-        if (firstAttr.name === "symbol") {
-          if (firstAttr.value === "common") {
+        const symbolAttribute: IXmlAttribute = timeNode.attribute("symbol");
+        if (symbolAttribute) {
+          if (symbolAttribute.value === "common") {
             symbolEnum = RhythmSymbolEnum.COMMON;
             symbolEnum = RhythmSymbolEnum.COMMON;
-          } else if (firstAttr.value === "cut") {
+          } else if (symbolAttribute.value === "cut") {
             symbolEnum = RhythmSymbolEnum.CUT;
             symbolEnum = RhythmSymbolEnum.CUT;
           }
           }
         }
         }
+
+        const printObjectAttribute: IXmlAttribute = timeNode.attribute("print-object");
+        if (printObjectAttribute) {
+          if (printObjectAttribute.value === "no") {
+            timePrintObject = false;
+          }
+        }
       }
       }
+
       let num: number = 0;
       let num: number = 0;
       let denom: number = 0;
       let denom: number = 0;
       const senzaMisura: boolean = (timeNode !== undefined && timeNode.element("senza-misura") !== undefined);
       const senzaMisura: boolean = (timeNode !== undefined && timeNode.element("senza-misura") !== undefined);
@@ -778,9 +792,11 @@ export class InstrumentReader {
           log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
           log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
         }
 
 
-        this.abstractInstructions.push([1, new RhythmInstruction(
+        const newRhythmInstruction: RhythmInstruction = new RhythmInstruction(
           new Fraction(num, denom, 0, false), symbolEnum
           new Fraction(num, denom, 0, false), symbolEnum
-        )]);
+        );
+        newRhythmInstruction.PrintObject = timePrintObject;
+        this.abstractInstructions.push([1, newRhythmInstruction]);
       } else {
       } else {
         this.abstractInstructions.push([1, new RhythmInstruction(new Fraction(4, 4, 0, false), RhythmSymbolEnum.NONE)]);
         this.abstractInstructions.push([1, new RhythmInstruction(new Fraction(4, 4, 0, false), RhythmSymbolEnum.NONE)]);
       }
       }

+ 18 - 9
src/MusicalScore/ScoreIO/MusicSheetReader.ts

@@ -64,6 +64,7 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
             return this._createMusicSheet(root, path);
             return this._createMusicSheet(root, path);
         } catch (e) {
         } catch (e) {
             log.info("MusicSheetReader.CreateMusicSheet", e);
             log.info("MusicSheetReader.CreateMusicSheet", e);
+            return undefined;
         }
         }
     }
     }
 
 
@@ -486,21 +487,24 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
     private pushSheetLabels(root: IXmlElement, filePath: string): void {
     private pushSheetLabels(root: IXmlElement, filePath: string): void {
         this.readComposer(root);
         this.readComposer(root);
         this.readTitle(root);
         this.readTitle(root);
-        if (this.musicSheet.Title === undefined || this.musicSheet.Composer === undefined) {
-            this.readTitleAndComposerFromCredits(root);
+        try {
+            if (this.musicSheet.Title === undefined || this.musicSheet.Composer === undefined) {
+                this.readTitleAndComposerFromCredits(root); // this can also throw an error
+            }
+        } catch (ex) {
+            log.info("MusicSheetReader.pushSheetLabels", "readTitleAndComposerFromCredits", ex);
         }
         }
-        if (this.musicSheet.Title === undefined) {
-            try {
+        try {
+            if (this.musicSheet.Title === undefined) {
                 const barI: number = Math.max(
                 const barI: number = Math.max(
                     0, filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")
                     0, filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")
                 );
                 );
                 const filename: string = filePath.substr(barI);
                 const filename: string = filePath.substr(barI);
                 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) {
-                log.info("MusicSheetReader.pushSheetLabels: ", ex);
             }
             }
-
+        } catch (ex) {
+            log.info("MusicSheetReader.pushSheetLabels", "read title from file name", ex);
         }
         }
     }
     }
 
 
@@ -611,8 +615,13 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
         }
         }
         let paperHeight: number = 0;
         let paperHeight: number = 0;
         let topSystemDistance: number = 0;
         let topSystemDistance: number = 0;
-        const defi: string = root.element("defaults").element("page-layout").element("page-height").value;
-        paperHeight = parseFloat(defi);
+        try {
+            const defi: string = root.element("defaults").element("page-layout").element("page-height").value;
+            paperHeight = parseFloat(defi);
+        } catch (e) {
+            log.info("MusicSheetReader.computeSystemYCoordinates(): couldn't find page height, not reading title/composer.");
+            return 0;
+        }
         let found: boolean = false;
         let found: boolean = false;
         const parts: IXmlElement[] = root.elements("part");
         const parts: IXmlElement[] = root.elements("part");
         for (let idx: number = 0, len: number = parts.length; idx < len; ++idx) {
         for (let idx: number = 0, len: number = parts.length; idx < len; ++idx) {

+ 7 - 2
src/MusicalScore/ScoreIO/MusicSymbolModules/ExpressionReader.ts

@@ -13,9 +13,10 @@ import {InstantaneousTempoExpression} from "../../VoiceData/Expressions/Instanta
 import {MoodExpression} from "../../VoiceData/Expressions/MoodExpression";
 import {MoodExpression} from "../../VoiceData/Expressions/MoodExpression";
 import {UnknownExpression} from "../../VoiceData/Expressions/UnknownExpression";
 import {UnknownExpression} from "../../VoiceData/Expressions/UnknownExpression";
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
-import {TextAlignmentAndPlacement} from "../../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../../Common/Enums/TextAlignment";
 import {ITextTranslation} from "../../Interfaces/ITextTranslation";
 import {ITextTranslation} from "../../Interfaces/ITextTranslation";
 import * as log from "loglevel";
 import * as log from "loglevel";
+import { EngravingRules } from "../../Graphical/EngravingRules";
 
 
 export class ExpressionReader {
 export class ExpressionReader {
     private musicSheet: MusicSheet;
     private musicSheet: MusicSheet;
@@ -535,8 +536,12 @@ export class ExpressionReader {
                 }
                 }
             }
             }
         }
         }
+        let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
+        if (EngravingRules.Rules.CompactMode) {
+            textAlignment = TextAlignmentEnum.LeftBottom;
+        }
         const unknownExpression: UnknownExpression = new UnknownExpression(
         const unknownExpression: UnknownExpression = new UnknownExpression(
-            stringTrimmed, this.placement, TextAlignmentAndPlacement.CenterBottom, this.staffNumber);
+            stringTrimmed, this.placement, textAlignment, this.staffNumber);
         this.getMultiExpression.addExpression(unknownExpression, prefix);
         this.getMultiExpression.addExpression(unknownExpression, prefix);
 
 
         return false;
         return false;

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

@@ -42,6 +42,9 @@ export class RepetitionCalculator {
   }
   }
 
 
   private handleRepetitionInstructions(currentRepetitionInstruction: RepetitionInstruction): boolean {
   private handleRepetitionInstructions(currentRepetitionInstruction: RepetitionInstruction): boolean {
+    if (!this.currentMeasure) {
+      return false;
+    }
     switch (currentRepetitionInstruction.type) {
     switch (currentRepetitionInstruction.type) {
       case RepetitionInstructionEnum.StartLine:
       case RepetitionInstructionEnum.StartLine:
         this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);
         this.currentMeasure.FirstRepetitionInstructions.push(currentRepetitionInstruction);

+ 12 - 20
src/MusicalScore/ScoreIO/MusicSymbolModules/RepetitionInstructionReader.ts

@@ -121,10 +121,10 @@ export class RepetitionInstructionReader {
   public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean {
   public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean {
     const wordsNode: IXmlElement = directionTypeNode.element("words");
     const wordsNode: IXmlElement = directionTypeNode.element("words");
     if (wordsNode !== undefined) {
     if (wordsNode !== undefined) {
+      const dsRegEx: string = "d\\s?\\.s\\."; // Input for new RegExp(). TS eliminates the first \
       // must Trim string and ToLower before compare
       // must Trim string and ToLower before compare
       const innerText: string = wordsNode.value.trim().toLowerCase();
       const innerText: string = wordsNode.value.trim().toLowerCase();
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al fine") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al fine")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, dsRegEx + " al fine")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
           measureIndex--;
           measureIndex--;
@@ -133,8 +133,8 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al coda") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. s. al coda")) {
+      const dcRegEx: string = "d\\.\\s?c\\.";
+      if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5) {
         if (relativeMeasurePosition < 0.5) {
           measureIndex--;
           measureIndex--;
@@ -143,8 +143,7 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al fine") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al fine")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al fine")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
           measureIndex--;
           measureIndex--;
@@ -153,8 +152,7 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al coda") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. c. al coda")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx + " al coda")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5) {
         if (relativeMeasurePosition < 0.5) {
           measureIndex--;
           measureIndex--;
@@ -163,10 +161,8 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.c.") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. c.") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "dacapo") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "da capo")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, dcRegEx) ||
+        StringUtil.StringContainsSeparatedWord(innerText, "da\\s?capo")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
           measureIndex--;
           measureIndex--;
@@ -175,10 +171,8 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "d.s.") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "d. s.") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "dalsegno") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "dal segno")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, dsRegEx) ||
+        StringUtil.StringContainsSeparatedWord(innerText, "dal\\s?segno")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
         if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure
           measureIndex--;
           measureIndex--;
@@ -187,10 +181,8 @@ export class RepetitionInstructionReader {
         this.addInstruction(this.repetitionInstructions, newInstruction);
         this.addInstruction(this.repetitionInstructions, newInstruction);
         return true;
         return true;
       }
       }
-      if (StringUtil.StringContainsSeparatedWord(innerText, "tocoda") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "to coda") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "a coda") ||
-        StringUtil.StringContainsSeparatedWord(innerText, "a la coda")) {
+      if (StringUtil.StringContainsSeparatedWord(innerText, "to\\s?coda") ||
+        StringUtil.StringContainsSeparatedWord(innerText, "a (la )?coda")) {
         let measureIndex: number = this.currentMeasureIndex;
         let measureIndex: number = this.currentMeasureIndex;
         if (relativeMeasurePosition < 0.5) {
         if (relativeMeasurePosition < 0.5) {
           measureIndex--;
           measureIndex--;

+ 25 - 10
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -99,18 +99,20 @@ export class VoiceGenerator {
    * @param maxTieNoteFraction
    * @param maxTieNoteFraction
    * @param chord
    * @param chord
    * @param guitarPro
    * @param guitarPro
+   * @param printObject whether the note should be rendered (true) or invisible (false)
    * @returns {Note}
    * @returns {Note}
    */
    */
   public read(noteNode: IXmlElement, noteDuration: Fraction, restNote: boolean,
   public read(noteNode: IXmlElement, noteDuration: Fraction, restNote: boolean,
               parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
               parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
-              measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean): Note {
+              measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean,
+              printObject: boolean = true): Note {
     this.currentStaffEntry = parentStaffEntry;
     this.currentStaffEntry = parentStaffEntry;
     this.currentMeasure = parentMeasure;
     this.currentMeasure = parentMeasure;
     //log.debug("read called:", restNote);
     //log.debug("read called:", restNote);
     try {
     try {
       this.currentNote = restNote
       this.currentNote = restNote
-        ? this.addRestNote(noteDuration)
-        : this.addSingleNote(noteNode, noteDuration, chord, guitarPro);
+        ? this.addRestNote(noteDuration, printObject)
+        : this.addSingleNote(noteNode, noteDuration, chord, guitarPro, printObject);
       // read lyrics
       // read lyrics
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
       if (this.lyricsReader !== undefined && lyricElements !== undefined) {
       if (this.lyricsReader !== undefined && lyricElements !== undefined) {
@@ -294,7 +296,8 @@ export class VoiceGenerator {
    * @param guitarPro
    * @param guitarPro
    * @returns {Note}
    * @returns {Note}
    */
    */
-  private addSingleNote(node: IXmlElement, noteDuration: Fraction, chord: boolean, guitarPro: boolean): Note {
+  private addSingleNote(node: IXmlElement, noteDuration: Fraction, chord: boolean, guitarPro: boolean,
+                        printObject: boolean = true): Note {
     //log.debug("addSingleNote called");
     //log.debug("addSingleNote called");
     let noteAlter: number = 0;
     let noteAlter: number = 0;
     let noteAccidental: AccidentalEnum = AccidentalEnum.NONE;
     let noteAccidental: AccidentalEnum = AccidentalEnum.NONE;
@@ -387,6 +390,7 @@ export class VoiceGenerator {
     const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAccidental);
     const pitch: Pitch = new Pitch(noteStep, noteOctave, noteAccidental);
     const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
     const noteLength: Fraction = Fraction.createFromFraction(noteDuration);
     const note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
     const note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
+    note.PrintObject = printObject;
     note.PlaybackInstrumentId = playbackInstrumentId;
     note.PlaybackInstrumentId = playbackInstrumentId;
     if (noteHeadShapeXml !== undefined && noteHeadShapeXml !== "normal") {
     if (noteHeadShapeXml !== undefined && noteHeadShapeXml !== "normal") {
       note.NoteHead = new NoteHead(note, noteHeadShapeXml, noteHeadFilledXml);
       note.NoteHead = new NoteHead(note, noteHeadShapeXml, noteHeadFilledXml);
@@ -404,9 +408,10 @@ export class VoiceGenerator {
    * @param divisions
    * @param divisions
    * @returns {Note}
    * @returns {Note}
    */
    */
-  private addRestNote(noteDuration: Fraction): Note {
+  private addRestNote(noteDuration: Fraction, printObject: boolean = true): Note {
     const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
     const restFraction: Fraction = Fraction.createFromFraction(noteDuration);
     const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, undefined);
     const restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, undefined);
+    restNote.PrintObject = printObject;
     this.currentVoiceEntry.Notes.push(restNote);
     this.currentVoiceEntry.Notes.push(restNote);
     if (this.openBeam !== undefined) {
     if (this.openBeam !== undefined) {
       this.openBeam.ExtendedNoteList.push(restNote);
       this.openBeam.ExtendedNoteList.push(restNote);
@@ -515,6 +520,7 @@ export class VoiceGenerator {
    * @returns {number}
    * @returns {number}
    */
    */
   private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
   private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
+    let bracketed: boolean = false; // xml bracket attribute value
     if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
     if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
       let timeModNode: IXmlElement = node.element("time-modification");
       let timeModNode: IXmlElement = node.element("time-modification");
       if (timeModNode !== undefined) {
       if (timeModNode !== undefined) {
@@ -524,8 +530,12 @@ export class VoiceGenerator {
       for (let idx: number = 0, len: number = tupletNodeListArr.length; idx < len; ++idx) {
       for (let idx: number = 0, len: number = tupletNodeListArr.length; idx < len; ++idx) {
         const tupletNode: IXmlElement = tupletNodeListArr[idx];
         const tupletNode: IXmlElement = tupletNodeListArr[idx];
         if (tupletNode !== undefined && tupletNode.attributes()) {
         if (tupletNode !== undefined && tupletNode.attributes()) {
-          const type: string = tupletNode.attribute("type").value;
-          if (type === "start") {
+          const bracketAttr: Attr = tupletNode.attribute("bracket");
+          if (bracketAttr && bracketAttr.value === "yes") {
+            bracketed = true;
+          }
+          const type: Attr = tupletNode.attribute("type");
+          if (type && type.value === "start") {
             let tupletNumber: number = 1;
             let tupletNumber: number = 1;
             if (tupletNode.attribute("number")) {
             if (tupletNode.attribute("number")) {
               tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
               tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
@@ -542,7 +552,7 @@ export class VoiceGenerator {
               }
               }
 
 
             }
             }
-            const tuplet: Tuplet = new Tuplet(tupletLabelNumber);
+            const tuplet: Tuplet = new Tuplet(tupletLabelNumber, bracketed);
             if (this.tupletDict[tupletNumber] !== undefined) {
             if (this.tupletDict[tupletNumber] !== undefined) {
               delete this.tupletDict[tupletNumber];
               delete this.tupletDict[tupletNumber];
               if (Object.keys(this.tupletDict).length === 0) {
               if (Object.keys(this.tupletDict).length === 0) {
@@ -558,7 +568,7 @@ export class VoiceGenerator {
             tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
             tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
             this.currentNote.NoteTuplet = tuplet;
             this.currentNote.NoteTuplet = tuplet;
             this.openTupletNumber = tupletNumber;
             this.openTupletNumber = tupletNumber;
-          } else if (type === "stop") {
+          } else if (type.value === "stop") {
             let tupletNumber: number = 1;
             let tupletNumber: number = 1;
             if (tupletNode.attribute("number")) {
             if (tupletNode.attribute("number")) {
               tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
               tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
@@ -590,6 +600,11 @@ export class VoiceGenerator {
         }
         }
         const noTupletNumbering: boolean = isNaN(tupletnumber);
         const noTupletNumbering: boolean = isNaN(tupletnumber);
 
 
+        const bracketAttr: Attr = n.attribute("bracket");
+        if (bracketAttr && bracketAttr.value === "yes") {
+          bracketed = true;
+        }
+
         if (type === "start") {
         if (type === "start") {
           let tupletLabelNumber: number = 0;
           let tupletLabelNumber: number = 0;
           let timeModNode: IXmlElement = node.element("time-modification");
           let timeModNode: IXmlElement = node.element("time-modification");
@@ -613,7 +628,7 @@ export class VoiceGenerator {
           }
           }
           let tuplet: Tuplet = this.tupletDict[tupletnumber];
           let tuplet: Tuplet = this.tupletDict[tupletnumber];
           if (tuplet === undefined) {
           if (tuplet === undefined) {
-            tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber);
+            tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber, bracketed);
           }
           }
           const subnotelist: Note[] = [];
           const subnotelist: Note[] = [];
           subnotelist.push(this.currentNote);
           subnotelist.push(this.currentNote);

+ 5 - 5
src/MusicalScore/VoiceData/Expressions/UnknownExpression.ts

@@ -1,20 +1,20 @@
 import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
 import {PlacementEnum, AbstractExpression} from "./AbstractExpression";
-import {TextAlignmentAndPlacement} from "../../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../../Common/Enums/TextAlignment";
 
 
 export class UnknownExpression extends AbstractExpression {
 export class UnknownExpression extends AbstractExpression {
-    constructor(label: string, placementEnum: PlacementEnum, textAlignment: TextAlignmentAndPlacement, staffNumber: number) {
+    constructor(label: string, placementEnum: PlacementEnum, textAlignment: TextAlignmentEnum, staffNumber: number) {
         super();
         super();
         this.label = label;
         this.label = label;
         this.placement = placementEnum;
         this.placement = placementEnum;
         this.staffNumber = staffNumber;
         this.staffNumber = staffNumber;
         if (textAlignment === undefined) {
         if (textAlignment === undefined) {
-            textAlignment = TextAlignmentAndPlacement.LeftBottom;
+            textAlignment = TextAlignmentEnum.LeftBottom;
         }
         }
         this.textAlignment = textAlignment;
         this.textAlignment = textAlignment;
     }
     }
     private label: string;
     private label: string;
     private placement: PlacementEnum;
     private placement: PlacementEnum;
-    private textAlignment: TextAlignmentAndPlacement;
+    private textAlignment: TextAlignmentEnum;
     private staffNumber: number;
     private staffNumber: number;
 
 
     public get Label(): string {
     public get Label(): string {
@@ -32,7 +32,7 @@ export class UnknownExpression extends AbstractExpression {
     public set StaffNumber(value: number) {
     public set StaffNumber(value: number) {
         this.staffNumber = value;
         this.staffNumber = value;
     }
     }
-    public get TextAlignment(): TextAlignmentAndPlacement {
+    public get TextAlignment(): TextAlignmentEnum {
         return this.textAlignment;
         return this.textAlignment;
     }
     }
 }
 }

+ 9 - 0
src/MusicalScore/VoiceData/Instructions/AbstractNotationInstruction.ts

@@ -7,6 +7,8 @@ export abstract class AbstractNotationInstruction {
     }
     }
 
 
     protected parent: SourceStaffEntry;
     protected parent: SourceStaffEntry;
+    /** States whether the object should be displayed. False if xmlNode.attribute("print-object").value = "no". */
+    private printObject: boolean = true;
 
 
     public get Parent(): SourceStaffEntry {
     public get Parent(): SourceStaffEntry {
         return this.parent;
         return this.parent;
@@ -15,4 +17,11 @@ export abstract class AbstractNotationInstruction {
         this.parent = value;
         this.parent = value;
     }
     }
 
 
+    public get PrintObject(): boolean {
+        return this.printObject;
+    }
+
+    public set PrintObject(value: boolean) {
+        this.printObject = value;
+    }
 }
 }

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

@@ -45,6 +45,8 @@ export class Note {
     private slurs: Slur[] = [];
     private slurs: Slur[] = [];
     private playbackInstrumentId: string = undefined;
     private playbackInstrumentId: string = undefined;
     private noteHead: NoteHead = undefined;
     private noteHead: NoteHead = undefined;
+    /** States whether the note should be displayed. False if xmlNode.attribute("print-object").value = "no". */
+    private printObject: boolean = true;
 
 
 
 
     public get ParentVoiceEntry(): VoiceEntry {
     public get ParentVoiceEntry(): VoiceEntry {
@@ -104,6 +106,13 @@ export class Note {
     public get NoteHead(): NoteHead {
     public get NoteHead(): NoteHead {
         return this.noteHead;
         return this.noteHead;
     }
     }
+    public get PrintObject(): boolean {
+        return this.printObject;
+    }
+
+    public set PrintObject(value: boolean) {
+        this.printObject = value;
+    }
 
 
     public isRest(): boolean {
     public isRest(): boolean {
         return this.Pitch === undefined;
         return this.Pitch === undefined;

+ 2 - 1
src/MusicalScore/VoiceData/NoteHead.ts

@@ -69,6 +69,7 @@ export class NoteHead {
                 return NoteHeadShape.SQUARE;
                 return NoteHeadShape.SQUARE;
             case "la": // Musescore displays this as a square
             case "la": // Musescore displays this as a square
                 return NoteHeadShape.SQUARE;
                 return NoteHeadShape.SQUARE;
+            case "do":
             case "triangle":
             case "triangle":
                 return NoteHeadShape.TRIANGLE;
                 return NoteHeadShape.TRIANGLE;
             case "x":
             case "x":
@@ -78,7 +79,7 @@ export class NoteHead {
             case "circle-x":
             case "circle-x":
                 return NoteHeadShape.CIRCLEX;
                 return NoteHeadShape.CIRCLEX;
             default:
             default:
-                log.warn("unhandled shapeTypeXml: " + shapeTypeXml);
+                log.info("unsupported/unhandled xml notehead '" + shapeTypeXml + "'. Using normal notehead.");
                 return NoteHeadShape.NORMAL;
                 return NoteHeadShape.NORMAL;
         }
         }
     }
     }

+ 12 - 1
src/MusicalScore/VoiceData/Tuplet.ts

@@ -6,13 +6,16 @@ import {Fraction} from "../../Common/DataObjects/Fraction";
  */
  */
 export class Tuplet {
 export class Tuplet {
 
 
-    constructor(tupletLabelNumber: number) {
+    constructor(tupletLabelNumber: number, bracket: boolean = false) {
         this.tupletLabelNumber = tupletLabelNumber;
         this.tupletLabelNumber = tupletLabelNumber;
+        this.bracket = bracket;
     }
     }
 
 
     private tupletLabelNumber: number;
     private tupletLabelNumber: number;
     private notes: Note[][] = [];
     private notes: Note[][] = [];
     private fractions: Fraction[] = [];
     private fractions: Fraction[] = [];
+    /** Whether this tuplet has a bracket. (e.g. showing |--3--| or just 3 for a triplet) */
+    private bracket: boolean;
 
 
     public get TupletLabelNumber(): number {
     public get TupletLabelNumber(): number {
         return this.tupletLabelNumber;
         return this.tupletLabelNumber;
@@ -38,6 +41,14 @@ export class Tuplet {
         this.fractions = value;
         this.fractions = value;
     }
     }
 
 
+    public get Bracket(): boolean {
+        return this.bracket;
+    }
+
+    public set Bracket(value: boolean) {
+        this.bracket = value;
+    }
+
     /**
     /**
      * Returns the index of the given Note in the Tuplet List (notes[0], notes[1],...).
      * Returns the index of the given Note in the Tuplet List (notes[0], notes[1],...).
      * @param note
      * @param note

+ 11 - 8
src/OpenSheetMusicDisplay/Cursor.ts

@@ -28,6 +28,7 @@ export class Cursor {
   private hidden: boolean = true;
   private hidden: boolean = true;
   private cursorElement: HTMLImageElement;
   private cursorElement: HTMLImageElement;
 
 
+  /** Initialize the cursor. Necessary before using functions like show() and next(). */
   public init(manager: MusicPartManager, graphic: GraphicalMusicSheet): void {
   public init(manager: MusicPartManager, graphic: GraphicalMusicSheet): void {
     this.manager = manager;
     this.manager = manager;
     this.reset();
     this.reset();
@@ -42,9 +43,12 @@ export class Cursor {
   public show(): void {
   public show(): void {
     this.hidden = false;
     this.hidden = false;
     this.update();
     this.update();
-    // Forcing the sheet to re-render is not necessary anymore,
-    // since the cursor is an HTML element.
-    // this.openSheetMusicDisplay.render();
+  }
+
+  private getStaffEntriesFromVoiceEntry(voiceEntry: VoiceEntry): VexFlowStaffEntry {
+    const measureIndex: number = voiceEntry.ParentSourceStaffEntry.VerticalContainerParent.ParentMeasure.measureListIndex;
+    const staffIndex: number = voiceEntry.ParentSourceStaffEntry.ParentStaff.idInMusicSheet;
+    return <VexFlowStaffEntry>this.graphic.findGraphicalStaffEntryFromMeasureList(staffIndex, measureIndex, voiceEntry.ParentSourceStaffEntry);
   }
   }
 
 
   public update(): void {
   public update(): void {
@@ -59,12 +63,11 @@ export class Cursor {
     }
     }
     let x: number = 0, y: number = 0, height: number = 0;
     let x: number = 0, y: number = 0, height: number = 0;
 
 
-    const voiceEntry: VoiceEntry = iterator.CurrentVoiceEntries[0];
-    const measureIndex: number = voiceEntry.ParentSourceStaffEntry.VerticalContainerParent.ParentMeasure.measureListIndex;
-    const staffIndex: number = voiceEntry.ParentSourceStaffEntry.ParentStaff.idInMusicSheet;
+    // get all staff entries inside the current voice entry
+    const gseArr: VexFlowStaffEntry[] = iterator.CurrentVoiceEntries.map(ve => this.getStaffEntriesFromVoiceEntry(ve));
+    // sort them by x position and take the leftmost entry
     const gse: VexFlowStaffEntry =
     const gse: VexFlowStaffEntry =
-      <VexFlowStaffEntry>this.graphic.findGraphicalStaffEntryFromMeasureList(staffIndex, measureIndex, voiceEntry.ParentSourceStaffEntry);
-
+          gseArr.sort((a, b) => a.PositionAndShape.AbsolutePosition.x <= b.PositionAndShape.AbsolutePosition.x ? -1 : 1 )[0];
     x = gse.PositionAndShape.AbsolutePosition.x;
     x = gse.PositionAndShape.AbsolutePosition.x;
     const musicSystem: MusicSystem = gse.parentMeasure.parentMusicSystem;
     const musicSystem: MusicSystem = gse.parentMeasure.parentMusicSystem;
     y = musicSystem.PositionAndShape.AbsolutePosition.y + musicSystem.StaffLines[0].PositionAndShape.RelativePosition.y;
     y = musicSystem.PositionAndShape.AbsolutePosition.y + musicSystem.StaffLines[0].PositionAndShape.RelativePosition.y;

+ 58 - 0
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -0,0 +1,58 @@
+import { DrawingParametersEnum } from "../MusicalScore/Graphical/DrawingParameters";
+
+/** Possible options for the OpenSheetMusicDisplay constructor, none are mandatory. */
+export interface IOSMDOptions {
+    /** Not yet supported. Will always beam automatically. */ // TODO
+    autoBeam?: boolean;
+    /** Automatically resize score with canvas size. Default is true. */
+    autoResize?: boolean;
+    /** Not yet supported. Will always place stems automatically. */ // TODO
+    autoStem?: boolean;
+    /** Render Backend, will be SVG if given undefined, SVG or svg, otherwise Canvas. */
+    backend?: string;
+    /** Don't show/load cursor. Will override disableCursor in drawingParameters. */
+    disableCursor?: boolean;
+    /** Broad Parameters like compact or preview mode. */
+    drawingParameters?: string | DrawingParametersEnum;
+    /** Whether to draw hidden/invisible notes (print-object="no" in XML). Default false. Not yet supported. */ // TODO
+    drawHiddenNotes?: boolean;
+    /** Default color for a note head (without stem). Default black. Not yet supported. */ // TODO
+    defaultColorNoteHead?: string;
+    /** Default color for a note stem. Default black. Not yet supported. */ // TODO
+    defaultColorStem?: string;
+    /** Whether to draw the title of the piece. If false, disables drawing Subtitle as well. */
+    drawTitle?: boolean;
+    /** Whether to draw the subtitle of the piece. If true, enables drawing Title as well. */
+    drawSubtitle?: boolean;
+    /** Whether to draw credits (title, composer, arranger, copyright etc., see <credit>. Not yet supported. */ // TODO
+    drawCredits?: boolean;
+    /** Whether to draw part (instrument) names. */
+    drawPartNames?: boolean;
+    /** Whether to draw the lyricist's name, if given. */
+    drawLyricist?: boolean;
+    /** Whether tuplets are labeled with ratio (e.g. 5:2 instead of 5 for quintuplets). Default false. */
+    tupletsRatioed?: boolean;
+    /** Whether all tuplets should be bracketed (e.g. |--5--| instead of 5). Default false.
+     * If false, only tuplets given as bracketed in XML (bracket="yes") will be bracketed.
+     */
+    tupletsBracketed?: boolean;
+    /** Whether all triplets should be bracketed. Overrides tupletsBracketed for triplets.
+     * If false, only triplets given as bracketed in XML (bracket="yes") will be bracketed.
+     * (Bracketing all triplets can be cluttering)
+     */
+    tripletsBracketed?: boolean;
+}
+
+/** Handles [[IOSMDOptions]], e.g. returning default options with OSMDOptionsStandard() */
+export class OSMDOptions {
+    /** Returns the default options for OSMD.
+     * These are e.g. used if no options are given in the [[OpenSheetMusicDisplay]] constructor.
+     */
+    public static OSMDOptionsStandard(): IOSMDOptions {
+        return {
+            autoResize: true,
+            backend: "svg",
+            drawingParameters: DrawingParametersEnum.default,
+        };
+    }
+}

+ 122 - 25
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -13,14 +13,27 @@ import {MXLHelper} from "../Common/FileIO/Mxl";
 import {Promise} from "es6-promise";
 import {Promise} from "es6-promise";
 import {AJAX} from "./AJAX";
 import {AJAX} from "./AJAX";
 import * as log from "loglevel";
 import * as log from "loglevel";
+import {DrawingParametersEnum, DrawingParameters} from "../MusicalScore/Graphical/DrawingParameters";
+import {IOSMDOptions, OSMDOptions} from "./OSMDOptions";
+import {EngravingRules} from "../MusicalScore/Graphical/EngravingRules";
 
 
+/**
+ * The main class and control point of OpenSheetMusicDisplay.<br>
+ * It can display MusicXML sheet music files in an HTML element container.<br>
+ * After the constructor, use load() and render() to load and render a MusicXML file.
+ */
 export class OpenSheetMusicDisplay {
 export class OpenSheetMusicDisplay {
     /**
     /**
-     * The easy way of displaying a MusicXML sheet music file
-     * @param container is either the ID, or the actual "div" element which will host the music sheet
-     * @autoResize automatically resize the sheet to full page width on window resize
+     * Creates and attaches an OpenSheetMusicDisplay object to an HTML element container.<br>
+     * After the constructor, use load() and render() to load and render a MusicXML file.
+     * @param container The container element OSMD will be rendered into.<br>
+     *                  Either a string specifying the ID of an HTML container element,<br>
+     *                  or a reference to the HTML element itself (e.g. div)
+     * @param options An object for rendering options like the backend (svg/canvas) or autoResize.<br>
+     *                For defaults see the OSMDOptionsStandard method in the [[OSMDOptions]] class.
      */
      */
-    constructor(container: string|HTMLElement, autoResize: boolean = false, backend: string = "svg") {
+    constructor(container: string|HTMLElement,
+                options: IOSMDOptions = OSMDOptions.OSMDOptionsStandard()) {
         // Store container element
         // Store container element
         if (typeof container === "string") {
         if (typeof container === "string") {
             // ID passed
             // ID passed
@@ -33,22 +46,23 @@ export class OpenSheetMusicDisplay {
             throw new Error("Please pass a valid div container to OpenSheetMusicDisplay");
             throw new Error("Please pass a valid div container to OpenSheetMusicDisplay");
         }
         }
 
 
-        if (backend === "svg") {
+        if (options.backend === undefined || options.backend.toLowerCase() === "svg") {
             this.backend = new SvgVexFlowBackend();
             this.backend = new SvgVexFlowBackend();
         } else {
         } else {
             this.backend = new CanvasVexFlowBackend();
             this.backend = new CanvasVexFlowBackend();
         }
         }
 
 
+        this.setDrawingParameters(options);
+
         this.backend.initialize(this.container);
         this.backend.initialize(this.container);
         this.canvas = this.backend.getCanvas();
         this.canvas = this.backend.getCanvas();
-        const inner: HTMLElement = this.backend.getInnerElement();
+        this.innerElement = this.backend.getInnerElement();
+        this.enableOrDisableCursor(this.drawingParameters.drawCursors);
 
 
         // Create the drawer
         // Create the drawer
-        this.drawer = new VexFlowMusicSheetDrawer(this.canvas, this.backend, false);
-        // Create the cursor
-        this.cursor = new Cursor(inner, this);
+        this.drawer = new VexFlowMusicSheetDrawer(this.canvas, this.backend, this.drawingParameters);
 
 
-        if (autoResize) {
+        if (options.autoResize) {
             this.autoResize();
             this.autoResize();
         }
         }
     }
     }
@@ -59,9 +73,11 @@ export class OpenSheetMusicDisplay {
     private container: HTMLElement;
     private container: HTMLElement;
     private canvas: HTMLElement;
     private canvas: HTMLElement;
     private backend: VexFlowBackend;
     private backend: VexFlowBackend;
+    private innerElement: HTMLElement;
     private sheet: MusicSheet;
     private sheet: MusicSheet;
     private drawer: VexFlowMusicSheetDrawer;
     private drawer: VexFlowMusicSheetDrawer;
     private graphic: GraphicalMusicSheet;
     private graphic: GraphicalMusicSheet;
+    private drawingParameters: DrawingParameters;
 
 
     /**
     /**
      * Load a MusicXML file
      * Load a MusicXML file
@@ -122,9 +138,15 @@ export class OpenSheetMusicDisplay {
         const score: IXmlElement = new IXmlElement(elem);
         const score: IXmlElement = new IXmlElement(elem);
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
         const reader: MusicSheetReader = new MusicSheetReader();
         const reader: MusicSheetReader = new MusicSheetReader();
-        this.sheet = reader.createMusicSheet(score, "Unknown path");
+        this.sheet = reader.createMusicSheet(score, "Untitled Score");
+        if (this.sheet === undefined) {
+            // error loading sheet, probably already logged, do nothing
+            return Promise.reject(new Error("given music sheet was incomplete or could not be loaded."));
+        }
         this.graphic = new GraphicalMusicSheet(this.sheet, calc);
         this.graphic = new GraphicalMusicSheet(this.sheet, calc);
-        this.cursor.init(this.sheet.MusicPartManager, this.graphic);
+        if (this.drawingParameters.drawCursors) {
+            this.cursor.init(this.sheet.MusicPartManager, this.graphic);
+        }
         log.info(`Loaded sheet ${this.sheet.TitleString} successfully.`);
         log.info(`Loaded sheet ${this.sheet.TitleString} successfully.`);
         return Promise.resolve({});
         return Promise.resolve({});
     }
     }
@@ -147,15 +169,9 @@ export class OpenSheetMusicDisplay {
         this.sheet.pageWidth = width / this.zoom / 10.0;
         this.sheet.pageWidth = width / this.zoom / 10.0;
         // Calculate again
         // Calculate again
         this.graphic.reCalculate();
         this.graphic.reCalculate();
-        this.graphic.Cursors.length = 0;
-        /*this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(0, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(1, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(2, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(3, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(4, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(5, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(6, 4), OutlineAndFillStyleEnum.PlaybackCursor));
-        this.graphic.Cursors.push(this.graphic.calculateCursorLineAtTimestamp(new Fraction(7, 4), OutlineAndFillStyleEnum.PlaybackCursor));*/
+        if (this.drawingParameters.drawCursors) {
+            this.graphic.Cursors.length = 0;
+        }
         // Update Sheet Page
         // Update Sheet Page
         const height: number = this.graphic.MusicPages[0].PositionAndShape.BorderBottom * 10.0 * this.zoom;
         const height: number = this.graphic.MusicPages[0].PositionAndShape.BorderBottom * 10.0 * this.zoom;
         this.drawer.clear();
         this.drawer.clear();
@@ -163,8 +179,10 @@ export class OpenSheetMusicDisplay {
         this.drawer.scale(this.zoom);
         this.drawer.scale(this.zoom);
         // Finally, draw
         // Finally, draw
         this.drawer.drawSheet(this.graphic);
         this.drawer.drawSheet(this.graphic);
-        // Update the cursor position
-        this.cursor.update();
+        if (this.drawingParameters.drawCursors) {
+            // Update the cursor position
+            this.cursor.update();
+        }
     }
     }
 
 
     /**
     /**
@@ -201,7 +219,9 @@ export class OpenSheetMusicDisplay {
      * FIXME: Probably unnecessary
      * FIXME: Probably unnecessary
      */
      */
     private reset(): void {
     private reset(): void {
-        this.cursor.hide();
+        if (this.drawingParameters.drawCursors) {
+            this.cursor.hide();
+        }
         this.sheet = undefined;
         this.sheet = undefined;
         this.graphic = undefined;
         this.graphic = undefined;
         this.zoom = 1.0;
         this.zoom = 1.0;
@@ -213,6 +233,7 @@ export class OpenSheetMusicDisplay {
      * 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: OpenSheetMusicDisplay = this;
         const self: OpenSheetMusicDisplay = this;
         this.handleResize(
         this.handleResize(
             () => {
             () => {
@@ -229,7 +250,9 @@ export class OpenSheetMusicDisplay {
                 //    document.documentElement.offsetWidth
                 //    document.documentElement.offsetWidth
                 //);
                 //);
                 //self.container.style.width = width + "px";
                 //self.container.style.width = width + "px";
-                self.render();
+                if (this.graphic !== undefined) {
+                    self.render();
+                }
             }
             }
         );
         );
     }
     }
@@ -240,6 +263,9 @@ export class OpenSheetMusicDisplay {
      * @param endCallback is the function called when resizing (kind-of) ends
      * @param endCallback is the function called when resizing (kind-of) ends
      */
      */
     private handleResize(startCallback: () => void, endCallback: () => void): void {
     private handleResize(startCallback: () => void, endCallback: () => void): void {
+        if (this.graphic === undefined) {
+            return;
+        }
         let rtime: number;
         let rtime: number;
         let timeout: number = undefined;
         let timeout: number = undefined;
         const delta: number = 200;
         const delta: number = 200;
@@ -274,7 +300,78 @@ export class OpenSheetMusicDisplay {
         window.setTimeout(endCallback, 1);
         window.setTimeout(endCallback, 1);
     }
     }
 
 
+    /** Enable or disable (hide) the cursor.
+     * @param enable whether to enable (true) or disable (false) the cursor
+     */
+    public enableOrDisableCursor(enable: boolean): void {
+        this.drawingParameters.drawCursors = enable;
+        if (enable) {
+            if (!this.cursor) {
+                this.cursor = new Cursor(this.innerElement, this);
+                if (this.sheet && this.graphic) { // else init is called in load()
+                    this.cursor.init(this.sheet.MusicPartManager, this.graphic);
+                }
+            }
+        } else { // disable cursor
+            if (!this.cursor) {
+                return;
+            }
+            this.cursor.hide();
+            // this.cursor = undefined;
+            // TODO cursor should be disabled, not just hidden. otherwise user can just call osmd.cursor.hide().
+            // however, this could cause null calls (cursor.next() etc), maybe that needs some solution.
+        }
+    }
+
     //#region GETTER / SETTER
     //#region GETTER / SETTER
+    private setDrawingParameters(options: IOSMDOptions): void {
+        this.drawingParameters = new DrawingParameters();
+        if (options.drawingParameters) {
+            this.drawingParameters.DrawingParametersEnum =
+                (<any>DrawingParametersEnum)[options.drawingParameters.toLowerCase()];
+        }
+        // individual drawing parameters options
+        if (options.disableCursor) {
+            this.drawingParameters.drawCursors = false;
+        }
+        if (options.drawHiddenNotes) {
+            this.drawingParameters.drawHiddenNotes = true;
+        }
+        if (options.drawTitle !== undefined) {
+            this.drawingParameters.DrawTitle = options.drawTitle;
+            // TODO these settings are duplicate in drawingParameters and EngravingRules. Maybe we only need them in EngravingRules.
+            // this sets the parameter in DrawingParameters, which in turn sets the parameter in EngravingRules.
+            // see tuplets settings below for the immediate approach
+        }
+        if (options.drawSubtitle !== undefined) {
+            this.drawingParameters.DrawSubtitle = options.drawSubtitle;
+        }
+        if (options.drawPartNames !== undefined) {
+            this.drawingParameters.DrawPartNames = options.drawPartNames;
+        }
+        if (options.drawLyricist !== undefined) {
+            this.drawingParameters.DrawLyricist = options.drawLyricist;
+        }
+        if (options.drawCredits !== undefined) {
+            this.drawingParameters.drawCredits = options.drawCredits;
+        }
+        if (options.defaultColorNoteHead) {
+            this.drawingParameters.defaultColorNoteHead = options.defaultColorNoteHead;
+        }
+        if (options.defaultColorStem) {
+            this.drawingParameters.defaultColorStem = options.defaultColorStem;
+        }
+        if (options.tupletsRatioed) {
+            EngravingRules.Rules.TupletsRatioed = true;
+        }
+        if (options.tupletsBracketed) {
+            EngravingRules.Rules.TupletsBracketed = true;
+        }
+        if (options.tripletsBracketed) {
+            EngravingRules.Rules.TripletsBracketed = true;
+        }
+    }
+
     public set DrawSkyLine(value: boolean) {
     public set DrawSkyLine(value: boolean) {
         if (this.drawer) {
         if (this.drawer) {
             this.drawer.skyLineVisible = value;
             this.drawer.skyLineVisible = value;

+ 2 - 1
test/Common/FileIO/Xml_Test.ts

@@ -48,7 +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 openSheetMusicDisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+            const openSheetMusicDisplay: OpenSheetMusicDisplay =
+                TestUtils.createOpenSheetMusicDisplay(div);
             openSheetMusicDisplay.load(score);
             openSheetMusicDisplay.load(score);
             done();
             done();
         }).timeout(3000);
         }).timeout(3000);

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

@@ -2,7 +2,6 @@ import chai = require("chai");
 import {OpenSheetMusicDisplay} from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 import {OpenSheetMusicDisplay} from "../../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
 import {TestUtils} from "../../Util/TestUtils";
 import {TestUtils} from "../../Util/TestUtils";
 
 
-
 describe("OpenSheetMusicDisplay Main Export", () => {
 describe("OpenSheetMusicDisplay Main Export", () => {
     let container1: HTMLElement;
     let container1: HTMLElement;
 
 
@@ -24,7 +23,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load MXL from string", (done: MochaDone) => {
     it("load MXL from string", (done: MochaDone) => {
         const mxl: string = TestUtils.getMXL("Mozart_Clarinet_Quintet_Excerpt.mxl");
         const mxl: string = TestUtils.getMXL("Mozart_Clarinet_Quintet_Excerpt.mxl");
         const div: HTMLElement = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(mxl).then(
         opensheetmusicdisplay.load(mxl).then(
             (_: {}) => {
             (_: {}) => {
                 opensheetmusicdisplay.render();
                 opensheetmusicdisplay.render();
@@ -37,7 +36,7 @@ describe("OpenSheetMusicDisplay 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 = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(mxl).then(
         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,7 +55,7 @@ describe("OpenSheetMusicDisplay 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 = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(xml).then(
         opensheetmusicdisplay.load(xml).then(
             (_: {}) => {
             (_: {}) => {
                 opensheetmusicdisplay.render();
                 opensheetmusicdisplay.render();
@@ -69,7 +68,7 @@ describe("OpenSheetMusicDisplay 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 = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(score).then(
         opensheetmusicdisplay.load(score).then(
             (_: {}) => {
             (_: {}) => {
                 opensheetmusicdisplay.render();
                 opensheetmusicdisplay.render();
@@ -82,7 +81,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load MXL Document by URL", (done: MochaDone) => {
     it("load MXL Document by URL", (done: MochaDone) => {
         const url: string = "base/test/data/Mozart_Clarinet_Quintet_Excerpt.mxl";
         const url: string = "base/test/data/Mozart_Clarinet_Quintet_Excerpt.mxl";
         const div: HTMLElement = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(url).then(
         opensheetmusicdisplay.load(url).then(
             (_: {}) => {
             (_: {}) => {
                 opensheetmusicdisplay.render();
                 opensheetmusicdisplay.render();
@@ -92,10 +91,28 @@ describe("OpenSheetMusicDisplay Main Export", () => {
         );
         );
     });
     });
 
 
-    it("load MXL Document by invalid URL", (done: MochaDone) => {
+    it("load something invalid by URL", (done: MochaDone) => {
         const url: string = "https://www.google.com";
         const url: string = "https://www.google.com";
         const div: HTMLElement = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
+        opensheetmusicdisplay.load(url).then(
+            (_: {}) => {
+                done(new Error("Invalid URL appears to be loaded correctly"));
+            },
+            (exc: Error) => {
+                if (exc.message.toLowerCase().match(/opensheetmusicdisplay.*invalid/)) {
+                    done();
+                } else {
+                    done(new Error("Unexpected error: " + exc.message));
+                }
+            }
+        );
+    }).timeout(5000);
+
+    it("load invalid URL", (done: MochaDone) => {
+        const url: string = "https://www.afjkhfjkauu2ui3z2uiu.com";
+        const div: HTMLElement = TestUtils.getDivElement(document);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(url).then(
         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,7 +130,7 @@ describe("OpenSheetMusicDisplay 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 = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(xml).then(
         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,7 +147,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
 
 
     it("render without loading", (done: MochaDone) => {
     it("render without loading", (done: MochaDone) => {
         const div: HTMLElement = TestUtils.getDivElement(document);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         chai.expect(() => {
         chai.expect(() => {
             return opensheetmusicdisplay.render();
             return opensheetmusicdisplay.render();
         }).to.throw(/load/);
         }).to.throw(/load/);
@@ -139,8 +156,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
 
 
     before((): void => {
     before((): void => {
         // Create the container for the "test width" test
         // Create the container for the "test width" test
-        container1 = document.createElement("div");
-        document.body.appendChild(container1);
+        container1 = TestUtils.getDivElement(document);
     });
     });
     after((): void => {
     after((): void => {
         // Destroy the container for the "test width" test
         // Destroy the container for the "test width" test
@@ -150,7 +166,7 @@ describe("OpenSheetMusicDisplay 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 opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         opensheetmusicdisplay.load(score).then(
         opensheetmusicdisplay.load(score).then(
             (_: {}) => {
             (_: {}) => {
@@ -165,7 +181,7 @@ describe("OpenSheetMusicDisplay 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 opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         opensheetmusicdisplay.load(score).then(
         opensheetmusicdisplay.load(score).then(
             (_: {}) => {
             (_: {}) => {

+ 2 - 1
test/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer_Test.ts

@@ -9,6 +9,7 @@ import {IXmlElement} from "../../../../src/Common/FileIO/Xml";
 import {Fraction} from "../../../../src/Common/DataObjects/Fraction";
 import {Fraction} from "../../../../src/Common/DataObjects/Fraction";
 import {VexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/VexFlowBackend";
 import {VexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/VexFlowBackend";
 import {CanvasVexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend";
 import {CanvasVexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend";
+import {DrawingParameters} from "../../../../src/MusicalScore/Graphical/DrawingParameters";
 
 
 /* tslint:disable:no-unused-expression */
 /* tslint:disable:no-unused-expression */
 describe("VexFlow Music Sheet Drawer", () => {
 describe("VexFlow Music Sheet Drawer", () => {
@@ -47,7 +48,7 @@ describe("VexFlow Music Sheet Drawer", () => {
         const canvas: HTMLCanvasElement = document.createElement("canvas");
         const canvas: HTMLCanvasElement = document.createElement("canvas");
         const backend: VexFlowBackend = new CanvasVexFlowBackend();
         const backend: VexFlowBackend = new CanvasVexFlowBackend();
         backend.initialize(canvas);
         backend.initialize(canvas);
-        const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer(canvas, backend);
+        const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer(canvas, backend, new DrawingParameters());
         drawer.drawSheet(gms);
         drawer.drawSheet(gms);
         done();
         done();
     });
     });

+ 5 - 0
test/Util/TestUtils.ts

@@ -1,3 +1,5 @@
+import { OpenSheetMusicDisplay } from "../../src/OpenSheetMusicDisplay/OpenSheetMusicDisplay";
+
 /**
 /**
  * This class collects useful methods to interact with test data.
  * This class collects useful methods to interact with test data.
  * During tests, XML and MXL documents are preprocessed by karma,
  * During tests, XML and MXL documents are preprocessed by karma,
@@ -37,4 +39,7 @@ export class TestUtils {
         }
         }
     }
     }
 
 
+    public static createOpenSheetMusicDisplay(div: HTMLElement): OpenSheetMusicDisplay {
+        return new OpenSheetMusicDisplay(div);
+    }
 }
 }

+ 765 - 8
test/data/OSMD_function_test_all.xml

@@ -2272,21 +2272,778 @@
         <rest/>
         <rest/>
         <duration>16</duration>
         <duration>16</duration>
         <voice>1</voice>
         <voice>1</voice>
+	<lyric number="1" default-x="11.65" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Tuplets:</text>
+          </lyric>
         </note>
         </note>
       </measure>
       </measure>
-    <measure number="36" width="103.42">
-      <note>
-        <rest/>
-        <duration>16</duration>
+    <measure number="36" width="1110.61">
+      <print>
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <top-system-distance>170.00</top-system-distance>
+          </system-layout>
+        </print>
+      <attributes>
+        <divisions>480</divisions>
+        <key>
+          <fifths>0</fifths>
+          </key>
+        <time>
+          <beats>4</beats>
+          <beat-type>4</beat-type>
+          </time>
+        <clef>
+          <sign>G</sign>
+          <line>2</line>
+          </clef>
+        </attributes>
+      <note default-x="148.28" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        <lyric number="1" default-x="6.58" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>NoBracketsInXml</text>
+          </lyric>
+        </note>
+      <note default-x="237.05" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="283.57" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="330.08" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="376.59" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="423.11" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="485.15" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="547.19" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="609.23" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="650.20" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="691.17" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="732.14" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="773.11" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="814.08" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="855.05" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="no"/>
+          </notations>
+        </note>
+      <note default-x="891.53" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
         <voice>1</voice>
         <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
         </note>
         </note>
+      <note default-x="928.00" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="964.47" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="1000.95" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="1037.42" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="1073.89" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <backup>
+        <duration>3</duration>
+        </backup>
       </measure>
       </measure>
-    <measure number="37" width="103.42">
-      <note>
-        <rest/>
-        <duration>16</duration>
+    <measure number="37" width="1110.61">
+      <print new-system="yes">
+        <system-layout>
+          <system-margins>
+            <left-margin>0.00</left-margin>
+            <right-margin>0.00</right-margin>
+            </system-margins>
+          <system-distance>150.00</system-distance>
+          </system-layout>
+        </print>
+      <note default-x="108.79" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        <lyric number="1" default-x="6.58" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>BracketsInXml</text>
+          </lyric>
+        </note>
+      <note default-x="184.17" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="233.03" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="281.88" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>96</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="330.74" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>96</duration>
         <voice>1</voice>
         <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>5</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
         </note>
         </note>
+      <note default-x="379.59" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        </note>
+      <note default-x="444.75" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        </note>
+      <note default-x="509.92" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>160</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <time-modification>
+          <actual-notes>3</actual-notes>
+          <normal-notes>2</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="575.08" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        </note>
+      <note default-x="618.12" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="661.15" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="704.18" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="747.21" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="790.24" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>80</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>6</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>up</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <note default-x="833.27" default-y="-35.00">
+        <pitch>
+          <step>F</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">begin</beam>
+        <beam number="2">begin</beam>
+        <notations>
+          <tuplet type="start" bracket="yes"/>
+          </notations>
+        </note>
+      <note default-x="871.58" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="909.89" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="948.20" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="986.51" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="1024.82" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">continue</beam>
+        <beam number="2">continue</beam>
+        </note>
+      <note default-x="1063.12" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>69</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <time-modification>
+          <actual-notes>7</actual-notes>
+          <normal-notes>4</normal-notes>
+          </time-modification>
+        <stem>down</stem>
+        <beam number="1">end</beam>
+        <beam number="2">end</beam>
+        <notations>
+          <tuplet type="stop"/>
+          </notations>
+        </note>
+      <backup>
+        <duration>3</duration>
+        </backup>
+      <barline location="right">
+        <bar-style>light-heavy</bar-style>
+        </barline>
       </measure>
       </measure>
     <measure number="38" width="112.42">
     <measure number="38" width="112.42">
       <note>
       <note>

+ 3 - 1
tsconfig.json

@@ -3,9 +3,11 @@
     "target": "es5",
     "target": "es5",
     "module": "commonjs",
     "module": "commonjs",
     "moduleResolution": "node",
     "moduleResolution": "node",
+    "noUnusedLocals": true,
     "outDir": "dist",
     "outDir": "dist",
     "declaration": true,
     "declaration": true,
-    "sourceMap": true
+    "sourceMap": true,
+    "typeRoots": ["./node_modules/@types"]
   },
   },
   "exclude": [
   "exclude": [
     "node_modules",
     "node_modules",

+ 0 - 1
tslint.json

@@ -56,7 +56,6 @@
     "no-switch-case-fall-through": true,
     "no-switch-case-fall-through": true,
     "no-trailing-whitespace": true,
     "no-trailing-whitespace": true,
     "no-unused-expression": true,
     "no-unused-expression": true,
-    "no-unused-variable": true,
     "no-var-keyword": true,
     "no-var-keyword": true,
     "no-var-requires": true,
     "no-var-requires": true,
     "object-literal-sort-keys": true,
     "object-literal-sort-keys": true,

+ 1 - 1
webpack.prod.js

@@ -35,6 +35,6 @@ module.exports = merge(common, {
             path: path.resolve(__dirname, 'build'),
             path: path.resolve(__dirname, 'build'),
             filename: './statistics.html'
             filename: './statistics.html'
         }),
         }),
-        new Cleaner(pathsToClean, {verbose: true, dry: false})
+        new Cleaner(pathsToClean, { verbose: true, dry: false })
     ]
     ]
 })
 })

+ 8 - 0
webpack.sourcemap.js

@@ -0,0 +1,8 @@
+var merge = require('webpack-merge')
+var common = require('./webpack.common.js')
+
+// will create a build plus separate .min.js.map source map for debugging
+module.exports = merge(common, {
+    devtool: 'source-map',
+    mode: 'development'
+})