فهرست منبع

included code from @matt-uib that handles ghostnote positions and added ghost note converter

Benjamin Giesinger 7 سال پیش
والد
کامیت
6dc04602e9

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

@@ -76,6 +76,15 @@ declare namespace Vex {
             public setStemDirection(direction: number): StemmableNote;
         }
 
+        export class GhostNote extends StemmableNote {
+            constructor(note_struct: any);
+            isRest(): boolean;
+
+            setStave(stave): void;
+
+            draw(): void;
+        }
+
         export class StaveNote extends StemmableNote {
             constructor(note_struct: any);
 

+ 7 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -137,6 +137,13 @@ export class VexFlowConverter {
         return acc;
     }
 
+    public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
+        // const frac: Fraction = notes[0].graphicalNoteLength;
+        return new Vex.Flow.GhostNote({
+            duration: VexFlowConverter.duration(frac, false),
+        });
+    }
+
     /**
      * Convert a set of GraphicalNotes to a VexFlow StaveNote
      * @param notes form a chord on the staff

+ 98 - 12
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -20,6 +20,7 @@ import {Tuplet} from "../../VoiceData/Tuplet";
 import { RepetitionInstructionEnum } from "../../VoiceData/Instructions/RepetitionInstruction";
 import { SystemLinePosition } from "../SystemLinePosition";
 import { StemDirectionType } from "../../VoiceData/VoiceEntry";
+import { Fraction } from "../../../Common/DataObjects/Fraction";
 
 export class VexFlowMeasure extends StaffMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -342,6 +343,91 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     /**
+     * Finds the gaps between the existing notes within a measure.
+     * Problem here is, that the graphicalVoiceEntry does not exist yet and
+     * that Tied notes are not present in the normal voiceEntries.
+     * To handle this, calculation with absolute timestamps is needed.
+     * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
+     */
+    private fillMissingRests(): void {
+        const latestVoiceTimestampDict: { [voiceID: number]: Fraction; } = {};
+
+        // 1) find front- and in-measure-gaps:
+        for (const staffEntry of this.staffEntries as VexFlowStaffEntry[]) {
+            for (const gNotesPerVoice of staffEntry.notes) {
+                // get voice id:
+                const voiceId: number = gNotesPerVoice[0].sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
+                const gNotesStartTimestamp: Fraction = gNotesPerVoice[0].sourceNote.getAbsoluteTimestamp();
+
+                // These 3 lines one render all "regular" notes:
+                const gnotes: { [voiceID: number]: GraphicalNote[]; } = staffEntry.graphicalNotes;
+                const vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceId]);
+                staffEntry.vfNotes[voiceId] = vfnote;
+
+                // find the voiceEntry end timestamp:
+                let gNotesEndTimestamp: Fraction = new Fraction();
+                for (const graphicalNote of gNotesPerVoice) {
+                    // console.log(graphicalNote);
+                    const noteEnd: Fraction  = Fraction.plus(graphicalNote.sourceNote.getAbsoluteTimestamp(), graphicalNote.sourceNote.Length);
+                    if (gNotesEndTimestamp < noteEnd) {
+                        gNotesEndTimestamp = noteEnd;
+                    }
+                }
+
+                // ToDo: maybe check needed if this throws an exception when not in dict:
+                const latestVoiceTimestamp: Fraction = latestVoiceTimestampDict[voiceId];
+
+                // check if this voice has just been found the first time:
+                if (latestVoiceTimestamp === undefined) {
+
+                    // if this voice is new, check for a gap from measure start to the start of the current voice entry:
+                    const gapFromMeasureStart: Fraction = Fraction.minus(gNotesStartTimestamp, this.parentSourceMeasure.AbsoluteTimestamp);
+                    if (gapFromMeasureStart.RealValue > 0) {
+                        console.log("Ghost Found at start",  this)
+                        const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(gapFromMeasureStart);
+                        // staffEntry.vfNotes[voiceId] = (vfghost as any);
+                        // ToDo: fill the gap with a rest ghost note
+                        // from this.parentSourceMeasure.AbsoluteTimestamp
+                        // with length gapFromMeasureStart:
+                        // (maybe remember in a list and add later in a second loop)
+
+                    }
+                } else {
+                    // get the length of the empty space between notes:
+                    const restLength: Fraction = Fraction.minus(gNotesStartTimestamp, latestVoiceTimestamp);
+
+                    if (restLength.RealValue > 0) {
+                        console.log("Ghost Found in between",  this)
+                        // ToDo: fill the gap with a rest ghost note
+                        // starting from latestVoiceTimestamp
+                        // with length restLength:
+                        // (maybe remember in a list and add later in a second loop)
+
+                    }
+                }
+
+                // finally set the latest timestamp of this voice to the end timestamp of the longest note in the current voiceEntry:
+                latestVoiceTimestampDict[voiceId] = gNotesEndTimestamp;
+            }
+        }
+
+        // 2) find gaps from last notes to end of this measure:
+        for (const voiceId in latestVoiceTimestampDict) {
+            if (voiceId !== undefined) {
+                const lastFraction: Fraction = latestVoiceTimestampDict[voiceId];
+                const measureEndTimestamp: Fraction = Fraction.plus(this.parentSourceMeasure.AbsoluteTimestamp, this.parentSourceMeasure.Duration);
+                const restLength: Fraction  = Fraction.minus(measureEndTimestamp, lastFraction);
+                if (restLength.RealValue > 0) {
+                    // fill the gap with a rest ghost note
+                    // starting from lastFraction
+                    // with length restLength:
+                    console.log("Ghost Found at end",  this)
+                }
+            }
+        }
+    }
+
+    /**
      * Add a note to a beam
      * @param graphicalNote
      * @param beam
@@ -471,18 +557,18 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     public staffMeasureCreatedCalculations(): void {
-        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
-            const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
-
-            // create vex flow Notes:
-            const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
-            for (const voiceID in gnotes) {
-                if (gnotes.hasOwnProperty(voiceID)) {
-                    const vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
-                    (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
-                }
-            }
-        }
+        this.fillMissingRests();
+        // for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
+        //     // create vex flow Notes:
+        //     const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
+        //     for (const voiceID in gnotes) {
+        //         if (gnotes.hasOwnProperty(voiceID)) {
+        //             // console.log(gnotes[voiceID][0]);
+        //             // const vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
+        //             // (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
+        //         }
+        //     }
+        // }
 
         this.finalizeBeams();
         this.finalizeTuplets();