VexFlow - Copyright (c) Mohit Muthanna 2010.

Description

This file implements NoteHeads. NoteHeads are typically not manipulated directly, but used internally in StaveNote.

See tests/notehead_tests.js for usage examples.

import { Vex } from './vex';
import { Flow } from './tables';
import { Note } from './note';
import { Stem } from './stem';
import { StaveNote } from './stavenote';
import { Glyph } from './glyph';

To enable logging for this class. Set Vex.Flow.NoteHead.DEBUG to true.

function L(...args) { if (NoteHead.DEBUG) Vex.L('Vex.Flow.NoteHead', args); }

Draw slashnote head manually. No glyph exists for this.

Parameters:

function drawSlashNoteHead(ctx, duration, x, y, stem_direction, staveSpace) {
  const width = Flow.SLASH_NOTEHEAD_WIDTH;
  ctx.save();
  ctx.setLineWidth(Flow.STEM_WIDTH);

  let fill = false;

  if (Flow.durationToNumber(duration) > 2) {
    fill = true;
  }

  if (!fill) x -= (Flow.STEM_WIDTH / 2) * stem_direction;

  ctx.beginPath();
  ctx.moveTo(x, y + staveSpace);
  ctx.lineTo(x, y + 1);
  ctx.lineTo(x + width, y - staveSpace);
  ctx.lineTo(x + width, y);
  ctx.lineTo(x, y + staveSpace);
  ctx.closePath();

  if (fill) {
    ctx.fill();
  } else {
    ctx.stroke();
  }

  if (Flow.durationToFraction(duration).equals(0.5)) {
    const breve_lines = [-3, -1, width + 1, width + 3];
    for (let i = 0; i < breve_lines.length; i++) {
      ctx.beginPath();
      ctx.moveTo(x + breve_lines[i], y - 10);
      ctx.lineTo(x + breve_lines[i], y + 11);
      ctx.stroke();
    }
  }

  ctx.restore();
}

export class NoteHead extends Note {
  static get CATEGORY() { return 'notehead'; }

  constructor(head_options) {
    super(head_options);
    this.attrs.type = 'NoteHead';

    this.index = head_options.index;
    this.x = head_options.x || 0;
    this.y = head_options.y || 0;
    this.note_type = head_options.note_type;
    this.duration = head_options.duration;
    this.displaced = head_options.displaced || false;
    this.stem_direction = head_options.stem_direction || StaveNote.STEM_UP;
    this.line = head_options.line;

Get glyph code based on duration and note type. This could be regular notes, rests, or other custom codes.

    this.glyph = Flow.durationToGlyph(this.duration, this.note_type);
    if (!this.glyph) {
      throw new Vex.RuntimeError(
        'BadArguments',
        `No glyph found for duration '${this.duration}' and type '${this.note_type}'`);
    }

    this.glyph_code = this.glyph.code_head;
    this.x_shift = head_options.x_shift;
    if (head_options.custom_glyph_code) {
      this.custom_glyph = true;
      this.glyph_code = head_options.custom_glyph_code;
    }

    this.style = head_options.style;
    this.slashed = head_options.slashed;

    Vex.Merge(this.render_options, {

font size for note heads

      glyph_font_scale: head_options.glyph_font_scale || Flow.DEFAULT_NOTATION_FONT_SCALE,

number of stroke px to the left and right of head

      stroke_px: 3,
    });

    this.setWidth(this.glyph.getWidth(this.render_options.glyph_font_scale));
  }

  getCategory() { return NoteHead.CATEGORY; }

Get the width of the notehead

  getWidth() { return this.width; }

Determine if the notehead is displaced

  isDisplaced() { return this.displaced === true; }

Get/set the notehead’s style

style is an object with the following properties: shadowColor, shadowBlur, fillStyle, strokeStyle

  getStyle() { return this.style; }
  setStyle(style) { this.style = style; return this; }

Get the glyph data

  getGlyph() { return this.glyph; }

Set the X coordinate

  setX(x) { this.x = x; return this; }

get/set the Y coordinate

  getY() { return this.y; }
  setY(y) { this.y = y;  return this; }

Get/set the stave line the notehead is placed on

  getLine() { return this.line; }
  setLine(line) { this.line = line; return this; }

Get the canvas x coordinate position of the notehead.

  getAbsoluteX() {

If the note has not been preformatted, then get the static x value Otherwise, it’s been formatted and we should use it’s x value relative to its tick context

    const x = !this.preFormatted ? this.x : super.getAbsoluteX();

For a more natural displaced notehead, we adjust the displacement amount by half the stem width in order to maintain a slight overlap with the stem

    const displacementStemAdjustment = (Stem.WIDTH / 2);

    return x + (this.displaced
      ? (this.width - displacementStemAdjustment) * this.stem_direction
      : 0
    );
  }

Get the BoundingBox for the NoteHead

  getBoundingBox() {
    if (!this.preFormatted) {
      throw new Vex.RERR('UnformattedNote', "Can't call getBoundingBox on an unformatted note.");
    }

    const spacing = this.stave.getSpacingBetweenLines();
    const half_spacing = spacing / 2;
    const min_y = this.y - half_spacing;

    return new Flow.BoundingBox(this.getAbsoluteX(), min_y, this.width, spacing);
  }

Apply current style to Canvas context

  applyStyle(context) {
    const style = this.getStyle();
    if (style.shadowColor) context.setShadowColor(style.shadowColor);
    if (style.shadowBlur) context.setShadowBlur(style.shadowBlur);
    if (style.fillStyle) context.setFillStyle(style.fillStyle);
    if (style.strokeStyle) context.setStrokeStyle(style.strokeStyle);
    return this;
  }

Set notehead to a provided stave

  setStave(stave) {
    const line = this.getLine();

    this.stave = stave;
    this.setY(stave.getYForNote(line));
    this.context = this.stave.context;
    return this;
  }

Pre-render formatting

  preFormat() {
    if (this.preFormatted) return this;

    const width = this.getWidth() + this.extraLeftPx + this.extraRightPx;

    this.setWidth(width);
    this.setPreFormatted(true);
    return this;
  }

Draw the notehead

  draw() {
    this.checkContext();

    const ctx = this.context;
    const head_x = this.getAbsoluteX();
    const y = this.y;

    L("Drawing note head '", this.note_type, this.duration, "' at", head_x, y);

Begin and end positions for head.

    const stem_direction = this.stem_direction;
    const glyph_font_scale = this.render_options.glyph_font_scale;
    const line = this.line;

If note above/below the staff, draw the small staff

    if (line <= 0 || line >= 6) {
      let line_y = y;
      const floor = Math.floor(line);
      if (line < 0 && floor - line === -0.5) {
        line_y -= 5;
      } else if (line > 6 &&  floor - line === -0.5) {
        line_y += 5;
      }

      if (this.note_type !== 'r') {
        ctx.fillRect(
          head_x - this.render_options.stroke_px,
          line_y,
          this.getWidth() + (this.render_options.stroke_px * 2),
          1
        );
      }
    }

    if (this.note_type === 's') {
      const staveSpace = this.stave.getSpacingBetweenLines();
      drawSlashNoteHead(ctx, this.duration, head_x, y, stem_direction, staveSpace);
    } else {
      if (this.style) {
        ctx.save();
        this.applyStyle(ctx);
        Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
        ctx.restore();
      } else {
        Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
      }
    }
  }
}
h