vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
216 lines (215 loc) • 8.72 kB
JavaScript
import { Glyph } from './glyph.js';
import { Modifier } from './modifier.js';
import { Stem } from './stem.js';
import { Tables } from './tables.js';
import { TickContext } from './tickcontext.js';
import { isTabNote } from './typeguard.js';
import { defined, log, RuntimeError } from './util.js';
function L(...args) {
if (Ornament.DEBUG)
log('Vex.Flow.Ornament', args);
}
class Ornament extends Modifier {
static get CATEGORY() {
return "Ornament";
}
static get minPadding() {
const musicFont = Tables.currentMusicFont();
return musicFont.lookupMetric('noteHead.minPadding');
}
static format(ornaments, state) {
if (!ornaments || ornaments.length === 0)
return false;
let width = 0;
let right_shift = state.right_shift;
let left_shift = state.left_shift;
let yOffset = 0;
for (let i = 0; i < ornaments.length; ++i) {
const ornament = ornaments[i];
const increment = 2;
if (Ornament.ornamentRelease.indexOf(ornament.type) >= 0) {
ornament.x_shift += right_shift + 2;
}
if (Ornament.ornamentAttack.indexOf(ornament.type) >= 0) {
ornament.x_shift -= left_shift + 2;
}
if (ornament.reportedWidth && ornament.x_shift < 0) {
left_shift += ornament.reportedWidth;
}
else if (ornament.reportedWidth && ornament.x_shift >= 0) {
right_shift += ornament.reportedWidth + Ornament.minPadding;
}
else {
width = Math.max(ornament.getWidth(), width);
}
if (Ornament.ornamentArticulation.indexOf(ornament.type) >= 0) {
const ornamentNote = defined(ornament.note, 'NoAttachedNote');
if (ornamentNote.getLineNumber() >= 3 || ornament.getPosition() === Modifier.Position.ABOVE) {
state.top_text_line += increment;
ornament.y_shift += yOffset;
yOffset -= ornament.glyph.bbox.getH();
}
else {
state.text_line += increment;
ornament.y_shift += yOffset;
yOffset += ornament.glyph.bbox.getH();
}
}
else {
if (ornament.getPosition() === Modifier.Position.ABOVE) {
ornament.setTextLine(state.top_text_line);
state.top_text_line += increment;
}
else {
ornament.setTextLine(state.text_line);
state.text_line += increment;
}
}
}
state.left_shift = left_shift + width / 2;
state.right_shift = right_shift + width / 2;
return true;
}
static get ornamentNoteTransition() {
return ['flip', 'jazzTurn', 'smear'];
}
static get ornamentAttack() {
return ['scoop'];
}
static get ornamentAlignWithNoteHead() {
return ['doit', 'fall', 'fallLong', 'doitLong', 'bend', 'plungerClosed', 'plungerOpen', 'scoop'];
}
static get ornamentRelease() {
return ['doit', 'fall', 'fallLong', 'doitLong', 'jazzTurn', 'smear', 'flip'];
}
static get ornamentArticulation() {
return ['bend', 'plungerClosed', 'plungerOpen'];
}
getMetrics() {
const ornamentMetrics = Tables.currentMusicFont().getMetrics().ornament;
if (!ornamentMetrics)
throw new RuntimeError('BadMetrics', `ornament missing`);
return ornamentMetrics[this.ornament.code];
}
constructor(type) {
super();
this.type = type;
this.delayed = false;
this.render_options = {
font_scale: Tables.NOTATION_FONT_SCALE,
accidentalLowerPadding: 3,
accidentalUpperPadding: 3,
};
this.ornament = Tables.ornamentCodes(this.type);
const metrics = this.getMetrics();
this.adjustForStemDirection = false;
this.reportedWidth = metrics && metrics.reportedWidth ? metrics.reportedWidth : 0;
this.stemUpYOffset = metrics && metrics.stemUpYOffset ? metrics.stemUpYOffset : 0;
this.ornamentAlignWithNoteHead = Ornament.ornamentAlignWithNoteHead.indexOf(this.type) >= 0;
if (!this.ornament) {
throw new RuntimeError('ArgumentError', `Ornament not found: '${this.type}'`);
}
this.x_shift = metrics ? metrics.xOffset : 0;
this.y_shift = metrics ? metrics.yOffset : 0;
this.glyph = new Glyph(this.ornament.code, this.render_options.font_scale, {
category: `ornament.${this.ornament.code}`,
});
if (Ornament.ornamentNoteTransition.indexOf(this.type) >= 0) {
this.delayed = true;
}
if (!metrics) {
this.glyph.setOrigin(0.5, 1.0);
}
}
setDelayed(delayed) {
this.delayed = delayed;
return this;
}
setUpperAccidental(accid) {
const scale = this.render_options.font_scale / 1.3;
this.accidentalUpper = new Glyph(Tables.accidentalCodes(accid).code, scale);
this.accidentalUpper.setOrigin(0.5, 1.0);
return this;
}
setLowerAccidental(accid) {
const scale = this.render_options.font_scale / 1.3;
this.accidentalLower = new Glyph(Tables.accidentalCodes(accid).code, scale);
this.accidentalLower.setOrigin(0.5, 1.0);
return this;
}
draw() {
const ctx = this.checkContext();
const note = this.checkAttachedNote();
this.setRendered();
const stemDir = note.getStemDirection();
const stave = note.checkStave();
this.applyStyle();
ctx.openGroup('ornament', this.getAttribute('id'));
const stemExtents = note.checkStem().getExtents();
let y = stemDir === Stem.DOWN ? stemExtents.baseY : stemExtents.topY;
if (isTabNote(note)) {
if (note.hasStem()) {
if (stemDir === Stem.DOWN) {
y = stave.getYForTopText(this.text_line);
}
}
else {
y = stave.getYForTopText(this.text_line);
}
}
const isPlacedOnNoteheadSide = stemDir === Stem.DOWN;
const spacing = stave.getSpacingBetweenLines();
let lineSpacing = 1;
if (!isPlacedOnNoteheadSide && note.hasBeam()) {
lineSpacing += 0.5;
}
const totalSpacing = spacing * (this.text_line + lineSpacing);
const glyphYBetweenLines = y - totalSpacing;
const start = note.getModifierStartXY(this.position, this.index);
let glyphX = start.x;
let glyphY = this.ornamentAlignWithNoteHead
? start.y
: Math.min(stave.getYForTopText(this.text_line), glyphYBetweenLines);
glyphY += this.y_shift;
if (this.delayed) {
let delayXShift = 0;
const startX = glyphX - (stave.getX() - 10);
if (this.delayXShift !== undefined) {
delayXShift = this.delayXShift;
}
else {
delayXShift += this.glyph.getMetrics().width / 2;
const nextContext = TickContext.getNextContext(note.getTickContext());
if (nextContext) {
delayXShift += (nextContext.getX() - startX) * 0.5;
}
else {
delayXShift += (stave.getX() + stave.getWidth() - startX) * 0.5;
}
this.delayXShift = delayXShift;
}
glyphX += delayXShift;
}
L('Rendering ornament: ', this.ornament, glyphX, glyphY);
if (this.accidentalLower) {
this.accidentalLower.render(ctx, glyphX, glyphY);
glyphY -= this.accidentalLower.getMetrics().height;
glyphY -= this.render_options.accidentalLowerPadding;
}
if (this.stemUpYOffset && note.hasStem() && note.getStemDirection() === 1) {
glyphY += this.stemUpYOffset;
}
if (note.getLineNumber() < 5 && Ornament.ornamentNoteTransition.indexOf(this.type) >= 0) {
glyphY = note.checkStave().getBoundingBox().getY() + 40;
}
this.glyph.render(ctx, glyphX + this.x_shift, glyphY);
if (this.accidentalUpper) {
glyphY -= this.glyph.getMetrics().height + this.render_options.accidentalUpperPadding;
this.accidentalUpper.render(ctx, glyphX, glyphY);
}
ctx.closeGroup();
this.restoreStyle();
}
}
Ornament.DEBUG = false;
export { Ornament };