vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature.
196 lines (195 loc) • 6.64 kB
JavaScript
import { Glyph } from './glyph.js';
import { Note } from './note.js';
import { Stem } from './stem.js';
import { Tables } from './tables.js';
import { RuntimeError } from './util.js';
export class StemmableNote extends Note {
static get CATEGORY() {
return "StemmableNote";
}
constructor(noteStruct) {
super(noteStruct);
}
getStem() {
return this.stem;
}
checkStem() {
if (!this.stem) {
throw new RuntimeError('NoStem', 'No stem attached to instance');
}
return this.stem;
}
setStem(stem) {
this.stem = stem;
this.addChildElement(stem);
return this;
}
buildStem() {
const stem = new Stem();
this.setStem(stem);
return this;
}
buildFlag(category = 'flag') {
const { glyphProps } = this;
if (this.hasFlag()) {
const flagCode = this.getStemDirection() === Stem.DOWN ? glyphProps.code_flag_downstem : glyphProps.code_flag_upstem;
if (flagCode)
this.flag = new Glyph(flagCode, this.render_options.glyph_font_scale, { category });
}
}
getBaseCustomNoteHeadGlyphProps() {
if (this.getStemDirection() === Stem.DOWN) {
return this.customGlyphs[this.customGlyphs.length - 1];
}
else {
return this.customGlyphs[0];
}
}
getStemLength() {
return Stem.HEIGHT + this.getStemExtension();
}
getBeamCount() {
const glyphProps = this.getGlyphProps();
if (glyphProps) {
return glyphProps.beam_count;
}
else {
return 0;
}
}
getStemMinimumLength() {
const frac = Tables.durationToFraction(this.duration);
let length = frac.value() <= 1 ? 0 : 20;
switch (this.duration) {
case '8':
if (this.beam == undefined)
length = 35;
break;
case '16':
length = this.beam == undefined ? 35 : 25;
break;
case '32':
length = this.beam == undefined ? 45 : 35;
break;
case '64':
length = this.beam == undefined ? 50 : 40;
break;
case '128':
length = this.beam == undefined ? 55 : 45;
break;
default:
break;
}
return length;
}
getStemDirection() {
if (!this.stem_direction)
throw new RuntimeError('NoStem', 'No stem attached to this note.');
return this.stem_direction;
}
setStemDirection(direction) {
if (!direction)
direction = Stem.UP;
if (direction !== Stem.UP && direction !== Stem.DOWN) {
throw new RuntimeError('BadArgument', `Invalid stem direction: ${direction}`);
}
this.stem_direction = direction;
this.reset();
if (this.hasFlag()) {
this.buildFlag();
}
this.beam = undefined;
if (this.stem) {
this.stem.setDirection(direction);
this.stem.setExtension(this.getStemExtension());
const glyphProps = this.getBaseCustomNoteHeadGlyphProps() || this.getGlyphProps();
const offsets = Tables.currentMusicFont().lookupMetric(`stem.noteHead.${glyphProps.code_head}`, {
offsetYBaseStemUp: 0,
offsetYTopStemUp: 0,
offsetYBaseStemDown: 0,
offsetYTopStemDown: 0,
});
this.stem.setOptions({
stem_up_y_offset: offsets.offsetYTopStemUp,
stem_down_y_offset: offsets.offsetYTopStemDown,
stem_up_y_base_offset: offsets.offsetYBaseStemUp,
stem_down_y_base_offset: offsets.offsetYBaseStemDown,
});
}
if (this.preFormatted) {
this.preFormat();
}
return this;
}
getStemX() {
const x_begin = this.getAbsoluteX() + this.x_shift;
const x_end = this.getAbsoluteX() + this.x_shift + this.getGlyphWidth();
const stem_x = this.stem_direction === Stem.DOWN ? x_begin : x_end;
return stem_x;
}
getCenterGlyphX() {
return this.getAbsoluteX() + this.x_shift + this.getGlyphWidth() / 2;
}
getStemExtension() {
const glyphProps = this.getGlyphProps();
if (this.stem_extension_override != undefined) {
return this.stem_extension_override;
}
if (this.beam) {
return glyphProps.stem_beam_extension;
}
if (glyphProps) {
return this.getStemDirection() === Stem.UP ? glyphProps.stem_up_extension : glyphProps.stem_down_extension;
}
return 0;
}
setStemLength(height) {
this.stem_extension_override = height - Stem.HEIGHT;
return this;
}
getStemExtents() {
if (!this.stem)
throw new RuntimeError('NoStem', 'No stem attached to this note.');
return this.stem.getExtents();
}
getYForTopText(textLine) {
const stave = this.checkStave();
if (this.hasStem()) {
const extents = this.getStemExtents();
if (!extents)
throw new RuntimeError('InvalidState', 'Stem does not have extents.');
return Math.min(stave.getYForTopText(textLine), extents.topY - this.render_options.annotation_spacing * (textLine + 1));
}
else {
return stave.getYForTopText(textLine);
}
}
getYForBottomText(textLine) {
const stave = this.checkStave();
if (this.hasStem()) {
const extents = this.getStemExtents();
if (!extents)
throw new RuntimeError('InvalidState', 'Stem does not have extents.');
return Math.max(stave.getYForTopText(textLine), extents.baseY + this.render_options.annotation_spacing * textLine);
}
else {
return stave.getYForBottomText(textLine);
}
}
hasFlag() {
return Tables.getGlyphProps(this.duration).flag == true && !this.beam;
}
postFormat() {
var _a;
(_a = this.beam) === null || _a === void 0 ? void 0 : _a.postFormat();
this.postFormatted = true;
return this;
}
drawStem(stemOptions) {
var _a;
this.checkContext();
this.setRendered();
this.setStem(new Stem(stemOptions));
(_a = this.stem) === null || _a === void 0 ? void 0 : _a.setContext(this.getContext()).draw();
}
}