Browse Source

Merge branch 'release/0.5.1' into master

sschmidTU 6 năm trước cách đây
mục cha
commit
f86929ef86
49 tập tin đã thay đổi với 1746 bổ sung336 xóa
  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
 node_js:
 - '6'
 - '8'
 env:
-  - timeout=4000
+  - timeout=10000
 notifications:
   email: false
   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>
 # [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)
 [![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.
 
@@ -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.
 
+Try the [Demo](https://opensheetmusicdisplay.github.io/demo/) to see what OSMD can do.
+
 
 <!--# <a name="license"></a>License
 The MIT License (MIT)

+ 17 - 8
demo/index.html

@@ -2,7 +2,8 @@
 <html lang="en">
 <head>
     <meta charset="utf-8">
-
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    
     <title><%= htmlWebpackPlugin.options.title %></title>
     <meta name="description" content="A showcase for OpenSheetMusicDisplay.">
     <meta name="author" content="OpenSheetMusicDisplay contributors">
@@ -18,14 +19,14 @@
     <img src="./favicon.ico?" class="ui image">
     <%= htmlWebpackPlugin.options.title %>
 </h1>
-<div class="ui three column grid container">
+<div class="ui four column grid container">
     <div class="column">
         <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 class="column">
         <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="canvas">Canvas</option>>
         </select>
@@ -81,11 +82,13 @@
             <option value="none">None</option>
             <option value="all">All</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="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>
     </div>
     <div class="column">
@@ -105,6 +108,12 @@
             </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>
 <table cellspacing="0" style="max-width:700px;">
     <tr id="error-tr">

+ 48 - 10
demo/index.js

@@ -57,7 +57,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
     resetCursorBtn,
     showCursorBtn,
     hideCursorBtn,
-    backendSelect;
+    backendSelect,
+    debugReRenderBtn;
 
     // Initialization code
     function init() {
@@ -79,6 +80,8 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         showCursorBtn = document.getElementById("show-cursor-btn");
         hideCursorBtn = document.getElementById("hide-cursor-btn");
         backendSelect = document.getElementById("backend-select");
+        debugReRenderBtn = document.getElementById("debug-re-render-btn");
+
 
         // Hide error
         error();
@@ -113,12 +116,28 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             openSheetMusicDisplay.DrawSkyLine = !openSheetMusicDisplay.DrawSkyLine;
         }
 
-        bottomlineDebug .onclick = function() {
+        bottomlineDebug.onclick = function() {
             openSheetMusicDisplay.DrawBottomLine = !openSheetMusicDisplay.DrawBottomLine;
         }
 
+        debugReRenderBtn.onclick = function() {
+            rerender();
+        }
+
         // 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');
         document.body.appendChild(canvas);
 
@@ -141,7 +160,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 try {
                     openSheetMusicDisplay.render();
                 } catch (e) {
-                    console.warn(e.stack);
+                    errorLoadingOrRenderingSheet(e, "rendering");
                 }
                 enable();
             }
@@ -163,7 +182,11 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
             openSheetMusicDisplay.cursor.hide();
         });
         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) {
@@ -178,7 +201,6 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
     }
 
     function Resize(startCallback, endCallback) {
-
       var rtime;
       var timeout = false;
       var delta = 200;
@@ -225,20 +247,28 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
                 return openSheetMusicDisplay.render();
             },
             function(e) {
-                console.warn(e.stack);
-                error("Error reading sheet: " + e);
+                errorLoadingOrRenderingSheet(e, "rendering");
             }
         ).then(
             function() {
                 return onLoadingEnd(isCustom);
             }, function(e) {
-                console.warn(e.stack);
-                error("Error rendering sheet: " + process.env.DEBUG ? e.stack : e);
+                errorLoadingOrRenderingSheet(e, "loading");
                 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) {
         sampleLoaded = true;
         // Remove option from select
@@ -262,6 +292,14 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         }, 0);
     }
 
+    function rerender() {
+        disable();
+        window.setTimeout(function(){
+            openSheetMusicDisplay.render();
+            enable();
+        }, 0);
+    }
+
     function error(errString) {
         if (!errString) {
             error_tr.style.display = "none";

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

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

+ 30 - 31
karma.conf.js

@@ -13,24 +13,22 @@ module.exports = function (config) {
         // list of files to 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
         // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
@@ -38,7 +36,6 @@ module.exports = function (config) {
             'test/data/*.xml': ['xml2js'],
             'test/data/*.mxl.base64': ['base64-to-js'],
             // add webpack as preprocessor
-            'src/**/*.ts': ['webpack'],
             'test/**/*.ts': ['webpack']
         },
 
@@ -48,23 +45,18 @@ module.exports = function (config) {
             // webpack watches dependencies
 
             // 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',
             module: {
                 rules: common.module.rules
             },
             resolve: common.resolve
         },
-        webpackMiddleware: {
-            // webpack-dev-middleware configuration
-            // i. e.
-            noInfo: true
-        },
 
         // Required for Firefox and Chorme to work
         // see https://github.com/webpack-contrib/karma-webpack/issues/188
         mime: {
-            'text/x-typescript': ['ts', 'tsx']
+            'text/x-typescript': ['ts']
         },
 
         // test results reporter to use
@@ -75,19 +67,21 @@ module.exports = function (config) {
         // web server port
         port: 9876,
         // timeout in ms:
-        browserNoActivityTimeout: 100000,
+        browserNoActivityTimeout: 100000, // default 10000
+        browserDisconnectTimeout: 10000, // default 2000
+        browserDisconnectTolerance: 1, // default 0
         captureTimeout: 60000,
         // enable / disable colors in the output (reporters and logs)
         colors: true,
 
         // level of logging
         // 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: {
             captureConsole: true,
             mocha: {
-                timeout: process.env.timeout || 2000
+                timeout: process.env.timeout || 6000
             }
         },
 
@@ -95,14 +89,19 @@ module.exports = function (config) {
         autoWatch: false,
 
         // start these browsers
-        browsers: [process.env.CI ? 'ChromeHeadlessNoSandbox' : 'ChromeHeadless'],
+        browsers: ['ChromeHeadlessNoSandbox'],
 
         // For security reasons, Google Chrome is unable to provide sandboxing
         // when it is running in container-based environments (e.g. CI).
         customLaunchers: {
             ChromeHeadlessNoSandbox: {
                 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",
-  "version": "0.5.0",
+  "version": "0.5.1",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
   "main": "build/opensheetmusicdisplay.min.js",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
@@ -10,12 +10,13 @@
     "tslint": "tslint --project tsconfig.json \"src/**/*.ts\" \"test/**/*.ts\"",
     "lint": "npm-run-all eslint tslint",
     "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",
     "build": "npm-run-all lint build:webpack",
     "build:doc": "cross-env STATIC_FILES_SUBFOLDER=sheets npm run build",
     "build:webpack": "webpack --progress --colors --config webpack.prod.js",
     "build:webpack-dev": "webpack --progress --colors --config webpack.dev.js",
+    "build:webpack-sourcemap": "webpack --progress --colors --config webpack.sourcemap.js",
     "start": "webpack-dev-server --progress --colors --config webpack.dev.js",
     "fix-memory-limit": "increase-memory-limit"
   },
@@ -47,7 +48,7 @@
   },
   "homepage": "http://opensheetmusicdisplay.org",
   "dependencies": {
-    "es6-promise": "^4.0.5",
+    "es6-promise": "^4.2.5",
     "increase-memory-limit": "^1.0.6",
     "jszip": "^3.0.0",
     "loglevel": "^1.5.0",
@@ -59,18 +60,19 @@
     "@types/chai": "^4.0.3",
     "@types/loglevel": "^1.4.29",
     "@types/mocha": "^5.2.3",
+    "browserify": ">=10.x",
     "chai": "^4.1.0",
     "clean-webpack-plugin": "^0.1.18",
     "cross-env": "^5.1.3",
     "cz-conventional-changelog": "^2.0.0",
     "eslint": "^5.0.1",
-    "eslint-config-standard": "^11.0.0",
+    "eslint-config-standard": "^12.0.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",
-    "html-webpack-plugin": "^3.1.0",
+    "html-webpack-plugin": "^3.2.0",
     "http-server": "^0.11.0",
     "jquery": "^3.2.1",
     "karma": "^3.0.0",
@@ -80,22 +82,22 @@
     "karma-firefox-launcher": "^1.0.0",
     "karma-mocha": "^1.1.1",
     "karma-mocha-reporter": "^2.0.4",
-    "karma-webpack": "^3.0.0",
+    "karma-webpack": "^3.0.4",
     "karma-xml2js-preprocessor": "^0.0.3",
     "mocha": "^5.2.0",
     "npm-run-all": "^4.1.2",
     "pre-commit": "^1.2.2",
     "ts-loader": "^4.1.0",
-    "tsify": "^3.0.0",
+    "tsify": "^4.0.0",
     "tslint": "^5.8.0",
     "tslint-loader": "^3.5.3",
     "typedoc": "^0.12.0",
     "typescript": "^2.6.1",
-    "uglifyjs-webpack-plugin": "^1.2.4",
+    "uglifyjs-webpack-plugin": "^2.0.0",
     "underscore-template-loader": "^1.0.0",
-    "webpack": "^4.15.1",
+    "webpack": "^4.18.1",
     "webpack-cli": "^3.0.8",
-    "webpack-dev-server": "3.1.6",
+    "webpack-dev-server": "3.1.9",
     "webpack-merge": "^4.1.2",
     "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.)
- * 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,
     LeftCenter,
     LeftBottom,
@@ -17,23 +16,28 @@ export enum TextAlignmentAndPlacement {
     RightCenter,
     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 {
-    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 {
-  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 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 {
+    /** will set other settings if changed with set method */
+    private drawingParametersEnum: DrawingParametersEnum;
     public drawHighlights: boolean;
     public drawErrors: boolean;
     public drawSelectionStartSymbol: boolean;
@@ -8,6 +21,46 @@ export class DrawingParameters {
     public drawScrollIndicator: boolean;
     public drawComments: 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 {
         this.drawHighlights = true;
@@ -19,9 +72,22 @@ export class DrawingParameters {
         this.drawScrollIndicator = true;
         this.drawComments = 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.drawErrors = false;
         this.drawSelectionStartSymbol = false;
@@ -31,6 +97,18 @@ export class DrawingParameters {
         this.drawScrollIndicator = false;
         this.drawComments = 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 {
@@ -44,4 +122,59 @@ export class DrawingParameters {
         this.drawComments = 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 {MusicSymbol} from "./MusicSymbol";
 import * as log from "loglevel";
-import { TextAlignmentAndPlacement } from "../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
 
 export class EngravingRules {
     private static rules: EngravingRules;
@@ -13,9 +13,11 @@ export class EngravingRules {
     private sheetMinimumDistanceBetweenTitleAndSubtitle: number;
     private sheetComposerHeight: number;
     private sheetAuthorHeight: number;
+    private compactMode: boolean;
     private pagePlacementEnum: PagePlacementEnum;
     private pageHeight: number;
     private pageTopMargin: number;
+    private pageTopMarginNarrow: number;
     private pageBottomMargin: number;
     private pageLeftMargin: number;
     private pageRightMargin: number;
@@ -69,7 +71,7 @@ export class EngravingRules {
     private distanceOffsetBetweenTwoHorizontallyCrossedWedges: number;
     private wedgeMinLength: number;
     private distanceBetweenAdjacentDynamics: number;
-    private tempoChangeMeasureValitidy: number;
+    private tempoChangeMeasureValidity: number;
     private tempoContinousFactor: number;
     private staccatoScalingFactor: number;
     private betweenDotsDistance: number;
@@ -79,6 +81,18 @@ export class EngravingRules {
     private fingeringLabelFontHeight: number;
     private measureNumberLabelHeight: 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 tupletNumberYOffset: number;
     private labelMarginBorderFactor: number;
@@ -88,7 +102,7 @@ export class EngravingRules {
     private repetitionEndingLabelYOffset: number;
     private repetitionEndingLineYLowerOffset: number;
     private repetitionEndingLineYUpperOffset: number;
-    private lyricsAlignmentStandard: TextAlignmentAndPlacement;
+    private lyricsAlignmentStandard: TextAlignmentEnum;
     private lyricsHeight: number;
     private lyricsYOffsetToStaffHeight: number;
     private verticalBetweenLyricsDistance: number;
@@ -140,11 +154,17 @@ export class EngravingRules {
     private minNoteDistance: number;
     private subMeasureXSpacingThreshold: number;
     private measureDynamicsMaxScalingFactor: number;
+    private wholeRestXShiftVexflow: number;
     private maxInstructionsConstValue: number;
     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 durationDistanceDict: {[_: number]: number; } = {};
     private durationScalingDistanceDict: {[_: number]: number; } = {};
+    private renderComposer: boolean;
+    private renderTitle: boolean;
+    private renderSubtitle: boolean;
+    private renderLyricist: boolean;
+    private renderInstrumentNames: boolean;
 
     constructor() {
         // global variables
@@ -158,9 +178,11 @@ export class EngravingRules {
         this.sheetAuthorHeight = 2.0;
 
         // Staff sizing Variables
+        this.compactMode = false;
         this.pagePlacementEnum = PagePlacementEnum.Down;
         this.pageHeight = 100001.0;
         this.pageTopMargin = 5.0;
+        this.pageTopMarginNarrow = 0.0; // for compact mode
         this.pageBottomMargin = 5.0;
         this.pageLeftMargin = 5.0;
         this.pageRightMargin = 5.0;
@@ -218,7 +240,7 @@ export class EngravingRules {
         this.graceNoteScalingFactor = 0.6;
         this.graceNoteXOffset = 0.2;
 
-        // GraceNote Variables
+        // Wedge Variables
         this.wedgeOpeningLength = 1.2;
         this.wedgeMeasureEndOpeningLength = 0.75;
         this.wedgeMeasureBeginOpeningLength = 0.75;
@@ -230,8 +252,8 @@ export class EngravingRules {
         this.wedgeMinLength = 2.0;
         this.distanceBetweenAdjacentDynamics = 0.75;
 
-        // GraceNote Variables
-        this.tempoChangeMeasureValitidy = 4;
+        // Tempo Variables
+        this.tempoChangeMeasureValidity = 4;
         this.tempoContinousFactor = 0.7;
 
         // various
@@ -241,15 +263,18 @@ export class EngravingRules {
         this.chordSymbolTextHeight = 2.0;
         this.fingeringLabelFontHeight = 1.7;
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Tuplets, MeasureNumber and TupletNumber Labels
         this.measureNumberLabelHeight = 1.5 * EngravingRules.unit;
         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.tupletNumberYOffset = 0.5;
         this.labelMarginBorderFactor = 0.1;
         this.tupletVerticalLineLength = 0.5;
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Slur and Tie variables
         this.bezierCurveStepSize = 1000;
         this.calculateCurveParametersArrays();
         this.tieGhostObjectWidth = 0.75;
@@ -266,7 +291,7 @@ export class EngravingRules {
         this.slurTangentMaxAngle = 80.0;
         this.slursStartingAtSameStaffEntryYOffset = 0.8;
 
-        // MeasureNumber- and TupletNumberLabel variables
+        // Repetitions
         this.repetitionEndingLabelHeight = 2.0;
         this.repetitionEndingLabelXOffset = 0.5;
         this.repetitionEndingLabelYOffset = 0.3;
@@ -274,7 +299,7 @@ export class EngravingRules {
         this.repetitionEndingLineYUpperOffset = 0.3;
 
         // 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.lyricsYOffsetToStaffHeight = 3.0; // distance between lyrics and staff. could partly be even lower/dynamic
         this.verticalBetweenLyricsDistance = 0.5;
@@ -316,6 +341,14 @@ export class EngravingRules {
         this.minNoteDistance = 2.0;
         this.subMeasureXSpacingThreshold = 35;
         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();
         try {
@@ -372,6 +405,12 @@ export class EngravingRules {
     public set PagePlacement(value: PagePlacementEnum) {
         this.pagePlacementEnum = value;
     }
+    public get CompactMode(): boolean {
+        return this.compactMode;
+    }
+    public set CompactMode(value: boolean) {
+        this.compactMode = value;
+    }
     public get PageHeight(): number {
         return this.pageHeight;
     }
@@ -384,6 +423,12 @@ export class EngravingRules {
     public set PageTopMargin(value: number) {
         this.pageTopMargin = value;
     }
+    public get PageTopMarginNarrow(): number {
+        return this.pageTopMarginNarrow;
+    }
+    public set PageTopMarginNarrow(value: number) {
+        this.pageTopMarginNarrow = value;
+    }
     public get PageBottomMargin(): number {
         return this.pageBottomMargin;
     }
@@ -708,11 +753,11 @@ export class EngravingRules {
     public set DistanceBetweenAdjacentDynamics(value: number) {
         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 {
         return this.tempoContinousFactor;
@@ -762,6 +807,24 @@ export class EngravingRules {
     public set MeasureNumberLabelOffset(value: number) {
         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 {
         return this.tupletNumberLabelHeight;
     }
@@ -816,10 +879,10 @@ export class EngravingRules {
     public set RepetitionEndingLineYUpperOffset(value: number) {
         this.repetitionEndingLineYUpperOffset = value;
     }
-    public get LyricsAlignmentStandard(): TextAlignmentAndPlacement {
+    public get LyricsAlignmentStandard(): TextAlignmentEnum {
         return this.lyricsAlignmentStandard;
     }
-    public set LyricsAlignmentStandard(value: TextAlignmentAndPlacement) {
+    public set LyricsAlignmentStandard(value: TextAlignmentEnum) {
         this.lyricsAlignmentStandard = value;
     }
     public get LyricsHeight(): number {
@@ -1128,6 +1191,12 @@ export class EngravingRules {
     public set MeasureDynamicsMaxScalingFactor(value: number) {
         this.measureDynamicsMaxScalingFactor = value;
     }
+    public get WholeRestXShiftVexflow(): number {
+        return this.wholeRestXShiftVexflow;
+    }
+    public set WholeRestXShiftVexflow(value: number) {
+        this.wholeRestXShiftVexflow = value;
+    }
     public get MaxInstructionsConstValue(): number {
         return this.maxInstructionsConstValue;
     }
@@ -1152,6 +1221,36 @@ export class EngravingRules {
     public get DurationScalingDistanceDict(): {[_: number]: number; } {
         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.

+ 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 {GraphicalLabel} from "./GraphicalLabel";
 import {ChordSymbolContainer} from "../VoiceData/ChordSymbolContainer";
@@ -23,7 +23,7 @@ export class GraphicalChordSymbolContainer extends GraphicalObject {
     }
     private calculateLabel(textHeight: number, transposeHalftones: number): void {
         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);
     }
 }

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

@@ -1,5 +1,5 @@
 import {Label} from "../Label";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {Clickable} from "./Clickable";
 import {BoundingBox} from "./BoundingBox";
 import {EngravingRules} from "./EngravingRules";
@@ -18,7 +18,7 @@ export class GraphicalLabel extends Clickable {
      * @param alignment Alignement like left, right, top, ...
      * @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();
         this.label = label;
         this.boundingBox = new BoundingBox(this, parent);
@@ -50,55 +50,55 @@ export class GraphicalLabel extends Clickable {
         const bbox: BoundingBox = this.PositionAndShape;
 
         switch (this.Label.textAlignment) {
-            case TextAlignmentAndPlacement.CenterBottom:
+            case TextAlignmentEnum.CenterBottom:
                 bbox.BorderTop = -height;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = 0;
                 bbox.BorderRight = width / 2;
                 break;
-            case TextAlignmentAndPlacement.CenterCenter:
+            case TextAlignmentEnum.CenterCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = width / 2;
                 break;
-            case TextAlignmentAndPlacement.CenterTop:
+            case TextAlignmentEnum.CenterTop:
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = -width / 2;
                 bbox.BorderBottom = height;
                 bbox.BorderRight = width / 2;
                 break;
-            case TextAlignmentAndPlacement.LeftBottom:
+            case TextAlignmentEnum.LeftBottom:
                 bbox.BorderTop = -height;
-                bbox.BorderLeft = -1;
+                bbox.BorderLeft = 0;
                 bbox.BorderBottom = 0;
-                bbox.BorderRight = width - 1;
+                bbox.BorderRight = width;
                 break;
-            case TextAlignmentAndPlacement.LeftCenter:
+            case TextAlignmentEnum.LeftCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = 0;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = width;
                 break;
-            case TextAlignmentAndPlacement.LeftTop:
+            case TextAlignmentEnum.LeftTop:
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = 0;
                 bbox.BorderBottom = height;
                 bbox.BorderRight = width;
                 break;
-            case TextAlignmentAndPlacement.RightBottom:
+            case TextAlignmentEnum.RightBottom:
                 bbox.BorderTop = -height;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = 0;
                 bbox.BorderRight = 0;
                 break;
-            case TextAlignmentAndPlacement.RightCenter:
+            case TextAlignmentEnum.RightCenter:
                 bbox.BorderTop = -height / 2;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = height / 2;
                 bbox.BorderRight = 0;
                 break;
-            case TextAlignmentAndPlacement.RightTop:
+            case TextAlignmentEnum.RightTop:
                 bbox.BorderTop = 0;
                 bbox.BorderLeft = -width;
                 bbox.BorderBottom = height;

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

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

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

@@ -31,7 +31,7 @@ import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
 import {BoundingBox} from "./BoundingBox";
 import {Instrument} from "../Instrument";
 import {GraphicalLabel} from "./GraphicalLabel";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {VerticalGraphicalStaffEntryContainer} from "./VerticalGraphicalStaffEntryContainer";
 import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
 import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
@@ -161,7 +161,7 @@ export abstract class MusicSheetCalculator {
         }
         this.handleStaffEntries();
         this.calculateVerticalContainersList();
-        this.setIndecesToVerticalGraphicalContainers();
+        this.setIndicesToVerticalGraphicalContainers();
     }
 
     /**
@@ -224,22 +224,34 @@ export abstract class MusicSheetCalculator {
         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 {
-        // xPosition is always fix
+        // xPosition is always fixed
         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
         const firstMusicSystem: MusicSystem = graphicalMusicPage.MusicSystems[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 {
-            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;
 
         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 {
         const labelNumber: string = measure.MeasureNumber.toString();
         const graphicalLabel: GraphicalLabel = new GraphicalLabel(new Label(labelNumber), this.rules.MeasureNumberLabelHeight,
-                                                                  TextAlignmentAndPlacement.LeftBottom);
+                                                                  TextAlignmentEnum.LeftBottom);
 
         const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
 
@@ -435,7 +447,7 @@ export abstract class MusicSheetCalculator {
             measure.PositionAndShape.RelativePosition.x - graphicalLabel.PositionAndShape.BorderMarginLeft;
         let relativeY: number;
 
-        // and the corresponding SkyLine indeces
+        // and the corresponding SkyLine indices
         let start: number = relativeX;
         let end: number = relativeX - graphicalLabel.PositionAndShape.BorderLeft + graphicalLabel.PositionAndShape.BorderMarginRight;
 
@@ -542,7 +554,8 @@ export abstract class MusicSheetCalculator {
                 if (this.leadSheet) {
                     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);
             }
         }
@@ -550,9 +563,10 @@ export abstract class MusicSheetCalculator {
         // update BottomLine (on the whole StaffLine's length)
         if (lyricsStaffEntriesList.length > 0) {
             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].parentMeasure.PositionAndShape.RelativePosition.x;
+            startX = startX > endX ? endX : startX;
             skyBottomLineCalculator.updateBottomLineInRange(startX, endX, maxPosition);
         }
         return lyricsStaffEntriesList;
@@ -896,17 +910,18 @@ export abstract class MusicSheetCalculator {
                              combinedString: string,
                              style: FontStyles,
                              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;
 
         // 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;
         const marginFactor: number = 1.1;
 
         if (placement === PlacementEnum.Below) {
-            graphLabel.Label.textAlignment = TextAlignmentAndPlacement.LeftTop;
+            graphLabel.Label.textAlignment = TextAlignmentEnum.LeftTop;
         }
 
         graphLabel.setLabelPositionAndShapeBorders();
@@ -983,27 +998,36 @@ export abstract class MusicSheetCalculator {
                 instantaniousTempo.Placement = PlacementEnum.Above;
 
                 // 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
                 if (staffLine.Measures[0].staffEntries.length > 0 &&
                     Math.abs(relative.x - staffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x) === 0 &&
                     staffLine.ParentMusicSystem === staffLine.ParentMusicSystem.Parent.MusicSystems[0]) {
                     const firstInstructionEntry: GraphicalStaffEntry = staffLine.Measures[0].FirstInstructionStaffEntry;
                     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[] = [];
             for (const entry of multiTempoExpression.EntriesList) {
+                let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
+                if (EngravingRules.Rules.CompactMode) {
+                    textAlignment = TextAlignmentEnum.LeftBottom;
+                }
                 const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
                                                                        relative,
                                                                        entry.label,
                                                                        multiTempoExpression.getFontstyleOfFirstEntry(),
                                                                        entry.Expression.Placement,
-                                                                       EngravingRules.Rules.UnknownTextHeight);
+                                                                       EngravingRules.Rules.UnknownTextHeight,
+                                                                       textAlignment);
 
                 if (entry.Expression instanceof InstantaneousTempoExpression) {
                     let alreadyAdded: boolean = false;
@@ -1098,10 +1122,23 @@ export abstract class MusicSheetCalculator {
                                openTuplets: Tuplet[], openBeams: Beam[],
                                octaveShiftValue: OctaveEnum, linkedNotes: Note[] = undefined,
                                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);
         const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
         for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
             const note: Note = voiceEntry.Notes[idx];
+            if (note === undefined || !note.PrintObject) {
+                continue;
+            }
             if (sourceStaffEntry !== undefined && sourceStaffEntry.Link !== undefined && linkedNotes !== undefined && linkedNotes.indexOf(note) > -1) {
                 continue;
             }
@@ -1163,11 +1200,14 @@ export abstract class MusicSheetCalculator {
     }
 
     protected maxInstrNameLabelLength(): number {
+        if (!EngravingRules.Rules.RenderInstrumentNames) {
+            return 0;
+        }
         let maxLabelLength: number = 0.0;
         for (const instrument of this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
             if (instrument.Voices.length > 0 && instrument.Voices[0].Visible) {
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                    instrument.NameLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentAndPlacement.LeftCenter);
+                    instrument.NameLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentEnum.LeftCenter);
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 maxLabelLength = Math.max(maxLabelLength, graphicalLabel.PositionAndShape.MarginSize.width);
             }
@@ -1177,25 +1217,33 @@ export abstract class MusicSheetCalculator {
 
     protected calculateSheetLabelBoundingBoxes(): void {
         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;
             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;
             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;
             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;
             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++) {
             this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].Index = i;
         }
@@ -1895,9 +1943,12 @@ export abstract class MusicSheetCalculator {
             // start- and End margins from the text Labels
             const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 startStaffEntry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
                 lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
+
             const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffentry.PositionAndShape.RelativePosition.x +
+                lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
                 nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
             const y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
             let numberOfDashes: number = 1;
@@ -1984,7 +2035,7 @@ export abstract class MusicSheetCalculator {
      * @param {number} y
      */
     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();
         staffLine.LyricsDashes.push(dash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2032,8 +2083,7 @@ export abstract class MusicSheetCalculator {
             // start- and End margins from the text Labels
             const startX: number = startStaffEntry.parentMeasure.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
             const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
                 endStaffEntry.PositionAndShape.RelativePosition.x +
@@ -2099,7 +2149,7 @@ export abstract class MusicSheetCalculator {
      * @returns {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();
         staffLine.LyricsDashes.push(leftDash);
         if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
@@ -2108,7 +2158,7 @@ export abstract class MusicSheetCalculator {
         leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
         const leftDashRelative: PointF2D = new PointF2D(startX, y);
         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();
         staffLine.LyricsDashes.push(rightDash);
         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 {GraphicalLabel} from "./GraphicalLabel";
 import {Label} from "../Label";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {ArgumentOutOfRangeException} from "../Exceptions";
 import {SelectionStartSymbol} from "./SelectionStartSymbol";
 import {SelectionEndSymbol} from "./SelectionEndSymbol";
@@ -24,7 +24,6 @@ import {Instrument} from "../Instrument";
 import {MusicSymbolDrawingStyle, PhonicScoreModes} from "./DrawingMode";
 import {GraphicalObject} from "./GraphicalObject";
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
-import { unitInPixels } from "./VexFlow/VexFlowMusicSheetDrawer";
 
 /**
  * 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.
  */
 export abstract class MusicSheetDrawer {
-    public drawingParameters: DrawingParameters = new DrawingParameters();
+    public drawingParameters: DrawingParameters;
     public splitScreenLineColor: number;
     public midiPlaybackAvailable: boolean;
     public drawableBoundingBoxElement: string = process.env.DRAW_BOUNDING_BOX_ELEMENT;
@@ -53,14 +52,10 @@ export abstract class MusicSheetDrawer {
     private phonicScoreMode: PhonicScoreModes = PhonicScoreModes.Manual;
 
     constructor(textMeasurer: ITextMeasurer,
-                isPreviewImageDrawer: boolean = false) {
+                drawingParameters: DrawingParameters) {
         this.textMeasurer = textMeasurer;
         this.splitScreenLineColor = -1;
-        if (isPreviewImageDrawer) {
-            this.drawingParameters.setForThumbmail();
-        } else {
-            this.drawingParameters.setForAllOn();
-        }
+        this.drawingParameters = drawingParameters;
     }
 
     public set Mode(value: PhonicScoreModes) {
@@ -150,36 +145,36 @@ export abstract class MusicSheetDrawer {
         const bitmapWidth: number = Math.ceil(widthInPixel);
         const bitmapHeight: number = Math.ceil(heightInPixel * 1.2);
         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;
-            case TextAlignmentAndPlacement.LeftCenter:
+            case TextAlignmentEnum.LeftCenter:
                 screenPosition.y -= bitmapHeight / 2;
                 break;
-            case TextAlignmentAndPlacement.LeftBottom:
+            case TextAlignmentEnum.LeftBottom:
                 screenPosition.y -= bitmapHeight;
-                screenPosition.x -= unitInPixels; // lyrics-specific to align with notes
                 break;
-            case TextAlignmentAndPlacement.CenterTop:
+            case TextAlignmentEnum.CenterTop:
                 screenPosition.x -= bitmapWidth / 2;
                 break;
-            case TextAlignmentAndPlacement.CenterCenter:
+            case TextAlignmentEnum.CenterCenter:
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.y -= bitmapHeight / 2;
                 break;
-            case TextAlignmentAndPlacement.CenterBottom:
+            case TextAlignmentEnum.CenterBottom:
                 screenPosition.x -= bitmapWidth / 2;
                 screenPosition.y -= bitmapHeight;
                 break;
-            case TextAlignmentAndPlacement.RightTop:
+            case TextAlignmentEnum.RightTop:
                 screenPosition.x -= bitmapWidth;
                 break;
-            case TextAlignmentAndPlacement.RightCenter:
+            case TextAlignmentEnum.RightCenter:
                 screenPosition.x -= bitmapWidth;
                 screenPosition.y -= bitmapHeight / 2;
                 break;
-            case TextAlignmentAndPlacement.RightBottom:
+            case TextAlignmentEnum.RightBottom:
                 screenPosition.x -= bitmapWidth;
                 screenPosition.y -= bitmapHeight;
                 break;
@@ -510,7 +505,7 @@ export abstract class MusicSheetDrawer {
 
             tmpRect = this.applyScreenTransformationForRect(tmpRect);
             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++;

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

@@ -4,7 +4,7 @@ import {BoundingBox} from "./BoundingBox";
 import {Fraction} from "../../Common/DataObjects/Fraction";
 import {SourceMeasure} from "../VoiceData/SourceMeasure";
 import {InstrumentalGroup} from "../InstrumentalGroup";
-import {TextAlignmentAndPlacement} from "../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../Common/Enums/TextAlignment";
 import {GraphicalMusicPage} from "./GraphicalMusicPage";
 import {GraphicalLabel} from "./GraphicalLabel";
 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) {
                 const instrument: Instrument = instruments[idx];
                 const graphicalLabel: GraphicalLabel = new GraphicalLabel(
-                    instrument.NameLabel, instrumentLabelTextHeight, TextAlignmentAndPlacement.LeftCenter, this.boundingBox
+                    instrument.NameLabel, instrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.boundingBox
                 );
                 graphicalLabel.setLabelPositionAndShapeBorders();
                 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
         this.currentSystemParams.currentSystem = this.initMusicSystem();
         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;
 
         let numberOfMeasures: number = 0;
@@ -329,18 +331,17 @@ export class MusicSystemBuilder {
             musicSystem.StaffLines.push(staffLine);
             const boundingBox: BoundingBox = staffLine.PositionAndShape;
             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;
+                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
             } else {
                 relativePosition.x = 0.0;
+                boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
             }
             relativePosition.y = relativeYPosition;
             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.BorderTop = 0.0;
             boundingBox.BorderBottom = this.rules.StaffHeight;
@@ -488,7 +489,7 @@ export class MusicSystemBuilder {
             measure.addKeyAtBegin(currentKey, previousKey, currentClef);
             keyAdded = true;
         }
-        if (currentRhythm !== undefined) {
+        if (currentRhythm !== undefined && currentRhythm.PrintObject) {
             measure.addRhythmAtBegin(currentRhythm);
             rhythmAdded = true;
         }
@@ -615,7 +616,7 @@ export class MusicSystemBuilder {
         if (keyInstruction !== undefined) {
             measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
         }
-        if (rhythmInstruction !== undefined) {
+        if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
             measure.addRhythmAtBegin(rhythmInstruction);
         }
         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 { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
 import { NoteHead, NoteHeadShape } from "../../VoiceData/NoteHead";
+import { unitInPixels } from "./VexFlowMusicSheetDrawer";
+import { EngravingRules } from "../EngravingRules";
 
 /**
  * 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
                     // if the measure has no modifiers.
                     alignCenter = true;
-                    xShift = -25; // TODO: Either replace by EngravingRules entry or find a way to make it dependent on the modifiers
+                    xShift = EngravingRules.Rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
+                    // affects VexFlowStaffEntry.calculateXPosition()
                 }
                 duration += "r";
                 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 { GraphicalLabel } from "../GraphicalLabel";
 import { Label } from "../../Label";
-import { TextAlignmentAndPlacement } from "../../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum } from "../../../Common/Enums/TextAlignment";
 import { EngravingRules } from "../EngravingRules";
 import { FontStyles } from "../../../Common/Enums/FontStyles";
 import { StaffLine } from "../StaffLine";
@@ -14,9 +14,13 @@ export class VexFlowInstantaneousDynamicExpression extends GraphicalInstantaneou
     constructor(instantaneousDynamicExpression: InstantaneousDynamicExpression, staffLine: StaffLine, measure: GraphicalMeasure) {
         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,
-                                         TextAlignmentAndPlacement.CenterTop,
+                                         labelAlignment,
                                          this.PositionAndShape);
 
         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 {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
 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 {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -595,10 +596,16 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     }
                     if (tupletStaveNotes.length > 1) {
                       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,
                                                           {
+                                                            bracketed: bracketed,
                                                             notes_occupied: notesOccupied,
-                                                            num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
+                                                            num_notes: tupletStaveNotes.length, //, location: -1, ratioed: true
+                                                            ratioed: EngravingRules.Rules.TupletsRatioed,
                                                           }));
                     } else {
                         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;
                 }
-                (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) {
                     const graceNotes: Vex.Flow.GraceNote[] = [];
                     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);
                     (gve as VexFlowVoiceEntry).vfStaveNote.addModifier(0, graceNoteGroup.beamNotes());
@@ -644,6 +658,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
         const voices: Voice[] = this.getVoicesWithinMeasure();
 
         for (const voice of voices) {
+            if (voice === undefined) {
+                continue;
+            }
             const isMainVoice: boolean = !(voice instanceof LinkedVoice);
 
             // 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 { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
 import { Staff } from "../../VoiceData/Staff";
-import { TextAlignmentAndPlacement, TextAlignment } from "../../../Common/Enums/TextAlignment";
+import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
 import { GraphicalSlur } from "../GraphicalSlur";
 
 export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
@@ -118,7 +118,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             }
         }
         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;
         }
         // 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++) {
           const lyricsEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[j];
           // 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;
           // 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 {GraphicalInstantaneousDynamicExpression} from "../GraphicalInstantaneousDynamicExpression";
 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
@@ -37,8 +38,8 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
 
     constructor(element: HTMLElement,
                 backend: VexFlowBackend,
-                isPreviewImageDrawer: boolean = false) {
-        super(new VexFlowTextMeasurer(), isPreviewImageDrawer);
+                drawingParameters: DrawingParameters = new DrawingParameters()) {
+        super(new VexFlowTextMeasurer(), drawingParameters);
         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 {unitInPixels} from "./VexFlowMusicSheetDrawer";
 import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
+import { Note } from "../../VoiceData/Note";
+import { EngravingRules } from "../EngravingRules";
 
 export class VexFlowStaffEntry extends GraphicalStaffEntry {
     constructor(measure: VexFlowMeasure, sourceStaffEntry: SourceStaffEntry, staffEntryParent: VexFlowStaffEntry) {
@@ -27,8 +29,18 @@ export class VexFlowStaffEntry extends GraphicalStaffEntry {
         for (const gve of this.graphicalVoiceEntries as VexFlowVoiceEntry[]) {
             if (gve.vfStaveNote) {
                 gve.vfStaveNote.setStave(stave);
+                if (!gve.vfStaveNote.preFormatted) {
+                    continue;
+                }
                 gve.applyBordersFromVexflow();
                 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) {
                     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 {Fonts} from "../Common/Enums/Fonts";
 import {FontStyles} from "../Common/Enums/FontStyles";
@@ -9,7 +9,7 @@ import {FontStyles} from "../Common/Enums/FontStyles";
  */
 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.textAlignment = alignment;
         this.font = font;
@@ -20,7 +20,7 @@ export class Label {
     public font: Fonts;
     public fontStyle: FontStyles;
     public fontHeight: number;
-    public textAlignment: TextAlignmentAndPlacement;
+    public textAlignment: TextAlignmentEnum;
 
     public ToString(): string {
         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();
       for (const xmlNode of xmlMeasureListArr) {
         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;
           if (this.instrument.Staves.length > 1) {
@@ -259,7 +264,7 @@ export class InstrumentReader {
             xmlNode, noteDuration, restNote,
             this.currentStaffEntry, this.currentMeasure,
             measureStartAbsoluteTimestamp,
-            this.maxTieNoteFraction, isChord, guitarPro
+            this.maxTieNoteFraction, isChord, guitarPro, printObject
           );
 
           const notationsNode: IXmlElement = xmlNode.element("notations");
@@ -713,18 +718,27 @@ export class InstrumentReader {
       this.abstractInstructions.push([1, keyInstruction]);
     }
     if (node.element("time") !== undefined) {
-      let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
       const timeNode: IXmlElement = node.element("time");
+      let symbolEnum: RhythmSymbolEnum = RhythmSymbolEnum.NONE;
+      let timePrintObject: boolean = true;
       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;
-          } else if (firstAttr.value === "cut") {
+          } else if (symbolAttribute.value === "cut") {
             symbolEnum = RhythmSymbolEnum.CUT;
           }
         }
+
+        const printObjectAttribute: IXmlAttribute = timeNode.attribute("print-object");
+        if (printObjectAttribute) {
+          if (printObjectAttribute.value === "no") {
+            timePrintObject = false;
+          }
+        }
       }
+
       let num: number = 0;
       let denom: number = 0;
       const senzaMisura: boolean = (timeNode !== undefined && timeNode.element("senza-misura") !== undefined);
@@ -778,9 +792,11 @@ export class InstrumentReader {
           log.debug("InstrumentReader.addAbstractInstruction", errorMsg, ex);
         }
 
-        this.abstractInstructions.push([1, new RhythmInstruction(
+        const newRhythmInstruction: RhythmInstruction = new RhythmInstruction(
           new Fraction(num, denom, 0, false), symbolEnum
-        )]);
+        );
+        newRhythmInstruction.PrintObject = timePrintObject;
+        this.abstractInstructions.push([1, newRhythmInstruction]);
       } else {
         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);
         } catch (e) {
             log.info("MusicSheetReader.CreateMusicSheet", e);
+            return undefined;
         }
     }
 
@@ -486,21 +487,24 @@ export class MusicSheetReader /*implements IMusicSheetReader*/ {
     private pushSheetLabels(root: IXmlElement, filePath: string): void {
         this.readComposer(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(
                     0, filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")
                 );
                 const filename: string = filePath.substr(barI);
                 const filenameSplits: string[] = filename.split(".", 1);
                 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 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;
         const parts: IXmlElement[] = root.elements("part");
         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 {UnknownExpression} from "../../VoiceData/Expressions/UnknownExpression";
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
-import {TextAlignmentAndPlacement} from "../../../Common/Enums/TextAlignment";
+import {TextAlignmentEnum} from "../../../Common/Enums/TextAlignment";
 import {ITextTranslation} from "../../Interfaces/ITextTranslation";
 import * as log from "loglevel";
+import { EngravingRules } from "../../Graphical/EngravingRules";
 
 export class ExpressionReader {
     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(
-            stringTrimmed, this.placement, TextAlignmentAndPlacement.CenterBottom, this.staffNumber);
+            stringTrimmed, this.placement, textAlignment, this.staffNumber);
         this.getMultiExpression.addExpression(unknownExpression, prefix);
 
         return false;

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

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

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

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

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

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

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

@@ -7,6 +7,8 @@ export abstract class AbstractNotationInstruction {
     }
 
     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 {
         return this.parent;
@@ -15,4 +17,11 @@ export abstract class AbstractNotationInstruction {
         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 playbackInstrumentId: string = 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 {
@@ -104,6 +106,13 @@ export class Note {
     public get NoteHead(): NoteHead {
         return this.noteHead;
     }
+    public get PrintObject(): boolean {
+        return this.printObject;
+    }
+
+    public set PrintObject(value: boolean) {
+        this.printObject = value;
+    }
 
     public isRest(): boolean {
         return this.Pitch === undefined;

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

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

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

@@ -6,13 +6,16 @@ import {Fraction} from "../../Common/DataObjects/Fraction";
  */
 export class Tuplet {
 
-    constructor(tupletLabelNumber: number) {
+    constructor(tupletLabelNumber: number, bracket: boolean = false) {
         this.tupletLabelNumber = tupletLabelNumber;
+        this.bracket = bracket;
     }
 
     private tupletLabelNumber: number;
     private notes: Note[][] = [];
     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 {
         return this.tupletLabelNumber;
@@ -38,6 +41,14 @@ export class Tuplet {
         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],...).
      * @param note

+ 11 - 8
src/OpenSheetMusicDisplay/Cursor.ts

@@ -28,6 +28,7 @@ export class Cursor {
   private hidden: boolean = true;
   private cursorElement: HTMLImageElement;
 
+  /** Initialize the cursor. Necessary before using functions like show() and next(). */
   public init(manager: MusicPartManager, graphic: GraphicalMusicSheet): void {
     this.manager = manager;
     this.reset();
@@ -42,9 +43,12 @@ export class Cursor {
   public show(): void {
     this.hidden = false;
     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 {
@@ -59,12 +63,11 @@ export class Cursor {
     }
     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 =
-      <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;
     const musicSystem: MusicSystem = gse.parentMeasure.parentMusicSystem;
     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 {AJAX} from "./AJAX";
 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 {
     /**
-     * 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
         if (typeof container === "string") {
             // ID passed
@@ -33,22 +46,23 @@ export class 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();
         } else {
             this.backend = new CanvasVexFlowBackend();
         }
 
+        this.setDrawingParameters(options);
+
         this.backend.initialize(this.container);
         this.canvas = this.backend.getCanvas();
-        const inner: HTMLElement = this.backend.getInnerElement();
+        this.innerElement = this.backend.getInnerElement();
+        this.enableOrDisableCursor(this.drawingParameters.drawCursors);
 
         // 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();
         }
     }
@@ -59,9 +73,11 @@ export class OpenSheetMusicDisplay {
     private container: HTMLElement;
     private canvas: HTMLElement;
     private backend: VexFlowBackend;
+    private innerElement: HTMLElement;
     private sheet: MusicSheet;
     private drawer: VexFlowMusicSheetDrawer;
     private graphic: GraphicalMusicSheet;
+    private drawingParameters: DrawingParameters;
 
     /**
      * Load a MusicXML file
@@ -122,9 +138,15 @@ export class OpenSheetMusicDisplay {
         const score: IXmlElement = new IXmlElement(elem);
         const calc: MusicSheetCalculator = new VexFlowMusicSheetCalculator();
         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.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.`);
         return Promise.resolve({});
     }
@@ -147,15 +169,9 @@ export class OpenSheetMusicDisplay {
         this.sheet.pageWidth = width / this.zoom / 10.0;
         // Calculate again
         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
         const height: number = this.graphic.MusicPages[0].PositionAndShape.BorderBottom * 10.0 * this.zoom;
         this.drawer.clear();
@@ -163,8 +179,10 @@ export class OpenSheetMusicDisplay {
         this.drawer.scale(this.zoom);
         // Finally, draw
         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
      */
     private reset(): void {
-        this.cursor.hide();
+        if (this.drawingParameters.drawCursors) {
+            this.cursor.hide();
+        }
         this.sheet = undefined;
         this.graphic = undefined;
         this.zoom = 1.0;
@@ -213,6 +233,7 @@ export class OpenSheetMusicDisplay {
      * Attach the appropriate handler to the window.onResize event
      */
     private autoResize(): void {
+
         const self: OpenSheetMusicDisplay = this;
         this.handleResize(
             () => {
@@ -229,7 +250,9 @@ export class OpenSheetMusicDisplay {
                 //    document.documentElement.offsetWidth
                 //);
                 //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
      */
     private handleResize(startCallback: () => void, endCallback: () => void): void {
+        if (this.graphic === undefined) {
+            return;
+        }
         let rtime: number;
         let timeout: number = undefined;
         const delta: number = 200;
@@ -274,7 +300,78 @@ export class OpenSheetMusicDisplay {
         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
+    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) {
         if (this.drawer) {
             this.drawer.skyLineVisible = value;

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

@@ -48,7 +48,8 @@ describe("XML interface", () => {
             // Load the xml file content
             const score: Document = TestUtils.getScore(scoreName);
             const div: HTMLElement = document.createElement("div");
-            const openSheetMusicDisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+            const openSheetMusicDisplay: OpenSheetMusicDisplay =
+                TestUtils.createOpenSheetMusicDisplay(div);
             openSheetMusicDisplay.load(score);
             done();
         }).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 {TestUtils} from "../../Util/TestUtils";
 
-
 describe("OpenSheetMusicDisplay Main Export", () => {
     let container1: HTMLElement;
 
@@ -24,7 +23,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load MXL from string", (done: MochaDone) => {
         const mxl: string = TestUtils.getMXL("Mozart_Clarinet_Quintet_Excerpt.mxl");
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(mxl).then(
             (_: {}) => {
                 opensheetmusicdisplay.render();
@@ -37,7 +36,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load invalid MXL from string", (done: MochaDone) => {
         const mxl: string = "\x50\x4b\x03\x04";
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(mxl).then(
             (_: {}) => {
                 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 xml: string = new XMLSerializer().serializeToString(score);
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(xml).then(
             (_: {}) => {
                 opensheetmusicdisplay.render();
@@ -69,7 +68,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load XML Document", (done: MochaDone) => {
         const score: Document = TestUtils.getScore("MuzioClementi_SonatinaOpus36No1_Part1.xml");
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(score).then(
             (_: {}) => {
                 opensheetmusicdisplay.render();
@@ -82,7 +81,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("load MXL Document by URL", (done: MochaDone) => {
         const url: string = "base/test/data/Mozart_Clarinet_Quintet_Excerpt.mxl";
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(url).then(
             (_: {}) => {
                 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 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(
             (_: {}) => {
                 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) => {
         const xml: string = "<?xml";
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         opensheetmusicdisplay.load(xml).then(
             (_: {}) => {
                 done(new Error("Corrupted XML appears to be loaded correctly"));
@@ -130,7 +147,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
 
     it("render without loading", (done: MochaDone) => {
         const div: HTMLElement = TestUtils.getDivElement(document);
-        const opensheetmusicdisplay: OpenSheetMusicDisplay = new OpenSheetMusicDisplay(div);
+        const opensheetmusicdisplay: OpenSheetMusicDisplay = TestUtils.createOpenSheetMusicDisplay(div);
         chai.expect(() => {
             return opensheetmusicdisplay.render();
         }).to.throw(/load/);
@@ -139,8 +156,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
 
     before((): void => {
         // Create the container for the "test width" test
-        container1 = document.createElement("div");
-        document.body.appendChild(container1);
+        container1 = TestUtils.getDivElement(document);
     });
     after((): void => {
         // Destroy the container for the "test width" test
@@ -150,7 +166,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("test width 500", (done: MochaDone) => {
         const div: HTMLElement = container1;
         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");
         opensheetmusicdisplay.load(score).then(
             (_: {}) => {
@@ -165,7 +181,7 @@ describe("OpenSheetMusicDisplay Main Export", () => {
     it("test width 200", (done: MochaDone) => {
         const div: HTMLElement = container1;
         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");
         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 {VexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/VexFlowBackend";
 import {CanvasVexFlowBackend} from "../../../../src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend";
+import {DrawingParameters} from "../../../../src/MusicalScore/Graphical/DrawingParameters";
 
 /* tslint:disable:no-unused-expression */
 describe("VexFlow Music Sheet Drawer", () => {
@@ -47,7 +48,7 @@ describe("VexFlow Music Sheet Drawer", () => {
         const canvas: HTMLCanvasElement = document.createElement("canvas");
         const backend: VexFlowBackend = new CanvasVexFlowBackend();
         backend.initialize(canvas);
-        const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer(canvas, backend);
+        const drawer: VexFlowMusicSheetDrawer = new VexFlowMusicSheetDrawer(canvas, backend, new DrawingParameters());
         drawer.drawSheet(gms);
         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.
  * 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/>
         <duration>16</duration>
         <voice>1</voice>
+	<lyric number="1" default-x="11.65" default-y="-80.00">
+          <syllabic>single</syllabic>
+          <text>Tuplets:</text>
+          </lyric>
         </note>
       </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>
+        <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="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 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>
+        <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="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 number="38" width="112.42">
       <note>

+ 3 - 1
tsconfig.json

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

+ 0 - 1
tslint.json

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

+ 1 - 1
webpack.prod.js

@@ -35,6 +35,6 @@ module.exports = merge(common, {
             path: path.resolve(__dirname, 'build'),
             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'
+})