ソースを参照

fix(empty measures): not filled with whole rests by default anymore. new option fillEmptyMeasuresWithWholeRest (#625)

previously, measures with no notes given in the xml were always filled with whole rest notes.
this is not necessary e.g. for a piano piece where the right hand notes are moved to the bass clef for a while.

new option fillEmptyMeasuresWithWholeRest:
0 = don't fill (new default).
1 = fill, rests visible
2 = fill, rests invisible (can solve vexflow layouting issues, because the measure has rest notes, just invisible)

fix #625
sschmid 5 年 前
コミット
00522db3b2

+ 9 - 1
src/MusicalScore/Graphical/EngravingRules.ts

@@ -3,7 +3,7 @@ import { PagePlacementEnum } from "./GraphicalMusicPage";
 import * as log from "loglevel";
 import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
 import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
-import { AutoBeamOptions, AlignRestOption } from "../../OpenSheetMusicDisplay/OSMDOptions";
+import { AutoBeamOptions, AlignRestOption, FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSMDOptions";
 import { ColoringModes as ColoringMode } from "./DrawingParameters";
 import { Dictionary } from "typescript-collections";
 import { NoteEnum } from "../..";
@@ -181,6 +181,7 @@ export class EngravingRules {
     private durationScalingDistanceDict: {[_: number]: number; } = {};
 
     private alignRests: number; // 0 = false, 1 = true, 2 = auto
+    private fillEmptyMeasuresWithWholeRest: FillEmptyMeasuresWithWholeRests | number;
     private arpeggiosGoAcrossVoices: boolean;
     private renderArpeggios: boolean;
     private renderSlurs: boolean;
@@ -408,6 +409,7 @@ export class EngravingRules {
 
         // Render options (whether to render specific or invisible elements)
         this.alignRests = AlignRestOption.Never; // 0 = false, 1 = true, 2 = auto
+        this.fillEmptyMeasuresWithWholeRest = FillEmptyMeasuresWithWholeRests.No;
         this.arpeggiosGoAcrossVoices = false; // safe option, as otherwise arpeggios will always go across all voices in Vexflow, which is often unwanted
         this.renderArpeggios = true;
         this.renderSlurs = true;
@@ -1370,6 +1372,12 @@ export class EngravingRules {
     public set AlignRests(value: number) {
         this.alignRests = value;
     }
+    public get FillEmptyMeasuresWithWholeRest(): FillEmptyMeasuresWithWholeRests | number {
+        return this.fillEmptyMeasuresWithWholeRest;
+    }
+    public set FillEmptyMeasuresWithWholeRest(value: FillEmptyMeasuresWithWholeRests | number) {
+        this.fillEmptyMeasuresWithWholeRest = value;
+    }
     public get ArpeggiosGoAcrossVoices(): boolean {
         return this.arpeggiosGoAcrossVoices;
     }

+ 25 - 19
src/MusicalScore/Graphical/MusicSheetCalculator.ts

@@ -66,6 +66,7 @@ import { AbstractTempoExpression } from "../VoiceData/Expressions/AbstractTempoE
 import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
 import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
 import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
+import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay";
 
 /**
  * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
@@ -2083,25 +2084,30 @@ export abstract class MusicSheetCalculator {
             }
         }
         // if there are no staffEntries in this measure, create a rest for the whole measure:
-        if (measure.staffEntries.length === 0) {
-            const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(
-                new VerticalSourceStaffEntryContainer(measure.parentSourceMeasure,
-                                                      measure.parentSourceMeasure.AbsoluteTimestamp,
-                                                      measure.parentSourceMeasure.CompleteNumberOfStaves),
-                staff);
-            const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
-            const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined);
-            voiceEntry.Notes.push(note);
-            const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
-            measure.addGraphicalStaffEntry(graphicalStaffEntry);
-            graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
-            const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
-            graphicalStaffEntry.graphicalVoiceEntries.push(gve);
-            const graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(note,
-                                                                                               gve,
-                                                                                               new ClefInstruction(),
-                                                                                               OctaveEnum.NONE, undefined);
-            gve.notes.push(graphicalNote);
+        // check OSMDOptions.fillEmptyMeasuresWithWholeRest
+        if (EngravingRules.Rules.FillEmptyMeasuresWithWholeRest >= 1) { // fill measures with no notes given with whole rests, visible (1) or invisible (2)
+            if (measure.staffEntries.length === 0) {
+                const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(
+                    new VerticalSourceStaffEntryContainer(measure.parentSourceMeasure,
+                                                          measure.parentSourceMeasure.AbsoluteTimestamp,
+                                                          measure.parentSourceMeasure.CompleteNumberOfStaves),
+                    staff);
+                const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
+                const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined);
+                note.PrintObject = EngravingRules.Rules.FillEmptyMeasuresWithWholeRest === FillEmptyMeasuresWithWholeRests.YesVisible;
+                  // don't display whole rest that wasn't given in XML, only for layout/voice completion
+                voiceEntry.Notes.push(note);
+                const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
+                measure.addGraphicalStaffEntry(graphicalStaffEntry);
+                graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
+                const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
+                graphicalStaffEntry.graphicalVoiceEntries.push(gve);
+                const graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(note,
+                                                                                                   gve,
+                                                                                                   new ClefInstruction(),
+                                                                                                   OctaveEnum.NONE, undefined);
+                gve.notes.push(graphicalNote);
+            }
         }
         return measure;
     }

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

@@ -831,7 +831,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
 
     public graphicalMeasureCreatedCalculations(): void {
         let graceSlur: boolean;
-        let graceGVoiceEntriesBefore: GraphicalVoiceEntry[];
+        let graceGVoiceEntriesBefore: GraphicalVoiceEntry[] = [];
         for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
             graceSlur = false;
             graceGVoiceEntriesBefore = [];

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

@@ -117,7 +117,8 @@ export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
         }
       }
       if (voices.length === 0) {
-        log.info("Found a measure with no voices. Continuing anyway.", mvoices);
+        log.debug("Found a measure with no voices. Continuing anyway.", mvoices);
+        // no need to log this, measures with no voices/notes are fine. see OSMDOptions.fillEmptyMeasuresWithWholeRest
         continue;
       }
       // all voices that belong to one stave are collectively added to create a common context in VexFlow.

+ 8 - 0
src/OpenSheetMusicDisplay/OSMDOptions.ts

@@ -93,6 +93,8 @@ export interface IOSMDOptions {
     drawUpToMeasureNumber?: number;
     /** Only draw measure n to m, where n is the number you specify. */
     drawFromMeasureNumber?: number;
+    /** Whether to fill measures that don't have notes given in the XML with whole rests (visible = 1, invisible = 2, for layouting). Default No (0). */
+    fillEmptyMeasuresWithWholeRest?: FillEmptyMeasuresWithWholeRests | number;
     /** Whether to set the wanted stem direction by xml (default) or automatically. */
     setWantedStemDirectionByXml?: boolean;
     /** Whether tuplets are labeled with ratio (e.g. 5:2 instead of 5 for quintuplets). Default false. */
@@ -116,6 +118,12 @@ export enum AlignRestOption {
     Auto = 2
 }
 
+export enum FillEmptyMeasuresWithWholeRests {
+    No = 0,
+    YesVisible = 1,
+    YesInvisible = 2
+}
+
 /** Handles [[IOSMDOptions]], e.g. returning default options with OSMDOptionsStandard() */
 export class OSMDOptions {
     /** Returns the default options for OSMD.

+ 3 - 0
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -335,6 +335,9 @@ export class OpenSheetMusicDisplay {
         if (options.fingeringInsideStafflines !== undefined) {
             EngravingRules.Rules.FingeringInsideStafflines = options.fingeringInsideStafflines;
         }
+        if (options.fillEmptyMeasuresWithWholeRest !== undefined) {
+            EngravingRules.Rules.FillEmptyMeasuresWithWholeRest = options.fillEmptyMeasuresWithWholeRest;
+        }
         if (options.followCursor !== undefined) {
             this.FollowCursor = options.followCursor;
         }