Forráskód Böngészése

Implemented Tuplet handling - tuplets are rendered but x-layout is not correct.
Fixed accidental calculator bug.
Added ties cleanup code.

Matthias 9 éve
szülő
commit
3ce937d1d5

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

@@ -174,6 +174,14 @@ declare namespace Vex {
             public draw(): void;
         }
 
+        export class Tuplet {
+            constructor(notes: StaveNote[]);
+
+            public setContext(ctx: CanvasContext): Tuplet;
+
+            public draw(): void;
+        }
+
         export class CanvasContext {
             public scale(x: number, y: number): CanvasContext;
         }

+ 37 - 7
src/MusicalScore/Graphical/AccidentalCalculator.ts

@@ -12,7 +12,7 @@ import Dictionary from "typescript-collections/dist/lib/Dictionary";
 export class AccidentalCalculator {
     private symbolFactory: IGraphicalSymbolFactory;
     private keySignatureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
-    private currentAlterationsComparedToKeyInstructionDict: number[] = [];
+    private currentAlterationsComparedToKeyInstructionList: number[] = [];
     private currentInMeasureNoteAlterationsDict: Dictionary<number, AccidentalEnum> = new Dictionary<number, AccidentalEnum>();
     private activeKeyInstruction: KeyInstruction;
 
@@ -41,24 +41,54 @@ export class AccidentalCalculator {
             return;
         }
         let pitchKey: number = <number>pitch.FundamentalNote + pitch.Octave * 12;
-        let pitchKeyGivenInMeasureDict: boolean = this.currentInMeasureNoteAlterationsDict.containsKey(pitchKey);
+        /*let pitchKeyGivenInMeasureDict: boolean = this.currentInMeasureNoteAlterationsDict.containsKey(pitchKey);
         if (
             (pitchKeyGivenInMeasureDict && this.currentInMeasureNoteAlterationsDict.getValue(pitchKey) !== pitch.Accidental)
             || (!pitchKeyGivenInMeasureDict && pitch.Accidental !== AccidentalEnum.NONE)
         ) {
-            if (this.currentAlterationsComparedToKeyInstructionDict.indexOf(pitchKey) === -1) {
-                this.currentAlterationsComparedToKeyInstructionDict.push(pitchKey);
+            if (this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey) === -1) {
+                this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
             }
             this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
             this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
         } else if (
-            this.currentAlterationsComparedToKeyInstructionDict.indexOf(pitchKey) !== -1
+            this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey) !== -1
             && ((pitchKeyGivenInMeasureDict && this.currentInMeasureNoteAlterationsDict.getValue(pitchKey) !== pitch.Accidental)
             || (!pitchKeyGivenInMeasureDict && pitch.Accidental === AccidentalEnum.NONE))
         ) {
-            delete this.currentAlterationsComparedToKeyInstructionDict[pitchKey];
+            this.currentAlterationsComparedToKeyInstructionList.splice(this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey), 1);
             this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
             this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+        }*/
+
+        let isInCurrentAlterationsToKeyList: boolean = this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey) >= 0;
+        if (this.currentInMeasureNoteAlterationsDict.containsKey(pitchKey)) {
+            if (isInCurrentAlterationsToKeyList) {
+                this.currentAlterationsComparedToKeyInstructionList.splice(this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey), 1);
+            }
+            if (this.currentInMeasureNoteAlterationsDict.getValue(pitchKey) !== pitch.Accidental) {
+                if (this.keySignatureNoteAlterationsDict.containsKey(pitchKey) &&
+                    this.keySignatureNoteAlterationsDict.getValue(pitchKey) !== pitch.Accidental) {
+                    this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
+                    this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
+                } else {
+                    this.currentInMeasureNoteAlterationsDict.remove(pitchKey);
+                }
+                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+            }
+        } else {
+            if (pitch.Accidental !== AccidentalEnum.NONE) {
+                if (!isInCurrentAlterationsToKeyList) {
+                    this.currentAlterationsComparedToKeyInstructionList.push(pitchKey);
+                }
+                this.currentInMeasureNoteAlterationsDict.setValue(pitchKey, pitch.Accidental);
+                this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+            } else {
+                if (isInCurrentAlterationsToKeyList) {
+                    this.currentAlterationsComparedToKeyInstructionList.splice(this.currentAlterationsComparedToKeyInstructionList.indexOf(pitchKey), 1);
+                    this.symbolFactory.addGraphicalAccidental(graphicalNote, pitch, grace, graceScalingFactor);
+                }
+            }
         }
     }
 
@@ -71,7 +101,7 @@ export class AccidentalCalculator {
             keyAccidentalType = AccidentalEnum.FLAT;
         }
         this.keySignatureNoteAlterationsDict.clear();
-        this.currentAlterationsComparedToKeyInstructionDict.length = 0;
+        this.currentAlterationsComparedToKeyInstructionList.length = 0;
         for (let octave: number = -9; octave < 9; octave++) {
             for (let i: number = 0; i < noteEnums.length; i++) {
                 this.keySignatureNoteAlterationsDict.setValue(<number>noteEnums[i] + octave * 12, keyAccidentalType);

+ 11 - 0
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -267,6 +267,12 @@ export abstract class MusicSheetCalculator {
     }
 
     protected clearRecreatedObjects(): void {
+        // Clear StaffEntries with GraphicalTies
+        for (let idx: number = 0, len: number = this.staffEntriesWithGraphicalTies.length; idx < len; ++idx) {
+            let staffEntriesWithGraphicalTie: GraphicalStaffEntry = this.staffEntriesWithGraphicalTies[idx];
+            staffEntriesWithGraphicalTie.GraphicalTies.length = 0;
+        }
+        this.staffEntriesWithGraphicalTies.length = 0;
         return;
     }
 
@@ -476,6 +482,10 @@ export abstract class MusicSheetCalculator {
         return;
     }
 
+    protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
+        return;
+    }
+
     protected clearSystemsAndMeasures(): void {
         for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
             let graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
@@ -1017,6 +1027,7 @@ export abstract class MusicSheetCalculator {
                 sourceMeasure, tieTimestampListDictList[staffIndex], openTuplets, openBeams,
                 accidentalCalculators[staffIndex], activeClefs, openOctaveShifts, openLyricWords, staffIndex, staffEntryLinks
             );
+            this.staffMeasureCreatedCalculations(measure);
             verticalMeasureList.push(measure);
         }
         this.graphicalMusicSheet.sourceToGraphicalMeasureLinks.setValue(sourceMeasure, verticalMeasureList);

+ 11 - 8
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalSymbolFactory.ts

@@ -96,13 +96,16 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
                       activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE,  graphicalNoteLength: Fraction = undefined): GraphicalNote {
         // Creates the note:
         let graphicalNote: GraphicalNote = new VexFlowGraphicalNote(note, graphicalStaffEntry, activeClef, octaveShift, graphicalNoteLength);
-        // Adds the note to the right (graphical) voice (mynotes)
-        let voiceID: number = note.ParentVoiceEntry.ParentVoice.VoiceId;
-        let mynotes: { [id: number]: GraphicalNote[]; } = (graphicalStaffEntry as VexFlowStaffEntry).graphicalNotes;
-        if (!(voiceID in mynotes)) {
-            mynotes[voiceID] = [];
+        if (note.ParentVoiceEntry !== undefined) {
+            // Adds the note to the right (graphical) voice (mynotes)
+            let voiceID: number = note.ParentVoiceEntry.ParentVoice.VoiceId;
+            let mynotes: { [id: number]: GraphicalNote[]; } = (graphicalStaffEntry as VexFlowStaffEntry).graphicalNotes;
+            if (!(voiceID in mynotes)) {
+                mynotes[voiceID] = [];
+            }
+            mynotes[voiceID].push(graphicalNote);
         }
-        mynotes[voiceID].push(graphicalNote);
+
         return graphicalNote;
     }
 
@@ -117,7 +120,7 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      */
     public createGraceNote(note: Note, graphicalStaffEntry: GraphicalStaffEntry,
                            activeClef: ClefInstruction, octaveShift: OctaveEnum = OctaveEnum.NONE): GraphicalNote {
-        return new GraphicalNote(note, graphicalStaffEntry);
+        return new VexFlowGraphicalNote(note, graphicalStaffEntry, activeClef, octaveShift);
     }
 
     /**
@@ -130,7 +133,7 @@ export class VexFlowGraphicalSymbolFactory implements IGraphicalSymbolFactory {
      */
     public addGraphicalAccidental(graphicalNote: GraphicalNote, pitch: Pitch, grace: boolean, graceScalingFactor: number): void {
         // ToDo: set accidental here from pitch.Accidental
-        let note: VexFlowGraphicalNote = <VexFlowGraphicalNote> graphicalNote;
+        let note: VexFlowGraphicalNote = (graphicalNote as VexFlowGraphicalNote);
         note.setPitch(pitch);
     }
 

+ 110 - 22
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -16,6 +16,7 @@ import StaveConnector = Vex.Flow.StaveConnector;
 import StaveNote = Vex.Flow.StaveNote;
 import {Logging} from "../../../Common/Logging";
 import {unitInPixels} from "./VexFlowMusicSheetDrawer";
+import {Tuplet} from "../../VoiceData/Tuplet";
 
 export class VexFlowMeasure extends StaffMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -41,6 +42,10 @@ export class VexFlowMeasure extends StaffMeasure {
     private beams: { [voiceID: number]: [Beam, VexFlowStaffEntry[]][]; } = {};
     // VexFlow Beams
     private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
+    // Intermediate object to construct tuplets
+    private tuplets: { [voiceID: number]: [Tuplet, VexFlowStaffEntry[]][]; } = {};
+    // VexFlow Tuplets
+    private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
 
     // Sets the absolute coordinates of the VFStave on the canvas
     public setAbsoluteCoordinates(x: number, y: number): void {
@@ -64,8 +69,6 @@ export class VexFlowMeasure extends StaffMeasure {
     }
 
     public clean(): void {
-        //this.beams = {};
-        //this.vfbeams = undefined;
         this.vfTies.length = 0;
         this.connectors = [];
         // Clean up instructions
@@ -153,12 +156,7 @@ export class VexFlowMeasure extends StaffMeasure {
         this.stave.setWidth(width * unitInPixels);
         // Force the width of the Begin Instructions
         //this.stave.setNoteStartX(this.beginInstructionsWidth * UnitInPixels);
-        // If this is the first stave in the vertical measure, call the format
-        // method to set the width of all the voices
-        if (this.formatVoices) {
-            // The width of the voices does not include the instructions (StaveModifiers)
-            this.formatVoices((width - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
-        }
+
     }
 
     /**
@@ -184,6 +182,13 @@ export class VexFlowMeasure extends StaffMeasure {
      * @param ctx
      */
     public draw(ctx: Vex.Flow.CanvasContext): void {
+        // If this is the first stave in the vertical measure, call the format
+        // method to set the width of all the voices
+        if (this.formatVoices) {
+            // The width of the voices does not include the instructions (StaveModifiers)
+            this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
+        }
+
         // Force the width of the Begin Instructions
         this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
         // Draw stave lines
@@ -203,6 +208,15 @@ export class VexFlowMeasure extends StaffMeasure {
             }
         }
 
+        // Draw tuplets
+        for (let voiceID in this.vftuplets) {
+            if (this.vftuplets.hasOwnProperty(voiceID)) {
+                for (let tuplet of this.vftuplets[voiceID]) {
+                    tuplet.setContext(ctx).draw();
+                }
+            }
+        }
+
         // Draw ties
         for (let tie of this.vfTies) {
             tie.setContext(ctx).draw();
@@ -236,11 +250,33 @@ export class VexFlowMeasure extends StaffMeasure {
             beams.push(data);
         }
         let parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
-        if (data[1].indexOf(parent) === -1) {
+        if (data[1].indexOf(parent) < 0) {
             data[1].push(parent);
         }
     }
 
+    public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
+        let voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
+        let tuplets: [Tuplet, VexFlowStaffEntry[]][] = this.tuplets[voiceID];
+        if (tuplets === undefined) {
+            tuplets = this.tuplets[voiceID] = [];
+        }
+        let currentTupletBuilder: [Tuplet, VexFlowStaffEntry[]];
+        for (let t of tuplets) {
+            if (t[0] === tuplet) {
+                currentTupletBuilder = t;
+            }
+        }
+        if (currentTupletBuilder === undefined) {
+            currentTupletBuilder = [tuplet, []];
+            tuplets.push(currentTupletBuilder);
+        }
+        let parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
+        if (currentTupletBuilder[1].indexOf(parent) < 0) {
+            currentTupletBuilder[1].push(parent);
+        }
+    }
+
     /**
      * Complete the creation of VexFlow Beams in this measure
      */
@@ -270,21 +306,73 @@ export class VexFlowMeasure extends StaffMeasure {
         }
     }
 
+    /**
+     * Complete the creation of VexFlow Tuplets in this measure
+     */
+    public finalizeTuplets(): void {
+        // The following line resets the created Vex.Flow Tuplets and
+        // created them brand new. Is this needed? And more importantly,
+        // should the old tuplets be removed manually by the notes?
+        this.vftuplets = {};
+        for (let voiceID in this.tuplets) {
+            if (this.tuplets.hasOwnProperty(voiceID)) {
+                let vftuplets: Vex.Flow.Tuplet[] = this.vftuplets[voiceID];
+                if (vftuplets === undefined) {
+                    vftuplets = this.vftuplets[voiceID] = [];
+                }
+                for (let tuplet of this.tuplets[voiceID]) {
+                    let notes: Vex.Flow.StaveNote[] = [];
+                    for (let entry of tuplet[1]) {
+                        notes.push((<VexFlowStaffEntry>entry).vfNotes[voiceID]);
+                    }
+                    if (notes.length > 1) {
+                        vftuplets.push(new Vex.Flow.Tuplet(notes));
+                    } else {
+                        Logging.log("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
+                    }
+                }
+            }
+        }
+    }
+
     public layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
-        let gnotes: { [voiceID: number]: GraphicalNote[]; } = (graphicalStaffEntry as VexFlowStaffEntry).graphicalNotes;
-        let vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = this.vfVoices;
-        for (let voiceID in gnotes) {
-            if (gnotes.hasOwnProperty(voiceID)) {
-                if (!(voiceID in vfVoices)) {
-                    vfVoices[voiceID] = new Vex.Flow.Voice({
-                        beat_value: this.parentSourceMeasure.Duration.Denominator,
-                        num_beats: this.parentSourceMeasure.Duration.Numerator,
-                        resolution: Vex.Flow.RESOLUTION,
-                    }).setMode(Vex.Flow.Voice.Mode.SOFT);
+        return;
+    }
+
+    public staffMeasureCreatedCalculations(): void {
+        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
+            let graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
+
+            // create vex flow Notes:
+            let gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
+            for (let voiceID in gnotes) {
+                if (gnotes.hasOwnProperty(voiceID)) {
+                    let vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
+                    (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
+                }
+            }
+        }
+
+        this.finalizeBeams();
+        this.finalizeTuplets();
+
+        for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
+            let graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
+            let gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
+            // create vex flow voices and add tickables to it:
+            let vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = this.vfVoices;
+            for (let voiceID in gnotes) {
+                if (gnotes.hasOwnProperty(voiceID)) {
+                    if (!(voiceID in vfVoices)) {
+                        vfVoices[voiceID] = new Vex.Flow.Voice({
+                            beat_value: this.parentSourceMeasure.Duration.Denominator,
+                            num_beats: this.parentSourceMeasure.Duration.Numerator,
+                            resolution: Vex.Flow.RESOLUTION,
+                        }).setMode(Vex.Flow.Voice.Mode.SOFT);
+                    }
+
+                    vfVoices[voiceID].addTickable(graphicalStaffEntry.vfNotes[voiceID]);
                 }
-                let vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
-                (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
-                vfVoices[voiceID].addTickable(vfnote);
             }
         }
     }

+ 25 - 13
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -62,14 +62,16 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
      */
     protected calculateMeasureXLayout(measures: StaffMeasure[]): number {
         // Finalize beams
-        for (let measure of measures) {
+        /*for (let measure of measures) {
             (measure as VexFlowMeasure).finalizeBeams();
-        }
+            (measure as VexFlowMeasure).finalizeTuplets();
+        }*/
         // Format the voices
         let allVoices: Vex.Flow.Voice[] = [];
         let formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter({
             align_rests: true,
         });
+
         for (let measure of measures) {
             let mvoices:  { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
             let voices: Vex.Flow.Voice[] = [];
@@ -77,6 +79,7 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
                 if (mvoices.hasOwnProperty(voiceID)) {
                     voices.push(mvoices[voiceID]);
                     allVoices.push(mvoices[voiceID]);
+
                 }
             }
             if (voices.length === 0) {
@@ -85,17 +88,22 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
             }
             formatter.joinVoices(voices);
         }
-        let firstMeasure: VexFlowMeasure = measures[0] as VexFlowMeasure;
-        // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
-        // FIXME: a more relaxed formatting of voices
-        let width: number = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
-        for (let measure of measures) {
-            measure.minimumStaffEntriesWidth = width;
-            (measure as VexFlowMeasure).formatVoices = undefined;
+
+        let width: number = 200;
+        if (allVoices.length > 0) {
+            let firstMeasure: VexFlowMeasure = measures[0] as VexFlowMeasure;
+            // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
+            // FIXME: a more relaxed formatting of voices
+            width = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
+            for (let measure of measures) {
+                measure.minimumStaffEntriesWidth = width;
+                (measure as VexFlowMeasure).formatVoices = undefined;
+            }
+            firstMeasure.formatVoices = (w: number) => {
+                formatter.format(allVoices, w);
+            };
         }
-        firstMeasure.formatVoices = (w: number) => {
-            formatter.format(allVoices, w);
-        };
+
         return width;
     }
 
@@ -113,6 +121,10 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         return;
     }
 
+    protected staffMeasureCreatedCalculations(measure: StaffMeasure): void {
+        (measure as VexFlowMeasure).staffMeasureCreatedCalculations();
+    }
+
     /**
      * Can be used to calculate stem directions, helper(ledger) lines, and overlapping note x-displacement.
      * Is Excecuted per voice entry of a staff entry.
@@ -261,6 +273,6 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
      * @param openTuplets a list of all currently open tuplets
      */
     protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
-        return;
+        (graphicalNote.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
     }
 }