ソースを参照

feat(color): color Notehead/Stem, parsed by XML (#436)

VexFlowMeasure: save and restore context so that colors don't overflow
rename VoiceEntry.WantedStemDirectionXml -> StemDirectionXml
Simon 6 年 前
コミット
41261339ac

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

@@ -121,11 +121,15 @@ declare namespace Vex {
 
             public addAnnotation(index: number, annotation: Annotation): StaveNote;
 
+            public addDotToAll(): void;
+
             public addModifier(index: number, modifier: Modifier): StaveNote;
 
             public setStyle(style: any): void;
 
-            public addDotToAll(): void;
+            public setStemStyle(style: any): void;
+
+            public note_heads: any; // NoteHead[]; // temp solution until noteheadStyles PR is through
         }
 
         export class GraceNote extends StaveNote {

+ 3 - 3
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -1486,10 +1486,10 @@ export abstract class MusicSheetCalculator {
                                openTuplets: Tuplet[], openBeams: Beam[],
                                octaveShiftValue: OctaveEnum, linkedNotes: Note[] = undefined,
                                sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
-        if (voiceEntry.WantedStemDirectionXml !== StemDirectionType.Undefined &&
+        if (voiceEntry.StemDirectionXml !== StemDirectionType.Undefined &&
             EngravingRules.Rules.SetWantedStemDirectionByXml &&
-            voiceEntry.WantedStemDirectionXml !== undefined) {
-                voiceEntry.WantedStemDirection = voiceEntry.WantedStemDirectionXml;
+            voiceEntry.StemDirectionXml !== undefined) {
+                voiceEntry.WantedStemDirection = voiceEntry.StemDirectionXml;
         } else {
             this.calculateStemDirectionFromVoices(voiceEntry);
         }

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

@@ -216,17 +216,13 @@ export class VexFlowConverter {
         let alignCenter: boolean = false;
         let xShift: number = 0;
         let slashNoteHead: boolean = false;
+        const noteheadStyles: any = [];
+        const stemColor: string = gve.parentVoiceEntry.StemColorXml;
+        const stemStyle: Object = { fillStyle: stemColor, strokeStyle: stemColor };
         for (const note of notes) {
             if (numDots < note.numberOfDots) {
                 numDots = note.numberOfDots;
             }
-            if (note.sourceNote.NoteHead) {
-                if (note.sourceNote.NoteHead.Shape === NoteHeadShape.SLASH) {
-                    slashNoteHead = true;
-                    // if we have slash heads and other heads in the voice entry, this will create the same head for all.
-                    // same problem with numDots. The slash case should be extremely rare though.
-                }
-            }
             // if it is a rest:
             if (note.sourceNote.isRest()) {
                 keys = ["b/4"];
@@ -245,6 +241,22 @@ export class VexFlowConverter {
                 duration += "r";
                 break;
             }
+
+            if (note.sourceNote.NoteHead) {
+                if (note.sourceNote.NoteHead.Shape === NoteHeadShape.SLASH) {
+                    slashNoteHead = true;
+                    // if we have slash heads and other heads in the voice entry, this will create the same head for all.
+                    // same problem with numDots. The slash case should be extremely rare though.
+                }
+            }
+
+            const noteheadColor: string = note.sourceNote.NoteheadColorXml;
+            if (noteheadColor) {
+                noteheadStyles.push({fillStyle: noteheadColor, strokeStyle: noteheadColor});
+            } else {
+                noteheadStyles.push(undefined);
+            }
+
             const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
             keys.push(pitch[0]);
             accidentals.push(pitch[1]);
@@ -262,15 +274,19 @@ export class VexFlowConverter {
         }
 
         let vfnote: Vex.Flow.StaveNote;
+
         const vfnoteStruct: Object = {
             align_center: alignCenter,
             auto_stem: true,
             clef: vfClefType,
             duration: duration,
             keys: keys,
+            noteheadStyles: noteheadStyles,
             slash: gve.parentVoiceEntry.GraceNoteSlash,
         };
-
+        if (stemColor) {
+            (<any>vfnoteStruct).stemStyle = stemStyle;
+        }
         if (gve.notes[0].sourceNote.IsCueNote) {
             (<any>vfnoteStruct).glyph_font_scale = Vex.Flow.DEFAULT_NOTATION_FONT_SCALE * Vex.Flow.GraceNote.SCALE;
             (<any>vfnoteStruct).stroke_px = Vex.Flow.GraceNote.LEDGER_LINE_OFFSET;
@@ -282,6 +298,18 @@ export class VexFlowConverter {
             vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
         }
 
+        if (stemColor) {
+            vfnote.setStemStyle(stemStyle);
+        }
+
+        // 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) {
+                vfnote.note_heads[i].setStyle(style);
+            }
+        }
+
         vfnote.x_shift = xShift;
 
         if (gve.parentVoiceEntry.IsGrace && gve.notes[0].sourceNote.NoteBeam) {

+ 2 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowInstrumentBracket.ts

@@ -1,6 +1,6 @@
 import Vex = require("vexflow");
-import {GraphicalObject} from "../GraphicalObject";
-import {VexFlowStaffLine} from "./VexFlowStaffLine";
+import { GraphicalObject } from "../GraphicalObject";
+import { VexFlowStaffLine } from "./VexFlowStaffLine";
 import { BoundingBox } from "../BoundingBox";
 import { VexFlowMeasure } from "./VexFlowMeasure";
 import { unitInPixels } from "./VexFlowMusicSheetDrawer";

+ 2 - 4
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -334,7 +334,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
         // Draw all voices
         for (const voiceID in this.vfVoices) {
             if (this.vfVoices.hasOwnProperty(voiceID)) {
+                ctx.save();
                 this.vfVoices[voiceID].draw(ctx, this.stave);
+                ctx.restore();
                 // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
                 // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
             }
@@ -589,10 +591,6 @@ export class VexFlowMeasure extends GraphicalMeasure {
                             (<any>vfBeam).render_options.partial_beam_length = 4;
                         }
                         vfbeams.push(vfBeam);
-                        // just a test for coloring the notes:
-                        // for (let note of notes) {
-                        //     (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
-                        // }
                     } else {
                         log.debug("Warning! Beam with no notes!");
                     }

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

@@ -465,7 +465,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
       } else if (graphicalContinuousDynamic.IsVerbal) {
         this.calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
       } else {
-        log.warn("This continous dynamic is not covered");
+        log.warn("This continuous dynamic is not covered");
       }
     }
   }

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

@@ -231,6 +231,7 @@ export class InstrumentReader {
 
           // check stem element
           let stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
+          let stemColorXml: string;
           const stemNode: IXmlElement = xmlNode.element("stem");
           if (stemNode !== undefined) {
             switch (stemNode.value) {
@@ -249,6 +250,29 @@ export class InstrumentReader {
               default:
                 stemDirectionXml = StemDirectionType.Undefined;
             }
+
+            const stemColorAttr: Attr = stemNode.attribute("color");
+            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);
+              }
+            }
+          }
+
+          // check notehead/color
+          let noteheadColorXml: string;
+          const noteheadNode: IXmlElement = xmlNode.element("notehead");
+          if (noteheadNode) {
+            const colorAttr: Attr = noteheadNode.attribute("color");
+            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);
+            }
           }
 
           let musicTimestamp: Fraction = currentFraction.clone();
@@ -304,7 +328,8 @@ export class InstrumentReader {
             xmlNode, noteDuration, restNote,
             this.currentStaffEntry, this.currentMeasure,
             measureStartAbsoluteTimestamp,
-            this.maxTieNoteFraction, isChord, guitarPro, printObject, isCueNote, stemDirectionXml
+            this.maxTieNoteFraction, isChord, guitarPro,
+            printObject, isCueNote, stemDirectionXml, stemColorXml, noteheadColorXml
           );
 
           const notationsNode: IXmlElement = xmlNode.element("notations");

+ 12 - 5
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -106,14 +106,16 @@ export class VoiceGenerator {
   public read(noteNode: IXmlElement, noteDuration: Fraction, restNote: boolean,
               parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
               measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean,
-              printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType): Note {
+              printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType,
+              stemColorXml: string, noteheadColorXml: string): Note {
     this.currentStaffEntry = parentStaffEntry;
     this.currentMeasure = parentMeasure;
     //log.debug("read called:", restNote);
     try {
       this.currentNote = restNote
         ? this.addRestNote(noteDuration, printObject, isCueNote)
-        : this.addSingleNote(noteNode, noteDuration, chord, guitarPro, printObject, isCueNote, stemDirectionXml);
+        : this.addSingleNote(noteNode, noteDuration, chord, guitarPro,
+                             printObject, isCueNote, stemDirectionXml, stemColorXml, noteheadColorXml);
       // read lyrics
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
       if (this.lyricsReader !== undefined && lyricElements !== undefined) {
@@ -321,7 +323,8 @@ export class VoiceGenerator {
    * @returns {Note}
    */
   private addSingleNote(node: IXmlElement, noteDuration: Fraction, chord: boolean, guitarPro: boolean,
-                        printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType): Note {
+                        printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType,
+                        stemColorXml: string, noteheadColorXml: string): Note {
     //log.debug("addSingleNote called");
     let noteAlter: number = 0;
     let noteAccidental: AccidentalEnum = AccidentalEnum.NONE;
@@ -416,13 +419,17 @@ export class VoiceGenerator {
     const note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
     note.PrintObject = printObject;
     note.IsCueNote = isCueNote;
-    note.StemDirectionXml = stemDirectionXml;
+    note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
+    note.NoteheadColorXml = noteheadColorXml;
     note.PlaybackInstrumentId = playbackInstrumentId;
     if (noteHeadShapeXml !== undefined && noteHeadShapeXml !== "normal") {
       note.NoteHead = new NoteHead(note, noteHeadShapeXml, noteHeadFilledXml);
     } // if normal, leave note head undefined to save processing/runtime
     this.currentVoiceEntry.Notes.push(note);
-    this.currentVoiceEntry.WantedStemDirectionXml = stemDirectionXml;
+    this.currentVoiceEntry.StemDirectionXml = stemDirectionXml;
+    if (stemColorXml) {
+      this.currentVoiceEntry.StemColorXml = stemColorXml;
+    }
     if (node.elements("beam") && !chord) {
       this.createBeam(node, note);
     }

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

@@ -54,6 +54,10 @@ export class Note {
     private isCueNote: boolean;
     /** The stem direction asked for in XML. Not necessarily final or wanted stem direction. */
     private stemDirectionXml: StemDirectionType;
+    /** Color of the stem given in the XML Stem tag. RGB Hexadecimal, like #00FF00. */
+    private stemColorXml: string;
+    /** Color of the note given in the XML Notehead tag. RGB Hexadecimal, like #00FF00. */
+    private noteheadColorXml: string;
 
     public get ParentVoiceEntry(): VoiceEntry {
         return this.voiceEntry;
@@ -136,6 +140,18 @@ export class Note {
     public set StemDirectionXml(value: StemDirectionType) {
         this.stemDirectionXml = value;
     }
+    public get StemColorXml(): string {
+        return this.stemColorXml;
+    }
+    public set StemColorXml(value: string) {
+        this.stemColorXml = value;
+    }
+    public get NoteheadColorXml(): string {
+        return this.noteheadColorXml;
+    }
+    public set NoteheadColorXml(value: string) {
+        this.noteheadColorXml = value;
+    }
 
     public isRest(): boolean {
         return this.Pitch === undefined;

+ 12 - 5
src/MusicalScore/VoiceData/VoiceEntry.ts

@@ -52,8 +52,9 @@ export class VoiceEntry {
     private ornamentContainer: OrnamentContainer;
     private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
     /** Stem direction specified in the xml stem element. */
-    private wantedStemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
+    private stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
     private stemDirection: StemDirectionType = StemDirectionType.Undefined;
+    private stemColorXml: string;
 
     public get ParentSourceStaffEntry(): SourceStaffEntry {
         return this.parentSourceStaffEntry;
@@ -124,11 +125,11 @@ export class VoiceEntry {
     public get WantedStemDirection(): StemDirectionType {
         return this.wantedStemDirection;
     }
-    public set WantedStemDirectionXml(value: StemDirectionType) {
-        this.wantedStemDirectionXml = value;
+    public set StemDirectionXml(value: StemDirectionType) {
+        this.stemDirectionXml = value;
     }
-    public get WantedStemDirectionXml(): StemDirectionType {
-        return this.wantedStemDirectionXml;
+    public get StemDirectionXml(): StemDirectionType {
+        return this.stemDirectionXml;
     }
     // StemDirection holds the actual value of the stem
     public set StemDirection(value: StemDirectionType) {
@@ -137,6 +138,12 @@ export class VoiceEntry {
     public get StemDirection(): StemDirectionType {
         return this.stemDirection;
     }
+    public get StemColorXml(): string {
+        return this.stemColorXml;
+    }
+    public set StemColorXml(value: string) {
+        this.stemColorXml = value;
+    }
 
     public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
         switch (articulation) {