소스 검색

feat(tremolo): display single note tremolos

possible/fixed since Vexflow 1.2.88
part of #431
sschmidTU 6 년 전
부모
커밋
db1840c249

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

@@ -257,14 +257,6 @@ export class VexFlowConverter {
             vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
         }
 
-        // half note tremolo: set notehead to half note (Vexflow otherwise takes the notehead from duration):
-        if (firstNote.Length.RealValue === 0.25 && firstNote.Notehead && firstNote.Notehead.Filled === false) {
-            const keyProps: Object[] = vfnote.getKeyProps();
-            for (let i: number = 0; i < keyProps.length; i++) {
-                (<any>keyProps[i]).code = "v81";
-            }
-        }
-
         if (EngravingRules.Rules.ColoringEnabled) {
             const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
             let stemColor: string = gve.parentVoiceEntry.StemColor;
@@ -317,7 +309,22 @@ export class VexFlowConverter {
                 }
                 vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i])); // normal accidental
             }
+
+            // add Tremolo strokes (only single note tremolos for now, Vexflow doesn't have beams for two-note tremolos yet)
+            const tremoloStrokes: number = notes[i].sourceNote.TremoloStrokes;
+            if (tremoloStrokes > 0) {
+                vfnote.addModifier(i, new Vex.Flow.Tremolo(tremoloStrokes));
+            }
         }
+
+        // half note tremolo: set notehead to half note (Vexflow otherwise takes the notehead from duration) (Hack)
+        if (firstNote.Length.RealValue === 0.25 && firstNote.Notehead && firstNote.Notehead.Filled === false) {
+            const keyProps: Object[] = vfnote.getKeyProps();
+            for (let i: number = 0; i < keyProps.length; i++) {
+                (<any>keyProps[i]).code = "v81";
+            }
+        }
+
         for (let i: number = 0, len: number = numDots; i < len; ++i) {
             vfnote.addDotToAll();
         }

+ 23 - 2
src/MusicalScore/ScoreIO/InstrumentReader.ts

@@ -197,6 +197,8 @@ export class InstrumentReader {
           const restNote: boolean = xmlNode.element("rest") !== undefined;
           //log.info("New note found!", noteDivisions, noteDuration.toString(), restNote);
 
+          const notationsNode: IXmlElement = xmlNode.element("notations"); // used for multiple checks further on
+
           const isGraceNote: boolean = xmlNode.element("grace") !== undefined || noteDivisions === 0 || isChord && lastNoteWasGrace;
           let graceNoteSlash: boolean = false;
           let graceSlur: boolean = false;
@@ -267,6 +269,25 @@ export class InstrumentReader {
             }
           }
 
+          // check Tremolo
+          let tremoloStrokes: number = 0;
+          if (notationsNode !== undefined) {
+            const ornamentsNode: IXmlElement = notationsNode.element("ornaments");
+            if (ornamentsNode !== undefined) {
+              const tremoloNode: IXmlElement = ornamentsNode.element("tremolo");
+              if (tremoloNode !== undefined) {
+                const tremoloType: Attr = tremoloNode.attribute("type");
+                if (tremoloType && tremoloType.value === "single") {
+                  const tremoloStrokesGiven: number = parseInt(tremoloNode.value, 10);
+                  if (tremoloStrokesGiven > 0) {
+                    tremoloStrokes = tremoloStrokesGiven;
+                  }
+                }
+                // TODO implement type "start". Vexflow doesn't have tremolo beams yet though (shorter than normal beams)
+              }
+            }
+          }
+
           // check notehead/color
           let noteheadColorXml: string;
           const noteheadNode: IXmlElement = xmlNode.element("notehead");
@@ -343,10 +364,10 @@ export class InstrumentReader {
             this.currentStaffEntry, this.currentMeasure,
             measureStartAbsoluteTimestamp,
             this.maxTieNoteFraction, isChord, guitarPro,
-            printObject, isCueNote, stemDirectionXml, stemColorXml, noteheadColorXml
+            printObject, isCueNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml
           );
 
-          const notationsNode: IXmlElement = xmlNode.element("notations");
+          // notationsNode created further up for multiple checks
           if (notationsNode !== undefined && notationsNode.element("dynamics") !== undefined) {
             const expressionReader: ExpressionReader = this.expressionReaders[this.readExpressionStaffNumber(xmlNode) - 1];
             if (expressionReader !== undefined) {

+ 2 - 1
src/MusicalScore/ScoreIO/MusicSymbolModules/ArticulationReader.ts

@@ -211,5 +211,6 @@ export class ArticulationReader {
         currentVoiceEntry.OrnamentContainer = ornament;
       }
     }
-  }
+  } // /addOrnament
+
 }

+ 7 - 3
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -106,7 +106,7 @@ export class VoiceGenerator {
   public read(noteNode: IXmlElement, noteDuration: Fraction, typeDuration: Fraction, normalNotes: number, restNote: boolean,
               parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
               measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean,
-              printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType,
+              printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
               stemColorXml: string, noteheadColorXml: string): Note {
     this.currentStaffEntry = parentStaffEntry;
     this.currentMeasure = parentMeasure;
@@ -115,7 +115,7 @@ export class VoiceGenerator {
       this.currentNote = restNote
         ? this.addRestNote(noteDuration, printObject, isCueNote, noteheadColorXml)
         : this.addSingleNote(noteNode, noteDuration, typeDuration, normalNotes, chord, guitarPro,
-                             printObject, isCueNote, stemDirectionXml, stemColorXml, noteheadColorXml);
+                             printObject, isCueNote, stemDirectionXml, tremoloStrokes, stemColorXml, noteheadColorXml);
       // read lyrics
       const lyricElements: IXmlElement[] = noteNode.elements("lyric");
       if (this.lyricsReader !== undefined && lyricElements !== undefined) {
@@ -310,7 +310,10 @@ export class VoiceGenerator {
     const ornaNode: IXmlElement = notationNode.element("ornaments");
     if (ornaNode !== undefined) {
       this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
+      // const tremoloNode: IXmlElement = ornaNode.element("tremolo");
+      // tremolo should be and is added per note, not per VoiceEntry. see addSingleNote()
     }
+
   }
 
   /**
@@ -323,7 +326,7 @@ export class VoiceGenerator {
    * @returns {Note}
    */
   private addSingleNote(node: IXmlElement, noteDuration: Fraction, typeDuration: Fraction, normalNotes: number, chord: boolean, guitarPro: boolean,
-                        printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType,
+                        printObject: boolean, isCueNote: boolean, stemDirectionXml: StemDirectionType, tremoloStrokes: number,
                         stemColorXml: string, noteheadColorXml: string): Note {
     //log.debug("addSingleNote called");
     let noteAlter: number = 0;
@@ -421,6 +424,7 @@ export class VoiceGenerator {
     note.PrintObject = printObject;
     note.IsCueNote = isCueNote;
     note.StemDirectionXml = stemDirectionXml; // maybe unnecessary, also in VoiceEntry
+    note.TremoloStrokes = tremoloStrokes; // could be a Tremolo object in future if we have more data to manage like two-note tremolo
     if ((noteheadShapeXml !== undefined && noteheadShapeXml !== "normal") || noteheadFilledXml !== undefined) {
       note.Notehead = new Notehead(note, noteheadShapeXml, noteheadFilledXml);
     } // if normal, leave note head undefined to save processing/runtime

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

@@ -58,6 +58,10 @@ export class Note {
     private isCueNote: boolean;
     /** The stem direction asked for in XML. Not necessarily final or wanted stem direction. */
     private stemDirectionXml: StemDirectionType;
+    /** The number of tremolo strokes this note has (16th tremolo = 2 strokes).
+     * Could be a Tremolo object in future when there is more data like tremolo between two notes.
+     */
+    private tremoloStrokes: number;
     /** Color of the stem given in the XML Stem tag. RGB Hexadecimal, like #00FF00.
      * This is not used for rendering, which takes VoiceEntry.StemColor.
      * It is merely given in the note's stem element in XML and stored here for reference.
@@ -169,6 +173,12 @@ export class Note {
     public set StemDirectionXml(value: StemDirectionType) {
         this.stemDirectionXml = value;
     }
+    public get TremoloStrokes(): number {
+        return this.tremoloStrokes;
+    }
+    public set TremoloStrokes(value: number) {
+        this.tremoloStrokes = value;
+    }
     public get StemColorXml(): string {
         return this.stemColorXml;
     }