Browse Source

Merge branch 'fix/SlurPositioning' into develop

* fix/SlurPositioning:
  Slurs starting/ending at stems now have endpoints exactly at the position of the stem. To read out and set wanted (in case of more than 1 voice) stem position, the variable StemPosition in VoiceEntry has been renamed to WantedStemPosition and a new variable StemPosition has been added, to just copy the stem direction automatically chosen by VexFlow.
  Added shift of start- and endpoint of slurs in x direction (timestamp) from the center of the bounding box to the stem position for better graphical appearance. TODO: There are still some wrong placings to be corrected!
Christoph Uiberacker 6 years ago
parent
commit
ceb0dd39eb

+ 40 - 0
src/MusicalScore/Graphical/GraphicalSlur.ts

@@ -11,6 +11,8 @@ import { Matrix2D } from "../../Common/DataObjects/Matrix2D";
 import { LinkedVoice } from "../VoiceData/LinkedVoice";
 import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
 import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
+import { Fraction } from "../../Common/DataObjects/Fraction";
+import { StemDirectionType } from "../VoiceData/VoiceEntry";
 
 export class GraphicalSlur extends GraphicalCurve {
     // private intersection: PointF2D;
@@ -27,6 +29,28 @@ export class GraphicalSlur extends GraphicalCurve {
     public graceEnd: boolean;
 
     /**
+     * Compares the timespan of two Graphical Slurs
+     * @param x
+     * @param y
+     */
+    public static Compare (x: GraphicalSlur, y: GraphicalSlur ): number {
+        const xTimestampSpan: Fraction = Fraction.minus(x.staffEntries[x.staffEntries.length - 1].getAbsoluteTimestamp(),
+                                                        x.staffEntries[0].getAbsoluteTimestamp());
+        const yTimestampSpan: Fraction = Fraction.minus(y.staffEntries[y.staffEntries.length - 1].getAbsoluteTimestamp(),
+                                                        y.staffEntries[0].getAbsoluteTimestamp());
+
+        if (xTimestampSpan.RealValue > yTimestampSpan.RealValue) {
+            return 1;
+        }
+
+        if (yTimestampSpan.RealValue > xTimestampSpan.RealValue) {
+            return -1;
+        }
+
+        return 0;
+    }
+
+    /**
      *
      * @param rules
      */
@@ -396,6 +420,14 @@ export class GraphicalSlur extends GraphicalCurve {
                 startY = slurStartVE.PositionAndShape.RelativePosition.y + slurStartVE.PositionAndShape.BorderBottom;
             }
 
+            // If the stem points towards the starting point of the slur, shift the slur by a small amount to start (approximately) at the x-position
+            // of the notehead. Note: an exact calculation using the position of the note is too complicate for the payoff
+            if ( slurStartVE.parentVoiceEntry.StemDirection === StemDirectionType.Down && this.placement === PlacementEnum.Below ) {
+                startX -= 0.5;
+            }
+            if (slurStartVE.parentVoiceEntry.StemDirection === StemDirectionType.Up && this.placement === PlacementEnum.Above) {
+                startX += 0.5;
+            }
             // if (first.NoteStem !== undefined && first.NoteStem.Direction === StemEnum.StemUp && this.placement === PlacementEnum.Above) {
             //     startX += first.NoteStem.PositionAndShape.RelativePosition.x;
             //     startY = skyBottomLineCalculator.getSkyLineMinAtPoint(staffLine, startX);
@@ -427,6 +459,14 @@ export class GraphicalSlur extends GraphicalCurve {
                 endY = slurEndVE.PositionAndShape.RelativePosition.y + slurEndVE.PositionAndShape.BorderBottom;
             }
 
+            // If the stem points towards the endpoint of the slur, shift the slur by a small amount to start (approximately) at the x-position
+            // of the notehead. Note: an exact calculation using the position of the note is too complicate for the payoff
+            if ( slurEndVE.parentVoiceEntry.StemDirection === StemDirectionType.Down && this.placement === PlacementEnum.Below ) {
+                endX -= 0.5;
+            }
+            if (slurEndVE.parentVoiceEntry.StemDirection === StemDirectionType.Up && this.placement === PlacementEnum.Above) {
+                endX += 0.5;
+            }
             // const first: GraphicalNote = <GraphicalNote>slurEndNote.parentVoiceEntry.notes[0];
             // if (first.NoteStem !== undefined && first.NoteStem.Direction === StemEnum.StemUp && this.placement === PlacementEnum.Above) {
             //     endX += first.NoteStem.PositionAndShape.RelativePosition.x;

+ 0 - 21
src/MusicalScore/Graphical/GraphicalSlurSorter.ts

@@ -1,21 +0,0 @@
-import { Fraction } from "../../Common/DataObjects/Fraction";
-import { GraphicalSlur } from "./GraphicalSlur";
-
-export interface GraphicalSlurSorterKeyValuePair {
-    key: Fraction;
-    value: GraphicalSlur;
-}
-
-export class GraphicalSlurSorter {
-    public Compare (x: GraphicalSlurSorterKeyValuePair, y: GraphicalSlurSorterKeyValuePair ): number {
-        if (x.key > y.key) {
-            return 1;
-        }
-
-        if (y.key > x.key) {
-            return -1;
-        }
-
-        return 0;
-    }
-}

+ 7 - 7
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -2285,23 +2285,23 @@ export abstract class MusicSheetCalculator {
             // in case of StaffEntryLink don't check mainVoice / linkedVoice
             if (voiceEntry === voiceEntry.ParentSourceStaffEntry.VoiceEntries[0]) {
                 // set stem up:
-                voiceEntry.StemDirection = StemDirectionType.Up;
+                voiceEntry.WantedStemDirection = StemDirectionType.Up;
                 return;
             } else {
                 // set stem down:
-                voiceEntry.StemDirection = StemDirectionType.Down;
+                voiceEntry.WantedStemDirection = StemDirectionType.Down;
                 return;
             }
         } else {
             if (voiceEntry.ParentVoice instanceof LinkedVoice) {
                 // Linked voice: set stem down:
-                voiceEntry.StemDirection = StemDirectionType.Down;
+                voiceEntry.WantedStemDirection = StemDirectionType.Down;
             } else {
                 // if this voiceEntry belongs to the mainVoice:
                 // check first that there are also more voices present:
                 if (voiceEntry.ParentSourceStaffEntry.VoiceEntries.length > 1) {
                     // as this voiceEntry belongs to the mainVoice: stem Up
-                    voiceEntry.StemDirection = StemDirectionType.Up;
+                    voiceEntry.WantedStemDirection = StemDirectionType.Up;
                 }
             }
         }
@@ -2309,7 +2309,7 @@ export abstract class MusicSheetCalculator {
         // ToDo: shift code to end of measure to only check once for all beams
         // check for a beam:
         // if this voice entry currently has no desired direction yet:
-        if (voiceEntry.StemDirection === StemDirectionType.Undefined &&
+        if (voiceEntry.WantedStemDirection === StemDirectionType.Undefined &&
             voiceEntry.Notes.length > 0) {
             const beam: Beam = voiceEntry.Notes[0].NoteBeam;
             if (beam !== undefined) {
@@ -2317,9 +2317,9 @@ export abstract class MusicSheetCalculator {
                 for (const note of beam.Notes) {
                     if (note.ParentVoiceEntry === voiceEntry) {
                         continue;
-                    } else if (note.ParentVoiceEntry.StemDirection !== StemDirectionType.Undefined) {
+                    } else if (note.ParentVoiceEntry.WantedStemDirection !== StemDirectionType.Undefined) {
                         // set the stem direction
-                        voiceEntry.StemDirection = note.ParentVoiceEntry.StemDirection;
+                        voiceEntry.WantedStemDirection = note.ParentVoiceEntry.WantedStemDirection;
                         break;
                     }
                 }

+ 1 - 2
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -271,7 +271,7 @@ export class VexFlowConverter {
         vfnote.x_shift = xShift;
 
         if (gve.parentVoiceEntry !== undefined) {
-            const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.StemDirection;
+            const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.WantedStemDirection;
             switch (wantedStemDirection) {
                 case(StemDirectionType.Up):
                     vfnote.setStemDirection(Vex.Flow.Stem.UP);
@@ -280,7 +280,6 @@ export class VexFlowConverter {
                     vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
                     break;
                 default:
-                    break;
             }
         }
 

+ 26 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -31,6 +31,7 @@ import {LinkedVoice} from "../../VoiceData/LinkedVoice";
 import {EngravingRules} from "../EngravingRules";
 import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
 import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
+import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
 
 export class VexFlowMeasure extends GraphicalMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -551,7 +552,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                     let autoStemBeam: boolean = true;
                     for (const gve of voiceEntries) {
                         if (gve.parentVoiceEntry.ParentVoice === psBeam.Notes[0].ParentVoiceEntry.ParentVoice) {
-                            autoStemBeam = gve.parentVoiceEntry.StemDirection === StemDirectionType.Undefined;
+                            autoStemBeam = gve.parentVoiceEntry.WantedStemDirection === StemDirectionType.Undefined;
                         }
                     }
 
@@ -710,6 +711,30 @@ export class VexFlowMeasure extends GraphicalMeasure {
         }
         this.createArticulations();
         this.createOrnaments();
+        this.setStemDirectionFromVexFlow();
+    }
+
+    /**
+     * Copy the stem directions chosen by VexFlow to the StemDirection variable of the graphical notes
+     */
+    private setStemDirectionFromVexFlow(): void {
+        //if StemDirection was not set then read out what VexFlow has chosen
+        for ( const vfStaffEntry of this.staffEntries ) {
+            for ( const gVoiceEntry of vfStaffEntry.graphicalVoiceEntries) {
+                for ( const gnote of gVoiceEntry.notes) {
+                    const vfStemDir: any = (gnote as VexFlowGraphicalNote).vfnote[0].getStemDirection();
+                    switch (vfStemDir) {
+                        case (Vex.Flow.Stem.UP):
+                            gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Up;
+                            break;
+                        case (Vex.Flow.Stem.DOWN):
+                            gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Down;
+                            break;
+                        default:
+                    }
+                }
+            }
+        }
     }
 
     /**

+ 3 - 1
src/MusicalScore/Graphical/VexFlow/VexFlowMusicSheetCalculator.ts

@@ -949,7 +949,9 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
     for (const graphicalMusicPage of this.graphicalMusicSheet.MusicPages) {
         for (const musicSystem of graphicalMusicPage.MusicSystems) {
             for (const staffLine of musicSystem.StaffLines) {
-                for (const gSlur of staffLine.GraphicalSlurs) {
+                // Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
+                const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
+                for (const gSlur of sortedGSlurs) {
                     // crossed slurs will be handled later:
                     if (gSlur.slur.isCrossed()) {
                         continue;

+ 12 - 2
src/MusicalScore/VoiceData/VoiceEntry.ts

@@ -45,6 +45,7 @@ export class VoiceEntry {
     private lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
     private arpeggiosNotesIndices: number[] = [];
     private ornamentContainer: OrnamentContainer;
+    private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
     private stemDirection: StemDirectionType = StemDirectionType.Undefined;
 
     public get ParentSourceStaffEntry(): SourceStaffEntry {
@@ -102,12 +103,21 @@ export class VoiceEntry {
         this.ornamentContainer = value;
     }
 
-    public get StemDirection(): StemDirectionType {
-        return this.stemDirection;
+    // WantedStemDirection provides the stem direction to VexFlow in case of more than 1 voice
+    // for optimal graphical appearance
+    public set WantedStemDirection(value: StemDirectionType) {
+        this.wantedStemDirection = value;
+    }
+    public get WantedStemDirection(): StemDirectionType {
+        return this.wantedStemDirection;
     }
+    // StemDirection holds the actual value of the stem
     public set StemDirection(value: StemDirectionType) {
         this.stemDirection = value;
     }
+    public get StemDirection(): StemDirectionType {
+        return this.stemDirection;
+    }
 
     public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
         switch (articulation) {