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.
- data/app/assets/javascripts/abcjs-rails.js +1 -0
- data/app/assets/javascripts/abcjs/api/abc_tunebook.js +158 -0
- data/app/assets/javascripts/abcjs/data/abc_tune.js +686 -0
- data/app/assets/javascripts/abcjs/edit/abc_editor.js +414 -0
- data/app/assets/javascripts/abcjs/midi/abc_midiwriter.js +698 -0
- data/app/assets/javascripts/abcjs/parse/abc_common.js +76 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse.js +1385 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_directive.js +546 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_header.js +521 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_key_voice.js +781 -0
- data/app/assets/javascripts/abcjs/parse/abc_tokenizer.js +751 -0
- data/app/assets/javascripts/abcjs/write/abc_glyphs.js +105 -0
- data/app/assets/javascripts/abcjs/write/abc_graphelements.js +781 -0
- data/app/assets/javascripts/abcjs/write/abc_layout.js +959 -0
- data/app/assets/javascripts/abcjs/write/abc_write.js +487 -0
- data/app/assets/javascripts/abcjs/write/raphael.js +3395 -0
- data/app/assets/javascripts/abcjs/write/sprintf.js +61 -0
- data/lib/abcjs-rails/version.rb +1 -1
- metadata +18 -1
@@ -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
|
+
};
|