Browse Source

fix(autoBeamOption): don't beam notes of type quarter or longer in tuplets

this prevents a crash with autoBeam option turned on,
and samples that have e.g. tuplets of quarter or half notes in appearance,
which Vexflow can't beam, throwing an error.

read NoteType (e.g. Quarter) from XML
TODO: get note type interactively from current sheet model,
so the note could be changed at runtime

add sample bars 17 and 18 to OSMD function test auto beam
sschmid 5 years ago
parent
commit
c3b3b5add5

+ 33 - 5
src/MusicalScore/Graphical/VexFlow/VexFlowMeasure.ts

@@ -33,6 +33,7 @@ import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstru
 import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
 import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
 import {AutoBeamOptions} from "../../../OpenSheetMusicDisplay/OSMDOptions";
+import {NoteType} from "../../VoiceData";
 
 export class VexFlowMeasure extends GraphicalMeasure {
     constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
@@ -648,20 +649,45 @@ export class VexFlowMeasure extends GraphicalMeasure {
             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)) >= 0 || // don't beam quarter or longer 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) { // don't beam notes surrounded by quarter notes etc.
+                    if (consecutiveBeamableNotes.length >= 2) {
+                        // 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);
+                            notesToAutoBeam.push(note); // "flush" already beamed notes
                         }
                     }
-                    consecutiveBeamableNotes = [];
+                    consecutiveBeamableNotes = []; // reset notes to beam
                     continue;
                 }
 
                 // create beams for tuplets separately
                 const noteTuplet: Tuplet = gve.notes[0].sourceNote.NoteTuplet;
                 if (noteTuplet) {
+                    // check if there are quarter notes or longer in the tuplet, then don't beam.
+                    // (TODO: check for consecutiveBeamableNotes inside tuplets like for non-tuplet notes above
+                    //   e.g quarter eigth eighth -> beam the two eigth notes)
+                    let tupletContainsUnbeamableNote: boolean = false;
+                    for (const notes of noteTuplet.Notes) {
+                        for (const note of notes) {
+                            //const stavenote: StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
+                            //console.log("note " + note.ToString() + ", stavenote type: " + stavenote.getNoteType());
+                            if (note.NoteTypeXml >= NoteType.QUARTER || // quarter note or longer: don't beam
+                            // TODO: don't take Note (head) type from XML, but from current model,
+                            //   so that rendering can react dynamically to changes compared to the XML.
+                            //   however, taking the note length as fraction is tricky because of tuplets.
+                            //   a quarter in a triplet has length < quarter, but quarter note head, which Vexflow can't beam.
+                                note.ParentVoiceEntry.IsGrace ||
+                                note.isRest() && !EngravingRules.Rules.AutoBeamOptions.beam_rests) {
+                                tupletContainsUnbeamableNote = true;
+                                break;
+                            }
+                        }
+                        if (tupletContainsUnbeamableNote) {
+                            break;
+                        }
+                    }
+
                     if (currentTuplet === undefined) {
                         currentTuplet = noteTuplet;
                     } else {
@@ -673,7 +699,9 @@ export class VexFlowMeasure extends GraphicalMeasure {
                             currentTuplet = noteTuplet;
                         }
                     }
-                    tupletNotesToAutoBeam.push(<StaveNote>(gve as VexFlowVoiceEntry).vfStaveNote);
+                    if (!tupletContainsUnbeamableNote) {
+                        tupletNotesToAutoBeam.push(<StaveNote>(gve as VexFlowVoiceEntry).vfStaveNote);
+                    }
                     continue;
                 } else {
                     currentTuplet = undefined;

+ 166 - 1
test/data/OSMD_function_test_autobeam.musicxml

@@ -1570,7 +1570,172 @@
         <duration>1</duration>
         </forward>
       </measure>
-    <measure number="17" width="513.06">
+      <measure number="17" width="370.66">
+        <note default-x="152.87" default-y="-5.00">
+          <pitch>
+            <step>E</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>320</duration>
+          <voice>1</voice>
+          <type>quarter</type>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="start" bracket="yes"/>
+            </notations>
+          <lyric number="1" default-x="6.58" default-y="-57.93" relative-y="-30.00">
+            <syllabic>single</syllabic>
+            <text>DontBeamQuarterOrLongerTypes</text>
+            </lyric>
+          </note>
+        <note default-x="185.50" default-y="0.00">
+          <pitch>
+            <step>F</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>320</duration>
+          <voice>1</voice>
+          <type>quarter</type>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          </note>
+        <note default-x="218.13" default-y="0.00">
+          <pitch>
+            <step>F</step>
+            <alter>1</alter>
+            <octave>5</octave>
+            </pitch>
+          <duration>320</duration>
+          <voice>1</voice>
+          <type>quarter</type>
+          <accidental>sharp</accidental>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="stop"/>
+            </notations>
+          </note>
+        <note default-x="250.77" default-y="-5.00">
+          <pitch>
+            <step>E</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>160</duration>
+          <voice>1</voice>
+          <type>eighth</type>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="start" bracket="no"/>
+            </notations>
+          </note>
+        <note default-x="277.79" default-y="0.00">
+          <pitch>
+            <step>F</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>160</duration>
+          <voice>1</voice>
+          <type>eighth</type>
+          <accidental>natural</accidental>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          </note>
+        <note default-x="308.87" default-y="0.00">
+          <pitch>
+            <step>F</step>
+            <alter>1</alter>
+            <octave>5</octave>
+            </pitch>
+          <duration>160</duration>
+          <voice>1</voice>
+          <type>eighth</type>
+          <accidental>sharp</accidental>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="stop"/>
+            </notations>
+          </note>
+        <note>
+          <rest/>
+          <duration>480</duration>
+          <voice>1</voice>
+          <type>quarter</type>
+          </note>
+        </measure>
+      <measure number="18" width="126.36">
+        <note default-x="10.36" default-y="5.00">
+          <pitch>
+            <step>G</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>640</duration>
+          <voice>1</voice>
+          <type>half</type>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="start" bracket="yes"/>
+            </notations>
+          </note>
+        <note default-x="48.37" default-y="5.00">
+          <pitch>
+            <step>G</step>
+            <alter>1</alter>
+            <octave>5</octave>
+            </pitch>
+          <duration>640</duration>
+          <voice>1</voice>
+          <type>half</type>
+          <accidental>sharp</accidental>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          </note>
+        <note default-x="86.39" default-y="10.00">
+          <pitch>
+            <step>A</step>
+            <octave>5</octave>
+            </pitch>
+          <duration>640</duration>
+          <voice>1</voice>
+          <type>half</type>
+          <time-modification>
+            <actual-notes>3</actual-notes>
+            <normal-notes>2</normal-notes>
+            </time-modification>
+          <stem>down</stem>
+          <notations>
+            <tuplet type="stop"/>
+            </notations>
+          </note>
+        </measure>
+    <measure number="19" width="513.06">
       <note default-x="12.00" default-y="-50.00">
         <pitch>
           <step>C</step>