Prechádzať zdrojové kódy

fix(autoBeam): don't beam over half notes or beat, prevent separate beams connecting

previously separately beamed notes could be put into one big beam,
now there is a collection of separate beams.

demo: function test auto beam: add 16th note variations as last bar

fix #574
Simon Schmid 5 rokov pred
rodič
commit
da464aa8be

+ 35 - 6
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -640,24 +640,47 @@ export class VexFlowMeasure extends GraphicalMeasure {
      * @param beamedNotes notes that will not be autobeamed (usually because they are already beamed)
      */
     private autoBeamNotes(beamedNotes: StemmableNote[]): void {
-        const notesToAutoBeam: StemmableNote[] = [];
+        let notesToAutoBeam: StemmableNote[] = [];
         let consecutiveBeamableNotes: StemmableNote[] = [];
         let currentTuplet: Tuplet;
         let tupletNotesToAutoBeam: StaveNote[] = [];
         this.autoTupletVfBeams = [];
+        const separateAutoBeams: StemmableNote[][] = []; // a set of separate beams, each having a set of notes (StemmableNote[]).
+        this.autoVfBeams = []; // final Vex.Flow.Beams will be pushed/collected into this
+        // let timeSignature: Fraction = this.parentSourceMeasure.Duration; // doesn't always give full signature, sometimes 1/1 in a 4/4 signature
+        const timeSignature: Fraction = this.parentSourceMeasure.ActiveTimeSignature;
+        /*if (this.parentSourceMeasure.FirstInstructionsStaffEntries[0]) {
+            for (const instruction of this.parentSourceMeasure.FirstInstructionsStaffEntries[0].Instructions) {
+                if (instruction instanceof RhythmInstruction) { // there is not always a RhythmInstruction, but this could be useful some time.
+                    timeSignature = (instruction as RhythmInstruction).Rhythm;
+                }
+            }
+        }*/
+
         for (const staffEntry of this.staffEntries) {
             for (const gve of staffEntry.graphicalVoiceEntries) {
                 const vfStaveNote: StaveNote = <StaveNote> (gve as VexFlowVoiceEntry).vfStaveNote;
-                if (gve.parentVoiceEntry.IsGrace || // don't beam grace notes
-                    gve.notes[0].graphicalNoteLength.CompareTo(new Fraction(1, 4)) === 1 || // don't beam quarter or longer notes
-                    beamedNotes.contains(vfStaveNote)) { // don't beam already beamed notes
-                    if (consecutiveBeamableNotes.length >= 2) {
+                const gNote: GraphicalNote = gve.notes[0]; // TODO check for all notes within the graphical voice entry
+                const isOnBeat: boolean = staffEntry.relInMeasureTimestamp.isOnBeat(timeSignature);
+                const haveTwoOrMoreNotesToBeamAlready: boolean = consecutiveBeamableNotes.length >= 2;
+                const unbeamableNote: boolean =
+                    gve.parentVoiceEntry.IsGrace || // don't beam grace notes
+                    gNote.graphicalNoteLength.CompareTo(new Fraction(1, 4)) === 1 || // don't beam quarter or longer notes
+                    beamedNotes.contains(vfStaveNote);
+                if (unbeamableNote || isOnBeat) { // end beam
+                    if (haveTwoOrMoreNotesToBeamAlready) {
                         // if we already have at least 2 notes to beam, beam them. don't beam notes surrounded by quarter notes etc.
                         for (const note of consecutiveBeamableNotes) {
                             notesToAutoBeam.push(note); // "flush" already beamed notes
                         }
+                        separateAutoBeams.push(notesToAutoBeam.slice()); // copy array, otherwise this beam gets the next notes of next beam later
+                        notesToAutoBeam = []; // reset notesToAutoBeam, otherwise the next beam includes the previous beam's notes too
                     }
                     consecutiveBeamableNotes = []; // reset notes to beam
+                    if (!unbeamableNote) { // beamable note, probably new beat, take into next beam
+                        consecutiveBeamableNotes.push(vfStaveNote); // start new beam on beat
+                    }
+
                     continue;
                 }
 
@@ -717,6 +740,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
             for (const note of consecutiveBeamableNotes) {
                 notesToAutoBeam.push(note);
             }
+            separateAutoBeams.push(notesToAutoBeam);
         }
 
         // create options for generateBeams
@@ -734,7 +758,12 @@ export class VexFlowMeasure extends GraphicalMeasure {
             generateBeamOptions.groups = groups;
         }
 
-        this.autoVfBeams = Vex.Flow.Beam.generateBeams(notesToAutoBeam, generateBeamOptions);
+        for (const notesForSeparateAutoBeam of separateAutoBeams) {
+            const newBeams: Vex.Flow.Beam[] = Vex.Flow.Beam.generateBeams(notesForSeparateAutoBeam, generateBeamOptions);
+            for (const beam of newBeams) {
+                this.autoVfBeams.push(beam);
+            }
+        }
     }
 
     /**

+ 86 - 0
test/data/OSMD_function_test_autobeam.musicxml

@@ -1826,6 +1826,92 @@
         <stem>down</stem>
 
         </note>
+      </measure>
+      <measure number="20" width="547.49">
+      <note default-x="79.33" default-y="-30.00">
+        <pitch>
+          <step>G</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        <lyric number="1" default-x="6.58" default-y="-49.85" relative-y="-30.00">
+          <syllabic>single</syllabic>
+          <text>SeparateBeams16th</text>
+          </lyric>
+        </note>
+      <note default-x="123.33" default-y="-25.00">
+        <pitch>
+          <step>A</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>up</stem>
+        </note>
+      <note default-x="166.96" default-y="-20.00">
+        <pitch>
+          <step>B</step>
+          <octave>4</octave>
+          </pitch>
+        <duration>960</duration>
+        <voice>1</voice>
+        <type>half</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="290.51" default-y="-15.00">
+        <pitch>
+          <step>C</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="334.51" default-y="-10.00">
+        <pitch>
+          <step>D</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="378.50" default-y="-5.00">
+        <pitch>
+          <step>E</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="422.50" default-y="0.00">
+        <pitch>
+          <step>F</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>240</duration>
+        <voice>1</voice>
+        <type>eighth</type>
+        <stem>down</stem>
+        </note>
+      <note default-x="492.89" default-y="5.00">
+        <pitch>
+          <step>G</step>
+          <octave>5</octave>
+          </pitch>
+        <duration>120</duration>
+        <voice>1</voice>
+        <type>16th</type>
+        <stem>down</stem>
+        </note>
       <barline location="right">
         <bar-style>light-heavy</bar-style>
         </barline>