vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
203 lines (202 loc) • 7.47 kB
JavaScript
import { Font, FontStyle, FontWeight } from './font.js';
import { Glyph } from './glyph.js';
import { Modifier } from './modifier.js';
import { Tables } from './tables.js';
import { isNote, isStaveNote, isTabNote } from './typeguard.js';
import { RuntimeError } from './util.js';
class Stroke extends Modifier {
static get CATEGORY() {
return "Stroke";
}
static format(strokes, state) {
const left_shift = state.left_shift;
const stroke_spacing = 0;
if (!strokes || strokes.length === 0)
return false;
const strokeList = strokes.map((stroke) => {
const note = stroke.getNote();
const index = stroke.checkIndex();
if (isStaveNote(note)) {
const { line } = note.getKeyProps()[index];
const shift = note.getLeftDisplacedHeadPx();
return { line, shift, stroke };
}
else if (isTabNote(note)) {
const { str: string } = note.getPositions()[index];
return { line: string, shift: 0, stroke };
}
else {
throw new RuntimeError('Internal', 'Unexpected instance.');
}
});
const strokeShift = left_shift;
const xShift = strokeList.reduce((xShift, { stroke, shift }) => {
stroke.setXShift(strokeShift + shift);
return Math.max(stroke.getWidth() + stroke_spacing, xShift);
}, 0);
state.left_shift += xShift;
return true;
}
constructor(type, options) {
super();
this.options = Object.assign({ all_voices: true }, options);
this.all_voices = this.options.all_voices;
this.type = type;
this.position = Modifier.Position.LEFT;
this.render_options = {
font_scale: Tables.NOTATION_FONT_SCALE,
};
this.resetFont();
this.setXShift(0);
this.setWidth(10);
}
getPosition() {
return this.position;
}
addEndNote(note) {
this.note_end = note;
return this;
}
draw() {
const ctx = this.checkContext();
const note = this.checkAttachedNote();
this.setRendered();
const start = note.getModifierStartXY(this.position, this.index);
let ys = note.getYs();
let topY = start.y;
let botY = start.y;
const x = start.x - 5;
const line_space = note.checkStave().getSpacingBetweenLines();
const notes = this.checkModifierContext().getMembers(note.getCategory());
for (let i = 0; i < notes.length; i++) {
const note = notes[i];
if (isNote(note)) {
ys = note.getYs();
for (let n = 0; n < ys.length; n++) {
if (this.note === notes[i] || this.all_voices) {
topY = Math.min(topY, ys[n]);
botY = Math.max(botY, ys[n]);
}
}
}
}
let arrow = '';
let arrow_shift_x = 0;
let arrow_y = 0;
let text_shift_x = 0;
let text_y = 0;
switch (this.type) {
case Stroke.Type.BRUSH_DOWN:
arrow = 'arrowheadBlackUp';
arrow_shift_x = -3;
arrow_y = topY - line_space / 2 + 10;
botY += line_space / 2;
break;
case Stroke.Type.BRUSH_UP:
arrow = 'arrowheadBlackDown';
arrow_shift_x = 0.5;
arrow_y = botY + line_space / 2;
topY -= line_space / 2;
break;
case Stroke.Type.ROLL_DOWN:
case Stroke.Type.RASQUEDO_DOWN:
arrow = 'arrowheadBlackUp';
arrow_shift_x = -3;
text_shift_x = this.x_shift + arrow_shift_x - 2;
if (isStaveNote(note)) {
topY += 1.5 * line_space;
if ((botY - topY) % 2 !== 0) {
botY += 0.5 * line_space;
}
else {
botY += line_space;
}
arrow_y = topY - line_space;
text_y = botY + line_space + 2;
}
else {
topY += 1.5 * line_space;
botY += line_space;
arrow_y = topY - 0.75 * line_space;
text_y = botY + 0.25 * line_space;
}
break;
case Stroke.Type.ROLL_UP:
case Stroke.Type.RASQUEDO_UP:
arrow = 'arrowheadBlackDown';
arrow_shift_x = -4;
text_shift_x = this.x_shift + arrow_shift_x - 1;
if (isStaveNote(note)) {
arrow_y = line_space / 2;
topY += 0.5 * line_space;
if ((botY - topY) % 2 === 0) {
botY += line_space / 2;
}
arrow_y = botY + 0.5 * line_space;
text_y = topY - 1.25 * line_space;
}
else {
topY += 0.25 * line_space;
botY += 0.5 * line_space;
arrow_y = botY + 0.25 * line_space;
text_y = topY - line_space;
}
break;
case Stroke.Type.ARPEGGIO_DIRECTIONLESS:
topY += 0.5 * line_space;
botY += line_space;
break;
default:
throw new RuntimeError('InvalidType', `The stroke type ${this.type} does not exist`);
}
let strokeLine = 'straight';
if (this.type === Stroke.Type.BRUSH_DOWN || this.type === Stroke.Type.BRUSH_UP) {
ctx.fillRect(x + this.x_shift, topY, 1, botY - topY);
}
else {
strokeLine = 'wiggly';
if (isStaveNote(note)) {
for (let i = topY; i <= botY; i += line_space) {
Glyph.renderGlyph(ctx, x + this.x_shift - 4, i, this.render_options.font_scale, 'vexWiggleArpeggioUp');
}
}
else {
let i;
for (i = topY; i <= botY; i += 10) {
Glyph.renderGlyph(ctx, x + this.x_shift - 4, i, this.render_options.font_scale, 'vexWiggleArpeggioUp');
}
if (this.type === Stroke.Type.RASQUEDO_DOWN) {
text_y = i + 0.25 * line_space;
}
}
}
if (this.type === Stroke.Type.ARPEGGIO_DIRECTIONLESS) {
return;
}
Glyph.renderGlyph(ctx, x + this.x_shift + arrow_shift_x, arrow_y, this.render_options.font_scale, arrow, {
category: `stroke_${strokeLine}.${arrow}`,
});
if (this.type === Stroke.Type.RASQUEDO_DOWN || this.type === Stroke.Type.RASQUEDO_UP) {
ctx.save();
ctx.setFont(this.textFont);
ctx.fillText('R', x + text_shift_x, text_y);
ctx.restore();
}
}
}
Stroke.Type = {
BRUSH_DOWN: 1,
BRUSH_UP: 2,
ROLL_DOWN: 3,
ROLL_UP: 4,
RASQUEDO_DOWN: 5,
RASQUEDO_UP: 6,
ARPEGGIO_DIRECTIONLESS: 7,
};
Stroke.TEXT_FONT = {
family: Font.SERIF,
size: Font.SIZE,
weight: FontWeight.BOLD,
style: FontStyle.ITALIC,
};
export { Stroke };