Hi there!
Since the ingame notation editor is somewhat painful to use, i wrote a script which converts standard MusicXML to a PlaneShift compatible format (PS uses a subset of MusicXML). I'm not claiming this will work for everyone with every MusicXML file, but it works for me and i thought i'd share it.
-- Edit: There's now an easier method that does not require node.js, see below --1) Software neededFirst, you need a notation editor which can write MusicXML files. Personally, i use
MuseScore. Then, you'll need
node.js and the
jsdom library. On Linux, you can install jsdom with
npm install jsdom
after installing node.js. Node's npm package manager is kind of weird and i haven't completely figured out when node considers something to be installed and when not :|
2) The scriptCopy the following into a file called
converter.js in your
musicalsheets directory (on Linux it's ~/.PlaneShift/musicalsheets/)
#!/usr/bin/env node
var jsdom = require("jsdom");
var fs = require('fs');
console.log("MusicXML to PlaneShift converter by Firiban - version 0.2");
console.log("parsing file "+process.argv[2]+"...");
// read file, parse it, create outfile with header
var infile = jsdom.jsdom(fs.readFileSync(process.argv[2], "utf8"));
var outfile = '<score-partwise version="2.0"><part-list><score-part id="P1"><part-name>'+process.argv[2].substr(0,process.argv[2].length-4)+'</part-name></score-part></part-list><part id="P1">';
var measures = infile.getElementsByTagName("measure");
console.log("Sorting the stuff...");
// This loop does the work: Iterates through the measures and copies the important stuff (read: "PlaneShift compatible tags") to outfile
var i=1;
var j=0;
var k=0;
var l=0;
var alter=0;
var duration=1;
var multiplier=1;
for (var i=0;i<measures.length;i++) {
outfile += '<measure number="'+(i+1)+'">';
if (i==0) {
multiplier = (4 / infile.getElementsByTagName("divisions")[0].childNodes[0].__nodeValue);
outfile += '<attributes><divisions>4</divisions><key><fifths>'+((infile.getElementsByTagName("fifths").length>0)?infile.getElementsByTagName("fifths")[0].childNodes[0].__nodeValue:"0")+'</fifths></key><time><beats>'+infile.getElementsByTagName("beats")[0].childNodes[0].__nodeValue+'</beats><beat-type>'+infile.getElementsByTagName("beat-type")[0].childNodes[0].__nodeValue+'</beat-type></time></attributes><direction>';
if (infile.getElementsByTagName("sound").length>0) {
outfile += '<sound tempo="'+infile.getElementsByTagName("sound")[0].attributes[0].value+'"/>';
} else {
outfile += '<sound tempo="90"/>';
}
outfile += "</direction>";
}
//console.log(measures[i]);
for (j=0;j<measures[i].children.length;j++) {
// we only need the notes, no stupid <print> stuff or text nodes (whitespaces/tabs/newlines etc.)
if (measures[i].children[j].tagName=="NOTE") {
outfile +="<note>";
duration=1;
// iterate through note's parameters and copy the neccessary ones (sort out <stem> etc., since they are not used by PS)
for (k=0;k<measures[i].children[j].children.length;k++) {
if (measures[i].children[j].children[k].tagName=="REST") {
outfile += "<rest/>";
} else if (measures[i].children[j].children[k].tagName=="CHORD") {
outfile += "<chord/>";
} else if (measures[i].children[j].children[k].tagName=="DURATION") {
duration = measures[i].children[j].children[k].textContent;
} else if (measures[i].children[j].children[k].tagName=="PITCH") {
outfile += "<pitch>";
alter = 0;
for (l=0;l<measures[i].children[j].children[k].children.length;l++) {
if ((measures[i].children[j].children[k].children[l].tagName=='STEP') || (measures[i].children[j].children[k].children[l].tagName=='OCTAVE')) {
outfile += "<"+measures[i].children[j].children[k].children[l].tagName.toLowerCase()+">"+measures[i].children[j].children[k].children[l].textContent+"</"+measures[i].children[j].children[k].children[l].tagName.toLowerCase()+">";
} else if (measures[i].children[j].children[k].children[l].tagName=='ALTER') {
alter = measures[i].children[j].children[k].children[l].textContent;
}
}
outfile += "<alter>"+alter+"</alter></pitch>";
}
}
outfile += "<duration>"+duration*multiplier+"</duration>";
outfile +="</note>";
}
}
outfile += '</measure>';
}
// close outfile and write it
outfile += '</part></score-partwise>';
fs.writeFileSync("PS-"+process.argv[2],outfile);
console.log("Done. Your PlaneShift-compatible music is now in PS-"+process.argv[2]+" . Enjoy! :)");
Save the file and make it executable. (
chmod +x ./converter.js in a terminal)
3) Write some musicWell, open MuseScore and create a new sheet with one voice. Use flute or guitar or some other non-transposing one-staff instrument. There are some things which are not supported by PlaneShift, so you shouldn't use them in your composition:
- Polyrhythm (e.g. whole note chord with quarter note melody above it)
- I haven't tested key signatures different from C major/A minor (but single #s and bs should work)
- tied notes
Be sure there's a time signature at the beginning of your piece, otherwise the script will fail.
You may add a tempo definition, otherwise, it will use 90BPM.
4) Make your song PlaneShift compatibleSave your composition as MusicXML (not compressed MusicXML) in your
musicalsheets directory. Then let the script convert it. On Linux, open in a terminal and do
./converter.js yoursong.xml
(
converter.js and
yoursong.xml should both be in
musicalsheets, and
yoursong.xml should be the filename you used for your song
)
You'll now get a file called
PS-song.xml which you can put in your
musicalsheets directory and open with the ingame editor. Have fun!
Known Bugs: In the ingame editor, only the first measure of the converted piece is shown, but everything is played. If anyone can tell me how to fix this, please do so.
If you have any questions, please post them here (though i might not be able to help you, depending on your problem
).
Happy composing
Firiban
Easier method1) Create a file called converter.html with the following content
<html>
<head>
</head>
<body>
<h1>MusicXML to PlaneShift converter by Firiban - version 0.2</h1>
<form name="convert" action="#" method="get">
<textarea name="code" cols="100" rows="30"></textarea>
<br />
<input type="button" value="Convert!" name="startme" />
</form>
<script type="application/x-javascript">
// NOTE: This is just the web browser compatible port of the original converter.js
document.forms.convert.startme.onclick = function () {
console.log("MusicXML to PlaneShift converter by Firiban - version 0.2");
console.log("parsing file input...");
// read file, parse it, create outfile with header
if (window.DOMParser) {
var infile = (new DOMParser()).parseFromString(document.forms.convert.code.value,"text/xml");
}
else {
alert ("Incompatible Browser. Try Firefox.");
}
var outfile = '<score-partwise version="2.0"><part-list><score-part id="P1"><part-name>converted stuff</part-name></score-part></part-list><part id="P1">';
var measures = infile.getElementsByTagName("measure");
console.log("Sorting the stuff...");
// This loop does the work: Iterates through the measures and copies the important stuff (read: "PlaneShift compatible tags") to outfile
var i=1;
var j=0;
var k=0;
var l=0;
var alter=0;
var duration=1;
var multiplier=1;
for (var i=0;i<measures.length;i++) {
outfile += '<measure number="'+(i+1)+'">';
if (i==0) {
multiplier = (4 / infile.getElementsByTagName("divisions")[0].childNodes[0].textContent);
outfile += '<attributes><divisions>4</divisions><key><fifths>'+((infile.getElementsByTagName("fifths").length>0)?infile.getElementsByTagName("fifths")[0].childNodes[0].__nodeValue:"0")+'</fifths></key><time><beats>'+infile.getElementsByTagName("beats")[0].childNodes[0].__nodeValue+'</beats><beat-type>'+infile.getElementsByTagName("beat-type")[0].childNodes[0].__nodeValue+'</beat-type></time></attributes><direction>';
if (infile.getElementsByTagName("sound").length>0) {
outfile += '<sound tempo="'+infile.getElementsByTagName("sound")[0].attributes[0].value+'"/>';
} else {
outfile += '<sound tempo="90"/>';
}
outfile += "</direction>";
}
//console.log(measures[i]);
for (j=0;j<measures[i].children.length;j++) {
// we only need the notes, no stupid <print> stuff or text nodes (whitespaces/tabs/newlines etc.)
if (measures[i].children[j].tagName=="note") {
outfile +="<note>";
duration=1;
// iterate through note's parameters and copy the neccessary ones (sort out <stem> etc., since they are not used by PS)
for (k=0;k<measures[i].children[j].children.length;k++) {
if (measures[i].children[j].children[k].tagName=="rest") {
outfile += "<rest/>";
} else if (measures[i].children[j].children[k].tagName=="chord") {
outfile += "<chord/>";
} else if (measures[i].children[j].children[k].tagName=="duration") {
duration = measures[i].children[j].children[k].textContent;
} else if (measures[i].children[j].children[k].tagName=="pitch") {
outfile += "<pitch>";
alter = 0;
for (l=0;l<measures[i].children[j].children[k].children.length;l++) {
if ((measures[i].children[j].children[k].children[l].tagName=='step') || (measures[i].children[j].children[k].children[l].tagName=='octave')) {
outfile += "<"+measures[i].children[j].children[k].children[l].tagName+">"+measures[i].children[j].children[k].children[l].textContent+"</"+measures[i].children[j].children[k].children[l].tagName+">";
} else if (measures[i].children[j].children[k].children[l].tagName=='alter') {
alter = measures[i].children[j].children[k].children[l].textContent;
}
}
outfile += "<alter>"+alter+"</alter></pitch>";
}
}
outfile += "<duration>"+duration*multiplier+"</duration>";
outfile +="</note>";
}
}
outfile += '</measure>';
}
// close outfile and write it
outfile += '</part></score-partwise>';
document.forms.convert.code.value = outfile;
console.log("Done. Enjoy! :)");
}
</script>
</body>
</html>
2) open that file with your web browser. paste the content of your musicxml file there, click convert.
3) save the resulting code to an xml file in your musicalsheets folder and load it with planeshift.
Edit #1: added html methodEdit #2: Fixed bug with accidentalsEdit #3: Added support for adding missing <fifths> tag; now supports Rosegarden