瀏覽代碼

Merge tag '0.6.3'

Release 0.6.3
sschmidTU 6 年之前
父節點
當前提交
5e383f6269

+ 15 - 0
CHANGELOG.md

@@ -1,3 +1,18 @@
+<a name="0.6.3"></a>
+## [0.6.3](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.6.2...0.6.3) (2018-11-08)
+
+
+### Bug Fixes
+
+* **color:** correctly parse XML color with alpha channel. ([0c28816](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/0c28816))
+* **color:** notes are re-colored during render() ([3e3d9b3](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/3e3d9b3)), closes [#440](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/440) [#448](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/448)
+* **color:** set default colors during render(). ([298632f](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/298632f)), closes [#440](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/440)
+
+### Features
+
+* **color:** can set defaultColorLabel, defaultColorTitle ([91a6b1f](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/commit/91a6b1f)), closes [#440](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues/440)
+* **osmd:** Grant public access to osmd and cursor member elements ([#452)](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/pull/452))
+
 <a name="0.6.2"></a>
 <a name="0.6.2"></a>
 ## [0.6.2](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.6.1...0.6.2) (2018-10-25)
 ## [0.6.2](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/compare/0.6.1...0.6.2) (2018-10-25)
 
 

+ 10 - 5
README.md

@@ -6,9 +6,8 @@
 [![Greenkeeper badge](https://badges.greenkeeper.io/opensheetmusicdisplay/opensheetmusicdisplay.svg)](https://greenkeeper.io/)
 [![Greenkeeper badge](https://badges.greenkeeper.io/opensheetmusicdisplay/opensheetmusicdisplay.svg)](https://greenkeeper.io/)
 [![Travis Build Status](https://travis-ci.org/opensheetmusicdisplay/opensheetmusicdisplay.svg?branch=master)](https://travis-ci.org/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Travis Build Status](https://travis-ci.org/opensheetmusicdisplay/opensheetmusicdisplay.svg?branch=master)](https://travis-ci.org/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/r88lnffso55nq1ko?svg=true)](https://ci.appveyor.com/project/sebastianhaas/opensheetmusicdisplay/branch/master)
 [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/r88lnffso55nq1ko?svg=true)](https://ci.appveyor.com/project/sebastianhaas/opensheetmusicdisplay/branch/master)
-[![Dependency Status](https://david-dm.org/opensheetmusicdisplay/opensheetmusicdisplay.png)](https://david-dm.org/opensheetmusicdisplay/opensheetmusicdisplay)
+[![Dependency Status](https://david-dm.org/opensheetmusicdisplay/opensheetmusicdisplay/status.svg)](https://david-dm.org/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/opensheetmusicdisplay/opensheetmusicdisplay?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/opensheetmusicdisplay/opensheetmusicdisplay?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[![Code Climate](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay/badges/gpa.svg)](https://codeclimate.com/github/opensheetmusicdisplay/opensheetmusicdisplay)
 [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
 [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
 
 
 [opensheetmusicdisplay.org](https://opensheetmusicdisplay.org/)
 [opensheetmusicdisplay.org](https://opensheetmusicdisplay.org/)
@@ -21,13 +20,19 @@ OpenSheetMusicDisplay brings the two together and offers an open source turnkey
 
 
 Written in [TypeScript](https://www.typescriptlang.org) and released under [MIT license](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/blob/develop/LICENSE).
 Written in [TypeScript](https://www.typescriptlang.org) and released under [MIT license](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/blob/develop/LICENSE).
 
 
+Try the [Demo](https://opensheetmusicdisplay.github.io/demo/) to see what OSMD can do.
+
 See the [Wiki](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/wiki) for information about the source code and how to use OSMD.
 See the [Wiki](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/wiki) for information about the source code and how to use OSMD.
 
 
-Try the [Demo](https://opensheetmusicdisplay.github.io/demo/) to see what OSMD can do.
+If you have further technical questions, you can browse through our [Issues](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/issues?&q=is%3Aissue) or open a new one.
 
 
-Brought to you by [PhonicScore](https://phonicscore.com/) and [our Github Contributors](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/graphs/contributors)<br>
-(Creators of [PracticeBird for iOS](https://itunes.apple.com/us/app/practice-bird-pro/id1253492926?ls=1&mt=8) and [PhonicScore for Android](https://play.google.com/store/apps/details?id=phonicscore.phonicscore_lite))
+Brought to you by [PhonicScore](https://phonicscore.com/)
+(Creators of [PracticeBird for iOS](https://itunes.apple.com/us/app/practice-bird-pro/id1253492926?ls=1&mt=8) and [PhonicScore for Android](https://play.google.com/store/apps/details?id=phonicscore.phonicscore_lite))<br>
+and [our Github Contributors](https://github.com/opensheetmusicdisplay/opensheetmusicdisplay/graphs/contributors)
 
 
+To contact us directly, you can use the [Contact form on opensheetmusicdisplay.org](https://opensheetmusicdisplay.org/contact/),
+or<br>
+[join the chat on Gitter](https://gitter.im/opensheetmusicdisplay/opensheetmusicdisplay).
 
 
 <!--# <a name="license"></a>License
 <!--# <a name="license"></a>License
 The MIT License (MIT)
 The MIT License (MIT)

+ 1 - 1
demo/index.js

@@ -29,7 +29,7 @@ import { OpenSheetMusicDisplay } from '../src/OpenSheetMusicDisplay/OpenSheetMus
         "OSMD Function Test - Expressions": "OSMD_function_test_expressions.musicxml",
         "OSMD Function Test - Expressions": "OSMD_function_test_expressions.musicxml",
         "OSMD Function Test - Expressions Overlap": "OSMD_function_test_expressions_overlap.musicxml",
         "OSMD Function Test - Expressions Overlap": "OSMD_function_test_expressions_overlap.musicxml",
         "OSMD Function Test - Grace Notes": "OSMD_function_test_GraceNotes.xml",
         "OSMD Function Test - Grace Notes": "OSMD_function_test_GraceNotes.xml",
-        "OSMD Function Test - NoteHeadShapes": "OSMD_function_test_noteHeadShapes.musicxml",
+        "OSMD Function Test - Notehead Shapes": "OSMD_function_test_noteheadShapes.musicxml",
         "OSMD Function Test - Ornaments": "OSMD_function_test_Ornaments.xml",
         "OSMD Function Test - Ornaments": "OSMD_function_test_Ornaments.xml",
         "Schubert, F. - An Die Musik": "Schubert_An_die_Musik.xml",
         "Schubert, F. - An Die Musik": "Schubert_An_die_Musik.xml",
         "Actor, L. - Prelude (Sample)": "ActorPreludeSample.xml",
         "Actor, L. - Prelude (Sample)": "ActorPreludeSample.xml",

+ 4 - 4
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "opensheetmusicdisplay",
   "name": "opensheetmusicdisplay",
-  "version": "0.6.2",
+  "version": "0.6.3",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
   "description": "An open source JavaScript engine for displaying MusicXML based on VexFlow.",
   "main": "build/opensheetmusicdisplay.min.js",
   "main": "build/opensheetmusicdisplay.min.js",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
   "typings": "build/dist/src/OpenSheetMusicDisplay/OpenSheetMusicDisplay",
@@ -52,7 +52,7 @@
     "increase-memory-limit": "^1.0.6",
     "increase-memory-limit": "^1.0.6",
     "jszip": "^3.0.0",
     "jszip": "^3.0.0",
     "loglevel": "^1.5.0",
     "loglevel": "^1.5.0",
-    "shortid": "^2.2.6",
+    "shortid": "^2.2.14",
     "typescript-collections": "^1.1.2",
     "typescript-collections": "^1.1.2",
     "vexflow": "^1.2.87"
     "vexflow": "^1.2.87"
   },
   },
@@ -68,7 +68,7 @@
     "eslint": "^5.0.1",
     "eslint": "^5.0.1",
     "eslint-config-standard": "^12.0.0",
     "eslint-config-standard": "^12.0.0",
     "eslint-plugin-import": "^2.9.0",
     "eslint-plugin-import": "^2.9.0",
-    "eslint-plugin-node": "^7.0.1",
+    "eslint-plugin-node": "^8.0.0",
     "eslint-plugin-promise": "^4.0.1",
     "eslint-plugin-promise": "^4.0.1",
     "eslint-plugin-standard": "^4.0.0",
     "eslint-plugin-standard": "^4.0.0",
     "file-loader": "^2.0.0",
     "file-loader": "^2.0.0",
@@ -95,7 +95,7 @@
     "typescript": "^2.6.1",
     "typescript": "^2.6.1",
     "uglifyjs-webpack-plugin": "^2.0.0",
     "uglifyjs-webpack-plugin": "^2.0.0",
     "underscore-template-loader": "^1.0.0",
     "underscore-template-loader": "^1.0.0",
-    "webpack": "^4.18.1",
+    "webpack": "^4.25.0",
     "webpack-cli": "^3.0.8",
     "webpack-cli": "^3.0.8",
     "webpack-dev-server": "3.1.10",
     "webpack-dev-server": "3.1.10",
     "webpack-merge": "^4.1.2",
     "webpack-merge": "^4.1.2",

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

@@ -236,6 +236,47 @@ export class Pitch {
         }
         }
     }
     }
 
 
+    /**
+     * Converts AccidentalEnum to a string which represents an accidental in VexFlow
+     * Can also be useful in other cases, but has to match Vexflow accidental codes.
+     * @param accidental
+     * @returns {string} Vexflow Accidental code
+     */
+    public static accidentalVexflow(accidental: AccidentalEnum): string {
+        let acc: string;
+        switch (accidental) {
+            case AccidentalEnum.NATURAL:
+                acc = "n";
+                break;
+            case AccidentalEnum.FLAT:
+                acc = "b";
+                break;
+            case AccidentalEnum.SHARP:
+                acc = "#";
+                break;
+            case AccidentalEnum.DOUBLESHARP:
+                acc = "##";
+                break;
+            case AccidentalEnum.TRIPLESHARP:
+                acc = "++";
+                break;
+            case AccidentalEnum.DOUBLEFLAT:
+                acc = "bb";
+                break;
+            case AccidentalEnum.TRIPLEFLAT:
+                acc = "bbs"; // there is no "bbb" in VexFlow yet, unfortunately.
+                break;
+            case AccidentalEnum.QUARTERTONESHARP:
+                acc = "+";
+                break;
+            case AccidentalEnum.QUARTERTONEFLAT:
+                acc = "d";
+                break;
+            default:
+        }
+        return acc;
+    }
+
     public get AccidentalHalfTones(): number {
     public get AccidentalHalfTones(): number {
         return Pitch.HalfTonesFromAccidental(this.accidental);
         return Pitch.HalfTonesFromAccidental(this.accidental);
     }
     }
@@ -298,8 +339,12 @@ export class Pitch {
     }
     }
 
 
     public ToString(): string {
     public ToString(): string {
-        return "Note: " + this.fundamentalNote + ", octave: " + this.octave.toString() + ", alter: " +
-            this.accidental;
+        let accidentalString: string = Pitch.accidentalVexflow(this.accidental);
+        if (!accidentalString) {
+            accidentalString = "";
+        }
+        return "Key: " + Pitch.getNoteEnumString(this.fundamentalNote) + accidentalString +
+        ", Note: " + this.fundamentalNote + ", octave: " + this.octave.toString();
     }
     }
 
 
     public OperatorEquals(p2: Pitch): boolean {
     public OperatorEquals(p2: Pitch): boolean {

+ 17 - 1
src/MusicalScore/Graphical/EngravingRules.ts

@@ -180,6 +180,8 @@ export class EngravingRules {
     private defaultColorNotehead: string;
     private defaultColorNotehead: string;
     private defaultColorRest: string;
     private defaultColorRest: string;
     private defaultColorStem: string;
     private defaultColorStem: string;
+    private defaultColorLabel: string;
+    private defaultColorTitle: string;
     /** Whether to render a label for the composer of the piece at the top of the sheet. */
     /** Whether to render a label for the composer of the piece at the top of the sheet. */
     private renderComposer: boolean;
     private renderComposer: boolean;
     private renderTitle: boolean;
     private renderTitle: boolean;
@@ -386,9 +388,11 @@ export class EngravingRules {
         this.coloringEnabled = true;
         this.coloringEnabled = true;
         this.colorBeams = true;
         this.colorBeams = true;
         this.colorFlags = true;
         this.colorFlags = true;
-        this.defaultColorNotehead = undefined;
+        this.defaultColorNotehead = undefined; // undefined colors mean black
         this.defaultColorRest = undefined;
         this.defaultColorRest = undefined;
         this.defaultColorStem = undefined;
         this.defaultColorStem = undefined;
+        this.defaultColorLabel = undefined;
+        this.defaultColorTitle = undefined;
         this.renderComposer = true;
         this.renderComposer = true;
         this.renderTitle = true;
         this.renderTitle = true;
         this.renderSubtitle = true;
         this.renderSubtitle = true;
@@ -1344,6 +1348,18 @@ export class EngravingRules {
     public set DefaultColorStem(value: string) {
     public set DefaultColorStem(value: string) {
         this.defaultColorStem = value;
         this.defaultColorStem = value;
     }
     }
+    public get DefaultColorLabel(): string {
+        return this.defaultColorLabel;
+    }
+    public set DefaultColorLabel(value: string) {
+        this.defaultColorLabel = value;
+    }
+    public get DefaultColorTitle(): string {
+        return this.defaultColorTitle;
+    }
+    public set DefaultColorTitle(value: string) {
+        this.defaultColorTitle = value;
+    }
     public get RenderComposer(): boolean {
     public get RenderComposer(): boolean {
         return this.renderComposer;
         return this.renderComposer;
     }
     }

+ 1 - 1
src/MusicalScore/Graphical/GraphicalNote.ts

@@ -7,7 +7,7 @@ import {Pitch} from "../../Common/DataObjects/Pitch";
 import {GraphicalObject} from "./GraphicalObject";
 import {GraphicalObject} from "./GraphicalObject";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {MusicSheetCalculator} from "./MusicSheetCalculator";
 import {BoundingBox} from "./BoundingBox";
 import {BoundingBox} from "./BoundingBox";
-import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
+import {GraphicalVoiceEntry} from "./GraphicalVoiceEntry";
 
 
 /**
 /**
  * The graphical counterpart of a [[Note]]
  * The graphical counterpart of a [[Note]]

+ 1 - 1
src/MusicalScore/Graphical/GraphicalStaffEntry.ts

@@ -277,7 +277,7 @@ export abstract class GraphicalStaffEntry extends GraphicalObject {
     public addGraphicalNoteToListAtCorrectYPosition(gve: GraphicalVoiceEntry, graphicalNote: GraphicalNote): void {
     public addGraphicalNoteToListAtCorrectYPosition(gve: GraphicalVoiceEntry, graphicalNote: GraphicalNote): void {
         const graphicalNotes: GraphicalNote[] = gve.notes;
         const graphicalNotes: GraphicalNote[] = gve.notes;
         if (graphicalNotes.length === 0 ||
         if (graphicalNotes.length === 0 ||
-            graphicalNote.PositionAndShape.RelativePosition.y < CollectionUtil.last(graphicalNotes).PositionAndShape.RelativePosition.Y) {
+            graphicalNote.PositionAndShape.RelativePosition.y < CollectionUtil.last(graphicalNotes).PositionAndShape.RelativePosition.y) {
             graphicalNotes.push(graphicalNote);
             graphicalNotes.push(graphicalNote);
         } else {
         } else {
             for (let i: number = graphicalNotes.length - 1; i >= 0; i--) {
             for (let i: number = graphicalNotes.length - 1; i >= 0; i--) {

+ 66 - 1
src/MusicalScore/Graphical/GraphicalVoiceEntry.ts

@@ -1,9 +1,11 @@
-import {GraphicalObject} from "./GraphicalObject";
+import { GraphicalObject } from "./GraphicalObject";
 import { VoiceEntry } from "../VoiceData/VoiceEntry";
 import { VoiceEntry } from "../VoiceData/VoiceEntry";
 import { BoundingBox } from "./BoundingBox";
 import { BoundingBox } from "./BoundingBox";
 import { GraphicalNote } from "./GraphicalNote";
 import { GraphicalNote } from "./GraphicalNote";
 import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
 import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
 import { OctaveEnum } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
 import { OctaveEnum } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
+import { VexFlowVoiceEntry } from "./VexFlow/VexFlowVoiceEntry";
+import { EngravingRules } from "./EngravingRules";
 
 
 /**
 /**
  * The graphical counterpart of a [[VoiceEntry]].
  * The graphical counterpart of a [[VoiceEntry]].
@@ -32,4 +34,67 @@ export class GraphicalVoiceEntry extends GraphicalObject {
             return b.sourceNote.Pitch.getHalfTone() - a.sourceNote.Pitch.getHalfTone();
             return b.sourceNote.Pitch.getHalfTone() - a.sourceNote.Pitch.getHalfTone();
         });
         });
     }
     }
+
+    /** (Re-)color notes and stems by setting their Vexflow styles.
+     * Could be made redundant by a Vexflow PR, but Vexflow needs more solid and permanent color methods/variables for that
+     * See VexFlowConverter.StaveNote()
+     */
+    public color(): void {
+        const defaultColorNotehead: string = EngravingRules.Rules.DefaultColorNotehead;
+        const defaultColorRest: string = EngravingRules.Rules.DefaultColorRest;
+        const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
+
+        const vfStaveNote: any = (<VexFlowVoiceEntry>(this as any)).vfStaveNote;
+        for (let i: number = 0; i < this.notes.length; i++) {
+            const note: GraphicalNote = this.notes[i];
+
+            let noteheadColor: string = note.sourceNote.NoteheadColorXml;
+
+            // DEBUG runtime coloring test
+            /*const testColor: string = "#FF0000";
+            if (i === 2 && Math.random() < 0.1 && note.sourceNote.NoteheadColor !== testColor) {
+                const measureNumber: number = note.parentVoiceEntry.parentStaffEntry.parentMeasure.MeasureNumber;
+                noteheadColor = testColor;
+                console.log("color changed to " + noteheadColor + " of this note:\n" + note.sourceNote.Pitch.ToString() +
+                    ", in measure #" + measureNumber);
+            }*/
+
+            if (!noteheadColor) {
+                if (!note.sourceNote.isRest() && defaultColorNotehead) {
+                    noteheadColor = defaultColorNotehead;
+                } else if (note.sourceNote.isRest() && defaultColorRest) {
+                    noteheadColor = defaultColorRest;
+                }
+            }
+            if (noteheadColor) {
+                note.sourceNote.NoteheadColor = noteheadColor;
+            } else {
+                continue;
+            }
+
+            if (vfStaveNote) {
+                if (vfStaveNote.note_heads) { // see VexFlowConverter, needs Vexflow PR
+                    const notehead: any = vfStaveNote.note_heads[i];
+                    if (notehead) {
+                        vfStaveNote.note_heads[i].setStyle({ fillStyle: noteheadColor, strokeStyle: noteheadColor });
+                    }
+                }
+            }
+        }
+
+        // color stems
+        let stemColor: string = this.parentVoiceEntry.StemColorXml;
+        if (!stemColor && defaultColorStem) {
+            stemColor = defaultColorStem;
+        }
+        const stemStyle: Object = { fillStyle: stemColor, strokeStyle: stemColor };
+
+        if (stemColor && vfStaveNote.setStemStyle) {
+            this.parentVoiceEntry.StemColor = stemColor;
+            vfStaveNote.setStemStyle(stemStyle);
+            if (vfStaveNote.flag && vfStaveNote.setFlagStyle && EngravingRules.Rules.ColorFlags) {
+                vfStaveNote.setFlagStyle(stemStyle);
+            }
+        }
+    }
 }
 }

+ 17 - 1
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -173,7 +173,7 @@ export abstract class MusicSheetCalculator {
     public calculate(): void {
     public calculate(): void {
         this.clearSystemsAndMeasures();
         this.clearSystemsAndMeasures();
 
 
-        // delete graphicalObjects that will be recalculated and create the GraphicalObjects that strech over a single StaffEntry new.
+        // delete graphicalObjects (currently: ties) that will be recalculated, newly create GraphicalObjects streching over a single StaffEntry
         this.clearRecreatedObjects();
         this.clearRecreatedObjects();
 
 
         this.createGraphicalTies();
         this.createGraphicalTies();
@@ -676,8 +676,18 @@ export abstract class MusicSheetCalculator {
             const visiblegraphicalMeasures: GraphicalMeasure[] = [];
             const visiblegraphicalMeasures: GraphicalMeasure[] = [];
             for (let idx2: number = 0, len2: number = graphicalMeasures.length; idx2 < len2; ++idx2) {
             for (let idx2: number = 0, len2: number = graphicalMeasures.length; idx2 < len2; ++idx2) {
                 const graphicalMeasure: GraphicalMeasure = allMeasures[idx][idx2];
                 const graphicalMeasure: GraphicalMeasure = allMeasures[idx][idx2];
+
                 if (graphicalMeasure.isVisible()) {
                 if (graphicalMeasure.isVisible()) {
                     visiblegraphicalMeasures.push(graphicalMeasure);
                     visiblegraphicalMeasures.push(graphicalMeasure);
+
+                    if (EngravingRules.Rules.ColoringEnabled) {
+                        // (re-)color notes
+                        for (const staffEntry of graphicalMeasure.staffEntries) {
+                            for (const gve of staffEntry.graphicalVoiceEntries) {
+                                gve.color();
+                            }
+                        }
+                    }
                 }
                 }
             }
             }
             visibleMeasureList.push(visiblegraphicalMeasures);
             visibleMeasureList.push(visiblegraphicalMeasures);
@@ -689,6 +699,7 @@ export abstract class MusicSheetCalculator {
         for (let idx: number = 0, len: number = visibleMeasureList.length; idx < len; ++idx) {
         for (let idx: number = 0, len: number = visibleMeasureList.length; idx < len; ++idx) {
             const gmlist: GraphicalMeasure[] = visibleMeasureList[idx];
             const gmlist: GraphicalMeasure[] = visibleMeasureList[idx];
             numberOfStaffLines = Math.max(gmlist.length, numberOfStaffLines);
             numberOfStaffLines = Math.max(gmlist.length, numberOfStaffLines);
+
             break;
             break;
         }
         }
         if (numberOfStaffLines === 0) {
         if (numberOfStaffLines === 0) {
@@ -1578,8 +1589,10 @@ export abstract class MusicSheetCalculator {
 
 
     protected calculateSheetLabelBoundingBoxes(): void {
     protected calculateSheetLabelBoundingBoxes(): void {
         const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
         const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
+        const defaultColorTitle: string = EngravingRules.Rules.DefaultColorTitle; // can be undefined => black
         if (musicSheet.Title !== undefined && EngravingRules.Rules.RenderTitle) {
         if (musicSheet.Title !== undefined && EngravingRules.Rules.RenderTitle) {
             const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom);
             const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom);
+            title.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Title = title;
             this.graphicalMusicSheet.Title = title;
             title.setLabelPositionAndShapeBorders();
             title.setLabelPositionAndShapeBorders();
         } else if (!EngravingRules.Rules.RenderTitle) {
         } else if (!EngravingRules.Rules.RenderTitle) {
@@ -1587,6 +1600,7 @@ export abstract class MusicSheetCalculator {
         }
         }
         if (musicSheet.Subtitle !== undefined && EngravingRules.Rules.RenderSubtitle) {
         if (musicSheet.Subtitle !== undefined && EngravingRules.Rules.RenderSubtitle) {
             const subtitle: GraphicalLabel = new GraphicalLabel(musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter);
             const subtitle: GraphicalLabel = new GraphicalLabel(musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter);
+            subtitle.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Subtitle = subtitle;
             this.graphicalMusicSheet.Subtitle = subtitle;
             subtitle.setLabelPositionAndShapeBorders();
             subtitle.setLabelPositionAndShapeBorders();
         } else if (!EngravingRules.Rules.RenderSubtitle) {
         } else if (!EngravingRules.Rules.RenderSubtitle) {
@@ -1594,6 +1608,7 @@ export abstract class MusicSheetCalculator {
         }
         }
         if (musicSheet.Composer !== undefined && EngravingRules.Rules.RenderComposer) {
         if (musicSheet.Composer !== undefined && EngravingRules.Rules.RenderComposer) {
             const composer: GraphicalLabel = new GraphicalLabel(musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter);
             const composer: GraphicalLabel = new GraphicalLabel(musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter);
+            composer.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Composer = composer;
             this.graphicalMusicSheet.Composer = composer;
             composer.setLabelPositionAndShapeBorders();
             composer.setLabelPositionAndShapeBorders();
         } else if (!EngravingRules.Rules.RenderComposer) {
         } else if (!EngravingRules.Rules.RenderComposer) {
@@ -1601,6 +1616,7 @@ export abstract class MusicSheetCalculator {
         }
         }
         if (musicSheet.Lyricist !== undefined && EngravingRules.Rules.RenderLyricist) {
         if (musicSheet.Lyricist !== undefined && EngravingRules.Rules.RenderLyricist) {
             const lyricist: GraphicalLabel = new GraphicalLabel(musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter);
             const lyricist: GraphicalLabel = new GraphicalLabel(musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter);
+            lyricist.Label.colorDefault = defaultColorTitle;
             this.graphicalMusicSheet.Lyricist = lyricist;
             this.graphicalMusicSheet.Lyricist = lyricist;
             lyricist.setLabelPositionAndShapeBorders();
             lyricist.setLabelPositionAndShapeBorders();
         } else if (!EngravingRules.Rules.RenderLyricist) {
         } else if (!EngravingRules.Rules.RenderLyricist) {

+ 5 - 1
src/MusicalScore/Graphical/VexFlow/CanvasVexFlowBackend.ts

@@ -55,14 +55,18 @@ export class CanvasVexFlowBackend extends VexFlowBackend {
         this.canvasRenderingCtx.translate(x, y);
         this.canvasRenderingCtx.translate(x, y);
     }
     }
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
-                      heightInPixel: number, screenPosition: PointF2D): void  {
+                      heightInPixel: number, screenPosition: PointF2D, color: string = undefined): void  {
         const old: string = this.canvasRenderingCtx.font;
         const old: string = this.canvasRenderingCtx.font;
+        this.canvasRenderingCtx.save();
         this.canvasRenderingCtx.font = VexFlowConverter.font(
         this.canvasRenderingCtx.font = VexFlowConverter.font(
             fontHeight,
             fontHeight,
             fontStyle,
             fontStyle,
             font
             font
         );
         );
+        this.canvasRenderingCtx.fillStyle = color;
+        this.canvasRenderingCtx.strokeStyle = color;
         this.canvasRenderingCtx.fillText(text, screenPosition.x, screenPosition.y + heightInPixel);
         this.canvasRenderingCtx.fillText(text, screenPosition.x, screenPosition.y + heightInPixel);
+        this.canvasRenderingCtx.restore();
         this.canvasRenderingCtx.font = old;
         this.canvasRenderingCtx.font = old;
     }
     }
     public renderRectangle(rectangle: RectangleF2D, styleId: number, alpha: number = 1): void {
     public renderRectangle(rectangle: RectangleF2D, styleId: number, alpha: number = 1): void {

+ 5 - 1
src/MusicalScore/Graphical/VexFlow/SvgVexFlowBackend.ts

@@ -50,9 +50,13 @@ export class SvgVexFlowBackend extends VexFlowBackend {
         // TODO: implement this
         // TODO: implement this
     }
     }
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
     public renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
-                      heightInPixel: number, screenPosition: PointF2D): void {
+                      heightInPixel: number, screenPosition: PointF2D, color: string = undefined): void {
         this.ctx.save();
         this.ctx.save();
 
 
+        if (color) {
+            this.ctx.attributes.fill = color;
+            this.ctx.attributes.stroke = color;
+        }
         this.ctx.setFont("Times New Roman", fontHeight, VexFlowConverter.fontStyle(fontStyle));
         this.ctx.setFont("Times New Roman", fontHeight, VexFlowConverter.fontStyle(fontStyle));
         // font size is set by VexFlow in `pt`. This overwrites the font so it's set to px instead
         // font size is set by VexFlow in `pt`. This overwrites the font so it's set to px instead
         this.ctx.attributes["font-size"] = `${fontHeight}px`;
         this.ctx.attributes["font-size"] = `${fontHeight}px`;

+ 1 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowBackend.ts

@@ -43,7 +43,7 @@ export abstract class VexFlowBackend {
 
 
   public abstract translate(x: number, y: number): void;
   public abstract translate(x: number, y: number): void;
   public abstract renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
   public abstract renderText(fontHeight: number, fontStyle: FontStyles, font: Fonts, text: string,
-                             heightInPixel: number, screenPosition: PointF2D): void;
+                             heightInPixel: number, screenPosition: PointF2D, color?: string): void;
   /**
   /**
    * Renders a rectangle with the given style to the screen.
    * Renders a rectangle with the given style to the screen.
    * It is given in screen coordinates.
    * It is given in screen coordinates.

+ 13 - 83
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -20,7 +20,7 @@ import { ArticulationEnum, StemDirectionType } from "../../VoiceData/VoiceEntry"
 import { SystemLinePosition } from "../SystemLinePosition";
 import { SystemLinePosition } from "../SystemLinePosition";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
 import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
-import { NoteHead, NoteHeadShape } from "../../VoiceData/NoteHead";
+import { Notehead, NoteHeadShape } from "../../VoiceData/Notehead";
 import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 import { unitInPixels } from "./VexFlowMusicSheetDrawer";
 import { EngravingRules } from "../EngravingRules";
 import { EngravingRules } from "../EngravingRules";
 
 
@@ -108,22 +108,22 @@ export class VexFlowConverter {
      */
      */
     public static pitch(note: VexFlowGraphicalNote, pitch: Pitch): [string, string, ClefInstruction] {
     public static pitch(note: VexFlowGraphicalNote, pitch: Pitch): [string, string, ClefInstruction] {
         const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
         const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
-        const acc: string = VexFlowConverter.accidental(pitch.Accidental);
+        const acc: string = Pitch.accidentalVexflow(pitch.Accidental);
         // The octave seems to need a shift of three FIXME?
         // The octave seems to need a shift of three FIXME?
         const octave: number = pitch.Octave - note.Clef().OctaveOffset + 3;
         const octave: number = pitch.Octave - note.Clef().OctaveOffset + 3;
-        const noteHead: NoteHead = note.sourceNote.NoteHead;
-        let noteHeadCode: string = "";
-        if (noteHead !== undefined) {
-            noteHeadCode = this.NoteHeadCode(noteHead);
+        const notehead: Notehead = note.sourceNote.NoteHead;
+        let noteheadCode: string = "";
+        if (notehead !== undefined) {
+            noteheadCode = this.NoteHeadCode(notehead);
         }
         }
-        return [fund + "n/" + octave + noteHeadCode, acc, note.Clef()];
+        return [fund + "n/" + octave + noteheadCode, acc, note.Clef()];
     }
     }
 
 
     /** returns the Vexflow code for a note head. Some are still unsupported, see Vexflow/tables.js */
     /** returns the Vexflow code for a note head. Some are still unsupported, see Vexflow/tables.js */
-    public static NoteHeadCode(noteHead: NoteHead): string {
+    public static NoteHeadCode(notehead: Notehead): string {
         const codeStart: string = "/";
         const codeStart: string = "/";
-        const codeFilled: string = noteHead.Filled ? "2" : "1"; // filled/unfilled notehead code in most vexflow glyphs
-        switch (noteHead.Shape) {
+        const codeFilled: string = notehead.Filled ? "2" : "1"; // filled/unfilled notehead code in most vexflow glyphs
+        switch (notehead.Shape) {
             case NoteHeadShape.NORMAL:
             case NoteHeadShape.NORMAL:
                 return "";
                 return "";
             case NoteHeadShape.DIAMOND:
             case NoteHeadShape.DIAMOND:
@@ -145,46 +145,6 @@ export class VexFlowConverter {
         }
         }
     }
     }
 
 
-    /**
-     * Converts AccidentalEnum to a string which represents an accidental in VexFlow
-     * @param accidental
-     * @returns {string}
-     */
-    public static accidental(accidental: AccidentalEnum): string {
-        let acc: string;
-        switch (accidental) {
-            case AccidentalEnum.NATURAL:
-                acc = "n";
-                break;
-            case AccidentalEnum.FLAT:
-                acc = "b";
-                break;
-            case AccidentalEnum.SHARP:
-                acc = "#";
-                break;
-            case AccidentalEnum.DOUBLESHARP:
-                acc = "##";
-                break;
-            case AccidentalEnum.TRIPLESHARP:
-                acc = "++";
-                break;
-            case AccidentalEnum.DOUBLEFLAT:
-                acc = "bb";
-                break;
-            case AccidentalEnum.TRIPLEFLAT:
-                acc = "bbs"; // there is no "bbb" in VexFlow yet, unfortunately.
-                break;
-            case AccidentalEnum.QUARTERTONESHARP:
-                acc = "+";
-                break;
-            case AccidentalEnum.QUARTERTONEFLAT:
-                acc = "d";
-                break;
-            default:
-        }
-        return acc;
-    }
-
     public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
     public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
         return new Vex.Flow.GhostNote({
         return new Vex.Flow.GhostNote({
             duration: VexFlowConverter.duration(frac, false),
             duration: VexFlowConverter.duration(frac, false),
@@ -216,31 +176,11 @@ export class VexFlowConverter {
         let alignCenter: boolean = false;
         let alignCenter: boolean = false;
         let xShift: number = 0;
         let xShift: number = 0;
         let slashNoteHead: boolean = false;
         let slashNoteHead: boolean = false;
-        const noteheadStyles: any = [];
         for (const note of notes) {
         for (const note of notes) {
             if (numDots < note.numberOfDots) {
             if (numDots < note.numberOfDots) {
                 numDots = note.numberOfDots;
                 numDots = note.numberOfDots;
             }
             }
 
 
-            if (EngravingRules.Rules.ColoringEnabled) {
-                let noteheadColor: string = note.sourceNote.NoteheadColorXml;
-                const defaultColorNotehead: string = EngravingRules.Rules.DefaultColorNotehead;
-                const defaultColorRest: string = EngravingRules.Rules.DefaultColorRest;
-                if (!noteheadColor) {
-                    if (!note.sourceNote.isRest() && defaultColorNotehead) {
-                        noteheadColor = defaultColorNotehead;
-                    } else if (note.sourceNote.isRest() && defaultColorRest) {
-                        noteheadColor = defaultColorRest;
-                    }
-                }
-                if (noteheadColor) {
-                    noteheadStyles.push({ fillStyle: noteheadColor, strokeStyle: noteheadColor });
-                    note.sourceNote.NoteheadColor = noteheadColor;
-                } else {
-                    noteheadStyles.push(undefined);
-                }
-            }
-
             // if it is a rest:
             // if it is a rest:
             if (note.sourceNote.isRest()) {
             if (note.sourceNote.isRest()) {
                 keys = ["b/4"];
                 keys = ["b/4"];
@@ -292,7 +232,6 @@ export class VexFlowConverter {
             clef: vfClefType,
             clef: vfClefType,
             duration: duration,
             duration: duration,
             keys: keys,
             keys: keys,
-            noteheadStyles: noteheadStyles,
             slash: gve.parentVoiceEntry.GraceNoteSlash,
             slash: gve.parentVoiceEntry.GraceNoteSlash,
         };
         };
 
 
@@ -307,7 +246,7 @@ export class VexFlowConverter {
             vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
             vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
         }
         }
 
 
-        if (EngravingRules.Rules.ColoringEnabled) { // this method requires a Vexflow PR
+        if (EngravingRules.Rules.ColoringEnabled) {
             const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
             const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
             let stemColor: string = gve.parentVoiceEntry.StemColorXml;
             let stemColor: string = gve.parentVoiceEntry.StemColorXml;
             if (!stemColor && defaultColorStem) {
             if (!stemColor && defaultColorStem) {
@@ -322,15 +261,6 @@ export class VexFlowConverter {
                     vfnote.setFlagStyle(stemStyle);
                     vfnote.setFlagStyle(stemStyle);
                 }
                 }
             }
             }
-
-            // color noteheads (again)
-            // TODO temporary fix until Vexflow PR is through (should be set by vfnotestruct.noteheadStyles)
-            for (let i: number = 0; i < noteheadStyles.length; i++) {
-                const style: string = noteheadStyles[i];
-                if (style !== undefined) {
-                    vfnote.note_heads[i].setStyle(style);
-                }
-            }
         }
         }
 
 
         vfnote.x_shift = xShift;
         vfnote.x_shift = xShift;
@@ -497,10 +427,10 @@ export class VexFlowConverter {
         }
         }
         if (vfOrna !== undefined) {
         if (vfOrna !== undefined) {
             if (oContainer.AccidentalBelow !== AccidentalEnum.NONE) {
             if (oContainer.AccidentalBelow !== AccidentalEnum.NONE) {
-                vfOrna.setLowerAccidental(this.accidental(oContainer.AccidentalBelow));
+                vfOrna.setLowerAccidental(Pitch.accidentalVexflow(oContainer.AccidentalBelow));
             }
             }
             if (oContainer.AccidentalAbove !== AccidentalEnum.NONE) {
             if (oContainer.AccidentalAbove !== AccidentalEnum.NONE) {
-                vfOrna.setUpperAccidental(this.accidental(oContainer.AccidentalAbove));
+                vfOrna.setUpperAccidental(Pitch.accidentalVexflow(oContainer.AccidentalAbove));
             }
             }
             vfOrna.setPosition(vfPosition);
             vfOrna.setPosition(vfPosition);
             vfnote.addModifier(0, vfOrna);
             vfnote.addModifier(0, vfOrna);

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

@@ -40,7 +40,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
      */
      */
     public setPitch(pitch: Pitch): void {
     public setPitch(pitch: Pitch): void {
         if (this.vfnote) {
         if (this.vfnote) {
-            const acc: string = VexFlowConverter.accidental(pitch.Accidental);
+            const acc: string = Pitch.accidentalVexflow(pitch.Accidental);
             if (acc) {
             if (acc) {
                 alert(acc);
                 alert(acc);
                 this.vfnote[0].addAccidental(this.vfnote[1], new Vex.Flow.Accidental(acc));
                 this.vfnote[0].addAccidental(this.vfnote[1], new Vex.Flow.Accidental(acc));

+ 8 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetDrawer.ts

@@ -341,7 +341,14 @@ export class VexFlowMusicSheetDrawer extends MusicSheetDrawer {
                           bitmapHeight: number, heightInPixel: number, screenPosition: PointF2D): void {
                           bitmapHeight: number, heightInPixel: number, screenPosition: PointF2D): void {
         const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
         const height: number = graphicalLabel.Label.fontHeight * unitInPixels;
         const { fontStyle, font, text } = graphicalLabel.Label;
         const { fontStyle, font, text } = graphicalLabel.Label;
-        this.backend.renderText(height, fontStyle, font, text, heightInPixel, screenPosition);
+        let color: string;
+        if (EngravingRules.Rules.ColoringEnabled) {
+            color = graphicalLabel.Label.colorDefault;
+            if (!color) {
+                color = EngravingRules.Rules.DefaultColorLabel;
+            }
+        }
+        this.backend.renderText(height, fontStyle, font, text, heightInPixel, screenPosition, color);
     }
     }
 
 
     /**
     /**

+ 1 - 0
src/MusicalScore/Label.ts

@@ -17,6 +17,7 @@ export class Label {
 
 
     public text: string;
     public text: string;
     public color: OSMDColor;
     public color: OSMDColor;
+    public colorDefault: string; // TODO this is Vexflow format, convert to OSMDColor. for now convenient for default colors.
     public font: Fonts;
     public font: Fonts;
     public fontStyle: FontStyles;
     public fontStyle: FontStyles;
     public fontHeight: number;
     public fontHeight: number;

+ 20 - 18
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -253,12 +253,7 @@ export class InstrumentReader {
 
 
             const stemColorAttr: Attr = stemNode.attribute("color");
             const stemColorAttr: Attr = stemNode.attribute("color");
             if (stemColorAttr) { // can be null, maybe also undefined
             if (stemColorAttr) { // can be null, maybe also undefined
-              const stemColorValue: string = stemColorAttr.value;
-              if (stemColorValue.length === 7) { // #RGB hexa
-                stemColorXml = stemColorValue;
-              } else if (stemColorValue.length === 9) { // #ARGB hexa, first part alpha channel
-                stemColorXml = stemColorValue.substr(2);
-              }
+              stemColorXml = this.parseXmlColor(stemColorAttr.value);
             }
             }
           }
           }
 
 
@@ -268,24 +263,14 @@ export class InstrumentReader {
           if (noteheadNode) {
           if (noteheadNode) {
             const colorAttr: Attr = noteheadNode.attribute("color");
             const colorAttr: Attr = noteheadNode.attribute("color");
             if (colorAttr) {
             if (colorAttr) {
-              const colorValue: string = colorAttr.value;
-              if (colorValue.length === 7) { // #RGB hexa
-                noteheadColorXml = colorValue;
-              } else if (colorValue.length === 9) { // #ARGB hexa, first part alpha channel
-                noteheadColorXml = colorValue.substr(2);
-              }
+              noteheadColorXml = this.parseXmlColor(colorAttr.value);
             }
             }
           }
           }
 
 
           let noteColorXml: string;
           let noteColorXml: string;
           const noteColorAttr: Attr = xmlNode.attribute("color");
           const noteColorAttr: Attr = xmlNode.attribute("color");
           if (noteColorAttr) { // can be undefined
           if (noteColorAttr) { // can be undefined
-            const noteColorValue: string = noteColorAttr.value;
-            if (noteColorValue.length === 7) { // #RGB hexa
-              noteColorXml = noteColorValue;
-            } else if (noteColorValue.length === 9) { // #ARGB hexa, first part alpha channel
-              noteColorXml = noteColorValue.substr(2);
-            }
+            noteColorXml = this.parseXmlColor(noteColorAttr.value);
             if (noteheadColorXml === undefined) {
             if (noteheadColorXml === undefined) {
               noteheadColorXml = noteColorXml;
               noteheadColorXml = noteColorXml;
             }
             }
@@ -500,6 +485,23 @@ export class InstrumentReader {
     return true;
     return true;
   }
   }
 
 
+  /** Parse a color in XML format. Can be #ARGB or #RGB format, colors as byte hex values.
+   *  @return color in Vexflow format #[A]RGB or undefined for invalid xmlColorString
+   */
+  public parseXmlColor(xmlColorString: string): string {
+    if (!xmlColorString) {
+      return undefined;
+    }
+
+    if (xmlColorString.length === 7) { // #RGB
+      return xmlColorString;
+    } else if (xmlColorString.length === 9) { // #ARGB
+      return "#" + xmlColorString.substr(3); // cut away alpha channel
+    } else {
+      return undefined; // invalid xml color
+    }
+  }
+
   public doCalculationsAfterDurationHasBeenSet(): void {
   public doCalculationsAfterDurationHasBeenSet(): void {
     for (const j in this.voiceGeneratorsDict) {
     for (const j in this.voiceGeneratorsDict) {
       if (this.voiceGeneratorsDict.hasOwnProperty(j)) {
       if (this.voiceGeneratorsDict.hasOwnProperty(j)) {

+ 9 - 9
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -25,7 +25,7 @@ import { IXmlAttribute } from "../../Common/FileIO/Xml";
 import { CollectionUtil } from "../../Util/CollectionUtil";
 import { CollectionUtil } from "../../Util/CollectionUtil";
 import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader";
 import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader";
 import { SlurReader } from "./MusicSymbolModules/SlurReader";
 import { SlurReader } from "./MusicSymbolModules/SlurReader";
-import { NoteHead } from "../VoiceData/NoteHead";
+import { Notehead } from "../VoiceData/Notehead";
 import { Arpeggio, ArpeggioType } from "../VoiceData/Arpeggio";
 import { Arpeggio, ArpeggioType } from "../VoiceData/Arpeggio";
 
 
 export class VoiceGenerator {
 export class VoiceGenerator {
@@ -331,8 +331,8 @@ export class VoiceGenerator {
     let noteStep: NoteEnum = NoteEnum.C;
     let noteStep: NoteEnum = NoteEnum.C;
     let noteOctave: number = 0;
     let noteOctave: number = 0;
     let playbackInstrumentId: string = undefined;
     let playbackInstrumentId: string = undefined;
-    let noteHeadShapeXml: string = undefined;
-    let noteHeadFilledXml: boolean = undefined; // if undefined, the final filled parameter will be calculated from duration
+    let noteheadShapeXml: string = undefined;
+    let noteheadFilledXml: boolean = undefined; // if undefined, the final filled parameter will be calculated from duration
 
 
     const xmlnodeElementsArr: IXmlElement[] = node.elements();
     const xmlnodeElementsArr: IXmlElement[] = node.elements();
     for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
     for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
@@ -342,8 +342,8 @@ export class VoiceGenerator {
           const noteElementsArr: IXmlElement[] = noteElement.elements();
           const noteElementsArr: IXmlElement[] = noteElement.elements();
           for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
           for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
             const pitchElement: IXmlElement = noteElementsArr[idx2];
             const pitchElement: IXmlElement = noteElementsArr[idx2];
-            noteHeadShapeXml = undefined; // reinitialize for each pitch
-            noteHeadFilledXml = undefined;
+            noteheadShapeXml = undefined; // reinitialize for each pitch
+            noteheadFilledXml = undefined;
             try {
             try {
               if (pitchElement.name === "step") {
               if (pitchElement.name === "step") {
                 noteStep = NoteEnum[pitchElement.value];
                 noteStep = NoteEnum[pitchElement.value];
@@ -402,9 +402,9 @@ export class VoiceGenerator {
             playbackInstrumentId = noteElement.firstAttribute.value;
             playbackInstrumentId = noteElement.firstAttribute.value;
           }
           }
         } else if (noteElement.name === "notehead") {
         } else if (noteElement.name === "notehead") {
-          noteHeadShapeXml = noteElement.value;
+          noteheadShapeXml = noteElement.value;
           if (noteElement.attribute("filled") !== null) {
           if (noteElement.attribute("filled") !== null) {
-            noteHeadFilledXml = noteElement.attribute("filled").value === "yes";
+            noteheadFilledXml = noteElement.attribute("filled").value === "yes";
           }
           }
         }
         }
       } catch (ex) {
       } catch (ex) {
@@ -421,8 +421,8 @@ export class VoiceGenerator {
     note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
     note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
     note.NoteheadColorXml = noteheadColorXml;
     note.NoteheadColorXml = noteheadColorXml;
     note.PlaybackInstrumentId = playbackInstrumentId;
     note.PlaybackInstrumentId = playbackInstrumentId;
-    if (noteHeadShapeXml !== undefined && noteHeadShapeXml !== "normal") {
-      note.NoteHead = new NoteHead(note, noteHeadShapeXml, noteHeadFilledXml);
+    if (noteheadShapeXml !== undefined && noteheadShapeXml !== "normal") {
+      note.NoteHead = new Notehead(note, noteheadShapeXml, noteheadFilledXml);
     } // if normal, leave note head undefined to save processing/runtime
     } // if normal, leave note head undefined to save processing/runtime
     this.currentVoiceEntry.Notes.push(note);
     this.currentVoiceEntry.Notes.push(note);
     this.currentVoiceEntry.StemDirectionXml = stemDirectionXml;
     this.currentVoiceEntry.StemDirectionXml = stemDirectionXml;

+ 6 - 6
src/MusicalScore/VoiceData/Note.ts

@@ -8,7 +8,7 @@ import {Tie} from "./Tie";
 import {Staff} from "./Staff";
 import {Staff} from "./Staff";
 import {Slur} from "./Expressions/ContinuousExpressions/Slur";
 import {Slur} from "./Expressions/ContinuousExpressions/Slur";
 import {NoteState} from "../Graphical/DrawingEnums";
 import {NoteState} from "../Graphical/DrawingEnums";
-import {NoteHead} from "./NoteHead";
+import {Notehead} from "./Notehead";
 import {Arpeggio} from "./Arpeggio";
 import {Arpeggio} from "./Arpeggio";
 
 
 /**
 /**
@@ -45,7 +45,7 @@ export class Note {
     private tie: Tie;
     private tie: Tie;
     private slurs: Slur[] = [];
     private slurs: Slur[] = [];
     private playbackInstrumentId: string = undefined;
     private playbackInstrumentId: string = undefined;
-    private noteHead: NoteHead = undefined;
+    private notehead: Notehead = undefined;
     /** States whether the note should be displayed. False if xmlNode.attribute("print-object").value = "no". */
     /** States whether the note should be displayed. False if xmlNode.attribute("print-object").value = "no". */
     private printObject: boolean = true;
     private printObject: boolean = true;
     /** The Arpeggio this note is part of. */
     /** The Arpeggio this note is part of. */
@@ -112,11 +112,11 @@ export class Note {
     public set PlaybackInstrumentId(value: string) {
     public set PlaybackInstrumentId(value: string) {
         this.playbackInstrumentId = value;
         this.playbackInstrumentId = value;
     }
     }
-    public set NoteHead(value: NoteHead) {
-        this.noteHead = value;
+    public set NoteHead(value: Notehead) {
+        this.notehead = value;
     }
     }
-    public get NoteHead(): NoteHead {
-        return this.noteHead;
+    public get NoteHead(): Notehead {
+        return this.notehead;
     }
     }
     public get PrintObject(): boolean {
     public get PrintObject(): boolean {
         return this.printObject;
         return this.printObject;

+ 3 - 3
src/MusicalScore/VoiceData/NoteHead.ts → src/MusicalScore/VoiceData/Notehead.ts

@@ -4,7 +4,7 @@ import * as log from "loglevel";
 /**
 /**
  * A note head with shape and fill information belonging to a [[Note]].
  * A note head with shape and fill information belonging to a [[Note]].
  */
  */
-export class NoteHead {
+export class Notehead {
     /**
     /**
      * @param sourceNote
      * @param sourceNote
      * @param shapeTypeXml The shape type given from XML.
      * @param shapeTypeXml The shape type given from XML.
@@ -30,13 +30,13 @@ export class NoteHead {
      *                           If undefined, this.sourceNote should not be undefined.
      *                           If undefined, this.sourceNote should not be undefined.
      */
      */
     public setShapeFromXml(shapeTypeXml: string, filledXmlAttribute: boolean = undefined): void {
     public setShapeFromXml(shapeTypeXml: string, filledXmlAttribute: boolean = undefined): void {
-        this.shape = NoteHead.ShapeTypeXmlToShape(shapeTypeXml);
+        this.shape = Notehead.ShapeTypeXmlToShape(shapeTypeXml);
 
 
         let filled: boolean = filledXmlAttribute;
         let filled: boolean = filledXmlAttribute;
         if (filled === undefined) {
         if (filled === undefined) {
             if (this.sourceNote === undefined) {
             if (this.sourceNote === undefined) {
                 // this should not happen. Either filledXmlAttribute or sourceNote should be defined.
                 // this should not happen. Either filledXmlAttribute or sourceNote should be defined.
-                log.warn("noteHead: sourceNote and filledXmlAttribute undefined.");
+                log.warn("notehead: sourceNote and filledXmlAttribute undefined.");
                 filled = true;
                 filled = true;
             } else {
             } else {
                 filled = this.sourceNote.Length.Denominator > 2;
                 filled = this.sourceNote.Length.Denominator > 2;

+ 9 - 1
src/OpenSheetMusicDisplay/Cursor.ts

@@ -23,7 +23,7 @@ export class Cursor {
   private container: HTMLElement;
   private container: HTMLElement;
   private openSheetMusicDisplay: OpenSheetMusicDisplay;
   private openSheetMusicDisplay: OpenSheetMusicDisplay;
   private manager: MusicPartManager;
   private manager: MusicPartManager;
-  private iterator: MusicPartManagerIterator;
+  protected iterator: MusicPartManagerIterator;
   private graphic: GraphicalMusicSheet;
   private graphic: GraphicalMusicSheet;
   private hidden: boolean = true;
   private hidden: boolean = true;
   private cursorElement: HTMLImageElement;
   private cursorElement: HTMLImageElement;
@@ -159,4 +159,12 @@ export class Cursor {
     // Set the actual image
     // Set the actual image
     this.cursorElement.src = c.toDataURL("image/png");
     this.cursorElement.src = c.toDataURL("image/png");
   }
   }
+
+  public get Iterator(): MusicPartManagerIterator {
+    return this.iterator;
+  }
+
+  public get Hidden(): boolean {
+    return this.hidden;
+  }
 }
 }

+ 4 - 2
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -8,8 +8,6 @@ export interface IOSMDOptions {
     autoBeamOptions?: AutoBeamOptions;
     autoBeamOptions?: AutoBeamOptions;
     /** Automatically resize score with canvas size. Default is true. */
     /** Automatically resize score with canvas size. Default is true. */
     autoResize?: boolean;
     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. */
     /** Render Backend, will be SVG if given undefined, SVG or svg, otherwise Canvas. */
     backend?: string;
     backend?: string;
     /** Whether to enable coloring noteheads and stems by their XML color attribute. */
     /** Whether to enable coloring noteheads and stems by their XML color attribute. */
@@ -20,6 +18,10 @@ export interface IOSMDOptions {
     defaultColorStem?: string;
     defaultColorStem?: string;
     /** Default color for rests. Default black (undefined). */
     /** Default color for rests. Default black (undefined). */
     defaultColorRest?: string;
     defaultColorRest?: string;
+    /** Default color for Labels like title or lyrics. Default black (undefined). */
+    defaultColorLabel?: string;
+    /** Default color for labels in the title. Overrides defaultColorLabel for title labels like composer. Default black (undefined). */
+    defaultColorTitle?: string;
     /** Don't show/load cursor. Will override disableCursor in drawingParameters. */
     /** Don't show/load cursor. Will override disableCursor in drawingParameters. */
     disableCursor?: boolean;
     disableCursor?: boolean;
     /** Broad Parameters like compact or preview mode. */
     /** Broad Parameters like compact or preview mode. */

+ 19 - 0
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -288,6 +288,12 @@ export class OpenSheetMusicDisplay {
         if (options.defaultColorStem) {
         if (options.defaultColorStem) {
             EngravingRules.Rules.DefaultColorStem = options.defaultColorStem;
             EngravingRules.Rules.DefaultColorStem = options.defaultColorStem;
         }
         }
+        if (options.defaultColorLabel) {
+            EngravingRules.Rules.DefaultColorLabel = options.defaultColorLabel;
+        }
+        if (options.defaultColorTitle) {
+            EngravingRules.Rules.DefaultColorTitle = options.defaultColorTitle;
+        }
         if (options.tupletsRatioed) {
         if (options.tupletsRatioed) {
             EngravingRules.Rules.TupletsRatioed = true;
             EngravingRules.Rules.TupletsRatioed = true;
         }
         }
@@ -481,5 +487,18 @@ export class OpenSheetMusicDisplay {
     public set AutoResizeEnabled(value: boolean) {
     public set AutoResizeEnabled(value: boolean) {
         this.autoResizeEnabled = value;
         this.autoResizeEnabled = value;
     }
     }
+
+    public get Sheet(): MusicSheet {
+        return this.sheet;
+    }
+    public get Drawer(): VexFlowMusicSheetDrawer {
+        return this.drawer;
+    }
+    public get GraphicSheet(): GraphicalMusicSheet {
+        return this.graphic;
+    }
+    public get DrawingParameters(): DrawingParameters {
+        return this.drawingParameters;
+    }
     //#endregion
     //#endregion
 }
 }

+ 4 - 4
test/data/OSMD_function_test_color.musicxml

@@ -537,7 +537,7 @@
         <notehead color="#AA5500">normal</notehead>
         <notehead color="#AA5500">normal</notehead>
         <staff>1</staff>
         <staff>1</staff>
         </note>
         </note>
-      <note>
+      <note color="#AAAAAA">
         <rest/>
         <rest/>
         <duration>2</duration>
         <duration>2</duration>
         <voice>1</voice>
         <voice>1</voice>
@@ -602,7 +602,7 @@
           </direction-type>
           </direction-type>
         <staff>2</staff>
         <staff>2</staff>
         </direction>
         </direction>
-      <note>
+      <note color="#CCCCCC">
         <rest/>
         <rest/>
         <duration>2</duration>
         <duration>2</duration>
         <voice>5</voice>
         <voice>5</voice>
@@ -611,7 +611,7 @@
         </note>
         </note>
       </measure>
       </measure>
     <measure number="3" width="349.44">
     <measure number="3" width="349.44">
-      <note>
+      <note color="#FF00FF">
         <rest/>
         <rest/>
         <duration>2</duration>
         <duration>2</duration>
         <voice>1</voice>
         <voice>1</voice>
@@ -666,7 +666,7 @@
         <stem>up</stem>
         <stem>up</stem>
         <staff>1</staff>
         <staff>1</staff>
         </note>
         </note>
-      <note>
+      <note color="#FF00FF">
         <rest/>
         <rest/>
         <duration>1</duration>
         <duration>1</duration>
         <voice>1</voice>
         <voice>1</voice>

+ 2 - 24
test/data/OSMD_function_test_noteHeadShapes.musicxml → test/data/OSMD_function_test_noteheadShapes.musicxml

@@ -2,7 +2,7 @@
 <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
 <!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 3.1 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
 <score-partwise version="3.1">
 <score-partwise version="3.1">
   <work>
   <work>
-    <work-title>OSMD Function Test - Note Head Shapes</work-title>
+    <work-title>OSMD Function Test - Notehead Shapes</work-title>
     </work>
     </work>
   <identification>
   <identification>
     <encoding>
     <encoding>
@@ -639,29 +639,7 @@
         <stem>up</stem>
         <stem>up</stem>
         </note>
         </note>
       </measure>
       </measure>
-    <measure number="19" width="134.20">
-      <note default-x="26.11" default-y="-30.00">
-        <pitch>
-          <step>G</step>
-          <octave>4</octave>
-          </pitch>
-        <duration>8</duration>
-        <voice>1</voice>
-        <type>half</type>
-        <stem>up</stem>
-        <lyric number="1" default-x="6.22" default-y="-80.00">
-          <syllabic>single</syllabic>
-          <text>none</text>
-          </lyric>
-        </note>
-      <note>
-        <rest/>
-        <duration>8</duration>
-        <voice>1</voice>
-        <type>half</type>
-        </note>
-      </measure>
-    <measure number="20" width="111.45">
+    <measure number="19" width="111.45">
       <note default-x="14.97" default-y="-30.00">
       <note default-x="14.97" default-y="-30.00">
         <pitch>
         <pitch>
           <step>G</step>
           <step>G</step>