Quellcode durchsuchen

feat(Rests): For rests encircled by beamed notes, if no display-step given, put the rest on the same y-height to avoid collisions.

mpat #66
sschmid vor 4 Jahren
Ursprung
Commit
a74d77b964

+ 3 - 3
src/Common/DataObjects/Pitch.ts

@@ -73,12 +73,12 @@ export class Pitch {
      *          ret[1] = the octave shift (not the new octave!)
      * @constructor
      */
-    public static CalculateTransposedHalfTone(pitch: Pitch, transpose: number): { value: number; overflow: number; } {
+    public static CalculateTransposedHalfTone(pitch: Pitch, transpose: number): { halftone: number; overflow: number; } {
         const newHalfTone: number = <number>pitch.fundamentalNote + pitch.AccidentalHalfTones + transpose;
         return Pitch.WrapAroundCheck(newHalfTone, 12);
     }
 
-    public static WrapAroundCheck(value: number, limit: number): { value: number; overflow: number; } {
+    public static WrapAroundCheck(value: number, limit: number): { halftone: number; overflow: number; } {
         let overflow: number = 0;
 
         while (value < 0) {
@@ -89,7 +89,7 @@ export class Pitch {
             value -= limit;
             overflow++; // the octave change
         }
-        return {overflow: overflow, value: value};
+        return {overflow: overflow, halftone: value};
     }
 
     //public static calcFrequency(pitch: Pitch): number;

+ 58 - 11
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -16,7 +16,7 @@ import {FontStyles} from "../../../Common/Enums/FontStyles";
 import {Fonts} from "../../../Common/Enums/Fonts";
 import {OutlineAndFillStyleEnum, OUTLINE_AND_FILL_STYLE_DICT} from "../DrawingEnums";
 import log from "loglevel";
-import { ArticulationEnum, StemDirectionType } from "../../VoiceData/VoiceEntry";
+import { ArticulationEnum, StemDirectionType, VoiceEntry } from "../../VoiceData/VoiceEntry";
 import { SystemLinePosition } from "../SystemLinePosition";
 import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
 import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
@@ -28,6 +28,7 @@ import StaveNote = Vex.Flow.StaveNote;
 import { ArpeggioType } from "../../VoiceData/Arpeggio";
 import { TabNote } from "../../VoiceData/TabNote";
 import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
+import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
 
 /**
  * Helper class, which contains static methods which actually convert
@@ -115,16 +116,17 @@ export class VexFlowConverter {
      * @param pitch
      * @returns {string[]}
      */
-    public static pitch(note: VexFlowGraphicalNote, pitch: Pitch): [string, string, ClefInstruction] {
+    public static pitch(pitch: Pitch, isRest: boolean, clef: ClefInstruction,
+                        notehead: Notehead = undefined): [string, string, ClefInstruction] {
         //FIXME: The octave seems to need a shift of three?
         //FIXME: Also rests seem to use different offsets depending on the clef.
         let fixmeOffset: number = 3;
-        if (note.sourceNote.isRest()) {
+        if (isRest) {
             fixmeOffset = 0;
-            if (note.Clef().ClefType === ClefEnum.F) {
+            if (clef.ClefType === ClefEnum.F) {
                 fixmeOffset = 2;
             }
-            if (note.Clef().ClefType === ClefEnum.C) {
+            if (clef.ClefType === ClefEnum.C) {
                 fixmeOffset = 2;
             }
             // TODO the pitch for rests will be the start position, for eights rests it will be the bottom point
@@ -132,13 +134,28 @@ export class VexFlowConverter {
         }
         const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
         const acc: string = Pitch.accidentalVexflow(pitch.Accidental);
-        const octave: number = pitch.Octave - note.Clef().OctaveOffset + fixmeOffset;
-        const notehead: Notehead = note.sourceNote.Notehead;
+        const octave: number = pitch.Octave - clef.OctaveOffset + fixmeOffset;
         let noteheadCode: string = "";
         if (notehead) {
             noteheadCode = this.NoteHeadCode(notehead);
         }
-        return [fund + "n/" + octave + noteheadCode, acc, note.Clef()];
+        return [fund + "n/" + octave + noteheadCode, acc, clef];
+    }
+
+    public static restToNotePitch(pitch: Pitch, clefType: ClefEnum): Pitch {
+        let octave: number = pitch.Octave;
+        // offsets see pitch()
+        switch (clefType) {
+            case ClefEnum.C:
+            case ClefEnum.F: {
+                octave += 2;
+                break;
+            }
+            case ClefEnum.G:
+            default:
+        }
+
+        return new Pitch(pitch.FundamentalNote, octave, AccidentalEnum.NONE);
     }
 
     /** returns the Vexflow code for a note head. Some are still unsupported, see Vexflow/tables.js */
@@ -212,11 +229,41 @@ export class VexFlowConverter {
             if (note.sourceNote.isRest()) {
                 isRest = true;
                 if (note.sourceNote.Pitch) {
-                    const restPitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
-                    keys = [restPitch[0]];
+                    const restVfPitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
+                    keys = [restVfPitch[0]];
                     break;
                 } else {
-                    keys = ["b/4"];
+                    keys = ["b/4"]; // default placement
+
+                    // pause rest encircled by two beamed notes: place rest just below previous note
+                    const pauseVoiceEntry: VoiceEntry = note.parentVoiceEntry?.parentVoiceEntry;
+                    if (pauseVoiceEntry) {
+                        const neighborGSEs: GraphicalStaffEntry[] = note.parentVoiceEntry?.parentStaffEntry.parentMeasure.staffEntries;
+                        let previousVoiceEntry: VoiceEntry, followingVoiceEntry: VoiceEntry;
+                        let pauseVEIndex: number = -1;
+                        for (let i: number = 0; i < neighborGSEs.length; i++) {
+                            if (neighborGSEs[i]?.graphicalVoiceEntries[0].parentVoiceEntry === pauseVoiceEntry) {
+                                pauseVEIndex = i;
+                                break;
+                            }
+                        }
+                        if (pauseVEIndex >= 1 && (neighborGSEs.length - 1) >= (pauseVEIndex + 1)) {
+                            previousVoiceEntry = neighborGSEs[pauseVEIndex - 1]?.graphicalVoiceEntries[0]?.parentVoiceEntry;
+                            followingVoiceEntry = neighborGSEs[pauseVEIndex + 1]?.graphicalVoiceEntries[0]?.parentVoiceEntry;
+                            if (previousVoiceEntry && followingVoiceEntry) {
+                                const previousNote: Note = previousVoiceEntry.Notes[0];
+                                const followingNote: Note = followingVoiceEntry.Notes[0];
+                                if (previousNote.NoteBeam?.Notes.includes(followingNote)) {
+                                    const previousNotePitch: Pitch = previousVoiceEntry.Notes.last().Pitch;
+                                    const clef: ClefInstruction = (note as VexFlowGraphicalNote).Clef();
+                                    const vfpitch: [string, string, ClefInstruction] = VexFlowConverter.pitch(
+                                        VexFlowConverter.restToNotePitch(previousNotePitch.getTransposedPitch(-2), clef.ClefType),
+                                        false, clef, undefined);
+                                    keys = [vfpitch[0]];
+                                }
+                            }
+                        }
+                    }
                 }
                 // TODO do collision checking, place rest e.g. either below staff (A3, for stem direction below voice) or above (C5)
                 // if it is a full measure rest:

+ 3 - 3
src/MusicalScore/Graphical/VexFlow/VexFlowGraphicalNote.ts

@@ -21,7 +21,7 @@ export class VexFlowGraphicalNote extends GraphicalNote {
         if (note.Pitch) {
             // TODO: Maybe shift to Transpose function when available
             const drawPitch: Pitch = note.isRest() ? note.Pitch : OctaveShift.getPitchFromOctaveShift(note.Pitch, octaveShift);
-            this.vfpitch = VexFlowConverter.pitch(this, drawPitch);
+            this.vfpitch = VexFlowConverter.pitch(drawPitch, note.isRest(), this.clef, this.sourceNote.Notehead);
             this.vfpitch[1] = undefined;
         }
     }
@@ -51,13 +51,13 @@ export class VexFlowGraphicalNote extends GraphicalNote {
         // revert octave shift, as the placement of the note is independent of octave brackets
         const drawPitch: Pitch = OctaveShift.getPitchFromOctaveShift(pitch, this.octaveShift);
         // recalculate the pitch, and this time don't ignore the accidental:
-        this.vfpitch = VexFlowConverter.pitch(this, drawPitch);
+        this.vfpitch = VexFlowConverter.pitch(drawPitch, this.sourceNote.isRest(), this.clef, this.sourceNote.Notehead);
         //}
     }
     public Transpose(keyInstruction: KeyInstruction, activeClef: ClefInstruction, halfTones: number, octaveEnum: OctaveEnum): Pitch {
         const tranposedPitch: Pitch = super.Transpose(keyInstruction, activeClef, halfTones, octaveEnum);
         const drawPitch: Pitch = OctaveShift.getPitchFromOctaveShift(tranposedPitch, this.octaveShift);
-        this.vfpitch = VexFlowConverter.pitch(this, drawPitch);
+        this.vfpitch = VexFlowConverter.pitch(drawPitch, this.sourceNote.isRest(), this.clef, this.sourceNote.Notehead);
         this.vfpitch[1] = undefined;
         return drawPitch;
     }

+ 4 - 4
test/Common/DataObjects/Pitch_Test.ts

@@ -3,16 +3,16 @@ import { Pitch, NoteEnum, AccidentalEnum } from "../../../src/Common/DataObjects
 describe("Pitch Unit Tests:", () => {
     describe("transpose Pitch", () => {
         const pitch: Pitch = new Pitch(NoteEnum.A, 1, AccidentalEnum.NONE);
-        const transposedFundamentalAndOctave: {value: number; overflow: number; } =
+        const transposedFundamentalAndOctave: {halftone: number; overflow: number; } =
           Pitch.CalculateTransposedHalfTone(pitch, 12);
-        const higherTransposedFundamentalAndOctave: {value: number; overflow: number; } =
+        const higherTransposedFundamentalAndOctave: {halftone: number; overflow: number; } =
           Pitch.CalculateTransposedHalfTone(pitch, 26);
 
         it("should be 1 octave higher and same fundamental", (done: MochaDone) => {
             chai.expect(transposedFundamentalAndOctave.overflow).to.equal(1);
-            chai.expect(transposedFundamentalAndOctave.value).to.equal(pitch.FundamentalNote);
+            chai.expect(transposedFundamentalAndOctave.halftone).to.equal(pitch.FundamentalNote);
             chai.expect(higherTransposedFundamentalAndOctave.overflow).to.equal(2);
-            chai.expect(higherTransposedFundamentalAndOctave.value).to.equal(pitch.FundamentalNote + 2);
+            chai.expect(higherTransposedFundamentalAndOctave.halftone).to.equal(pitch.FundamentalNote + 2);
             done();
         });
     });