1
Guides and Tutorials / How to use an external music editor
« on: December 26, 2011, 01:20:24 pm »
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 needed
First, 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
2) The script
Copy the following into a file called converter.js in your musicalsheets directory (on Linux it's ~/.PlaneShift/musicalsheets/)
3) Write some music
Well, 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:
You may add a tempo definition, otherwise, it will use 90BPM.
4) Make your song PlaneShift compatible
Save 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
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 method
1) Create a file called converter.html with the following content
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 method
Edit #2: Fixed bug with accidentals
Edit #3: Added support for adding missing <fifths> tag; now supports Rosegarden
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 needed
First, 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
Code: [Select]
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 script
Copy the following into a file called converter.js in your musicalsheets directory (on Linux it's ~/.PlaneShift/musicalsheets/)
Code: [Select]
#!/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 music
Well, 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
You may add a tempo definition, otherwise, it will use 90BPM.
4) Make your song PlaneShift compatible
Save 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
Code: [Select]
./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 method
1) Create a file called converter.html with the following content
Code: [Select]
<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 method
Edit #2: Fixed bug with accidentals
Edit #3: Added support for adding missing <fifths> tag; now supports Rosegarden