abcjs-rails 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,751 @@
1
+ // abc_tokenizer.js: tokenizes an ABC Music Notation string to support abc_parse.
2
+ // Copyright (C) 2010 Paul Rosen (paul at paulrosen dot net)
3
+ //
4
+ // This program is free software: you can redistribute it and/or modify
5
+ // it under the terms of the GNU General Public License as published by
6
+ // the Free Software Foundation, either version 3 of the License, or
7
+ // (at your option) any later version.
8
+ //
9
+ // This program is distributed in the hope that it will be useful,
10
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ // GNU General Public License for more details.
13
+ //
14
+ // You should have received a copy of the GNU General Public License
15
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+ /*global window */
18
+
19
+ if (!window.ABCJS)
20
+ window.ABCJS = {};
21
+
22
+ if (!window.ABCJS.parse)
23
+ window.ABCJS.parse = {};
24
+
25
+ // this is a series of functions that get a particular element out of the passed stream.
26
+ // the return is the number of characters consumed, so 0 means that the element wasn't found.
27
+ // also returned is the element found. This may be a different length because spaces may be consumed that aren't part of the string.
28
+ // The return structure for most calls is { len: num_chars_consumed, token: str }
29
+ window.ABCJS.parse.tokenizer = function() {
30
+ this.skipWhiteSpace = function(str) {
31
+ for (var i = 0; i < str.length; i++) {
32
+ if (!this.isWhiteSpace(str.charAt(i)))
33
+ return i;
34
+ }
35
+ return str.length; // It must have been all white space
36
+ };
37
+ var finished = function(str, i) {
38
+ return i >= str.length;
39
+ };
40
+ this.eatWhiteSpace = function(line, index) {
41
+ for (var i = index; i < line.length; i++) {
42
+ if (!this.isWhiteSpace(line.charAt(i)))
43
+ return i-index;
44
+ }
45
+ return i-index;
46
+ };
47
+
48
+ // This just gets the basic pitch letter, ignoring leading spaces, and normalizing it to a capital
49
+ this.getKeyPitch = function(str) {
50
+ var i = this.skipWhiteSpace(str);
51
+ if (finished(str, i))
52
+ return {len: 0};
53
+ switch (str.charAt(i)) {
54
+ case 'A':return {len: i+1, token: 'A'};
55
+ case 'B':return {len: i+1, token: 'B'};
56
+ case 'C':return {len: i+1, token: 'C'};
57
+ case 'D':return {len: i+1, token: 'D'};
58
+ case 'E':return {len: i+1, token: 'E'};
59
+ case 'F':return {len: i+1, token: 'F'};
60
+ case 'G':return {len: i+1, token: 'G'};
61
+ // case 'a':return {len: i+1, token: 'A'};
62
+ // case 'b':return {len: i+1, token: 'B'};
63
+ // case 'c':return {len: i+1, token: 'C'};
64
+ // case 'd':return {len: i+1, token: 'D'};
65
+ // case 'e':return {len: i+1, token: 'E'};
66
+ // case 'f':return {len: i+1, token: 'F'};
67
+ // case 'g':return {len: i+1, token: 'G'};
68
+ }
69
+ return {len: 0};
70
+ };
71
+
72
+ // This just gets the basic accidental, ignoring leading spaces, and only the ones that appear in a key
73
+ this.getSharpFlat = function(str) {
74
+ if (str === 'bass')
75
+ return {len: 0};
76
+ switch (str.charAt(0)) {
77
+ case '#':return {len: 1, token: '#'};
78
+ case 'b':return {len: 1, token: 'b'};
79
+ }
80
+ return {len: 0};
81
+ };
82
+
83
+ this.getMode = function(str) {
84
+ var skipAlpha = function(str, start) {
85
+ // This returns the index of the next non-alphabetic char, or the entire length of the string if not found.
86
+ while (start < str.length && ((str.charAt(start) >= 'a' && str.charAt(start) <= 'z') || (str.charAt(start) >= 'A' && str.charAt(start) <= 'Z')))
87
+ start++;
88
+ return start;
89
+ };
90
+
91
+ var i = this.skipWhiteSpace(str);
92
+ if (finished(str, i))
93
+ return {len: 0};
94
+ var firstThree = str.substring(i,i+3).toLowerCase();
95
+ if (firstThree.length > 1 && firstThree.charAt(1) === ' ' || firstThree.charAt(1) === '^' || firstThree.charAt(1) === '_' || firstThree.charAt(1) === '=') firstThree = firstThree.charAt(0); // This will handle the case of 'm'
96
+ switch (firstThree) {
97
+ case 'mix':return {len: skipAlpha(str, i), token: 'Mix'};
98
+ case 'dor':return {len: skipAlpha(str, i), token: 'Dor'};
99
+ case 'phr':return {len: skipAlpha(str, i), token: 'Phr'};
100
+ case 'lyd':return {len: skipAlpha(str, i), token: 'Lyd'};
101
+ case 'loc':return {len: skipAlpha(str, i), token: 'Loc'};
102
+ case 'aeo':return {len: skipAlpha(str, i), token: 'm'};
103
+ case 'maj':return {len: skipAlpha(str, i), token: ''};
104
+ case 'ion':return {len: skipAlpha(str, i), token: ''};
105
+ case 'min':return {len: skipAlpha(str, i), token: 'm'};
106
+ case 'm':return {len: skipAlpha(str, i), token: 'm'};
107
+ }
108
+ return {len: 0};
109
+ };
110
+
111
+ this.getClef = function(str, bExplicitOnly) {
112
+ var strOrig = str;
113
+ var i = this.skipWhiteSpace(str);
114
+ if (finished(str, i))
115
+ return {len: 0};
116
+ // The word 'clef' is optional, but if it appears, a clef MUST appear
117
+ var needsClef = false;
118
+ var strClef = str.substring(i);
119
+ if (window.ABCJS.parse.startsWith(strClef, 'clef=')) {
120
+ needsClef = true;
121
+ strClef = strClef.substring(5);
122
+ i += 5;
123
+ }
124
+ if (strClef.length === 0 && needsClef)
125
+ return {len: i+5, warn: "No clef specified: " + strOrig};
126
+
127
+ var j = this.skipWhiteSpace(strClef);
128
+ if (finished(strClef, j))
129
+ return {len: 0};
130
+ if (j > 0) {
131
+ i += j;
132
+ strClef = strClef.substring(j);
133
+ }
134
+ var name = null;
135
+ if (window.ABCJS.parse.startsWith(strClef, 'treble'))
136
+ name = 'treble';
137
+ else if (window.ABCJS.parse.startsWith(strClef, 'bass3'))
138
+ name = 'bass3';
139
+ else if (window.ABCJS.parse.startsWith(strClef, 'bass'))
140
+ name = 'bass';
141
+ else if (window.ABCJS.parse.startsWith(strClef, 'tenor'))
142
+ name = 'tenor';
143
+ else if (window.ABCJS.parse.startsWith(strClef, 'alto2'))
144
+ name = 'alto2';
145
+ else if (window.ABCJS.parse.startsWith(strClef, 'alto1'))
146
+ name = 'alto1';
147
+ else if (window.ABCJS.parse.startsWith(strClef, 'alto'))
148
+ name = 'alto';
149
+ else if (!bExplicitOnly && (needsClef && window.ABCJS.parse.startsWith(strClef, 'none')))
150
+ name = 'none';
151
+ else if (window.ABCJS.parse.startsWith(strClef, 'perc'))
152
+ name = 'perc';
153
+ else if (!bExplicitOnly && (needsClef && window.ABCJS.parse.startsWith(strClef, 'C')))
154
+ name = 'tenor';
155
+ else if (!bExplicitOnly && (needsClef && window.ABCJS.parse.startsWith(strClef, 'F')))
156
+ name = 'bass';
157
+ else if (!bExplicitOnly && (needsClef && window.ABCJS.parse.startsWith(strClef, 'G')))
158
+ name = 'treble';
159
+ else
160
+ return {len: i+5, warn: "Unknown clef specified: " + strOrig};
161
+
162
+ strClef = strClef.substring(name.length);
163
+ j = this.isMatch(strClef, '+8');
164
+ if (j > 0)
165
+ name += "+8";
166
+ else {
167
+ j = this.isMatch(strClef, '-8');
168
+ if (j > 0)
169
+ name += "-8";
170
+ }
171
+ return {len: i+name.length, token: name, explicit: needsClef};
172
+ };
173
+
174
+ // This returns one of the legal bar lines
175
+ // This is called alot and there is no obvious tokenable items, so this is broken apart.
176
+ this.getBarLine = function(line, i) {
177
+ switch (line.charAt(i)) {
178
+ case ']':
179
+ ++i;
180
+ switch (line.charAt(i)) {
181
+ case '|': return {len: 2, token: "bar_thick_thin"};
182
+ case '[':
183
+ ++i;
184
+ if ((line.charAt(i) >= '1' && line.charAt(i) <= '9') || line.charAt(i) === '"')
185
+ return {len: 2, token: "bar_invisible"};
186
+ return {len: 1, warn: "Unknown bar symbol"};
187
+ default:
188
+ return {len: 1, token: "bar_invisible"};
189
+ }
190
+ break;
191
+ case ':':
192
+ ++i;
193
+ switch (line.charAt(i)) {
194
+ case ':': return {len: 2, token: "bar_dbl_repeat"};
195
+ case '|': // :|
196
+ ++i;
197
+ switch (line.charAt(i)) {
198
+ case ']': // :|]
199
+ ++i;
200
+ switch (line.charAt(i)) {
201
+ case '|': // :|]|
202
+ ++i;
203
+ if (line.charAt(i) === ':') return {len: 5, token: "bar_dbl_repeat"};
204
+ return {len: 3, token: "bar_right_repeat"};
205
+ default:
206
+ return {len: 3, token: "bar_right_repeat"};
207
+ }
208
+ break;
209
+ case '|': // :||
210
+ ++i;
211
+ if (line.charAt(i) === ':') return {len: 4, token: "bar_dbl_repeat"};
212
+ return {len: 3, token: "bar_right_repeat"};
213
+ default:
214
+ return {len: 2, token: "bar_right_repeat"};
215
+ }
216
+ break;
217
+ default:
218
+ return {len: 1, warn: "Unknown bar symbol"};
219
+ }
220
+ break;
221
+ case '[': // [
222
+ ++i;
223
+ if (line.charAt(i) === '|') { // [|
224
+ ++i;
225
+ switch (line.charAt(i)) {
226
+ case ':': return {len: 3, token: "bar_left_repeat"};
227
+ case ']': return {len: 3, token: "bar_invisible"};
228
+ default: return {len: 2, token: "bar_thick_thin"};
229
+ }
230
+ } else {
231
+ if ((line.charAt(i) >= '1' && line.charAt(i) <= '9') || line.charAt(i) === '"')
232
+ return {len: 1, token: "bar_invisible"};
233
+ return {len: 0};
234
+ }
235
+ break;
236
+ case '|': // |
237
+ ++i;
238
+ switch (line.charAt(i)) {
239
+ case ']': return {len: 2, token: "bar_thin_thick"};
240
+ case '|': // ||
241
+ ++i;
242
+ if (line.charAt(i) === ':') return {len: 3, token: "bar_left_repeat"};
243
+ return {len: 2, token: "bar_thin_thin"};
244
+ case ':': // |:
245
+ var colons = 0;
246
+ while (line.charAt(i+colons) === ':') colons++;
247
+ return { len: 1+colons, token: "bar_left_repeat"};
248
+ default: return {len: 1, token: "bar_thin"};
249
+ }
250
+ break;
251
+ }
252
+ return {len: 0};
253
+ };
254
+
255
+ // this returns all the characters in the string that match one of the characters in the legalChars string
256
+ this.getTokenOf = function(str, legalChars) {
257
+ for (var i = 0; i < str.length; i++) {
258
+ if (legalChars.indexOf(str.charAt(i)) < 0)
259
+ return {len: i, token: str.substring(0, i)};
260
+ }
261
+ return {len: i, token: str};
262
+ };
263
+
264
+ this.getToken = function(str, start, end) {
265
+ // This returns the next set of chars that doesn't contain spaces
266
+ var i = start;
267
+ while (i < end && !this.isWhiteSpace(str.charAt(i)))
268
+ i++;
269
+ return str.substring(start, i);
270
+ };
271
+
272
+ // This just sees if the next token is the word passed in, with possible leading spaces
273
+ this.isMatch = function(str, match) {
274
+ var i = this.skipWhiteSpace(str);
275
+ if (finished(str, i))
276
+ return 0;
277
+ if (window.ABCJS.parse.startsWith(str.substring(i), match))
278
+ return i+match.length;
279
+ return 0;
280
+ };
281
+
282
+ this.getPitchFromTokens = function(tokens) {
283
+ var ret = { };
284
+ 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};
285
+ ret.position = pitches[tokens[0].token];
286
+ if (ret.position === undefined)
287
+ return { warn: "Pitch expected. Found: " + tokens[0].token };
288
+ tokens.shift();
289
+ while (tokens.length) {
290
+ switch (tokens[0].token) {
291
+ case ',': ret.position -= 7; tokens.shift(); break;
292
+ case '\'': ret.position += 7; tokens.shift(); break;
293
+ default: return ret;
294
+ }
295
+ }
296
+ return ret;
297
+ };
298
+
299
+ this.getKeyAccidentals2 = function(tokens) {
300
+ var accs;
301
+ // find and strip off all accidentals in the token list
302
+ while (tokens.length > 0) {
303
+ var acc;
304
+ if (tokens[0].token === '^') {
305
+ acc = 'sharp';
306
+ tokens.shift();
307
+ if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
308
+ switch (tokens[0].token) {
309
+ case '^': acc = 'dblsharp'; tokens.shift(); break;
310
+ case '/': acc = 'quartersharp'; tokens.shift(); break;
311
+ }
312
+ } else if (tokens[0].token === '=') {
313
+ acc = 'natural';
314
+ tokens.shift();
315
+ } else if (tokens[0].token === '_') {
316
+ acc = 'flat';
317
+ tokens.shift();
318
+ if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
319
+ switch (tokens[0].token) {
320
+ case '_': acc = 'dblflat'; tokens.shift(); break;
321
+ case '/': acc = 'quarterflat'; tokens.shift(); break;
322
+ }
323
+ } else {
324
+ // Not an accidental, we'll assume that a later parse will recognize it.
325
+ return { accs: accs };
326
+ }
327
+ if (tokens.length === 0) return {accs: accs, warn: 'Expected note name after ' + acc};
328
+ switch (tokens[0].token.charAt(0))
329
+ {
330
+ case 'a':
331
+ case 'b':
332
+ case 'c':
333
+ case 'd':
334
+ case 'e':
335
+ case 'f':
336
+ case 'g':
337
+ case 'A':
338
+ case 'B':
339
+ case 'C':
340
+ case 'D':
341
+ case 'E':
342
+ case 'F':
343
+ case 'G':
344
+ if (accs === undefined)
345
+ accs = [];
346
+ accs.push({ acc: acc, note: tokens[0].token.charAt(0) });
347
+ if (tokens[0].token.length === 1)
348
+ tokens.shift();
349
+ else
350
+ tokens[0].token = tokens[0].token.substring(1);
351
+ break;
352
+ default:
353
+ return {accs: accs, warn: 'Expected note name after ' + acc + ' Found: ' + tokens[0].token };
354
+ }
355
+ }
356
+ return { accs: accs };
357
+ };
358
+
359
+ // This gets an accidental marking for the key signature. It has the accidental then the pitch letter.
360
+ this.getKeyAccidental = function(str) {
361
+ var accTranslation = {
362
+ '^': 'sharp',
363
+ '^^': 'dblsharp',
364
+ '=': 'natural',
365
+ '_': 'flat',
366
+ '__': 'dblflat',
367
+ '_/': 'quarterflat',
368
+ '^/': 'quartersharp'
369
+ };
370
+ var i = this.skipWhiteSpace(str);
371
+ if (finished(str, i))
372
+ return {len: 0};
373
+ var acc = null;
374
+ switch (str.charAt(i))
375
+ {
376
+ case '^':
377
+ case '_':
378
+ case '=':
379
+ acc = str.charAt(i);
380
+ break;
381
+ default:return {len: 0};
382
+ }
383
+ i++;
384
+ if (finished(str, i))
385
+ return {len: 1, warn: 'Expected note name after accidental'};
386
+ switch (str.charAt(i))
387
+ {
388
+ case 'a':
389
+ case 'b':
390
+ case 'c':
391
+ case 'd':
392
+ case 'e':
393
+ case 'f':
394
+ case 'g':
395
+ case 'A':
396
+ case 'B':
397
+ case 'C':
398
+ case 'D':
399
+ case 'E':
400
+ case 'F':
401
+ case 'G':
402
+ return {len: i+1, token: {acc: accTranslation[acc], note: str.charAt(i)}};
403
+ case '^':
404
+ case '_':
405
+ case '/':
406
+ acc += str.charAt(i);
407
+ i++;
408
+ if (finished(str, i))
409
+ return {len: 2, warn: 'Expected note name after accidental'};
410
+ switch (str.charAt(i))
411
+ {
412
+ case 'a':
413
+ case 'b':
414
+ case 'c':
415
+ case 'd':
416
+ case 'e':
417
+ case 'f':
418
+ case 'g':
419
+ case 'A':
420
+ case 'B':
421
+ case 'C':
422
+ case 'D':
423
+ case 'E':
424
+ case 'F':
425
+ case 'G':
426
+ return {len: i+1, token: {acc: accTranslation[acc], note: str.charAt(i)}};
427
+ default:
428
+ return {len: 2, warn: 'Expected note name after accidental'};
429
+ }
430
+ break;
431
+ default:
432
+ return {len: 1, warn: 'Expected note name after accidental'};
433
+ }
434
+ };
435
+
436
+ this.isWhiteSpace = function(ch) {
437
+ return ch === ' ' || ch === '\t' || ch === '\x12';
438
+ };
439
+
440
+ this.getMeat = function(line, start, end) {
441
+ // This removes any comments starting with '%' and trims the ends of the string so that there are no leading or trailing spaces.
442
+ // it returns just the start and end characters that contain the meat.
443
+ var comment = line.indexOf('%', start);
444
+ if (comment >= 0 && comment < end)
445
+ end = comment;
446
+ while (start < end && (line.charAt(start) === ' ' || line.charAt(start) === '\t' || line.charAt(start) === '\x12'))
447
+ start++;
448
+ while (start < end && (line.charAt(end-1) === ' ' || line.charAt(end-1) === '\t' || line.charAt(end-1) === '\x12'))
449
+ end--;
450
+ return {start: start, end: end};
451
+ };
452
+
453
+ var isLetter = function(ch) {
454
+ return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
455
+ };
456
+
457
+ var isNumber = function(ch) {
458
+ return (ch >= '0' && ch <= '9');
459
+ };
460
+
461
+ this.tokenize = function(line, start, end) {
462
+ // this returns all the tokens inside the passed string. A token is a punctuation mark, a string of digits, a string of letters.
463
+ // Quoted strings are one token.
464
+ // If there is a minus sign next to a number, then it is included in the number.
465
+ // If there is a period immediately after a number, with a number immediately following, then a float is returned.
466
+ // The type of token is returned: quote, alpha, number, punct
467
+ var ret = this.getMeat(line, start, end);
468
+ start = ret.start;
469
+ end = ret.end;
470
+ var tokens = [];
471
+ var i;
472
+ while (start < end) {
473
+ if (line.charAt(start) === '"') {
474
+ i = start+1;
475
+ while (i < end && line.charAt(i) !== '"') i++;
476
+ tokens.push({ type: 'quote', token: line.substring(start+1, i), start: start+1, end: i});
477
+ i++;
478
+ } else if (isLetter(line.charAt(start))) {
479
+ i = start+1;
480
+ while (i < end && isLetter(line.charAt(i))) i++;
481
+ tokens.push({ type: 'alpha', token: line.substring(start, i), continueId: isNumber(line.charAt(i)), start: start, end: i});
482
+ start = i + 1;
483
+ } else if (line.charAt(start) === '.' && isNumber(line.charAt(i+1))) {
484
+ i = start+1;
485
+ var int2 = null;
486
+ var float2 = null;
487
+ while (i < end && isNumber(line.charAt(i))) i++;
488
+
489
+ float2 = parseFloat(line.substring(start, i));
490
+ tokens.push({ type: 'number', token: line.substring(start, i), intt: int2, floatt: float2, continueId: isLetter(line.charAt(i)), start: start, end: i});
491
+ start = i + 1;
492
+ } else if (isNumber(line.charAt(start)) || (line.charAt(start) === '-' && isNumber(line.charAt(i+1)))) {
493
+ i = start+1;
494
+ var intt = null;
495
+ var floatt = null;
496
+ while (i < end && isNumber(line.charAt(i))) i++;
497
+ if (line.charAt(i) === '.' && isNumber(line.charAt(i+1))) {
498
+ i++;
499
+ while (i < end && isNumber(line.charAt(i))) i++;
500
+ } else
501
+ intt = parseInt(line.substring(start, i));
502
+
503
+ floatt = parseFloat(line.substring(start, i));
504
+ tokens.push({ type: 'number', token: line.substring(start, i), intt: intt, floatt: floatt, continueId: isLetter(line.charAt(i)), start: start, end: i});
505
+ start = i + 1;
506
+ } else if (line.charAt(start) === ' ' || line.charAt(start) === '\t') {
507
+ i = start+1;
508
+ } else {
509
+ tokens.push({ type: 'punct', token: line.charAt(start), start: start, end: start+1});
510
+ i = start+1;
511
+ }
512
+ start = i;
513
+ }
514
+ return tokens;
515
+ };
516
+
517
+ this.getVoiceToken = function(line, start, end) {
518
+ // This finds the next token. A token is delimited by a space or an equal sign. If it starts with a quote, then the portion between the quotes is returned.
519
+ var i = start;
520
+ while (i < end && this.isWhiteSpace(line.charAt(i)) || line.charAt(i) === '=')
521
+ i++;
522
+
523
+ if (line.charAt(i) === '"') {
524
+ var close = line.indexOf('"', i+1);
525
+ if (close === -1 || close >= end)
526
+ return {len: 1, err: "Missing close quote"};
527
+ return {len: close-start+1, token: this.translateString(line.substring(i+1, close))};
528
+ } else {
529
+ var ii = i;
530
+ while (ii < end && !this.isWhiteSpace(line.charAt(ii)) && line.charAt(ii) !== '=')
531
+ ii++;
532
+ return {len: ii-start+1, token: line.substring(i, ii)};
533
+ }
534
+ };
535
+
536
+ var charMap = {
537
+ "`a": 'à', "'a": "á", "^a": "â", "~a": "ã", "\"a": "ä", "oa": "å", "=a": "ā", "ua": "ă", ";a": "ą",
538
+ "`e": 'è', "'e": "é", "^e": "ê", "\"e": "ë", "=e": "ē", "ue": "ĕ", ";e": "ę", ".e": "ė",
539
+ "`i": 'ì', "'i": "í", "^i": "î", "\"i": "ï", "=i": "ī", "ui": "ĭ", ";i": "į",
540
+ "`o": 'ò', "'o": "ó", "^o": "ô", "~o": "õ", "\"o": "ö", "=o": "ō", "uo": "ŏ", "/o": "ø",
541
+ "`u": 'ù', "'u": "ú", "^u": "û", "~u": "ũ", "\"u": "ü", "ou": "ů", "=u": "ū", "uu": "ŭ", ";u": "ų",
542
+ "`A": 'À', "'A": "Á", "^A": "Â", "~A": "Ã", "\"A": "Ä", "oA": "Å", "=A": "Ā", "uA": "Ă", ";A": "Ą",
543
+ "`E": 'È', "'E": "É", "^E": "Ê", "\"E": "Ë", "=E": "Ē", "uE": "Ĕ", ";E": "Ę", ".E": "Ė",
544
+ "`I": 'Ì', "'I": "Í", "^I": "Î", "~I": "Ĩ", "\"I": "Ï", "=I": "Ī", "uI": "Ĭ", ";I": "Į", ".I": "İ",
545
+ "`O": 'Ò', "'O": "Ó", "^O": "Ô", "~O": "Õ", "\"O": "Ö", "=O": "Ō", "uO": "Ŏ", "/O": "Ø",
546
+ "`U": 'Ù', "'U": "Ú", "^U": "Û", "~U": "Ũ", "\"U": "Ü", "oU": "Ů", "=U": "Ū", "uU": "Ŭ", ";U": "Ų",
547
+ "ae": "æ", "AE": "Æ", "oe": "œ", "OE": "Œ", "ss": "ß",
548
+ "'c": "ć", "^c": "ĉ", "uc": "č", "cc": "ç", ".c": "ċ", "cC": "Ç", "'C": "Ć", "^C": "Ĉ", "uC": "Č", ".C": "Ċ",
549
+ "~n": "ñ",
550
+ "=s": "š", "vs": "š",
551
+ "vz": 'ž'
552
+
553
+ // More chars: Ñ IJ ij Ď ď Đ đ Ĝ ĝ Ğ ğ Ġ ġ Ģ ģ Ĥ ĥ Ħ ħ Ĵ ĵ Ķ ķ ĸ Ĺ ĺ Ļ ļ Ľ ľ Ŀ ŀ Ł ł Ń ń Ņ ņ Ň ň ʼn Ŋ ŋ Ŕ ŕ Ŗ ŗ Ř ř Ś ś Ŝ ŝ Ş ş Š Ţ ţ Ť ť Ŧ ŧ Ŵ ŵ Ŷ ŷ Ÿ ÿ Ÿ Ź ź Ż ż Ž
554
+ };
555
+ var charMap1 = {
556
+ "#": "♯",
557
+ "b": "♭",
558
+ "=": "♮"
559
+ };
560
+ var charMap2 = {
561
+ "201": "♯",
562
+ "202": "♭",
563
+ "203": "♮",
564
+ "241": "¡",
565
+ "242": "¢", "252": "a", "262": "2", "272": "o", "302": "Â", "312": "Ê", "322": "Ò", "332": "Ú", "342": "â", "352": "ê", "362": "ò", "372": "ú",
566
+ "243": "£", "253": "«", "263": "3", "273": "»", "303": "Ã", "313": "Ë", "323": "Ó", "333": "Û", "343": "ã", "353": "ë", "363": "ó", "373": "û",
567
+ "244": "¤", "254": "¬", "264": " ́", "274": "1⁄4", "304": "Ä", "314": "Ì", "324": "Ô", "334": "Ü", "344": "ä", "354": "ì", "364": "ô", "374": "ü",
568
+ "245": "¥", "255": "-", "265": "μ", "275": "1⁄2", "305": "Å", "315": "Í", "325": "Õ", "335": "Ý", "345": "å", "355": "í", "365": "õ", "375": "ý",
569
+ "246": "¦", "256": "®", "266": "¶", "276": "3⁄4", "306": "Æ", "316": "Î", "326": "Ö", "336": "Þ", "346": "æ", "356": "î", "366": "ö", "376": "þ",
570
+ "247": "§", "257": " ̄", "267": "·", "277": "¿", "307": "Ç", "317": "Ï", "327": "×", "337": "ß", "347": "ç", "357": "ï", "367": "÷", "377": "ÿ",
571
+ "250": " ̈", "260": "°", "270": " ̧", "300": "À", "310": "È", "320": "Ð", "330": "Ø", "340": "à", "350": "è", "360": "ð", "370": "ø",
572
+ "251": "©", "261": "±", "271": "1", "301": "Á", "311": "É", "321": "Ñ", "331": "Ù", "341": "á", "351": "é", "361": "ñ", "371": "ù" };
573
+ this.translateString = function(str) {
574
+ var arr = str.split('\\');
575
+ if (arr.length === 1) return str;
576
+ var out = null;
577
+ window.ABCJS.parse.each(arr, function(s) {
578
+ if (out === null)
579
+ out = s;
580
+ else {
581
+ var c = charMap[s.substring(0, 2)];
582
+ if (c !== undefined)
583
+ out += c + s.substring(2);
584
+ else {
585
+ c = charMap2[s.substring(0, 3)];
586
+ if (c !== undefined)
587
+ out += c + s.substring(3);
588
+ else {
589
+ c = charMap1[s.substring(0, 1)];
590
+ if (c !== undefined)
591
+ out += c + s.substring(1);
592
+ else
593
+ out += "\\" + s;
594
+ }
595
+ }
596
+ }
597
+ });
598
+ return out;
599
+ };
600
+ this.getNumber = function(line, index) {
601
+ var num = 0;
602
+ while (index < line.length) {
603
+ switch (line.charAt(index)) {
604
+ case '0':num = num*10;index++;break;
605
+ case '1':num = num*10+1;index++;break;
606
+ case '2':num = num*10+2;index++;break;
607
+ case '3':num = num*10+3;index++;break;
608
+ case '4':num = num*10+4;index++;break;
609
+ case '5':num = num*10+5;index++;break;
610
+ case '6':num = num*10+6;index++;break;
611
+ case '7':num = num*10+7;index++;break;
612
+ case '8':num = num*10+8;index++;break;
613
+ case '9':num = num*10+9;index++;break;
614
+ default:
615
+ return {num: num, index: index};
616
+ }
617
+ }
618
+ return {num: num, index: index};
619
+ };
620
+
621
+ this.getFraction = function(line, index) {
622
+ var num = 1;
623
+ var den = 1;
624
+ if (line.charAt(index) !== '/') {
625
+ var ret = this.getNumber(line, index);
626
+ num = ret.num;
627
+ index = ret.index;
628
+ }
629
+ if (line.charAt(index) === '/') {
630
+ index++;
631
+ if (line.charAt(index) === '/') {
632
+ var div = 0.5;
633
+ while (line.charAt(index++) === '/')
634
+ div = div /2;
635
+ return {value: num * div, index: index-1};
636
+ } else {
637
+ var iSave = index;
638
+ var ret2 = this.getNumber(line, index);
639
+ if (ret2.num === 0 && iSave === index) // If we didn't use any characters, it is an implied 2
640
+ ret2.num = 2;
641
+ if (ret2.num !== 0)
642
+ den = ret2.num;
643
+ index = ret2.index;
644
+ }
645
+ }
646
+
647
+ return {value: num/den, index: index};
648
+ };
649
+
650
+ this.theReverser = function(str) {
651
+ if (window.ABCJS.parse.endsWith(str, ", The"))
652
+ return "The " + str.substring(0, str.length-5);
653
+ if (window.ABCJS.parse.endsWith(str, ", A"))
654
+ return "A " + str.substring(0, str.length-3);
655
+ return str;
656
+ };
657
+
658
+ this.stripComment = function(str) {
659
+ var i = str.indexOf('%');
660
+ if (i >= 0)
661
+ return window.ABCJS.parse.strip(str.substring(0, i));
662
+ return window.ABCJS.parse.strip(str);
663
+ };
664
+
665
+ this.getInt = function(str) {
666
+ // This parses the beginning of the string for a number and returns { value: num, digits: num }
667
+ // If digits is 0, then the string didn't point to a number.
668
+ var x = parseInt(str);
669
+ if (isNaN(x))
670
+ return {digits: 0};
671
+ var s = "" + x;
672
+ var i = str.indexOf(s); // This is to account for leading spaces
673
+ return {value: x, digits: i+s.length};
674
+ };
675
+
676
+ this.getFloat = function(str) {
677
+ // This parses the beginning of the string for a number and returns { value: num, digits: num }
678
+ // If digits is 0, then the string didn't point to a number.
679
+ var x = parseFloat(str);
680
+ if (isNaN(x))
681
+ return {digits: 0};
682
+ var s = "" + x;
683
+ var i = str.indexOf(s); // This is to account for leading spaces
684
+ return {value: x, digits: i+s.length};
685
+ };
686
+
687
+ this.getMeasurement = function(tokens) {
688
+ if (tokens.length === 0) return { used: 0 };
689
+ var used = 1;
690
+ var num = '';
691
+ if (tokens[0].token === '-') {
692
+ tokens.shift();
693
+ num = '-';
694
+ used++;
695
+ }
696
+ else if (tokens[0].type !== 'number') return { used: 0 };
697
+ num += tokens.shift().token;
698
+ if (tokens.length === 0) return { used: 1, value: parseInt(num) };
699
+ var x = tokens.shift();
700
+ if (x.token === '.') {
701
+ used++;
702
+ if (tokens.length === 0) return { used: used, value: parseInt(num) };
703
+ if (tokens[0].type === 'number') {
704
+ x = tokens.shift();
705
+ num = num + '.' + x.token;
706
+ used++;
707
+ if (tokens.length === 0) return { used: used, value: parseFloat(num) };
708
+ }
709
+ x = tokens.shift();
710
+ }
711
+ switch (x.token) {
712
+ case 'pt': return { used: used+1, value: parseFloat(num) };
713
+ case 'cm': return { used: used+1, value: parseFloat(num)/2.54*72 };
714
+ case 'in': return { used: used+1, value: parseFloat(num)*72 };
715
+ default: tokens.unshift(x); return { used: used, value: parseFloat(num) };
716
+ }
717
+ return { used: 0 };
718
+ };
719
+ var substInChord = function(str)
720
+ {
721
+ while ( str.indexOf("\\n") !== -1)
722
+ {
723
+ str = str.replace("\\n", "\n");
724
+ }
725
+ return str;
726
+ };
727
+ this.getBrackettedSubstring = function(line, i, maxErrorChars, _matchChar)
728
+ {
729
+ // This extracts the sub string by looking at the first character and searching for that
730
+ // character later in the line (or search for the optional _matchChar).
731
+ // For instance, if the first character is a quote it will look for
732
+ // the end quote. If the end of the line is reached, then only up to the default number
733
+ // of characters are returned, so that a missing end quote won't eat up the entire line.
734
+ // It returns the substring and the number of characters consumed.
735
+ // The number of characters consumed is normally two more than the size of the substring,
736
+ // but in the error case it might not be.
737
+ var matchChar = _matchChar || line.charAt(i);
738
+ var pos = i+1;
739
+ while ((pos < line.length) && (line.charAt(pos) !== matchChar))
740
+ ++pos;
741
+ if (line.charAt(pos) === matchChar)
742
+ return [pos-i+1,substInChord(line.substring(i+1, pos)), true];
743
+ else // we hit the end of line, so we'll just pick an arbitrary num of chars so the line doesn't disappear.
744
+ {
745
+ pos = i+maxErrorChars;
746
+ if (pos > line.length-1)
747
+ pos = line.length-1;
748
+ return [pos-i+1, substInChord(line.substring(i+1, pos)), false];
749
+ }
750
+ };
751
+ };