@stringsync/vexml
Version:
MusicXML to Vexflow
372 lines (371 loc) • 16.9 kB
JavaScript
"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;