Browse Source

Fixed a bug in Tuplet reading from XML.
Improved preparation of Tuplet notes for vexflow. Better now, and x-Layout also works, there are still open issues. Accurate calculation of tuplet parameters needed to fix this.

Matthias Uiberacker 7 years ago
parent
commit
8c4952b541

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

@@ -186,7 +186,7 @@ declare namespace Vex {
         }
 
         export class Tuplet {
-            constructor(notes: StaveNote[]);
+            constructor(notes: StaveNote[], options: any);
 
             public setContext(ctx: RenderContext): Tuplet;
 

+ 56 - 28
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -44,22 +44,53 @@ export class VexFlowConverter {
      * @param fraction a fraction representing the duration of a note
      * @returns {string}
      */
-    public static duration(fraction: Fraction): string {
-        let dur: number = fraction.RealValue;
-        if (dur >= 1) {
-            return "w";
-        } else if (dur < 1 && dur >= 0.5) {
-            return "h";
-        } else if (dur < 0.5 && dur >= 0.25) {
-            return "q";
-        } else if (dur < 0.25 && dur >= 0.125) {
-            return "8";
-        } else if (dur < 0.125 && dur >= 0.0625) {
-            return "16";
-        } else if (dur < 0.0625 && dur >= 0.03125) {
-            return "32";
+    public static duration(fraction: Fraction, isTuplet: boolean): string {
+      let dur: number = fraction.RealValue;
+
+      if (dur >= 1) {
+          return "w";
+      } else if (dur < 1 && dur >= 0.5) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet) {
+          return "w";
+        }
+        return "h";
+      } else if (dur < 0.5 && dur >= 0.25) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet && dur > 0.25) {
+          return "h";
+        }
+        return "q";
+      } else if (dur < 0.25 && dur >= 0.125) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet && dur > 0.125) {
+          return "q";
+        }
+        return "8";
+      } else if (dur < 0.125 && dur >= 0.0625) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet && dur > 0.0625) {
+          return "8";
         }
-        return "128";
+        return "16";
+      } else if (dur < 0.0625 && dur >= 0.03125) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet && dur > 0.03125) {
+          return "16";
+        }
+        return "32";
+      } else if (dur < 0.03125 && dur >= 0.015625) {
+        // change to the next higher straight note to get the correct note display type
+        if (isTuplet && dur > 0.015625) {
+          return "32";
+        }
+        return "64";
+      }
+
+      if (isTuplet) {
+        return "64";
+      }
+      return "128";
     }
 
     /**
@@ -113,20 +144,21 @@ export class VexFlowConverter {
         let keys: string[] = [];
         let accidentals: string[] = [];
         let frac: Fraction = notes[0].graphicalNoteLength;
-        let duration: string = VexFlowConverter.duration(frac);
+        let isTuplet: boolean = notes[0].sourceNote.NoteTuplet !== undefined;
+        let duration: string = VexFlowConverter.duration(frac, isTuplet);
         let vfClefType: string = undefined;
         let numDots: number = 0;
         for (let note of notes) {
-            let res: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
-            if (res === undefined) {
-                keys = ["b/4"];
-                duration += "r";
-                break;
+            let pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
+            if (pitch === undefined) { // if it is a rest:
+              keys = ["b/4"];
+              duration += "r";
+              break;
             }
-            keys.push(res[0]);
-            accidentals.push(res[1]);
+            keys.push(pitch[0]);
+            accidentals.push(pitch[1]);
             if (!vfClefType) {
-                let vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(res[2]);
+                let vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
                 vfClefType = vfClef.type;
             }
             if (numDots < note.numberOfDots) {
@@ -141,10 +173,6 @@ export class VexFlowConverter {
             auto_stem: true,
             clef: vfClefType,
             duration: duration,
-            duration_override: {
-                denominator: frac.Denominator,
-                numerator: frac.Numerator,
-            },
             keys: keys,
         });
 

+ 14 - 7
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -260,6 +260,7 @@ export class VexFlowMeasure extends StaffMeasure {
 
     public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
         let voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
+        tuplet = graphicalNote.sourceNote.NoteTuplet;
         let tuplets: [Tuplet, VexFlowStaffEntry[]][] = this.tuplets[voiceID];
         if (tuplets === undefined) {
             tuplets = this.tuplets[voiceID] = [];
@@ -322,7 +323,7 @@ export class VexFlowMeasure extends StaffMeasure {
     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?
+        // should the old tuplets be removed manually from the notes?
         this.vftuplets = {};
         for (let voiceID in this.tuplets) {
             if (this.tuplets.hasOwnProperty(voiceID)) {
@@ -330,13 +331,19 @@ export class VexFlowMeasure extends StaffMeasure {
                 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]);
+                for (let tupletBuilder of this.tuplets[voiceID]) {
+                    let tupletStaveNotes: Vex.Flow.StaveNote[] = [];
+                    let tupletStaffEntries: VexFlowStaffEntry[] = tupletBuilder[1];
+                    for (let tupletStaffEntry of tupletStaffEntries) {
+                      tupletStaveNotes.push((tupletStaffEntry).vfNotes[voiceID]);
                     }
-                    if (notes.length > 1) {
-                        vftuplets.push(new Vex.Flow.Tuplet(notes));
+                    if (tupletStaveNotes.length > 1) {
+                      let notesOccupied: number = 2;
+                      vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
+                                                          {
+                                                            notes_occupied: notesOccupied,
+                                                            num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
+                                                          }));
                     } else {
                         Logging.log("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
                     }

+ 15 - 6
src/MusicalScore/ScoreIO/VoiceGenerator.ts

@@ -122,6 +122,7 @@ export class VoiceGenerator {
             //    this.lyricsReader.addLyricEntry(noteNode, this.currentVoiceEntry);
             //    this.voice.Parent.HasLyrics = true;
             //}
+            let hasTupletCommand: boolean = false;
             let notationNode: IXmlElement = noteNode.element("notations");
             if (notationNode !== undefined) {
                 // let articNode: IXmlElement = undefined;
@@ -133,18 +134,23 @@ export class VoiceGenerator {
                 // (*)
                 //if (this.slurReader !== undefined && (slurNodes = notationNode.elements("slur")))
                 //    this.slurReader.addSlur(slurNodes, this.currentNote);
+                // check for Tuplets
                 let tupletNodeList: IXmlElement[] = notationNode.elements("tuplet");
-                if (tupletNodeList) {
-                    this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
+                if (tupletNodeList.length > 0) {
+                  this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
+                  hasTupletCommand = true;
                 }
+                // check for Arpeggios
                 if (notationNode.element("arpeggiate") !== undefined && !graceNote) {
                     this.currentVoiceEntry.ArpeggiosNotesIndices.push(this.currentVoiceEntry.Notes.indexOf(this.currentNote));
                 }
+                // check for Ties - must be the last check
                 let tiedNodeList: IXmlElement[] = notationNode.elements("tied");
-                if (tiedNodeList) {
+                if (tiedNodeList.length > 0) {
                     this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
                 }
 
+                // remove open ties, if there is already a gap between the last tie note and now.
                 let openTieDict: { [_: number]: Tie; } = this.openTieDict;
                 for (let key in openTieDict) {
                     if (openTieDict.hasOwnProperty(key)) {
@@ -155,7 +161,9 @@ export class VoiceGenerator {
                     }
                 }
             }
-            if (noteNode.element("time-modification") !== undefined && notationNode === undefined) {
+            // time-modification yields tuplet in currentNote
+            // mustn't execute method, if this is the Note where the Tuplet has been created
+            if (noteNode.element("time-modification") !== undefined && !hasTupletCommand) {
                 this.handleTimeModificationNode(noteNode);
             }
         } catch (err) {
@@ -731,12 +739,13 @@ export class VoiceGenerator {
     }
 
     /**
-     * Handle the time-modification [[IXmlElement]] for the [[Tuplet]] case (tupletNotes not at begin/end of [[Tuplet]]).
+     * This method handles the time-modification IXmlElement for the Tuplet case (tupletNotes not at begin/end of Tuplet).
      * @param noteNode
      */
     private handleTimeModificationNode(noteNode: IXmlElement): void {
-        if (this.openTupletNumber in this.tupletDict) {
+        if (this.tupletDict[this.openTupletNumber] !== undefined) {
             try {
+                // Tuplet should already be created
                 let tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
                 let notes: Note[] = CollectionUtil.last(tuplet.Notes);
                 let lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;

+ 1 - 1
src/MusicalScore/VoiceData/Tuplet.ts

@@ -39,7 +39,7 @@ export class Tuplet {
     }
 
     /**
-     * Return the index of the first List (notes[0], notes[1],...).
+     * Returns the index of the given Note in the Tuplet List (notes[0], notes[1],...).
      * @param note
      * @returns {number}
      */