UNPKG

vexflow-musicxml

Version:

MusicXml Parser for vexflow

141 lines (121 loc) 4.64 kB
/** * @file * @description Parser and renderer for Music XML files to Vex Flow * @author {@link mailto:neumann.benni@gmail.com|neumann.benni@gmail.com} * @version 0.1 */ import Vex from 'vexflow'; import { MusicXml } from './xml/MusicXml.js'; import { Measure } from './vex/Measure.js'; const { Flow } = Vex; /** * MusicXmlRenderer * @param */ export class MusicXmlRenderer { constructor(data, canvas) { this.musicXml = new MusicXml(data); console.profileEnd('parsing'); console.log(this.musicXml); if (false) { const part = 1; const from = 1; const to = 2; this.musicXml.Parts = [this.musicXml.Parts[part]]; this.musicXml.Parts[0].Measures = this.musicXml.Parts[0].Measures.slice(from, to); } this.mStartMeasure = 0; this.mStopMeasure = this.musicXml.Parts[0].Measures.length; this.isSvg = !(canvas instanceof HTMLCanvasElement); this.canvas = canvas; // eslint-disable-next-line max-len this.renderer = new Flow.Renderer(this.canvas, this.isSvg ? Flow.Renderer.Backends.SVG : Flow.Renderer.Backends.CANVAS); // Properties for rendering this.ctx = this.renderer.getContext(); this.Drawables = []; // Some formatting constants this.staveSpace = 100; this.staveXOffset = 20; this.staveYOffset = 20; this.calculateLayout(); console.time('parse'); this.parse().render(); console.timeEnd('parse'); } getScoreHeight() { return this.systemSpace * this.format.linesPerPage; } // https://github.com/0xfe/vexflow/blob/1.2.83/tests/formatter_tests.js line 271 parse() { const allParts = this.musicXml.Parts; for (const [p] of allParts.entries()) { const part = allParts[p]; for (let m = this.mStartMeasure; m < this.mStopMeasure; m++) { const measure = part.Measures[m]; this.Drawables.push(new Measure(measure, this.format, this.ctx)); } } // Connect the first measures in a line const MeasuresFirstInLine = this.Drawables.filter(m => m.firstInLine); const MeasureNumsFirstInLine = new Set(MeasuresFirstInLine.map(m => m.xmlMeasure.Number)); MeasureNumsFirstInLine.forEach((n) => { // Get all the measures from all parts with the same starting number. // Get their stavelist(s) and concatenate them in one array. Now we have an // array of staves that are in the first line. const system = [].concat(...MeasuresFirstInLine.filter(m => m.xmlMeasure.Number === n).map(m => m.staveList)); for (let s = 0; s < system.length - 1; s++) { // It actually doesn't matter which measure we use for the connectors MeasuresFirstInLine[0].addConnector(system[s], system[s + 1], Flow.StaveConnector.type.SINGLE_LEFT); } }); return this; } clear() { $(this.ctx.svg).empty(); } set StartMeasure(value) { // TODO: Recalculate layout and rerender this.mStartMeasure = value; this.calculateLayout(); this.Drawables = []; this.clear(); this.parse().render(); } set StopMeasure(value) { // TODO: Recalculate layout and rerender this.mStopMeasure = value; this.calculateLayout(); this.clear(); this.Drawables = []; this.parse().render(); } calculateLayout() { this.stavesPerSystem = this.musicXml.Parts .map(p => p.getAllStaves()) // get all the staves in a part .reduce((e, ne) => e + ne); // sum them up this.width = this.isSvg ? parseInt(this.canvas.getAttribute('width'), 10) : this.canvas.width; const startWidth = document.body.clientWidth < 250 ? document.body.clientWidth : 250; this.systemSpace = this.staveSpace * this.stavesPerSystem + 50; this.measuresPerStave = Math.floor(this.width / startWidth); // measures per stave this.staveWidth = Math.round(this.width / this.measuresPerStave) - this.staveXOffset; this.format = { staveSpace: this.staveSpace, staveXOffset: this.staveXOffset, staveYOffset: this.staveYOffset, systemSpace: this.systemSpace, // FIXME: Refactor to stavesPerMeasure measuresPerStave: this.measuresPerStave, totalMeasures: (this.mStopMeasure - this.mStartMeasure), staveWidth: this.staveWidth, stavesPerSystem: this.musicXml.getStavesPerSystem(), width: this.width, linesPerPage: Math.ceil((this.mStopMeasure - this.mStartMeasure) + 1 / this.measuresPerStave), }; // Set the SVG viewbox according to the calculated layout const vb = [0, 0, this.width, this.getScoreHeight()]; this.ctx.setViewBox(vb); } render() { this.Drawables.forEach(d => d.draw()); } }