UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

184 lines (156 loc) 5.38 kB
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010. // // ## Description // This class implements varies types of ties between contiguous notes. The // ties include: regular ties, hammer ons, pull offs, and slides. import { Vex } from './vex'; import { Element } from './element'; export class StaveTie extends Element { constructor(notes, text) { /** * Notes is a struct that has: * * { * first_note: Note, * last_note: Note, * first_indices: [n1, n2, n3], * last_indices: [n1, n2, n3] * } * **/ super(); this.setAttribute('type', 'StaveTie'); this.notes = notes; this.context = null; this.text = text; this.direction = null; this.render_options = { cp1: 8, // Curve control point 1 cp2: 12, // Curve control point 2 text_shift_x: 0, first_x_shift: 0, last_x_shift: 0, y_shift: 7, tie_spacing: 0, font: { family: 'Arial', size: 10, style: '' }, }; this.font = this.render_options.font; this.setNotes(notes); } setFont(font) { this.font = font; return this; } setDirection(direction) { this.direction = direction; return this; } /** * Set the notes to attach this tie to. * * @param {!Object} notes The notes to tie up. */ setNotes(notes) { if (!notes.first_note && !notes.last_note) { throw new Vex.RuntimeError( 'BadArguments', 'Tie needs to have either first_note or last_note set.' ); } if (!notes.first_indices) notes.first_indices = [0]; if (!notes.last_indices) notes.last_indices = [0]; if (notes.first_indices.length !== notes.last_indices.length) { throw new Vex.RuntimeError('BadArguments', 'Tied notes must have similar index sizes'); } // Success. Lets grab 'em notes. this.first_note = notes.first_note; this.first_indices = notes.first_indices; this.last_note = notes.last_note; this.last_indices = notes.last_indices; return this; } /** * @return {boolean} Returns true if this is a partial bar. */ isPartial() { return (!this.first_note || !this.last_note); } renderTie(params) { if (params.first_ys.length === 0 || params.last_ys.length === 0) { throw new Vex.RERR('BadArguments', 'No Y-values to render'); } const ctx = this.context; let cp1 = this.render_options.cp1; let cp2 = this.render_options.cp2; if (Math.abs(params.last_x_px - params.first_x_px) < 10) { cp1 = 2; cp2 = 8; } const first_x_shift = this.render_options.first_x_shift; const last_x_shift = this.render_options.last_x_shift; const y_shift = this.render_options.y_shift * params.direction; for (let i = 0; i < this.first_indices.length; ++i) { const cp_x = ((params.last_x_px + last_x_shift) + (params.first_x_px + first_x_shift)) / 2; const first_y_px = params.first_ys[this.first_indices[i]] + y_shift; const last_y_px = params.last_ys[this.last_indices[i]] + y_shift; if (isNaN(first_y_px) || isNaN(last_y_px)) { throw new Vex.RERR('BadArguments', 'Bad indices for tie rendering.'); } const top_cp_y = ((first_y_px + last_y_px) / 2) + (cp1 * params.direction); const bottom_cp_y = ((first_y_px + last_y_px) / 2) + (cp2 * params.direction); ctx.beginPath(); ctx.moveTo(params.first_x_px + first_x_shift, first_y_px); ctx.quadraticCurveTo(cp_x, top_cp_y, params.last_x_px + last_x_shift, last_y_px); ctx.quadraticCurveTo(cp_x, bottom_cp_y, params.first_x_px + first_x_shift, first_y_px); ctx.closePath(); ctx.fill(); } } renderText(first_x_px, last_x_px) { if (!this.text) return; let center_x = (first_x_px + last_x_px) / 2; center_x -= this.context.measureText(this.text).width / 2; this.context.save(); this.context.setFont(this.font.family, this.font.size, this.font.style); this.context.fillText( this.text, center_x + this.render_options.text_shift_x, (this.first_note || this.last_note).getStave().getYForTopText() - 1 ); this.context.restore(); } draw() { this.checkContext(); this.setRendered(); const first_note = this.first_note; const last_note = this.last_note; let first_x_px; let last_x_px; let first_ys; let last_ys; let stem_direction; if (first_note) { first_x_px = first_note.getTieRightX() + this.render_options.tie_spacing; stem_direction = first_note.getStemDirection(); first_ys = first_note.getYs(); } else { first_x_px = last_note.getStave().getTieStartX(); first_ys = last_note.getYs(); this.first_indices = this.last_indices; } if (last_note) { last_x_px = last_note.getTieLeftX() + this.render_options.tie_spacing; stem_direction = last_note.getStemDirection(); last_ys = last_note.getYs(); } else { last_x_px = first_note.getStave().getTieEndX(); last_ys = first_note.getYs(); this.last_indices = this.first_indices; } if (this.direction) { stem_direction = this.direction; } this.renderTie({ first_x_px, last_x_px, first_ys, last_ys, direction: stem_direction, }); this.renderText(first_x_px, last_x_px); return true; } }