mscx2ly
Version:
Tool to render lilypond code from a MuseScore save file
93 lines (83 loc) • 3.21 kB
JavaScript
import fs from 'fs';
import xml2js from 'xml2js';
import { renderLyricTimings } from './lib.js';
import admZip from 'adm-zip';
import { XmlWrapper } from './xml_wrapper.js';
function detectVersion (data) {
const createdWith = data.get('programVersion');
const [ programMajor, programMinor, programPatch] = createdWith.split('.');
const formatVersion = data.get('version');
const [ formatMajor, formatMinor] = formatVersion.split('.');
return {
createdWith: {
major: parseInt(programMajor, 10),
minor: parseInt(programMinor, 10),
patch: parseInt(programPatch, 10),
text: createdWith
},
formatVersion: {
major: parseInt(formatMajor, 10),
minor: parseInt(formatMinor, 10),
text: formatVersion
}
};
}
const parser = new xml2js.Parser({ preserveChildrenOrder: true, explicitChildren: true });
async function parseMuseScoreXML(sourceFile) {
let source;
try {
const zip = new admZip(sourceFile);
const zipEntries = zip.getEntries();
const mscxEntry = zipEntries.find(entry => entry.entryName.endsWith('.mscx'));
// read source file
// const source = fs.readFileSync(sourceFile, 'utf8');
source = zip.readAsText(mscxEntry);
}
catch (e) {
console.log('Could not read source file as zip. Assuming it is an uncompressed MuseScore file.');
source = fs.readFileSync(sourceFile, 'utf8');
}
let json;
try {
// convert to json
const parser = new xml2js.Parser({ preserveChildrenOrder : true, explicitChildren: true});
json = await parser.parseStringPromise(source);
}
catch (e) {
console.error('Could not parse source file. Is it a MuseScore file? Exiting.');
process.exit(1);
}
let result;
try {
// convert to a format we can interact with
const data = new XmlWrapper(json.museScore);
const versions = detectVersion(data);
if (versions.createdWith.major < 4 || versions.formatVersion.major < 4) {
console.warn(`This file was created with an older version of MuseScore ${versions.createdWith.text}. Fingers crossed!`);
}
result = renderLyricTimings(data);
}
catch (e) {
console.log('Error:', e);
const errorlog = e.stack;
fs.writeFileSync('error.log', errorlog);
console.error('Could not convert source file. Exiting.');
process.exit(1);
}
// write out json
fs.writeFileSync('timing.json', JSON.stringify(result, null, 2));
}
function getTempo(score) {
const tempoElements = score.Tempo || [];
if (tempoElements.length > 0) {
return parseInt(tempoElements[0].text[0]);
}
return 120; // Default to 120 BPM
}
function convertTicksToSeconds(ticks, ticksPerQuarter, tempo) {
const beatsPerSecond = tempo / 60;
const secondsPerTick = 1 / (beatsPerSecond * ticksPerQuarter);
return ticks * secondsPerTick;
}
// Example usage
parseMuseScoreXML("tests/calmes_for_lyrics.mscz");