abcjs-rails 1.1.0 → 1.1.1

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.
@@ -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
+ };