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