UNPKG

satie

Version:

A sheet music renderer for the web

204 lines (203 loc) 23.3 kB
/** * 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 }); });