|
@@ -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();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|