VexFlow - 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 = 4;
    const group_spacing_tab = 0;

    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);
      const spacing = (is_stavenote ?  group_spacing_stave : group_spacing_tab);

      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.attrs.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.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() {
    if (this.grace_notes.length > 1) {
      const beam = new Beam(this.grace_notes);

      beam.render_options.beam_width = 3;
      beam.render_options.partial_beam_length = 4;

      this.beam = beam;
    }

    return this;
  }

  setNote(note) {
    this.note = note;
  }
  setWidth(width) {
    this.width = width;
  }
  getWidth() {
    return this.width;
  }
  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.");
    }

    const that = this;
    function alignGraceNotesWithNote(grace_notes, note) {

Shift over the tick contexts of each note So that th aligned with the note

      const tickContext = note.getTickContext();
      const extraPx = tickContext.getExtraPx();
      const x = tickContext.getX()
        - extraPx.left
        - extraPx.extraLeft
        + that.getSpacingFromNextModifier();

      grace_notes.forEach(graceNote => {
        const tick_context = graceNote.getTickContext();
        const x_offset = tick_context.getX();
        graceNote.setStave(note.stave);
        tick_context.setX(x + x_offset);
      });
    }

    alignGraceNotesWithNote(this.grace_notes, note, this.width);

Draw notes

    this.grace_notes.forEach(graceNote => {
      graceNote.setContext(this.context).draw();
    });

Draw beam

    if (this.beam) {
      this.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();
    }
  }
}
h