vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
134 lines (133 loc) • 5.37 kB
JavaScript
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;
}
}