vexmxl
Version:
MusicXML binding for VexFlow
194 lines (193 loc) • 8.94 kB
JavaScript
define(["require", "exports", "musicxml-interfaces", "./vexmxl.tab", "vexflow"], function (require, exports, mxl, vexmxl_tab_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var Renderer = Vex.Flow.Renderer;
const timeMap = {
4: vexmxl_tab_1.Duration.WHOLE,
3: vexmxl_tab_1.Duration.HALF_DOT,
2: vexmxl_tab_1.Duration.HALF,
1.5: vexmxl_tab_1.Duration.QUARTER_DOT,
1: vexmxl_tab_1.Duration.QUARTER,
0.75: vexmxl_tab_1.Duration.EIGHTH_DOT,
0.5: vexmxl_tab_1.Duration.EIGHTH,
0.375: vexmxl_tab_1.Duration.T16_DOT,
0.25: vexmxl_tab_1.Duration.T16,
0.1875: vexmxl_tab_1.Duration.T32_DOT,
0.125: vexmxl_tab_1.Duration.T32,
0.09375: vexmxl_tab_1.Duration.T64_DOT,
0.0625: vexmxl_tab_1.Duration.T64 // T32 / 2
};
/**
* Parses the vextab and renders a SVG or CANVAS engraving of the tablature
* @param {Tablature} tab the tab to be parsed
* @param {HTMLElement} div the dom element to be filled with the result
* @param {boolean} canvas wether to use the canvas or not
* @returns {VexTab} the VexTab object containing the result's infos
*/
function displayTablature(tab, div, canvas) {
let artist = new Artist(0, 0, tab.width());
let vt = new VexTab(artist);
let renderer = new Renderer(div, canvas ? 1 /* CANVAS */ : 3 /* SVG */);
let parsed = tab.toVextab();
try {
vt.parse(parsed);
artist.render(renderer);
}
catch (e) {
console.error(e);
}
return vt;
}
/**
* Generates an SVG element from a tablature element
* @param {Tablature} tab
* @returns {{svg: SVGElement; vt: VexTab}} the SVG element and the generated VexTab
*/
function generateSVG(tab) {
let div = document.createElement("div");
let vt = displayTablature(tab, div, false);
return { svg: div.children[0], vt: vt };
}
exports.generateSVG = generateSVG;
/**
* Generates a Canvas element from a tablature element
* @param {Tablature} tab
* @returns {{canvas: HTMLCanvasElement; vt: VexTab}} the Canvas element and the generated VexTab
*/
function generateCanvas(tab) {
let canvas = document.createElement("canvas");
console.warn("Canvas size is limited, e.g. Chrome's canvas can only be 32,767x32,767 pixels. " +
"If it exceeds, nothing will be displayed.");
let vt = displayTablature(tab, canvas, true);
return { canvas: canvas, vt: vt };
}
exports.generateCanvas = generateCanvas;
/**
* Generates an image from a tablature element
* @param {Tablature} tab
* @returns {{img: HTMLImageElement; vt: VexTab}} the Image element and the generated VexTab
*/
function generateImage(tab) {
let svg = generateSVG(tab); // uses SVG rendering instead of canvas because of size limitations
let svgData = new XMLSerializer().serializeToString(svg.svg);
let data = "data:image/svg+xml;base64," + btoa(svgData);
let img = document.createElement("img");
img.setAttribute('src', data);
return { img: img, vt: svg.vt };
}
exports.generateImage = generateImage;
/**
* Parses the musicxml and produces a tablature
* @param {string} xml the musicxml file as a string
* @param {boolean} displayTab wether to display the tablature or not
* @param {boolean} displayStave wether to display the music sheet or not
* @returns {Promise<Tablature>} a future tablature
*/
function parseXML(xml, displayTab, displayStave) {
return Promise.resolve(null).then((_) => {
let doc = mxl.parseScore(xml);
// @Future let the user choose the part to play (and detect instrument)
let partName = doc.partList[0].id;
let times;
let metronome;
// The first measure contains general infos on the tablature
// @Future read these infos on all tablatures, these mecanisms can change, the time signature could change etc.
for (let obj of doc.measures[0].parts[partName]) {
if (obj.hasOwnProperty("directionTypes")) {
metronome = obj.directionTypes[0].metronome;
}
if (obj.hasOwnProperty("times")) {
times = obj.times[0];
}
}
let bpm = +metronome.perMinute.data;
let title = doc.movementTitle;
let time = new vexmxl_tab_1.TimeSignature(+times.beats[0], times.beatTypes[0]);
let divisions = 1; // Number of notes in measure
let tab = new vexmxl_tab_1.Tablature(title, time, bpm, displayTab, displayStave);
for (let docMeasure of doc.measures) {
let measure = new vexmxl_tab_1.Measure();
let chord; // constructs the chord iteratively
for (let elem of docMeasure.parts[partName]) {
if (elem._class === "Attributes") {
let attributes = elem;
if (attributes.divisions) {
divisions = attributes.divisions; // override divisions
}
}
else if (elem._class === "Note") {
let note = elem;
// calculates the duration based on defined divisions and the duration
let duration = timeMap[1 / divisions * note.duration];
if (!duration) {
console.error(`Unable to parse the duration of the note. Duration input: ${note.duration}, divisions: ${divisions}`);
continue;
}
if (note.rest) {
if (chord && chord.notEmpty()) {
measure.addTime(chord); // validates previously made chord
chord = undefined; // for next note
}
measure.addTime(new vexmxl_tab_1.Rest(duration));
}
else if (note.pitch) {
let tech = note.notations[0].technicals[0]; // read note's tune
if (note.chord) {
// a chord must have been previously initialized
if (!chord)
throw new Error("Chord element has not been initialized properly");
}
else {
if (chord && chord.notEmpty()) {
measure.addTime(chord); // current note is for another chord, validates previous one
}
chord = new vexmxl_tab_1.Chord(duration); // initialize a new chord
}
let vNote = new vexmxl_tab_1.Note(tech.fret.fret, tech.string.stringNum);
if (tech.bend) {
vNote.bend(+tech.bend.bendAlter);
}
chord.addNote(vNote); // adds note to chord
}
else {
throw new Error("note has not been recognized");
}
}
}
if (chord && chord.notEmpty()) {
measure.addTime(chord);
}
if (measure.notEmpty()) {
tab.addMeasure(measure);
}
}
return tab;
});
}
/**
* Parse the MusicXML to a Tablature element for further displaying from a string value of the xml file
* @param {string} xml
* @param {boolean} displayTab
* @param {boolean} displayStave
* @returns {Promise<Tablature>}
*/
function parseXMLFromString(xml, displayTab = true, displayStave = true) {
return parseXML(xml, displayTab, displayStave);
}
exports.parseXMLFromString = parseXMLFromString;
/**
* Parse the MusicXML to a Tablature element for further displaying from a filepath to the xml file
* @param {string} path
* @param {boolean} displayTab
* @param {boolean} displayStave
* @returns {Promise<Tablature>}
*/
function parseXMLFromFile(path, displayTab = true, displayStave = true) {
return fetch(path).then((response) => {
return response.text();
}).then(str => {
return parseXML(str, displayTab, displayStave);
});
}
exports.parseXMLFromFile = parseXMLFromFile;
});