UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

134 lines (133 loc) 5.37 kB
import { StaveNote } from './stavenote.js'; import { Stem } from './stem.js'; import { Tables } from './tables.js'; import { RuntimeError } from './util.js'; export class GraceNote extends StaveNote { static get CATEGORY() { return "GraceNote"; } static get LEDGER_LINE_OFFSET() { return 2; } static get SCALE() { return 0.66; } constructor(noteStruct) { super(Object.assign({ glyph_font_scale: Tables.NOTATION_FONT_SCALE * GraceNote.SCALE, stroke_px: GraceNote.LEDGER_LINE_OFFSET }, noteStruct)); this.slash = noteStruct.slash || false; this.slur = true; this.buildNoteHeads(); this.width = 3; } getStemExtension() { if (this.stem_extension_override) { return this.stem_extension_override; } const glyphProps = this.getGlyphProps(); if (glyphProps) { let ret = super.getStemExtension(); if (glyphProps.stem) { const staveNoteScale = this.getStaveNoteScale(); ret = (Stem.HEIGHT + ret) * staveNoteScale - Stem.HEIGHT; } return ret; } return 0; } getStaveNoteScale() { return this.render_options.glyph_font_scale / Tables.NOTATION_FONT_SCALE; } draw() { super.draw(); this.setRendered(); const stem = this.stem; if (this.slash && stem) { const staveNoteScale = this.getStaveNoteScale(); const offsetScale = staveNoteScale / 0.66; let slashBBox = undefined; const beam = this.beam; if (beam) { if (!beam.postFormatted) { beam.postFormat(); } slashBBox = this.calcBeamedNotesSlashBBox(8 * offsetScale, 8 * offsetScale, { stem: 6 * offsetScale, beam: 5 * offsetScale, }); } else { const stem_direction = this.getStemDirection(); const noteHeadBounds = this.getNoteHeadBounds(); const noteStemHeight = stem.getHeight(); let x = this.getAbsoluteX(); let y = stem_direction === Stem.DOWN ? noteHeadBounds.y_top - noteStemHeight : noteHeadBounds.y_bottom - noteStemHeight; const defaultStemExtention = stem_direction === Stem.DOWN ? this.glyphProps.stem_down_extension : this.glyphProps.stem_up_extension; let defaultOffsetY = Tables.STEM_HEIGHT; defaultOffsetY -= defaultOffsetY / 2.8; defaultOffsetY += defaultStemExtention; y += defaultOffsetY * staveNoteScale * stem_direction; const offsets = stem_direction === Stem.UP ? { x1: 1, y1: 0, x2: 13, y2: -9, } : { x1: -4, y1: 1, x2: 13, y2: 9, }; x += offsets.x1 * offsetScale; y += offsets.y1 * offsetScale; slashBBox = { x1: x, y1: y, x2: x + offsets.x2 * offsetScale, y2: y + offsets.y2 * offsetScale, }; } const ctx = this.checkContext(); ctx.save(); ctx.setLineWidth(1 * offsetScale); ctx.beginPath(); ctx.moveTo(slashBBox.x1, slashBBox.y1); ctx.lineTo(slashBBox.x2, slashBBox.y2); ctx.closePath(); ctx.stroke(); ctx.restore(); } } calcBeamedNotesSlashBBox(slashStemOffset, slashBeamOffset, protrusions) { const beam = this.beam; if (!beam) throw new RuntimeError('NoBeam', "Can't calculate without a beam."); const beam_slope = beam.slope; const isBeamEndNote = beam.notes[beam.notes.length - 1] === this; const scaleX = isBeamEndNote ? -1 : 1; const beam_angle = Math.atan(beam_slope * scaleX); const iPointOnBeam = { dx: Math.cos(beam_angle) * slashBeamOffset, dy: Math.sin(beam_angle) * slashBeamOffset, }; slashStemOffset *= this.getStemDirection(); const slash_angle = Math.atan((iPointOnBeam.dy - slashStemOffset) / iPointOnBeam.dx); const protrusion_stem_dx = Math.cos(slash_angle) * protrusions.stem * scaleX; const protrusion_stem_dy = Math.sin(slash_angle) * protrusions.stem; const protrusion_beam_dx = Math.cos(slash_angle) * protrusions.beam * scaleX; const protrusion_beam_dy = Math.sin(slash_angle) * protrusions.beam; const stemX = this.getStemX(); const stem0X = beam.notes[0].getStemX(); const stemY = beam.getBeamYToDraw() + (stemX - stem0X) * beam_slope; const ret = { x1: stemX - protrusion_stem_dx, y1: stemY + slashStemOffset - protrusion_stem_dy, x2: stemX + iPointOnBeam.dx * scaleX + protrusion_beam_dx, y2: stemY + iPointOnBeam.dy + protrusion_beam_dy, }; return ret; } }