abcjs 6.0.0-beta.7 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.github/workflows/tests.yml +29 -0
  2. package/CODE_OF_CONDUCT.md +76 -0
  3. package/CONTRIBUTING.md +1 -0
  4. package/LICENSE.md +1 -1
  5. package/README.md +92 -3
  6. package/RELEASE.md +957 -1
  7. package/abcjs-audio.css +14 -5
  8. package/dist/.gitignore +1 -2
  9. package/dist/abcjs-basic-min.js +3 -0
  10. package/dist/abcjs-basic-min.js.LICENSE +23 -0
  11. package/dist/abcjs-basic.js +28231 -0
  12. package/dist/abcjs-basic.js.map +1 -0
  13. package/dist/abcjs-plugin-min.js +3 -0
  14. package/dist/abcjs-plugin-min.js.LICENSE +23 -0
  15. package/dist/report-basic.html +37 -0
  16. package/dist/report-before-glyph-compress.html +37 -0
  17. package/dist/report-brown-ts-target-es5.html +37 -0
  18. package/dist/report-dev-orig-no-babel.html +37 -0
  19. package/dist/report-synth.html +37 -0
  20. package/docker-build.sh +1 -0
  21. package/glyphs.json +1 -0
  22. package/index.js +27 -2
  23. package/{static-wrappers/license.js → license.js} +1 -1
  24. package/package.json +26 -29
  25. package/{src/plugin/abc_plugin.js → plugin.js} +31 -19
  26. package/src/api/abc_animation.js +1 -17
  27. package/src/api/abc_tablatures.js +144 -0
  28. package/src/api/abc_timing_callbacks.js +234 -116
  29. package/src/api/abc_tunebook.js +18 -67
  30. package/src/api/abc_tunebook_svg.js +38 -46
  31. package/src/data/abc_tune.js +232 -972
  32. package/src/data/deline-tune.js +199 -0
  33. package/src/edit/abc_editarea.js +112 -0
  34. package/src/edit/abc_editor.js +95 -221
  35. package/src/midi/abc_midi_create.js +48 -50
  36. package/src/parse/abc_common.js +0 -14
  37. package/src/parse/abc_parse.js +167 -1321
  38. package/src/parse/abc_parse_book.js +62 -0
  39. package/src/parse/abc_parse_directive.js +164 -41
  40. package/src/parse/abc_parse_header.js +116 -145
  41. package/src/parse/abc_parse_key_voice.js +26 -20
  42. package/src/parse/abc_parse_music.js +1337 -0
  43. package/src/parse/abc_tokenizer.js +21 -15
  44. package/src/parse/abc_transpose.js +3 -15
  45. package/src/parse/tune-builder.js +896 -0
  46. package/src/parse/wrap_lines.js +205 -453
  47. package/src/synth/abc_midi_flattener.js +1292 -0
  48. package/src/{midi → synth}/abc_midi_renderer.js +44 -17
  49. package/src/synth/abc_midi_sequencer.js +648 -0
  50. package/src/synth/active-audio-context.js +3 -14
  51. package/src/synth/cents-to-factor.js +10 -0
  52. package/src/synth/create-note-map.js +21 -32
  53. package/src/synth/create-synth-control.js +20 -103
  54. package/src/synth/create-synth.js +185 -77
  55. package/src/synth/download-buffer.js +7 -21
  56. package/src/synth/get-midi-file.js +13 -20
  57. package/src/synth/images/{loading.svg → loading.svg.js} +4 -0
  58. package/src/synth/images/loop.svg.js +65 -0
  59. package/src/synth/images/pause.svg.js +10 -0
  60. package/src/synth/images/play.svg.js +9 -0
  61. package/src/synth/images/{reset.svg → reset.svg.js} +5 -1
  62. package/src/synth/instrument-index-to-name.js +1 -16
  63. package/src/synth/load-note.js +37 -76
  64. package/src/synth/pitch-to-note-name.js +0 -15
  65. package/src/synth/pitches-to-perc.js +64 -0
  66. package/src/synth/place-note.js +78 -68
  67. package/src/synth/play-event.js +17 -18
  68. package/src/synth/register-audio-context.js +11 -23
  69. package/src/synth/sounds-cache.js +0 -15
  70. package/src/synth/supports-audio.js +9 -23
  71. package/src/synth/synth-controller.js +80 -49
  72. package/src/synth/synth-sequence.js +20 -34
  73. package/src/tablatures/instruments/guitar/guitar-fonts.js +19 -0
  74. package/src/tablatures/instruments/guitar/guitar-patterns.js +23 -0
  75. package/src/tablatures/instruments/guitar/tab-guitar.js +50 -0
  76. package/src/tablatures/instruments/string-patterns.js +277 -0
  77. package/src/tablatures/instruments/string-tablature.js +56 -0
  78. package/src/tablatures/instruments/tab-note.js +282 -0
  79. package/src/tablatures/instruments/tab-notes.js +41 -0
  80. package/src/tablatures/instruments/violin/tab-violin.js +47 -0
  81. package/src/tablatures/instruments/violin/violin-fonts.js +19 -0
  82. package/src/tablatures/instruments/violin/violin-patterns.js +23 -0
  83. package/src/tablatures/tab-absolute-elements.js +310 -0
  84. package/src/tablatures/tab-common.js +29 -0
  85. package/src/tablatures/tab-renderer.js +243 -0
  86. package/src/tablatures/transposer.js +110 -0
  87. package/src/test/abc_midi_lint.js +5 -22
  88. package/src/test/abc_midi_sequencer_lint.js +11 -14
  89. package/src/test/abc_parser_lint.js +136 -32
  90. package/src/test/abc_vertical_lint.js +94 -32
  91. package/src/test/rendering-lint.js +38 -5
  92. package/src/write/abc_absolute_element.js +112 -120
  93. package/src/write/abc_abstract_engraver.js +102 -253
  94. package/src/write/abc_beam_element.js +30 -290
  95. package/src/write/abc_brace_element.js +12 -121
  96. package/src/write/abc_create_clef.js +21 -32
  97. package/src/write/abc_create_key_signature.js +8 -26
  98. package/src/write/abc_create_note_head.js +107 -0
  99. package/src/write/abc_create_time_signature.js +2 -21
  100. package/src/write/abc_crescendo_element.js +3 -50
  101. package/src/write/abc_decoration.js +7 -30
  102. package/src/write/abc_dynamic_decoration.js +3 -37
  103. package/src/write/abc_ending_element.js +1 -57
  104. package/src/write/abc_engraver_controller.js +111 -234
  105. package/src/write/abc_glyphs.js +9 -19
  106. package/src/write/abc_relative_element.js +57 -97
  107. package/src/write/abc_renderer.js +10 -832
  108. package/src/write/abc_spacing.js +0 -15
  109. package/src/write/abc_staff_group_element.js +14 -349
  110. package/src/write/abc_tempo_element.js +9 -117
  111. package/src/write/abc_tie_element.js +5 -68
  112. package/src/write/abc_triplet_element.js +6 -124
  113. package/src/write/abc_voice_element.js +7 -222
  114. package/src/write/add-chord.js +103 -0
  115. package/src/write/add-text-if.js +33 -0
  116. package/src/write/bottom-text.js +79 -0
  117. package/src/write/calcHeight.js +17 -0
  118. package/src/write/classes.js +100 -0
  119. package/src/write/draw/absolute.js +68 -0
  120. package/src/write/draw/beam.js +56 -0
  121. package/src/write/draw/brace.js +106 -0
  122. package/src/write/draw/crescendo.js +38 -0
  123. package/src/write/draw/debug-box.js +8 -0
  124. package/src/write/draw/draw.js +56 -0
  125. package/src/write/draw/dynamics.js +20 -0
  126. package/src/write/draw/ending.js +46 -0
  127. package/src/write/draw/group-elements.js +66 -0
  128. package/src/write/draw/horizontal-line.js +25 -0
  129. package/src/write/draw/non-music.js +50 -0
  130. package/src/write/draw/print-line.js +24 -0
  131. package/src/write/draw/print-path.js +7 -0
  132. package/src/write/draw/print-stem.js +30 -0
  133. package/src/write/draw/print-symbol.js +59 -0
  134. package/src/write/draw/print-vertical-line.js +18 -0
  135. package/src/write/draw/relative.js +77 -0
  136. package/src/write/draw/round-number.js +5 -0
  137. package/src/write/draw/selectables.js +59 -0
  138. package/src/write/draw/separator.js +16 -0
  139. package/src/write/draw/set-paper-size.js +45 -0
  140. package/src/write/{sprintf.js → draw/sprintf.js} +0 -0
  141. package/src/write/draw/staff-group.js +226 -0
  142. package/src/write/draw/staff-line.js +9 -0
  143. package/src/write/draw/staff.js +33 -0
  144. package/src/write/draw/tab-line.js +40 -0
  145. package/src/write/draw/tempo.js +45 -0
  146. package/src/write/draw/text.js +71 -0
  147. package/src/write/draw/tie.js +97 -0
  148. package/src/write/draw/triplet.js +46 -0
  149. package/src/write/draw/voice.js +102 -0
  150. package/src/write/format-jazz-chord.js +15 -0
  151. package/src/write/free-text.js +41 -0
  152. package/src/write/get-font-and-attr.js +37 -0
  153. package/src/write/get-text-size.js +56 -0
  154. package/src/write/highlight.js +11 -0
  155. package/src/write/layout/VoiceElements.js +121 -0
  156. package/src/write/layout/beam.js +213 -0
  157. package/src/write/layout/get-left-edge-of-staff.js +56 -0
  158. package/src/write/layout/getBarYAt.js +6 -0
  159. package/src/write/layout/layout.js +94 -0
  160. package/src/write/layout/setUpperAndLowerElements.js +232 -0
  161. package/src/write/layout/staffGroup.js +146 -0
  162. package/src/write/layout/triplet.js +75 -0
  163. package/src/write/layout/voice.js +137 -0
  164. package/src/write/selection.js +188 -70
  165. package/src/write/separator.js +10 -0
  166. package/src/write/set-class.js +21 -0
  167. package/src/write/subtitle.js +12 -0
  168. package/src/write/svg.js +95 -43
  169. package/src/write/top-text.js +54 -0
  170. package/src/write/unhighlight.js +11 -0
  171. package/temp.txt +17 -0
  172. package/test.js +27 -64
  173. package/types/index.d.ts +1095 -0
  174. package/version.js +1 -1
  175. package/.babelrc +0 -5
  176. package/.eslintrc +0 -3
  177. package/.gitmodules +0 -3
  178. package/abcjs-midi.css +0 -166
  179. package/build-utils/loadPresets.js +0 -14
  180. package/build-utils/presets/webpack.analyze.js +0 -6
  181. package/build-utils/presets/webpack.optimize.js +0 -30
  182. package/build-utils/webpack.development.js +0 -14
  183. package/build-utils/webpack.production.js +0 -35
  184. package/deploy-docs.sh +0 -25
  185. package/docs/README.md +0 -33
  186. package/fix-versions.sh +0 -23
  187. package/mei.js +0 -43
  188. package/midi.js +0 -62
  189. package/src/api/abc_tunebook_midi.js +0 -116
  190. package/src/midi/abc_midi_controls.js +0 -701
  191. package/src/midi/abc_midi_flattener.js +0 -1119
  192. package/src/midi/abc_midi_js_preparer.js +0 -243
  193. package/src/midi/abc_midi_sequencer.js +0 -401
  194. package/src/midi/abc_midi_ui_generator.js +0 -86
  195. package/src/plugin/abc_plugin_midi.js +0 -220
  196. package/src/synth/images/loop.svg +0 -61
  197. package/src/synth/images/pause.svg +0 -6
  198. package/src/synth/images/play.svg +0 -5
  199. package/src/transform/abc2abc_write.js +0 -395
  200. package/static-wrappers/basic.js +0 -2
  201. package/static-wrappers/midi.js +0 -2
  202. package/static-wrappers/plugin-midi.js +0 -6
  203. package/static-wrappers/plugin.js +0 -6
  204. package/webpack.config.js +0 -29
@@ -0,0 +1,1337 @@
1
+ var parseCommon = require('./abc_common');
2
+ var parseKeyVoice = require('./abc_parse_key_voice');
3
+ var transpose = require('./abc_transpose');
4
+
5
+ var tokenizer;
6
+ var warn;
7
+ var multilineVars;
8
+ var tune;
9
+ var tuneBuilder;
10
+ var header;
11
+
12
+ var MusicParser = function(_tokenizer, _warn, _multilineVars, _tune, _tuneBuilder, _header) {
13
+ tokenizer = _tokenizer;
14
+ warn = _warn;
15
+ multilineVars = _multilineVars;
16
+ tune = _tune;
17
+ tuneBuilder = _tuneBuilder;
18
+ header = _header;
19
+ this.lineContinuation = false;
20
+ }
21
+
22
+ //
23
+ // Parse line of music
24
+ //
25
+ // This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
26
+ // core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
27
+ // chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
28
+ // grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
29
+ // note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
30
+ // bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
31
+ // header is <open-bracket:required, K|M|L|V:required, colon:required, field:required, close-bracket:required> spaces can occur between the colon, in the field, and before the close bracket
32
+ // header can also be the only thing on a line. This is true even if it is a continuation line. In this case the brackets are not required.
33
+ // a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
34
+
35
+ // Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
36
+ // Then, all leading and trailing spaces are ignored.
37
+ // If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
38
+ // of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
39
+
40
+ // TODO-PER: How to handle ! for line break?
41
+ // TODO-PER: dots before bar, dots before slur
42
+ // TODO-PER: U: redefinable symbols.
43
+
44
+ // Ambiguous symbols:
45
+ // "[" can be the start of a chord, the start of a header element or part of a bar line.
46
+ // --- if it is immediately followed by "|", it is a bar line
47
+ // --- if it is immediately followed by K: L: M: V: it is a header (note: there are other headers mentioned in the standard, but I'm not sure how they would be used.)
48
+ // --- otherwise it is the beginning of a chord
49
+ // "(" can be the start of a slur or a triplet
50
+ // --- if it is followed by a number from 2-9, then it is a triplet
51
+ // --- otherwise it is a slur
52
+ // "]"
53
+ // --- if there is a chord open, then this is the close
54
+ // --- if it is after a [|, then it is an invisible bar line
55
+ // --- otherwise, it is par of a bar
56
+ // "." can be a bar modifier or a slur modifier, or a decoration
57
+ // --- if it comes immediately before a bar, it is a bar modifier
58
+ // --- if it comes immediately before a slur, it is a slur modifier
59
+ // --- otherwise it is a decoration for the next note.
60
+ // number:
61
+ // --- if it is after a bar, with no space, it is an ending marker
62
+ // --- if it is after a ( with no space, it is a triplet count
63
+ // --- if it is after a pitch or octave or slash, then it is a duration
64
+
65
+ // Unambiguous symbols (except inside quoted strings):
66
+ // vertical-bar, colon: part of a bar
67
+ // ABCDEFGabcdefg: pitch
68
+ // xyzZ: rest
69
+ // comma, prime: octave
70
+ // close-paren: end-slur
71
+ // hyphen: tie
72
+ // tilde, v, u, bang, plus, THLMPSO: decoration
73
+ // carat, underscore, equal: accidental
74
+ // ampersand: time reset
75
+ // open-curly, close-curly: grace notes
76
+ // double-quote: chord symbol
77
+ // less-than, greater-than, slash: duration
78
+ // back-tick, space, tab: space
79
+ var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
80
+
81
+ var isInTie = function(multilineVars, overlayLevel, el) {
82
+ if (multilineVars.inTie[overlayLevel] === undefined)
83
+ return false;
84
+ // If this is single voice music then the voice index isn't set, so we use the first voice.
85
+ var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
86
+ if (multilineVars.inTie[overlayLevel][voiceIndex]) {
87
+ if (el.pitches !== undefined || el.rest.type !== 'spacer')
88
+ return true;
89
+ }
90
+ return false;
91
+ };
92
+
93
+ var el = { };
94
+ MusicParser.prototype.parseMusic = function(line) {
95
+ header.resolveTempo();
96
+ //multilineVars.havent_set_length = false; // To late to set this now.
97
+ multilineVars.is_in_header = false; // We should have gotten a key header by now, but just in case, this is definitely out of the header.
98
+ var i = 0;
99
+ var startOfLine = multilineVars.iChar;
100
+ // see if there is nothing but a comment on this line. If so, just ignore it. A full line comment is optional white space followed by %
101
+ while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
102
+ i++;
103
+ if (i === line.length || line.charAt(i) === '%')
104
+ return;
105
+
106
+ // Start with the standard staff, clef and key symbols on each line
107
+ var delayStartNewLine = multilineVars.start_new_line;
108
+ if (multilineVars.continueall === undefined)
109
+ multilineVars.start_new_line = true;
110
+ else
111
+ multilineVars.start_new_line = false;
112
+ var tripletNotesLeft = 0;
113
+
114
+ // See if the line starts with a header field
115
+ var retHeader = header.letter_to_body_header(line, i);
116
+ if (retHeader[0] > 0) {
117
+ i += retHeader[0];
118
+ // fixes bug on this: c[V:2]d
119
+ if (retHeader[1] === 'V')
120
+ this.startNewLine();
121
+ // delayStartNewLine = true;
122
+ // TODO-PER: Handle inline headers
123
+ }
124
+
125
+ var overlayLevel = 0;
126
+ while (i < line.length)
127
+ {
128
+ var startI = i;
129
+ if (line.charAt(i) === '%')
130
+ break;
131
+
132
+ var retInlineHeader = header.letter_to_inline_header(line, i, delayStartNewLine);
133
+ if (retInlineHeader[0] > 0) {
134
+ i += retInlineHeader[0];
135
+ if (retInlineHeader[1] === 'V')
136
+ delayStartNewLine = true; // fixes bug on this: c[V:2]d
137
+ // TODO-PER: Handle inline headers
138
+ //multilineVars.start_new_line = false;
139
+ } else {
140
+ // Wait until here to actually start the line because we know we're past the inline statements.
141
+ if (!tuneBuilder.hasBeginMusic() || (delayStartNewLine && !this.lineContinuation)) {
142
+ this.startNewLine();
143
+ delayStartNewLine = false;
144
+ }
145
+
146
+ // We need to decide if the following characters are a bar-marking or a note-group.
147
+ // Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
148
+ // If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
149
+ // If there is a bar marker, it is definitely a bar-marking.
150
+ // If there is either a core-note or chord, it is definitely a note-group.
151
+ // So, loop while we find grace-notes, chords-symbols, or decorations. [It is an error to have more than one grace-note group in a row; the others can be multiple]
152
+ // Then, if there is a grace-note, we know where to go.
153
+ // Else see if we have a chord, core-note, slur, triplet, or bar.
154
+
155
+ var ret;
156
+ while (1) {
157
+ ret = tokenizer.eatWhiteSpace(line, i);
158
+ if (ret > 0) {
159
+ i += ret;
160
+ }
161
+ if (i > 0 && line.charAt(i-1) === '\x12') {
162
+ // there is one case where a line continuation isn't the same as being on the same line, and that is if the next character after it is a header.
163
+ ret = header.letter_to_body_header(line, i);
164
+ if (ret[0] > 0) {
165
+ if (ret[1] === 'V')
166
+ this.startNewLine(); // fixes bug on this: c\\nV:2]\\nd
167
+ // TODO: insert header here
168
+ i = ret[0];
169
+ multilineVars.start_new_line = false;
170
+ }
171
+ }
172
+ // gather all the grace notes, chord symbols and decorations
173
+ ret = letter_to_spacer(line, i);
174
+ if (ret[0] > 0) {
175
+ i += ret[0];
176
+ }
177
+
178
+ ret = letter_to_chord(line, i);
179
+ if (ret[0] > 0) {
180
+ // There could be more than one chord here if they have different positions.
181
+ // If two chords have the same position, then connect them with newline.
182
+ if (!el.chord)
183
+ el.chord = [];
184
+ var chordName = tokenizer.translateString(ret[1]);
185
+ chordName = chordName.replace(/;/g, "\n");
186
+ var addedChord = false;
187
+ for (var ci = 0; ci < el.chord.length; ci++) {
188
+ if (el.chord[ci].position === ret[2]) {
189
+ addedChord = true;
190
+ el.chord[ci].name += "\n" + chordName;
191
+ }
192
+ }
193
+ if (addedChord === false) {
194
+ if (ret[2] === null && ret[3])
195
+ el.chord.push({name: chordName, rel_position: ret[3]});
196
+ else
197
+ el.chord.push({name: chordName, position: ret[2]});
198
+ }
199
+
200
+ i += ret[0];
201
+ var ii = tokenizer.skipWhiteSpace(line.substring(i));
202
+ if (ii > 0)
203
+ el.force_end_beam_last = true;
204
+ i += ii;
205
+ } else {
206
+ if (nonDecorations.indexOf(line.charAt(i)) === -1)
207
+ ret = letter_to_accent(line, i);
208
+ else ret = [ 0 ];
209
+ if (ret[0] > 0) {
210
+ if (ret[1] === null) {
211
+ if (i + 1 < line.length)
212
+ this.startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
213
+ } else if (ret[1].length > 0) {
214
+ if (ret[1].indexOf("style=") === 0) {
215
+ el.style = ret[1].substr(6);
216
+ } else {
217
+ if (el.decoration === undefined)
218
+ el.decoration = [];
219
+ if (ret[1] === 'beambr1')
220
+ el.beambr = 1;
221
+ else if (ret[1] === "beambr2")
222
+ el.beambr = 2;
223
+ else el.decoration.push(ret[1]);
224
+ }
225
+ }
226
+ i += ret[0];
227
+ } else {
228
+ ret = letter_to_grace(line, i);
229
+ // TODO-PER: Be sure there aren't already grace notes defined. That is an error.
230
+ if (ret[0] > 0) {
231
+ el.gracenotes = ret[1];
232
+ i += ret[0];
233
+ } else
234
+ break;
235
+ }
236
+ }
237
+ }
238
+
239
+ ret = letter_to_bar(line, i);
240
+ if (ret[0] > 0) {
241
+ // This is definitely a bar
242
+ overlayLevel = 0;
243
+ if (el.gracenotes !== undefined) {
244
+ // Attach the grace note to an invisible note
245
+ el.rest = { type: 'spacer' };
246
+ el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
247
+ multilineVars.addFormattingOptions(el, tune.formatting, 'note');
248
+ tuneBuilder.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
249
+ multilineVars.measureNotEmpty = true;
250
+ el = {};
251
+ }
252
+ var bar = {type: ret[1]};
253
+ if (bar.type.length === 0)
254
+ warn("Unknown bar type", line, i);
255
+ else {
256
+ if (multilineVars.inEnding && bar.type !== 'bar_thin') {
257
+ bar.endEnding = true;
258
+ multilineVars.inEnding = false;
259
+ }
260
+ if (ret[2]) {
261
+ bar.startEnding = ret[2];
262
+ if (multilineVars.inEnding)
263
+ bar.endEnding = true;
264
+ multilineVars.inEnding = true;
265
+ if (ret[1] === "bar_right_repeat") {
266
+ // restore the tie and slur state from the start repeat
267
+ multilineVars.restoreStartEndingHoldOvers();
268
+ } else {
269
+ // save inTie, inTieChord
270
+ multilineVars.duplicateStartEndingHoldOvers();
271
+ }
272
+ }
273
+ if (el.decoration !== undefined)
274
+ bar.decoration = el.decoration;
275
+ if (el.chord !== undefined)
276
+ bar.chord = el.chord;
277
+ if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
278
+ multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
279
+ else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
280
+ multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
281
+ else if (bar.endEnding)
282
+ multilineVars.barFirstEndingNum = undefined;
283
+ if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
284
+ var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
285
+ if (isFirstVoice) {
286
+ multilineVars.currBarNumber++;
287
+ if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
288
+ bar.barNumber = multilineVars.currBarNumber;
289
+ }
290
+ }
291
+ multilineVars.addFormattingOptions(el, tune.formatting, 'bar');
292
+ tuneBuilder.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
293
+ multilineVars.measureNotEmpty = false;
294
+ el = {};
295
+ }
296
+ i += ret[0];
297
+ } else if (line[i] === '&') { // backtrack to beginning of measure
298
+ ret = letter_to_overlay(line, i);
299
+ if (ret[0] > 0) {
300
+ tuneBuilder.appendElement('overlay', startOfLine, startOfLine+1, {});
301
+ i += 1;
302
+ overlayLevel++;
303
+ }
304
+
305
+ } else {
306
+ // This is definitely a note group
307
+ //
308
+ // Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
309
+ ret = letter_to_open_slurs_and_triplets(line, i);
310
+ if (ret.consumed > 0) {
311
+ if (ret.startSlur !== undefined)
312
+ el.startSlur = ret.startSlur;
313
+ if (ret.dottedSlur)
314
+ el.dottedSlur = true;
315
+ if (ret.triplet !== undefined) {
316
+ if (tripletNotesLeft > 0)
317
+ warn("Can't nest triplets", line, i);
318
+ else {
319
+ el.startTriplet = ret.triplet;
320
+ el.tripletMultiplier = ret.tripletQ / ret.triplet;
321
+ el.tripletR = ret.num_notes;
322
+ tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
323
+ }
324
+ }
325
+ i += ret.consumed;
326
+ }
327
+
328
+ // handle chords.
329
+ if (line.charAt(i) === '[') {
330
+ var chordStartChar = i;
331
+ i++;
332
+ var chordDuration = null;
333
+ var rememberEndBeam = false;
334
+
335
+ var done = false;
336
+ while (!done) {
337
+ var accent = letter_to_accent(line, i);
338
+ if (accent[0] > 0) {
339
+ i += accent[0];
340
+ }
341
+
342
+ var chordNote = getCoreNote(line, i, {}, false);
343
+ if (chordNote !== null && chordNote.pitch !== undefined) {
344
+ if (accent[0] > 0) { // If we found a decoration above, it modifies the entire chord. "style" is handled below.
345
+ if (accent[1].indexOf("style=") !== 0) {
346
+ if (el.decoration === undefined)
347
+ el.decoration = [];
348
+ el.decoration.push(accent[1]);
349
+ }
350
+ }
351
+ if (chordNote.end_beam) {
352
+ el.end_beam = true;
353
+ delete chordNote.end_beam;
354
+ }
355
+ if (el.pitches === undefined) {
356
+ el.duration = chordNote.duration;
357
+ el.pitches = [ chordNote ];
358
+ } else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
359
+ el.pitches.push(chordNote);
360
+ delete chordNote.duration;
361
+ if (accent[0] > 0) { // If we found a style above, it modifies the individual pitch, not the entire chord.
362
+ if (accent[1].indexOf("style=") === 0) {
363
+ el.pitches[el.pitches.length-1].style = accent[1].substr(6);
364
+ }
365
+ }
366
+
367
+ if (multilineVars.inTieChord[el.pitches.length]) {
368
+ chordNote.endTie = true;
369
+ multilineVars.inTieChord[el.pitches.length] = undefined;
370
+ }
371
+ if (chordNote.startTie)
372
+ multilineVars.inTieChord[el.pitches.length] = true;
373
+
374
+ i = chordNote.endChar;
375
+ delete chordNote.endChar;
376
+ } else if (line.charAt(i) === ' ') {
377
+ // Spaces are not allowed in chords, but we can recover from it by ignoring it.
378
+ warn("Spaces are not allowed in chords", line, i);
379
+ i++;
380
+ } else {
381
+ if (i < line.length && line.charAt(i) === ']') {
382
+ // consume the close bracket
383
+ i++;
384
+
385
+ if (multilineVars.next_note_duration !== 0) {
386
+ el.duration = el.duration * multilineVars.next_note_duration;
387
+ multilineVars.next_note_duration = 0;
388
+ }
389
+
390
+ if (isInTie(multilineVars, overlayLevel, el)) {
391
+ parseCommon.each(el.pitches, function(pitch) { pitch.endTie = true; });
392
+ setIsInTie(multilineVars, overlayLevel, false);
393
+ }
394
+
395
+ if (tripletNotesLeft > 0 && !(el.rest && el.rest.type === "spacer")) {
396
+ tripletNotesLeft--;
397
+ if (tripletNotesLeft === 0) {
398
+ el.endTriplet = true;
399
+ }
400
+ }
401
+
402
+ var postChordDone = false;
403
+ while (i < line.length && !postChordDone) {
404
+ switch (line.charAt(i)) {
405
+ case ' ':
406
+ case '\t':
407
+ addEndBeam(el);
408
+ break;
409
+ case ')':
410
+ if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
411
+ break;
412
+ case '-':
413
+ parseCommon.each(el.pitches, function(pitch) { pitch.startTie = {}; });
414
+ setIsInTie(multilineVars, overlayLevel, true);
415
+ break;
416
+ case '>':
417
+ case '<':
418
+ var br2 = getBrokenRhythm(line, i);
419
+ i += br2[0] - 1; // index gets incremented below, so we'll let that happen
420
+ multilineVars.next_note_duration = br2[2];
421
+ if (chordDuration)
422
+ chordDuration = chordDuration * br2[1];
423
+ else
424
+ chordDuration = br2[1];
425
+ break;
426
+ case '1':
427
+ case '2':
428
+ case '3':
429
+ case '4':
430
+ case '5':
431
+ case '6':
432
+ case '7':
433
+ case '8':
434
+ case '9':
435
+ case '/':
436
+ var fraction = tokenizer.getFraction(line, i);
437
+ chordDuration = fraction.value;
438
+ i = fraction.index;
439
+ if (line.charAt(i) === ' ')
440
+ rememberEndBeam = true;
441
+ if (line.charAt(i) === '-' || line.charAt(i) === ')' || line.charAt(i) === ' ' || line.charAt(i) === '<' || line.charAt(i) === '>')
442
+ i--; // Subtracting one because one is automatically added below
443
+ else
444
+ postChordDone = true;
445
+ break;
446
+ default:
447
+ postChordDone = true;
448
+ break;
449
+ }
450
+ if (!postChordDone) {
451
+ i++;
452
+ }
453
+ }
454
+ } else
455
+ warn("Expected ']' to end the chords", line, i);
456
+
457
+ if (el.pitches !== undefined) {
458
+ if (chordDuration !== null) {
459
+ el.duration = el.duration * chordDuration;
460
+ if (rememberEndBeam)
461
+ addEndBeam(el);
462
+ }
463
+
464
+ multilineVars.addFormattingOptions(el, tune.formatting, 'note');
465
+ tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
466
+ multilineVars.measureNotEmpty = true;
467
+ el = {};
468
+ }
469
+ done = true;
470
+ }
471
+ }
472
+
473
+ } else {
474
+ // Single pitch
475
+ var el2 = {};
476
+ var core = getCoreNote(line, i, el2, true);
477
+ if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true);
478
+ if (core !== null) {
479
+ if (core.pitch !== undefined) {
480
+ el.pitches = [ { } ];
481
+ // TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
482
+ if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
483
+ el.pitches[0].pitch = core.pitch;
484
+ el.pitches[0].name = core.name;
485
+ if (core.midipitch || core.midipitch === 0)
486
+ el.pitches[0].midipitch = core.midipitch;
487
+ if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
488
+ if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
489
+ if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
490
+ if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
491
+ if (el.dottedSlur !== undefined) el.pitches[0].dottedSlur = true;
492
+ if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
493
+ if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
494
+ } else {
495
+ el.rest = core.rest;
496
+ if (core.endSlur !== undefined) el.endSlur = core.endSlur;
497
+ if (core.endTie !== undefined) el.rest.endTie = core.endTie;
498
+ if (core.startSlur !== undefined) el.startSlur = core.startSlur;
499
+ if (core.startTie !== undefined) el.rest.startTie = core.startTie;
500
+ if (el.startTie !== undefined) el.rest.startTie = el.startTie;
501
+ }
502
+
503
+ if (core.chord !== undefined) el.chord = core.chord;
504
+ if (core.duration !== undefined) el.duration = core.duration;
505
+ if (core.decoration !== undefined) el.decoration = core.decoration;
506
+ if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
507
+ delete el.startSlur;
508
+ delete el.dottedSlur;
509
+ if (isInTie(multilineVars, overlayLevel, el)) {
510
+ if (el.pitches !== undefined) {
511
+ el.pitches[0].endTie = true;
512
+ } else if (el.rest.type !== 'spacer') {
513
+ el.rest.endTie = true;
514
+ }
515
+ setIsInTie(multilineVars, overlayLevel, false);
516
+ }
517
+ if (core.startTie || el.startTie)
518
+ setIsInTie(multilineVars, overlayLevel, true);
519
+ i = core.endChar;
520
+
521
+ if (tripletNotesLeft > 0 && !(core.rest && core.rest.type === "spacer")) {
522
+ tripletNotesLeft--;
523
+ if (tripletNotesLeft === 0) {
524
+ el.endTriplet = true;
525
+ }
526
+ }
527
+
528
+ if (core.end_beam)
529
+ addEndBeam(el);
530
+
531
+ // If there is a whole rest, then it should be the duration of the measure, not it's own duration. We need to special case it.
532
+ // If the time signature length is greater than 4/4, though, then a whole rest has no special treatment.
533
+ if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) {
534
+ el.rest.type = 'whole';
535
+
536
+ el.duration = durationOfMeasure(multilineVars);
537
+ }
538
+
539
+ // Create a warning if this is not a displayable duration.
540
+ // The first item on a line is a regular note value, each item after that represents a dot placed after the previous note.
541
+ // Only durations less than a whole note are tested because whole note durations have some tricky rules.
542
+ var durations = [
543
+ 0.5, 0.75, 0.875, 0.9375, 0.96875, 0.984375,
544
+ 0.25, 0.375, 0.4375, 0.46875, 0.484375, 0.4921875,
545
+ 0.125, 0.1875, 0.21875, 0.234375, 0.2421875, 0.24609375,
546
+ 0.0625, 0.09375, 0.109375, 0.1171875, 0.12109375, 0.123046875,
547
+ 0.03125, 0.046875, 0.0546875, 0.05859375, 0.060546875, 0.0615234375,
548
+ 0.015625, 0.0234375, 0.02734375, 0.029296875, 0.0302734375, 0.03076171875,
549
+ ];
550
+ if (el.duration < 1 && durations.indexOf(el.duration) === -1 && el.duration !== 0) {
551
+ if (!el.rest || el.rest.type !== 'spacer')
552
+ warn("Duration not representable: " + line.substring(startI, i), line, i);
553
+ }
554
+
555
+ multilineVars.addFormattingOptions(el, tune.formatting, 'note');
556
+ tuneBuilder.appendElement('note', startOfLine+startI, startOfLine+i, el);
557
+ multilineVars.measureNotEmpty = true;
558
+ el = {};
559
+ }
560
+ }
561
+
562
+ if (i === startI) { // don't know what this is, so ignore it.
563
+ if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
564
+ warn("Unknown character ignored", line, i);
565
+ i++;
566
+ }
567
+ }
568
+ }
569
+ }
570
+ this.lineContinuation = line.indexOf('\x12') >= 0 || (retHeader[0] > 0)
571
+ if (!this.lineContinuation) { el = { } }
572
+ };
573
+
574
+ var setIsInTie =function(multilineVars, overlayLevel, value) {
575
+ // If this is single voice music then the voice index isn't set, so we use the first voice.
576
+ var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
577
+ if (multilineVars.inTie[overlayLevel] === undefined)
578
+ multilineVars.inTie[overlayLevel] = [];
579
+ multilineVars.inTie[overlayLevel][voiceIndex] = value;
580
+ };
581
+
582
+ var letter_to_chord = function(line, i) {
583
+ if (line.charAt(i) === '"')
584
+ {
585
+ var chord = tokenizer.getBrackettedSubstring(line, i, 5);
586
+ if (!chord[2])
587
+ warn("Missing the closing quote while parsing the chord symbol", line , i);
588
+ // If it starts with ^, then the chord appears above.
589
+ // If it starts with _ then the chord appears below.
590
+ // (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
591
+ if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
592
+ chord[1] = chord[1].substring(1);
593
+ chord[2] = 'above';
594
+ } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
595
+ chord[1] = chord[1].substring(1);
596
+ chord[2] = 'below';
597
+ } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
598
+ chord[1] = chord[1].substring(1);
599
+ chord[2] = 'left';
600
+ } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
601
+ chord[1] = chord[1].substring(1);
602
+ chord[2] = 'right';
603
+ } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '@') {
604
+ // @-15,5.7
605
+ chord[1] = chord[1].substring(1);
606
+ var x = tokenizer.getFloat(chord[1]);
607
+ if (x.digits === 0)
608
+ warn("Missing first position in absolutely positioned annotation.", line , i);
609
+ chord[1] = chord[1].substring(x.digits);
610
+ if (chord[1][0] !== ',')
611
+ warn("Missing comma absolutely positioned annotation.", line , i);
612
+ chord[1] = chord[1].substring(1);
613
+ var y = tokenizer.getFloat(chord[1]);
614
+ if (y.digits === 0)
615
+ warn("Missing second position in absolutely positioned annotation.", line , i);
616
+ chord[1] = chord[1].substring(y.digits);
617
+ var ws = tokenizer.skipWhiteSpace(chord[1]);
618
+ chord[1] = chord[1].substring(ws);
619
+ chord[2] = null;
620
+ chord[3] = { x: x.value, y: y.value };
621
+ } else {
622
+ if (multilineVars.freegchord !== true) {
623
+ chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭");
624
+ chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯");
625
+ chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o([^A-Za-z])/g, "$1$2°$3");
626
+ chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)o$/g, "$1$2°");
627
+ chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)0([^A-Za-z])/g, "$1$2ø$3");
628
+ chord[1] = chord[1].replace(/^([ABCDEFG])([♯♭]?)\^([^A-Za-z])/g, "$1$2∆$3");
629
+ }
630
+ chord[2] = 'default';
631
+ chord[1] = transpose.chordName(multilineVars, chord[1]);
632
+ }
633
+ return chord;
634
+ }
635
+ return [0, ""];
636
+ };
637
+
638
+ var letter_to_grace = function(line, i) {
639
+ // Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
640
+ if (line.charAt(i) === '{') {
641
+ // fetch the gracenotes string and consume that into the array
642
+ var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
643
+ if (!gra[2])
644
+ warn("Missing the closing '}' while parsing grace note", line, i);
645
+ // If there is a slur after the grace construction, then move it to the last note inside the grace construction
646
+ if (line[i+gra[0]] === ')') {
647
+ gra[0]++;
648
+ gra[1] += ')';
649
+ }
650
+
651
+ var gracenotes = [];
652
+ var ii = 0;
653
+ var inTie = false;
654
+ while (ii < gra[1].length) {
655
+ var acciaccatura = false;
656
+ if (gra[1].charAt(ii) === '/') {
657
+ acciaccatura = true;
658
+ ii++;
659
+ }
660
+ var note = getCoreNote(gra[1], ii, {}, false);
661
+ if (note !== null) {
662
+ // The grace note durations should not be affected by the default length: they should be based on 1/16, so if that isn't the default, then multiply here.
663
+ note.duration = note.duration / (multilineVars.default_length * 8);
664
+ if (acciaccatura)
665
+ note.acciaccatura = true;
666
+ gracenotes.push(note);
667
+
668
+ if (inTie) {
669
+ note.endTie = true;
670
+ inTie = false;
671
+ }
672
+ if (note.startTie)
673
+ inTie = true;
674
+
675
+ ii = note.endChar;
676
+ delete note.endChar;
677
+
678
+ if (note.end_beam) {
679
+ note.endBeam = true;
680
+ delete note.end_beam;
681
+ }
682
+ }
683
+ else {
684
+ // We shouldn't get anything but notes or a space here, so report an error
685
+ if (gra[1].charAt(ii) === ' ') {
686
+ if (gracenotes.length > 0)
687
+ gracenotes[gracenotes.length-1].endBeam = true;
688
+ } else
689
+ warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
690
+ ii++;
691
+ }
692
+ }
693
+ if (gracenotes.length)
694
+ return [gra[0], gracenotes];
695
+ }
696
+ return [ 0 ];
697
+ };
698
+
699
+ function letter_to_overlay(line, i) {
700
+ if (line.charAt(i) === '&') {
701
+ var start = i;
702
+ while (line.charAt(i) && line.charAt(i) !== ':' && line.charAt(i) !== '|')
703
+ i++;
704
+ return [ i-start, line.substring(start+1, i) ];
705
+ }
706
+ return [ 0 ];
707
+ }
708
+
709
+ function durationOfMeasure(multilineVars) {
710
+ // TODO-PER: This could be more complicated if one of the unusual measures is used.
711
+ var meter = multilineVars.origMeter;
712
+ if (!meter || meter.type !== 'specified')
713
+ return 1;
714
+ if (!meter.value || meter.value.length === 0)
715
+ return 1;
716
+ return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10);
717
+ }
718
+
719
+ var legalAccents = [
720
+ "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
721
+ "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
722
+ "open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
723
+ "segno", "coda", "D.S.", "D.C.", "fine", "beambr1", "beambr2",
724
+ "slide", "marcato",
725
+ "upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
726
+ "turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark", "umarcato",
727
+ "style=normal", "style=harmonic", "style=rhythm", "style=x", "style=triangle"
728
+ ];
729
+
730
+ var volumeDecorations = [
731
+ "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz"
732
+ ];
733
+
734
+ var dynamicDecorations = [
735
+ "crescendo(", "crescendo)", "diminuendo(", "diminuendo)"
736
+ ];
737
+
738
+ var accentPseudonyms = [
739
+ ["<", "accent"], [">", "accent"], ["tr", "trill"],
740
+ ["plus", "+"], [ "emphasis", "accent"],
741
+ [ "^", "umarcato" ], [ "marcato", "umarcato" ]
742
+ ];
743
+
744
+ var accentDynamicPseudonyms = [
745
+ ["<(", "crescendo("], ["<)", "crescendo)"],
746
+ [">(", "diminuendo("], [">)", "diminuendo)"]
747
+ ];
748
+
749
+ var letter_to_accent = function(line, i) {
750
+ var macro = multilineVars.macros[line.charAt(i)];
751
+
752
+ if (macro !== undefined) {
753
+ if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
754
+ macro = macro.substring(1);
755
+ if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
756
+ macro = macro.substring(0, macro.length-1);
757
+ if (parseCommon.detect(legalAccents, function(acc) {
758
+ return (macro === acc);
759
+ }))
760
+ return [ 1, macro ];
761
+ else if (parseCommon.detect(volumeDecorations, function(acc) {
762
+ return (macro === acc);
763
+ })) {
764
+ if (multilineVars.volumePosition === 'hidden')
765
+ macro = "";
766
+ return [1, macro];
767
+ } else if (parseCommon.detect(dynamicDecorations, function(acc) {
768
+ if (multilineVars.dynamicPosition === 'hidden')
769
+ macro = "";
770
+ return (macro === acc);
771
+ })) {
772
+ return [1, macro];
773
+ } else {
774
+ if (!parseCommon.detect(multilineVars.ignoredDecorations, function(dec) {
775
+ return (macro === dec);
776
+ }))
777
+ warn("Unknown macro: " + macro, line, i);
778
+ return [1, '' ];
779
+ }
780
+ }
781
+ switch (line.charAt(i))
782
+ {
783
+ case '.':
784
+ if (line[i+1] === '(' || line[i+1] === '-') // a dot then open paren is a dotted slur; likewise dot dash is dotted tie.
785
+ break;
786
+ return [1, 'staccato'];
787
+ case 'u':return [1, 'upbow'];
788
+ case 'v':return [1, 'downbow'];
789
+ case '~':return [1, 'irishroll'];
790
+ case '!':
791
+ case '+':
792
+ var ret = tokenizer.getBrackettedSubstring(line, i, 5);
793
+ // Be sure that the accent is recognizable.
794
+ if (ret[1].length > 1 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
795
+ ret[1] = ret[1].substring(1); // TODO-PER: The test files have indicators forcing the ornament to the top or bottom, but that isn't in the standard. We'll just ignore them.
796
+ if (parseCommon.detect(legalAccents, function(acc) {
797
+ return (ret[1] === acc);
798
+ }))
799
+ return ret;
800
+ if (parseCommon.detect(volumeDecorations, function(acc) {
801
+ return (ret[1] === acc);
802
+ })) {
803
+ if (multilineVars.volumePosition === 'hidden' )
804
+ ret[1] = '';
805
+ return ret;
806
+ }
807
+ if (parseCommon.detect(dynamicDecorations, function(acc) {
808
+ return (ret[1] === acc);
809
+ })) {
810
+ if (multilineVars.dynamicPosition === 'hidden' )
811
+ ret[1] = '';
812
+ return ret;
813
+ }
814
+
815
+ if (parseCommon.detect(accentPseudonyms, function(acc) {
816
+ if (ret[1] === acc[0]) {
817
+ ret[1] = acc[1];
818
+ return true;
819
+ } else
820
+ return false;
821
+ }))
822
+ return ret;
823
+
824
+ if (parseCommon.detect(accentDynamicPseudonyms, function(acc) {
825
+ if (ret[1] === acc[0]) {
826
+ ret[1] = acc[1];
827
+ return true;
828
+ } else
829
+ return false;
830
+ })) {
831
+ if (multilineVars.dynamicPosition === 'hidden' )
832
+ ret[1] = '';
833
+ return ret;
834
+ }
835
+ // We didn't find the accent in the list, so consume the space, but don't return an accent.
836
+ // Although it is possible that ! was used as a line break, so accept that.
837
+ if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
838
+ return [1, null ];
839
+ warn("Unknown decoration: " + ret[1], line, i);
840
+ ret[1] = "";
841
+ return ret;
842
+ case 'H':return [1, 'fermata'];
843
+ case 'J':return [1, 'slide'];
844
+ case 'L':return [1, 'accent'];
845
+ case 'M':return [1, 'mordent'];
846
+ case 'O':return[1, 'coda'];
847
+ case 'P':return[1, 'pralltriller'];
848
+ case 'R':return [1, 'roll'];
849
+ case 'S':return [1, 'segno'];
850
+ case 'T':return [1, 'trill'];
851
+ }
852
+ return [0, 0];
853
+ };
854
+
855
+ var letter_to_spacer = function(line, i) {
856
+ var start = i;
857
+ while (tokenizer.isWhiteSpace(line.charAt(i)))
858
+ i++;
859
+ return [ i-start ];
860
+ };
861
+
862
+ // returns the class of the bar line
863
+ // the number of the repeat
864
+ // and the number of characters used up
865
+ // if 0 is returned, then the next element was not a bar line
866
+ var letter_to_bar = function(line, curr_pos) {
867
+ var ret = tokenizer.getBarLine(line, curr_pos);
868
+ if (ret.len === 0)
869
+ return [0,""];
870
+ if (ret.warn) {
871
+ warn(ret.warn, line, curr_pos);
872
+ return [ret.len,""];
873
+ }
874
+
875
+ // Now see if this is a repeated ending
876
+ // A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
877
+ // It can also optionally start with '[', which is ignored.
878
+ // Also, it can have white space before the '['.
879
+ for (var ws = 0; ws < line.length; ws++)
880
+ if (line.charAt(curr_pos + ret.len + ws) !== ' ')
881
+ break;
882
+ var orig_bar_len = ret.len;
883
+ if (line.charAt(curr_pos+ret.len+ws) === '[') {
884
+ ret.len += ws + 1;
885
+ }
886
+
887
+ // It can also be a quoted string. It is unclear whether that construct requires '[', but it seems like it would. otherwise it would be confused with a regular chord.
888
+ if (line.charAt(curr_pos+ret.len) === '"' && line.charAt(curr_pos+ret.len-1) === '[') {
889
+ var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
890
+ return [ret.len+ending[0], ret.token, ending[1]];
891
+ }
892
+ var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
893
+ if (retRep.len === 0 || retRep.token[0] === '-')
894
+ return [orig_bar_len, ret.token];
895
+
896
+ return [ret.len+retRep.len, ret.token, retRep.token];
897
+ };
898
+
899
+ var tripletQ = {
900
+ 2: 3,
901
+ 3: 2,
902
+ 4: 3,
903
+ 5: 2, // TODO-PER: not handling 6/8 rhythm yet
904
+ 6: 2,
905
+ 7: 2, // TODO-PER: not handling 6/8 rhythm yet
906
+ 8: 3,
907
+ 9: 2 // TODO-PER: not handling 6/8 rhythm yet
908
+ };
909
+
910
+ var letter_to_open_slurs_and_triplets = function(line, i) {
911
+ // consume spaces, and look for all the open parens. If there is a number after the open paren,
912
+ // that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
913
+ var ret = {};
914
+ var start = i;
915
+ if (line[i] === '.' && line[i+1] === '(') {
916
+ ret.dottedSlur = true;
917
+ i++;
918
+ }
919
+ while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
920
+ if (line.charAt(i) === '(') {
921
+ if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
922
+ if (ret.triplet !== undefined)
923
+ warn("Can't nest triplets", line, i);
924
+ else {
925
+ ret.triplet = line.charAt(i+1) - '0';
926
+ ret.tripletQ = tripletQ[ret.triplet];
927
+ ret.num_notes = ret.triplet;
928
+ if (i+2 < line.length && line.charAt(i+2) === ':') {
929
+ // We are expecting "(p:q:r" or "(p:q" or "(p::r"
930
+ // That is: "put p notes into the time of q for the next r notes"
931
+ // if r is missing, then it is equal to p.
932
+ // if q is missing, it is determined from this table:
933
+ // (2 notes in the time of 3
934
+ // (3 notes in the time of 2
935
+ // (4 notes in the time of 3
936
+ // (5 notes in the time of n | if time sig is (6/8, 9/8, 12/8), n=3, else n=2
937
+ // (6 notes in the time of 2
938
+ // (7 notes in the time of n
939
+ // (8 notes in the time of 3
940
+ // (9 notes in the time of n
941
+ if (i+3 < line.length && line.charAt(i+3) === ':') {
942
+ // The second number, 'q', is not present.
943
+ if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
944
+ ret.num_notes = line.charAt(i+4) - '0';
945
+ i += 3;
946
+ } else
947
+ warn("expected number after the two colons after the triplet to mark the duration", line, i);
948
+ } else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
949
+ ret.tripletQ = line.charAt(i+3) - '0';
950
+ if (i+4 < line.length && line.charAt(i+4) === ':') {
951
+ if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
952
+ ret.num_notes = line.charAt(i+5) - '0';
953
+ i += 4;
954
+ }
955
+ } else {
956
+ i += 2;
957
+ }
958
+ } else
959
+ warn("expected number after the triplet to mark the duration", line, i);
960
+ }
961
+ }
962
+ i++;
963
+ }
964
+ else {
965
+ if (ret.startSlur === undefined)
966
+ ret.startSlur = 1;
967
+ else
968
+ ret.startSlur++;
969
+ }
970
+ }
971
+ i++;
972
+ }
973
+ ret.consumed = i-start;
974
+ return ret;
975
+ };
976
+
977
+ MusicParser.prototype.startNewLine = function() {
978
+ var params = { startChar: -1, endChar: -1};
979
+ if (multilineVars.partForNextLine.title)
980
+ params.part = multilineVars.partForNextLine;
981
+ params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
982
+ var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
983
+ params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
984
+ params.key.mode = multilineVars.key.mode;
985
+ if (multilineVars.key.impliedNaturals)
986
+ params.key.impliedNaturals = multilineVars.key.impliedNaturals;
987
+ if (multilineVars.key.explicitAccidentals) {
988
+ for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) {
989
+ var found = false;
990
+ for (var j = 0; j < params.key.accidentals.length; j++) {
991
+ if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) {
992
+ // If the note is already in the list, override it with the new value
993
+ params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc;
994
+ found = true;
995
+ }
996
+ }
997
+ if (!found)
998
+ params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]);
999
+ }
1000
+ }
1001
+ multilineVars.targetKey = params.key;
1002
+ if (params.key.explicitAccidentals)
1003
+ delete params.key.explicitAccidentals;
1004
+ parseKeyVoice.addPosToKey(params.clef, params.key);
1005
+ if (multilineVars.meter !== null) {
1006
+ if (multilineVars.currentVoice) {
1007
+ parseCommon.each(multilineVars.staves, function(st) {
1008
+ st.meter = multilineVars.meter;
1009
+ });
1010
+ params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
1011
+ multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
1012
+ } else
1013
+ params.meter = multilineVars.meter;
1014
+ multilineVars.meter = null;
1015
+ } else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
1016
+ // Make sure that each voice gets the meter marking.
1017
+ params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
1018
+ multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
1019
+ }
1020
+ if (multilineVars.currentVoice && multilineVars.currentVoice.name)
1021
+ params.name = multilineVars.currentVoice.name;
1022
+ if (multilineVars.vocalfont)
1023
+ params.vocalfont = multilineVars.vocalfont;
1024
+ if (multilineVars.tripletfont)
1025
+ params.tripletfont = multilineVars.tripletfont;
1026
+ if (multilineVars.gchordfont)
1027
+ params.gchordfont = multilineVars.gchordfont;
1028
+ if (multilineVars.style)
1029
+ params.style = multilineVars.style;
1030
+ if (multilineVars.currentVoice) {
1031
+ var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
1032
+ if (staff.brace) params.brace = staff.brace;
1033
+ if (staff.bracket) params.bracket = staff.bracket;
1034
+ if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
1035
+ if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
1036
+ if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
1037
+ if (multilineVars.currentVoice.stem)
1038
+ params.stem = multilineVars.currentVoice.stem;
1039
+ if (multilineVars.currentVoice.stafflines)
1040
+ params.stafflines = multilineVars.currentVoice.stafflines;
1041
+ if (multilineVars.currentVoice.staffscale)
1042
+ params.staffscale = multilineVars.currentVoice.staffscale;
1043
+ if (multilineVars.currentVoice.scale)
1044
+ params.scale = multilineVars.currentVoice.scale;
1045
+ if (multilineVars.currentVoice.style)
1046
+ params.style = multilineVars.currentVoice.style;
1047
+ if (multilineVars.currentVoice.transpose)
1048
+ params.clef.transpose = multilineVars.currentVoice.transpose;
1049
+ }
1050
+ var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
1051
+ if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)
1052
+ params.barNumber = multilineVars.currBarNumber;
1053
+ tuneBuilder.startNewLine(params);
1054
+ if (multilineVars.key.impliedNaturals)
1055
+ delete multilineVars.key.impliedNaturals;
1056
+
1057
+ multilineVars.partForNextLine = {};
1058
+ if (multilineVars.tempoForNextLine.length === 4)
1059
+ tuneBuilder.appendElement(multilineVars.tempoForNextLine[0],multilineVars.tempoForNextLine[1],multilineVars.tempoForNextLine[2],multilineVars.tempoForNextLine[3]);
1060
+ multilineVars.tempoForNextLine = [];
1061
+ }
1062
+
1063
+ // TODO-PER: make this a method in el.
1064
+ var addEndBeam = function(el) {
1065
+ if (el.duration !== undefined && el.duration < 0.25)
1066
+ el.end_beam = true;
1067
+ return el;
1068
+ };
1069
+
1070
+ var pitches = {A: 5, B: 6, C: 0, D: 1, E: 2, F: 3, G: 4, a: 12, b: 13, c: 7, d: 8, e: 9, f: 10, g: 11};
1071
+ var rests = {x: 'invisible', X: 'invisible-multimeasure', y: 'spacer', z: 'rest', Z: 'multimeasure' };
1072
+ var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^', 'quarterflat': '_/', 'quartersharp': '^/'};
1073
+ var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
1074
+ //var el = { startChar: index };
1075
+ var isComplete = function(state) {
1076
+ return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
1077
+ };
1078
+ var dottedTie;
1079
+ if (line[index] === '.' && line[index+1] === '-') {
1080
+ dottedTie = true;
1081
+ index++;
1082
+ }
1083
+ var state = 'startSlur';
1084
+ var durationSetByPreviousNote = false;
1085
+ while (1) {
1086
+ switch(line.charAt(index)) {
1087
+ case '(':
1088
+ if (state === 'startSlur') {
1089
+ if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
1090
+ } else if (isComplete(state)) {el.endChar = index;return el;}
1091
+ else return null;
1092
+ break;
1093
+ case ')':
1094
+ if (isComplete(state)) {
1095
+ if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
1096
+ } else return null;
1097
+ break;
1098
+ case '^':
1099
+ if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
1100
+ else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
1101
+ else if (isComplete(state)) {el.endChar = index;return el;}
1102
+ else return null;
1103
+ break;
1104
+ case '_':
1105
+ if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
1106
+ else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
1107
+ else if (isComplete(state)) {el.endChar = index;return el;}
1108
+ else return null;
1109
+ break;
1110
+ case '=':
1111
+ if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
1112
+ else if (isComplete(state)) {el.endChar = index;return el;}
1113
+ else return null;
1114
+ break;
1115
+ case 'A':
1116
+ case 'B':
1117
+ case 'C':
1118
+ case 'D':
1119
+ case 'E':
1120
+ case 'F':
1121
+ case 'G':
1122
+ case 'a':
1123
+ case 'b':
1124
+ case 'c':
1125
+ case 'd':
1126
+ case 'e':
1127
+ case 'f':
1128
+ case 'g':
1129
+ if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
1130
+ el.pitch = pitches[line.charAt(index)];
1131
+ el.name = line.charAt(index);
1132
+ if (el.accidental)
1133
+ el.name = accMap[el.accidental] + el.name;
1134
+ transpose.note(multilineVars, el);
1135
+ state = 'octave';
1136
+ // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
1137
+ if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
1138
+ el.duration = multilineVars.default_length * multilineVars.next_note_duration;
1139
+ multilineVars.next_note_duration = 0;
1140
+ durationSetByPreviousNote = true;
1141
+ } else
1142
+ el.duration = multilineVars.default_length;
1143
+ // If the clef is percussion, there is probably some translation of the pitch to a particular drum kit item.
1144
+ if ((multilineVars.clef && multilineVars.clef.type === "perc") ||
1145
+ (multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) {
1146
+ var key = line.charAt(index);
1147
+ if (el.accidental) {
1148
+ key = accMap[el.accidental] + key;
1149
+ }
1150
+ if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap)
1151
+ el.midipitch = tune.formatting.midi.drummap[key];
1152
+ }
1153
+ } else if (isComplete(state)) {el.endChar = index;return el;}
1154
+ else return null;
1155
+ break;
1156
+ case ',':
1157
+ if (state === 'octave') {el.pitch -= 7; el.name += ','; }
1158
+ else if (isComplete(state)) {el.endChar = index;return el;}
1159
+ else return null;
1160
+ break;
1161
+ case '\'':
1162
+ if (state === 'octave') {el.pitch += 7; el.name += "'"; }
1163
+ else if (isComplete(state)) {el.endChar = index;return el;}
1164
+ else return null;
1165
+ break;
1166
+ case 'x':
1167
+ case 'X':
1168
+ case 'y':
1169
+ case 'z':
1170
+ case 'Z':
1171
+ if (state === 'startSlur') {
1172
+ el.rest = { type: rests[line.charAt(index)] };
1173
+ // There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
1174
+ // just nix them here.
1175
+ delete el.accidental;
1176
+ delete el.startSlur;
1177
+ delete el.startTie;
1178
+ delete el.endSlur;
1179
+ delete el.endTie;
1180
+ delete el.end_beam;
1181
+ delete el.grace_notes;
1182
+ // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
1183
+ if (el.rest.type.indexOf('multimeasure') >= 0) {
1184
+ el.duration = tune.getBarLength();
1185
+ el.rest.text = 1;
1186
+ state = 'Zduration';
1187
+ } else {
1188
+ if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
1189
+ el.duration = multilineVars.default_length * multilineVars.next_note_duration;
1190
+ multilineVars.next_note_duration = 0;
1191
+ durationSetByPreviousNote = true;
1192
+ } else
1193
+ el.duration = multilineVars.default_length;
1194
+ state = 'duration';
1195
+ }
1196
+ } else if (isComplete(state)) {el.endChar = index;return el;}
1197
+ else return null;
1198
+ break;
1199
+ case '1':
1200
+ case '2':
1201
+ case '3':
1202
+ case '4':
1203
+ case '5':
1204
+ case '6':
1205
+ case '7':
1206
+ case '8':
1207
+ case '9':
1208
+ case '0':
1209
+ case '/':
1210
+ if (state === 'octave' || state === 'duration') {
1211
+ var fraction = tokenizer.getFraction(line, index);
1212
+ //if (!durationSetByPreviousNote)
1213
+ el.duration = el.duration * fraction.value;
1214
+ // TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
1215
+ el.endChar = fraction.index;
1216
+ while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
1217
+ if (line.charAt(fraction.index) === '-')
1218
+ el.startTie = {};
1219
+ else
1220
+ el = addEndBeam(el);
1221
+ fraction.index++;
1222
+ }
1223
+ index = fraction.index-1;
1224
+ state = 'broken_rhythm';
1225
+ } else if (state === 'sharp2') {
1226
+ el.accidental = 'quartersharp';state = 'pitch';
1227
+ } else if (state === 'flat2') {
1228
+ el.accidental = 'quarterflat';state = 'pitch';
1229
+ } else if (state === 'Zduration') {
1230
+ var num = tokenizer.getNumber(line, index);
1231
+ el.duration = num.num * tune.getBarLength();
1232
+ el.rest.text = num.num;
1233
+ el.endChar = num.index;
1234
+ return el;
1235
+ } else return null;
1236
+ break;
1237
+ case '-':
1238
+ if (state === 'startSlur') {
1239
+ // This is the first character, so it must have been meant for the previous note. Correct that here.
1240
+ tuneBuilder.addTieToLastNote(dottedTie);
1241
+ el.endTie = true;
1242
+ } else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
1243
+ el.startTie = {};
1244
+ if (!durationSetByPreviousNote && canHaveBrokenRhythm)
1245
+ state = 'broken_rhythm';
1246
+ else {
1247
+ // Peek ahead to the next character. If it is a space, then we have an end beam.
1248
+ if (tokenizer.isWhiteSpace(line.charAt(index + 1)))
1249
+ addEndBeam(el);
1250
+ el.endChar = index+1;
1251
+ return el;
1252
+ }
1253
+ } else if (state === 'broken_rhythm') {el.endChar = index;return el;}
1254
+ else return null;
1255
+ break;
1256
+ case ' ':
1257
+ case '\t':
1258
+ if (isComplete(state)) {
1259
+ el.end_beam = true;
1260
+ // look ahead to see if there is a tie
1261
+ dottedTie = false;
1262
+ do {
1263
+ if (line.charAt(index) === '.' && line.charAt(index+1) === '-') {
1264
+ dottedTie = true;
1265
+ index++;
1266
+ }
1267
+ if (line.charAt(index) === '-') {
1268
+ el.startTie = {};
1269
+ if (dottedTie)
1270
+ el.startTie.style = "dotted";
1271
+ }
1272
+ index++;
1273
+ } while (index < line.length &&
1274
+ (tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-') ||
1275
+ (line.charAt(index) === '.' && line.charAt(index+1) === '-'));
1276
+ el.endChar = index;
1277
+ if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line.charAt(index) === '<' || line.charAt(index) === '>')) { // TODO-PER: Don't need the test for < and >, but that makes the endChar work out for the regression test.
1278
+ index--;
1279
+ state = 'broken_rhythm';
1280
+ } else
1281
+ return el;
1282
+ }
1283
+ else return null;
1284
+ break;
1285
+ case '>':
1286
+ case '<':
1287
+ if (isComplete(state)) {
1288
+ if (canHaveBrokenRhythm) {
1289
+ var br2 = getBrokenRhythm(line, index);
1290
+ index += br2[0] - 1; // index gets incremented below, so we'll let that happen
1291
+ multilineVars.next_note_duration = br2[2];
1292
+ el.duration = br2[1]*el.duration;
1293
+ state = 'end_slur';
1294
+ } else {
1295
+ el.endChar = index;
1296
+ return el;
1297
+ }
1298
+ } else
1299
+ return null;
1300
+ break;
1301
+ default:
1302
+ if (isComplete(state)) {
1303
+ el.endChar = index;
1304
+ return el;
1305
+ }
1306
+ return null;
1307
+ }
1308
+ index++;
1309
+ if (index === line.length) {
1310
+ if (isComplete(state)) {el.endChar = index;return el;}
1311
+ else return null;
1312
+ }
1313
+ }
1314
+ return null;
1315
+ };
1316
+
1317
+ var getBrokenRhythm = function(line, index) {
1318
+ switch (line.charAt(index)) {
1319
+ case '>':
1320
+ if (index < line.length - 2 && line.charAt(index + 1) === '>' && line.charAt(index + 2) === '>') // triple >>>
1321
+ return [3, 1.875, 0.125];
1322
+ else if (index < line.length - 1 && line.charAt(index + 1) === '>') // double >>
1323
+ return [2, 1.75, 0.25];
1324
+ else
1325
+ return [1, 1.5, 0.5];
1326
+ case '<':
1327
+ if (index < line.length - 2 && line.charAt(index + 1) === '<' && line.charAt(index + 2) === '<') // triple <<<
1328
+ return [3, 0.125, 1.875];
1329
+ else if (index < line.length - 1 && line.charAt(index + 1) === '<') // double <<
1330
+ return [2, 0.25, 1.75];
1331
+ else
1332
+ return [1, 0.5, 1.5];
1333
+ }
1334
+ return null;
1335
+ };
1336
+
1337
+ module.exports = MusicParser;