├── fonts ├── glyphicons-halflings-regular.eot ├── glyphicons-halflings-regular.ttf ├── glyphicons-halflings-regular.woff └── glyphicons-halflings-regular.woff2 ├── soundfont └── acoustic_grand_piano-mp3 │ ├── A0.mp3 │ ├── A1.mp3 │ ├── A2.mp3 │ ├── A3.mp3 │ ├── A4.mp3 │ ├── A5.mp3 │ ├── A6.mp3 │ ├── A7.mp3 │ ├── Ab1.mp3 │ ├── Ab2.mp3 │ ├── Ab3.mp3 │ ├── Ab4.mp3 │ ├── Ab5.mp3 │ ├── Ab6.mp3 │ ├── Ab7.mp3 │ ├── B0.mp3 │ ├── B1.mp3 │ ├── B2.mp3 │ ├── B3.mp3 │ ├── B4.mp3 │ ├── B5.mp3 │ ├── B6.mp3 │ ├── B7.mp3 │ ├── Bb0.mp3 │ ├── Bb1.mp3 │ ├── Bb2.mp3 │ ├── Bb3.mp3 │ ├── Bb4.mp3 │ ├── Bb5.mp3 │ ├── Bb6.mp3 │ ├── Bb7.mp3 │ ├── C1.mp3 │ ├── C2.mp3 │ ├── C3.mp3 │ ├── C4.mp3 │ ├── C5.mp3 │ ├── C6.mp3 │ ├── C7.mp3 │ ├── C8.mp3 │ ├── D1.mp3 │ ├── D2.mp3 │ ├── D3.mp3 │ ├── D4.mp3 │ ├── D5.mp3 │ ├── D6.mp3 │ ├── D7.mp3 │ ├── Db1.mp3 │ ├── Db2.mp3 │ ├── Db3.mp3 │ ├── Db4.mp3 │ ├── Db5.mp3 │ ├── Db6.mp3 │ ├── Db7.mp3 │ ├── Db8.mp3 │ ├── E1.mp3 │ ├── E2.mp3 │ ├── E3.mp3 │ ├── E4.mp3 │ ├── E5.mp3 │ ├── E6.mp3 │ ├── E7.mp3 │ ├── Eb1.mp3 │ ├── Eb2.mp3 │ ├── Eb3.mp3 │ ├── Eb4.mp3 │ ├── Eb5.mp3 │ ├── Eb6.mp3 │ ├── Eb7.mp3 │ ├── F1.mp3 │ ├── F2.mp3 │ ├── F3.mp3 │ ├── F4.mp3 │ ├── F5.mp3 │ ├── F6.mp3 │ ├── F7.mp3 │ ├── G1.mp3 │ ├── G2.mp3 │ ├── G3.mp3 │ ├── G4.mp3 │ ├── G5.mp3 │ ├── G6.mp3 │ ├── G7.mp3 │ ├── Gb1.mp3 │ ├── Gb2.mp3 │ ├── Gb3.mp3 │ ├── Gb4.mp3 │ ├── Gb5.mp3 │ ├── Gb6.mp3 │ └── Gb7.mp3 ├── css ├── style.css └── jquery.selectBoxIt.css ├── LICENSE.txt ├── README.md ├── icons ├── double-sharp.svg ├── note_1.svg ├── note_4.svg ├── flat.svg ├── natural.svg ├── note_2.svg ├── note_8.svg ├── double-flat.svg ├── note_16.svg └── sharp.svg ├── lib ├── json2xml.js ├── Base64.js ├── base64-binary.js ├── bootstrap-filestyle.min.js ├── xml2json.js └── MIDI.min.js ├── src ├── file_upload.js ├── table.js ├── init.js ├── file_download.js ├── delete.js ├── note_tool.js ├── vexflow_extension.js ├── edit.js ├── player.js ├── listeners.js ├── util.js ├── parse.js ├── add.js └── draw.js ├── index.html └── examples └── Chant.xml /fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A0.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/A7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/A7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Ab7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Ab7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B0.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/B7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/B7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb0.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Bb7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Bb7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/C8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/C8.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/D7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/D7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Db8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Db8.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/E7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/E7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Eb7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Eb7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/F7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/F7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/G7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/G7.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb1.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb2.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb3.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb4.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb5.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb6.mp3 -------------------------------------------------------------------------------- /soundfont/acoustic_grand_piano-mp3/Gb7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SymphoniaIO/web-musicxml-editor/HEAD/soundfont/acoustic_grand_piano-mp3/Gb7.mp3 -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 160px; 3 | } 4 | #play-bar { 5 | float: right; 6 | margin-top: 5px; 7 | } 8 | #svg-wrapper { 9 | overflow-y:auto; 10 | overflow-x:auto; 11 | border:1px solid lightgray; 12 | } 13 | .tool-section { 14 | margin-right: 10px; 15 | display: inline-block; 16 | } 17 | #file > .tool-section { 18 | margin-right: 10px; 19 | display: inline-table; 20 | } 21 | .tool-section button { 22 | padding: 1px 10px 1px; 23 | position:relative; 24 | top:-3px; 25 | } 26 | .tab-content { 27 | border: 1px solid lightgray; 28 | position: relative; 29 | top: -1px; 30 | border-top: 0; 31 | padding: 20px 10px; 32 | } 33 | 34 | input[type="radio"] { 35 | display: none; 36 | } 37 | 38 | input[type="radio"]+label>img { 39 | cursor: pointer; 40 | padding: 5px; 41 | } 42 | 43 | input[type="radio"]:checked+label>img { 44 | border: 1px solid red; 45 | } 46 | 47 | .biggerIcon { 48 | width: 50%; 49 | height: 50%; 50 | } 51 | 52 | .smallerIcon { 53 | width: 70%; 54 | height: 70%; 55 | } 56 | footer { 57 | text-align: center; 58 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Thomas Hudziec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Editor of MusicXML Files 2 | Browser based music score editor with basic MusicXML import/export. Uses Vexflow for music engraving into HTML5 svg element. Provides mouse interactivity for manipulation with musical score. 3 | 4 | You can see live demo [here](http://freetomik.github.io/). 5 | 6 | ## Dependencies 7 | Vexflow, jQuery and Bootstrap 8 | 9 | ## Current limitations 10 | 15 | 16 | ## To Do 17 |
    18 |
  1. Duration check for notes in measure - in progress
  2. 19 |
  3. Playback - basics done, improvements needed
  4. 20 |
  5. Bug fixes - in progress
  6. 21 |
  7. Chords support - waiting
  8. 22 |
  9. Keyboard interactivity - waiting
  10. 23 |
  11. Multiple score parts - waiting
  12. 24 |
25 | 26 | ## Contribution 27 | You are welcome to contribute! 28 | 29 | Fork, clone, make your feature branch, implement feature, make pull request :-) 30 | 31 | Running project locally: 32 | Run following command in project directory: 33 | ``` 34 | $ python -m SimpleHTTPServer 35 | ``` 36 | Open localhost:8000 in your browser. 37 | 38 | This project was initially created as a bachelor [thesis](https://www.fit.vut.cz/study/thesis/18587/.en). 39 | -------------------------------------------------------------------------------- /icons/double-sharp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /lib/json2xml.js: -------------------------------------------------------------------------------- 1 | /* This work is licensed under Creative Commons GNU LGPL License. 2 | 3 | License: http://creativecommons.org/licenses/LGPL/2.1/ 4 | Version: 0.9 5 | Author: Stefan Goessner/2006 6 | Web: http://goessner.net/ 7 | */ 8 | function json2xml(o, tab) { 9 | var toXml = function(v, name, ind) { 10 | var xml = ""; 11 | if (v instanceof Array) { 12 | for (var i=0, n=v.length; i" : "/>"; 25 | if (hasChild) { 26 | for (var m in v) { 27 | if (m == "#text") 28 | xml += v[m]; 29 | else if (m == "#cdata") 30 | xml += ""; 31 | else if (m.charAt(0) != "@") 32 | xml += toXml(v[m], m, ind+"\t"); 33 | } 34 | xml += (xml.charAt(xml.length-1)=="\n"?ind:"") + ""; 35 | } 36 | } 37 | else { 38 | xml += ind + "<" + name + ">" + v.toString() + ""; 39 | } 40 | return xml; 41 | }, xml=""; 42 | for (var m in o) 43 | xml += toXml(o[m], m, ""); 44 | return tab ? xml.replace(/\t/g, tab) : xml.replace(/\t|\n/g, ""); 45 | } 46 | -------------------------------------------------------------------------------- /icons/note_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 33 | 35 | 39 | 40 | 41 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /icons/note_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 33 | 35 | 39 | 40 | 42 | 46 | 47 | 48 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /icons/flat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /icons/natural.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /icons/note_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 33 | 35 | 39 | 40 | 42 | 46 | 47 | 48 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/Base64.js: -------------------------------------------------------------------------------- 1 | // http://ntt.cc/2008/01/19/base64-encoder-decoder-with-javascript.html 2 | 3 | // window.atob and window.btoa 4 | 5 | (function (window) { 6 | 7 | var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 8 | 9 | window.btoa || (window.btoa = function encode64(input) { 10 | input = escape(input); 11 | var output = ""; 12 | var chr1, chr2, chr3 = ""; 13 | var enc1, enc2, enc3, enc4 = ""; 14 | var i = 0; 15 | do { 16 | chr1 = input.charCodeAt(i++); 17 | chr2 = input.charCodeAt(i++); 18 | chr3 = input.charCodeAt(i++); 19 | enc1 = chr1 >> 2; 20 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 21 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 22 | enc4 = chr3 & 63; 23 | if (isNaN(chr2)) { 24 | enc3 = enc4 = 64; 25 | } else if (isNaN(chr3)) { 26 | enc4 = 64; 27 | } 28 | output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4); 29 | chr1 = chr2 = chr3 = ""; 30 | enc1 = enc2 = enc3 = enc4 = ""; 31 | } while (i < input.length); 32 | return output; 33 | }); 34 | 35 | window.atob || (window.atob = function(input) { 36 | var output = ""; 37 | var chr1, chr2, chr3 = ""; 38 | var enc1, enc2, enc3, enc4 = ""; 39 | var i = 0; 40 | // remove all characters that are not A-Z, a-z, 0-9, +, /, or = 41 | var base64test = /[^A-Za-z0-9\+\/\=]/g; 42 | if (base64test.exec(input)) { 43 | alert("There were invalid base64 characters in the input text.\n" + "Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" + "Expect errors in decoding."); 44 | } 45 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 46 | do { 47 | enc1 = keyStr.indexOf(input.charAt(i++)); 48 | enc2 = keyStr.indexOf(input.charAt(i++)); 49 | enc3 = keyStr.indexOf(input.charAt(i++)); 50 | enc4 = keyStr.indexOf(input.charAt(i++)); 51 | chr1 = (enc1 << 2) | (enc2 >> 4); 52 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 53 | chr3 = ((enc3 & 3) << 6) | enc4; 54 | output = output + String.fromCharCode(chr1); 55 | if (enc3 != 64) { 56 | output = output + String.fromCharCode(chr2); 57 | } 58 | if (enc4 != 64) { 59 | output = output + String.fromCharCode(chr3); 60 | } 61 | chr1 = chr2 = chr3 = ""; 62 | enc1 = enc2 = enc3 = enc4 = ""; 63 | } while (i < input.length); 64 | return unescape(output); 65 | }); 66 | 67 | }(this)); -------------------------------------------------------------------------------- /icons/note_8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 33 | 35 | 39 | 40 | 42 | 46 | 47 | 49 | 53 | 54 | 55 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /icons/double-flat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /src/file_upload.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | 3 | fileInput.addEventListener('change', function(e) { 4 | try { 5 | var file = fileInput.files[0]; 6 | var maxFileSize = 2 * 1024 * 1024; // 2 MB 7 | 8 | if(fileInput.files[0].size > maxFileSize) 9 | throw 'Uploaded file size exceeded is bigger than 2MB.'; 10 | 11 | if (file.type.match(/.xml/)) { 12 | console.log('xml file Uploaded'); 13 | var reader = new FileReader(); 14 | 15 | // after FileReader finishes reading: 16 | reader.onload = function(e) { 17 | try { 18 | initUI(); 19 | uploadedFileName = file.name; 20 | loadAndDraw(reader.result); 21 | } 22 | catch(err) { 23 | console.exception(err); 24 | } 25 | } 26 | 27 | reader.readAsText(file); 28 | 29 | } 30 | else { 31 | throw 'Uploaded file is not XML file.'; 32 | } 33 | } 34 | catch(err) { 35 | console.exception(err); 36 | } 37 | }); 38 | } 39 | 40 | function loadAndDraw(inputFile) { 41 | // parse xml using jQuery into xml document object 42 | if(typeof inputFile === 'string') 43 | var xmlDoc = $.parseXML(inputFile); 44 | // example file loaded via ajax is already of xml Document type 45 | else 46 | var xmlDoc = inputFile; 47 | if(xmlDoc.documentElement.nodeName !== "score-partwise") 48 | throw 'Uploaded file is not MusicXML score-partwise file.'; 49 | // convert xml to json for faster access 50 | jsonFromXml = xml2json(xmlDoc, ' '); 51 | // load json to memory; parseJSON is safer than eval 52 | scoreJson = $.parseJSON(jsonFromXml); 53 | // turn some only properties into one element array 54 | scoreJson = onlyChildren2Array(scoreJson); 55 | // parse json into vexflow structures 56 | editor.parse.all(); 57 | // draw 58 | editor.draw.score(); 59 | } 60 | 61 | function loadExample(url) { 62 | console.log('loading example file: ' + url); 63 | $.ajax({ 64 | url: url, 65 | data: null, 66 | success: function(data) { 67 | initUI(); 68 | uploadedFileName = 'example.xml'; 69 | loadAndDraw(data); 70 | }, 71 | dataType: 'xml' 72 | }); 73 | } 74 | 75 | //wraps part, measure and note only child elements 76 | //into one element arrays for later better manipulation 77 | function onlyChildren2Array(scoreJson) { 78 | if(! $.isArray(scoreJson["score-partwise"].part) ) //or !(x instanceof Array) 79 | scoreJson["score-partwise"].part = [ scoreJson["score-partwise"].part ]; 80 | if(! $.isArray(scoreJson["score-partwise"].part[0].measure) ) 81 | scoreJson["score-partwise"].part[0].measure = 82 | [ scoreJson["score-partwise"].part[0].measure ]; 83 | for(var i = 0; i < scoreJson["score-partwise"].part[0].measure.length; i++) 84 | if(! $.isArray(scoreJson["score-partwise"].part[0].measure[i].note) ) 85 | scoreJson["score-partwise"].part[0].measure[i].note = 86 | [ scoreJson["score-partwise"].part[0].measure[i].note ]; 87 | return scoreJson; 88 | } -------------------------------------------------------------------------------- /src/table.js: -------------------------------------------------------------------------------- 1 | /* 2 | Project: Concerto 3 | https://github.com/panarch/concerto 4 | Code authors: 5 | Taehoon Moon , 2014 6 | Licensed under MIT license 7 | https://github.com/panarch/concerto/blob/master/LICENSE 8 | Modifications: 9 | minor modifications: Tomas Hudziec 2016 10 | */ 11 | 12 | editor.table = {}; 13 | 14 | editor.table.ACCIDENTAL_DICT = { 15 | 'sharp': '#', 16 | 'double-sharp': '##', 17 | 'natural': 'n', 18 | 'flat': 'b', 19 | 'flat-flat': 'bb' 20 | // 'double-flat' doesn't exists in MusicXML, it's named 'flat-flat' instead 21 | }; 22 | 23 | editor.table.TONES = ['c', 'd', 'e', 'f', 'g', 'a', 'b']; 24 | 25 | editor.table.DEFAULT_CLEF = 'treble'; 26 | editor.table.DEFAULT_TIME_BEATS = 4; 27 | editor.table.DEFAULT_TIME_BEAT_TYPE = 4; 28 | 29 | editor.table.DEFAULT_REST_PITCH = 'b/4'; 30 | 31 | editor.table.FLAT_MAJOR_KEY_SIGNATURES = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'Cb']; 32 | editor.table.SHARP_MAJOR_KEY_SIGNATURES = ['G', 'D', 'A', 'E', 'B', 'F#', 'C#']; 33 | 34 | editor.table.NOTE_QUARTER_INDEX = 8; 35 | editor.table.NOTE_TYPES = ['1024th', '512th', '256th', '128th', 36 | '64th', '32nd', '16th', 'eighth', 'quarter', 'half', 'whole', 'breve', 37 | 'long', 'maxima']; 38 | 39 | editor.table.NOTE_VEX_QUARTER_INDEX = 8; 40 | editor.table.NOTE_VEX_TYPES = ['1024', '512', '256', '128', 41 | '64', '32', '16', '8', 'q', 'h', 'w', 'w', 42 | 'w', 'w']; 43 | 44 | editor.table.NOTE_TYPE_DICT = { 45 | '1024th': '64', 46 | '512th': '64', 47 | '256th': '64', 48 | '128th': '128', 49 | '64th': '64', 50 | '32nd': '32', 51 | '16th': '16', 52 | 'eighth': '8', 53 | 'quarter': 'q', 54 | 'half': 'h', 55 | 'whole': 'w', 56 | 'breve': 'w', 57 | 'long': 'w', 58 | 'maxima': 'w' 59 | }; 60 | 61 | editor.table.NOTE_VEX_TYPE_DICT = { 62 | 63 | }; 64 | 65 | editor.table.DURATION_DICT = { 66 | 'w' : '1', 67 | 'h' : '2', 68 | 'q' : '4', 69 | '8' : '8', 70 | '16' : '16', 71 | '32' : '32', 72 | '64' : '64', 73 | '128' : '128' 74 | }; 75 | 76 | editor.table.CLEF_TYPE_DICT = { 77 | 'G/2': 'treble', 78 | 'F/4': 'bass', 79 | 'C/3': 'alto', 80 | 'C/4': 'tenor', 81 | 'C/1': 'soprano', 82 | 'C/2': 'mezzo-soprano', 83 | 'C/5': 'baritone-c', 84 | 'F/3': 'baritone-f', 85 | 'F/5': 'subbass', 86 | 'G/1': 'french', 87 | 'percussion/2': 'percussion' 88 | }; 89 | 90 | editor.table.CLEF_VEX_TYPE_DICT = { 91 | 'treble': 'G/2', 92 | 'bass': 'F/4', 93 | 'alto': 'C/3', 94 | 'tenor': 'C/4', 95 | 'soprano': 'C/1', 96 | 'mezzo-soprano': 'C/2', 97 | 'baritone-c': 'C/5', 98 | 'baritone-f': 'F/3', 99 | 'subbass': 'F/5', 100 | 'french': 'G/1', 101 | 'percussion': 'percussion/2' 102 | }; 103 | 104 | editor.table.STAVE_DEFAULT_OPTIONS = { 105 | 'space_above_staff_ln': 0 106 | }; 107 | 108 | editor.MidiClefOffsets = { 109 | "treble": 0, 110 | "bass" : -21, 111 | "alto": -10, 112 | "tenor": -14, 113 | "percussion": 0, 114 | "soprano": -4, 115 | "mezzo-soprano": -7, 116 | "baritone-c": -17, 117 | "baritone-f": -17, 118 | "subbass": -24, 119 | "french": 6 120 | }; -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | /** Web Editor of MusicXML Files 2 | * 3 | * (c) Thomas Hudziec, 2016 4 | * https://github.com/freetomik/web-musicxml-editor 5 | * MIT license 6 | */ 7 | 8 | scoreJson = { 9 | 'score-partwise': { 10 | '@version': '3.0', 11 | 'part-list': { 12 | 'score-part': { 13 | '@id': 'P1', 14 | 'part-name': {} 15 | } 16 | }, 17 | part: [ 18 | { 19 | '@id': 'P1', 20 | measure: [ 21 | { 22 | '@number': 1, 23 | attributes: { 24 | divisions: 4, 25 | key: { 26 | fifths: 0, 27 | mode: 'major' 28 | }, 29 | time: { 30 | beats: 4, 31 | 'beat-type': 4 32 | }, 33 | clef: { 34 | sign: 'G', 35 | line: 2 36 | } 37 | }, 38 | note: [ 39 | { 40 | rest: null, 41 | duration: 16 42 | } 43 | ] 44 | } 45 | ] 46 | } 47 | ] 48 | } 49 | }; 50 | 51 | uploadedFileName = 'score'; 52 | 53 | // one in MusicXML -> one Vex.Flow.Stave 54 | // all of these three arrays below use share same index 55 | gl_VfStaves = []; // array with currently rendered vexflow measures(Vex.Flow.Stave) 56 | gl_StaveAttributes = []; // array of attributes for each measure 57 | gl_VfStaveNotes = []; // array of arrays with notes to corresponding stave in gl_VfStaves 58 | 59 | editor = {}; 60 | editor.svgElem = $("#svg-container")[0]; 61 | // editor.renderer = new Vex.Flow.Renderer('svg-container', Vex.Flow.Renderer.Backends.SVG); 62 | editor.renderer = new Vex.Flow.Renderer(editor.svgElem, Vex.Flow.Renderer.Backends.SVG); 63 | editor.ctx = editor.renderer.getContext(); //SVGContext 64 | 65 | // some default sizes 66 | editor.staveWidth = 150; 67 | editor.staveHeight = 140; 68 | editor.noteWidth = 40; 69 | 70 | editor.mode = "measure"; // measure or note 71 | editor.measureColor = "#428bca"; 72 | 73 | function initUI() { 74 | editor.selected = { 75 | cursorNoteKey: 'b/4', 76 | measure: { 77 | id: 'm0', 78 | previousId: 'm0' 79 | }, 80 | note: { 81 | id: 'm0n0', 82 | previousId: 'm0n0' 83 | } 84 | } 85 | 86 | editor.mousePos = { 87 | current: { 88 | x: 0, 89 | y: 0 90 | }, 91 | previous: { 92 | x: 0, 93 | y: 0 94 | } 95 | } 96 | 97 | // uncheck checked accidental radio button 98 | $("input:radio[name='note-accidental']:checked").prop("checked", false); 99 | // uncheck note-value radio button 100 | $("input:radio[name='note-value']:checked").prop("checked", false); 101 | // check whole note radio button 102 | $("input:radio[name='note-value'][value='w']").prop("checked", true); 103 | // examples dropdown 104 | $("#examples-dropdown").val("default"); 105 | // uncheck doted checkbox 106 | $("#dotted-checkbox").prop("checked", false); 107 | // set selected clef to treble 108 | $("#clef-dropdown").val("treble"); 109 | // set selected key signature to C 110 | $("#keySig-dropdown").val("C"); 111 | // set selected time signature to 4/4 112 | $("#timeSigTop").val("4"); 113 | $("#timeSigBottom").val("4"); 114 | 115 | $("#button-play").prop("disabled", false); 116 | $("#button-stop").prop("disabled", true); 117 | } 118 | -------------------------------------------------------------------------------- /icons/note_16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 31 | 33 | 35 | 39 | 40 | 42 | 46 | 47 | 49 | 53 | 54 | 55 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /lib/base64-binary.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Daniel Guerrero 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL DANIEL GUERRERO BE LIABLE FOR ANY 17 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | */ 24 | 25 | /** 26 | * Uses the new array typed in javascript to binary base64 encode/decode 27 | * at the moment just decodes a binary base64 encoded 28 | * into either an ArrayBuffer (decodeArrayBuffer) 29 | * or into an Uint8Array (decode) 30 | * 31 | * References: 32 | * https://developer.mozilla.org/en/JavaScript_typed_arrays/ArrayBuffer 33 | * https://developer.mozilla.org/en/JavaScript_typed_arrays/Uint8Array 34 | */ 35 | 36 | var Base64Binary = { 37 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", 38 | 39 | /* will return a Uint8Array type */ 40 | decodeArrayBuffer: function(input) { 41 | var bytes = (input.length/4) * 3; 42 | var ab = new ArrayBuffer(bytes); 43 | this.decode(input, ab); 44 | 45 | return ab; 46 | }, 47 | 48 | removePaddingChars: function(input){ 49 | var lkey = this._keyStr.indexOf(input.charAt(input.length - 1)); 50 | if(lkey == 64){ 51 | return input.substring(0,input.length - 1); 52 | } 53 | return input; 54 | }, 55 | 56 | decode: function (input, arrayBuffer) { 57 | //get last chars to see if are valid 58 | input = this.removePaddingChars(input); 59 | input = this.removePaddingChars(input); 60 | 61 | var bytes = parseInt((input.length / 4) * 3, 10); 62 | 63 | var uarray; 64 | var chr1, chr2, chr3; 65 | var enc1, enc2, enc3, enc4; 66 | var i = 0; 67 | var j = 0; 68 | 69 | if (arrayBuffer) 70 | uarray = new Uint8Array(arrayBuffer); 71 | else 72 | uarray = new Uint8Array(bytes); 73 | 74 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 75 | 76 | for (i=0; i> 4); 84 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 85 | chr3 = ((enc3 & 3) << 6) | enc4; 86 | 87 | uarray[i] = chr1; 88 | if (enc3 != 64) uarray[i+1] = chr2; 89 | if (enc4 != 64) uarray[i+2] = chr3; 90 | } 91 | 92 | return uarray; 93 | } 94 | } -------------------------------------------------------------------------------- /src/file_download.js: -------------------------------------------------------------------------------- 1 | function xmlToString(xmlData) { 2 | try { 3 | // Gecko- and Webkit-based browsers (Firefox, Chrome), Opera. 4 | return (new XMLSerializer()).serializeToString(xmlData); 5 | } 6 | catch (e) { 7 | try { 8 | // IE 9 | return xmlData.xml; 10 | } 11 | catch (e) { 12 | //Other browsers without XML Serializer 13 | alert('Xmlserializer not supported'); 14 | } 15 | } 16 | return false; 17 | } 18 | 19 | function setupDownloadLink(link) { 20 | var xmlFromJson = json2xml(scoreJson, ' '); 21 | //TODO: read header from original file 22 | var xmlHeaderString = '\n'+ 23 | '\n'; 24 | link.download = uploadedFileName.split(".xml")[0] + '[edited].xml'; 25 | link.href = 'data:text/xml;charset=utf-8,' + encodeURIComponent(xmlHeaderString + formatXml(xmlFromJson)); 26 | } 27 | 28 | /* 29 | Project: Code from StackOverflow page 30 | http://stackoverflow.com/questions/376373/pretty-printing-xml-with-javascript 31 | Code authors: 32 | Darin Dimitrov - http://stackoverflow.com/users/29407/darin-dimitrov 33 | schellsan - http://stackoverflow.com/users/223455/schellsan 34 | Licensed under CC-Wiki 35 | http://creativecommons.org/licenses/by-sa/3.0/ 36 | Modifications: 37 | two lines of code before return added by Thomas Hudziec, 2016 38 | */ 39 | function formatXml(xml) { 40 | var reg = /(>)\s*(<)(\/*)/g; 41 | var wsexp = / *(.*) +\n/g; 42 | var contexp = /(<.+>)(.+\n)/g; 43 | xml = xml.replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2'); 44 | var pad = 0; 45 | var formatted = ''; 46 | var lines = xml.split('\n'); 47 | var indent = 0; 48 | var lastType = 'other'; 49 | // 4 types of tags - single, closing, opening, other (text, doctype, comment) - 4*4 = 16 transitions 50 | var transitions = { 51 | 'single->single' : 0, 52 | 'single->closing' : -1, 53 | 'single->opening' : 0, 54 | 'single->other' : 0, 55 | 'closing->single' : 0, 56 | 'closing->closing' : -1, 57 | 'closing->opening' : 0, 58 | 'closing->other' : 0, 59 | 'opening->single' : 1, 60 | 'opening->closing' : 0, 61 | 'opening->opening' : 1, 62 | 'opening->other' : 1, 63 | 'other->single' : 0, 64 | 'other->closing' : -1, 65 | 'other->opening' : 0, 66 | 'other->other' : 0 67 | }; 68 | 69 | for (var i=0; i < lines.length; i++) { 70 | var ln = lines[i]; 71 | var single = Boolean(ln.match(/<.+\/>/)); // is this line a single tag? ex.
72 | var closing = Boolean(ln.match(/<\/.+>/)); // is this a closing tag? ex. 73 | var opening = Boolean(ln.match(/<[^!].*>/)); // is this even a tag (that's not ) 74 | var type = single ? 'single' : closing ? 'closing' : opening ? 'opening' : 'other'; 75 | var fromTo = lastType + '->' + type; 76 | lastType = type; 77 | var padding = ''; 78 | 79 | indent += transitions[fromTo]; 80 | for (var j = 0; j < indent; j++) { 81 | padding += ' '; 82 | } 83 | 84 | formatted += padding + ln + '\n'; 85 | } 86 | 87 | // put leaf elements with their text content on one line 88 | formatted = formatted.replace(/(>)\n\s*(\w+)/g, '$1$2'); 89 | 90 | // remove <#comment/> elements 91 | formatted = formatted.replace(/\s*<#comment\/>/g, ''); 92 | 93 | return formatted; 94 | } -------------------------------------------------------------------------------- /icons/sharp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 19 | image/svg+xml 20 | 22 | 23 | 24 | 25 | 26 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /src/delete.js: -------------------------------------------------------------------------------- 1 | editor.delete = { 2 | // removes selected measure 3 | measure: function() { 4 | // protection from removing last remaining measure 5 | if(scoreJson["score-partwise"].part[0].measure.length <= 1) { 6 | // TODO error message "Could not remove last remaining measure" 7 | return; 8 | } 9 | 10 | var measureIndex = getSelectedMeasureIndex(); 11 | // to avoid inconsistency between measure and note id 12 | editor.selected.note.id = 'm' + measureIndex + 'n0'; 13 | 14 | // merge attributes of measure being deleted with next measure attributes 15 | if(measureIndex !== gl_StaveAttributes.length - 1) { 16 | mergePropertiesInPlace(gl_StaveAttributes[measureIndex], gl_StaveAttributes[measureIndex + 1]); 17 | } 18 | 19 | // remove measure from global arrays 20 | gl_VfStaves.splice(measureIndex, 1); 21 | gl_StaveAttributes.splice(measureIndex, 1); 22 | gl_VfStaveNotes.splice(measureIndex, 1); 23 | 24 | // re-number all following notes ids in measures in part 25 | for(var m = measureIndex; m < gl_VfStaveNotes.length; m++) { 26 | for(var n = 0; n < gl_VfStaveNotes[m].length; n++) { 27 | gl_VfStaveNotes[m][n].setId('m' + m + 'n' + n); 28 | } 29 | } 30 | 31 | // TODO merge attributes in json like above in gl_StaveAttributes 32 | 33 | // remove measure from scoreJson 34 | scoreJson["score-partwise"].part[0].measure.splice(measureIndex, 1); 35 | 36 | // shift numbering for all following measures in part 37 | for(var m = measureIndex; m < scoreJson["score-partwise"].part[0].measure.length; m++) { 38 | scoreJson["score-partwise"].part[0].measure[m]["@number"] = m; 39 | } 40 | // if deleted measure was last, mark current last measure as selected 41 | if(measureIndex >= scoreJson["score-partwise"].part[0].measure.length - 1) { 42 | editor.selected.measure.id = 'm'+(scoreJson["score-partwise"].part[0].measure.length - 1); 43 | // mark first note in that measure as selected 44 | editor.selected.note.id = editor.selected.measure.id + 'n0'; 45 | } 46 | }, 47 | // deletes note by replacing it with a rest of the same duration 48 | note: function(){ 49 | // get and parse id of selected note (id='m13n10') 50 | var measureIndex = getSelectedMeasureIndex(); 51 | var noteIndex = getSelectedNoteIndex(); 52 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 53 | // if note is already a rest, do nothing 54 | if(vfStaveNote.isRest()) 55 | return; 56 | // get notes duration properties 57 | var duration = vfStaveNote.getDuration(); 58 | // create new Vex.Flow.StaveNote for rest 59 | var vfRest = new Vex.Flow.StaveNote({ 60 | keys: [ editor.table.DEFAULT_REST_PITCH ], 61 | duration: duration + 'r' // TODO add dots before 'r': /d*/ 62 | }); 63 | // set id for note DOM element in svg 64 | vfRest.setId(editor.selected.note.id); 65 | // set dots for a rest, however, currently supports only one dot(see parse.js line 140) 66 | if(vfStaveNote.isDotted()) { 67 | var dots = vfStaveNote.getDots().length; 68 | for(var i = 0; i < dots; i++) 69 | vfRest.addDotToAll(); 70 | } 71 | // replace deleted note with a rest 72 | gl_VfStaveNotes[measureIndex].splice(noteIndex, 1, vfRest); 73 | // delete pitch property from json 74 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch; 75 | // delete accidental if any 76 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].accidental; 77 | // create empty rest property 78 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex]['rest'] = null; 79 | // I assume, that property order does not matter 80 | // also, currently I don't delete some non-rest elements, like stem, lyric, notations (e.g.slur) 81 | // uncheck checked accidental radio button 82 | $("input:radio[name='note-accidental']:checked").prop("checked", false); 83 | }, 84 | accidental: function(){ 85 | var measureIndex = getSelectedMeasureIndex(); 86 | var noteIndex = getSelectedNoteIndex(); 87 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 88 | 89 | vfStaveNote.removeAccidental(); 90 | 91 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].accidental; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/note_tool.js: -------------------------------------------------------------------------------- 1 | /* 2 | Project: Concerto 3 | https://github.com/panarch/concerto 4 | Code authors: 5 | Taehoon Moon , 2014 6 | Licensed under MIT license 7 | https://github.com/panarch/concerto/blob/master/LICENSE 8 | Modifications: 9 | function transposeNote created by Thomas Hudziec, 2016 10 | */ 11 | 12 | editor.NoteTool = {}; 13 | 14 | /** 15 | * @param {Object} staveNote 16 | * @param {number} divisions 17 | * @return {number} 18 | */ 19 | editor.NoteTool.getDurationFromStaveNote = function getDurationFromStaveNote(staveNote, divisions) { 20 | var noteType = staveNote.getDuration(); 21 | var numDots; 22 | 23 | if(staveNote.isDotted()) 24 | numDots = staveNote.dots; 25 | else 26 | numDots = 0; 27 | 28 | var index = editor.table.NOTE_VEX_TYPES.indexOf(noteType); 29 | var offset = index - editor.table.NOTE_VEX_QUARTER_INDEX; 30 | var duration = Math.pow(2, offset) * divisions; 31 | duration = duration * 2 - duration * Math.pow(2, -numDots); 32 | 33 | return duration; 34 | }; 35 | 36 | function _calculateNoteType(duration, divisions) { 37 | var i = 0; 38 | var count; 39 | var num; 40 | for (count = 0; count < 20; count++) { 41 | num = Math.floor(duration / divisions); 42 | if (num === 1) 43 | break; 44 | else if (num > 1) { 45 | divisions *= 2; 46 | i++; 47 | } 48 | else { 49 | divisions /= 2; 50 | i--; 51 | } 52 | } 53 | 54 | if (count === 20) 55 | // TODO throw exception 56 | console.error('No proper StaveNote type'); 57 | 58 | var dots = 0; 59 | for (count = 0; count < 5; count++) { 60 | duration -= Math.floor(duration / divisions); 61 | divisions /= 2; 62 | num = Math.floor(duration / divisions); 63 | if (num === 1) 64 | dots++; 65 | else 66 | break; 67 | } 68 | 69 | return { 70 | index: i, 71 | dots: dots 72 | }; 73 | } 74 | 75 | /** 76 | * @param {number} duration 77 | * @param {number} divisions 78 | * @param {boolean=} withDots 79 | */ 80 | editor.NoteTool.getStaveNoteTypeFromDuration = function getStaveNoteTypeFromDuration(duration, divisions, withDots) { 81 | if (withDots === undefined) 82 | withDots = false; 83 | 84 | var result = _calculateNoteType(duration, divisions); 85 | var index = editor.table.NOTE_VEX_QUARTER_INDEX + result.index; 86 | var noteType = editor.table.NOTE_VEX_TYPES[index]; 87 | 88 | if (withDots) { 89 | for (var i = 0; i < result.dots; i++) 90 | noteType += 'd'; 91 | } 92 | 93 | return noteType; 94 | }; 95 | 96 | editor.NoteTool.getNoteTypeFromDuration = function getNoteTypeFromDuration(duration, divisions) { 97 | var result = _calculateNoteType(duration, divisions); 98 | var index = editor.table.NOTE_QUARTER_INDEX + result.index; 99 | 100 | return { 101 | type: editor.table.NOTE_TYPES[index], 102 | dot: result.dots 103 | }; 104 | }; 105 | 106 | // transposes note by whole tones 107 | // example: 108 | // key: 'c/4' 109 | // interval: 2 110 | // return: 'e/4' 111 | // TODO perform automated testing to proof complete correctness 112 | // author Thomas Hudziec, 2016 113 | editor.NoteTool.transposeNote = function transposeNote(key, interval) { 114 | var step = key[0]; 115 | var octave = +key[key.length - 1]; 116 | var maxInterval = 24; 117 | 118 | if(interval > maxInterval) interval = maxInterval; 119 | else if(interval < -maxInterval) interval = -maxInterval; 120 | 121 | var mod = editor.table.TONES.length; 122 | 123 | var currentIndex = editor.table.TONES.indexOf(step); 124 | var shifted = currentIndex + interval; 125 | var newIndex = shifted % mod; 126 | if(newIndex < 0) newIndex += mod; 127 | var newKey = editor.table.TONES[newIndex]; 128 | 129 | var octaveShift = shifted / mod; 130 | octaveShift = Math.floor(octaveShift); 131 | var newOctave = octave + octaveShift; 132 | 133 | return newKey+'/'+newOctave; 134 | }; -------------------------------------------------------------------------------- /src/vexflow_extension.js: -------------------------------------------------------------------------------- 1 | /* 2 | Vex.Flow.Stave extensions 3 | author: 4 | Tomas Hudziec, 2016 5 | inspiration from: 6 | https://github.com/andrebakker/VexUI/blob/master/src/VexFlowExtension.js 7 | by Andre Bakker, MIT license 8 | */ 9 | 10 | Vex.Flow.Stave.prototype.getModifierIndex = function(constructor){ 11 | if(this.modifiers) 12 | for(var i = 0; i < this.modifiers.length; i++) 13 | if(this.modifiers[i] instanceof constructor) 14 | return i; 15 | return -1; 16 | } 17 | 18 | Vex.Flow.Stave.prototype.removeClef = function(){ 19 | var clefs = this.getModifiers(Vex.Flow.StaveModifier.Position.BEGIN, Vex.Flow.Clef.category); 20 | if(clefs.length === 0) 21 | return; 22 | if(this.getModifierIndex(Vex.Flow.Clef) !== -1) { 23 | this.modifiers.splice(this.getModifierIndex(Vex.Flow.Clef), 1); 24 | // set default stave clef 25 | this.clef = "treble"; 26 | } 27 | } 28 | 29 | Vex.Flow.Stave.prototype.removeKeySignature = function(){ 30 | var keySignatures = this.getModifiers(Vex.Flow.StaveModifier.Position.BEGIN, 31 | Vex.Flow.KeySignature.category); 32 | if(keySignatures.length === 0) 33 | return; 34 | this.modifiers.splice(this.getModifierIndex(Vex.Flow.KeySignature), 1); 35 | } 36 | 37 | Vex.Flow.Stave.prototype.removeTimeSignature = function(){ 38 | var timeSignatures = this.getModifiers(Vex.Flow.StaveModifier.Position.BEGIN, 39 | Vex.Flow.TimeSignature.category); 40 | if(timeSignatures.length === 0) 41 | return; 42 | this.modifiers.splice(this.getModifierIndex(Vex.Flow.TimeSignature), 1); 43 | } 44 | 45 | Vex.Flow.StaveNote.prototype.getModifierIndex = function(constructor){ 46 | if(this.modifiers) 47 | for(var i = 0; i < this.modifiers.length; i++) 48 | if(this.modifiers[i] instanceof constructor) 49 | return i; 50 | return -1; 51 | } 52 | 53 | Vex.Flow.StaveNote.prototype.removeAccidental = function(){ 54 | var accidentals = this.getAccidentals(); 55 | if(accidentals && accidentals.length === 0) 56 | return; 57 | this.modifiers.splice(this.getModifierIndex(Vex.Flow.Accidental), 1); 58 | } 59 | 60 | Vex.Flow.StaveNote.prototype.setAccidental = function(i, accidental){ 61 | var accidentals = this.getAccidentals(); 62 | if(accidentals && accidentals.length !== 0) 63 | this.removeAccidental(); 64 | 65 | this.addAccidental(i, accidental); 66 | } 67 | 68 | // sets one dot to StaveNote if it doesn't have any 69 | Vex.Flow.StaveNote.prototype.setDot = function(){ 70 | if(!this.isDotted()) { 71 | this.addDotToAll(); 72 | } 73 | } 74 | 75 | // removes one dot from StaveNote 76 | Vex.Flow.StaveNote.prototype.removeDot = function(){ 77 | if(this.isDotted()) { 78 | this.dots = 0; 79 | this.modifiers.splice(this.getModifierIndex(Vex.Flow.Dot), 1); 80 | } 81 | } 82 | 83 | 84 | // for MIDI playback 85 | 86 | Vex.Flow.StaveNote.prototype.getPlayEvents = function(playInfo){ 87 | //Prepare the notes to be sent 88 | var notes = []; 89 | 90 | for(var i = 0; i < this.keys.length; i++){ 91 | notes.push(MIDI.keyToNote[this.keys[i].replace('/','').toUpperCase()]); 92 | } 93 | 94 | //Set clef offset for notes 95 | for (var i = 0; i < notes.length; i++) { 96 | notes[i] += editor.MidiClefOffsets[playInfo.clef]; 97 | }; 98 | 99 | var keyPressTime = playInfo.defaultTime / editor.table.DURATION_DICT[this.duration]; 100 | 101 | //Set the modifiers for this note (update note value) 102 | for (var i = 0; i < this.modifiers.length; i++) { 103 | var modifier = this.modifiers[i]; 104 | if(modifier instanceof Vex.Flow.Accidental){ 105 | var modValue; 106 | 107 | switch(modifier.type){ 108 | case "bb": 109 | modValue = -2; 110 | break; 111 | case "b": 112 | modValue = -1; 113 | break; 114 | case "n": 115 | modValue = 0; 116 | break; 117 | case "#": 118 | modValue = 1; 119 | break; 120 | case "##": 121 | modValue = 2; 122 | break; 123 | } 124 | 125 | notes[modifier.index] += modValue; 126 | } 127 | else if(modifier instanceof Vex.Flow.Dot){ 128 | keyPressTime *= 1.5; 129 | } 130 | 131 | }; 132 | 133 | // we don't play rests 134 | if(this.isRest()) 135 | notes = [NaN]; 136 | 137 | // velocity is set as 127 138 | 139 | var events = []; 140 | 141 | events.push({ 142 | type: 'channel', 143 | channel: 0, 144 | subtype: notes.length==1?'noteOn':'chordOn', 145 | noteNumber: notes.length==1?notes[0]:notes, 146 | velocity: 127, 147 | queuedTime: playInfo.delay, 148 | note: this 149 | }); 150 | events.push({ 151 | type: 'channel', 152 | channel: 0, 153 | subtype: notes.length==1?'noteOff':'chordOff', 154 | noteNumber: notes.length==1?notes[0]:notes, 155 | queuedTime: playInfo.delay + keyPressTime, 156 | note: this 157 | }); 158 | 159 | 160 | //increment the delay 161 | playInfo.delay = playInfo.delay + keyPressTime; 162 | 163 | return events; 164 | }; 165 | -------------------------------------------------------------------------------- /src/edit.js: -------------------------------------------------------------------------------- 1 | editor.edit = { 2 | // changes selected notes pitch 3 | notePitch: function(interval){ 4 | // get and parse id of selected note (id='m13n10') 5 | var measureIndex = getSelectedMeasureIndex(); 6 | var noteIndex = getSelectedNoteIndex(); 7 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 8 | // if note is rest, do nothing 9 | if(vfStaveNote.isRest()) 10 | return; 11 | // get notes duration 12 | var duration = vfStaveNote.getDuration(); 13 | // get notes pitch; currently no chord support 14 | var key = vfStaveNote.getKeys()[0]; // e.g. 'g##/4' 15 | // transpose note 16 | var newKey = editor.NoteTool.transposeNote(key, interval); 17 | // get current clef 18 | var currentClef = getCurAttrForMeasure(measureIndex, 'vfClef'); 19 | // create new Vex.Flow.StaveNote 20 | var newNote = new Vex.Flow.StaveNote({ 21 | keys: [ newKey ], 22 | duration: duration, // TODO add dots: /d*/ 23 | clef: currentClef, 24 | auto_stem: true 25 | }); 26 | // set id for note DOM element in svg 27 | newNote.setId(editor.selected.note.id); 28 | // set dots for a rest, however, currently supports only one dot(see parse.js line 140) 29 | if(vfStaveNote.isDotted()) { 30 | var dots = vfStaveNote.getDots().length; 31 | for(var i = 0; i < dots; i++) 32 | newNote.addDotToAll(); 33 | } 34 | // replace old note with a transposed one 35 | gl_VfStaveNotes[measureIndex].splice(noteIndex, 1, newNote); 36 | // change pitch property in json 37 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch 38 | .step = newKey[0].toUpperCase(); 39 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch 40 | .octave = newKey[newKey.length - 1]; 41 | // delete accidental if any 42 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].accidental; 43 | // uncheck checked accidental radio button 44 | $("input:radio[name='note-accidental']:checked").prop("checked", false); 45 | }, 46 | 47 | // TODO change duration in json also 48 | noteDuration: function() { 49 | var measureIndex = getSelectedMeasureIndex(); 50 | var noteIndex = getSelectedNoteIndex(); 51 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 52 | 53 | var noteDuration = getRadioValue('note-value'); 54 | 55 | // get notes pitch; currently no chord support 56 | var key = vfStaveNote.getKeys()[0]; // e.g. 'g##/4' 57 | var rest = vfStaveNote.isRest() ? 'r' : ''; 58 | 59 | if(vfStaveNote.getAccidentals()) 60 | var accOfSelNote = vfStaveNote.getAccidentals()[0].type; 61 | 62 | // get current clef 63 | var currentClef = getCurAttrForMeasure(measureIndex, 'vfClef'); 64 | 65 | // create new Vex.Flow.StaveNote 66 | var newNote = new Vex.Flow.StaveNote({ 67 | keys: [ key ], 68 | duration: noteDuration + rest, // TODO add dots: /d*/ 69 | clef: currentClef, 70 | auto_stem: true 71 | }); 72 | 73 | if(accOfSelNote) 74 | newNote.addAccidental(0, new Vex.Flow.Accidental(accOfSelNote)); 75 | 76 | // set id for note DOM element in svg 77 | newNote.setId(editor.selected.note.id); 78 | // set dots for a rest, however, currently supports only one dot(see parse.js line 140) 79 | if(vfStaveNote.isDotted()) { 80 | var dots = vfStaveNote.getDots().length; 81 | for(var i = 0; i < dots; i++) 82 | newNote.addDotToAll(); 83 | } 84 | // replace old note with a transposed one 85 | gl_VfStaveNotes[measureIndex].splice(noteIndex, 1, newNote); 86 | 87 | // change duration in json 88 | // get divisions 89 | var divisions = 0; 90 | // finds attributes of closest previous measure or current measure 91 | divisions = getCurAttrForMeasure(measureIndex, 'xmlDivisions'); 92 | // for(var a = 0; a <= measureIndex; a++) 93 | // if(! $.isEmptyObject(gl_StaveAttributes[a]) && gl_StaveAttributes[a].xmlDivisions) 94 | // divisions = gl_StaveAttributes[a].xmlDivisions; 95 | 96 | if(!divisions) 97 | console.error('divisions for measures 1 to '+(measureIndex+1)+' are not set'); 98 | 99 | var xmlDuration = editor.NoteTool.getDurationFromStaveNote(newNote, divisions); 100 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].duration = xmlDuration; 101 | 102 | }, 103 | 104 | noteDot: function() { 105 | var measureIndex = getSelectedMeasureIndex(); 106 | var noteIndex = getSelectedNoteIndex(); 107 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 108 | 109 | if(vfStaveNote.isDotted()) { 110 | vfStaveNote.removeDot(); 111 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].dot; 112 | } 113 | else { 114 | vfStaveNote.setDot(); 115 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].dot = null; 116 | } 117 | }, 118 | 119 | noteAccidental: function(vexAcc) { 120 | var measureIndex = getSelectedMeasureIndex(); 121 | var noteIndex = getSelectedNoteIndex(); 122 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 123 | 124 | vfStaveNote.setAccidental(0, new Vex.Flow.Accidental(vexAcc)); 125 | 126 | // add accidental to json 127 | var xmlAcc = ''; 128 | for(var xmlname in editor.table.ACCIDENTAL_DICT) 129 | if(vexAcc === editor.table.ACCIDENTAL_DICT[xmlname]) 130 | xmlAcc = xmlname; 131 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].accidental = xmlAcc; 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /lib/bootstrap-filestyle.min.js: -------------------------------------------------------------------------------- 1 | (function($){var nextId=0;var Filestyle=function(element,options){this.options=options;this.$elementFilestyle=[];this.$element=$(element)};Filestyle.prototype={clear:function(){this.$element.val("");this.$elementFilestyle.find(":text").val("");this.$elementFilestyle.find(".badge").remove()},destroy:function(){this.$element.removeAttr("style").removeData("filestyle");this.$elementFilestyle.remove()},disabled:function(value){if(value===true){if(!this.options.disabled){this.$element.attr("disabled","true");this.$elementFilestyle.find("label").attr("disabled","true");this.options.disabled=true}}else{if(value===false){if(this.options.disabled){this.$element.removeAttr("disabled");this.$elementFilestyle.find("label").removeAttr("disabled");this.options.disabled=false}}else{return this.options.disabled}}},buttonBefore:function(value){if(value===true){if(!this.options.buttonBefore){this.options.buttonBefore=true;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{if(value===false){if(this.options.buttonBefore){this.options.buttonBefore=false;if(this.options.input){this.$elementFilestyle.remove();this.constructor();this.pushNameFiles()}}}else{return this.options.buttonBefore}}},icon:function(value){if(value===true){if(!this.options.icon){this.options.icon=true;this.$elementFilestyle.find("label").prepend(this.htmlIcon())}}else{if(value===false){if(this.options.icon){this.options.icon=false;this.$elementFilestyle.find(".icon-span-filestyle").remove()}}else{return this.options.icon}}},input:function(value){if(value===true){if(!this.options.input){this.options.input=true;if(this.options.buttonBefore){this.$elementFilestyle.append(this.htmlInput())}else{this.$elementFilestyle.prepend(this.htmlInput())}this.$elementFilestyle.find(".badge").remove();this.pushNameFiles();this.$elementFilestyle.find(".group-span-filestyle").addClass("input-group-btn")}}else{if(value===false){if(this.options.input){this.options.input=false;this.$elementFilestyle.find(":text").remove();var files=this.pushNameFiles();if(files.length>0&&this.options.badge){this.$elementFilestyle.find("label").append(' '+files.length+"")}this.$elementFilestyle.find(".group-span-filestyle").removeClass("input-group-btn")}}else{return this.options.input}}},size:function(value){if(value!==undefined){var btn=this.$elementFilestyle.find("label"),input=this.$elementFilestyle.find("input");btn.removeClass("btn-lg btn-sm");input.removeClass("input-lg input-sm");if(value!="nr"){btn.addClass("btn-"+value);input.addClass("input-"+value)}}else{return this.options.size}},placeholder:function(value){if(value!==undefined){this.options.placeholder=value;this.$elementFilestyle.find("input").attr("placeholder",value)}else{return this.options.placeholder}},buttonText:function(value){if(value!==undefined){this.options.buttonText=value;this.$elementFilestyle.find("label .buttonText").html(this.options.buttonText)}else{return this.options.buttonText}},buttonName:function(value){if(value!==undefined){this.options.buttonName=value;this.$elementFilestyle.find("label").attr({"class":"btn "+this.options.buttonName})}else{return this.options.buttonName}},iconName:function(value){if(value!==undefined){this.$elementFilestyle.find(".icon-span-filestyle").attr({"class":"icon-span-filestyle "+this.options.iconName})}else{return this.options.iconName}},htmlIcon:function(){if(this.options.icon){return' '}else{return""}},htmlInput:function(){if(this.options.input){return' '}else{return""}},pushNameFiles:function(){var content="",files=[];if(this.$element[0].files===undefined){files[0]={name:this.$element[0]&&this.$element[0].value}}else{files=this.$element[0].files}for(var i=0;i";html=_self.options.buttonBefore?btn+_self.htmlInput():_self.htmlInput()+btn;_self.$elementFilestyle=$('
'+html+"
");_self.$elementFilestyle.find(".group-span-filestyle").attr("tabindex","0").keypress(function(e){if(e.keyCode===13||e.charCode===32){_self.$elementFilestyle.find("label").click();return false}});_self.$element.css({position:"absolute",clip:"rect(0px 0px 0px 0px)"}).attr("tabindex","-1").after(_self.$elementFilestyle);if(_self.options.disabled){_self.$element.attr("disabled","true")}_self.$element.change(function(){var files=_self.pushNameFiles();if(_self.options.input==false&&_self.options.badge){if(_self.$elementFilestyle.find(".badge").length==0){_self.$elementFilestyle.find("label").append(' '+files.length+"")}else{if(files.length==0){_self.$elementFilestyle.find(".badge").remove()}else{_self.$elementFilestyle.find(".badge").html(files.length)}}}else{_self.$elementFilestyle.find(".badge").remove()}});if(window.navigator.userAgent.search(/firefox/i)>-1){_self.$elementFilestyle.find("label").click(function(){_self.$element.click();return false})}}};var old=$.fn.filestyle;$.fn.filestyle=function(option,value){var get="",element=this.each(function(){if($(this).attr("type")==="file"){var $this=$(this),data=$this.data("filestyle"),options=$.extend({},$.fn.filestyle.defaults,option,typeof option==="object"&&option);if(!data){$this.data("filestyle",(data=new Filestyle(this,options)));data.constructor()}if(typeof option==="string"){get=data[option](value)}}});if(typeof get!==undefined){return get}else{return element}};$.fn.filestyle.defaults={buttonText:"Choose file",iconName:"glyphicon glyphicon-folder-open",buttonName:"btn-default",size:"nr",input:true,badge:true,icon:true,buttonBefore:false,disabled:false,placeholder:""};$.fn.filestyle.noConflict=function(){$.fn.filestyle=old;return this};$(function(){$(".filestyle").each(function(){var $this=$(this),options={input:$this.attr("data-input")==="false"?false:true,icon:$this.attr("data-icon")==="false"?false:true,buttonBefore:$this.attr("data-buttonBefore")==="true"?true:false,disabled:$this.attr("data-disabled")==="true"?true:false,size:$this.attr("data-size"),buttonText:$this.attr("data-buttonText"),buttonName:$this.attr("data-buttonName"),iconName:$this.attr("data-iconName"),badge:$this.attr("data-badge")==="false"?false:true,placeholder:$this.attr("data-placeholder")};$this.filestyle(options)})})})(window.jQuery); -------------------------------------------------------------------------------- /src/player.js: -------------------------------------------------------------------------------- 1 | /* Author: Andre Bakker, VexUI project 2 | * This class requires the MIDI.js in the project to work correctly. 3 | * 4 | */ 5 | 6 | Player = function(){ //(handler){ 7 | this.events = []; 8 | // this.handler = handler; 9 | this.currentTime = 0; 10 | this.currentEventIndex = 0; 11 | this.ready = false; 12 | this.loadInstrument("acoustic_grand_piano"); 13 | this.playing = false; 14 | }; 15 | 16 | Player.prototype.loadInstrument = function(instrumentName){ //, onReady){ 17 | var player = this; 18 | //Initialize the player 19 | MIDI.loadPlugin({ 20 | soundfontUrl: "./soundfont/", 21 | instrument: instrumentName, 22 | callback: function(){ 23 | player.ready = true; 24 | // if(onReady) 25 | // onReady(); 26 | } 27 | }); 28 | } 29 | 30 | Player.prototype.onPlayFinished = function(callback){ 31 | this.callback = callback; 32 | } 33 | 34 | /** 35 | * Add functionality to add events manually, instead of using loadFile 36 | * @param event -> must have these attributes: 37 | * channel -> integer 38 | * subtype -> 'noteOn' | 'noteOff' | 'chordOn' | 'chordOff' 39 | * noteNumber -> integer 40 | * velocity -> integer (only required when subtype == 'noteOn' | 'chordOn') 41 | * queuedTime -> float (when the event will be triggered) 42 | */ 43 | Player.prototype.addEvent = function(event){ 44 | this.events.push(event); 45 | }; 46 | 47 | 48 | Player.prototype.addEvents = function(eventList){ 49 | this.events = this.events.concat(eventList); 50 | }; 51 | 52 | Player.prototype.play = function(self){ 53 | console.log('player play()'); 54 | if(self === undefined) 55 | self = this; 56 | self.playing = true; 57 | if(self.currentEventIndex >= self.events.length){ 58 | self.playing = false; 59 | return self.callback(); 60 | } 61 | 62 | var event = self.events[self.currentEventIndex]; 63 | 64 | if(self.currentTime <= event.queuedTime){ 65 | //Fire the event 66 | self.fireEvent(event); 67 | 68 | //Increment the current event and add current time 69 | if(self.currentEventIndex + 1 >= self.events.length){ 70 | self.playing = false; 71 | return self.callback(); 72 | } 73 | var timeUntilNextEvent = self.events[self.currentEventIndex + 1].queuedTime - 74 | self.events[self.currentEventIndex].queuedTime; 75 | 76 | self.currentEventIndex++; 77 | self.currentTime += timeUntilNextEvent; 78 | 79 | self.scheduledId = setTimeout(self.play, timeUntilNextEvent * 1000, self); 80 | } 81 | 82 | }; 83 | 84 | Player.prototype.stop = function(){ 85 | console.log('player stop'); 86 | if(this.scheduledId){ 87 | clearTimeout(this.scheduledId); 88 | this.clear(); 89 | this.playing = false; 90 | while(this.events.length){ 91 | var event = this.events.pop(); 92 | if(event.subtype == "noteOff" || event.subtype == "chordOff") 93 | this.fireEvent(event); 94 | } 95 | } 96 | }; 97 | 98 | 99 | Player.prototype.fireEvent = function(event){ 100 | switch(event.subtype){ 101 | case 'noteOn': 102 | MIDI.noteOn(event.channel, event.noteNumber, event.velocity, 0); 103 | $('svg #vf-'+event.note.id).colourNote("red"); 104 | // event.note.setHighlight(true); 105 | // self.handler.redraw(); 106 | break; 107 | case 'noteOff': 108 | MIDI.noteOff(event.channel, event.noteNumber, 0); 109 | $('svg #vf-'+event.note.id).colourNote("black"); 110 | // event.note.setHighlight(false); 111 | // self.handler.redraw(); 112 | break; 113 | case 'chordOn': 114 | MIDI.chordOn(event.channel, event.noteNumber, event.velocity, 0); 115 | // event.note.setHighlight(true); 116 | // self.handler.redraw(); 117 | break; 118 | case 'chordOff': 119 | MIDI.chordOff(event.channel, event.noteNumber, 0); 120 | // event.note.setHighlight(false); 121 | // self.handler.redraw(); 122 | break; 123 | } 124 | }; 125 | 126 | Player.prototype.clear = function(){ 127 | this.scheduledId = null; 128 | this.currentTime = 0; 129 | this.currentEventIndex = 0; 130 | }; 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | editor.player = new Player(); 139 | 140 | editor.play = function(){ 141 | console.log('editor play()'); 142 | var playButton, stopButton; 143 | 144 | playButton = document.getElementById("button-play"); 145 | stopButton = document.getElementById("button-stop"); 146 | 147 | //enable stop, disable play 148 | stopButton.disabled = false; 149 | playButton.disabled = true; 150 | 151 | //TODO RPM should be set outside... 152 | var rpm = 120; 153 | var playInfo = { 154 | delay: 0, 155 | rpm: rpm, 156 | defaultTime : (rpm / 60) // to seconds 157 | }; 158 | //var script = "MIDI.setVolume(0, 127);"; 159 | var playEvents = []; 160 | 161 | var selMeasureIn = getSelectedMeasureIndex(); 162 | var selNoteIn = getSelectedNoteIndex(); 163 | 164 | for(var i = selMeasureIn; i < gl_VfStaves.length; i++){ 165 | var stave = gl_VfStaves[i]; 166 | 167 | // set clef to playinfo 168 | // playInfo.clef = stave.clef; 169 | 170 | // get clef rather from my saved attributes, not from Vex.Flow.Stave object, 171 | // because VexFlow has it tied with graphical element of clef, 172 | // but I want clef information only for playing 173 | playInfo.clef = getCurAttrForMeasure(i, 'vfClef'); 174 | 175 | //Call initial barline play events 176 | // var barNote = new Vex.Flow.BarNote(); 177 | // barNote.setType(stave.modifiers[0].barline); 178 | // playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents)); 179 | 180 | var j = (i == selMeasureIn) ? selNoteIn : 0; 181 | 182 | for(; j < gl_VfStaveNotes[i].length; j++){ 183 | 184 | var staveNote = gl_VfStaveNotes[i][j]; 185 | playEvents = playEvents.concat(staveNote.getPlayEvents(playInfo));//, playEvents)); 186 | } 187 | 188 | //Call final barline play events 189 | // barNote.setType(stave.modifiers[1].barline); 190 | // playEvents = playEvents.concat(barNote.getPlayEvents(playInfo, playEvents)); 191 | } 192 | 193 | editor.player.addEvents(playEvents); 194 | editor.player.onPlayFinished(function(){ 195 | //Reenable play and disable stop 196 | playButton.disabled = false; 197 | stopButton.disabled = true; 198 | // highlight first played note 199 | $('svg #vf-'+editor.selected.note.id).colourNote("red"); 200 | }); 201 | editor.player.play(); 202 | }; 203 | 204 | editor.stop = function(){ 205 | console.log('editor stop'); 206 | editor.player.stop(); 207 | 208 | playButton = document.getElementById("button-play"); 209 | stopButton = document.getElementById("button-stop"); 210 | 211 | //enable stop, disable play 212 | playButton.disabled = false; 213 | stopButton.disabled = true; 214 | 215 | // highlight first played note 216 | $('svg #vf-'+editor.selected.note.id).colourNote("red"); 217 | }; -------------------------------------------------------------------------------- /src/listeners.js: -------------------------------------------------------------------------------- 1 | // redraw whole score when window resizes 2 | debouncedResize = null; 3 | $(window).resize(function() { 4 | if (! debouncedResize) 5 | debouncedResize = setTimeout(function() { 6 | editor.draw.score(); 7 | debouncedResize = null; 8 | }, 50); 9 | }); 10 | // $(window).resize(editor.draw.score); 11 | 12 | // for highlighting measures 13 | function attachListenersToMeasureRect(measureRectElem) { 14 | // to avoid multiple handlers attachment 15 | if(measureRectElem.data('handlers-added')) 16 | return true; 17 | measureRectElem.data('handlers-added', true); 18 | 19 | measureRectElem.on('click', function() { 20 | $(this).css({'fill': editor.measureColor, 'opacity': '0.4'}); 21 | console.log($(this).attr('id')); 22 | // if it is not second click on already selected measure 23 | if(editor.selected.measure.id !== $(this).attr('id')) { 24 | // save currently selected id to previous 25 | editor.selected.measure.previousId = editor.selected.measure.id; 26 | editor.selected.measure.id = $(this).attr('id'); 27 | editor.selected.note.previousId = editor.selected.note.id; 28 | editor.selected.note.id = $(this).attr('id') + 'n0'; 29 | var prevId = editor.selected.measure.previousId; 30 | $('svg .measureRect#'+prevId).css({'fill': 'transparent'}); 31 | $('svg .measureRect#'+editor.selected.measure.id) 32 | .css({'fill': editor.measureColor, 'opacity': '0.4'}); 33 | highlightSelectedMeasureProperties(); 34 | } 35 | }); 36 | measureRectElem.on('mouseenter', function() { 37 | if(editor.selected.measure.id !== $(this).attr('id')) 38 | $(this).css({'fill': editor.measureColor, 'opacity': '0.1'}); 39 | }); 40 | measureRectElem.on('mouseleave', function() { 41 | if(editor.selected.measure.id !== $(this).attr('id')) 42 | $(this).css({'fill': 'transparent'}); 43 | }); 44 | } 45 | 46 | // for highlighting notes 47 | function attachListenersToNote(noteElem) { 48 | noteElem.addEventListener("mouseover", function() { 49 | // if editor is in mode for working with notes 50 | if(editor.mode === 'note') { 51 | // don't change colour of already selected note 52 | if(editor.selected.note.id !== $(this).attr('id').split('-')[1]) { 53 | // change colour for each note parts - stem, head, dot, accidental... 54 | $(this).colourNote("orange"); 55 | } 56 | } 57 | }, false); 58 | 59 | noteElem.addEventListener("mouseout", function() { 60 | if(editor.mode === 'note') { 61 | if(editor.selected.note.id !== $(this).attr('id').split('-')[1]) { 62 | $(this).colourNote("black"); 63 | } 64 | } 65 | }, false); 66 | 67 | noteElem.addEventListener("click", function() { 68 | if(editor.mode === 'note') { 69 | // if it is not second click on already selected note 70 | if(editor.selected.note.id !== $(this).attr('id').split('-')[1]) { 71 | $(this).colourNote("red"); 72 | // save currently selected id to previous 73 | editor.selected.measure.previousId = editor.selected.measure.id; 74 | editor.selected.note.previousId = editor.selected.note.id; 75 | // format of id: id='vf-m13n10' - eleventh note in fourteenth measure(indexing from 0) 76 | var mnId = $(this).attr('id'); 77 | // save id of newly selected note 78 | editor.selected.measure.id = mnId.split('-')[1].split('n')[0]; // 'm13' 79 | editor.selected.note.id = mnId.split('-')[1]; // 'm13n10' 80 | // unhighlight previous selected note 81 | $('svg #vf-'+editor.selected.note.previousId).colourNote("black"); 82 | // highlight properties on control panel accordingly 83 | highlightSelectedNoteProperties(); 84 | } 85 | } 86 | }, false); 87 | 88 | } 89 | 90 | jQuery.fn.colourNote = function (colour) { 91 | Vex.forEach(this.find("*"), function(child) { 92 | child.setAttribute("fill", colour); 93 | child.setAttribute("stroke", colour); 94 | }); 95 | return this; 96 | } 97 | 98 | clefDropdown = $("#clef-dropdown").selectBoxIt().data("selectBox-selectBoxIt"); // use fancy theme for select box 99 | $("#clef-dropdown").on("change", function() { 100 | // see comment in util.js in highlightSelectedMeasureProperties() 101 | if(!gl_selectBoxChangeOnMeasureSelect) { 102 | editor.add.clef(); 103 | editor.draw.score(); 104 | } 105 | }); 106 | 107 | keySigDropdown = $("#keySig-dropdown").selectBoxIt().data("selectBox-selectBoxIt"); 108 | $("#keySig-dropdown").on("change", function() { 109 | // see comment in util.js in highlightSelectedMeasureProperties() 110 | if(!gl_selectBoxChangeOnMeasureSelect) { 111 | editor.add.keySignature(); 112 | editor.draw.score(); 113 | } 114 | }); 115 | 116 | examplesDropdown = $("#examples-dropdown").selectBoxIt().data("selectBox-selectBoxIt"); 117 | $("#examples-dropdown").on("change", function() { 118 | var url = $("#examples-dropdown").val(); 119 | if(url !== 'default') 120 | loadExample(url); 121 | }); 122 | 123 | $("#timeSig-button").on("click", function() { 124 | editor.add.timeSignature(); 125 | // editor.draw.selectedMeasure(); 126 | editor.draw.score(); 127 | }); 128 | 129 | timeSigTop = $("#timeSigTop").selectBoxIt().data("selectBox-selectBoxIt"); 130 | timeSigBottom = $("#timeSigBottom").selectBoxIt().data("selectBox-selectBoxIt"); 131 | 132 | // setting/removing accidental to/from note via radio buttons 133 | $("input:radio[name='note-accidental']").on("click",function() { 134 | var radio = $(this); 135 | 136 | // get selected note 137 | var selNote = getSelectedNote(); 138 | 139 | // don't set accidental for rest 140 | if(selNote.isRest()) { 141 | // uncheck this checked radio button after while 142 | setTimeout(function() { 143 | $("input:radio[name='note-accidental']:checked").prop("checked", false); 144 | }, 50); 145 | return; 146 | } 147 | 148 | // radio already checked, uncheck it 149 | if(radio.is(".selAcc")) { 150 | // console.log('uncheck'); 151 | radio.prop("checked",false).removeClass("selAcc"); 152 | editor.delete.accidental(); 153 | } 154 | // radio unchecked, check it 155 | else { 156 | // console.log('check'); 157 | $("input:radio[name='"+radio.prop("name")+"'].selAcc").removeClass("selAcc"); 158 | radio.addClass("selAcc"); 159 | var vexAcc = $(this).prop("value"); 160 | // console.log($(this).prop("value")); 161 | editor.edit.noteAccidental(vexAcc); 162 | } 163 | editor.draw.selectedMeasure(); 164 | }); 165 | 166 | // changing note value(duration) 167 | $("input:radio[name='note-value']").on("change",function() { 168 | editor.edit.noteDuration(); 169 | editor.draw.selectedMeasure(); 170 | }); 171 | 172 | // call is already in HTML 173 | // toggle notes dot 174 | // $("#dotted-checkbox").on("change",function() { 175 | // console.log('dot checkbox change'); 176 | // editor.edit.noteDot(); 177 | // editor.draw.selectedMeasure(); 178 | // }); -------------------------------------------------------------------------------- /lib/xml2json.js: -------------------------------------------------------------------------------- 1 | /* This work is licensed under Creative Commons GNU LGPL License. 2 | 3 | License: http://creativecommons.org/licenses/LGPL/2.1/ 4 | Version: 0.9 5 | Author: Stefan Goessner/2006 6 | Web: http://goessner.net/ 7 | 8 | minor modification(added line 66): Tomas Hudziec/2016 9 | */ 10 | function xml2json(xml, tab) { 11 | var X = { 12 | toObj: function(xml) { 13 | var o = {}; 14 | if (xml.nodeType==1) { // element node .. 15 | if (xml.attributes.length) // element with attributes .. 16 | for (var i=0; i 1) 58 | o = X.escape(X.innerXml(xml)); 59 | else 60 | for (var n=xml.firstChild; n; n=n.nextSibling) 61 | o["#cdata"] = X.escape(n.nodeValue); 62 | } 63 | } 64 | if (!xml.attributes.length && !xml.firstChild) o = null; 65 | } 66 | else if (xml.nodeType==8) {} // comment node; added by Tomas Hudziec/2016 67 | else if (xml.nodeType==9) { // document.node 68 | o = X.toObj(xml.documentElement); 69 | } 70 | else 71 | alert("[Conversion xml to json]: unhandled node type: " + xml.nodeType); 72 | return o; 73 | }, 74 | toJson: function(o, name, ind) { 75 | var json = name ? ("\""+name+"\"") : ""; 76 | if (o instanceof Array) { 77 | for (var i=0,n=o.length; i 1 ? ("\n"+ind+"\t"+o.join(",\n"+ind+"\t")+"\n"+ind) : o.join("")) + "]"; 80 | } 81 | else if (o == null) 82 | json += (name&&":") + "null"; 83 | else if (typeof(o) == "object") { 84 | var arr = []; 85 | for (var m in o) 86 | arr[arr.length] = X.toJson(o[m], m, ind+"\t"); 87 | json += (name?":{":"{") + (arr.length > 1 ? ("\n"+ind+"\t"+arr.join(",\n"+ind+"\t")+"\n"+ind) : arr.join("")) + "}"; 88 | } 89 | else if (typeof(o) == "string") 90 | json += (name&&":") + "\"" + o.toString() + "\""; 91 | else 92 | json += (name&&":") + o.toString(); 93 | return json; 94 | }, 95 | innerXml: function(node) { 96 | var s = "" 97 | if ("innerHTML" in node) 98 | s = node.innerHTML; 99 | else { 100 | var asXml = function(n) { 101 | var s = ""; 102 | if (n.nodeType == 1) { 103 | s += "<" + n.nodeName; 104 | for (var i=0; i"; 111 | } 112 | else 113 | s += "/>"; 114 | } 115 | else if (n.nodeType == 3) 116 | s += n.nodeValue; 117 | else if (n.nodeType == 4) 118 | s += ""; 119 | return s; 120 | }; 121 | for (var c=node.firstChild; c; c=c.nextSibling) 122 | s += asXml(c); 123 | } 124 | return s; 125 | }, 126 | escape: function(txt) { 127 | return txt.replace(/[\\]/g, "\\\\") 128 | .replace(/[\"]/g, '\\"') 129 | .replace(/[\n]/g, '\\n') 130 | .replace(/[\r]/g, '\\r'); 131 | }, 132 | removeWhite: function(e) { 133 | e.normalize(); 134 | for (var n = e.firstChild; n; ) { 135 | if (n.nodeType == 3) { // text node 136 | if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { // pure whitespace text node 137 | var nxt = n.nextSibling; 138 | e.removeChild(n); 139 | n = nxt; 140 | } 141 | else 142 | n = n.nextSibling; 143 | } 144 | else if (n.nodeType == 1) { // element node 145 | X.removeWhite(n); 146 | n = n.nextSibling; 147 | } 148 | else // any other node 149 | n = n.nextSibling; 150 | } 151 | return e; 152 | } 153 | }; 154 | if (xml.nodeType == 9) // document node 155 | xml = xml.documentElement; 156 | var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "\t"); 157 | return "{\n" + tab + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "\n}"; 158 | } 159 | -------------------------------------------------------------------------------- /css/jquery.selectBoxIt.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.selectBoxIt.css 3.8.1 3 | * Author: @gregfranko 4 | */ 5 | 6 | /* 7 | Common CSS Properties 8 | --------------------- 9 | These properties will be applied to any themes that you use 10 | */ 11 | 12 | /* SelectBoxIt container */ 13 | .selectboxit-container { 14 | position: relative; 15 | display: inline-block; 16 | vertical-align: top; 17 | } 18 | 19 | /* Styles that apply to all SelectBoxIt elements */ 20 | .selectboxit-container * { 21 | font: 14px Helvetica, Arial; 22 | /* Prevents text selection */ 23 | -webkit-touch-callout: none; 24 | -webkit-user-select: none; 25 | -khtml-user-select: none; 26 | -moz-user-select: -moz-none; 27 | -ms-user-select: none; 28 | -o-user-select: none; 29 | user-select: none; 30 | outline: none; 31 | white-space: nowrap; 32 | } 33 | 34 | /* Button */ 35 | .selectboxit-container .selectboxit { 36 | width: 220px; /* Width of the dropdown button */ 37 | cursor: pointer; 38 | margin: 0; 39 | padding: 0; 40 | border-radius: 6px; 41 | overflow: hidden; 42 | display: block; 43 | position: relative; 44 | } 45 | 46 | /* Height and Vertical Alignment of Text */ 47 | .selectboxit-container span, .selectboxit-container .selectboxit-options a { 48 | height: 30px; /* Height of the drop down */ 49 | line-height: 30px; /* Vertically positions the drop down text */ 50 | display: block; 51 | } 52 | 53 | /* Focus pseudo selector */ 54 | .selectboxit-container .selectboxit:focus { 55 | outline: 0; 56 | } 57 | 58 | /* Disabled Mouse Interaction */ 59 | .selectboxit.selectboxit-disabled, .selectboxit-options .selectboxit-disabled { 60 | opacity: 0.65; 61 | filter: alpha(opacity=65); 62 | -webkit-box-shadow: none; 63 | -moz-box-shadow: none; 64 | box-shadow: none; 65 | cursor: default; 66 | } 67 | 68 | /* Button Text */ 69 | .selectboxit-text { 70 | text-indent: 5px; 71 | overflow: hidden; 72 | text-overflow: ellipsis; 73 | float: left; 74 | } 75 | 76 | .selectboxit .selectboxit-option-icon-container { 77 | margin-left: 5px; 78 | } 79 | 80 | /* Options List */ 81 | .selectboxit-container .selectboxit-options { 82 | -moz-box-sizing: border-box; 83 | box-sizing: border-box; 84 | min-width: 100%; /* Minimum Width of the dropdown list box options */ 85 | *width: 100%; 86 | margin: 0; 87 | padding: 0; 88 | list-style: none; 89 | position: absolute; 90 | overflow-x: hidden; 91 | overflow-y: auto; 92 | cursor: pointer; 93 | display: none; 94 | z-index: 9999999999999; 95 | border-radius: 6px; 96 | text-align: left; 97 | -webkit-box-shadow: none; 98 | -moz-box-shadow: none; 99 | box-shadow: none; 100 | } 101 | 102 | /* Individual options */ 103 | .selectboxit-option .selectboxit-option-anchor{ 104 | padding: 0 2px; 105 | } 106 | 107 | /* Individual Option Hover Action */ 108 | .selectboxit-option .selectboxit-option-anchor:hover { 109 | text-decoration: none; 110 | } 111 | 112 | /* Individual Option Optgroup Header */ 113 | .selectboxit-option, .selectboxit-optgroup-header { 114 | text-indent: 5px; /* Horizontal Positioning of the select box option text */ 115 | margin: 0; 116 | list-style-type: none; 117 | } 118 | 119 | /* The first Drop Down option */ 120 | .selectboxit-option-first { 121 | border-top-right-radius: 6px; 122 | border-top-left-radius: 6px; 123 | } 124 | 125 | /* The first Drop Down option optgroup */ 126 | .selectboxit-optgroup-header + .selectboxit-option-first { 127 | border-top-right-radius: 0px; 128 | border-top-left-radius: 0px; 129 | } 130 | 131 | /* The last Drop Down option */ 132 | .selectboxit-option-last { 133 | border-bottom-right-radius: 6px; 134 | border-bottom-left-radius: 6px; 135 | } 136 | 137 | /* Drop Down optgroup headers */ 138 | .selectboxit-optgroup-header { 139 | font-weight: bold; 140 | } 141 | 142 | /* Drop Down optgroup header hover psuedo class */ 143 | .selectboxit-optgroup-header:hover { 144 | cursor: default; 145 | } 146 | 147 | /* Drop Down down arrow container */ 148 | .selectboxit-arrow-container { 149 | /* Positions the down arrow */ 150 | width: 30px; 151 | position: absolute; 152 | right: 0; 153 | } 154 | 155 | /* Drop Down down arrow */ 156 | .selectboxit .selectboxit-arrow-container .selectboxit-arrow { 157 | /* Horizontally centers the down arrow */ 158 | margin: 0 auto; 159 | position: absolute; 160 | top: 50%; 161 | right: 0; 162 | left: 0; 163 | } 164 | 165 | /* Drop Down down arrow for jQueryUI and jQuery Mobile */ 166 | .selectboxit .selectboxit-arrow-container .selectboxit-arrow.ui-icon { 167 | top: 30%; 168 | } 169 | 170 | /* Drop Down individual option icon positioning */ 171 | .selectboxit-option-icon-container { 172 | float: left; 173 | } 174 | 175 | .selectboxit-container .selectboxit-option-icon { 176 | margin: 0; 177 | padding: 0; 178 | vertical-align: middle; 179 | } 180 | 181 | /* Drop Down individual option icon positioning */ 182 | .selectboxit-option-icon-url { 183 | width: 18px; 184 | background-size: 18px 18px; 185 | background-repeat: no-repeat; 186 | height: 100%; 187 | background-position: center; 188 | float: left; 189 | } 190 | 191 | .selectboxit-rendering { 192 | display: inline-block !important; 193 | *display: inline !important; 194 | zoom: 1 !important; 195 | visibility: visible !important; 196 | position: absolute !important; 197 | top: -9999px !important; 198 | left: -9999px !important; 199 | } 200 | 201 | /* jQueryUI and jQuery Mobile compatability fix - Feel free to remove this style if you are not using jQuery Mobile */ 202 | .jqueryui .ui-icon { 203 | background-color: inherit; 204 | } 205 | 206 | /* Another jQueryUI and jQuery Mobile compatability fix - Feel free to remove this style if you are not using jQuery Mobile */ 207 | .jqueryui .ui-icon-triangle-1-s { 208 | background-position: -64px -16px; 209 | } 210 | 211 | /* 212 | Default Theme 213 | ------------- 214 | Note: Feel free to remove all of the CSS underneath this line if you are not using the default theme 215 | */ 216 | .selectboxit-btn { 217 | background-color: #f5f5f5; 218 | background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); 219 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); 220 | background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 221 | background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); 222 | background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); 223 | background-repeat: repeat-x; 224 | border: 1px solid #cccccc; 225 | border-color: #e6e6e6 #e6e6e6 #bfbfbf; 226 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 227 | border-bottom-color: #b3b3b3; 228 | } 229 | 230 | .selectboxit-btn.selectboxit-enabled:hover, 231 | .selectboxit-btn.selectboxit-enabled:focus, 232 | .selectboxit-btn.selectboxit-enabled:active { 233 | color: #333333; 234 | background-color: #e6e6e6; 235 | } 236 | 237 | .selectboxit-btn.selectboxit-enabled:hover, 238 | .selectboxit-btn.selectboxit-enabled:focus { 239 | color: #333333; 240 | text-decoration: none; 241 | background-position: 0 -15px; 242 | } 243 | 244 | .selectboxit-default-arrow { 245 | width: 0; 246 | height: 0; 247 | border-top: 4px solid #000000; 248 | border-right: 4px solid transparent; 249 | border-left: 4px solid transparent; 250 | } 251 | 252 | .selectboxit-list { 253 | background-color: #ffffff; 254 | border: 1px solid #ccc; 255 | border: 1px solid rgba(0, 0, 0, 0.2); 256 | -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 257 | -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 258 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); 259 | } 260 | 261 | .selectboxit-list .selectboxit-option-anchor { 262 | color: #333333; 263 | } 264 | 265 | .selectboxit-list > .selectboxit-focus > .selectboxit-option-anchor { 266 | color: #ffffff; 267 | background-color: #0081c2; 268 | background-image: -moz-linear-gradient(top, #0088cc, #0077b3); 269 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); 270 | background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); 271 | background-image: -o-linear-gradient(top, #0088cc, #0077b3); 272 | background-image: linear-gradient(to bottom, #0088cc, #0077b3); 273 | background-repeat: repeat-x; 274 | } 275 | 276 | .selectboxit-list > .selectboxit-disabled > .selectboxit-option-anchor { 277 | color: #999999; 278 | } -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | function switchToNoteMode() { 2 | if(editor.mode !== 'note') { 3 | editor.mode = 'note'; 4 | editor.svgElem.addEventListener('mousemove', redrawMeasureWithCursorNote, false); 5 | editor.draw.score(); 6 | } 7 | } 8 | 9 | function switchToMeasureMode() { 10 | if(editor.mode !== 'measure') { 11 | editor.mode = 'measure'; 12 | editor.svgElem.removeEventListener('mousemove', redrawMeasureWithCursorNote, false); 13 | editor.draw.score(); 14 | } 15 | } 16 | 17 | // draws note, which is to be added, below mouse cursor when it is 18 | // moving in column of selected note(only rest currenly) 19 | function redrawMeasureWithCursorNote(event) { 20 | // get mouse position 21 | editor.mousePos.current = getMousePos(editor.svgElem, event); 22 | 23 | // get selected measure and note 24 | var vfStaveNote = getSelectedNote(); 25 | var vfStave = getSelectedMeasure(); 26 | 27 | // currently support only for replacing rest with a new note 28 | // building chords feature will be added soon 29 | if(!vfStaveNote.isRest()) return; 30 | 31 | // get column of selected note on stave 32 | var bb = vfStave.getBoundingBox(); 33 | var begin = vfStaveNote.getNoteHeadBeginX() - 5; 34 | bb.setX(begin); 35 | bb.setW(vfStaveNote.getNoteHeadEndX() - begin + 5); 36 | // bb.setW(20); 37 | // bb.draw(editor.ctx); 38 | 39 | // mouse cursor is within note column 40 | if(isCursorInBoundingBox(bb, editor.mousePos.current) ) { 41 | // save mouse position 42 | editor.mousePos.previous = editor.mousePos.current; 43 | // get new note below mouse cursor 44 | editor.selected.cursorNoteKey = getCursorNoteKey(); 45 | 46 | editor.svgElem.addEventListener('click', editor.add.note, false); 47 | 48 | // redraw only when cursor note changed pitch 49 | // (mouse changed y position between staff lines/spaces) 50 | if(editor.lastCursorNote !== editor.selected.cursorNoteKey) { 51 | // console.log(editor.selected.cursorNoteKey); 52 | editor.draw.selectedMeasure(true); 53 | 54 | } 55 | // save previous cursor note for latter comparison 56 | editor.lastCursorNote = editor.selected.cursorNoteKey; 57 | } 58 | // mouse cursor is NOT within note column 59 | else { 60 | 61 | editor.svgElem.removeEventListener('click', editor.add.note, false); 62 | 63 | // mouse cursor just left note column(previous position was inside n.c.) 64 | if(isCursorInBoundingBox(bb, editor.mousePos.previous) ) { 65 | // redraw measure to erase cursor note 66 | editor.draw.selectedMeasure(false); 67 | editor.mousePos.previous = editor.mousePos.current; 68 | editor.lastCursorNote = ''; 69 | } 70 | } 71 | 72 | } 73 | 74 | function getMousePos(canvas, evt) { 75 | var rect = canvas.getBoundingClientRect(); 76 | return { 77 | x: evt.clientX - rect.left, 78 | y: evt.clientY - rect.top 79 | }; 80 | } 81 | 82 | function getRadioValue(name) { 83 | var radios = document.getElementsByName(name); 84 | for(var i = 0; i < radios.length; i++) 85 | if(radios[i].checked) 86 | return radios[i].value; 87 | } 88 | 89 | /* 90 | TODO: documentary comment... 91 | */ 92 | // TODO rewrite with use of vfStave.getLineForY(editor.mousePos.current.y) 93 | function getCursorNoteKey() { 94 | // find the mouse position and return the correct note for that position. 95 | var y = gl_VfStaves[editor.selected.measure.id.split('m')[1]].y; 96 | // var y = editor.selected.measure.y; 97 | var notesArray = ['c/','d/','e/','f/','g/','a/','b/']; 98 | var count = 0; 99 | 100 | for(var i = 5; i >= 0; i--){ 101 | for(var l = 0; l < notesArray.length; l++){ 102 | var noteOffset = (count * 35) - (l * 5 - 17); 103 | if(editor.mousePos.current.y >= y + noteOffset && editor.mousePos.current.y <= 5 + y + noteOffset){ 104 | var cursorNoteKey = notesArray[l] + (i+1); 105 | var found = true; 106 | break; 107 | } 108 | if(found == true){ 109 | break; 110 | } 111 | } 112 | count++; 113 | } 114 | return cursorNoteKey; 115 | } 116 | 117 | function getSelectedNoteIndex() { 118 | var mnId = editor.selected.note.id; 119 | return +mnId.split('n')[1]; 120 | } 121 | 122 | function getSelectedMeasureIndex() { 123 | var mnId = editor.selected.note.id; 124 | return +mnId.split('n')[0].split('m')[1]; 125 | } 126 | 127 | function getSelectedNote() { 128 | var mnId = editor.selected.note.id; 129 | var measureIndex = mnId.split('n')[0].split('m')[1]; 130 | var noteIndex = mnId.split('n')[1]; 131 | return gl_VfStaveNotes[measureIndex][noteIndex]; 132 | } 133 | 134 | function getSelectedMeasure() { 135 | var mnId = editor.selected.note.id; 136 | var measureIndex = mnId.split('n')[0].split('m')[1]; 137 | return gl_VfStaves[measureIndex]; 138 | } 139 | 140 | // get current attribute for measure 141 | function getCurAttrForMeasure(measureIndex, attrname) { 142 | for(var i = measureIndex; i >= 0; i--) 143 | if(gl_StaveAttributes[i][attrname]) 144 | return gl_StaveAttributes[i][attrname]; 145 | } 146 | 147 | // highlights properties of selected note on control panel 148 | function highlightSelectedNoteProperties() { 149 | var vfStaveNote = getSelectedNote(); 150 | if(vfStaveNote.getAccidentals()) 151 | var accOfSelNote = vfStaveNote.getAccidentals()[0].type; 152 | // uncheck already checked radio button 153 | $("input:radio[name='note-accidental']:checked").prop("checked", false); 154 | // set radio button for accidental of selected note 155 | if(accOfSelNote) 156 | $("input:radio[name='note-accidental'][value='"+accOfSelNote+"']").prop("checked", true); 157 | // set radio button for duration of selected note 158 | var durOfSelNote = vfStaveNote.getDuration(); 159 | $("input:radio[name='note-value'][value='"+durOfSelNote+"']").prop("checked", true); 160 | // set dotted checkbox 161 | $("#dotted-checkbox").prop("checked", vfStaveNote.isDotted()); 162 | } 163 | 164 | // highlights properties of selected measure on control panel 165 | function highlightSelectedMeasureProperties() { 166 | 167 | // in this function are set options for select boxes, 168 | // but selectBoxIt's setOption() triggers change event, which is not desired (jQuery's val() does not) 169 | // this is temporary ugly hack with global variable, 170 | // and should be replaced with better mechanism 171 | gl_selectBoxChangeOnMeasureSelect = true; 172 | 173 | var measureIndex = getSelectedMeasureIndex(); 174 | var clef = gl_StaveAttributes[measureIndex].vfClef; 175 | if(!clef) clef = getCurAttrForMeasure(measureIndex, 'vfClef'); 176 | if(clef) clefDropdown.selectOption(clef); 177 | // if(clef) $('#clef-dropdown').val(clef); 178 | var keySig = gl_StaveAttributes[measureIndex].vfKeySpec; 179 | if(!keySig) keySig = getCurAttrForMeasure(measureIndex, 'vfKeySpec'); 180 | if(keySig) keySigDropdown.selectOption(keySig); 181 | // if(keySig) $('#keySig-dropdown').val(keySig); 182 | var timeSig = gl_StaveAttributes[measureIndex].vfTimeSpec; 183 | if(!timeSig) timeSig = getCurAttrForMeasure(measureIndex, 'vfTimeSpec'); 184 | if(timeSig) { 185 | timeSigTop.selectOption(timeSig.split('/')[0]); 186 | timeSigBottom.selectOption(timeSig.split('/')[1]); 187 | // $('#timeSigTop').val(timeSig.split('/')[0]); 188 | // $('#timeSigBottom').val(timeSig.split('/')[1]); 189 | } 190 | 191 | gl_selectBoxChangeOnMeasureSelect = false; 192 | } 193 | 194 | function isCursorInBoundingBox(bBox, cursorPos) { 195 | return cursorPos.x > bBox.getX() && cursorPos.x < bBox.getX() + bBox.getW() && 196 | cursorPos.y > bBox.getY() && cursorPos.y < bBox.getY() + bBox.getH(); 197 | } 198 | 199 | /** 200 | * @param obj1 The first object 201 | * @param obj2 The second object 202 | * @returns A new object representing the merged objects. If both objects passed as param have the same prop, then obj2 property is returned. 203 | */ 204 | // author Andre Bakker, VexUI: https://github.com/andrebakker/VexUI 205 | function mergeProperties(obj1, obj2){ 206 | var merged = {}; 207 | for (var attrname in obj1) { merged[attrname] = obj1[attrname]; } 208 | for (var attrname in obj2) { merged[attrname] = obj2[attrname]; } 209 | return merged; 210 | } 211 | 212 | // Merge `destination` hash with `source` hash, overwriting like keys 213 | // in `source` if necessary. 214 | function mergePropertiesInPlace(source, destination) { 215 | for (var property in source) 216 | destination[property] = source[property]; 217 | } -------------------------------------------------------------------------------- /src/parse.js: -------------------------------------------------------------------------------- 1 | /* 2 | performs transformation from scoreJson to gl_VfStaves[] and gl_VfStaveNotes[] 3 | prepares gl_VfStaves[] and gl_VfStaveNotes[] for editor.draw.score() function 4 | */ 5 | editor.parse = { 6 | all: function() { 7 | console.log('parse'); 8 | // clear global arrays 9 | gl_VfStaves = []; 10 | gl_VfStaveNotes = []; 11 | gl_StaveAttributes = []; 12 | 13 | var vfStave; 14 | // loop over all (MusicXML measures) and make Vex.Flow.Staves from them 15 | for(var i = 0; i < scoreJson["score-partwise"].part[0].measure.length; i++) { 16 | vfStave = editor.parse.attributes(i); 17 | 18 | vfStave = editor.parse.measure(scoreJson["score-partwise"].part[0].measure[i], i, vfStave); 19 | 20 | // push measure to global array, draw() will read from it 21 | gl_VfStaves.push(vfStave); 22 | } 23 | }, 24 | 25 | measure: function(measure, index, vfStave) { 26 | var vfStaveNote, vfStaveNotesPerMeasure = []; 27 | if(measure.note) { 28 | // loop over all notes in measure 29 | for(var i = 0; i < measure.note.length; i++) { 30 | vfStaveNote = editor.parse.note(measure.note[i], index, i); 31 | vfStaveNotesPerMeasure.push(vfStaveNote); 32 | } 33 | gl_VfStaveNotes.push(vfStaveNotesPerMeasure); 34 | // width of measure directly proportional to number of notes 35 | vfStave.setWidth(vfStaveNotesPerMeasure.length * editor.noteWidth); 36 | if(vfStave.getWidth() < editor.staveWidth) 37 | vfStave.setWidth(editor.staveWidth); 38 | } 39 | else // measure doesn't have notes 40 | gl_VfStaveNotes.push([]); 41 | 42 | if(measure['@width']) { 43 | // in MusicXML measure width unit is one tenth of interline space 44 | vfStave.setWidth(measure['@width'] * (vfStave.getSpacingBetweenLines() / 10)); 45 | } 46 | 47 | return vfStave; 48 | }, 49 | 50 | attributes: function(measureIndex) { 51 | var xmlAttributes = scoreJson["score-partwise"].part[0].measure[measureIndex]['attributes'] || {}; 52 | 53 | var staveAttributes = { 54 | // intentionally commented, by default is this object empty 55 | // just to show which properties object may contain 56 | // xmlClef: '', 57 | // vfClef: '', 58 | // xmlFifths: 0, 59 | // xmlDivisions: 4, 60 | // vfKeySpec: '', 61 | // vfTimeSpec: '' 62 | }; 63 | 64 | // create one Vex.Flow.Stave, it corresponds to one 65 | var vfStave = new Vex.Flow.Stave(0, 0, editor.staveWidth); 66 | 67 | // setting attributes for measure 68 | if(! $.isEmptyObject(xmlAttributes)) { 69 | 70 | if(xmlAttributes.clef) { 71 | if($.isArray(xmlAttributes.clef)) { 72 | console.warn("Multiple clefs for measure currently not supported."); 73 | var clef = xmlAttributes.clef[0]; 74 | } 75 | else 76 | var clef = xmlAttributes.clef; 77 | 78 | staveAttributes.xmlClef = clef.sign + '/' + clef.line; 79 | staveAttributes.vfClef = editor.table.CLEF_TYPE_DICT[staveAttributes.xmlClef]; 80 | vfStave.setClef(staveAttributes.vfClef); 81 | vfStave.setWidth(vfStave.getWidth() + 80); 82 | // editor.currentClef = vfClefType; 83 | } 84 | 85 | if(xmlAttributes.key) { 86 | if(xmlAttributes.key.hasOwnProperty('fifths')) { 87 | var fifths = +xmlAttributes.key.fifths; 88 | if(fifths === 0) 89 | keySpec = 'C'; 90 | else if(fifths > 0) 91 | keySpec = editor.table.SHARP_MAJOR_KEY_SIGNATURES[fifths - 1]; 92 | else 93 | keySpec = editor.table.FLAT_MAJOR_KEY_SIGNATURES[-fifths - 1]; 94 | vfStave.setKeySignature(keySpec); 95 | vfStave.setWidth(vfStave.getWidth() + (Math.abs(fifths) * 30)); 96 | staveAttributes.vfKeySpec = keySpec; 97 | staveAttributes.xmlFifths = fifths; 98 | // editor.currentKeySig = keySpec; 99 | } 100 | } 101 | 102 | if(xmlAttributes.time) { 103 | if($.isArray(xmlAttributes.time)) { 104 | console.warn("Multiple pairs of beats and beat-type elements in time signature not supported."); 105 | var time = xmlAttributes.time[0]; 106 | } 107 | else 108 | var time = xmlAttributes.time; 109 | 110 | var timeSpec = time.beats + '/' + time['beat-type']; 111 | vfStave.setTimeSignature(timeSpec); 112 | vfStave.setWidth(vfStave.getWidth() + 80); 113 | staveAttributes.vfTimeSpec = timeSpec; 114 | // editor.currentTimeSig = timeSpec; 115 | } 116 | 117 | if(xmlAttributes.divisions) { 118 | staveAttributes.xmlDivisions = xmlAttributes.divisions; 119 | } 120 | 121 | } 122 | 123 | // push attributes to global array 124 | gl_StaveAttributes.push(staveAttributes); 125 | 126 | return vfStave; 127 | }, 128 | 129 | note: function(note, measureIndex, noteIndex) { 130 | var rest = '', step = '', oct = '', dot = '', vfAcc = ''; 131 | // get MusicXML divisions from attributes for current measure 132 | var divisions = 4; 133 | // for(var i = 0; i <= measureIndex; i++) { 134 | // if(gl_StaveAttributes[i].xmlDivisions !== undefined) 135 | // divisions = gl_StaveAttributes[i].xmlDivisions; 136 | // } 137 | divisions = getCurAttrForMeasure(measureIndex, 'xmlDivisions'); 138 | 139 | // get note length from divisions and duration 140 | var staveNoteDuration = 141 | editor.NoteTool.getStaveNoteTypeFromDuration(note.duration, divisions); 142 | // to get also dots, add third argument to function - true 143 | // but currently dots calculating algorithm doesn't work correctly 144 | // and dot is taken from element 145 | 146 | // console.log(step+'/'+oct+', '+'divisions:'+divisions 147 | // +', '+'duration:'+note.duration+' -> '+staveNoteDuration); 148 | 149 | // rest is empty element in MusicXML, to json it is converted as {rest: null} 150 | if(note.hasOwnProperty('rest')) { 151 | rest = 'r'; 152 | // key = editor.table.DEFAULT_REST_PITCH; 153 | step = 'b'; 154 | oct = '4'; 155 | // whole measure rest 156 | if(note.rest && note.rest['@measure'] === 'yes') 157 | staveNoteDuration = 'w'; 158 | } 159 | else if(note.pitch) { 160 | // key = note.pitch.step.toLowerCase() + '/' + note.pitch.octave; 161 | step = note.pitch.step.toLowerCase(); 162 | oct = note.pitch.octave; 163 | // since this project is yet not interested in how note sounds, 164 | // alter element is not needed; accidental is read from accidental element 165 | // TODO: parse also alter element and save it, we are playing also now 166 | } 167 | 168 | if(note.accidental) { 169 | // accidental element can have attributes 170 | var mXmlAcc = (typeof note.accidental === 'string') 171 | ? note.accidental 172 | : note.accidental['#text']; 173 | vfAcc = editor.table.ACCIDENTAL_DICT[mXmlAcc]; 174 | } 175 | 176 | // get current clef 177 | var currentClef = getCurAttrForMeasure(measureIndex, 'vfClef'); 178 | 179 | var vfStaveNote = new Vex.Flow.StaveNote({ 180 | keys: [step+vfAcc+'/'+oct], 181 | duration: staveNoteDuration+rest, 182 | clef: rest === '' ? currentClef : 'treble', 183 | auto_stem: true 184 | }); 185 | 186 | // console.log(vfStaveNote.getKeys().toString()+' '+staveNoteDuration); 187 | 188 | // set id for note DOM element in svg 189 | vfStaveNote.setId('m' + measureIndex + 'n' + noteIndex); 190 | 191 | // set accidental 192 | if(vfAcc !== '') 193 | vfStaveNote.addAccidental(0, new Vex.Flow.Accidental(vfAcc)); 194 | 195 | // // set dots with dots calculated from duration and divisions 196 | // var dotsArray = staveNoteDuration.match(/d/g); 197 | // // how many dots, format of vf duration: 'hdd' - half note with 2 dots 198 | // if(dotsArray) { 199 | // dots = dotsArray.length; 200 | // for(var i = 0; i < dots; i++) { 201 | // vfStaveNote.addDotToAll(); 202 | // } 203 | // } 204 | 205 | // currently support for only one dot 206 | // to support more dots, xml2json.js needs to be changed - 207 | // (or use this improved one: https://github.com/henrikingo/xml2json) 208 | // - currently it is eating up more dots: 209 | // e.g. from it makes only one {dot: null} 210 | if(note.hasOwnProperty('dot')) { 211 | vfStaveNote.addDotToAll(); 212 | // console.log('dot'); 213 | } 214 | 215 | return vfStaveNote; 216 | } 217 | } -------------------------------------------------------------------------------- /src/add.js: -------------------------------------------------------------------------------- 1 | /* 2 | module for note/measure addition... 3 | */ 4 | editor.add = { 5 | // inserts new measure filled with whole rest AFTER selected measure 6 | measure: function(){ 7 | // get and parse id of selected measure (id='m13') 8 | var measureIndex = +editor.selected.measure.id.split('m')[1]; 9 | 10 | // create new Vex.Flow.Stave, positions will be set in draw function 11 | var vfNewStave = new Vex.Flow.Stave(0, 0, editor.staveWidth); 12 | // add measure to global array of Vex.Flow Staves 13 | // splice adds before, but we need to insert after - reason for measureIndex + 1 14 | // splice also takes higher index than biggest as biggest 15 | gl_VfStaves.splice(measureIndex + 1, 0, vfNewStave); 16 | // add empty attributes for measure 17 | gl_StaveAttributes.splice(measureIndex + 1, 0, {}); 18 | // fill measure with whole rest 19 | var wholeRest = new Vex.Flow.StaveNote({ keys: ["b/4"], duration: "wr" }); 20 | wholeRest.setId('m' + measureIndex + 'n0'); 21 | gl_VfStaveNotes.splice(measureIndex + 1, 0, [wholeRest]); 22 | 23 | // re-number all following notes ids in measures in part 24 | for(var m = measureIndex + 1; m < gl_VfStaveNotes.length; m++) { 25 | for(var n = 0; n < gl_VfStaveNotes[m].length; n++) { 26 | gl_VfStaveNotes[m][n].setId('m' + m + 'n' + n); 27 | } 28 | } 29 | 30 | // add new measure to scoreJson 31 | var newMeasure = { 32 | '@number': measureIndex + 2, 33 | note: [ 34 | { 35 | '@measure' : 'yes', 36 | rest: null, 37 | duration: 16 // TODO get duration from divisions in current attributes 38 | } 39 | ] 40 | }; 41 | // insert new measure to json 42 | scoreJson["score-partwise"].part[0].measure.splice(measureIndex + 1, 0, newMeasure); 43 | 44 | // shift numbering for all following measures in part 45 | for(var m = measureIndex + 1; m < scoreJson["score-partwise"].part[0].measure.length; m++) { 46 | scoreJson["score-partwise"].part[0].measure[m]["@number"] = m + 1; 47 | } 48 | }, 49 | note: function(){ 50 | // get and parse id of selected note (id='m13n10') 51 | var measureIndex = getSelectedMeasureIndex(); 52 | var noteIndex = getSelectedNoteIndex(); 53 | var vfStaveNote = gl_VfStaveNotes[measureIndex][noteIndex]; 54 | 55 | var noteValue = getRadioValue('note-value'); 56 | // var noteValue = vfStaveNote.getDuration(); //w, h, q, 8, 16 57 | var dot = $('#dotted-checkbox').is(":checked") ? 'd' : ''; 58 | // var dot = vfStaveNote.isDotted() ? 'd' : ''; 59 | 60 | // create new Vex.Flow.StaveNote 61 | var newNote = new Vex.Flow.StaveNote({ 62 | keys: [ editor.selected.cursorNoteKey ], 63 | duration: noteValue + dot, 64 | auto_stem: true 65 | }); 66 | // set id for note DOM element in svg 67 | newNote.setId(editor.selected.note.id); 68 | 69 | if(dot === 'd') 70 | newNote.addDotToAll(); 71 | 72 | // put new note in place of selected rest 73 | gl_VfStaveNotes[measureIndex].splice(noteIndex, 1, newNote); 74 | 75 | // put new note into scoreJson also 76 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].rest; 77 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex]['@measure']; 78 | 79 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch = {}; 80 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch 81 | .step = editor.selected.cursorNoteKey[0].toUpperCase(); 82 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].pitch 83 | .octave = editor.selected.cursorNoteKey[editor.selected.cursorNoteKey.length - 1]; 84 | 85 | divisions = getCurAttrForMeasure(measureIndex, 'xmlDivisions'); 86 | var xmlDuration = editor.NoteTool.getDurationFromStaveNote(newNote, divisions); 87 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].duration = xmlDuration; 88 | 89 | editor.svgElem.removeEventListener('click', editor.add.note, false); 90 | editor.draw.selectedMeasure(false); 91 | 92 | // fluent creating of score: 93 | // add new measure, if current one is the last one and the note is also the last one 94 | if(measureIndex === gl_VfStaves.length - 1 && 95 | noteIndex === gl_VfStaveNotes[measureIndex].length - 1) { 96 | editor.add.measure(); 97 | // select first note in added measure 98 | measureIndex++; 99 | editor.selected.measure.id = 'm' + measureIndex; 100 | editor.selected.note.id = 'm' + measureIndex + 'n0'; 101 | editor.draw.score(); 102 | } 103 | 104 | }, 105 | clef: function(){ 106 | var clefDropdown = $('#clef-dropdown').val(); 107 | // console.log('add clef: '+clefDropdown); 108 | var measureIndex = getSelectedMeasureIndex(); 109 | var noteIndex = getSelectedNoteIndex(); 110 | var vfStave = gl_VfStaves[measureIndex]; 111 | 112 | var currentClef = getCurAttrForMeasure(measureIndex, 'vfClef'); 113 | 114 | // change clef only if new is different from current 115 | if(currentClef !== clefDropdown) { 116 | vfStave.setClef(clefDropdown); 117 | gl_StaveAttributes[measureIndex].vfClef = clefDropdown; 118 | var xmlClef = editor.table.CLEF_VEX_TYPE_DICT[clefDropdown]; 119 | gl_StaveAttributes[measureIndex].xmlClef = xmlClef; 120 | // put clef into measure attributes in json 121 | var xmlAttr = scoreJson["score-partwise"].part[0].measure[measureIndex].attributes || {}; 122 | xmlAttr.clef = {}; 123 | xmlAttr.clef.sign = xmlClef.split('/')[0]; 124 | xmlAttr.clef.line = xmlClef.split('/')[1]; 125 | scoreJson["score-partwise"].part[0].measure[measureIndex].attributes = xmlAttr; 126 | } 127 | 128 | // remove changed clef, if it is the same like previous 129 | if(measureIndex > 0) { 130 | var previousClef = getCurAttrForMeasure(measureIndex - 1, 'vfClef'); 131 | if(clefDropdown === previousClef) { 132 | vfStave.removeClef(); 133 | delete gl_StaveAttributes[measureIndex].vfClef; 134 | delete gl_StaveAttributes[measureIndex].xmlClef; 135 | if(scoreJson["score-partwise"].part[0].measure[measureIndex].attributes) 136 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].attributes.clef; 137 | } 138 | } 139 | }, 140 | keySignature: function(){ 141 | var keySig = $('#keySig-dropdown').val(); 142 | 143 | var measureIndex = getSelectedMeasureIndex(); 144 | var vfStave = gl_VfStaves[measureIndex]; 145 | 146 | var currentKeysig = getCurAttrForMeasure(measureIndex, 'vfKeySpec'); 147 | 148 | if(keySig !== currentKeysig) { 149 | vfStave.setKeySignature(keySig); 150 | 151 | gl_StaveAttributes[measureIndex].vfKeySpec = keySig; 152 | var fifths = 0; 153 | fifths = editor.table.SHARP_MAJOR_KEY_SIGNATURES.indexOf(keySig) + 1; 154 | if(!fifths) 155 | fifths = -(editor.table.FLAT_MAJOR_KEY_SIGNATURES.indexOf(keySig) + 1); 156 | gl_StaveAttributes[measureIndex].xmlFifths = fifths; 157 | 158 | var xmlAttr = scoreJson["score-partwise"].part[0].measure[measureIndex].attributes || {}; 159 | xmlAttr.key = {}; 160 | xmlAttr.key.fifths = fifths; 161 | // mode is not mandatory (e.g. major, minor, dorian...) 162 | 163 | scoreJson["score-partwise"].part[0].measure[measureIndex].attributes = xmlAttr; 164 | } 165 | 166 | if(measureIndex > 0) { 167 | var previousKeysig = getCurAttrForMeasure(measureIndex - 1, 'vfKeySpec'); 168 | if(keySig === previousKeysig) { 169 | vfStave.removeKeySignature(); 170 | delete gl_StaveAttributes[measureIndex].vfKeySpec; 171 | delete gl_StaveAttributes[measureIndex].xmlFifths; 172 | if(scoreJson["score-partwise"].part[0].measure[measureIndex].attributes) 173 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].attributes.key; 174 | } 175 | } 176 | }, 177 | timeSignature: function(){ 178 | var top = $('#timeSigTop').val(); 179 | var bottom = $('#timeSigBottom').val(); 180 | var timeSig = top + '/' + bottom; 181 | 182 | var currentTimesig = getCurAttrForMeasure(measureIndex, 'vfTimeSpec'); 183 | 184 | if(timeSig !== currentTimesig) { 185 | var measureIndex = getSelectedMeasureIndex(); 186 | var vfStave = gl_VfStaves[measureIndex]; 187 | 188 | vfStave.setTimeSignature(timeSig); 189 | gl_StaveAttributes[measureIndex].vfTimeSpec = timeSig; 190 | 191 | var xmlAttr = scoreJson["score-partwise"].part[0].measure[measureIndex].attributes || {}; 192 | xmlAttr.time = {}; 193 | xmlAttr.time.beats = top; 194 | xmlAttr.time['beat-type'] = bottom; 195 | 196 | scoreJson["score-partwise"].part[0].measure[measureIndex].attributes = xmlAttr; 197 | } 198 | 199 | if(measureIndex > 0) { 200 | var previousTimesig = getCurAttrForMeasure(measureIndex - 1, 'vfTimeSpec'); 201 | if(timeSig === previousTimesig) { 202 | vfStave.removeTimeSignature(); 203 | delete gl_StaveAttributes[measureIndex].vfTimeSpec; 204 | if(scoreJson["score-partwise"].part[0].measure[measureIndex].attributes) 205 | delete scoreJson["score-partwise"].part[0].measure[measureIndex].attributes.time; 206 | } 207 | } 208 | }, 209 | accidental: function(){ 210 | var vexAcc = getRadioValue('note-accidental'); 211 | 212 | var vfStaveNote = getSelectedNote(); 213 | 214 | if(!vfStaveNote.isRest()) { 215 | // TODO change to setAccidental() 216 | vfStaveNote.addAccidental(0, new Vex.Flow.Accidental(vexAcc)); 217 | // no support for chords currently 218 | 219 | // add accidental to json 220 | var xmlAcc = ''; 221 | for(var xmlname in editor.table.ACCIDENTAL_DICT) 222 | if(vexAcc === editor.table.ACCIDENTAL_DICT[xmlname]) 223 | xmlAcc = xmlname; 224 | scoreJson["score-partwise"].part[0].measure[measureIndex].note[noteIndex].accidental = xmlAcc; 225 | } 226 | } 227 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Web Editor of MusicXML Files 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 180 | 181 |
182 |
183 |
184 |
185 |
186 | 187 | 188 | 189 | 190 |
191 |
192 |
193 |
194 |
195 | 196 |
197 | github page 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 235 |
236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /src/draw.js: -------------------------------------------------------------------------------- 1 | editor.draw = { 2 | // clears svg and draws all measures (whole score) again 3 | score: function() { 4 | console.log('draw'); 5 | 6 | var canvasWidth = document.getElementById('svg-wrapper').clientWidth; 7 | var canvasHeight = document.getElementById('svg-wrapper').clientHeight; 8 | $('#svg-container').attr('width', canvasWidth); 9 | 10 | // to avoid NaNs in svg viewbox: 11 | // https://groups.google.com/forum/?fromgroups=#!topic/vexflow/RWtqOhQoXMI 12 | // editor.ctx.svg.setAttribute("preserveAspectRatio", "xMinYMin meet"); 13 | // editor.ctx.scale(1, 1); 14 | 15 | // or resize ctx here and also on lines 43 - 49 16 | // editor.ctx.resize(canvasWidth, canvasHeight); 17 | 18 | // or this: 19 | // editor.ctx.width = editor.ctx.svg.clientWidth; 20 | // editor.ctx.height = editor.ctx.svg.clientHeight; 21 | // editor.ctx.width = canvasWidth; 22 | // editor.ctx.height = canvasHeight; 23 | 24 | // or this: 25 | // editor.ctx.svg.style.width = "100%"; 26 | // editor.ctx.svg.style.height = "100%"; 27 | 28 | editor.ctx.clear(); 29 | // editor.ctx.svg.setAttribute("preserveAspectRatio", "xMinYMin meet"); 30 | 31 | // no cursor note will be displayed 32 | editor.selected.cursorNoteKey = null; 33 | 34 | // var minWidth = noteWidth * maxLength; 35 | // var minWidth = editor.noteWidth * 4; 36 | 37 | var attributes = {}; 38 | // var count = 0; 39 | var staveX = 10, staveY = 0; 40 | 41 | // loop over all measures 42 | for(var staveIndex = 0; staveIndex < gl_VfStaves.length; staveIndex++) { 43 | 44 | var stave = gl_VfStaves[staveIndex]; 45 | 46 | var staveWidth = stave.getWidth(); 47 | 48 | // add changes in attributes for current measure to attributes object 49 | attributes = mergeProperties(attributes, gl_StaveAttributes[staveIndex]); 50 | 51 | // calculate newline 52 | var staveEnd = staveX + staveWidth; 53 | if(staveEnd > canvasWidth) { 54 | staveX = 10; 55 | staveY = staveY + editor.staveHeight; 56 | newLine = true; 57 | // gl_StaveAttributes[staveIndex].isFirstOnLine = true; 58 | } 59 | else { 60 | newLine = false; 61 | // gl_StaveAttributes[staveIndex].isFirstOnLine = false; 62 | } 63 | 64 | // gradually extend height of canvas 65 | // if((staveY + editor.staveHeight) > $('#svg-container').attr('height')) 66 | // $('#svg-container').attr('height', staveY + editor.staveHeight); 67 | 68 | // if one measure is wider than canvas(e.g. in Chant.xml), extend canvas 69 | if(staveWidth > $('#svg-container').attr('width')) { 70 | $('#svg-container').attr('width', staveWidth); 71 | // editor.ctx.width = staveWidth; 72 | // editor.ctx.resize(staveWidth, canvasHeight); 73 | } 74 | 75 | // set height of canvas after last rendered measure 76 | if(staveIndex == gl_VfStaves.length - 1) { 77 | $('#svg-container').attr('height', staveY + editor.staveHeight); 78 | // editor.ctx.height = staveY + editor.staveHeight; 79 | // editor.ctx.resize(canvasWidth, staveY + editor.staveHeight); 80 | } 81 | 82 | // set position and width of stave 83 | stave.setX(staveX); 84 | stave.setY(staveY); 85 | // if measure doesn't have its own width(set as attribute in xml) 86 | // if(stave.getWidth() == editor.staveWidth) 87 | // stave.setWidth(staveWidth); 88 | // else 89 | // staveWidth = stave.getWidth(); 90 | 91 | // set rendering context for stave 92 | stave.setContext(editor.ctx); 93 | 94 | // clef and key signature must be rendered on every first measure on new line 95 | if(newLine === true || staveIndex === 0) { 96 | stave.setClef(attributes.vfClef); 97 | stave.setKeySignature(attributes.vfKeySpec); 98 | if(!newLine && attributes.vfTimeSpec) stave.setTimeSignature(attributes.vfTimeSpec); 99 | // number of accidentals in key signature 100 | var numOfAcc = editor.table.SHARP_MAJOR_KEY_SIGNATURES.indexOf(attributes.vfKeySpec) + 1; 101 | if(!numOfAcc) 102 | numOfAcc = editor.table.FLAT_MAJOR_KEY_SIGNATURES.indexOf(attributes.vfKeySpec) + 1; 103 | 104 | // TODO extend width of measure with clef | keysig | timesig 105 | // stave.setWidth(stave.getWidth() + 80 + numOfAcc * 20); 106 | // not good solution, it would grow after each draw call 107 | 108 | } 109 | // remove clef and key signature when not newline 110 | else { 111 | // vexflow extension 112 | stave.removeClef(); 113 | stave.removeKeySignature(); 114 | } 115 | 116 | 117 | editor.draw.measure(staveIndex, false); 118 | 119 | // set start x position for next measure 120 | staveX = staveX + staveWidth; 121 | 122 | } // loop over measures 123 | 124 | // highlight selected note 125 | if(editor.mode === 'note') 126 | $('svg #vf-'+editor.selected.note.id).colourNote("red"); 127 | 128 | }, 129 | 130 | 131 | 132 | 133 | // removes particular measure(stave) from svg and draws it again 134 | measure: function(drawnMeasureIndex, cursorNoteEnabled) { 135 | // $('#vf-mg'+drawnMeasureIndex).empty(); 136 | $('#vf-m'+drawnMeasureIndex).remove(); 137 | 138 | var stave = gl_VfStaves[drawnMeasureIndex]; 139 | 140 | // set stave properties 141 | var clef = gl_StaveAttributes[drawnMeasureIndex].vfClef; 142 | if(clef) stave.setClef(clef); 143 | var keySig = gl_StaveAttributes[drawnMeasureIndex].vfKeySpec; 144 | if(keySig) stave.setKeySignature(keySig); 145 | var timeSig = gl_StaveAttributes[drawnMeasureIndex].vfTimeSpec; 146 | if(timeSig) stave.setTimeSignature(timeSig); 147 | 148 | // svg stave(=measure) group 149 | editor.ctx.openGroup("stave", "m"+drawnMeasureIndex, {pointerBBox: true}); 150 | // draw stave 151 | stave.draw(); 152 | 153 | // create svg element exactly overlapping stave for stave selection and highlight 154 | editor.ctx.rect(stave.getX(), 155 | stave.y, 156 | stave.getWidth(), 157 | editor.staveHeight, 158 | { 159 | 'class': 'measureRect', 160 | 'id': 'm'+drawnMeasureIndex, 161 | 'fill': 'transparent' 162 | } 163 | ); 164 | 165 | // find time signature in Attributes for current Measure 166 | var beats = 4, beat_type = 4; 167 | for(var a = drawnMeasureIndex; a >= 0; a--) { 168 | // finds attributes of closest previous measure or current measure 169 | if(! $.isEmptyObject(gl_StaveAttributes[a]) && gl_StaveAttributes[a].vfTimeSpec) { 170 | var timeSplitted = gl_StaveAttributes[a].vfTimeSpec.split('/'); 171 | beats = timeSplitted[0]; 172 | beat_type = timeSplitted[1]; 173 | break; 174 | } 175 | } 176 | 177 | var voice = new Vex.Flow.Voice({ 178 | num_beats: beats, 179 | beat_value: beat_type, 180 | resolution: Vex.Flow.RESOLUTION 181 | }); 182 | 183 | voice.setStrict(false); //TODO: let it be strict for check notes duration in measure 184 | 185 | voice.addTickables(gl_VfStaveNotes[drawnMeasureIndex]); 186 | 187 | //https://github.com/0xfe/vexflow/wiki/Automatic-Beaming: 188 | var beams = new Vex.Flow.Beam.generateBeams(gl_VfStaveNotes[drawnMeasureIndex], { 189 | groups: [new Vex.Flow.Fraction(beats, beat_type)] 190 | }); 191 | 192 | var selMeasureIndex = getSelectedMeasureIndex(); 193 | var selNoteIndex = getSelectedNoteIndex(); 194 | var selVFStaveNote = gl_VfStaveNotes[selMeasureIndex][selNoteIndex]; 195 | 196 | // draw the cursor note, if drawing selected measure and cursor note is enabled 197 | if(editor.mode === 'note' && +selMeasureIndex === drawnMeasureIndex && cursorNoteEnabled) { 198 | var noteValue = getRadioValue('note-value'); 199 | var dot = $('#dotted-checkbox').is(":checked") ? 'd' : ''; 200 | // get note properties 201 | // var noteValue = selVFStaveNote.getDuration(); //w, h, q, 8, 16 202 | // var dot = selVFStaveNote.isDotted() ? 'd' : ''; 203 | 204 | // create cursor note 205 | var cursorNote = new Vex.Flow.StaveNote({ 206 | keys: [editor.selected.cursorNoteKey], 207 | duration: noteValue + dot, 208 | auto_stem: true 209 | }); 210 | // console.log(cursorNote); 211 | if(dot === 'd') cursorNote.addDotToAll(); 212 | 213 | cursorNote.setStave(stave); 214 | 215 | // create separate voice for cursor note 216 | var cursorNoteVoice = new Vex.Flow.Voice({ 217 | num_beats: beats, 218 | beat_value: beat_type, 219 | resolution: Vex.Flow.RESOLUTION 220 | }); 221 | cursorNoteVoice.setStrict(false); 222 | cursorNoteVoice.addTickables([cursorNote]); 223 | 224 | new Vex.Flow.Formatter() 225 | .joinVoices([voice, cursorNoteVoice]) 226 | .format([voice, cursorNoteVoice], stave.getWidth() * 0.8); 227 | 228 | // cursor note is only note in its voice, so it is on place of the very first note 229 | // we need to shift it to selected note x position 230 | var xShift = selVFStaveNote.getX(); 231 | // shift back by width of accidentals on left side of first note in measure 232 | xShift -= gl_VfStaveNotes[drawnMeasureIndex][0].getMetrics().modLeftPx; 233 | cursorNote.setXShift(xShift); 234 | 235 | cursorNoteVoice.draw(editor.ctx, stave); 236 | } 237 | // measure mode, no cursor note 238 | else { 239 | // format and justify the notes to 80% of staveWidth 240 | new Vex.Flow.Formatter() 241 | .joinVoices([voice]) 242 | .format([voice], stave.getWidth() * 0.8); 243 | // also exists method formatToStave()... 244 | // but it is rather helper function I guess, like FormatAndDraw() in Voice 245 | } 246 | 247 | // draw normal voice always 248 | voice.draw(editor.ctx, stave); 249 | 250 | beams.forEach(function(beam) { 251 | beam.setContext(editor.ctx).draw(); 252 | }); 253 | 254 | // mouse events listeners on for selecting measures 255 | if(editor.mode === 'measure') { 256 | $('svg .measureRect#m'+drawnMeasureIndex).each(function() { 257 | attachListenersToMeasureRect($(this)); 258 | }); 259 | // highlight selected measure 260 | if(drawnMeasureIndex === selMeasureIndex) 261 | $('svg .measureRect#m'+selMeasureIndex) 262 | .css({'fill': editor.measureColor, 'opacity': '0.4'}); 263 | } 264 | 265 | // if last note is behind width of stave, extend stave 266 | // var lastNoteX = gl_VfStaveNotes[m][gl_VfStaveNotes[m].length - 1].getNoteHeadEndX(); 267 | // if((lastNoteX - stave.getX()) > staveWidth) { 268 | // console.log('stave['+m+'] extended, lastNoteX: '+lastNoteX+'staveWidth: '+staveWidth); 269 | // stave.setWidth(lastNoteX + 10); 270 | // stave.draw(); 271 | // } 272 | // TODO rather create function calculateStaveWidth(stave()) 273 | 274 | // svg measure group 275 | editor.ctx.closeGroup(); 276 | 277 | // adding event listeners to note objects 278 | for(var n = 0; n < gl_VfStaveNotes[drawnMeasureIndex].length; n++){ 279 | // adding listeners for interactivity: (from vexflow stavenote_tests.js line 463) 280 | // item is svg group: 281 | var item = gl_VfStaveNotes[drawnMeasureIndex][n].getElem(); 282 | attachListenersToNote(item); 283 | // var noteBBox = gl_VfStaveNotes[drawnMeasureIndex][n].getBoundingBox(); 284 | // noteBBox.draw(editor.ctx); 285 | } 286 | 287 | }, 288 | 289 | selectedMeasure: function(cursorNoteEnabled) { 290 | var measureIndex = getSelectedMeasureIndex(); 291 | 292 | console.log('redraw measure['+measureIndex+']'); 293 | 294 | editor.draw.measure(measureIndex, cursorNoteEnabled); 295 | 296 | // highlight selected note 297 | if(editor.mode === 'note') 298 | $('svg #vf-'+editor.selected.note.id).colourNote("red"); 299 | } 300 | 301 | } -------------------------------------------------------------------------------- /examples/Chant.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Quem queritis 5 | 6 | Copyright © 2010 Recordare LLC 7 | 8 | Finale 2011 for Windows 9 | Dolet 6.0 for Finale 10 | 2011-08-08 11 | 12 | 13 | 14 | 15 | 16 | 17 | 6.4382 18 | 40 19 | 20 | 21 | 1736 22 | 1341 23 | 24 | 81 25 | 81 26 | 99 27 | 99 28 | 29 | 30 | 31 | 32 | 0 33 | 0 34 | 35 | 136 36 | 79 37 | 38 | 39 | 0.957 40 | 5 41 | 1.25 42 | 1.4583 43 | 5 44 | 1.875 45 | 1.4583 46 | 0.9375 47 | 1.4583 48 | 1.4583 49 | 50 50 | 50 51 | 60 52 | 8 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Voice 61 | 62 | Grand Piano 63 | 64 | 65 | 1 66 | 1 67 | 80 68 | 0 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 237 78 | 79 | system 80 | 81 | 82 | 8 83 | 84 | 0 85 | major 86 | 87 | 88 | G 89 | 2 90 | 91 | 92 | 93 | 94 | 95 | Angelus dicit: 96 | 97 | 98 | 99 | 100 | G 101 | 4 102 | 103 | 8 104 | 1 105 | quarter 106 | up 107 | 108 | 109 | 110 | 111 | single 112 | Quem 113 | 114 | 115 | 116 | 117 | 118 | F 119 | 4 120 | 121 | 8 122 | 1 123 | quarter 124 | up 125 | 126 | 127 | 128 | 129 | 130 | 131 | D 132 | 4 133 | 134 | 8 135 | 1 136 | quarter 137 | up 138 | 139 | 140 | 141 | 142 | begin 143 | que 144 | 145 | 146 | 147 | 148 | F 149 | 4 150 | 151 | 8 152 | 1 153 | quarter 154 | up 155 | 156 | 157 | 158 | E 159 | 4 160 | 161 | 8 162 | 1 163 | quarter 164 | up 165 | 166 | 167 | 168 | 169 | 170 | 171 | F 172 | 4 173 | 174 | 8 175 | 1 176 | quarter 177 | up 178 | 179 | 180 | 181 | 182 | middle 183 | ri 184 | 185 | 186 | 187 | 188 | G 189 | 4 190 | 191 | 8 192 | 1 193 | quarter 194 | up 195 | 196 | 197 | 198 | F 199 | 4 200 | 201 | 8 202 | 1 203 | quarter 204 | up 205 | 206 | 207 | 208 | 209 | 210 | 211 | G 212 | 4 213 | 214 | 8 215 | 1 216 | quarter 217 | up 218 | 219 | end 220 | tis 221 | 222 | 223 | 224 | 225 | F 226 | 4 227 | 228 | 8 229 | 1 230 | quarter 231 | up 232 | 233 | single 234 | in 235 | 236 | 237 | 238 | 239 | A 240 | 4 241 | 242 | 8 243 | 1 244 | quarter 245 | up 246 | 247 | begin 248 | se 249 | 250 | 251 | 252 | 253 | A 254 | 4 255 | 256 | 8 257 | 1 258 | quarter 259 | up 260 | 261 | 262 | 263 | 264 | middle 265 | pul 266 | 267 | 268 | 269 | 270 | C 271 | 5 272 | 273 | 8 274 | 1 275 | quarter 276 | down 277 | 278 | 279 | 280 | B 281 | 4 282 | 283 | 8 284 | 1 285 | quarter 286 | down 287 | 288 | 289 | 290 | A 291 | 4 292 | 293 | 8 294 | 1 295 | quarter 296 | up 297 | 298 | 299 | 300 | 301 | 302 | 303 | C 304 | 5 305 | 306 | 8 307 | 1 308 | quarter 309 | down 310 | 311 | 312 | 313 | 314 | end 315 | chro, 316 | 317 | 318 | 319 | 320 | 321 | G 322 | 4 323 | 324 | 8 325 | 1 326 | quarter 327 | up 328 | 329 | 330 | 331 | A 332 | 4 333 | 334 | 8 335 | 1 336 | quarter 337 | up 338 | 339 | 340 | 341 | G 342 | 4 343 | 344 | 8 345 | 1 346 | quarter 347 | up 348 | 349 | 350 | 351 | 352 | 353 | 354 | | 355 | 356 | -1 357 | 358 | 359 | 360 | G 361 | 4 362 | 363 | 8 364 | 1 365 | quarter 366 | up 367 | 368 | 369 | 370 | 371 | single 372 | o 373 | 374 | 375 | 376 | 377 | A 378 | 4 379 | 380 | 8 381 | 1 382 | quarter 383 | up 384 | 385 | 386 | 387 | 388 | 389 | 390 | G 391 | 4 392 | 393 | 8 394 | 1 395 | quarter 396 | up 397 | 398 | 399 | 400 | 401 | begin 402 | Chri 403 | 404 | 405 | 406 | 407 | F 408 | 4 409 | 410 | 8 411 | 1 412 | quarter 413 | up 414 | 415 | 416 | 417 | 418 | 419 | 420 | A 421 | 4 422 | 423 | 8 424 | 1 425 | quarter 426 | up 427 | 428 | 429 | 430 | 431 | middle 432 | sti 433 | 434 | 435 | 436 | 437 | C 438 | 5 439 | 440 | 8 441 | 1 442 | quarter 443 | down 444 | 445 | 446 | 447 | 448 | 449 | 450 | A 451 | 4 452 | 453 | 8 454 | 1 455 | quarter 456 | up 457 | 458 | middle 459 | co 460 | 461 | 462 | 463 | 464 | G 465 | 4 466 | 467 | 8 468 | 1 469 | quarter 470 | up 471 | 472 | end 473 | lae? 474 | 475 | 476 | 477 | light-light 478 | 479 | 480 | 481 | 482 | 483 | -------------------------------------------------------------------------------- /lib/MIDI.min.js: -------------------------------------------------------------------------------- 1 | if("undefined"==typeof MIDI&&(MIDI={}),function(e){"use strict";var n={},t=0,r=function(e){t++;var r=document.body,o=new Audio,i=e.split(";")[0];o.id="audio",o.setAttribute("preload","auto"),o.setAttribute("audiobuffer",!0),o.addEventListener("error",function(){r.removeChild(o),n[i]=!1,t--},!1),o.addEventListener("canplaythrough",function(){r.removeChild(o),n[i]=!0,t--},!1),o.src="data:"+e,r.appendChild(o)};e.audioDetect=function(e){if(navigator.requestMIDIAccess){var o=Function.prototype.toString.call(navigator.requestMIDIAccess).indexOf("[native code]");if(o)n.webmidi=!0;else for(var i=0;navigator.plugins.length>i;i++){var a=navigator.plugins[i];a.name.indexOf("Jazz-Plugin")>=0&&(n.webmidi=!0)}}if("undefined"==typeof Audio)return e({});n.audiotag=!0,(window.AudioContext||window.webkitAudioContext)&&(n.webaudio=!0);var u=new Audio;if(u.canPlayType===void 0)return e(n);var s=u.canPlayType('audio/ogg; codecs="vorbis"');s="probably"===s||"maybe"===s;var c=u.canPlayType("audio/mpeg");if(c="probably"===c||"maybe"===c,!s&&!c)return e(n),void 0;s&&r("audio/ogg;base64,T2dnUwACAAAAAAAAAADqnjMlAAAAAOyyzPIBHgF2b3JiaXMAAAAAAUAfAABAHwAAQB8AAEAfAACZAU9nZ1MAAAAAAAAAAAAA6p4zJQEAAAANJGeqCj3//////////5ADdm9yYmlzLQAAAFhpcGguT3JnIGxpYlZvcmJpcyBJIDIwMTAxMTAxIChTY2hhdWZlbnVnZ2V0KQAAAAABBXZvcmJpcw9CQ1YBAAABAAxSFCElGVNKYwiVUlIpBR1jUFtHHWPUOUYhZBBTiEkZpXtPKpVYSsgRUlgpRR1TTFNJlVKWKUUdYxRTSCFT1jFloXMUS4ZJCSVsTa50FkvomWOWMUYdY85aSp1j1jFFHWNSUkmhcxg6ZiVkFDpGxehifDA6laJCKL7H3lLpLYWKW4q91xpT6y2EGEtpwQhhc+211dxKasUYY4wxxsXiUyiC0JBVAAABAABABAFCQ1YBAAoAAMJQDEVRgNCQVQBABgCAABRFcRTHcRxHkiTLAkJDVgEAQAAAAgAAKI7hKJIjSZJkWZZlWZameZaouaov+64u667t6roOhIasBACAAAAYRqF1TCqDEEPKQ4QUY9AzoxBDDEzGHGNONKQMMogzxZAyiFssLqgQBKEhKwKAKAAAwBjEGGIMOeekZFIi55iUTkoDnaPUUcoolRRLjBmlEluJMYLOUeooZZRCjKXFjFKJscRUAABAgAMAQICFUGjIigAgCgCAMAYphZRCjCnmFHOIMeUcgwwxxiBkzinoGJNOSuWck85JiRhjzjEHlXNOSuekctBJyaQTAAAQ4AAAEGAhFBqyIgCIEwAwSJKmWZomipamiaJniqrqiaKqWp5nmp5pqqpnmqpqqqrrmqrqypbnmaZnmqrqmaaqiqbquqaquq6nqrZsuqoum65q267s+rZru77uqapsm6or66bqyrrqyrbuurbtS56nqqKquq5nqq6ruq5uq65r25pqyq6purJtuq4tu7Js664s67pmqq5suqotm64s667s2rYqy7ovuq5uq7Ks+6os+75s67ru2rrwi65r66os674qy74x27bwy7ouHJMnqqqnqq7rmarrqq5r26rr2rqmmq5suq4tm6or26os67Yry7aumaosm64r26bryrIqy77vyrJui67r66Ys67oqy8Lu6roxzLat+6Lr6roqy7qvyrKuu7ru+7JuC7umqrpuyrKvm7Ks+7auC8us27oxuq7vq7It/KosC7+u+8Iy6z5jdF1fV21ZGFbZ9n3d95Vj1nVhWW1b+V1bZ7y+bgy7bvzKrQvLstq2scy6rSyvrxvDLux8W/iVmqratum6um7Ksq/Lui60dd1XRtf1fdW2fV+VZd+3hV9pG8OwjK6r+6os68Jry8ov67qw7MIvLKttK7+r68ow27qw3L6wLL/uC8uq277v6rrStXVluX2fsSu38QsAABhwAAAIMKEMFBqyIgCIEwBAEHIOKQahYgpCCKGkEEIqFWNSMuakZM5JKaWUFEpJrWJMSuaclMwxKaGUlkopqYRSWiqlxBRKaS2l1mJKqcVQSmulpNZKSa2llGJMrcUYMSYlc05K5pyUklJrJZXWMucoZQ5K6iCklEoqraTUYuacpA46Kx2E1EoqMZWUYgupxFZKaq2kFGMrMdXUWo4hpRhLSrGVlFptMdXWWqs1YkxK5pyUzDkqJaXWSiqtZc5J6iC01DkoqaTUYiopxco5SR2ElDLIqJSUWiupxBJSia20FGMpqcXUYq4pxRZDSS2WlFosqcTWYoy1tVRTJ6XFklKMJZUYW6y5ttZqDKXEVkqLsaSUW2sx1xZjjqGkFksrsZWUWmy15dhayzW1VGNKrdYWY40x5ZRrrT2n1mJNMdXaWqy51ZZbzLXnTkprpZQWS0oxttZijTHmHEppraQUWykpxtZara3FXEMpsZXSWiypxNhirLXFVmNqrcYWW62ltVprrb3GVlsurdXcYqw9tZRrrLXmWFNtBQAADDgAAASYUAYKDVkJAEQBAADGMMYYhEYpx5yT0ijlnHNSKucghJBS5hyEEFLKnINQSkuZcxBKSSmUklJqrYVSUmqttQIAAAocAAACbNCUWByg0JCVAEAqAIDBcTRNFFXVdX1fsSxRVFXXlW3jVyxNFFVVdm1b+DVRVFXXtW3bFn5NFFVVdmXZtoWiqrqybduybgvDqKqua9uybeuorqvbuq3bui9UXVmWbVu3dR3XtnXd9nVd+Bmzbeu2buu+8CMMR9/4IeTj+3RCCAAAT3AAACqwYXWEk6KxwEJDVgIAGQAAgDFKGYUYM0gxphhjTDHGmAAAgAEHAIAAE8pAoSErAoAoAADAOeecc84555xzzjnnnHPOOeecc44xxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY0wAwE6EA8BOhIVQaMhKACAcAABACCEpKaWUUkoRU85BSSmllFKqFIOMSkoppZRSpBR1lFJKKaWUIqWgpJJSSimllElJKaWUUkoppYw6SimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaVUSimllFJKKaWUUkoppRQAYPLgAACVYOMMK0lnhaPBhYasBAByAwAAhRiDEEJpraRUUkolVc5BKCWUlEpKKZWUUqqYgxBKKqmlklJKKbXSQSihlFBKKSWUUkooJYQQSgmhlFRCK6mEUkoHoYQSQimhhFRKKSWUzkEoIYUOQkmllNRCSB10VFIpIZVSSiklpZQ6CKGUklJLLZVSWkqpdBJSKamV1FJqqbWSUgmhpFZKSSWl0lpJJbUSSkklpZRSSymFVFJJJYSSUioltZZaSqm11lJIqZWUUkqppdRSSiWlkEpKqZSSUmollZRSaiGVlEpJKaTUSimlpFRCSamlUlpKLbWUSkmptFRSSaWUlEpJKaVSSksppRJKSqmllFpJKYWSUkoplZJSSyW1VEoKJaWUUkmptJRSSymVklIBAEAHDgAAAUZUWoidZlx5BI4oZJiAAgAAQABAgAkgMEBQMApBgDACAQAAAADAAAAfAABHARAR0ZzBAUKCwgJDg8MDAAAAAAAAAAAAAACAT2dnUwAEAAAAAAAAAADqnjMlAgAAADzQPmcBAQA="),c&&r("audio/mpeg;base64,/+MYxAAAAANIAUAAAASEEB/jwOFM/0MM/90b/+RhST//w4NFwOjf///PZu////9lns5GFDv//l9GlUIEEIAAAgIg8Ir/JGq3/+MYxDsLIj5QMYcoAP0dv9HIjUcH//yYSg+CIbkGP//8w0bLVjUP///3Z0x5QCAv/yLjwtGKTEFNRTMuOTeqqqqqqqqqqqqq/+MYxEkNmdJkUYc4AKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq");var l=(new Date).getTime(),d=window.setInterval(function(){var r=(new Date).getTime(),o=r-l>5e3;(!t||o)&&(window.clearInterval(d),e(n))},1)}}(MIDI),function(e){"use strict";e.GM=function(e){var n=function(e){return e.replace(/[^a-z0-9 ]/gi,"").replace(/[ ]/g,"_").toLowerCase()},t={byName:{},byId:{},byCategory:{}};for(var r in e)for(var o=e[r],i=0,a=o.length;a>i;i++){var u=o[i];if(u){var s=parseInt(u.substr(0,u.indexOf(" ")),10);u=u.replace(s+" ",""),t.byId[--s]=t.byName[n(u)]=t.byCategory[n(r)]={id:n(u),instrument:u,number:s,category:r}}}return t}({Piano:["1 Acoustic Grand Piano","2 Bright Acoustic Piano","3 Electric Grand Piano","4 Honky-tonk Piano","5 Electric Piano 1","6 Electric Piano 2","7 Harpsichord","8 Clavinet"],"Chromatic Percussion":["9 Celesta","10 Glockenspiel","11 Music Box","12 Vibraphone","13 Marimba","14 Xylophone","15 Tubular Bells","16 Dulcimer"],Organ:["17 Drawbar Organ","18 Percussive Organ","19 Rock Organ","20 Church Organ","21 Reed Organ","22 Accordion","23 Harmonica","24 Tango Accordion"],Guitar:["25 Acoustic Guitar (nylon)","26 Acoustic Guitar (steel)","27 Electric Guitar (jazz)","28 Electric Guitar (clean)","29 Electric Guitar (muted)","30 Overdriven Guitar","31 Distortion Guitar","32 Guitar Harmonics"],Bass:["33 Acoustic Bass","34 Electric Bass (finger)","35 Electric Bass (pick)","36 Fretless Bass","37 Slap Bass 1","38 Slap Bass 2","39 Synth Bass 1","40 Synth Bass 2"],Strings:["41 Violin","42 Viola","43 Cello","44 Contrabass","45 Tremolo Strings","46 Pizzicato Strings","47 Orchestral Harp","48 Timpani"],Ensemble:["49 String Ensemble 1","50 String Ensemble 2","51 Synth Strings 1","52 Synth Strings 2","53 Choir Aahs","54 Voice Oohs","55 Synth Choir","56 Orchestra Hit"],Brass:["57 Trumpet","58 Trombone","59 Tuba","60 Muted Trumpet","61 French Horn","62 Brass Section","63 Synth Brass 1","64 Synth Brass 2"],Reed:["65 Soprano Sax","66 Alto Sax","67 Tenor Sax","68 Baritone Sax","69 Oboe","70 English Horn","71 Bassoon","72 Clarinet"],Pipe:["73 Piccolo","74 Flute","75 Recorder","76 Pan Flute","77 Blown Bottle","78 Shakuhachi","79 Whistle","80 Ocarina"],"Synth Lead":["81 Lead 1 (square)","82 Lead 2 (sawtooth)","83 Lead 3 (calliope)","84 Lead 4 (chiff)","85 Lead 5 (charang)","86 Lead 6 (voice)","87 Lead 7 (fifths)","88 Lead 8 (bass + lead)"],"Synth Pad":["89 Pad 1 (new age)","90 Pad 2 (warm)","91 Pad 3 (polysynth)","92 Pad 4 (choir)","93 Pad 5 (bowed)","94 Pad 6 (metallic)","95 Pad 7 (halo)","96 Pad 8 (sweep)"],"Synth Effects":["97 FX 1 (rain)","98 FX 2 (soundtrack)","99 FX 3 (crystal)","100 FX 4 (atmosphere)","101 FX 5 (brightness)","102 FX 6 (goblins)","103 FX 7 (echoes)","104 FX 8 (sci-fi)"],Ethnic:["105 Sitar","106 Banjo","107 Shamisen","108 Koto","109 Kalimba","110 Bagpipe","111 Fiddle","112 Shanai"],Percussive:["113 Tinkle Bell","114 Agogo","115 Steel Drums","116 Woodblock","117 Taiko Drum","118 Melodic Tom","119 Synth Drum"],"Sound effects":["120 Reverse Cymbal","121 Guitar Fret Noise","122 Breath Noise","123 Seashore","124 Bird Tweet","125 Telephone Ring","126 Helicopter","127 Applause","128 Gunshot"]}),e.getInstrument=function(n){var t=e.channels[n];return t&&t.instrument},e.setInstrument=function(n,t,r){var o=e.channels[n];return r?setTimeout(function(){o.instrument=t},r):(o.instrument=t,void 0)},e.getMono=function(n){var t=e.channels[n];return t&&t.mono},e.setMono=function(n,t,r){var o=e.channels[n];return r?setTimeout(function(){o.mono=t},r):(o.mono=t,void 0)},e.getOmni=function(n){var t=e.channels[n];return t&&t.omni},e.setOmni=function(n,t){var r=e.channels[n];return delay?setTimeout(function(){r.omni=t},delay):(r.omni=t,void 0)},e.getSolo=function(n){var t=e.channels[n];return t&&t.solo},e.setSolo=function(n,t){var r=e.channels[n];return delay?setTimeout(function(){r.solo=t},delay):(r.solo=t,void 0)},e.channels=function(){for(var e={},n=0;16>n;n++)e[n]={instrument:n,pitchBend:0,mute:!1,mono:!1,omni:!1,solo:!1};return e}(),e.keyToNote={},e.noteToKey={},function(){for(var n=21,t=108,r=["C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"],o=n;t>=o;o++){var i=(o-12)/12>>0,a=r[o%12]+i;e.keyToNote[a]=o,e.noteToKey[o]=a}}()}(MIDI),"undefined"==typeof MIDI&&(MIDI={}),MIDI.Soundfont=MIDI.Soundfont||{},MIDI.Player=MIDI.Player||{},function(e){"use strict";e.DEBUG=!0,e.USE_XHR=!0,e.soundfontUrl="./soundfont/",e.loadPlugin=function(t){"function"==typeof t&&(t={onsuccess:t}),e.soundfontUrl=t.soundfontUrl||e.soundfontUrl,e.audioDetect(function(r){var o=window.location.hash,i="";if(r[t.api]?i=t.api:r[o.substr(1)]?i=o.substr(1):r.webmidi?i="webmidi":window.AudioContext?i="webaudio":window.Audio&&(i="audiotag"),n[i]){if(t.targetFormat)var a=t.targetFormat;else var a=r["audio/ogg"]?"ogg":"mp3";e.__api=i,e.__audioFormat=a,e.supports=r,e.loadResource(t)}})},e.loadResource=function(t){var r=t.instruments||t.instrument||"acoustic_grand_piano";"object"!=typeof r&&(r=r||0===r?[r]:[]);for(var o=0;r.length>o;o++){var i=r[o];i===+i&&e.GM.byId[i]&&(r[o]=e.GM.byId[i].id)}t.format=e.__audioFormat,t.instruments=r,n[e.__api](t)};var n={webmidi:function(n){e.WebMIDI.connect(n)},audiotag:function(e){t(e,"AudioTag")},webaudio:function(e){t(e,"WebAudio")}},t=function(n,t){for(var o=n.format,i=n.instruments,a=n.onprogress,u=n.onerror,s=i.length,c=s,l=function(){--c||(a&&a("load",1),e[t].connect(n))},d=0;s>d;d++){var f=i[d];MIDI.Soundfont[f]?l():r(i[d],o,function(e,n){var t=n/s,r=(s-c)/s;a&&a("load",t+r,f)},function(){l()},u)}},r=function(n,t,r,o,i){var a=e.soundfontUrl+n+"-"+t+".js";e.USE_XHR?e.util.request({url:a,format:"text",onerror:i,onprogress:r,onsuccess:function(e,n){var t=document.createElement("script");t.language="javascript",t.type="text/javascript",t.text=n,document.body.appendChild(t),o()}}):dom.loadScript.add({url:a,verify:'MIDI.Soundfont["'+n+'"]',onerror:i,onsuccess:function(){o()}})};e.setDefaultPlugin=function(n){for(var t in n)e[t]=n[t]}}(MIDI),"undefined"==typeof MIDI&&(MIDI={}),MIDI.Player===void 0&&(MIDI.Player={}),function(){"use strict";var e=MIDI.Player;e.currentTime=0,e.endTime=0,e.restart=0,e.playing=!1,e.timeWarp=1,e.startDelay=0,e.BPM=120,e.start=e.resume=function(n){-1>e.currentTime&&(e.currentTime=-1),d(e.currentTime,null,n)},e.pause=function(){var n=e.restart;f(),e.restart=n},e.stop=function(){f(),e.restart=0,e.currentTime=0},e.addListener=function(e){a=e},e.removeListener=function(){a=void 0},e.clearAnimation=function(){e.animationFrameId&&cancelAnimationFrame(e.animationFrameId)},e.setAnimation=function(n){var t=0,r=0,o=0;e.clearAnimation();var a=function(){if(e.animationFrameId=requestAnimationFrame(a),0!==e.endTime){e.playing?(t=o===e.currentTime?r-Date.now():0,t=0===e.currentTime?0:e.currentTime-t,o!==e.currentTime&&(r=Date.now(),o=e.currentTime)):t=e.currentTime;var u=e.endTime,s=t/1e3,c=s/60,l=s-60*c,d=60*c+l,f=u/1e3;-1>f-d||n({now:d,end:f,events:i})}};requestAnimationFrame(a)},e.loadMidiFile=function(n,t,r){try{e.replayer=new Replayer(MidiFile(e.currentData),e.timeWarp,null,e.BPM),e.data=e.replayer.getData(),e.endTime=c(),MIDI.loadPlugin({onsuccess:n,onprogress:t,onerror:r})}catch(o){r&&r(o)}},e.loadFile=function(n,t,r,o){if(e.stop(),-1!==n.indexOf("base64,")){var i=window.atob(n.split(",")[1]);e.currentData=i,e.loadMidiFile(t,r,o)}else{var a=new XMLHttpRequest;a.open("GET",n),a.overrideMimeType("text/plain; charset=x-user-defined"),a.onreadystatechange=function(){if(4===this.readyState)if(200===this.status){for(var n=this.responseText||"",i=[],a=n.length,u=String.fromCharCode,s=0;a>s;s++)i[s]=u(255&n.charCodeAt(s));var c=i.join("");e.currentData=c,e.loadMidiFile(t,r,o)}else o&&o("Unable to load MIDI file")},a.send()}},e.getFileInstruments=function(){for(var n={},t={},r=0;e.data.length>r;r++){var o=e.data[r][0].event;if("channel"===o.type){var i=o.channel;switch(o.subtype){case"controller":break;case"programChange":t[i]=o.programNumber;break;case"noteOn":var a=t[i],u=MIDI.GM.byId[isFinite(a)?a:i];n[u.id]=!0}}}var s=[];for(var c in n)s.push(c);return s};var n,t,r=[],o=0,i={},a=void 0,u=function(t,o,u,s,c,l){return setTimeout(function(){var s={channel:t,note:o,now:u,end:e.endTime,message:c,velocity:l};128===c?delete i[o]:i[o]=s,a&&a(s),e.currentTime=u,r.shift(),1e3>r.length?d(n,!0):e.currentTime===n&&e.endTime>n&&d(n,!0)},u-s)},s=function(){return"webaudio"===MIDI.api?MIDI.WebAudio.getContext():(e.ctx={currentTime:0},e.ctx)},c=function(){for(var n=e.data,t=n.length,r=.5,o=0;t>o;o++)r+=n[o][1];return r},l=function(){return window.performance&&window.performance.now?window.performance.now():Date.now()},d=function(i,a,d){if(e.replayer){a||(i===void 0&&(i=e.restart),e.playing&&f(),e.playing=!0,e.data=e.replayer.getData(),e.endTime=c());var p,A=0,m=0,v=e.data,q=s(),g=v.length;n=.5,r[0]&&r[0].interval||0;var h=i-e.currentTime;if("webaudio"!==MIDI.api){var y=l();t=t||y,q.currentTime=(y-t)/1e3}o=q.currentTime;for(var S=0;g>S&&100>m;S++){var I=v[S];if(i>=(n+=I[1]))A=n;else{i=n-A;var b=I[0].event;if("channel"===b.type){var T=b.channel,w=MIDI.channels[T],M=q.currentTime+(i+h+e.startDelay)/1e3,D=n-A+e.startDelay;switch(b.subtype){case"controller":MIDI.setController(T,b.controllerType,b.value,M);break;case"programChange":MIDI.programChange(T,b.programNumber,M);break;case"pitchBend":MIDI.pitchBend(T,b.value,M);break;case"noteOn":if(w.mute)break;p=b.noteNumber-(e.MIDIOffset||0),r.push({event:b,time:D,source:MIDI.noteOn(T,b.noteNumber,b.velocity,M),interval:u(T,p,n+e.startDelay,A-h,144,b.velocity)}),m++;break;case"noteOff":if(w.mute)break;p=b.noteNumber-(e.MIDIOffset||0),r.push({event:b,time:D,source:MIDI.noteOff(T,b.noteNumber,M),interval:u(T,p,n,A-h,128,0)});break;default:}}}}d&&d(r)}},f=function(){var n=s();for(e.playing=!1,e.restart+=1e3*(n.currentTime-o);r.length;){var t=r.pop();window.clearInterval(t.interval),t.source&&("number"==typeof t.source?window.clearTimeout(t.source):t.source.disconnect(0))}for(var u in i){var t=i[u];144===i[u].message&&a&&a({channel:t.channel,note:t.note,now:t.now,end:t.end,message:128,velocity:t.velocity})}i={}}}(),function(e){"use strict";window.Audio&&function(){for(var n=e.AudioTag={api:"audiotag"},t={},r=127,o=-1,i=[],a=[],u={},s=0;12>s;s++)i[s]=new Audio;var c=function(n,t){if(e.channels[n]){var s=e.channels[n].instrument,c=e.GM.byId[s].id,t=u[t];if(t){var l=c+""+t.id,d=(o+1)%i.length,f=i[d];if(a[d]=l,!e.Soundfont[c])return e.DEBUG&&console.log("404",c),void 0;f.src=e.Soundfont[c][t.id],f.volume=r/127,f.play(),o=d}}},l=function(n,t){if(e.channels[n]){var r=e.channels[n].instrument,s=e.GM.byId[r].id,t=u[t];if(t)for(var c=s+""+t.id,l=0,d=i.length;d>l;l++){var f=(l+o+1)%d,p=a[f];if(p&&p==c)return i[f].pause(),a[f]=null,void 0}}};n.audioBuffers=i,n.send=function(){},n.setController=function(){},n.setVolume=function(e,n){r=n},n.programChange=function(n,t){e.channels[n].instrument=t},n.pitchBend=function(){},n.noteOn=function(e,n,r,o){var i=t[n];if(u[i])return o?setTimeout(function(){c(e,i)},1e3*o):(c(e,i),void 0)},n.noteOff=function(){},n.chordOn=function(e,n,r,o){for(var i=0;n.length>i;i++){var a=n[i],s=t[a];if(u[s]){if(o)return setTimeout(function(){c(e,s)},1e3*o);c(e,s)}}},n.chordOff=function(e,n,r){for(var o=0;n.length>o;o++){var i=n[o],a=t[i];if(u[a]){if(r)return setTimeout(function(){l(e,a)},1e3*r);l(e,a)}}},n.stopAllNotes=function(){for(var e=0,n=i.length;n>e;e++)i[e].pause()},n.connect=function(r){e.setDefaultPlugin(n);for(var o in e.keyToNote)t[e.keyToNote[o]]=o,u[o]={id:o};r.onsuccess&&r.onsuccess()}}()}(MIDI),function(e){"use strict";window.AudioContext&&function(){function n(e,n,t){if(o){var i=new Audio;i.src=e,i.controls=!1,i.autoplay=!1,i.preload=!1,i.addEventListener("canplay",function(){n&&n(i)}),i.addEventListener("error",function(e){t&&t(e)}),document.body.appendChild(i)}else if(0===e.indexOf("data:audio")){var a=e.split(",")[1],u=Base64Binary.decodeArrayBuffer(a);r.decodeAudioData(u,n,t)}else{var s=new XMLHttpRequest;s.open("GET",e,!0),s.responseType="arraybuffer",s.onload=function(){r.decodeAudioData(s.response,n,t)},s.send()}}function t(){return new(window.AudioContext||window.webkitAudioContext)}var r,o=!1,i=e.WebAudio={api:"webaudio"},a={},u={},s=127,c={};i.audioBuffers=c,i.send=function(){},i.setController=function(){},i.setVolume=function(e,n,t){t?setTimeout(function(){s=n},1e3*t):s=n},i.programChange=function(n,t){var r=e.channels[n];r.instrument=t},i.pitchBend=function(n,t){var r=e.channels[n];r.pitchBend=t},i.noteOn=function(n,t,i,l){l=l||0;var d=e.channels[n],f=d.instrument,p=f+""+t,A=c[p];if(A){if(r.currentTime>l&&(l+=r.currentTime),o)var m=r.createMediaElementSource(A);else{var m=r.createBufferSource();m.buffer=A}if(u){var v=m;for(var q in u)v.connect(u[q].input),v=u[q]}var g=2*i/127*(s/127)-1;if(m.connect(r.destination),m.playbackRate.value=1,m.gainNode=r.createGain(),m.gainNode.connect(r.destination),m.gainNode.gain.value=Math.min(1,Math.max(-1,g)),m.connect(m.gainNode),o){if(l)return setTimeout(function(){A.currentTime=0,A.play()},1e3*l);A.currentTime=0,A.play()}else m.start(l||0);return a[n+""+t]=m,m}},i.noteOff=function(n,t,i){i=i||0;var u=e.channels[n],s=u.instrument,l=s+""+t,d=c[l];if(d){r.currentTime>i&&(i+=r.currentTime);var f=a[n+""+t];if(f){if(f.gainNode){var p=f.gainNode.gain;p.linearRampToValueAtTime(p.value,i),p.linearRampToValueAtTime(-1,i+.3)}return o?i?setTimeout(function(){d.pause()},1e3*i):d.pause():f.noteOff?f.noteOff(i+.5):f.stop(i+.5),delete a[n+""+t],f}}},i.chordOn=function(e,n,t,r){for(var o,a={},u=0,s=n.length;s>u;u++)a[o=n[u]]=i.noteOn(e,o,t,r);return a},i.chordOff=function(e,n,t){for(var r,o={},a=0,u=n.length;u>a;a++)o[r=n[a]]=i.noteOff(e,r,t);return o},i.stopAllNotes=function(){for(var e in a){var n=0;r.currentTime>n&&(n+=r.currentTime);var t=a[e];t.gain.linearRampToValueAtTime(1,n),t.gain.linearRampToValueAtTime(0,n+.3),t.noteOff?t.noteOff(n+.3):t.stop(n+.3),delete a[e]}},i.setEffects=function(e){if(!r.tunajs)return console.log("Effects module not installed.");for(var n=0;e.length>n;n++){var t=e[n],o=new r.tunajs[t.type](t);o.connect(r.destination),u[t.type]=o}},i.connect=function(n){e.setDefaultPlugin(i),i.setContext(r||t(),n.onsuccess)},i.getContext=function(){return r},i.setContext=function(t,o){r=t,"undefined"==typeof Tuna||r.tunajs||(r.tunajs=new Tuna(r));var i=[],a=e.keyToNote;for(var u in a)i.push(u);var s=function(){for(var e in d)if(d[e])return;o&&(o(),o=null)},l=function(t,r,o,i){var a=t[i];a&&(d[r]++,n(a,function(n){n.id=i;var o=e.keyToNote[i];c[r+""+o]=n,0===--d[r]&&(t.isLoaded=!0,s(f))},function(){}))},d={};for(var f in e.Soundfont){var p=e.Soundfont[f];if(!p.isLoaded){var A=e.GM.byName[f],m=A.number;d[m]=0;for(var v=0;i.length>v;v++){var u=i[v];l(p,m,v,u)}}}setTimeout(s,1)}}()}(MIDI),function(e){"use strict";var n=null,t=null,r=e.WebMIDI={api:"webmidi"};r.send=function(e,n){t.send(e,1e3*n)},r.setController=function(e,n,r,o){t.send([e,n,r],1e3*o)},r.setVolume=function(e,n,r){t.send([176+e,7,n],1e3*r)},r.programChange=function(e,n,r){t.send([192+e,n],1e3*r)},r.pitchBend=function(e,n,r){t.send([224+e,n],1e3*r)},r.noteOn=function(e,n,r,o){t.send([144+e,n,r],1e3*o)},r.noteOff=function(e,n,r){t.send([128+e,n,0],1e3*r)},r.chordOn=function(e,n,r,o){for(var i=0;n.length>i;i++){var a=n[i];t.send([144+e,a,r],1e3*o)}},r.chordOff=function(e,n,r){for(var o=0;n.length>o;o++){var i=n[o];t.send([128+e,i,0],1e3*r)}},r.stopAllNotes=function(){t.cancel();for(var e=0;16>e;e++)t.send([176+e,123,0])},r.connect=function(o){e.setDefaultPlugin(r);var i=function(){if(window.AudioContext)o.api="webaudio";else{if(!window.Audio)return;o.api="audiotag"}e.loadPlugin(o)};navigator.requestMIDIAccess().then(function(e){n=e;var r=n.outputs;t="function"==typeof r?r()[0]:r[0],void 0===t?i():o.onsuccess&&o.onsuccess()},i)}}(MIDI),"undefined"==typeof MIDI&&(MIDI={}),function(e){var n=e.util||(e.util={});if(n.request=function(n,r,o,i){"use strict";"string"==typeof n&&(n={url:n});var a=n.data,u=n.url,s=n.method||(n.data?"POST":"GET"),c=n.format,l=n.headers,d=n.responseType,f=n.withCredentials||!1,r=r||n.onsuccess,o=o||n.onerror,i=i||n.onprogress;if(t!==void 0&&e.loc.isLocalUrl(u))return t.readFile(u,"utf8",function(e,n){e?o&&o(e):r&&r({responseText:n})}),void 0;var p=new XMLHttpRequest;if(p.open(s,u,!0),l)for(var A in l)p.setRequestHeader(A,l[A]);else a&&p.setRequestHeader("Content-type","application/x-www-form-urlencoded");return"binary"===c&&p.overrideMimeType&&p.overrideMimeType("text/plain; charset=x-user-defined"),d&&(p.responseType=d),f&&(p.withCredentials="true"),o&&"onerror"in p&&(p.onerror=o),i&&p.upload&&"onprogress"in p.upload&&(a?p.upload.onprogress=function(e){i.call(p,e,event.loaded/event.total)}:p.addEventListener("progress",function(e){var n=0;if(e.lengthComputable)n=e.total;else if(p.totalBytes)n=p.totalBytes;else{var t=parseInt(p.getResponseHeader("Content-Length-Raw"));if(!isFinite(t))return;p.totalBytes=n=t}i.call(p,e,e.loaded/n)})),p.onreadystatechange=function(n){if(4===p.readyState)if(200===p.status||304===p.status||308===p.status||0===p.status&&e.client.cordova){if(r){var t;if("xml"===c)t=n.target.responseXML;else if("text"===c)t=n.target.responseText;else if("json"===c)try{t=JSON.parse(n.target.response)}catch(i){o&&o.call(p,n)}r.call(p,n,t)}}else o&&o.call(p,n)},p.send(a),p},"undefined"!=typeof module&&module.exports){var t=require("fs");XMLHttpRequest=require("xmlhttprequest").XMLHttpRequest,module.exports=e.util.request}}(MIDI),dom===void 0)var dom={};(function(){"use strict";dom.loadScript=function(){return this.loaded={},this.loading={},this},dom.loadScript.prototype.add=function(n){var t=this;"string"==typeof n&&(n={url:n});var r=n.urls;r===void 0&&(r=[{url:n.url,verify:n.verify}]);var o=document.getElementsByTagName("head")[0],i=function(n,r){t.loaded[n.url]||r&&e(r)===!1||(t.loaded[n.url]=!0,t.loading[n.url]&&t.loading[n.url](),delete t.loading[n.url],n.onsuccess&&n.onsuccess(),f!==void 0&&f())},a=!1,u=[],s=function(e){if("string"==typeof e&&(e={url:e,verify:n.verify}),/([\w\d.\[\]\'\"])$/.test(e.verify)){var r=e.test=e.verify;if("object"==typeof r)for(var s=0;r.length>s;s++)u.push(r[s]);else u.push(r)}if(!t.loaded[e.url]){var l=document.createElement("script");l.onreadystatechange=function(){("loaded"===this.readyState||"complete"===this.readyState)&&i(e)},l.onload=function(){i(e)},l.onerror=function(){if(a=!0,delete t.loading[e.url],"object"==typeof e.test)for(var n in e.test)c(e.test[n]);else c(e.test)},l.setAttribute("type","text/javascript"),l.setAttribute("src",e.url),o.appendChild(l),t.loading[e.url]=function(){}}},c=function(e){for(var n=[],t=0;u.length>t;t++)u[t]!==e&&n.push(u[t]);u=n},l=function(t){if(t)i(t,t.test);else for(var o=0;r.length>o;o++)i(r[o],r[o].test);for(var s=!0,o=0;u.length>o;o++)e(u[o])===!1&&(s=!1);!n.strictOrder&&s?a?n.error&&n.error():n.onsuccess&&n.onsuccess():setTimeout(function(){l(t)},10)};if(n.strictOrder){var d=-1,f=function(){if(d++,r[d]){var e=r[d],o=e.url;t.loading[o]?t.loading[o]=function(){e.onsuccess&&e.onsuccess(),f()}:t.loaded[o]?f():(s(e),l(e))}else a?n.error&&n.error():n.onsuccess&&n.onsuccess()};f()}else for(var d=0;r.length>d;d++)s(r[d]),l(r[d])},dom.loadScript=new dom.loadScript;var e=function(e,n){try{e=e.split('"').join("").split("'").join("").split("]").join("").split("[").join(".");for(var t=e.split("."),r=t.length,o=n||window,i=0;r>i;i++){var a=t[i];if(null==o[a])return!1;o=o[a]}return!0}catch(u){return!1}}})(),"undefined"!=typeof module&&module.exports&&(module.exports=dom.loadScript); --------------------------------------------------------------------------------