Parcourir la source

fix(Fingering): associate fingering with correct note when not all notes have fingerings, save Note.Fingering (#889)

Fingering was only stored for the voiceEntry as a whole,
so it was difficult to associate a fingering with a note.

Fingering is now stored in Note.Fingering
also, TechnicalInstruction saves the sourceNote for fingerings
(in VoiceEntry.TechnicalInstructions)

there was also a bug where a fingering was assigned to the wrong note because of this
(triad, only middle note no fingering)
sschmidTU il y a 4 ans
Parent
commit
a59e5d9739

+ 18 - 15
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -29,7 +29,7 @@ import {Voice} from "../../VoiceData/Voice";
 import {LinkedVoice} from "../../VoiceData/LinkedVoice";
 import {EngravingRules} from "../EngravingRules";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
-import {TechnicalInstruction, TechnicalInstructionType} from "../../VoiceData/Instructions/TechnicalInstruction";
+import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {AutoBeamOptions} from "../../../OpenSheetMusicDisplay/OSMDOptions";
@@ -1230,22 +1230,25 @@ export class VexFlowMeasure extends GraphicalMeasure {
 
     protected createFingerings(voiceEntry: GraphicalVoiceEntry): void {
         const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
-        const technicalInstructions: TechnicalInstruction[] = voiceEntry.parentVoiceEntry.TechnicalInstructions;
-        let fingeringsCount: number = 0;
-        for (const instruction of technicalInstructions) {
-            if (instruction.type === TechnicalInstructionType.Fingering) {
-                fingeringsCount++;
+        let numberOfFingerings: number = 0;
+        // count total number of fingerings
+        for (const note of voiceEntry.notes) {
+            const fingering: TechnicalInstruction = note.sourceNote.Fingering;
+            if (fingering) {
+                numberOfFingerings++;
             }
         }
         let fingeringIndex: number = -1;
-        for (const fingeringInstruction of technicalInstructions) {
-            if (fingeringInstruction.type !== TechnicalInstructionType.Fingering) {
+        for (const note of voiceEntry.notes) {
+            const fingering: TechnicalInstruction = note.sourceNote.Fingering;
+            if (!fingering) {
+                fingeringIndex++;
                 continue;
             }
             fingeringIndex++; // 0 for first fingering
             let fingeringPosition: PlacementEnum = this.rules.FingeringPosition;
-            if (fingeringInstruction.placement !== PlacementEnum.NotYetDefined) {
-                fingeringPosition = fingeringInstruction.placement;
+            if (fingering.placement !== PlacementEnum.NotYetDefined) {
+                fingeringPosition = fingering.placement;
             }
             let modifierPosition: any; // Vex.Flow.Stavemodifier.Position
             switch (fingeringPosition) {
@@ -1275,21 +1278,21 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     }
             }
 
-            const fretFinger: Vex.Flow.FretHandFinger = new Vex.Flow.FretHandFinger(fingeringInstruction.value);
+            const fretFinger: Vex.Flow.FretHandFinger = new Vex.Flow.FretHandFinger(fingering.value);
             fretFinger.setPosition(modifierPosition);
             fretFinger.setOffsetX(this.rules.FingeringOffsetX);
             if (fingeringPosition === PlacementEnum.Above || fingeringPosition === PlacementEnum.Below) {
                 const offsetYSign: number = fingeringPosition === PlacementEnum.Above ? -1 : 1; // minus y is up
                 const ordering: number = fingeringPosition === PlacementEnum.Above ? fingeringIndex :
-                    fingeringsCount - 1 - fingeringIndex; // reverse order for fingerings below staff
-                if (this.rules.FingeringInsideStafflines && fingeringsCount > 1) { // y-shift for single fingering is ok
+                    numberOfFingerings - 1 - fingeringIndex; // reverse order for fingerings below staff
+                if (this.rules.FingeringInsideStafflines && numberOfFingerings > 1) { // y-shift for single fingering is ok
                     // experimental, bounding boxes wrong for fretFinger above/below, better would be creating Labels
                     // set y-shift. vexflow fretfinger simply places directly above/below note
                     const perFingeringShift: number = fretFinger.getWidth() / 2;
-                    const shiftCount: number = fingeringsCount * 2.5;
+                    const shiftCount: number = numberOfFingerings * 2.5;
                     fretFinger.setOffsetY(offsetYSign * (ordering + shiftCount) * perFingeringShift);
                 } else if (!this.rules.FingeringInsideStafflines) { // use StringNumber for placement above/below stafflines
-                    const stringNumber: Vex.Flow.StringNumber = new Vex.Flow.StringNumber(fingeringInstruction.value);
+                    const stringNumber: Vex.Flow.StringNumber = new Vex.Flow.StringNumber(fingering.value);
                     (<any>stringNumber).radius = 0; // hack to remove the circle around the number
                     stringNumber.setPosition(modifierPosition);
                     stringNumber.setOffsetY(offsetYSign * ordering * stringNumber.getWidth() * 2 / 3);

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

@@ -6,6 +6,7 @@ import {OrnamentContainer, OrnamentEnum} from "../../VoiceData/OrnamentContainer
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
 import { Articulation } from "../../VoiceData/Articulation";
+import { Note } from "../../VoiceData/Note";
 export class ArticulationReader {
 
   private getAccEnumFromString(input: string): AccidentalEnum {
@@ -112,7 +113,7 @@ export class ArticulationReader {
    * @param xmlNode
    * @param currentVoiceEntry
    */
-  public addTechnicalArticulations(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+  public addTechnicalArticulations(xmlNode: IXmlElement, currentVoiceEntry: VoiceEntry, currentNote: Note): void {
     interface XMLElementToArticulationEnum {
       [xmlElement: string]: ArticulationEnum;
     }
@@ -172,6 +173,8 @@ export class ArticulationReader {
             currentTechnicalInstruction.placement = PlacementEnum.NotYetDefined;
         }
       }
+      currentTechnicalInstruction.sourceNote = currentNote;
+      currentNote.Fingering = currentTechnicalInstruction;
       currentVoiceEntry.TechnicalInstructions.push(currentTechnicalInstruction);
     }
   }

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

@@ -132,7 +132,7 @@ export class VoiceGenerator {
       if (notationNode) {
         // read articulations
         if (this.articulationReader) {
-          this.readArticulations(notationNode, this.currentVoiceEntry);
+          this.readArticulations(notationNode, this.currentVoiceEntry, this.currentNote);
         }
         // read slurs
         const slurElements: IXmlElement[] = notationNode.elements("slur");
@@ -284,7 +284,7 @@ export class VoiceGenerator {
     return this.currentVoiceEntry !== undefined;
   }
 
-  private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
+  private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry, currentNote: Note): void {
     const articNode: IXmlElement = notationNode.element("articulations");
     if (articNode) {
       this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
@@ -295,7 +295,7 @@ export class VoiceGenerator {
     }
     const tecNode: IXmlElement = notationNode.element("technical");
     if (tecNode) {
-      this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry);
+      this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry, currentNote);
     }
     const ornaNode: IXmlElement = notationNode.element("ornaments");
     if (ornaNode) {

+ 2 - 0
src/MusicalScore/VoiceData/Instructions/TechnicalInstruction.ts

@@ -1,4 +1,5 @@
 import { PlacementEnum } from "../Expressions/AbstractExpression";
+import { Note } from "../Note";
 
 export enum TechnicalInstructionType {
     Fingering
@@ -7,4 +8,5 @@ export class TechnicalInstruction {
     public type: TechnicalInstructionType;
     public value: string;
     public placement: PlacementEnum;
+    public sourceNote: Note;
 }

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

@@ -12,6 +12,7 @@ import {Notehead} from "./Notehead";
 import {Arpeggio} from "./Arpeggio";
 import {NoteType} from "./NoteType";
 import { SourceMeasure } from "./SourceMeasure";
+import { TechnicalInstruction } from "./Instructions";
 
 /**
  * Represents a single pitch with a duration (length)
@@ -91,6 +92,7 @@ export class Note {
      */
     private noteheadColor: string;
     private noteheadColorCurrentlyRendered: string;
+    public Fingering: TechnicalInstruction; // this is also stored in VoiceEntry.TechnicalInstructions
 
     public get ParentVoiceEntry(): VoiceEntry {
         return this.voiceEntry;