satie
Version:
A sheet music renderer for the web
/**
* This file is part of Satie music engraver <https://github.com/jnetterf/satie>.
* Copyright (C) Joshua Netterfield <joshua.ca> 2015 - present.
*
* Satie is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Satie is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Satie. If not, see <http://www.gnu.org/licenses/>.
*/
"use strict";
/**
* @file part of Satie test suite
*/
var musicxml_interfaces_1 = require("musicxml-interfaces");
var lodash_1 = require("lodash");
var chai_1 = require("chai");
var engine_factory_1 = require("../engine_factory");
var engine_import_1 = require("../engine_import");
var implAttributes_attributesModel_1 = require("../implAttributes_attributesModel");
var implBarline_barlineModel_1 = require("../implBarline_barlineModel");
var implChord_chordModel_1 = require("../implChord_chordModel");
var implDirection_directionModel_1 = require("../implDirection_directionModel");
var implPrint_printModel_1 = require("../implPrint_printModel");
var implSound_soundModel_1 = require("../implSound_soundModel");
var implSpacer_spacerModel_1 = require("../implSpacer_spacerModel");
var engine_scoreHeader_1 = require("../engine_scoreHeader");
/*---- samples ----------------------------------------------------------------------------------*/
var helloWorldXML = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 3.0 Partwise//EN\"\n \"http://www.musicxml.org/dtds/partwise.dtd\">\n<score-partwise version=\"3.0\">\n <movement-title>Song Title</movement-title>\n <identification>\n <creator type=\"composer\">Song Composer</creator>\n <creator type=\"lyricist\">Song Lyricist</creator>\n <creator type=\"arranger\">Song Arranger</creator>\n <rights>Song Copyright</rights>\n <encoding>\n <software>Song Software 1</software>\n <software>Song Software 2</software>\n <encoding-date>2015-03-10</encoding-date>\n <supports attribute=\"new-page\" element=\"print\" type=\"yes\" value=\"yes\"/>\n <supports element=\"accidental\" type=\"yes\"/>\n </encoding>\n </identification>\n <credit page=\"1\">\n <credit-type>title</credit-type>\n <credit-words default-x=\"597\" default-y=\"1440\" font-size=\"24\" justify=\"center\" valign=\"top\">\n Song Title\n </credit-words>\n </credit>\n <credit page=\"1\">\n <credit-type>composer</credit-type>\n <credit-words default-x=\"1124\" default-y=\"1362\" font-size=\"12\" justify=\"right\" valign=\"top\">\n Song Composer\n </credit-words>\n </credit>\n <credit page=\"1\">\n <credit-type>rights</credit-type>\n <credit-words default-x=\"597\" default-y=\"70\" font-size=\"10\" justify=\"center\" valign=\"bottom\">\n Song Copyright\n </credit-words>\n </credit>\n <credit page=\"1\">\n <credit-words default-x=\"70\" default-y=\"1453\" font-size=\"12\" valign=\"top\">\n Score\n </credit-words>\n </credit>\n <credit page=\"2\">\n <credit-type>rights</credit-type>\n <credit-words default-x=\"597\" default-y=\"70\" font-size=\"10\" justify=\"center\" valign=\"bottom\">\n Song Copyright\n </credit-words>\n </credit>\n <part-list>\n <score-part id=\"P1\">\n <part-name print-object=\"no\">MusicXML Part</part-name>\n <score-instrument id=\"P1-I1\">\n <instrument-name>SmartMusic SoftSynth 1</instrument-name>\n </score-instrument>\n <midi-instrument id=\"P1-I1\">\n <midi-channel>1</midi-channel>\n <midi-bank>15489</midi-bank>\n <midi-program>1</midi-program>\n <volume>80</volume>\n <pan>0</pan>\n </midi-instrument>\n </score-part>\n </part-list>\n <part id=\"P1\">\n <measure number=\"1\" width=\"983\">\n <print>\n <system-layout>\n <system-margins>\n <left-margin>70</left-margin>\n <right-margin>0</right-margin>\n </system-margins>\n <top-system-distance>211</top-system-distance>\n </system-layout>\n <measure-numbering>system</measure-numbering>\n </print>\n <attributes>\n <divisions>2</divisions>\n <key>\n <fifths>0</fifths>\n <mode>major</mode>\n </key>\n <time>\n <beats>4</beats>\n <beat-type>4</beat-type>\n </time>\n <clef>\n <sign>G</sign>\n <line>2</line>\n </clef>\n </attributes>\n <sound tempo=\"120\"/>\n <note>\n <rest measure=\"yes\"/>\n <duration>8</duration>\n <voice>1</voice>\n </note>\n <barline location=\"right\">\n <bar-style>light-heavy</bar-style>\n </barline>\n </measure>\n </part>\n</score-partwise>\n";
var lily43eXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE score-partwise PUBLIC \"-//Recordare//DTD MusicXML 1.0 Partwise//EN\"\n \"http://www.musicxml.org/dtds/partwise.dtd\">\n<score-partwise>\n <identification>\n <miscellaneous>\n <miscellaneous-field name=\"description\">A piano staff with dynamics and \n clef changes, where each element (ffff, wedge and clef changes) \n applies only to one voice or one staff, respectively.</miscellaneous-field>\n </miscellaneous>\n </identification>\n <part-list>\n <score-part id=\"P1\">\n <part-name>MusicXML Part</part-name>\n </score-part>\n </part-list>\n <!--=========================================================-->\n <part id=\"P1\">\n <measure number=\"1\">\n <attributes>\n <divisions>8</divisions>\n <key>\n <fifths>0</fifths>\n <mode>major</mode>\n </key>\n <time symbol=\"common\">\n <beats>4</beats>\n <beat-type>4</beat-type>\n </time>\n <staves>2</staves>\n <clef number=\"1\">\n <sign>G</sign>\n <line>2</line>\n </clef>\n <clef number=\"2\">\n <sign>F</sign>\n <line>4</line>\n </clef>\n </attributes>\n <direction placement=\"below\">\n <direction-type>\n <dynamics>\n <ffff/>\n </dynamics>\n </direction-type>\n <staff>1</staff>\n </direction>\n <note>\n <pitch>\n <step>C</step>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>A</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <direction placement=\"below\">\n <direction-type>\n <dynamics>\n <p/>\n </dynamics>\n </direction-type>\n <offset>1</offset>\n <staff>1</staff>\n </direction>\n <note>\n <pitch>\n <step>G</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <backup>\n <duration>32</duration>\n </backup>\n <direction placement=\"below\">\n <direction-type>\n <wedge spread=\"0\" type=\"crescendo\"/>\n </direction-type>\n <staff>2</staff>\n </direction>\n <note>\n <pitch>\n <step>A</step>\n <octave>2</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>2</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <direction>\n <direction-type>\n <wedge spread=\"15\" type=\"stop\"/>\n </direction-type>\n <staff>2</staff>\n </direction>\n <note>\n <pitch>\n <step>C</step>\n <octave>3</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>D</step>\n <octave>3</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n </measure>\n <!--=======================================================-->\n <measure number=\"2\">\n <attributes>\n <key>\n <fifths>2</fifths>\n <mode>major</mode>\n </key>\n <clef number=\"2\">\n <sign>G</sign>\n <line>2</line>\n </clef>\n </attributes>\n <note>\n <pitch>\n <step>A</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>C</step>\n <alter>1</alter>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>D</step>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <backup>\n <duration>32</duration>\n </backup>\n <note>\n <pitch>\n <step>F</step>\n <alter>1</alter>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>G</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>A</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n </measure>\n <!--=======================================================-->\n <measure number=\"3\">\n <attributes>\n <clef number=\"1\">\n <sign>C</sign>\n <line>2</line>\n </clef>\n </attributes>\n <note>\n <pitch>\n <step>D</step>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>C</step>\n <alter>1</alter>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <note>\n <pitch>\n <step>A</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>1</voice>\n <type>quarter</type>\n <staff>1</staff>\n </note>\n <backup>\n <duration>32</duration>\n </backup>\n <note>\n <pitch>\n <step>A</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>B</step>\n <octave>4</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>C</step>\n <alter>1</alter>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n <note>\n <pitch>\n <step>D</step>\n <octave>5</octave>\n </pitch>\n <duration>8</duration>\n <voice>2</voice>\n <type>quarter</type>\n <staff>2</staff>\n </note>\n </measure>\n <!--=======================================================-->\n <measure number=\"4\">\n <note>\n <rest/>\n <duration>32</duration>\n <voice>1</voice>\n <staff>1</staff>\n </note>\n <backup>\n <duration>32</duration>\n </backup>\n <note>\n <rest/>\n <duration>32</duration>\n <voice>2</voice>\n <staff>2</staff>\n </note>\n <barline location=\"right\">\n <bar-style>light-heavy</bar-style>\n </barline>\n </measure>\n </part>\n <!--=========================================================-->\n</score-partwise>";
describe("[musicxml/import.ts]", function () {
describe("_extractMXMLHeader", function () {
it("can parse all header properties", function () {
var mxmljson = musicxml_interfaces_1.parseScore(helloWorldXML);
var header = engine_import_1._extractMXMLHeader(mxmljson);
chai_1.expect(header).to.be.an.instanceof(engine_scoreHeader_1.default);
chai_1.expect(header.credits.length).to.eq(5);
chai_1.expect(header.credits[4].page).to.eq(2);
chai_1.expect(header.credits[0].creditTypes).to.deep.equal(["title"]);
chai_1.expect(header.credits[1].creditWords).to.deep.equal([{
color: "#000000",
defaultX: 1124,
defaultY: 1362,
dir: musicxml_interfaces_1.DirectionMode.Ltr,
enclosure: musicxml_interfaces_1.EnclosureShape.None,
fontFamily: "",
fontSize: "12",
fontStyle: musicxml_interfaces_1.NormalItalic.Normal,
fontWeight: musicxml_interfaces_1.NormalBold.Normal,
halign: musicxml_interfaces_1.LeftCenterRight.Right,
justify: musicxml_interfaces_1.LeftCenterRight.Right,
letterSpacing: "normal",
lineHeight: "normal",
lineThrough: 0,
overline: 0,
relativeX: null,
relativeY: null,
rotation: 0,
underline: 0,
valign: musicxml_interfaces_1.TopMiddleBottomBaseline.Top,
words: "Song Composer"
}]);
// Check that halign still follows justify
chai_1.expect(header.credits[0].creditWords[0].halign).to.eq(musicxml_interfaces_1.LeftCenterRight.Center);
chai_1.expect(header.identification.creators.length).to.eq(3);
chai_1.expect(header.identification.creators[0].type).to.eq("composer");
chai_1.expect(header.identification.creators[0].creator).to.eq("Song Composer");
chai_1.expect(header.identification.creators[1].type).to.eq("lyricist");
chai_1.expect(header.identification.creators[1].creator).to.eq("Song Lyricist");
chai_1.expect(header.identification.creators[2].type).to.eq("arranger");
chai_1.expect(header.identification.creators[2].creator).to.eq("Song Arranger");
chai_1.expect(header.identification.encoding).to.deep.equal({
encoders: [],
encodingDate: {
day: 10,
month: 3,
year: 2015
},
encodingDescriptions: [],
softwares: [
"Song Software 1",
"Song Software 2"
],
supports: {
accidental: {
attribute: "",
element: "accidental",
type: true,
value: ""
},
print_newPage: {
attribute: "new-page",
element: "print",
type: true,
value: "yes"
}
}
});
chai_1.expect(header.movementTitle).to.eq("Song Title");
chai_1.expect(header.partList).to.deep.equal([
{
_class: "ScorePart",
groups: [],
id: "P1",
identification: null,
midiDevices: [],
midiInstruments: [{
elevation: null,
id: "P1-I1",
midiBank: 15489,
midiChannel: 1,
midiName: "",
midiProgram: 1,
midiUnpitched: null,
pan: 0,
volume: 80
}],
partAbbreviation: null,
partAbbreviationDisplay: null,
partName: {
color: "#000000",
defaultX: null,
defaultY: null,
fontFamily: "",
fontSize: "",
fontStyle: musicxml_interfaces_1.NormalItalic.Normal,
fontWeight: musicxml_interfaces_1.NormalBold.Normal,
justify: musicxml_interfaces_1.LeftCenterRight.Left,
partName: "MusicXML Part",
printObject: false,
relativeX: null,
relativeY: null
},
partNameDisplay: null,
scoreInstruments: [{
ensemble: "",
id: "P1-I1",
instrumentAbbreviation: "",
instrumentName: "SmartMusic SoftSynth 1",
instrumentSound: "",
solo: null,
virtualInstrument: null
}]
}
]);
chai_1.expect(!("parts" in header), "Check _extractHeader");
// Extensions
chai_1.expect(header.composer).to.eq("Song Composer");
chai_1.expect(header.title).to.eq("Song Title");
});
});
describe("_extractMXMLPartsAndMeasures", function () {
it("parses a basic single-part song", function () {
var factory = new engine_factory_1.default([implAttributes_attributesModel_1.default, implChord_chordModel_1.default, implPrint_printModel_1.default, implSound_soundModel_1.default, implBarline_barlineModel_1.default]);
// does not need spacer
var mxmljson = musicxml_interfaces_1.parseScore(helloWorldXML);
var partsAndMeasures = engine_import_1._extractMXMLPartsAndMeasures(mxmljson, factory);
chai_1.expect(partsAndMeasures.measures.length).to.eq(1);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].staves[1].length).to.eq(4);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].staves[1][0].divCount).to.eq(0);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].staves[1][1].divCount).to.eq(0);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].staves[1][2].divCount).to.eq(8);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].staves[1][3].divCount).to.eq(0);
chai_1.expect(partsAndMeasures.measures[0].parts["P1"].voices[1].length).to.eq(1);
});
it("parses multi-voice, multi-staff songs with backup", function () {
var factory = new engine_factory_1.default([implAttributes_attributesModel_1.default, implDirection_directionModel_1.default, implChord_chordModel_1.default, implPrint_printModel_1.default, implSound_soundModel_1.default, implBarline_barlineModel_1.default, implSpacer_spacerModel_1.default]);
var mxmljson = musicxml_interfaces_1.parseScore(lily43eXML);
var partsAndMeasures = engine_import_1._extractMXMLPartsAndMeasures(mxmljson, factory);
chai_1.expect(partsAndMeasures.measures.length).to.eq(4);
chai_1.expect(partsAndMeasures.parts).to.eql(["P1"]);
var voices = partsAndMeasures.measures[0].parts["P1"].voices;
var staves = partsAndMeasures.measures[0].parts["P1"].staves;
chai_1.expect(!voices[0]).to.eq(true, "voices are 1-indexed");
chai_1.expect(!staves[0]).to.eq(true, "staves are 1-indexed");
chai_1.expect(voices.length).to.eq(3);
chai_1.expect(staves.length).to.eq(3);
chai_1.expect(voices[2].owner).to.eq(2);
chai_1.expect(voices[2].divisions).to.eq(8);
chai_1.expect(voices[1].divisions).to.eq(8);
chai_1.expect(staves[2].divisions).to.eq(8);
chai_1.expect(staves[1].divisions).to.eq(8);
chai_1.expect(staves[1].length).to.eq(3);
chai_1.expect(staves[2].length).to.eq(3);
lodash_1.forEach(staves[1], function (model) {
chai_1.expect(!lodash_1.some(staves[2], function (m2) { return model === m2; }));
});
lodash_1.forEach(voices[1], function (model) {
chai_1.expect(!lodash_1.some(voices[2], function (m2) { return model === m2; }));
});
});
});
describe("toScore", function () {
// todo
});
});