VexFlow - Copyright (c) Mohit Muthanna 2010.
This file implements an abstract interface for notes and chords that are rendered on a stave. Notes have some common properties: All of them have a value (e.g., pitch, fret, etc.) and a duration (quarter, half, etc.)
Some notes have stems, heads, dots, etc. Most notational elements that surround a note are called modifiers, and every note has an associated array of them. All notes also have a rendering context and belong to a stave.
import { Vex } from './vex';
import { Flow } from './tables';
import { Tickable } from './tickable';
export class Note extends Tickable {
  static get CATEGORY() { return 'note'; }Debug helper. Displays various note metrics for the given note.
  static plotMetrics(ctx, note, yPos) {
    const metrics = note.getMetrics();
    const xStart = note.getAbsoluteX() - metrics.modLeftPx - metrics.leftDisplacedHeadPx;
    const xPre1 = note.getAbsoluteX() - metrics.leftDisplacedHeadPx;
    const xAbs = note.getAbsoluteX();
    const xPost1 = note.getAbsoluteX() + metrics.notePx;
    const xPost2 = note.getAbsoluteX() + metrics.notePx + metrics.rightDisplacedHeadPx;
    const xEnd = note.getAbsoluteX()
      + metrics.notePx
      + metrics.rightDisplacedHeadPx
      + metrics.modRightPx;
    const xFreedomRight = xEnd + (note.getFormatterMetrics().freedom.right || 0);
    const xWidth = xEnd - xStart;
    ctx.save();
    ctx.setFont('Arial', 8, '');
    ctx.fillText(Math.round(xWidth) + 'px', xStart + note.getXShift(), yPos);
    const y = (yPos + 7);
    function stroke(x1, x2, color, yy = y) {
      ctx.beginPath();
      ctx.setStrokeStyle(color);
      ctx.setFillStyle(color);
      ctx.setLineWidth(3);
      ctx.moveTo(x1 + note.getXShift(), yy);
      ctx.lineTo(x2 + note.getXShift(), yy);
      ctx.stroke();
    }
    stroke(xStart, xPre1, 'red');
    stroke(xPre1, xAbs, '#999');
    stroke(xAbs, xPost1, 'green');
    stroke(xPost1, xPost2, '#999');
    stroke(xPost2, xEnd, 'red');
    stroke(xEnd, xFreedomRight, '#DD0');
    stroke(xStart - note.getXShift(), xStart, '#BBB'); // Shift
    Vex.drawDot(ctx, xAbs + note.getXShift(), y, 'blue');
    const formatterMetrics = note.getFormatterMetrics();
    if (formatterMetrics.iterations > 0) {
      const spaceDeviation = formatterMetrics.space.deviation;
      const prefix = spaceDeviation >= 0 ? '+' : '';
      ctx.setFillStyle('red');
      ctx.fillText(prefix + Math.round(spaceDeviation),
        xAbs + note.getXShift(), yPos - 10);
    }
    ctx.restore();
  }
  static parseDuration(durationString) {
    if (typeof (durationString) !== 'string') { return null; }
    const regexp = /(\d*\/?\d+|[a-z])(d*)([nrhms]|$)/;
    const result = regexp.exec(durationString);
    if (!result) { return null; }
    const duration = result[1];
    const dots = result[2].length;
    const type = result[3] || 'n';
    return { duration, dots, type };
  }
  static parseNoteStruct(noteStruct) {
    const durationString = noteStruct.duration;
    const customTypes = [];Preserve backwards-compatibility
    const durationProps = Note.parseDuration(durationString);
    if (!durationProps) { return null; }If specified type is invalid, return null
    let type = noteStruct.type;
    if (type && !Flow.getGlyphProps.validTypes[type]) { return null; }If no type specified, check duration or custom types
    if (!type) {
      type = durationProps.type || 'n';If we have keys, try and check if we’ve got a custom glyph
      if (noteStruct.keys !== undefined) {
        noteStruct.keys.forEach((k, i) => {
          const result = k.split('/');We have a custom glyph specified after the note eg. /X2
          customTypes[i] = (result && result.length === 3) ? result[2] : type;
        });
      }
    }Calculate the tick duration of the note
    let ticks = Flow.durationToTicks(durationProps.duration);
    if (ticks == null) { return null; }Are there any dots?
    const dots = noteStruct.dots ? noteStruct.dots : durationProps.dots;
    if (typeof (dots) !== 'number') { return null; }Add ticks as necessary depending on the numbr of dots
    let currentTicks = ticks;
    for (let i = 0; i < dots; i++) {
      if (currentTicks <= 1) return null;
      currentTicks = currentTicks / 2;
      ticks += currentTicks;
    }
    return {
      duration: durationProps.duration,
      type,
      customTypes,
      dots,
      ticks,
    };
  }Every note is a tickable, i.e., it can be mutated by the Formatter class for
positioning and layout.
To create a new note you need to provide a noteStruct, which consists
of the following fields:
type: The note type (e.g., r for rest, s for slash notes, etc.)
dots: The number of dots, which affects the duration.
duration: The time length (e.g., q for quarter, h for half, 8 for eighth etc.)
The range of values for these parameters are available in src/tables.js.
  constructor(noteStruct) {
    super();
    this.setAttribute('type', 'Note');
    if (!noteStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', 'Note must have valid initialization data to identify duration and type.'
      );
    }Parse noteStruct and get note properties.
    const initStruct = Note.parseNoteStruct(noteStruct);
    if (!initStruct) {
      throw new Vex.RuntimeError(
        'BadArguments', `Invalid note initialization object: ${JSON.stringify(noteStruct)}`
      );
    }Set note properties from parameters.
    this.duration = initStruct.duration;
    this.dots = initStruct.dots;
    this.noteType = initStruct.type;
    this.customTypes = initStruct.customTypes;
    if (noteStruct.duration_override) {Custom duration
      this.setDuration(noteStruct.duration_override);
    } else {Default duration
      this.setIntrinsicTicks(initStruct.ticks);
    }
    this.modifiers = [];Get the glyph code for this note from the font.
    this.glyph = Flow.getGlyphProps(this.duration, this.noteType);
    this.customGlyphs = this.customTypes.map(t => Flow.getGlyphProps(this.duration, t));
    if (this.positions && (typeof (this.positions) !== 'object' || !this.positions.length)) {
      throw new Vex.RuntimeError('BadArguments', 'Note keys must be array type.');
    }Note to play for audio players.
    this.playNote = null;Positioning contexts used by the Formatter.
    this.tickContext = null;    // The current tick context.
    this.modifierContext = null;
    this.ignore_ticks = false;Positioning variables
    this.width = 0;             // Width in pixels calculated after preFormat
    this.leftDisplacedHeadPx = 0;       // Extra room on left for displaced note head
    this.rightDisplacedHeadPx = 0;      // Extra room on right for displaced note head
    this.x_shift = 0;           // X shift from tick context X
    this.voice = null;          // The voice that this note is in
    this.preFormatted = false;  // Is this note preFormatted?
    this.ys = [];               // list of y coordinates for each notewe need to hold on to these for ties and beams.
    if (noteStruct.align_center) {
      this.setCenterAlignment(noteStruct.align_center);
    }The render surface.
    this.stave = null;
    this.render_options = {
      annotation_spacing: 5,
    };
  }Get and set the play note, which is arbitrary data that can be used by an audio player.
  getPlayNote() { return this.playNote; }
  setPlayNote(note) { this.playNote = note; return this; }Don’t play notes by default, call them rests. This is also used by things like beams and dots for positioning.
  isRest() { return false; }TODO(0xfe): Why is this method here?
  addStroke(index, stroke) {
    stroke.setNote(this);
    stroke.setIndex(index);
    this.modifiers.push(stroke);
    this.setPreFormatted(false);
    return this;
  }Get and set the target stave.
  getStave() { return this.stave; }
  setStave(stave) {
    this.stave = stave;
    this.setYs([stave.getYForLine(0)]); // Update Y values if the stave is changed.
    this.context = this.stave.context;
    return this;
  }Note is not really a modifier, but is used in
a ModifierContext.
  getCategory() { return Note.CATEGORY; }Set the rendering context for the note.
  setContext(context) { this.context = context; return this; }Get and set spacing to the left and right of the notes.
  getLeftDisplacedHeadPx() { return this.leftDisplacedHeadPx; }
  getRightDisplacedHeadPx() { return this.rightDisplacedHeadPx; }
  setLeftDisplacedHeadPx(x) { this.leftDisplacedHeadPx = x; return this; }
  setRightDisplacedHeadPx(x) { this.rightDisplacedHeadPx = x; return this; }Returns true if this note has no duration (e.g., bar notes, spacers, etc.)
  shouldIgnoreTicks() { return this.ignore_ticks; }Get the stave line number for the note.
  getLineNumber() { return 0; }Get the stave line number for rest.
  getLineForRest() { return 0; }Get the glyph associated with this note.
  getGlyph() { return this.glyph; }
  getGlyphWidth() {TODO: FIXME (multiple potential values for this.glyph)
    if (this.glyph) {
      if (this.glyph.getMetrics) {
        return this.glyph.getMetrics().width;
      } else if (this.glyph.getWidth) {
        return this.glyph.getWidth(this.render_options.glyph_font_scale);
      }
    }
    return 0;
  }Set and get Y positions for this note. Each Y value is associated with an individual pitch/key within the note/chord.
  setYs(ys) { this.ys = ys; return this; }
  getYs() {
    if (this.ys.length === 0) {
      throw new Vex.RERR('NoYValues', 'No Y-values calculated for this note.');
    }
    return this.ys;
  }Get the Y position of the space above the stave onto which text can be rendered.
  getYForTopText(text_line) {
    if (!this.stave) {
      throw new Vex.RERR('NoStave', 'No stave attached to this note.');
    }
    return this.stave.getYForTopText(text_line);
  }Get a BoundingBox for this note.
  getBoundingBox() { return null; }Returns the voice that this note belongs in.
  getVoice() {
    if (!this.voice) throw new Vex.RERR('NoVoice', 'Note has no voice.');
    return this.voice;
  }Attach this note to voice.
  setVoice(voice) {
    this.voice = voice;
    this.preFormatted = false;
    return this;
  }Get and set the TickContext for this note.
  getTickContext() { return this.tickContext; }
  setTickContext(tc) {
    this.tickContext = tc;
    this.preFormatted = false;
    return this;
  }Accessors for the note type.
  getDuration() { return this.duration; }
  isDotted() { return (this.dots > 0); }
  hasStem() { return false; }
  getDots() { return this.dots; }
  getNoteType() { return this.noteType; }
  setBeam() { return this; } // ignore parametersAttach this note to a modifier context.
  setModifierContext(mc) { this.modifierContext = mc; return this; }Attach a modifier to this note.
  addModifier(modifier, index = 0) {
    modifier.setNote(this);
    modifier.setIndex(index);
    this.modifiers.push(modifier);
    this.setPreFormatted(false);
    return this;
  }Get the coordinates for where modifiers begin.
  getModifierStartXY() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call GetModifierStartXY on an unformatted note");
    }
    return {
      x: this.getAbsoluteX(),
      y: this.ys[0],
    };
  }Get bounds and metrics for this note.
Returns a struct with fields:
width: The total width of the note (including modifiers.)
notePx: The width of the note head only.
left_shift: The horizontal displacement of the note.
modLeftPx: Start X for left modifiers.
modRightPx: Start X for right modifiers.
leftDisplacedHeadPx: Extra space on left of note.
rightDisplacedHeadPx: Extra space on right of note.
  getMetrics() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call getMetrics on an unformatted note.");
    }
    const modLeftPx = this.modifierContext ? this.modifierContext.state.left_shift : 0;
    const modRightPx = this.modifierContext ? this.modifierContext.state.right_shift : 0;
    const width = this.getWidth();
    const glyphWidth = this.getGlyphWidth();
    const notePx = width
      - modLeftPx           // subtract left modifiers
      - modRightPx          // subtract right modifiers
      - this.leftDisplacedHeadPx   // subtract left displaced head
      - this.rightDisplacedHeadPx;  // subtract right displaced head
    return {      width,
      glyphWidth,
      notePx,Modifier spacing.
      modLeftPx,
      modRightPx,Displaced note head on left or right.
      leftDisplacedHeadPx: this.leftDisplacedHeadPx,
      rightDisplacedHeadPx: this.rightDisplacedHeadPx,
    };
  }Get the absolute X position of this note’s tick context. This
excludes x_shift, so you’ll need to factor it in if you’re
looking for the post-formatted x-position.
  getAbsoluteX() {
    if (!this.tickContext) {
      throw new Vex.RERR('NoTickContext', 'Note needs a TickContext assigned for an X-Value');
    }Position note to left edge of tick context.
    let x = this.tickContext.getX();
    if (this.stave) {
      x += this.stave.getNoteStartX() + this.musicFont.lookupMetric('stave.padding');
    }
    if (this.isCenterAligned()) {
      x += this.getCenterXShift();
    }
    return x;
  }
  setPreFormatted(value) {
    this.preFormatted = value;
  }
}