├── 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 |
11 | - Missing check if notes fit the bar. Only one note can be added into measure.
12 | - Minimal subset of MusicXML format supported.
13 | - Editor works with only first musical part of music score.
14 |
15 |
16 | ## To Do
17 |
18 | - Duration check for notes in measure - in progress
19 | - Playback - basics done, improvements needed
20 | - Bug fixes - in progress
21 | - Chords support - waiting
22 | - Keyboard interactivity - waiting
23 | - Multiple score parts - waiting
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 |
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:"") + "" + name + ">";
35 | }
36 | }
37 | else {
38 | xml += ind + "<" + name + ">" + v.toString() + "" + name + ">";
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 |
45 |
--------------------------------------------------------------------------------
/icons/note_4.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
52 |
--------------------------------------------------------------------------------
/icons/flat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
--------------------------------------------------------------------------------
/icons/natural.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
33 |
--------------------------------------------------------------------------------
/icons/note_2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
59 |
--------------------------------------------------------------------------------
/icons/double-flat.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
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 |
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";
108 | for (var c=n.firstChild; c; c=c.nextSibling)
109 | s += asXml(c);
110 | s += ""+n.nodeName+">";
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 |
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);
--------------------------------------------------------------------------------