abcjs 6.0.0-beta.9 → 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 (205) 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 +88 -7
  6. package/RELEASE.md +939 -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 +216 -117
  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 +8 -18
  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/Dockerfile +0 -8
  179. package/abcjs-midi.css +0 -166
  180. package/build-utils/loadPresets.js +0 -14
  181. package/build-utils/presets/webpack.analyze.js +0 -6
  182. package/build-utils/presets/webpack.optimize.js +0 -30
  183. package/build-utils/webpack.development.js +0 -14
  184. package/build-utils/webpack.production.js +0 -35
  185. package/deploy-docs.sh +0 -25
  186. package/docker-compose.yml +0 -13
  187. package/docs/README.md +0 -33
  188. package/fix-versions.sh +0 -23
  189. package/midi.js +0 -62
  190. package/src/api/abc_tunebook_midi.js +0 -116
  191. package/src/midi/abc_midi_controls.js +0 -701
  192. package/src/midi/abc_midi_flattener.js +0 -1119
  193. package/src/midi/abc_midi_js_preparer.js +0 -243
  194. package/src/midi/abc_midi_sequencer.js +0 -401
  195. package/src/midi/abc_midi_ui_generator.js +0 -86
  196. package/src/plugin/abc_plugin_midi.js +0 -220
  197. package/src/synth/images/loop.svg +0 -61
  198. package/src/synth/images/pause.svg +0 -6
  199. package/src/synth/images/play.svg +0 -5
  200. package/src/transform/abc2abc_write.js +0 -395
  201. package/static-wrappers/basic.js +0 -2
  202. package/static-wrappers/midi.js +0 -2
  203. package/static-wrappers/plugin-midi.js +0 -6
  204. package/static-wrappers/plugin.js +0 -6
  205. package/webpack.config.js +0 -29
@@ -1,46 +1,36 @@
1
1
  // abc_parse.js: parses a string representing ABC Music Notation into a usable internal structure.
2
- // Copyright (C) 2010-2020 Paul Rosen (paul at paulrosen dot net)
3
- //
4
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
5
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
6
- // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
7
- // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8
- //
9
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10
- //
11
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
12
- // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
13
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
14
- // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
-
17
- /*global window */
18
2
 
19
3
  var parseCommon = require('./abc_common');
20
4
  var parseDirective = require('./abc_parse_directive');
21
5
  var ParseHeader = require('./abc_parse_header');
22
- var parseKeyVoice = require('./abc_parse_key_voice');
6
+ var ParseMusic = require('./abc_parse_music');
23
7
  var Tokenizer = require('./abc_tokenizer');
24
- var transpose = require('./abc_transpose');
25
8
  var wrap = require('./wrap_lines');
26
9
 
27
10
  var Tune = require('../data/abc_tune');
11
+ var TuneBuilder = require('../parse/tune-builder');
28
12
 
29
13
  var Parse = function() {
30
14
  "use strict";
31
15
  var tune = new Tune();
32
- var tokenizer = new Tokenizer();
16
+ var tuneBuilder = new TuneBuilder(tune);
17
+ var tokenizer;
18
+ var wordsContinuation = '';
19
+ var symbolContinuation = '';
33
20
 
34
21
  this.getTune = function() {
35
- return {
22
+ var t = {
36
23
  formatting: tune.formatting,
37
24
  lines: tune.lines,
38
25
  media: tune.media,
39
26
  metaText: tune.metaText,
27
+ metaTextInfo: tune.metaTextInfo,
40
28
  version: tune.version,
41
29
 
42
30
  addElementToEvents: tune.addElementToEvents,
43
31
  addUsefulCallbackInfo: tune.addUsefulCallbackInfo,
32
+ getTotalTime: tune.getTotalTime,
33
+ getTotalBeats: tune.getTotalBeats,
44
34
  getBarLength: tune.getBarLength,
45
35
  getBeatLength: tune.getBeatLength,
46
36
  getBeatsPerMeasure: tune.getBeatsPerMeasure,
@@ -49,11 +39,19 @@ var Parse = function() {
49
39
  getMeterFraction: tune.getMeterFraction,
50
40
  getPickupLength: tune.getPickupLength,
51
41
  getKeySignature: tune.getKeySignature,
42
+ getElementFromChar: tune.getElementFromChar,
52
43
  makeVoicesArray: tune.makeVoicesArray,
53
44
  millisecondsPerMeasure: tune.millisecondsPerMeasure,
54
45
  setupEvents: tune.setupEvents,
55
- setTiming: tune.setTiming
46
+ setTiming: tune.setTiming,
47
+ setUpAudio: tune.setUpAudio,
48
+ deline: tune.deline,
56
49
  };
50
+ if (tune.lineBreaks)
51
+ t.lineBreaks = tune.lineBreaks;
52
+ if (tune.visualTranspose)
53
+ t.visualTranspose = tune.visualTranspose;
54
+ return t;
57
55
  };
58
56
 
59
57
  function addPositioning(el, type, value) {
@@ -83,18 +81,15 @@ var Parse = function() {
83
81
  this.next_note_duration = 0;
84
82
  this.start_new_line = true;
85
83
  this.is_in_header = true;
86
- this.is_in_history = false;
87
84
  this.partForNextLine = {};
85
+ this.tempoForNextLine = [];
88
86
  this.havent_set_length = true;
89
87
  this.voices = {};
90
88
  this.staves = [];
91
89
  this.macros = {};
92
90
  this.currBarNumber = 1;
93
91
  this.barCounter = {};
94
- this.inTextBlock = false;
95
- this.inPsBlock = false;
96
92
  this.ignoredDecorations = [];
97
- this.textBlock = "";
98
93
  this.score_is_present = false; // Can't have original V: lines when there is the score directive
99
94
  this.inEnding = false;
100
95
  this.inTie = [];
@@ -106,6 +101,7 @@ var Parse = function() {
106
101
  this.volumePosition = "auto";
107
102
  this.openSlurs = [];
108
103
  this.freegchord = false;
104
+ this.endingHoldOver = {};
109
105
  },
110
106
  differentFont: function(type, defaultFonts) {
111
107
  if (this[type].decoration !== defaultFonts[type].decoration) return true;
@@ -134,7 +130,41 @@ var Parse = function() {
134
130
  if (this.differentFont("measurefont", defaultFonts)) addFont(el, 'measurefont', this.measurefont);
135
131
  if (this.differentFont("repeatfont", defaultFonts)) addFont(el, 'repeatfont', this.repeatfont);
136
132
  }
137
- }
133
+ },
134
+ duplicateStartEndingHoldOvers: function() {
135
+ this.endingHoldOver = {
136
+ inTie: [],
137
+ inTieChord: {}
138
+ };
139
+ for (var i = 0; i < this.inTie.length; i++) {
140
+ this.endingHoldOver.inTie.push([]);
141
+ if (this.inTie[i]) { // if a voice is suppressed there might be a gap in the array.
142
+ for (var j = 0; j < this.inTie[i].length; j++) {
143
+ this.endingHoldOver.inTie[i].push(this.inTie[i][j]);
144
+ }
145
+ }
146
+ }
147
+ for (var key in this.inTieChord) {
148
+ if (this.inTieChord.hasOwnProperty(key))
149
+ this.endingHoldOver.inTieChord[key] = this.inTieChord[key];
150
+ }
151
+ },
152
+ restoreStartEndingHoldOvers: function() {
153
+ if (!this.endingHoldOver.inTie)
154
+ return;
155
+ this.inTie = [];
156
+ this.inTieChord = {};
157
+ for (var i = 0; i < this.endingHoldOver.inTie.length; i++) {
158
+ this.inTie.push([]);
159
+ for (var j = 0; j < this.endingHoldOver.inTie[i].length; j++) {
160
+ this.inTie[i].push(this.endingHoldOver.inTie[i][j]);
161
+ }
162
+ }
163
+ for (var key in this.endingHoldOver.inTieChord) {
164
+ if (this.endingHoldOver.inTieChord.hasOwnProperty(key))
165
+ this.inTieChord[key] = this.endingHoldOver.inTieChord[key];
166
+ }
167
+ },
138
168
  };
139
169
 
140
170
  var addWarning = function(str) {
@@ -161,13 +191,13 @@ var Parse = function() {
161
191
  var bad_char = line.charAt(col_num);
162
192
  if (bad_char === ' ')
163
193
  bad_char = "SPACE";
164
- var clean_line = encode(line.substring(0, col_num)) +
165
- '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' +
166
- encode(line.substring(col_num+1));
167
- addWarning("Music Line:" + tune.getNumLines() + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
194
+ var clean_line = encode(line.substring(col_num - 64, col_num)) + '<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' + encode(line.substring(col_num + 1).substring(0,64));
195
+ addWarning("Music Line:" + tokenizer.lineIndex + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
168
196
  addWarningObject({message:str, line:line, startChar: multilineVars.iChar + col_num, column: col_num});
169
197
  };
170
- var header = new ParseHeader(tokenizer, warn, multilineVars, tune);
198
+
199
+ var header;
200
+ var music;
171
201
 
172
202
  this.getWarnings = function() {
173
203
  return multilineVars.warnings;
@@ -176,300 +206,14 @@ var Parse = function() {
176
206
  return multilineVars.warningObjects;
177
207
  };
178
208
 
179
- var letter_to_chord = function(line, i)
180
- {
181
- if (line.charAt(i) === '"')
182
- {
183
- var chord = tokenizer.getBrackettedSubstring(line, i, 5);
184
- if (!chord[2])
185
- warn("Missing the closing quote while parsing the chord symbol", line , i);
186
- // If it starts with ^, then the chord appears above.
187
- // If it starts with _ then the chord appears below.
188
- // (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
189
- if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
190
- chord[1] = chord[1].substring(1);
191
- chord[2] = 'above';
192
- } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
193
- chord[1] = chord[1].substring(1);
194
- chord[2] = 'below';
195
- } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
196
- chord[1] = chord[1].substring(1);
197
- chord[2] = 'left';
198
- } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
199
- chord[1] = chord[1].substring(1);
200
- chord[2] = 'right';
201
- } else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '@') {
202
- // @-15,5.7
203
- chord[1] = chord[1].substring(1);
204
- var x = tokenizer.getFloat(chord[1]);
205
- if (x.digits === 0)
206
- warn("Missing first position in absolutely positioned annotation.", line , i);
207
- chord[1] = chord[1].substring(x.digits);
208
- if (chord[1][0] !== ',')
209
- warn("Missing comma absolutely positioned annotation.", line , i);
210
- chord[1] = chord[1].substring(1);
211
- var y = tokenizer.getFloat(chord[1]);
212
- if (y.digits === 0)
213
- warn("Missing second position in absolutely positioned annotation.", line , i);
214
- chord[1] = chord[1].substring(y.digits);
215
- var ws = tokenizer.skipWhiteSpace(chord[1]);
216
- chord[1] = chord[1].substring(ws);
217
- chord[2] = null;
218
- chord[3] = { x: x.value, y: y.value };
219
- } else {
220
- if (multilineVars.freegchord !== true) {
221
- chord[1] = chord[1].replace(/([ABCDEFG0-9])b/g, "$1♭");
222
- chord[1] = chord[1].replace(/([ABCDEFG0-9])#/g, "$1♯");
223
- }
224
- chord[2] = 'default';
225
- chord[1] = transpose.chordName(multilineVars, chord[1]);
226
- }
227
- return chord;
228
- }
229
- return [0, ""];
230
- };
231
-
232
- var legalAccents = [ "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
233
- "fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
234
- "open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
235
- "segno", "coda", "D.S.", "D.C.", "fine",
236
- "slide", "^", "marcato",
237
- "upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
238
- "turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark", "umarcato",
239
- "style=normal", "style=harmonic", "style=rhythm", "style=x"
240
- ];
241
- var volumeDecorations = [ "p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz" ];
242
- var dynamicDecorations = ["crescendo(", "crescendo)", "diminuendo(", "diminuendo)"];
243
-
244
- var accentPseudonyms = [ ["<", "accent"], [">", "accent"], ["tr", "trill"],
245
- ["plus", "+"], [ "emphasis", "accent"],
246
- [ "^", "umarcato" ], [ "marcato", "umarcato" ] ];
247
- var accentDynamicPseudonyms = [ ["<(", "crescendo("], ["<)", "crescendo)"],
248
- [">(", "diminuendo("], [">)", "diminuendo)"] ];
249
- var letter_to_accent = function(line, i)
250
- {
251
- var macro = multilineVars.macros[line.charAt(i)];
252
-
253
- if (macro !== undefined) {
254
- if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
255
- macro = macro.substring(1);
256
- if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
257
- macro = macro.substring(0, macro.length-1);
258
- if (parseCommon.detect(legalAccents, function(acc) {
259
- return (macro === acc);
260
- }))
261
- return [ 1, macro ];
262
- else if (parseCommon.detect(volumeDecorations, function(acc) {
263
- return (macro === acc);
264
- })) {
265
- if (multilineVars.volumePosition === 'hidden')
266
- macro = "";
267
- return [1, macro];
268
- } else if (parseCommon.detect(dynamicDecorations, function(acc) {
269
- if (multilineVars.dynamicPosition === 'hidden')
270
- macro = "";
271
- return (macro === acc);
272
- })) {
273
- return [1, macro];
274
- } else {
275
- if (!parseCommon.detect(multilineVars.ignoredDecorations, function(dec) {
276
- return (macro === dec);
277
- }))
278
- warn("Unknown macro: " + macro, line, i);
279
- return [1, '' ];
280
- }
281
- }
282
- switch (line.charAt(i))
283
- {
284
- case '.':return [1, 'staccato'];
285
- case 'u':return [1, 'upbow'];
286
- case 'v':return [1, 'downbow'];
287
- case '~':return [1, 'irishroll'];
288
- case '!':
289
- case '+':
290
- var ret = tokenizer.getBrackettedSubstring(line, i, 5);
291
- // Be sure that the accent is recognizable.
292
- if (ret[1].length > 0 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
293
- 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.
294
- if (parseCommon.detect(legalAccents, function(acc) {
295
- return (ret[1] === acc);
296
- }))
297
- return ret;
298
- if (parseCommon.detect(volumeDecorations, function(acc) {
299
- return (ret[1] === acc);
300
- })) {
301
- if (multilineVars.volumePosition === 'hidden' )
302
- ret[1] = '';
303
- return ret;
304
- }
305
- if (parseCommon.detect(dynamicDecorations, function(acc) {
306
- return (ret[1] === acc);
307
- })) {
308
- if (multilineVars.dynamicPosition === 'hidden' )
309
- ret[1] = '';
310
- return ret;
311
- }
312
-
313
- if (parseCommon.detect(accentPseudonyms, function(acc) {
314
- if (ret[1] === acc[0]) {
315
- ret[1] = acc[1];
316
- return true;
317
- } else
318
- return false;
319
- }))
320
- return ret;
321
-
322
- if (parseCommon.detect(accentDynamicPseudonyms, function(acc) {
323
- if (ret[1] === acc[0]) {
324
- ret[1] = acc[1];
325
- return true;
326
- } else
327
- return false;
328
- })) {
329
- if (multilineVars.dynamicPosition === 'hidden' )
330
- ret[1] = '';
331
- return ret;
332
- }
333
- // We didn't find the accent in the list, so consume the space, but don't return an accent.
334
- // Although it is possible that ! was used as a line break, so accept that.
335
- if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
336
- return [1, null ];
337
- warn("Unknown decoration: " + ret[1], line, i);
338
- ret[1] = "";
339
- return ret;
340
- case 'H':return [1, 'fermata'];
341
- case 'J':return [1, 'slide'];
342
- case 'L':return [1, 'accent'];
343
- case 'M':return [1, 'mordent'];
344
- case 'O':return[1, 'coda'];
345
- case 'P':return[1, 'pralltriller'];
346
- case 'R':return [1, 'roll'];
347
- case 'S':return [1, 'segno'];
348
- case 'T':return [1, 'trill'];
349
- }
350
- return [0, 0];
351
- };
352
-
353
- var letter_to_spacer = function(line, i)
354
- {
355
- var start = i;
356
- while (tokenizer.isWhiteSpace(line.charAt(i)))
357
- i++;
358
- return [ i-start ];
359
- };
360
-
361
- // returns the class of the bar line
362
- // the number of the repeat
363
- // and the number of characters used up
364
- // if 0 is returned, then the next element was not a bar line
365
- var letter_to_bar = function(line, curr_pos)
366
- {
367
- var ret = tokenizer.getBarLine(line, curr_pos);
368
- if (ret.len === 0)
369
- return [0,""];
370
- if (ret.warn) {
371
- warn(ret.warn, line, curr_pos);
372
- return [ret.len,""];
373
- }
374
-
375
- // Now see if this is a repeated ending
376
- // A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
377
- // It can also optionally start with '[', which is ignored.
378
- // Also, it can have white space before the '['.
379
- for (var ws = 0; ws < line.length; ws++)
380
- if (line.charAt(curr_pos + ret.len + ws) !== ' ')
381
- break;
382
- var orig_bar_len = ret.len;
383
- if (line.charAt(curr_pos+ret.len+ws) === '[') {
384
- ret.len += ws + 1;
385
- }
386
-
387
- // 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.
388
- if (line.charAt(curr_pos+ret.len) === '"' && line.charAt(curr_pos+ret.len-1) === '[') {
389
- var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
390
- return [ret.len+ending[0], ret.token, ending[1]];
391
- }
392
- var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
393
- if (retRep.len === 0 || retRep.token[0] === '-')
394
- return [orig_bar_len, ret.token];
395
-
396
- return [ret.len+retRep.len, ret.token, retRep.token];
397
- };
398
-
399
- var tripletQ = {
400
- 2: 3,
401
- 3: 2,
402
- 4: 3,
403
- 5: 2, // TODO-PER: not handling 6/8 rhythm yet
404
- 6: 2,
405
- 7: 2, // TODO-PER: not handling 6/8 rhythm yet
406
- 8: 3,
407
- 9: 2 // TODO-PER: not handling 6/8 rhythm yet
408
- };
409
- var letter_to_open_slurs_and_triplets = function(line, i) {
410
- // consume spaces, and look for all the open parens. If there is a number after the open paren,
411
- // that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
412
- var ret = {};
413
- var start = i;
414
- while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
415
- if (line.charAt(i) === '(') {
416
- if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
417
- if (ret.triplet !== undefined)
418
- warn("Can't nest triplets", line, i);
419
- else {
420
- ret.triplet = line.charAt(i+1) - '0';
421
- ret.tripletQ = tripletQ[ret.triplet];
422
- ret.num_notes = ret.triplet;
423
- if (i+2 < line.length && line.charAt(i+2) === ':') {
424
- // We are expecting "(p:q:r" or "(p:q" or "(p::r"
425
- // That is: "put p notes into the time of q for the next r notes"
426
- // if r is missing, then it is equal to p.
427
- // if q is missing, it is determined from this table:
428
- // (2 notes in the time of 3
429
- // (3 notes in the time of 2
430
- // (4 notes in the time of 3
431
- // (5 notes in the time of n | if time sig is (6/8, 9/8, 12/8), n=3, else n=2
432
- // (6 notes in the time of 2
433
- // (7 notes in the time of n
434
- // (8 notes in the time of 3
435
- // (9 notes in the time of n
436
- if (i+3 < line.length && line.charAt(i+3) === ':') {
437
- // The second number, 'q', is not present.
438
- if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
439
- ret.num_notes = line.charAt(i+4) - '0';
440
- i += 3;
441
- } else
442
- warn("expected number after the two colons after the triplet to mark the duration", line, i);
443
- } else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
444
- ret.tripletQ = line.charAt(i+3) - '0';
445
- if (i+4 < line.length && line.charAt(i+4) === ':') {
446
- if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
447
- ret.num_notes = line.charAt(i+5) - '0';
448
- i += 4;
449
- }
450
- } else {
451
- i += 2;
452
- }
453
- } else
454
- warn("expected number after the triplet to mark the duration", line, i);
455
- }
456
- }
457
- i++;
458
- }
459
- else {
460
- if (ret.startSlur === undefined)
461
- ret.startSlur = 1;
462
- else
463
- ret.startSlur++;
464
- }
465
- }
466
- i++;
209
+ var addWords = function(line, words) {
210
+ if (words.indexOf('\x12') >= 0) {
211
+ wordsContinuation += words
212
+ return
467
213
  }
468
- ret.consumed = i-start;
469
- return ret;
470
- };
214
+ words = wordsContinuation + words
215
+ wordsContinuation = ''
471
216
 
472
- var addWords = function(line, words) {
473
217
  if (!line) { warn("Can't add words before the first line of music", line, 0); return; }
474
218
  words = parseCommon.strip(words);
475
219
  if (words.charAt(words.length-1) !== '-')
@@ -554,6 +298,13 @@ var Parse = function() {
554
298
  };
555
299
 
556
300
  var addSymbols = function(line, words) {
301
+ if (words.indexOf('\x12') >= 0) {
302
+ symbolContinuation += words
303
+ return
304
+ }
305
+ words = symbolContinuation + words
306
+ symbolContinuation = ''
307
+
557
308
  // TODO-PER: Currently copied from w: line. This needs to be read as symbols instead.
558
309
  if (!line) { warn("Can't add symbols before the first line of music", line, 0); return; }
559
310
  words = parseCommon.strip(words);
@@ -630,958 +381,43 @@ var Parse = function() {
630
381
  });
631
382
  };
632
383
 
633
- var getBrokenRhythm = function(line, index) {
634
- switch (line.charAt(index)) {
635
- case '>':
636
- if (index < line.length - 1 && line.charAt(index+1) === '>') // double >>
637
- return [2, 1.75, 0.25];
638
- else
639
- return [1, 1.5, 0.5];
640
- break;
641
- case '<':
642
- if (index < line.length - 1 && line.charAt(index+1) === '<') // double <<
643
- return [2, 0.25, 1.75];
644
- else
645
- return [1, 0.5, 1.5];
646
- break;
647
- }
648
- return null;
649
- };
650
-
651
- // TODO-PER: make this a method in el.
652
- var addEndBeam = function(el) {
653
- if (el.duration !== undefined && el.duration < 0.25)
654
- el.end_beam = true;
655
- return el;
656
- };
657
-
658
- 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};
659
- var rests = {x: 'invisible', y: 'spacer', z: 'rest', Z: 'multimeasure' };
660
- var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
661
- //var el = { startChar: index };
662
- var isComplete = function(state) {
663
- return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
664
- };
665
- var state = 'startSlur';
666
- var durationSetByPreviousNote = false;
667
- while (1) {
668
- switch(line.charAt(index)) {
669
- case '(':
670
- if (state === 'startSlur') {
671
- if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
672
- } else if (isComplete(state)) {el.endChar = index;return el;}
673
- else return null;
674
- break;
675
- case ')':
676
- if (isComplete(state)) {
677
- if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
678
- } else return null;
679
- break;
680
- case '^':
681
- if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
682
- else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
683
- else if (isComplete(state)) {el.endChar = index;return el;}
684
- else return null;
685
- break;
686
- case '_':
687
- if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
688
- else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
689
- else if (isComplete(state)) {el.endChar = index;return el;}
690
- else return null;
691
- break;
692
- case '=':
693
- if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
694
- else if (isComplete(state)) {el.endChar = index;return el;}
695
- else return null;
696
- break;
697
- case 'A':
698
- case 'B':
699
- case 'C':
700
- case 'D':
701
- case 'E':
702
- case 'F':
703
- case 'G':
704
- case 'a':
705
- case 'b':
706
- case 'c':
707
- case 'd':
708
- case 'e':
709
- case 'f':
710
- case 'g':
711
- if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
712
- el.pitch = pitches[line.charAt(index)];
713
- transpose.note(multilineVars, el);
714
- state = 'octave';
715
- // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
716
- if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
717
- el.duration = multilineVars.default_length * multilineVars.next_note_duration;
718
- multilineVars.next_note_duration = 0;
719
- durationSetByPreviousNote = true;
720
- } else
721
- el.duration = multilineVars.default_length;
722
- // If the clef is percussion, there is probably some translation of the pitch to a particular drum kit item.
723
- if ((multilineVars.clef && multilineVars.clef.type === "perc") ||
724
- (multilineVars.currentVoice && multilineVars.currentVoice.clef === "perc")) {
725
- var key = line.charAt(index);
726
- if (el.accidental) {
727
- var accMap = { 'dblflat': '__', 'flat': '_', 'natural': '=', 'sharp': '^', 'dblsharp': '^^'};
728
- key = accMap[el.accidental] + key;
729
- }
730
- if (tune.formatting && tune.formatting.midi && tune.formatting.midi.drummap)
731
- el.midipitch = tune.formatting.midi.drummap[key];
732
- }
733
- } else if (isComplete(state)) {el.endChar = index;return el;}
734
- else return null;
735
- break;
736
- case ',':
737
- if (state === 'octave') {el.pitch -= 7;}
738
- else if (isComplete(state)) {el.endChar = index;return el;}
739
- else return null;
740
- break;
741
- case '\'':
742
- if (state === 'octave') {el.pitch += 7;}
743
- else if (isComplete(state)) {el.endChar = index;return el;}
744
- else return null;
745
- break;
746
- case 'x':
747
- case 'y':
748
- case 'z':
749
- case 'Z':
750
- if (state === 'startSlur') {
751
- el.rest = { type: rests[line.charAt(index)] };
752
- // There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
753
- // just nix them here.
754
- delete el.accidental;
755
- delete el.startSlur;
756
- delete el.startTie;
757
- delete el.endSlur;
758
- delete el.endTie;
759
- delete el.end_beam;
760
- delete el.grace_notes;
761
- // At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
762
- if (el.rest.type === 'multimeasure') {
763
- el.duration = 1;
764
- state = 'Zduration';
765
- } else {
766
- if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
767
- el.duration = multilineVars.default_length * multilineVars.next_note_duration;
768
- multilineVars.next_note_duration = 0;
769
- durationSetByPreviousNote = true;
770
- } else
771
- el.duration = multilineVars.default_length;
772
- state = 'duration';
773
- }
774
- } else if (isComplete(state)) {el.endChar = index;return el;}
775
- else return null;
776
- break;
777
- case '1':
778
- case '2':
779
- case '3':
780
- case '4':
781
- case '5':
782
- case '6':
783
- case '7':
784
- case '8':
785
- case '9':
786
- case '0':
787
- case '/':
788
- if (state === 'octave' || state === 'duration') {
789
- var fraction = tokenizer.getFraction(line, index);
790
- //if (!durationSetByPreviousNote)
791
- el.duration = el.duration * fraction.value;
792
- // TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
793
- el.endChar = fraction.index;
794
- while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
795
- if (line.charAt(fraction.index) === '-')
796
- el.startTie = {};
797
- else
798
- el = addEndBeam(el);
799
- fraction.index++;
800
- }
801
- index = fraction.index-1;
802
- state = 'broken_rhythm';
803
- } else if (state === 'sharp2') {
804
- el.accidental = 'quartersharp';state = 'pitch';
805
- } else if (state === 'flat2') {
806
- el.accidental = 'quarterflat';state = 'pitch';
807
- } else if (state === 'Zduration') {
808
- var num = tokenizer.getNumber(line, index);
809
- el.duration = num.num;
810
- el.endChar = num.index;
811
- return el;
812
- } else return null;
813
- break;
814
- case '-':
815
- if (state === 'startSlur') {
816
- // This is the first character, so it must have been meant for the previous note. Correct that here.
817
- tune.addTieToLastNote();
818
- el.endTie = true;
819
- } else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
820
- el.startTie = {};
821
- if (!durationSetByPreviousNote && canHaveBrokenRhythm)
822
- state = 'broken_rhythm';
823
- else {
824
- // Peek ahead to the next character. If it is a space, then we have an end beam.
825
- if (tokenizer.isWhiteSpace(line.charAt(index + 1)))
826
- addEndBeam(el);
827
- el.endChar = index+1;
828
- return el;
829
- }
830
- } else if (state === 'broken_rhythm') {el.endChar = index;return el;}
831
- else return null;
832
- break;
833
- case ' ':
834
- case '\t':
835
- if (isComplete(state)) {
836
- el.end_beam = true;
837
- // look ahead to see if there is a tie
838
- do {
839
- if (line.charAt(index) === '-')
840
- el.startTie = {};
841
- index++;
842
- } while (index < line.length && (tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-'));
843
- el.endChar = index;
844
- 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.
845
- index--;
846
- state = 'broken_rhythm';
847
- } else
848
- return el;
849
- }
850
- else return null;
851
- break;
852
- case '>':
853
- case '<':
854
- if (isComplete(state)) {
855
- if (canHaveBrokenRhythm) {
856
- var br2 = getBrokenRhythm(line, index);
857
- index += br2[0] - 1; // index gets incremented below, so we'll let that happen
858
- multilineVars.next_note_duration = br2[2];
859
- el.duration = br2[1]*el.duration;
860
- state = 'end_slur';
861
- } else {
862
- el.endChar = index;
863
- return el;
864
- }
865
- } else
866
- return null;
867
- break;
868
- default:
869
- if (isComplete(state)) {
870
- el.endChar = index;
871
- return el;
872
- }
873
- return null;
874
- }
875
- index++;
876
- if (index === line.length) {
877
- if (isComplete(state)) {el.endChar = index;return el;}
878
- else return null;
879
- }
880
- }
881
- return null;
882
- };
883
-
884
- function startNewLine() {
885
- var params = { startChar: -1, endChar: -1};
886
- if (multilineVars.partForNextLine.title)
887
- params.part = multilineVars.partForNextLine;
888
- params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? parseCommon.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : parseCommon.clone(multilineVars.clef);
889
- var scoreTranspose = multilineVars.currentVoice ? multilineVars.currentVoice.scoreTranspose : 0;
890
- params.key = parseKeyVoice.standardKey(multilineVars.key.root+multilineVars.key.acc+multilineVars.key.mode, multilineVars.key.root, multilineVars.key.acc, scoreTranspose);
891
- params.key.mode = multilineVars.key.mode;
892
- if (multilineVars.key.impliedNaturals)
893
- params.key.impliedNaturals = multilineVars.key.impliedNaturals;
894
- if (multilineVars.key.explicitAccidentals) {
895
- for (var i = 0; i < multilineVars.key.explicitAccidentals.length; i++) {
896
- var found = false;
897
- for (var j = 0; j < params.key.accidentals.length; j++) {
898
- if (params.key.accidentals[j].note === multilineVars.key.explicitAccidentals[i].note) {
899
- // If the note is already in the list, override it with the new value
900
- params.key.accidentals[j].acc = multilineVars.key.explicitAccidentals[i].acc;
901
- found = true;
902
- }
903
- }
904
- if (!found)
905
- params.key.accidentals.push(multilineVars.key.explicitAccidentals[i]);
906
- }
907
- }
908
- multilineVars.targetKey = params.key;
909
- if (params.key.explicitAccidentals)
910
- delete params.key.explicitAccidentals;
911
- parseKeyVoice.addPosToKey(params.clef, params.key);
912
- if (multilineVars.meter !== null) {
913
- if (multilineVars.currentVoice) {
914
- parseCommon.each(multilineVars.staves, function(st) {
915
- st.meter = multilineVars.meter;
916
- });
917
- params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
918
- multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
919
- } else
920
- params.meter = multilineVars.meter;
921
- multilineVars.meter = null;
922
- } else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
923
- // Make sure that each voice gets the meter marking.
924
- params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
925
- multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
926
- }
927
- if (multilineVars.currentVoice && multilineVars.currentVoice.name)
928
- params.name = multilineVars.currentVoice.name;
929
- if (multilineVars.vocalfont)
930
- params.vocalfont = multilineVars.vocalfont;
931
- if (multilineVars.tripletfont)
932
- params.tripletfont = multilineVars.tripletfont;
933
- if (multilineVars.style)
934
- params.style = multilineVars.style;
935
- if (multilineVars.currentVoice) {
936
- var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
937
- if (staff.brace) params.brace = staff.brace;
938
- if (staff.bracket) params.bracket = staff.bracket;
939
- if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
940
- if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
941
- if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
942
- if (multilineVars.currentVoice.stem)
943
- params.stem = multilineVars.currentVoice.stem;
944
- if (multilineVars.currentVoice.stafflines)
945
- params.stafflines = multilineVars.currentVoice.stafflines;
946
- if (multilineVars.currentVoice.staffscale)
947
- params.staffscale = multilineVars.currentVoice.staffscale;
948
- if (multilineVars.currentVoice.scale)
949
- params.scale = multilineVars.currentVoice.scale;
950
- if (multilineVars.currentVoice.style)
951
- params.style = multilineVars.currentVoice.style;
952
- if (multilineVars.currentVoice.transpose)
953
- params.clef.transpose = multilineVars.currentVoice.transpose;
954
- }
955
- var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
956
- if (multilineVars.barNumbers === 0 && isFirstVoice && multilineVars.currBarNumber !== 1)
957
- params.barNumber = multilineVars.currBarNumber;
958
- tune.startNewLine(params);
959
- if (multilineVars.key.impliedNaturals)
960
- delete multilineVars.key.impliedNaturals;
961
-
962
- multilineVars.partForNextLine = {};
963
- }
964
-
965
- var letter_to_grace = function(line, i) {
966
- // Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
967
- if (line.charAt(i) === '{') {
968
- // fetch the gracenotes string and consume that into the array
969
- var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
970
- if (!gra[2])
971
- warn("Missing the closing '}' while parsing grace note", line, i);
972
- // If there is a slur after the grace construction, then move it to the last note inside the grace construction
973
- if (line[i+gra[0]] === ')') {
974
- gra[0]++;
975
- gra[1] += ')';
976
- }
977
-
978
- var gracenotes = [];
979
- var ii = 0;
980
- var inTie = false;
981
- while (ii < gra[1].length) {
982
- var acciaccatura = false;
983
- if (gra[1].charAt(ii) === '/') {
984
- acciaccatura = true;
985
- ii++;
986
- }
987
- var note = getCoreNote(gra[1], ii, {}, false);
988
- if (note !== null) {
989
- // 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.
990
- note.duration = note.duration / (multilineVars.default_length * 8);
991
- if (acciaccatura)
992
- note.acciaccatura = true;
993
- gracenotes.push(note);
994
-
995
- if (inTie) {
996
- note.endTie = true;
997
- inTie = false;
998
- }
999
- if (note.startTie)
1000
- inTie = true;
1001
-
1002
- ii = note.endChar;
1003
- delete note.endChar;
1004
- }
1005
- else {
1006
- // We shouldn't get anything but notes or a space here, so report an error
1007
- if (gra[1].charAt(ii) === ' ') {
1008
- if (gracenotes.length > 0)
1009
- gracenotes[gracenotes.length-1].end_beam = true;
1010
- } else
1011
- warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
1012
- ii++;
1013
- }
1014
- }
1015
- if (gracenotes.length)
1016
- return [gra[0], gracenotes];
1017
- }
1018
- return [ 0 ];
1019
- };
1020
-
1021
- function letter_to_overlay(line, i) {
1022
- if (line.charAt(i) === '&') {
1023
- var start = i;
1024
- while (line.charAt(i) && line.charAt(i) !== ':' && line.charAt(i) !== '|')
1025
- i++;
1026
- return [ i-start, line.substring(start+1, i) ];
384
+ var parseLine = function(line) {
385
+ if (parseCommon.startsWith(line, '%%')) {
386
+ var err = parseDirective.addDirective(line.substring(2));
387
+ if (err) warn(err, line, 2);
388
+ return;
1027
389
  }
1028
- return [ 0 ];
1029
- }
1030
-
1031
- function durationOfMeasure(multilineVars) {
1032
- // TODO-PER: This could be more complicated if one of the unusual measures is used.
1033
- var meter = multilineVars.origMeter;
1034
- if (!meter || meter.type !== 'specified')
1035
- return 1;
1036
- if (!meter.value || meter.value.length === 0)
1037
- return 1;
1038
- return parseInt(meter.value[0].num, 10) / parseInt(meter.value[0].den, 10);
1039
- }
1040
-
1041
- //
1042
- // Parse line of music
1043
- //
1044
- // This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
1045
- // core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
1046
- // chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
1047
- // grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
1048
- // note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
1049
- // bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
1050
- // 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
1051
- // 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.
1052
- // a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
1053
-
1054
- // Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
1055
- // Then, all leading and trailing spaces are ignored.
1056
- // If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
1057
- // of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
1058
390
 
1059
- // TODO-PER: How to handle ! for line break?
1060
- // TODO-PER: dots before bar, dots before slur
1061
- // TODO-PER: U: redefinable symbols.
391
+ var i = line.indexOf('%');
392
+ if (i >= 0)
393
+ line = line.substring(0, i);
394
+ line = line.replace(/\s+$/, '');
1062
395
 
1063
- // Ambiguous symbols:
1064
- // "[" can be the start of a chord, the start of a header element or part of a bar line.
1065
- // --- if it is immediately followed by "|", it is a bar line
1066
- // --- 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.)
1067
- // --- otherwise it is the beginning of a chord
1068
- // "(" can be the start of a slur or a triplet
1069
- // --- if it is followed by a number from 2-9, then it is a triplet
1070
- // --- otherwise it is a slur
1071
- // "]"
1072
- // --- if there is a chord open, then this is the close
1073
- // --- if it is after a [|, then it is an invisible bar line
1074
- // --- otherwise, it is par of a bar
1075
- // "." can be a bar modifier or a slur modifier, or a decoration
1076
- // --- if it comes immediately before a bar, it is a bar modifier
1077
- // --- if it comes immediately before a slur, it is a slur modifier
1078
- // --- otherwise it is a decoration for the next note.
1079
- // number:
1080
- // --- if it is after a bar, with no space, it is an ending marker
1081
- // --- if it is after a ( with no space, it is a triplet count
1082
- // --- if it is after a pitch or octave or slash, then it is a duration
1083
-
1084
- // Unambiguous symbols (except inside quoted strings):
1085
- // vertical-bar, colon: part of a bar
1086
- // ABCDEFGabcdefg: pitch
1087
- // xyzZ: rest
1088
- // comma, prime: octave
1089
- // close-paren: end-slur
1090
- // hyphen: tie
1091
- // tilde, v, u, bang, plus, THLMPSO: decoration
1092
- // carat, underscore, equal: accidental
1093
- // ampersand: time reset
1094
- // open-curly, close-curly: grace notes
1095
- // double-quote: chord symbol
1096
- // less-than, greater-than, slash: duration
1097
- // back-tick, space, tab: space
1098
- var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
1099
-
1100
- var parseRegularMusicLine = function(line) {
1101
- header.resolveTempo();
1102
- //multilineVars.havent_set_length = false; // To late to set this now.
1103
- 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.
1104
- var i = 0;
1105
- var startOfLine = multilineVars.iChar;
1106
- // 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 %
1107
- while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
1108
- i++;
1109
- if (i === line.length || line.charAt(i) === '%')
396
+ if (line.length === 0)
1110
397
  return;
1111
398
 
1112
- // Start with the standard staff, clef and key symbols on each line
1113
- var delayStartNewLine = multilineVars.start_new_line;
1114
- if (multilineVars.continueall === undefined)
1115
- multilineVars.start_new_line = true;
1116
- else
1117
- multilineVars.start_new_line = false;
1118
- var tripletNotesLeft = 0;
1119
-
1120
- // See if the line starts with a header field
1121
- var retHeader = header.letter_to_body_header(line, i);
1122
- if (retHeader[0] > 0) {
1123
- i += retHeader[0];
1124
- if (retHeader[1] === 'V')
1125
- delayStartNewLine = true; // fixes bug on this: c[V:2]d
1126
- // TODO-PER: Handle inline headers
399
+ if (wordsContinuation) {
400
+ addWords(tuneBuilder.getCurrentVoice(), line.substring(2));
401
+ return
1127
402
  }
1128
- var el = { };
1129
-
1130
- var overlayLevel = 0;
1131
- while (i < line.length)
1132
- {
1133
- var startI = i;
1134
- if (line.charAt(i) === '%')
1135
- break;
1136
-
1137
- var retInlineHeader = header.letter_to_inline_header(line, i);
1138
- if (retInlineHeader[0] > 0) {
1139
- i += retInlineHeader[0];
1140
- if (retInlineHeader[1] === 'V')
1141
- delayStartNewLine = true; // fixes bug on this: c[V:2]d
1142
- // TODO-PER: Handle inline headers
1143
- //multilineVars.start_new_line = false;
1144
- } else {
1145
- // Wait until here to actually start the line because we know we're past the inline statements.
1146
- if (delayStartNewLine) {
1147
- startNewLine();
1148
- delayStartNewLine = false;
1149
- }
1150
-
1151
- // We need to decide if the following characters are a bar-marking or a note-group.
1152
- // Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
1153
- // If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
1154
- // If there is a bar marker, it is definitely a bar-marking.
1155
- // If there is either a core-note or chord, it is definitely a note-group.
1156
- // 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]
1157
- // Then, if there is a grace-note, we know where to go.
1158
- // Else see if we have a chord, core-note, slur, triplet, or bar.
1159
-
1160
- var ret;
1161
- while (1) {
1162
- ret = tokenizer.eatWhiteSpace(line, i);
1163
- if (ret > 0) {
1164
- i += ret;
1165
- }
1166
- if (i > 0 && line.charAt(i-1) === '\x12') {
1167
- // 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.
1168
- ret = header.letter_to_body_header(line, i);
1169
- if (ret[0] > 0) {
1170
- if (ret[1] === 'V')
1171
- startNewLine(); // fixes bug on this: c\\nV:2]\\nd
1172
- // TODO: insert header here
1173
- i = ret[0];
1174
- multilineVars.start_new_line = false;
1175
- }
1176
- }
1177
- // gather all the grace notes, chord symbols and decorations
1178
- ret = letter_to_spacer(line, i);
1179
- if (ret[0] > 0) {
1180
- i += ret[0];
1181
- }
1182
-
1183
- ret = letter_to_chord(line, i);
1184
- if (ret[0] > 0) {
1185
- // There could be more than one chord here if they have different positions.
1186
- // If two chords have the same position, then connect them with newline.
1187
- if (!el.chord)
1188
- el.chord = [];
1189
- var chordName = tokenizer.translateString(ret[1]);
1190
- chordName = chordName.replace(/;/g, "\n");
1191
- var addedChord = false;
1192
- for (var ci = 0; ci < el.chord.length; ci++) {
1193
- if (el.chord[ci].position === ret[2]) {
1194
- addedChord = true;
1195
- el.chord[ci].name += "\n" + chordName;
1196
- }
1197
- }
1198
- if (addedChord === false) {
1199
- if (ret[2] === null && ret[3])
1200
- el.chord.push({name: chordName, rel_position: ret[3]});
1201
- else
1202
- el.chord.push({name: chordName, position: ret[2]});
1203
- }
1204
-
1205
- i += ret[0];
1206
- var ii = tokenizer.skipWhiteSpace(line.substring(i));
1207
- if (ii > 0)
1208
- el.force_end_beam_last = true;
1209
- i += ii;
1210
- } else {
1211
- if (nonDecorations.indexOf(line.charAt(i)) === -1)
1212
- ret = letter_to_accent(line, i);
1213
- else ret = [ 0 ];
1214
- if (ret[0] > 0) {
1215
- if (ret[1] === null) {
1216
- if (i + 1 < line.length)
1217
- startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
1218
- } else if (ret[1].length > 0) {
1219
- if (ret[1].indexOf("style=") === 0) {
1220
- el.style = ret[1].substr(6);
1221
- } else {
1222
- if (el.decoration === undefined)
1223
- el.decoration = [];
1224
- el.decoration.push(ret[1]);
1225
- }
1226
- }
1227
- i += ret[0];
1228
- } else {
1229
- ret = letter_to_grace(line, i);
1230
- // TODO-PER: Be sure there aren't already grace notes defined. That is an error.
1231
- if (ret[0] > 0) {
1232
- el.gracenotes = ret[1];
1233
- i += ret[0];
1234
- } else
1235
- break;
1236
- }
1237
- }
1238
- }
1239
-
1240
- ret = letter_to_bar(line, i);
1241
- if (ret[0] > 0) {
1242
- // This is definitely a bar
1243
- overlayLevel = 0;
1244
- if (el.gracenotes !== undefined) {
1245
- // Attach the grace note to an invisible note
1246
- el.rest = { type: 'spacer' };
1247
- el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
1248
- multilineVars.addFormattingOptions(el, tune.formatting, 'note');
1249
- tune.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
1250
- multilineVars.measureNotEmpty = true;
1251
- el = {};
1252
- }
1253
- var bar = {type: ret[1]};
1254
- if (bar.type.length === 0)
1255
- warn("Unknown bar type", line, i);
1256
- else {
1257
- if (multilineVars.inEnding && bar.type !== 'bar_thin') {
1258
- bar.endEnding = true;
1259
- multilineVars.inEnding = false;
1260
- }
1261
- if (ret[2]) {
1262
- bar.startEnding = ret[2];
1263
- if (multilineVars.inEnding)
1264
- bar.endEnding = true;
1265
- multilineVars.inEnding = true;
1266
- }
1267
- if (el.decoration !== undefined)
1268
- bar.decoration = el.decoration;
1269
- if (el.chord !== undefined)
1270
- bar.chord = el.chord;
1271
- if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
1272
- multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
1273
- else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
1274
- multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
1275
- else if (bar.endEnding)
1276
- multilineVars.barFirstEndingNum = undefined;
1277
- if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
1278
- var isFirstVoice = multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === 0 && multilineVars.currentVoice.index === 0);
1279
- if (isFirstVoice) {
1280
- multilineVars.currBarNumber++;
1281
- if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
1282
- bar.barNumber = multilineVars.currBarNumber;
1283
- }
1284
- }
1285
- multilineVars.addFormattingOptions(el, tune.formatting, 'bar');
1286
- tune.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
1287
- multilineVars.measureNotEmpty = false;
1288
- el = {};
1289
- }
1290
- i += ret[0];
1291
- var cv = multilineVars.currentVoice ? multilineVars.currentVoice.staffNum + '-' + multilineVars.currentVoice.index : 'ONLY';
1292
- // if (multilineVars.lineBreaks) {
1293
- // if (!multilineVars.barCounter[cv])
1294
- // multilineVars.barCounter[cv] = 0;
1295
- // var breakNow = multilineVars.lineBreaks[''+multilineVars.barCounter[cv]];
1296
- // multilineVars.barCounter[cv]++;
1297
- // if (breakNow)
1298
- // startNewLine();
1299
- // }
1300
- } else if (line[i] === '&') { // backtrack to beginning of measure
1301
- ret = letter_to_overlay(line, i);
1302
- if (ret[0] > 0) {
1303
- tune.appendElement('overlay', startOfLine, startOfLine+1, {});
1304
- i += 1;
1305
- overlayLevel++;
1306
- }
1307
-
1308
- } else {
1309
- // This is definitely a note group
1310
- //
1311
- // Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
1312
- ret = letter_to_open_slurs_and_triplets(line, i);
1313
- if (ret.consumed > 0) {
1314
- if (ret.startSlur !== undefined)
1315
- el.startSlur = ret.startSlur;
1316
- if (ret.triplet !== undefined) {
1317
- if (tripletNotesLeft > 0)
1318
- warn("Can't nest triplets", line, i);
1319
- else {
1320
- el.startTriplet = ret.triplet;
1321
- el.tripletMultiplier = ret.tripletQ / ret.triplet;
1322
- tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
1323
- }
1324
- }
1325
- i += ret.consumed;
1326
- }
1327
-
1328
- // handle chords.
1329
- if (line.charAt(i) === '[') {
1330
- var chordStartChar = i;
1331
- i++;
1332
- var chordDuration = null;
1333
- var rememberEndBeam = false;
1334
-
1335
- var done = false;
1336
- while (!done) {
1337
- var accent = letter_to_accent(line, i);
1338
- if (accent[0] > 0) {
1339
- i += accent[0];
1340
- }
1341
-
1342
- var chordNote = getCoreNote(line, i, {}, false);
1343
- if (chordNote !== null) {
1344
- if (accent[0] > 0) { // If we found a decoration above, it modifies the entire chord. "style" is handled below.
1345
- if (accent[1].indexOf("style=") !== 0) {
1346
- if (el.decoration === undefined)
1347
- el.decoration = [];
1348
- el.decoration.push(accent[1]);
1349
- }
1350
- }
1351
- if (chordNote.end_beam) {
1352
- el.end_beam = true;
1353
- delete chordNote.end_beam;
1354
- }
1355
- if (el.pitches === undefined) {
1356
- el.duration = chordNote.duration;
1357
- el.pitches = [ chordNote ];
1358
- } else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
1359
- el.pitches.push(chordNote);
1360
- delete chordNote.duration;
1361
- if (accent[0] > 0) { // If we found a style above, it modifies the individual pitch, not the entire chord.
1362
- if (accent[1].indexOf("style=") === 0) {
1363
- el.pitches[el.pitches.length-1].style = accent[1].substr(6);
1364
- }
1365
- }
1366
-
1367
- if (multilineVars.inTieChord[el.pitches.length]) {
1368
- chordNote.endTie = true;
1369
- multilineVars.inTieChord[el.pitches.length] = undefined;
1370
- }
1371
- if (chordNote.startTie)
1372
- multilineVars.inTieChord[el.pitches.length] = true;
1373
-
1374
- i = chordNote.endChar;
1375
- delete chordNote.endChar;
1376
- } else if (line.charAt(i) === ' ') {
1377
- // Spaces are not allowed in chords, but we can recover from it by ignoring it.
1378
- warn("Spaces are not allowed in chords", line, i);
1379
- i++;
1380
- } else {
1381
- if (i < line.length && line.charAt(i) === ']') {
1382
- // consume the close bracket
1383
- i++;
1384
-
1385
- if (multilineVars.next_note_duration !== 0) {
1386
- el.duration = el.duration * multilineVars.next_note_duration;
1387
- multilineVars.next_note_duration = 0;
1388
- }
1389
-
1390
- if (isInTie(multilineVars, overlayLevel, el)) {
1391
- parseCommon.each(el.pitches, function(pitch) { pitch.endTie = true; });
1392
- setIsInTie(multilineVars, overlayLevel, false);
1393
- }
1394
-
1395
- if (tripletNotesLeft > 0) {
1396
- tripletNotesLeft--;
1397
- if (tripletNotesLeft === 0) {
1398
- el.endTriplet = true;
1399
- }
1400
- }
1401
-
1402
- var postChordDone = false;
1403
- while (i < line.length && !postChordDone) {
1404
- switch (line.charAt(i)) {
1405
- case ' ':
1406
- case '\t':
1407
- addEndBeam(el);
1408
- break;
1409
- case ')':
1410
- if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
1411
- break;
1412
- case '-':
1413
- parseCommon.each(el.pitches, function(pitch) { pitch.startTie = {}; });
1414
- setIsInTie(multilineVars, overlayLevel, true);
1415
- break;
1416
- case '>':
1417
- case '<':
1418
- var br2 = getBrokenRhythm(line, i);
1419
- i += br2[0] - 1; // index gets incremented below, so we'll let that happen
1420
- multilineVars.next_note_duration = br2[2];
1421
- if (chordDuration)
1422
- chordDuration = chordDuration * br2[1];
1423
- else
1424
- chordDuration = br2[1];
1425
- break;
1426
- case '1':
1427
- case '2':
1428
- case '3':
1429
- case '4':
1430
- case '5':
1431
- case '6':
1432
- case '7':
1433
- case '8':
1434
- case '9':
1435
- case '/':
1436
- var fraction = tokenizer.getFraction(line, i);
1437
- chordDuration = fraction.value;
1438
- i = fraction.index;
1439
- if (line.charAt(i) === ' ')
1440
- rememberEndBeam = true;
1441
- if (line.charAt(i) === '-' || line.charAt(i) === ')' || line.charAt(i) === ' ' || line.charAt(i) === '<' || line.charAt(i) === '>')
1442
- i--; // Subtracting one because one is automatically added below
1443
- else
1444
- postChordDone = true;
1445
- break;
1446
- default:
1447
- postChordDone = true;
1448
- break;
1449
- }
1450
- if (!postChordDone) {
1451
- i++;
1452
- }
1453
- }
1454
- } else
1455
- warn("Expected ']' to end the chords", line, i);
1456
-
1457
- if (el.pitches !== undefined) {
1458
- if (chordDuration !== null) {
1459
- el.duration = el.duration * chordDuration;
1460
- if (rememberEndBeam)
1461
- addEndBeam(el);
1462
- }
1463
-
1464
- multilineVars.addFormattingOptions(el, tune.formatting, 'note');
1465
- tune.appendElement('note', startOfLine+chordStartChar, startOfLine+i, el);
1466
- multilineVars.measureNotEmpty = true;
1467
- el = {};
1468
- }
1469
- done = true;
1470
- }
1471
- }
1472
-
1473
- } else {
1474
- // Single pitch
1475
- var el2 = {};
1476
- var core = getCoreNote(line, i, el2, true);
1477
- if (el2.endTie !== undefined) setIsInTie(multilineVars, overlayLevel, true);
1478
- if (core !== null) {
1479
- if (core.pitch !== undefined) {
1480
- el.pitches = [ { } ];
1481
- // TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
1482
- if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
1483
- el.pitches[0].pitch = core.pitch;
1484
- if (core.midipitch)
1485
- el.pitches[0].midipitch = core.midipitch;
1486
- if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
1487
- if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
1488
- if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
1489
- if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
1490
- if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
1491
- if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
1492
- } else {
1493
- el.rest = core.rest;
1494
- if (core.endSlur !== undefined) el.endSlur = core.endSlur;
1495
- if (core.endTie !== undefined) el.rest.endTie = core.endTie;
1496
- if (core.startSlur !== undefined) el.startSlur = core.startSlur;
1497
- if (core.startTie !== undefined) el.rest.startTie = core.startTie;
1498
- if (el.startTie !== undefined) el.rest.startTie = el.startTie;
1499
- }
1500
-
1501
- if (core.chord !== undefined) el.chord = core.chord;
1502
- if (core.duration !== undefined) el.duration = core.duration;
1503
- if (core.decoration !== undefined) el.decoration = core.decoration;
1504
- if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
1505
- delete el.startSlur;
1506
- if (isInTie(multilineVars, overlayLevel, el)) {
1507
- if (el.pitches !== undefined) {
1508
- el.pitches[0].endTie = true;
1509
- } else if (el.rest.type !== 'spacer') {
1510
- el.rest.endTie = true;
1511
- }
1512
- setIsInTie(multilineVars, overlayLevel, false);
1513
- }
1514
- if (core.startTie || el.startTie)
1515
- setIsInTie(multilineVars, overlayLevel, true);
1516
- i = core.endChar;
1517
-
1518
- if (tripletNotesLeft > 0) {
1519
- tripletNotesLeft--;
1520
- if (tripletNotesLeft === 0) {
1521
- el.endTriplet = true;
1522
- }
1523
- }
1524
-
1525
- if (core.end_beam)
1526
- addEndBeam(el);
1527
-
1528
- // 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.
1529
- // If the time signature length is greater than 4/4, though, then a whole rest has no special treatment.
1530
- if (el.rest && el.rest.type === 'rest' && el.duration === 1 && durationOfMeasure(multilineVars) <= 1) {
1531
- el.rest.type = 'whole';
1532
-
1533
- el.duration = durationOfMeasure(multilineVars);
1534
- }
1535
-
1536
- multilineVars.addFormattingOptions(el, tune.formatting, 'note');
1537
- tune.appendElement('note', startOfLine+startI, startOfLine+i, el);
1538
- multilineVars.measureNotEmpty = true;
1539
- el = {};
1540
- }
1541
- }
1542
-
1543
- if (i === startI) { // don't know what this is, so ignore it.
1544
- if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
1545
- warn("Unknown character ignored", line, i);
1546
- i++;
1547
- }
1548
- }
1549
- }
403
+ if (symbolContinuation) {
404
+ addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2));
405
+ return
1550
406
  }
1551
- };
1552
-
1553
- var isInTie = function(multilineVars, overlayLevel, el) {
1554
- if (multilineVars.inTie[overlayLevel] === undefined)
1555
- return false;
1556
- // If this is single voice music then the voice index isn't set, so we use the first voice.
1557
- var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
1558
- if (multilineVars.inTie[overlayLevel][voiceIndex]) {
1559
- if (el.pitches !== undefined || el.rest.type !== 'spacer')
1560
- return true;
407
+ if (line.length < 2 || line.charAt(1) !== ':' || music.lineContinuation) {
408
+ music.parseMusic(line);
409
+ return
1561
410
  }
1562
- return false;
1563
- };
1564
411
 
1565
- var setIsInTie =function(multilineVars, overlayLevel, value) {
1566
- // If this is single voice music then the voice index isn't set, so we use the first voice.
1567
- var voiceIndex = multilineVars.currentVoice ? multilineVars.currentVoice.index : 0;
1568
- if (multilineVars.inTie[overlayLevel] === undefined)
1569
- multilineVars.inTie[overlayLevel] = [];
1570
- multilineVars.inTie[overlayLevel][voiceIndex] = value;
1571
- };
1572
-
1573
- var parseLine = function(line) {
1574
412
  var ret = header.parseHeader(line);
1575
413
  if (ret.regular)
1576
- parseRegularMusicLine(ret.str);
414
+ music.parseMusic(line);
1577
415
  if (ret.newline)
1578
- startNewLine();
416
+ music.startNewLine();
1579
417
  if (ret.words)
1580
- addWords(tune.getCurrentVoice(), line.substring(2));
418
+ addWords(tuneBuilder.getCurrentVoice(), line.substring(2));
1581
419
  if (ret.symbols)
1582
- addSymbols(tune.getCurrentVoice(), line.substring(2));
1583
- if (ret.recurse)
1584
- parseLine(ret.str);
420
+ addSymbols(tuneBuilder.getCurrentVoice(), line.substring(2));
1585
421
  };
1586
422
 
1587
423
  function appendLastMeasure(voice, nextVoice) {
@@ -1639,6 +475,34 @@ var Parse = function() {
1639
475
  if (!switches) switches = {};
1640
476
  if (!startPos) startPos = 0;
1641
477
  tune.reset();
478
+
479
+ // Take care of whatever line endings come our way
480
+ // Tack on newline temporarily to make the last line continuation work
481
+ strTune = strTune.replace(/\r\n?/g, '\n') + '\n';
482
+
483
+ // get rid of latex commands. If a line starts with a backslash, then it is replaced by spaces to keep the character count the same.
484
+ var arr = strTune.split("\n\\");
485
+ if (arr.length > 1) {
486
+ for (var i2 = 1; i2 < arr.length; i2++) {
487
+ while (arr[i2].length > 0 && arr[i2][0] !== "\n") {
488
+ arr[i2] = arr[i2].substr(1);
489
+ arr[i2-1] += ' ';
490
+ }
491
+ }
492
+ strTune = arr.join(" "); //. the split removed two characters, so this puts them back
493
+ }
494
+ // take care of line continuations right away, but keep the same number of characters
495
+ strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, function(all, backslash, comment){
496
+ var padding = comment ? Array(comment.length +1).join(' ') : "";
497
+ return backslash + "\x12" + padding + '\n';
498
+ });
499
+ var lines = strTune.split('\n')
500
+ if (parseCommon.last(lines).length === 0) // remove the blank line we added above.
501
+ lines.pop();
502
+ tokenizer = new Tokenizer(lines, multilineVars);
503
+ header = new ParseHeader(tokenizer, warn, multilineVars, tune, tuneBuilder);
504
+ music = new ParseMusic(tokenizer, warn, multilineVars, tune, tuneBuilder, header);
505
+
1642
506
  if (switches.print)
1643
507
  tune.media = 'print';
1644
508
  multilineVars.reset();
@@ -1647,92 +511,74 @@ var Parse = function() {
1647
511
  multilineVars.globalTranspose = parseInt(switches.visualTranspose);
1648
512
  if (multilineVars.globalTranspose === 0)
1649
513
  multilineVars.globalTranspose = undefined;
514
+ else
515
+ tuneBuilder.setVisualTranspose(switches.visualTranspose);
1650
516
  } else
1651
517
  multilineVars.globalTranspose = undefined;
1652
518
  if (switches.lineBreaks) {
1653
- // change the format of the the line breaks for easy testing.
1654
519
  // The line break numbers are 0-based and they reflect the last measure of the current line.
1655
- multilineVars.lineBreaks = {};
520
+ multilineVars.lineBreaks = switches.lineBreaks;
1656
521
  //multilineVars.continueall = true;
1657
- for (var i = 0; i < switches.lineBreaks.length; i++)
1658
- multilineVars.lineBreaks[''+(switches.lineBreaks[i]+1)] = true; // Add 1 so that the line break is the first measure of the next line.
1659
522
  }
1660
523
  header.reset(tokenizer, warn, multilineVars, tune);
1661
524
 
1662
- // Take care of whatever line endings come our way
1663
- strTune = parseCommon.gsub(strTune, '\r\n', '\n');
1664
- strTune = parseCommon.gsub(strTune, '\r', '\n');
1665
- strTune += '\n'; // Tacked on temporarily to make the last line continuation work
1666
- strTune = strTune.replace(/\n\\.*\n/g, "\n"); // get rid of latex commands.
1667
- var continuationReplacement = function(all, backslash, comment){
1668
- var spaces = " ";
1669
- var padding = comment ? spaces.substring(0, comment.length) : "";
1670
- return backslash + " \x12" + padding;
1671
- };
1672
- strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, continuationReplacement); // take care of line continuations right away, but keep the same number of characters
1673
- var lines = strTune.split('\n');
1674
- if (parseCommon.last(lines).length === 0) // remove the blank line we added above.
1675
- lines.pop();
1676
525
  try {
1677
526
  if (switches.format) {
1678
527
  parseDirective.globalFormatting(switches.format);
1679
528
  }
1680
- parseCommon.each(lines, function(line) {
529
+ var line = tokenizer.nextLine();
530
+ while (line) {
1681
531
  if (switches.header_only && multilineVars.is_in_header === false)
1682
532
  throw "normal_abort";
1683
533
  if (switches.stop_on_warning && multilineVars.warnings)
1684
534
  throw "normal_abort";
1685
- if (multilineVars.is_in_history) {
1686
- if (line.charAt(1) === ':') {
1687
- multilineVars.is_in_history = false;
1688
- parseLine(line);
1689
- } else
1690
- tune.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)));
1691
- } else if (multilineVars.inTextBlock) {
1692
- if (parseCommon.startsWith(line, "%%endtext")) {
1693
- //tune.addMetaText("textBlock", multilineVars.textBlock);
1694
- tune.addText(multilineVars.textBlock);
1695
- multilineVars.inTextBlock = false;
1696
- }
1697
- else {
1698
- if (parseCommon.startsWith(line, "%%"))
1699
- multilineVars.textBlock += ' ' + line.substring(2);
1700
- else
1701
- multilineVars.textBlock += ' ' + line;
1702
- }
1703
- } else if (multilineVars.inPsBlock) {
1704
- if (parseCommon.startsWith(line, "%%endps")) {
1705
- // Just ignore postscript
1706
- multilineVars.inPsBlock = false;
1707
- }
1708
- else
1709
- multilineVars.textBlock += ' ' + line;
1710
- } else
1711
- parseLine(line);
1712
- multilineVars.iChar += line.length + 1;
1713
- });
1714
- var ph = 11*72;
1715
- var pl = 8.5*72;
1716
- switch (multilineVars.papersize) {
1717
- //case "letter": ph = 11*72; pl = 8.5*72; break;
1718
- case "legal": ph = 14*72; pl = 8.5*72; break;
1719
- case "A4": ph = 11.7*72; pl = 8.3*72; break;
535
+
536
+ var wasInHeader = multilineVars.is_in_header;
537
+ parseLine(line);
538
+ if (wasInHeader && !multilineVars.is_in_header) {
539
+ tuneBuilder.setRunningFont("annotationfont", multilineVars.annotationfont);
540
+ tuneBuilder.setRunningFont("gchordfont", multilineVars.gchordfont);
541
+ tuneBuilder.setRunningFont("tripletfont", multilineVars.tripletfont);
542
+ tuneBuilder.setRunningFont("vocalfont", multilineVars.vocalfont);
543
+ }
544
+ line = tokenizer.nextLine();
545
+ }
546
+
547
+ if (wordsContinuation) {
548
+ addWords(tuneBuilder.getCurrentVoice(), '');
1720
549
  }
1721
- if (multilineVars.landscape) {
1722
- var x = ph;
1723
- ph = pl;
1724
- pl = x;
550
+ if (symbolContinuation) {
551
+ addSymbols(tuneBuilder.getCurrentVoice(), '');
1725
552
  }
1726
- multilineVars.openSlurs = tune.cleanUp(pl, ph, multilineVars.barsperstaff, multilineVars.staffnonote, multilineVars.openSlurs);
553
+ multilineVars.openSlurs = tuneBuilder.cleanUp(multilineVars.barsperstaff, multilineVars.staffnonote, multilineVars.openSlurs);
554
+
1727
555
  } catch (err) {
1728
556
  if (err !== "normal_abort")
1729
557
  throw err;
1730
558
  }
559
+
560
+ var ph = 11*72;
561
+ var pl = 8.5*72;
562
+ switch (multilineVars.papersize) {
563
+ //case "letter": ph = 11*72; pl = 8.5*72; break;
564
+ case "legal": ph = 14*72; pl = 8.5*72; break;
565
+ case "A4": ph = 11.7*72; pl = 8.3*72; break;
566
+ }
567
+ if (multilineVars.landscape) {
568
+ var x = ph;
569
+ ph = pl;
570
+ pl = x;
571
+ }
572
+ if (!tune.formatting.pagewidth)
573
+ tune.formatting.pagewidth = pl;
574
+ if (!tune.formatting.pageheight)
575
+ tune.formatting.pageheight = ph;
576
+
1731
577
  if (switches.hint_measures) {
1732
578
  addHintMeasures();
1733
579
  }
1734
580
 
1735
- wrap.wrapLines(tune, multilineVars.lineBreaks);
581
+ wrap.wrapLines(tune, multilineVars.lineBreaks, multilineVars.barNumbers);
1736
582
  };
1737
583
  };
1738
584