UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

875 lines (874 loc) 35 kB
import { BoundingBox } from './boundingbox.js'; import { Modifier } from './modifier.js'; import { NoteHead } from './notehead.js'; import { Stem } from './stem.js'; import { StemmableNote } from './stemmablenote.js'; import { Tables } from './tables.js'; import { defined, log, midLine, RuntimeError } from './util.js'; function showDeprecationWarningForNoteHeads() { console.warn('StaveNote.note_heads is deprecated. Use StaveNote.noteHeads instead.', 'This accessor will be removed in VexFlow 5.0.'); } function L(...args) { if (StaveNote.DEBUG) log('Vex.Flow.StaveNote', args); } const isInnerNoteIndex = (note, index) => index === (note.getStemDirection() === Stem.UP ? note.keyProps.length - 1 : 0); function shiftRestVertical(rest, note, dir) { const delta = dir; rest.line += delta; rest.maxLine += delta; rest.minLine += delta; rest.note.setKeyLine(0, rest.note.getKeyLine(0) + delta); } function centerRest(rest, noteU, noteL) { const delta = rest.line - midLine(noteU.minLine, noteL.maxLine); rest.note.setKeyLine(0, rest.note.getKeyLine(0) - delta); rest.line -= delta; rest.maxLine -= delta; rest.minLine -= delta; } class StaveNote extends StemmableNote { static get CATEGORY() { return "StaveNote"; } static get STEM_UP() { return Stem.UP; } static get STEM_DOWN() { return Stem.DOWN; } static get LEDGER_LINE_OFFSET() { return 3; } static get minNoteheadPadding() { const musicFont = Tables.currentMusicFont(); return musicFont.lookupMetric('noteHead.minPadding'); } static format(notes, state) { var _a, _b; if (!notes || notes.length < 2) return false; const notesList = []; for (let i = 0; i < notes.length; i++) { const props = notes[i].sortedKeyProps; const line = props[0].keyProps.line; let minL = props[props.length - 1].keyProps.line; const stemDirection = notes[i].getStemDirection(); const stemMax = notes[i].getStemLength() / 10; const stemMin = notes[i].getStemMinimumLength() / 10; let maxL; if (notes[i].isRest()) { maxL = line + notes[i].glyphProps.line_above; minL = line - notes[i].glyphProps.line_below; } else { maxL = stemDirection === 1 ? props[props.length - 1].keyProps.line + stemMax : props[props.length - 1].keyProps.line; minL = stemDirection === 1 ? props[0].keyProps.line : props[0].keyProps.line - stemMax; } notesList.push({ line: props[0].keyProps.line, maxLine: maxL, minLine: minL, isrest: notes[i].isRest(), stemDirection: stemDirection, stemMax, stemMin, voice_shift: notes[i].getVoiceShiftWidth(), is_displaced: notes[i].isDisplaced(), note: notes[i], }); } let voices = 0; let noteU = undefined; let noteM = undefined; let noteL = undefined; const draw = [false, false, false]; for (let i = 0; i < notesList.length; i++) { draw[i] = notesList[i].note.render_options.draw == false ? false : true; } if (draw[0] && draw[1] && draw[2]) { voices = 3; noteU = notesList[0]; noteM = notesList[1]; noteL = notesList[2]; } else if (draw[0] && draw[1]) { voices = 2; noteU = notesList[0]; noteL = notesList[1]; } else if (draw[0] && draw[2]) { voices = 2; noteU = notesList[0]; noteL = notesList[2]; } else if (draw[1] && draw[2]) { voices = 2; noteU = notesList[1]; noteL = notesList[2]; } else { return true; } if (voices === 2 && noteU.stemDirection === -1 && noteL.stemDirection === 1) { noteU = notesList[1]; noteL = notesList[0]; } const voiceXShift = Math.max(noteU.voice_shift, noteL.voice_shift); let xShift = 0; if (voices === 2) { const lineSpacing = noteU.note.hasStem() && noteL.note.hasStem() && noteU.stemDirection === noteL.stemDirection ? 0.0 : 0.5; if (noteL.isrest && noteU.isrest && noteU.note.duration === noteL.note.duration) { noteL.note.render_options.draw = false; } else if (noteU.minLine <= noteL.maxLine + lineSpacing) { if (noteU.isrest) { shiftRestVertical(noteU, noteL, 1); } else if (noteL.isrest) { shiftRestVertical(noteL, noteU, -1); } else { const lineDiff = Math.abs(noteU.line - noteL.line); if (noteU.note.hasStem() && noteL.note.hasStem()) { const noteUHead = Tables.codeNoteHead((_a = noteU.note.sortedKeyProps[0].keyProps.code) !== null && _a !== void 0 ? _a : 'N', noteU.note.duration); const noteLHead = Tables.codeNoteHead((_b = noteL.note.sortedKeyProps[noteL.note.sortedKeyProps.length - 1].keyProps.code) !== null && _b !== void 0 ? _b : 'N', noteL.note.duration); if (!Tables.UNISON || noteUHead !== noteLHead || noteU.note.getModifiers().filter((item) => item.getCategory() === "Dot" && item.getIndex() === 0) .length !== noteL.note.getModifiers().filter((item) => item.getCategory() === "Dot" && item.getIndex() === 0) .length || (lineDiff < 1 && lineDiff > 0) || JSON.stringify(noteU.note.getStyle()) !== JSON.stringify(noteL.note.getStyle())) { xShift = voiceXShift + 2; if (noteU.stemDirection === noteL.stemDirection) { noteU.note.setXShift(xShift); } else { noteL.note.setXShift(xShift); } } else if (noteU.note.voice !== noteL.note.voice) { if (noteU.stemDirection === noteL.stemDirection) { if (noteU.line != noteL.line) { xShift = voiceXShift + 2; noteU.note.setXShift(xShift); } else { if (noteL.stemDirection === 1) { noteL.stemDirection = -1; noteL.note.setStemDirection(-1); } } } } } else if (lineDiff < 1) { xShift = voiceXShift + 2; if (noteU.note.duration < noteL.note.duration) { noteU.note.setXShift(xShift); } else { noteL.note.setXShift(xShift); } } else if (noteU.note.hasStem()) { noteU.stemDirection = -noteU.note.getStemDirection(); noteU.note.setStemDirection(noteU.stemDirection); } else if (noteL.note.hasStem()) { noteL.stemDirection = -noteL.note.getStemDirection(); noteL.note.setStemDirection(noteL.stemDirection); } } } state.right_shift += xShift; return true; } if (!noteM) throw new RuntimeError('InvalidState', 'noteM not defined.'); if (noteM.isrest && !noteU.isrest && !noteL.isrest) { if (noteU.minLine <= noteM.maxLine || noteM.minLine <= noteL.maxLine) { const restHeight = noteM.maxLine - noteM.minLine; const space = noteU.minLine - noteL.maxLine; if (restHeight < space) { centerRest(noteM, noteU, noteL); } else { xShift = voiceXShift + 2; noteM.note.setXShift(xShift); if (noteL.note.hasBeam() === false) { noteL.stemDirection = -1; noteL.note.setStemDirection(-1); } if (noteU.minLine <= noteL.maxLine && noteU.note.hasBeam() === false) { noteU.stemDirection = 1; noteU.note.setStemDirection(1); } } state.right_shift += xShift; return true; } } if (noteU.isrest && noteM.isrest && noteL.isrest) { noteU.note.render_options.draw = false; noteL.note.render_options.draw = false; state.right_shift += xShift; return true; } if (noteM.isrest && noteU.isrest && noteM.minLine <= noteL.maxLine) { noteM.note.render_options.draw = false; } if (noteM.isrest && noteL.isrest && noteU.minLine <= noteM.maxLine) { noteM.note.render_options.draw = false; } if (noteU.isrest && noteU.minLine <= noteM.maxLine) { shiftRestVertical(noteU, noteM, 1); } if (noteL.isrest && noteM.minLine <= noteL.maxLine) { shiftRestVertical(noteL, noteM, -1); } if (noteU.minLine <= noteM.maxLine + 0.5 || noteM.minLine <= noteL.maxLine) { xShift = voiceXShift + 2; noteM.note.setXShift(xShift); if (noteL.note.hasBeam() === false) { noteL.stemDirection = -1; noteL.note.setStemDirection(-1); } if (noteU.minLine <= noteL.maxLine && noteU.note.hasBeam() === false) { noteU.stemDirection = 1; noteU.note.setStemDirection(1); } } state.right_shift += xShift; return true; } static postFormat(notes) { if (!notes) return false; notes.forEach((note) => note.postFormat()); return true; } constructor(noteStruct) { var _a, _b, _c; super(noteStruct); this.minLine = 0; this.maxLine = 0; this.sortedKeyProps = []; this.ledgerLineStyle = {}; this.clef = (_a = noteStruct.clef) !== null && _a !== void 0 ? _a : 'treble'; this.octave_shift = (_b = noteStruct.octave_shift) !== null && _b !== void 0 ? _b : 0; this.glyphProps = Tables.getGlyphProps(this.duration, this.noteType); defined(this.glyphProps, 'BadArguments', `No glyph found for duration '${this.duration}' and type '${this.noteType}'`); this.displaced = false; this.dot_shiftY = 0; this.use_default_head_x = false; this._noteHeads = []; this.modifiers = []; this.render_options = Object.assign(Object.assign({}, this.render_options), { glyph_font_scale: noteStruct.glyph_font_scale || Tables.NOTATION_FONT_SCALE, stroke_px: noteStruct.stroke_px || StaveNote.LEDGER_LINE_OFFSET }); this.calculateKeyProps(); this.buildStem(); if (noteStruct.auto_stem) { this.autoStem(); } else { this.setStemDirection((_c = noteStruct.stem_direction) !== null && _c !== void 0 ? _c : Stem.UP); } this.reset(); this.buildFlag(); } reset() { super.reset(); const noteHeadStyles = this._noteHeads.map((noteHead) => noteHead.getStyle()); this.buildNoteHeads(); this._noteHeads.forEach((noteHead, index) => { const noteHeadStyle = noteHeadStyles[index]; if (noteHeadStyle) noteHead.setStyle(noteHeadStyle); }); const stave = this.stave; if (stave) { this.setStave(stave); } this.calcNoteDisplacements(); return this; } setBeam(beam) { this.beam = beam; this.calcNoteDisplacements(); if (this.stem) { this.stem.setExtension(this.getStemExtension()); } return this; } buildStem() { this.setStem(new Stem({ hide: !!this.isRest() })); return this; } buildNoteHeads() { this._noteHeads = []; const stemDirection = this.getStemDirection(); const keys = this.getKeys(); let lastLine = undefined; let lineDiff = undefined; let displaced = false; let start; let end; let step; if (stemDirection === Stem.UP) { start = 0; end = keys.length; step = 1; } else { start = keys.length - 1; end = -1; step = -1; } for (let i = start; i !== end; i += step) { const noteProps = this.sortedKeyProps[i].keyProps; const line = noteProps.line; if (lastLine === undefined) { lastLine = line; } else { lineDiff = Math.abs(lastLine - line); if (lineDiff === 0 || lineDiff === 0.5) { displaced = !displaced; } else { displaced = false; this.use_default_head_x = true; } } lastLine = line; const notehead = new NoteHead({ duration: this.duration, note_type: this.noteType, displaced, stem_direction: stemDirection, custom_glyph_code: noteProps.code, glyph_font_scale: this.render_options.glyph_font_scale, x_shift: noteProps.shift_right, stem_up_x_offset: noteProps.stem_up_x_offset, stem_down_x_offset: noteProps.stem_down_x_offset, line: noteProps.line, }); this.addChildElement(notehead); this._noteHeads[this.sortedKeyProps[i].index] = notehead; } } autoStem() { this.setStemDirection(this.calculateOptimalStemDirection()); } calculateOptimalStemDirection() { this.minLine = this.sortedKeyProps[0].keyProps.line; this.maxLine = this.sortedKeyProps[this.keyProps.length - 1].keyProps.line; const MIDDLE_LINE = 3; const decider = (this.minLine + this.maxLine) / 2; const stemDirection = decider < MIDDLE_LINE ? Stem.UP : Stem.DOWN; return stemDirection; } calculateKeyProps() { let lastLine; for (let i = 0; i < this.keys.length; ++i) { const key = this.keys[i]; if (this.glyphProps.rest) this.glyphProps.position = key; const options = { octave_shift: this.octave_shift || 0, duration: this.duration }; const props = Tables.keyProperties(key, this.clef, options); if (!props) { throw new RuntimeError('BadArguments', `Invalid key for note properties: ${key}`); } if (props.key === 'R') { if (this.duration === '1' || this.duration === 'w') { props.line = 4; } else { props.line = 3; } } const line = props.line; if (lastLine == undefined) { lastLine = line; } else { if (Math.abs(lastLine - line) === 0.5) { this.displaced = true; props.displaced = true; if (this.keyProps.length > 0) { this.keyProps[i - 1].displaced = true; } } } lastLine = line; this.keyProps.push(props); } this.keyProps.forEach((keyProps, index) => { this.sortedKeyProps.push({ keyProps, index }); }); this.sortedKeyProps.sort((a, b) => a.keyProps.line - b.keyProps.line); } getBoundingBox() { var _a, _b; if (!this.preFormatted) { throw new RuntimeError('UnformattedNote', "Can't call getBoundingBox on an unformatted note."); } const { width: w, modLeftPx, leftDisplacedHeadPx } = this.getMetrics(); const x = this.getAbsoluteX() - modLeftPx - leftDisplacedHeadPx; let minY = 0; let maxY = 0; const halfLineSpacing = ((_b = (_a = this.getStave()) === null || _a === void 0 ? void 0 : _a.getSpacingBetweenLines()) !== null && _b !== void 0 ? _b : 0) / 2; const lineSpacing = halfLineSpacing * 2; if (this.isRest()) { const y = this.ys[0]; const frac = Tables.durationToFraction(this.duration); if (frac.equals(1) || frac.equals(2)) { minY = y - halfLineSpacing; maxY = y + halfLineSpacing; } else { minY = y - this.glyphProps.line_above * lineSpacing; maxY = y + this.glyphProps.line_below * lineSpacing; } } else if (this.glyphProps.stem) { const ys = this.getStemExtents(); ys.baseY += halfLineSpacing * this.getStemDirection(); minY = Math.min(ys.topY, ys.baseY); maxY = Math.max(ys.topY, ys.baseY); } else { minY = 0; maxY = 0; for (let i = 0; i < this.ys.length; ++i) { const yy = this.ys[i]; if (i === 0) { minY = yy; maxY = yy; } else { minY = Math.min(yy, minY); maxY = Math.max(yy, maxY); } } minY -= halfLineSpacing; maxY += halfLineSpacing; } return new BoundingBox(x, minY, w, maxY - minY); } getLineNumber(isTopNote) { if (!this.keyProps.length) { throw new RuntimeError('NoKeyProps', "Can't get bottom note line, because note is not initialized properly."); } let resultLine = this.keyProps[0].line; for (let i = 0; i < this.keyProps.length; i++) { const thisLine = this.keyProps[i].line; if (isTopNote) { if (thisLine > resultLine) resultLine = thisLine; } else { if (thisLine < resultLine) resultLine = thisLine; } } return resultLine; } isRest() { return this.glyphProps.rest; } isChord() { return !this.isRest() && this.keys.length > 1; } hasStem() { return this.glyphProps.stem; } hasFlag() { return super.hasFlag() && !this.isRest(); } getStemX() { if (this.noteType === 'r') { return this.getCenterGlyphX(); } else { return super.getStemX() + (this.stem_direction ? Stem.WIDTH / (2 * -this.stem_direction) : 0); } } getYForTopText(textLine) { const extents = this.getStemExtents(); return Math.min(this.checkStave().getYForTopText(textLine), extents.topY - this.render_options.annotation_spacing * (textLine + 1)); } getYForBottomText(textLine) { const extents = this.getStemExtents(); return Math.max(this.checkStave().getYForTopText(textLine), extents.baseY + this.render_options.annotation_spacing * textLine); } setStave(stave) { super.setStave(stave); const ys = this._noteHeads.map((notehead) => { notehead.setStave(stave); return notehead.getY(); }); this.setYs(ys); if (this.stem) { const { y_top, y_bottom } = this.getNoteHeadBounds(); this.stem.setYBounds(y_top, y_bottom); } return this; } isDisplaced() { return this.displaced; } setNoteDisplaced(displaced) { this.displaced = displaced; return this; } getTieRightX() { let tieStartX = this.getAbsoluteX(); tieStartX += this.getGlyphWidth() + this.x_shift + this.rightDisplacedHeadPx; if (this.modifierContext) tieStartX += this.modifierContext.getRightShift(); return tieStartX; } getTieLeftX() { let tieEndX = this.getAbsoluteX(); tieEndX += this.x_shift - this.leftDisplacedHeadPx; return tieEndX; } getLineForRest() { let restLine = this.keyProps[0].line; if (this.keyProps.length > 1) { const lastLine = this.keyProps[this.keyProps.length - 1].line; const top = Math.max(restLine, lastLine); const bot = Math.min(restLine, lastLine); restLine = midLine(top, bot); } return restLine; } getModifierStartXY(position, index, options = {}) { var _a, _b; if (!this.preFormatted) { throw new RuntimeError('UnformattedNote', "Can't call GetModifierStartXY on an unformatted note"); } if (this.ys.length === 0) { throw new RuntimeError('NoYValues', 'No Y-Values calculated for this note.'); } const { ABOVE, BELOW, LEFT, RIGHT } = Modifier.Position; let x = 0; if (position === LEFT) { x = -1 * 2; } else if (position === RIGHT) { x = this.getGlyphWidth() + this.x_shift + 2; if (this.stem_direction === Stem.UP && this.hasFlag() && (options.forceFlagRight || isInnerNoteIndex(this, index))) { x += (_b = (_a = this === null || this === void 0 ? void 0 : this.flag) === null || _a === void 0 ? void 0 : _a.getMetrics().width) !== null && _b !== void 0 ? _b : 0; } } else if (position === BELOW || position === ABOVE) { x = this.getGlyphWidth() / 2; } return { x: this.getAbsoluteX() + x, y: this.ys[index], }; } setStyle(style) { return super.setGroupStyle(style); } setStemStyle(style) { const stem = this.getStem(); if (stem) stem.setStyle(style); return this; } getStemStyle() { var _a; return (_a = this.stem) === null || _a === void 0 ? void 0 : _a.getStyle(); } setLedgerLineStyle(style) { this.ledgerLineStyle = style; } getLedgerLineStyle() { return this.ledgerLineStyle; } setFlagStyle(style) { var _a; (_a = this.flag) === null || _a === void 0 ? void 0 : _a.setStyle(style); } getFlagStyle() { var _a; return (_a = this.flag) === null || _a === void 0 ? void 0 : _a.getStyle(); } setKeyStyle(index, style) { this._noteHeads[index].setStyle(style); return this; } setKeyLine(index, line) { this.keyProps[index].line = line; this.reset(); return this; } getKeyLine(index) { return this.keyProps[index].line; } getVoiceShiftWidth() { return this.getGlyphWidth() * (this.displaced ? 2 : 1); } calcNoteDisplacements() { this.setLeftDisplacedHeadPx(this.displaced && this.stem_direction === Stem.DOWN ? this.getGlyphWidth() : 0); this.setRightDisplacedHeadPx(!this.hasFlag() && this.displaced && this.stem_direction === Stem.UP ? this.getGlyphWidth() : 0); } preFormat() { if (this.preFormatted) return; let noteHeadPadding = 0; if (this.modifierContext) { this.modifierContext.preFormat(); if (this.modifierContext.getWidth() === 0) { noteHeadPadding = StaveNote.minNoteheadPadding; } } let width = this.getGlyphWidth() + this.leftDisplacedHeadPx + this.rightDisplacedHeadPx + noteHeadPadding; if (this.shouldDrawFlag() && this.stem_direction === Stem.UP) { width += this.getGlyphWidth(); } this.setWidth(width); this.preFormatted = true; } getNoteHeadBounds() { let yTop = +Infinity; let yBottom = -Infinity; let nonDisplacedX; let displacedX; let highestLine = this.checkStave().getNumLines(); let lowestLine = 1; let highestDisplacedLine; let lowestDisplacedLine; let highestNonDisplacedLine = highestLine; let lowestNonDisplacedLine = lowestLine; this._noteHeads.forEach((notehead) => { const line = notehead.getLine(); const y = notehead.getY(); yTop = Math.min(y, yTop); yBottom = Math.max(y, yBottom); if (displacedX === undefined && notehead.isDisplaced()) { displacedX = notehead.getAbsoluteX(); } if (nonDisplacedX === undefined && !notehead.isDisplaced()) { nonDisplacedX = notehead.getAbsoluteX(); } highestLine = Math.max(line, highestLine); lowestLine = Math.min(line, lowestLine); if (notehead.isDisplaced()) { highestDisplacedLine = highestDisplacedLine === undefined ? line : Math.max(line, highestDisplacedLine); lowestDisplacedLine = lowestDisplacedLine === undefined ? line : Math.min(line, lowestDisplacedLine); } else { highestNonDisplacedLine = Math.max(line, highestNonDisplacedLine); lowestNonDisplacedLine = Math.min(line, lowestNonDisplacedLine); } }, this); return { y_top: yTop, y_bottom: yBottom, displaced_x: displacedX, non_displaced_x: nonDisplacedX, highest_line: highestLine, lowest_line: lowestLine, highest_displaced_line: highestDisplacedLine, lowest_displaced_line: lowestDisplacedLine, highest_non_displaced_line: highestNonDisplacedLine, lowest_non_displaced_line: lowestNonDisplacedLine, }; } getNoteHeadBeginX() { return this.getAbsoluteX() + this.x_shift; } getNoteHeadEndX() { const xBegin = this.getNoteHeadBeginX(); return xBegin + this.getGlyphWidth(); } get noteHeads() { return this._noteHeads.slice(); } get note_heads() { showDeprecationWarningForNoteHeads(); return this.noteHeads; } drawLedgerLines() { const stave = this.checkStave(); const { glyphProps, render_options: { stroke_px }, } = this; const ctx = this.checkContext(); const width = glyphProps.getWidth() + stroke_px * 2; const doubleWidth = 2 * (glyphProps.getWidth() + stroke_px) - Stem.WIDTH / 2; if (this.isRest()) return; if (!ctx) { throw new RuntimeError('NoCanvasContext', "Can't draw without a canvas context."); } const { highest_line, lowest_line, highest_displaced_line, highest_non_displaced_line, lowest_displaced_line, lowest_non_displaced_line, displaced_x, non_displaced_x, } = this.getNoteHeadBounds(); if (highest_line < 6 && lowest_line > 0) return; const min_x = Math.min(displaced_x !== null && displaced_x !== void 0 ? displaced_x : 0, non_displaced_x !== null && non_displaced_x !== void 0 ? non_displaced_x : 0); const drawLedgerLine = (y, normal, displaced) => { let x; if (displaced && normal) x = min_x - stroke_px; else if (normal) x = (non_displaced_x !== null && non_displaced_x !== void 0 ? non_displaced_x : 0) - stroke_px; else x = (displaced_x !== null && displaced_x !== void 0 ? displaced_x : 0) - stroke_px; const ledgerWidth = normal && displaced ? doubleWidth : width; ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + ledgerWidth, y); ctx.stroke(); }; const style = Object.assign(Object.assign({}, stave.getDefaultLedgerLineStyle()), this.getLedgerLineStyle()); this.applyStyle(ctx, style); for (let line = 6; line <= highest_line; ++line) { const normal = non_displaced_x !== undefined && line <= highest_non_displaced_line; const displaced = highest_displaced_line !== undefined && line <= highest_displaced_line; drawLedgerLine(stave.getYForNote(line), normal, displaced); } for (let line = 0; line >= lowest_line; --line) { const normal = non_displaced_x !== undefined && line >= lowest_non_displaced_line; const displaced = lowest_displaced_line !== undefined && line >= lowest_displaced_line; drawLedgerLine(stave.getYForNote(line), normal, displaced); } this.restoreStyle(ctx, style); } drawModifiers(noteheadParam) { const ctx = this.checkContext(); for (let i = 0; i < this.modifiers.length; i++) { const modifier = this.modifiers[i]; const index = modifier.checkIndex(); const notehead = this._noteHeads[index]; if (notehead == noteheadParam) { const noteheadStyle = notehead.getStyle(); notehead.applyStyle(ctx, noteheadStyle); modifier.setContext(ctx); modifier.drawWithStyle(); notehead.restoreStyle(ctx, noteheadStyle); } } } shouldDrawFlag() { const hasStem = this.stem !== undefined; const hasFlag = this.glyphProps.flag == true; const hasNoBeam = this.beam === undefined; return hasStem && hasFlag && hasNoBeam; } drawFlag() { var _a, _b, _c, _d, _e; const ctx = this.checkContext(); if (!ctx) { throw new RuntimeError('NoCanvasContext', "Can't draw without a canvas context."); } if (this.shouldDrawFlag()) { const { y_top, y_bottom } = this.getNoteHeadBounds(); const noteStemHeight = this.stem.getHeight(); const flagX = this.getStemX(); const flagY = this.getStemDirection() === Stem.DOWN ? y_top - noteStemHeight + 2 - (this.glyphProps ? this.glyphProps.stem_down_extension : 0) * this.getStaveNoteScale() - ((_b = (_a = this.flag) === null || _a === void 0 ? void 0 : _a.getMetrics().y_shift) !== null && _b !== void 0 ? _b : 0) * (1 - this.getStaveNoteScale()) : y_bottom - noteStemHeight - 2 + (this.glyphProps ? this.glyphProps.stem_up_extension : 0) * this.getStaveNoteScale() - ((_d = (_c = this.flag) === null || _c === void 0 ? void 0 : _c.getMetrics().y_shift) !== null && _d !== void 0 ? _d : 0) * (1 - this.getStaveNoteScale()); (_e = this.flag) === null || _e === void 0 ? void 0 : _e.render(ctx, flagX, flagY); } } drawNoteHeads() { const ctx = this.checkContext(); this._noteHeads.forEach((notehead) => { notehead.applyStyle(ctx); ctx.openGroup('notehead', notehead.getAttribute('id'), { pointerBBox: true }); notehead.setContext(ctx).draw(); this.drawModifiers(notehead); ctx.closeGroup(); notehead.restoreStyle(ctx); }); } drawStem(stemOptions) { const ctx = this.checkContext(); if (stemOptions) { this.setStem(new Stem(stemOptions)); } if (this.shouldDrawFlag() && this.stem) { this.stem.adjustHeightForFlag(); } if (this.stem) { this.stem.setContext(ctx).draw(); } } getStaveNoteScale() { return 1.0; } getStemExtension() { const super_stem_extension = super.getStemExtension(); if (!this.glyphProps.stem) { return super_stem_extension; } const stem_direction = this.getStemDirection(); if (stem_direction !== this.calculateOptimalStemDirection()) { return super_stem_extension; } let mid_line_distance; const MIDDLE_LINE = 3; if (stem_direction === Stem.UP) { mid_line_distance = MIDDLE_LINE - this.maxLine; } else { mid_line_distance = this.minLine - MIDDLE_LINE; } const lines_over_octave_from_mid_line = mid_line_distance - 3.5; if (lines_over_octave_from_mid_line <= 0) { return super_stem_extension; } const stave = this.getStave(); let spacing_between_lines = 10; if (stave != undefined) { spacing_between_lines = stave.getSpacingBetweenLines(); } return super_stem_extension + lines_over_octave_from_mid_line * spacing_between_lines; } draw() { if (this.render_options.draw === false) return; if (this.ys.length === 0) { throw new RuntimeError('NoYValues', "Can't draw note without Y values."); } const ctx = this.checkContext(); const xBegin = this.getNoteHeadBeginX(); const shouldRenderStem = this.hasStem() && !this.beam; this._noteHeads.forEach((notehead) => notehead.setX(xBegin)); if (this.stem) { const stemX = this.getStemX(); this.stem.setNoteHeadXBounds(stemX, stemX); } L('Rendering ', this.isChord() ? 'chord :' : 'note :', this.keys); this.applyStyle(); ctx.openGroup('stavenote', this.getAttribute('id')); this.drawLedgerLines(); if (shouldRenderStem) this.drawStem(); this.drawNoteHeads(); this.drawFlag(); ctx.closeGroup(); this.restoreStyle(); this.setRendered(); } } StaveNote.DEBUG = false; export { StaveNote };