Prechádzať zdrojové kódy

merge osmd-public 1.5.3: grace note+lyrics margin, tuplet fix

sschmidTU 2 rokov pred
rodič
commit
b2a74c7162

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "osmd-extended",
-  "version": "1.5.2",
+  "version": "1.5.3",
   "description": "Private / sponsor exclusive OSMD mirror/audio player.",
   "main": "build/opensheetmusicdisplay.min.js",
   "types": "build/dist/src/index.d.ts",

+ 5 - 0
src/MusicalScore/Graphical/EngravingRules.ts

@@ -99,6 +99,7 @@ export class EngravingRules {
     public SetWantedStemDirectionByXml: boolean;
     public GraceNoteScalingFactor: number;
     public GraceNoteXOffset: number;
+    public GraceNoteGroupXMargin: number;
     public WedgeOpeningLength: number;
     public WedgeMeasureEndOpeningLength: number;
     public WedgeMeasureBeginOpeningLength: number;
@@ -173,6 +174,7 @@ export class EngravingRules {
     public LyricsAlignmentStandard: TextAlignmentEnum;
     public LyricsHeight: number;
     public LyricsYOffsetToStaffHeight: number;
+    public LyricsYMarginToBottomLine: number;
     public VerticalBetweenLyricsDistance: number;
     public HorizontalBetweenLyricsDistance: number;
     public BetweenSyllableMaximumDistance: number;
@@ -483,6 +485,8 @@ export class EngravingRules {
         // GraceNote Variables
         this.GraceNoteScalingFactor = 0.6;
         this.GraceNoteXOffset = 0.2;
+        this.GraceNoteGroupXMargin = 0.0; // More than 0 leads to too much space in most cases.
+        //  see test_end_clef_measure. only potential 'tight' case: test_graceslash_simple
 
         // Wedge Variables
         this.WedgeOpeningLength = 1.2;
@@ -583,6 +587,7 @@ export class EngravingRules {
         this.LyricsAlignmentStandard = TextAlignmentEnum.LeftBottom; // CenterBottom and LeftBottom tested, spacing-optimized
         this.LyricsHeight = 2.0; // actually size of lyrics
         this.LyricsYOffsetToStaffHeight = 0.0; // distance between lyrics and staff. could partly be even lower/dynamic
+        this.LyricsYMarginToBottomLine = 0.2;
         this.VerticalBetweenLyricsDistance = 0.5;
         this.HorizontalBetweenLyricsDistance = 0.2;
         this.BetweenSyllableMaximumDistance = 10.0;

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

@@ -578,7 +578,7 @@ export abstract class MusicSheetCalculator {
 
                     // check BottomLine in this range and take the maximum between the two values
                     const bottomLineMax: number = skyBottomLineCalculator.getBottomLineMaxInRange(minMarginLeft, maxMarginRight);
-                    lyricsStartYPosition = Math.max(lyricsStartYPosition, bottomLineMax);
+                    lyricsStartYPosition = Math.max(lyricsStartYPosition, bottomLineMax + this.rules.LyricsYMarginToBottomLine);
                 }
             }
         }

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

@@ -1216,6 +1216,7 @@ export class VexFlowMeasure extends GraphicalMeasure {
                         graceNotes.push(vfStaveNote);
                     }
                     const graceNoteGroup: VF.GraceNoteGroup = new VF.GraceNoteGroup(graceNotes, graceSlur);
+                    (graceNoteGroup as any).spacing = this.rules.GraceNoteGroupXMargin * 10;
                     ((gve as VexFlowVoiceEntry).vfStaveNote as StaveNote).addModifier(0, graceNoteGroup);
                     graceGVoiceEntriesBefore = [];
                 }

+ 1 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -35,7 +35,7 @@ import { DynamicsCalculator } from "../MusicalScore/ScoreIO/MusicSymbolModules/D
  * After the constructor, use load() and render() to load and render a MusicXML file.
  */
 export class OpenSheetMusicDisplay {
-    private version: string = "1.5.2-audio-extended"; // getter: this.Version
+    private version: string = "1.5.3-audio-extended"; // getter: this.Version
     // at release, bump version and change to -release, afterwards to -dev again
 
     /**

+ 4 - 0
src/VexFlowPatch/readme.txt

@@ -21,6 +21,10 @@ formatter.js (custom addition, unnecessary in vexflow 4):
 comment out unnecessary error thrown, which prevents the fix to
 layouting improvements with whole measure rests and e.g. 12/8 rhythm in #1187.
 
+gracenotegroup.js (custom addition, needs check if necessary in vexflow 4):
+check for gracenotegroup.spacing set, to allow e.g. spacing = 0 by default.
+(with previous default 4, spacing is way too large unnecessarily, in most cases)
+
 keysignature.js (merged vexflow 4):
 open group to get SVG group+class for key signature
 

+ 193 - 0
src/VexFlowPatch/src/gracenotegroup.js

@@ -0,0 +1,193 @@
+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
+//
+// ## Description
+//
+// This file implements `GraceNoteGroup` which is used to format and
+// render grace notes.
+
+import { Vex } from './vex';
+import { Flow } from './tables';
+import { Modifier } from './modifier';
+import { Formatter } from './formatter';
+import { Voice } from './voice';
+import { Beam } from './beam';
+import { StaveTie } from './stavetie';
+import { TabTie } from './tabtie';
+import { StaveNote } from './stavenote';
+
+// To enable logging for this class. Set `Vex.Flow.GraceNoteGroup.DEBUG` to `true`.
+function L(...args) { if (GraceNoteGroup.DEBUG) Vex.L('Vex.Flow.GraceNoteGroup', args); }
+
+export class GraceNoteGroup extends Modifier {
+  static get CATEGORY() { return 'gracenotegroups'; }
+
+  // Arrange groups inside a `ModifierContext`
+  static format(gracenote_groups, state) {
+    const group_spacing_stave = 0; // overwritten later in `spacing` (vexflowpatch)
+    const group_spacing_tab = 0;
+    // vexflow calls these spacing, though they seem more like margins to be precise. -> EngravingRules.GraceNoteGroupXMargin
+
+    if (!gracenote_groups || gracenote_groups.length === 0) return false;
+
+    const group_list = [];
+    let prev_note = null;
+    let shiftL = 0;
+
+    for (let i = 0; i < gracenote_groups.length; ++i) {
+      const gracenote_group = gracenote_groups[i];
+      const note = gracenote_group.getNote();
+      const is_stavenote = (note.getCategory() === StaveNote.CATEGORY);
+      let spacing = (is_stavenote ? group_spacing_stave : group_spacing_tab);
+      // vexflowpatch: allow spacing to be set externally (e.g. to 0)
+      if (is_stavenote && gracenote_group.spacing !== null && gracenote_group.spacing !== undefined) {
+        spacing = gracenote_group.spacing;
+      }
+
+      if (is_stavenote && note !== prev_note) {
+        // Iterate through all notes to get the displaced pixels
+        for (let n = 0; n < note.keys.length; ++n) {
+          const props_tmp = note.getKeyProps()[n];
+          shiftL = (props_tmp.displaced ? note.getExtraLeftPx() : shiftL);
+        }
+        prev_note = note;
+      }
+
+      group_list.push({ shift: shiftL, gracenote_group, spacing });
+    }
+
+    // If first note left shift in case it is displaced
+    let group_shift = group_list[0].shift;
+    let formatWidth;
+    for (let i = 0; i < group_list.length; ++i) {
+      const gracenote_group = group_list[i].gracenote_group;
+      gracenote_group.preFormat();
+      formatWidth = gracenote_group.getWidth() + group_list[i].spacing;
+      group_shift = Math.max(formatWidth, group_shift);
+    }
+
+    for (let i = 0; i < group_list.length; ++i) {
+      const gracenote_group = group_list[i].gracenote_group;
+      formatWidth = gracenote_group.getWidth() + group_list[i].spacing;
+      gracenote_group.setSpacingFromNextModifier(group_shift - Math.min(formatWidth, group_shift));
+    }
+
+    state.left_shift += group_shift;
+    return true;
+  }
+
+  // ## Prototype Methods
+  //
+  // `GraceNoteGroup` inherits from `Modifier` and is placed inside a
+  // `ModifierContext`.
+  constructor(grace_notes, show_slur) {
+    super();
+    this.setAttribute('type', 'GraceNoteGroup');
+
+    this.note = null;
+    this.index = null;
+    this.position = Modifier.Position.LEFT;
+    this.grace_notes = grace_notes;
+    this.width = 0;
+
+    this.preFormatted = false;
+
+    this.show_slur = show_slur;
+    this.slur = null;
+
+    this.formatter = new Formatter();
+    this.voice = new Voice({
+      num_beats: 4,
+      beat_value: 4,
+      resolution: Flow.RESOLUTION,
+    }).setStrict(false);
+
+    this.render_options = {
+      slur_y_shift: 0,
+    };
+
+    this.beams = [];
+
+    this.voice.addTickables(this.grace_notes);
+
+    return this;
+  }
+
+  getCategory() { return GraceNoteGroup.CATEGORY; }
+
+  preFormat() {
+    if (this.preFormatted) return;
+
+    this.formatter.joinVoices([this.voice]).format([this.voice], 0);
+    this.setWidth(this.formatter.getMinTotalWidth());
+    this.preFormatted = true;
+  }
+
+  beamNotes(grace_notes) {
+    grace_notes = grace_notes || this.grace_notes;
+    if (grace_notes.length > 1) {
+      const beam = new Beam(grace_notes);
+
+      beam.render_options.beam_width = 3;
+      beam.render_options.partial_beam_length = 4;
+
+      this.beams.push(beam);
+    }
+
+    return this;
+  }
+
+  setNote(note) {
+    this.note = note;
+  }
+  setWidth(width) {
+    this.width = width;
+  }
+  getWidth() {
+    return this.width;
+  }
+  getGraceNotes() {
+    return this.grace_notes;
+  }
+  draw() {
+    this.checkContext();
+
+    const note = this.getNote();
+
+    L('Drawing grace note group for:', note);
+
+    if (!(note && (this.index !== null))) {
+      throw new Vex.RuntimeError('NoAttachedNote',
+        "Can't draw grace note without a parent note and parent note index.");
+    }
+
+    this.setRendered();
+    this.alignSubNotesWithNote(this.getGraceNotes(), note); // Modifier function
+
+    // Draw notes
+    this.grace_notes.forEach(graceNote => {
+      graceNote.setContext(this.context).draw();
+    });
+
+    // Draw beam
+    this.beams.forEach(beam => {
+      beam.setContext(this.context).draw();
+    });
+
+    if (this.show_slur) {
+      // Create and draw slur
+      const is_stavenote = (this.getNote().getCategory() === StaveNote.CATEGORY);
+      const TieClass = (is_stavenote ? StaveTie : TabTie);
+
+      this.slur = new TieClass({
+        last_note: this.grace_notes[0],
+        first_note: note,
+        first_indices: [0],
+        last_indices: [0],
+      });
+
+      this.slur.render_options.cp2 = 12;
+      this.slur.render_options.y_shift = (is_stavenote ? 7 : 5) + this.render_options.slur_y_shift;
+      this.slur.setContext(this.context).draw();
+    }
+  }
+}