UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

372 lines (371 loc) 16.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Stave = void 0; const vexflow = __importStar(require("vexflow")); const util = __importStar(require("../util")); const spatial_1 = require("../spatial"); const voice_1 = require("./voice"); const clef_1 = require("./clef"); const time_1 = require("./time"); const key_1 = require("./key"); const METRONOME_TOP_PADDING = 8; class Stave { config; log; document; key; position; constructor(config, log, document, key, position) { this.config = config; this.log = log; this.document = document; this.key = key; this.position = position; } render() { const staveLineCount = this.document.getStave(this.key).signature.lineCount; const isTabStave = this.document.isTabStave(this.key); const vexflowStave = isTabStave ? new vexflow.TabStave(this.position.x, this.position.y, 0, { numLines: staveLineCount }) : new vexflow.Stave(this.position.x, this.position.y, 0, { numLines: staveLineCount }); const { voiceRenders, vexflowMultiMeasureRest } = this.renderVoicesOrVexflowMultiMeasureRest(vexflowStave); const startClefRender = this.renderStartClef(vexflowStave); const endClefRender = this.renderEndClef(vexflowStave); const timeRender = this.renderTime(vexflowStave); const keyRender = this.renderKeySignature(vexflowStave); this.renderMeasureLabel(vexflowStave); this.renderBarlines(vexflowStave); this.renderEndingBrackets(vexflowStave); this.renderMetronome(vexflowStave); this.renderRepetitionSymbol(vexflowStave); return { type: 'stave', key: this.key, rect: spatial_1.Rect.empty(), // placeholder intrinsicRect: spatial_1.Rect.empty(), // placeholder playableRect: spatial_1.Rect.empty(), // placeholder excessHeight: 0, // placeholder voiceRenders, vexflowMultiMeasureRest, startClefRender, endClefRender, keyRender, timeRender, vexflowStave, }; } renderVoicesOrVexflowMultiMeasureRest(vexflowStave) { const voiceRenders = new Array(); const voiceCount = this.document.getVoiceCount(this.key); let vexflowMultiMeasureRest = null; const multiRestCount = this.document.getMeasureMultiRestCount(this.key); if (multiRestCount > 1) { // A multirest that spans a single measure should have a whole rest associated with it. We only want to render // multirests that span multiple measures. vexflowMultiMeasureRest = new vexflow.MultiMeasureRest(multiRestCount, { numberOfMeasures: 1 }); } else { for (let voiceIndex = 0; voiceIndex < voiceCount; voiceIndex++) { const key = { ...this.key, voiceIndex }; const voiceRender = new voice_1.Voice(this.config, this.log, this.document, key).render(); // Empty voices are problematic for vexflow, so we discard them as soon as we can. if (voiceRender.entryRenders.length > 0) { voiceRenders.push(voiceRender); } } } // Associate the vexflow voice and note data with the stave. for (const voiceRender of voiceRenders) { for (const vexflowVoice of voiceRender.vexflowVoices) { vexflowVoice.setStave(vexflowStave); } } if (vexflowMultiMeasureRest) { vexflowMultiMeasureRest.setStave(vexflowStave); } const isTabStave = this.document.isTabStave(this.key); if (!isTabStave) { this.adjustStems(voiceRenders); } return { voiceRenders, vexflowMultiMeasureRest }; } renderStartClef(vexflowStave) { const isClefEnabled = this.document.isTabStave(this.key) ? this.config.SHOW_TAB_CLEF : true; if (!isClefEnabled) { return null; } const isFirstMeasure = this.document.isFirstMeasure(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); if (isFirstMeasure && isFirstFragment) { const clefRender = new clef_1.Clef(this.config, this.log, this.document, this.key).render(); vexflowStave.addModifier(clefRender.vexflowClef); return clefRender; } return null; } renderEndClef(vexflowStave) { const isClefEnabled = this.document.isTabStave(this.key) ? this.config.SHOW_TAB_CLEF : true; if (!isClefEnabled) { return null; } const currentClef = this.document.getStave(this.key).signature.clef; const nextClef = this.document.getNextPlayedStave(this.key)?.signature.clef; const willClefChange = nextClef && currentClef.sign !== nextClef?.sign; if (willClefChange) { const clefRender = new clef_1.Clef(this.config, this.log, this.document, this.key).render(); vexflowStave.addEndModifier(clefRender.vexflowClef); return clefRender; } return null; } renderTime(vexflowStave) { const isTabStave = this.document.isTabStave(this.key); const isTimeEnabled = isTabStave ? this.config.SHOW_TAB_TIME_SIGNATURE : true; if (!isTimeEnabled) { return null; } const isFirstSystem = this.document.isFirstSystem(this.key); const isFirstMeasure = this.document.isFirstMeasure(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); const isAbsolutelyFirst = isFirstSystem && isFirstMeasure && isFirstFragment; const currentTime = this.document.getStave(this.key).signature.time; const previousTime = this.document.getPreviouslyPlayedStave(this.key)?.signature.time; const didTimeChange = currentTime.symbol !== previousTime?.symbol || currentTime.components.length !== previousTime?.components.length || currentTime.components.some((c, i) => c.numerator !== previousTime?.components[i].numerator || c.denominator !== previousTime?.components[i].denominator); if (isAbsolutelyFirst || didTimeChange) { const timeRender = new time_1.Time(this.config, this.log, this.document, this.key).render(); for (const vexflowTimeSignature of timeRender.vexflowTimeSignatures) { vexflowStave.addModifier(vexflowTimeSignature); } return timeRender; } return null; } renderMeasureLabel(vexflowStave) { const measureLabel = this.document.getMeasure(this.key).label; if (this.shouldShowMeasureLabel() && measureLabel) { vexflowStave.setMeasure(measureLabel); } } shouldShowMeasureLabel() { const isFirstPart = this.document.isFirstPart(this.key); const isFirstStave = this.document.isFirstStave(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); if (!isFirstPart || !isFirstStave || !isFirstFragment) { return false; } switch (this.config.MEASURE_LABELING_SCHEME) { case 'all': return true; case 'every2': return this.key.measureIndex % 2 === 0; case 'every3': return this.key.measureIndex % 3 === 0; case 'system': return this.key.measureIndex === 0; case 'none': return false; } } renderBarlines(vexflowStave) { const isFirstMeasure = this.document.isFirstMeasure(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); const isLastFragment = this.document.isLastFragment(this.key); const isTabStave = this.document.isTabStave(this.key); const startVexflowBarlineType = this.toVexflowBarlineType(this.document.getMeasure(this.key).startBarlineStyle); if (isTabStave || (!isFirstMeasure && isFirstFragment)) { vexflowStave.setBegBarType(startVexflowBarlineType); } else { vexflowStave.setBegBarType(vexflow.Barline.type.NONE); } const endVexflowBarlineType = this.toVexflowBarlineType(this.document.getMeasure(this.key).endBarlineStyle); if (isLastFragment) { vexflowStave.setEndBarType(endVexflowBarlineType); } else { vexflowStave.setEndBarType(vexflow.Barline.type.NONE); } } toVexflowBarlineType(barlineStyle) { switch (barlineStyle) { case 'single': return vexflow.Barline.type.SINGLE; case 'double': return vexflow.Barline.type.DOUBLE; case 'end': return vexflow.Barline.type.END; case 'repeatstart': return vexflow.Barline.type.REPEAT_BEGIN; case 'repeatend': return vexflow.Barline.type.REPEAT_END; case 'repeatboth': return vexflow.Barline.type.REPEAT_BOTH; case 'none': return vexflow.Barline.type.NONE; default: return vexflow.Barline.type.SINGLE; } } renderEndingBrackets(vexflowStave) { const isFirstPart = this.document.isFirstPart(this.key); const isFirstStave = this.document.isFirstStave(this.key); if (!isFirstPart || !isFirstStave) { return; } const jumps = this.document.getMeasure(this.key).jumps; const ending = jumps.find((jump) => jump.type === 'repeatending'); if (!ending) { return; } let vexflowVoltaType = vexflow.VoltaType.NONE; switch (ending.endingBracketType) { case 'begin': vexflowVoltaType = vexflow.VoltaType.BEGIN; break; case 'mid': vexflowVoltaType = vexflow.VoltaType.MID; break; case 'end': vexflowVoltaType = vexflow.VoltaType.END; break; case 'both': vexflowVoltaType = vexflow.VoltaType.BEGIN_END; break; } vexflowStave.setVoltaType(vexflowVoltaType, ending.label, 0); } /** * Adjusts the stems based on the first non-rest note of each voice by mutating the voice entry data in place. * * This method does _not_ change any stem directions that were explicitly defined. */ adjustStems(voiceRenders) { const voices = voiceRenders.filter((voice) => voice.entryRenders.some((entry) => entry.type === 'note')); if (voices.length <= 1) { return; } util.sortBy(voices, (voice) => { const vexflowStaveNotes = voice.entryRenders .filter((e) => e.type === 'note') .map((e) => e.vexflowNote) .filter((v) => v instanceof vexflow.StaveNote); const keyLine = vexflowStaveNotes.at(0)?.getKeyLine(0); return typeof keyLine === 'number' ? -keyLine : 0; }); const top = voices.at(0); const middle = voices.slice(1, -1); const bottom = voices.at(-1); for (const entry of top.entryRenders) { if (entry.type == 'note' && entry.stemDirection === 'auto' && entry.vexflowNote instanceof vexflow.StaveNote) { entry.stemDirection = 'up'; entry.vexflowNote.setStemDirection(vexflow.Stem.UP); } } for (const entry of middle.flatMap((v) => v.entryRenders)) { if (entry.type == 'note' && entry.stemDirection === 'auto' && entry.vexflowNote instanceof vexflow.StaveNote) { entry.stemDirection = 'none'; entry.vexflowNote.setStemDirection(undefined); } } for (const entry of bottom.entryRenders) { if (entry.type == 'note' && entry.stemDirection === 'auto' && entry.vexflowNote instanceof vexflow.StaveNote) { entry.stemDirection = 'down'; entry.vexflowNote.setStemDirection(vexflow.Stem.DOWN); } } } renderKeySignature(vexflowStave) { const isTabStave = this.document.isTabStave(this.key); if (isTabStave) { return null; } const isFirstMeasure = this.document.isFirstMeasure(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); const isFirstMusicalMeasureFragment = isFirstMeasure && isFirstFragment; const currentKey = this.document.getStave(this.key).signature.key; const previousKey = this.document.getPreviouslyPlayedStave(this.key)?.signature.key; const didKeyChange = currentKey.rootNote !== previousKey?.rootNote || currentKey.fifths !== previousKey?.fifths || currentKey.mode !== previousKey?.mode; if (isFirstMusicalMeasureFragment || didKeyChange) { const keyRender = new key_1.Key(this.config, this.log, this.document, this.key).render(); vexflowStave.addModifier(keyRender.vexflowKeySignature); return keyRender; } return null; } renderMetronome(vexflowStave) { const isFirstSystem = this.document.isFirstSystem(this.key); const isFirstMeasure = this.document.isFirstMeasure(this.key); const isFirstFragment = this.document.isFirstFragment(this.key); const isFirstPart = this.document.isFirstPart(this.key); const isFirstStave = this.document.isFirstStave(this.key); const isAbsolutelyFirst = isFirstSystem && isFirstMeasure && isFirstFragment; const currentMetronome = this.document.getFragment(this.key).signature.metronome; const previousMetronome = this.document.getPreviousFragment(this.key)?.signature.metronome; const didMetronomeChange = previousMetronome && (currentMetronome.displayBpm !== previousMetronome.displayBpm || currentMetronome.dots !== previousMetronome.dots || currentMetronome.dots2 !== previousMetronome.dots2 || currentMetronome.duration !== previousMetronome.duration); const hasMetronome = currentMetronome.displayBpm || currentMetronome.dots || currentMetronome.dots2 || currentMetronome.duration; if (hasMetronome && isFirstPart && isFirstStave && (isAbsolutelyFirst || didMetronomeChange)) { vexflowStave.setTempo({ ...currentMetronome, bpm: currentMetronome.displayBpm }, -METRONOME_TOP_PADDING); } } renderRepetitionSymbol(vexflowStave) { const isFirstPart = this.document.isFirstPart(this.key); const isFirstStave = this.document.isFirstStave(this.key); if (!isFirstPart || !isFirstStave) { return; } const measure = this.document.getMeasure(this.key); for (const repetitionSymbol of measure.repetitionSymbols) { switch (repetitionSymbol) { case 'segno': vexflowStave.setRepetitionType(vexflow.Repetition.type.SEGNO_LEFT); break; case 'coda': vexflowStave.setRepetitionType(vexflow.Repetition.type.CODA_LEFT); break; } } } } exports.Stave = Stave;