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,781 @@
1
+ /*global window */
2
+
3
+ if (!window.ABCJS)
4
+ window.ABCJS = {};
5
+
6
+ if (!window.ABCJS.parse)
7
+ window.ABCJS.parse = {};
8
+
9
+ window.ABCJS.parse.parseKeyVoice = {};
10
+
11
+ (function() {
12
+ var tokenizer;
13
+ var warn;
14
+ var multilineVars;
15
+ var tune;
16
+ window.ABCJS.parse.parseKeyVoice.initialize = function(tokenizer_, warn_, multilineVars_, tune_) {
17
+ tokenizer = tokenizer_;
18
+ warn = warn_;
19
+ multilineVars = multilineVars_;
20
+ tune = tune_;
21
+ };
22
+
23
+ window.ABCJS.parse.parseKeyVoice.standardKey = function(keyName) {
24
+ var key1sharp = {acc: 'sharp', note: 'f'};
25
+ var key2sharp = {acc: 'sharp', note: 'c'};
26
+ var key3sharp = {acc: 'sharp', note: 'g'};
27
+ var key4sharp = {acc: 'sharp', note: 'd'};
28
+ var key5sharp = {acc: 'sharp', note: 'A'};
29
+ var key6sharp = {acc: 'sharp', note: 'e'};
30
+ var key7sharp = {acc: 'sharp', note: 'B'};
31
+ var key1flat = {acc: 'flat', note: 'B'};
32
+ var key2flat = {acc: 'flat', note: 'e'};
33
+ var key3flat = {acc: 'flat', note: 'A'};
34
+ var key4flat = {acc: 'flat', note: 'd'};
35
+ var key5flat = {acc: 'flat', note: 'G'};
36
+ var key6flat = {acc: 'flat', note: 'c'};
37
+ var key7flat = {acc: 'flat', note: 'F'};
38
+
39
+ var keys = {
40
+ 'C#': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
41
+ 'A#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
42
+ 'G#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
43
+ 'D#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
44
+ 'E#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
45
+ 'F#Lyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
46
+ 'B#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ],
47
+
48
+ 'F#': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
49
+ 'D#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
50
+ 'C#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
51
+ 'G#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
52
+ 'A#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
53
+ 'BLyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
54
+ 'E#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp ],
55
+
56
+ 'B': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
57
+ 'G#m': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
58
+ 'F#Mix': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
59
+ 'C#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
60
+ 'D#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
61
+ 'ELyd': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
62
+ 'A#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp ],
63
+
64
+ 'E': [ key1sharp, key2sharp, key3sharp, key4sharp ],
65
+ 'C#m': [ key1sharp, key2sharp, key3sharp, key4sharp ],
66
+ 'BMix': [ key1sharp, key2sharp, key3sharp, key4sharp ],
67
+ 'F#Dor': [ key1sharp, key2sharp, key3sharp, key4sharp ],
68
+ 'G#Phr': [ key1sharp, key2sharp, key3sharp, key4sharp ],
69
+ 'ALyd': [ key1sharp, key2sharp, key3sharp, key4sharp ],
70
+ 'D#Loc': [ key1sharp, key2sharp, key3sharp, key4sharp ],
71
+
72
+ 'A': [ key1sharp, key2sharp, key3sharp ],
73
+ 'F#m': [ key1sharp, key2sharp, key3sharp ],
74
+ 'EMix': [ key1sharp, key2sharp, key3sharp ],
75
+ 'BDor': [ key1sharp, key2sharp, key3sharp ],
76
+ 'C#Phr': [ key1sharp, key2sharp, key3sharp ],
77
+ 'DLyd': [ key1sharp, key2sharp, key3sharp ],
78
+ 'G#Loc': [ key1sharp, key2sharp, key3sharp ],
79
+
80
+ 'D': [ key1sharp, key2sharp ],
81
+ 'Bm': [ key1sharp, key2sharp ],
82
+ 'AMix': [ key1sharp, key2sharp ],
83
+ 'EDor': [ key1sharp, key2sharp ],
84
+ 'F#Phr': [ key1sharp, key2sharp ],
85
+ 'GLyd': [ key1sharp, key2sharp ],
86
+ 'C#Loc': [ key1sharp, key2sharp ],
87
+
88
+ 'G': [ key1sharp ],
89
+ 'Em': [ key1sharp ],
90
+ 'DMix': [ key1sharp ],
91
+ 'ADor': [ key1sharp ],
92
+ 'BPhr': [ key1sharp ],
93
+ 'CLyd': [ key1sharp ],
94
+ 'F#Loc': [ key1sharp ],
95
+
96
+ 'C': [],
97
+ 'Am': [],
98
+ 'GMix': [],
99
+ 'DDor': [],
100
+ 'EPhr': [],
101
+ 'FLyd': [],
102
+ 'BLoc': [],
103
+
104
+ 'F': [ key1flat ],
105
+ 'Dm': [ key1flat ],
106
+ 'CMix': [ key1flat ],
107
+ 'GDor': [ key1flat ],
108
+ 'APhr': [ key1flat ],
109
+ 'BbLyd': [ key1flat ],
110
+ 'ELoc': [ key1flat ],
111
+
112
+ 'Bb': [ key1flat, key2flat ],
113
+ 'Gm': [ key1flat, key2flat ],
114
+ 'FMix': [ key1flat, key2flat ],
115
+ 'CDor': [ key1flat, key2flat ],
116
+ 'DPhr': [ key1flat, key2flat ],
117
+ 'EbLyd': [ key1flat, key2flat ],
118
+ 'ALoc': [ key1flat, key2flat ],
119
+
120
+ 'Eb': [ key1flat, key2flat, key3flat ],
121
+ 'Cm': [ key1flat, key2flat, key3flat ],
122
+ 'BbMix': [ key1flat, key2flat, key3flat ],
123
+ 'FDor': [ key1flat, key2flat, key3flat ],
124
+ 'GPhr': [ key1flat, key2flat, key3flat ],
125
+ 'AbLyd': [ key1flat, key2flat, key3flat ],
126
+ 'DLoc': [ key1flat, key2flat, key3flat ],
127
+
128
+ 'Ab': [ key1flat, key2flat, key3flat, key4flat ],
129
+ 'Fm': [ key1flat, key2flat, key3flat, key4flat ],
130
+ 'EbMix': [ key1flat, key2flat, key3flat, key4flat ],
131
+ 'BbDor': [ key1flat, key2flat, key3flat, key4flat ],
132
+ 'CPhr': [ key1flat, key2flat, key3flat, key4flat ],
133
+ 'DbLyd': [ key1flat, key2flat, key3flat, key4flat ],
134
+ 'GLoc': [ key1flat, key2flat, key3flat, key4flat ],
135
+
136
+ 'Db': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
137
+ 'Bbm': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
138
+ 'AbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
139
+ 'EbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
140
+ 'FPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
141
+ 'GbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
142
+ 'CLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat ],
143
+
144
+ 'Gb': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
145
+ 'Ebm': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
146
+ 'DbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
147
+ 'AbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
148
+ 'BbPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
149
+ 'CbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
150
+ 'FLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat ],
151
+
152
+ 'Cb': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
153
+ 'Abm': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
154
+ 'GbMix': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
155
+ 'DbDor': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
156
+ 'EbPhr': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
157
+ 'FbLyd': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
158
+ 'BbLoc': [ key1flat, key2flat, key3flat, key4flat, key5flat, key6flat, key7flat ],
159
+
160
+ // The following are not in the 2.0 spec, but seem normal enough.
161
+ // TODO-PER: These SOUND the same as what's written, but they aren't right
162
+ 'A#': [ key1flat, key2flat ],
163
+ 'B#': [],
164
+ 'D#': [ key1flat, key2flat, key3flat ],
165
+ 'E#': [ key1flat ],
166
+ 'G#': [ key1flat, key2flat, key3flat, key4flat ],
167
+ 'Gbm': [ key1sharp, key2sharp, key3sharp, key4sharp, key5sharp, key6sharp, key7sharp ]
168
+ };
169
+
170
+ return keys[keyName];
171
+ };
172
+
173
+ var calcMiddle = function(clef, oct) {
174
+ var mid = 0;
175
+ switch(clef) {
176
+ case 'treble':
177
+ case 'perc':
178
+ case 'none':
179
+ case 'treble+8':
180
+ case 'treble-8':
181
+ break;
182
+ case 'bass3':
183
+ case 'bass':
184
+ case 'bass+8':
185
+ case 'bass-8':
186
+ case 'bass+16':
187
+ case 'bass-16':
188
+ mid = -12;
189
+ break;
190
+ case 'tenor':
191
+ mid = -8;
192
+ break;
193
+ case 'alto2':
194
+ case 'alto1':
195
+ case 'alto':
196
+ case 'alto+8':
197
+ case 'alto-8':
198
+ mid = -6;
199
+ break;
200
+ }
201
+ return mid+oct;
202
+ };
203
+
204
+ window.ABCJS.parse.parseKeyVoice.deepCopyKey = function(key) {
205
+ var ret = { accidentals: [], root: key.root, acc: key.acc, mode: key.mode };
206
+ window.ABCJS.parse.each(key.accidentals, function(k) {
207
+ ret.accidentals.push(window.ABCJS.parse.clone(k));
208
+ });
209
+ return ret;
210
+ };
211
+
212
+ 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};
213
+
214
+ window.ABCJS.parse.parseKeyVoice.addPosToKey = function(clef, key) {
215
+ // Shift the key signature from the treble positions to whatever position is needed for the clef.
216
+ // This may put the key signature unnaturally high or low, so if it does, then shift it.
217
+ var mid = clef.verticalPos;
218
+ window.ABCJS.parse.each(key.accidentals, function(acc) {
219
+ var pitch = pitches[acc.note];
220
+ pitch = pitch - mid;
221
+ acc.verticalPos = pitch;
222
+ });
223
+ if (key.impliedNaturals)
224
+ window.ABCJS.parse.each(key.impliedNaturals, function(acc) {
225
+ var pitch = pitches[acc.note];
226
+ pitch = pitch - mid;
227
+ acc.verticalPos = pitch;
228
+ });
229
+
230
+ if (mid < -10) {
231
+ window.ABCJS.parse.each(key.accidentals, function(acc) {
232
+ acc.verticalPos -= 7;
233
+ if (acc.verticalPos >= 11 || (acc.verticalPos === 10 && acc.acc === 'flat'))
234
+ acc.verticalPos -= 7;
235
+ if (acc.note === 'A' && acc.acc === 'sharp' )
236
+ acc.verticalPos -=7;
237
+ if ((acc.note === 'G' || acc.note === 'F') && acc.acc === 'flat' )
238
+ acc.verticalPos -=7;
239
+ });
240
+ if (key.impliedNaturals)
241
+ window.ABCJS.parse.each(key.impliedNaturals, function(acc) {
242
+ acc.verticalPos -= 7;
243
+ if (acc.verticalPos >= 11 || (acc.verticalPos === 10 && acc.acc === 'flat'))
244
+ acc.verticalPos -= 7;
245
+ if (acc.note === 'A' && acc.acc === 'sharp' )
246
+ acc.verticalPos -=7;
247
+ if ((acc.note === 'G' || acc.note === 'F') && acc.acc === 'flat' )
248
+ acc.verticalPos -=7;
249
+ });
250
+ } else if (mid < -4) {
251
+ window.ABCJS.parse.each(key.accidentals, function(acc) {
252
+ acc.verticalPos -= 7;
253
+ if (mid === -8 && (acc.note === 'f' || acc.note === 'g') && acc.acc === 'sharp' )
254
+ acc.verticalPos -=7;
255
+ });
256
+ if (key.impliedNaturals)
257
+ window.ABCJS.parse.each(key.impliedNaturals, function(acc) {
258
+ acc.verticalPos -= 7;
259
+ if (mid === -8 && (acc.note === 'f' || acc.note === 'g') && acc.acc === 'sharp' )
260
+ acc.verticalPos -=7;
261
+ });
262
+ } else if (mid >= 7) {
263
+ window.ABCJS.parse.each(key.accidentals, function(acc) {
264
+ acc.verticalPos += 7;
265
+ });
266
+ if (key.impliedNaturals)
267
+ window.ABCJS.parse.each(key.impliedNaturals, function(acc) {
268
+ acc.verticalPos += 7;
269
+ });
270
+ }
271
+ };
272
+
273
+ window.ABCJS.parse.parseKeyVoice.fixKey = function(clef, key) {
274
+ var fixedKey = window.ABCJS.parse.clone(key);
275
+ window.ABCJS.parse.parseKeyVoice.addPosToKey(clef, fixedKey);
276
+ return fixedKey;
277
+ };
278
+
279
+ var parseMiddle = function(str) {
280
+ var mid = pitches[str.charAt(0)];
281
+ for (var i = 1; i < str.length; i++) {
282
+ if (str.charAt(i) === ',') mid -= 7;
283
+ else if (str.charAt(i) === ',') mid += 7;
284
+ else break;
285
+ }
286
+ return { mid: mid - 6, str: str.substring(i) }; // We get the note in the middle of the staff. We want the note that appears as the first ledger line below the staff.
287
+ };
288
+
289
+ var normalizeAccidentals = function(accs) {
290
+ for (var i = 0; i < accs.length; i++) {
291
+ if (accs[i].note === 'b')
292
+ accs[i].note = 'B';
293
+ else if (accs[i].note === 'a')
294
+ accs[i].note = 'A';
295
+ else if (accs[i].note === 'F')
296
+ accs[i].note = 'f';
297
+ else if (accs[i].note === 'E')
298
+ accs[i].note = 'e';
299
+ else if (accs[i].note === 'D')
300
+ accs[i].note = 'd';
301
+ else if (accs[i].note === 'C')
302
+ accs[i].note = 'c';
303
+ else if (accs[i].note === 'G' && accs[i].acc === 'sharp')
304
+ accs[i].note = 'g';
305
+ else if (accs[i].note === 'g' && accs[i].acc === 'flat')
306
+ accs[i].note = 'G';
307
+ }
308
+ };
309
+
310
+ window.ABCJS.parse.parseKeyVoice.parseKey = function(str) // (and clef)
311
+ {
312
+ // returns:
313
+ // { foundClef: true, foundKey: true }
314
+ // Side effects:
315
+ // calls warn() when there is a syntax error
316
+ // sets these members of multilineVars:
317
+ // clef
318
+ // key
319
+ // style
320
+ //
321
+ // The format is:
322
+ // K: [⟨key⟩] [⟨modifiers⟩*]
323
+ // modifiers are any of the following in any order:
324
+ // [⟨clef⟩] [middle=⟨pitch⟩] [transpose=[-]⟨number⟩] [stafflines=⟨number⟩] [staffscale=⟨number⟩][style=⟨style⟩]
325
+ // key is none|HP|Hp|⟨specified_key⟩
326
+ // clef is [clef=] [⟨clef type⟩] [⟨line number⟩] [+8|-8]
327
+ // specified_key is ⟨pitch⟩[#|b][mode(first three chars are significant)][accidentals*]
328
+ if (str.length === 0) {
329
+ // an empty K: field is the same as K:none
330
+ str = 'none';
331
+ }
332
+ var tokens = tokenizer.tokenize(str, 0, str.length);
333
+ var ret = {};
334
+
335
+ // first the key
336
+ switch (tokens[0].token) {
337
+ case 'HP':
338
+ window.ABCJS.parse.parseDirective.addDirective("bagpipes");
339
+ multilineVars.key = { root: "HP", accidentals: [], acc: "", mode: "" };
340
+ ret.foundKey = true;
341
+ tokens.shift();
342
+ break;
343
+ case 'Hp':
344
+ window.ABCJS.parse.parseDirective.addDirective("bagpipes");
345
+ multilineVars.key = { root: "Hp", accidentals: [{acc: 'natural', note: 'g'}, {acc: 'sharp', note: 'f'}, {acc: 'sharp', note: 'c'}], acc: "", mode: "" };
346
+ ret.foundKey = true;
347
+ tokens.shift();
348
+ break;
349
+ case 'none':
350
+ // we got the none key - that's the same as C to us
351
+ multilineVars.key = { root: "none", accidentals: [], acc: "", mode: "" };
352
+ ret.foundKey = true;
353
+ tokens.shift();
354
+ break;
355
+ default:
356
+ var retPitch = tokenizer.getKeyPitch(tokens[0].token);
357
+ if (retPitch.len > 0) {
358
+ ret.foundKey = true;
359
+ var acc = "";
360
+ var mode = "";
361
+ // The accidental and mode might be attached to the pitch, so we might want to just remove the first character.
362
+ if (tokens[0].token.length > 1)
363
+ tokens[0].token = tokens[0].token.substring(1);
364
+ else
365
+ tokens.shift();
366
+ var key = retPitch.token;
367
+ // We got a pitch to start with, so we might also have an accidental and a mode
368
+ if (tokens.length > 0) {
369
+ var retAcc = tokenizer.getSharpFlat(tokens[0].token);
370
+ if (retAcc.len > 0) {
371
+ if (tokens[0].token.length > 1)
372
+ tokens[0].token = tokens[0].token.substring(1);
373
+ else
374
+ tokens.shift();
375
+ key += retAcc.token;
376
+ acc = retAcc.token;
377
+ }
378
+ if (tokens.length > 0) {
379
+ var retMode = tokenizer.getMode(tokens[0].token);
380
+ if (retMode.len > 0) {
381
+ tokens.shift();
382
+ key += retMode.token;
383
+ mode = retMode.token;
384
+ }
385
+ }
386
+ }
387
+ // We need to do a deep copy because we are going to modify it
388
+ var oldKey = window.ABCJS.parse.parseKeyVoice.deepCopyKey(multilineVars.key);
389
+ multilineVars.key = window.ABCJS.parse.parseKeyVoice.deepCopyKey({accidentals: window.ABCJS.parse.parseKeyVoice.standardKey(key)});
390
+ multilineVars.key.root = retPitch.token;
391
+ multilineVars.key.acc = acc;
392
+ multilineVars.key.mode = mode;
393
+ if (oldKey) {
394
+ // Add natural in all places that the old key had an accidental.
395
+ var kk;
396
+ for (var k = 0; k < multilineVars.key.accidentals.length; k++) {
397
+ for (kk = 0; kk < oldKey.accidentals.length; kk++) {
398
+ if (oldKey.accidentals[kk].note && multilineVars.key.accidentals[k].note.toLowerCase() === oldKey.accidentals[kk].note.toLowerCase())
399
+ oldKey.accidentals[kk].note = null;
400
+ }
401
+ }
402
+ for (kk = 0; kk < oldKey.accidentals.length; kk++) {
403
+ if (oldKey.accidentals[kk].note) {
404
+ if (!multilineVars.key.impliedNaturals)
405
+ multilineVars.key.impliedNaturals = [];
406
+ multilineVars.key.impliedNaturals.push({ acc: 'natural', note: oldKey.accidentals[kk].note });
407
+ }
408
+ }
409
+ }
410
+ }
411
+ break;
412
+ }
413
+
414
+ // There are two special cases of deprecated syntax. Ignore them if they occur
415
+ if (tokens.length === 0) return ret;
416
+ if (tokens[0].token === 'exp') tokens.shift();
417
+ if (tokens.length === 0) return ret;
418
+ if (tokens[0].token === 'oct') tokens.shift();
419
+
420
+ // now see if there are extra accidentals
421
+ if (tokens.length === 0) return ret;
422
+ var accs = tokenizer.getKeyAccidentals2(tokens);
423
+ if (accs.warn)
424
+ warn(accs.warn, str, 0);
425
+ // If we have extra accidentals, first replace ones that are of the same pitch before adding them to the end.
426
+ if (accs.accs) {
427
+ if (!ret.foundKey) { // if there are only extra accidentals, make sure this is set.
428
+ ret.foundKey = true;
429
+ multilineVars.key = { root: "none", acc: "", mode: "", accidentals: [] };
430
+ }
431
+ normalizeAccidentals(accs.accs);
432
+ for (var i = 0; i < accs.accs.length; i++) {
433
+ var found = false;
434
+ for (var j = 0; j < multilineVars.key.accidentals.length && !found; j++) {
435
+ if (multilineVars.key.accidentals[j].note === accs.accs[i].note) {
436
+ found = true;
437
+ multilineVars.key.accidentals[j].acc = accs.accs[i].acc;
438
+ }
439
+ }
440
+ if (!found) {
441
+ multilineVars.key.accidentals.push(accs.accs[i]);
442
+ if (multilineVars.key.impliedNaturals) {
443
+ for (var kkk = 0; kkk < multilineVars.key.impliedNaturals.length; kkk++) {
444
+ if (multilineVars.key.impliedNaturals[kkk].note === accs.accs[i].note)
445
+ multilineVars.key.impliedNaturals.splice(kkk, 1);
446
+ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+
452
+ // Now see if any optional parameters are present. They have the form "key=value", except that "clef=" is optional
453
+ var token;
454
+ while (tokens.length > 0) {
455
+ switch (tokens[0].token) {
456
+ case "m":
457
+ case "middle":
458
+ tokens.shift();
459
+ if (tokens.length === 0) { warn("Expected = after middle", str, 0); return ret; }
460
+ token = tokens.shift();
461
+ if (token.token !== "=") { warn("Expected = after middle", str, token.start); break; }
462
+ if (tokens.length === 0) { warn("Expected parameter after middle=", str, 0); return ret; }
463
+ var pitch = tokenizer.getPitchFromTokens(tokens);
464
+ if (pitch.warn)
465
+ warn(pitch.warn, str, 0);
466
+ if (pitch.position)
467
+ multilineVars.clef.verticalPos = pitch.position - 6; // we get the position from the middle line, but want to offset it to the first ledger line.
468
+ break;
469
+ case "transpose":
470
+ tokens.shift();
471
+ if (tokens.length === 0) { warn("Expected = after transpose", str, 0); return ret; }
472
+ token = tokens.shift();
473
+ if (token.token !== "=") { warn("Expected = after transpose", str, token.start); break; }
474
+ if (tokens.length === 0) { warn("Expected parameter after transpose=", str, 0); return ret; }
475
+ if (tokens[0].type !== 'number') { warn("Expected number after transpose", str, tokens[0].start); break; }
476
+ multilineVars.clef.transpose = tokens[0].intt;
477
+ tokens.shift();
478
+ break;
479
+ case "stafflines":
480
+ tokens.shift();
481
+ if (tokens.length === 0) { warn("Expected = after stafflines", str, 0); return ret; }
482
+ token = tokens.shift();
483
+ if (token.token !== "=") { warn("Expected = after stafflines", str, token.start); break; }
484
+ if (tokens.length === 0) { warn("Expected parameter after stafflines=", str, 0); return ret; }
485
+ if (tokens[0].type !== 'number') { warn("Expected number after stafflines", str, tokens[0].start); break; }
486
+ multilineVars.clef.stafflines = tokens[0].intt;
487
+ tokens.shift();
488
+ break;
489
+ case "staffscale":
490
+ tokens.shift();
491
+ if (tokens.length === 0) { warn("Expected = after staffscale", str, 0); return ret; }
492
+ token = tokens.shift();
493
+ if (token.token !== "=") { warn("Expected = after staffscale", str, token.start); break; }
494
+ if (tokens.length === 0) { warn("Expected parameter after staffscale=", str, 0); return ret; }
495
+ if (tokens[0].type !== 'number') { warn("Expected number after staffscale", str, tokens[0].start); break; }
496
+ multilineVars.clef.staffscale = tokens[0].floatt;
497
+ tokens.shift();
498
+ break;
499
+ case "style":
500
+ tokens.shift();
501
+ if (tokens.length === 0) { warn("Expected = after style", str, 0); return ret; }
502
+ token = tokens.shift();
503
+ if (token.token !== "=") { warn("Expected = after style", str, token.start); break; }
504
+ if (tokens.length === 0) { warn("Expected parameter after style=", str, 0); return ret; }
505
+ switch (tokens[0].token) {
506
+ case "normal":
507
+ case "harmonic":
508
+ case "rhythm":
509
+ case "x":
510
+ multilineVars.style = tokens[0].token;
511
+ tokens.shift();
512
+ break;
513
+ default:
514
+ warn("error parsing style element: " + tokens[0].token, str, tokens[0].start);
515
+ break;
516
+ }
517
+ break;
518
+ case "clef":
519
+ tokens.shift();
520
+ if (tokens.length === 0) { warn("Expected = after clef", str, 0); return ret; }
521
+ token = tokens.shift();
522
+ if (token.token !== "=") { warn("Expected = after clef", str, token.start); break; }
523
+ if (tokens.length === 0) { warn("Expected parameter after clef=", str, 0); return ret; }
524
+ //break; yes, we want to fall through. That allows "clef=" to be optional.
525
+ case "treble":
526
+ case "bass":
527
+ case "alto":
528
+ case "tenor":
529
+ case "perc":
530
+ // clef is [clef=] [⟨clef type⟩] [⟨line number⟩] [+8|-8]
531
+ var clef = tokens.shift();
532
+ switch (clef.token) {
533
+ case 'treble':
534
+ case 'tenor':
535
+ case 'alto':
536
+ case 'bass':
537
+ case 'perc':
538
+ case 'none':
539
+ break;
540
+ case 'C': clef.token = 'alto'; break;
541
+ case 'F': clef.token = 'bass'; break;
542
+ case 'G': clef.token = 'treble'; break;
543
+ case 'c': clef.token = 'alto'; break;
544
+ case 'f': clef.token = 'bass'; break;
545
+ case 'g': clef.token = 'treble'; break;
546
+ default:
547
+ warn("Expected clef name. Found " + clef.token, str, clef.start);
548
+ break;
549
+ }
550
+ if (tokens.length > 0 && tokens[0].type === 'number') {
551
+ clef.token += tokens[0].token;
552
+ tokens.shift();
553
+ }
554
+ if (tokens.length > 1 && (tokens[0].token === '-' || tokens[0].token === '+') && tokens[1].token === '8') {
555
+ clef.token += tokens[0].token + tokens[1].token;
556
+ tokens.shift();
557
+ tokens.shift();
558
+ }
559
+ multilineVars.clef = {type: clef.token, verticalPos: calcMiddle(clef.token, 0)};
560
+ ret.foundClef = true;
561
+ break;
562
+ default:
563
+ warn("Unknown parameter: " + tokens[0].token, str, tokens[0].start);
564
+ tokens.shift();
565
+ }
566
+ }
567
+ return ret;
568
+ };
569
+
570
+ var setCurrentVoice = function(id) {
571
+ multilineVars.currentVoice = multilineVars.voices[id];
572
+ tune.setCurrentVoice(multilineVars.currentVoice.staffNum, multilineVars.currentVoice.index);
573
+ };
574
+
575
+ window.ABCJS.parse.parseKeyVoice.parseVoice = function(line, i, e) {
576
+ //First truncate the string to the first non-space character after V: through either the
577
+ //end of the line or a % character. Then remove trailing spaces, too.
578
+ var ret = tokenizer.getMeat(line, i, e);
579
+ var start = ret.start;
580
+ var end = ret.end;
581
+ //The first thing on the line is the ID. It can be any non-space string and terminates at the
582
+ //first space.
583
+ var id = tokenizer.getToken(line, start, end);
584
+ if (id.length === 0) {
585
+ warn("Expected a voice id", line, start);
586
+ return;
587
+ }
588
+ var isNew = false;
589
+ if (multilineVars.voices[id] === undefined) {
590
+ multilineVars.voices[id] = {};
591
+ isNew = true;
592
+ if (multilineVars.score_is_present)
593
+ warn("Can't have an unknown V: id when the %score directive is present", line, start);
594
+ }
595
+ start += id.length;
596
+ start += tokenizer.eatWhiteSpace(line, start);
597
+
598
+ var staffInfo = {startStaff: isNew};
599
+ var addNextTokenToStaffInfo = function(name) {
600
+ var attr = tokenizer.getVoiceToken(line, start, end);
601
+ if (attr.warn !== undefined)
602
+ warn("Expected value for " + name + " in voice: " + attr.warn, line, start);
603
+ else if (attr.token.length === 0 && line.charAt(start) !== '"')
604
+ warn("Expected value for " + name + " in voice", line, start);
605
+ else
606
+ staffInfo[name] = attr.token;
607
+ start += attr.len;
608
+ };
609
+ var addNextTokenToVoiceInfo = function(id, name, type) {
610
+ var attr = tokenizer.getVoiceToken(line, start, end);
611
+ if (attr.warn !== undefined)
612
+ warn("Expected value for " + name + " in voice: " + attr.warn, line, start);
613
+ else if (attr.token.length === 0 && line.charAt(start) !== '"')
614
+ warn("Expected value for " + name + " in voice", line, start);
615
+ else {
616
+ if (type === 'number')
617
+ attr.token = parseFloat(attr.token);
618
+ multilineVars.voices[id][name] = attr.token;
619
+ }
620
+ start += attr.len;
621
+ };
622
+
623
+ //Then the following items can occur in any order:
624
+ while (start < end) {
625
+ var token = tokenizer.getVoiceToken(line, start, end);
626
+ start += token.len;
627
+
628
+ if (token.warn) {
629
+ warn("Error parsing voice: " + token.warn, line, start);
630
+ } else {
631
+ var attr = null;
632
+ switch (token.token) {
633
+ case 'clef':
634
+ case 'cl':
635
+ addNextTokenToStaffInfo('clef');
636
+ // TODO-PER: check for a legal clef; do octavizing
637
+ var oct = 0;
638
+ // for (var ii = 0; ii < staffInfo.clef.length; ii++) {
639
+ // if (staffInfo.clef[ii] === ',') oct -= 7;
640
+ // else if (staffInfo.clef[ii] === "'") oct += 7;
641
+ // }
642
+ if (staffInfo.clef !== undefined) {
643
+ staffInfo.clef = staffInfo.clef.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
644
+ if (staffInfo.clef.indexOf('+16') !== -1) {
645
+ oct += 14;
646
+ staffInfo.clef = staffInfo.clef.replace('+16', '');
647
+ }
648
+ staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct);
649
+ }
650
+ break;
651
+ case 'treble':
652
+ case 'bass':
653
+ case 'tenor':
654
+ case 'alto':
655
+ case 'none':
656
+ case 'treble\'':
657
+ case 'bass\'':
658
+ case 'tenor\'':
659
+ case 'alto\'':
660
+ case 'none\'':
661
+ case 'treble\'\'':
662
+ case 'bass\'\'':
663
+ case 'tenor\'\'':
664
+ case 'alto\'\'':
665
+ case 'none\'\'':
666
+ case 'treble,':
667
+ case 'bass,':
668
+ case 'tenor,':
669
+ case 'alto,':
670
+ case 'none,':
671
+ case 'treble,,':
672
+ case 'bass,,':
673
+ case 'tenor,,':
674
+ case 'alto,,':
675
+ case 'none,,':
676
+ // TODO-PER: handle the octave indicators on the clef by changing the middle property
677
+ var oct2 = 0;
678
+ // for (var iii = 0; iii < token.token.length; iii++) {
679
+ // if (token.token[iii] === ',') oct2 -= 7;
680
+ // else if (token.token[iii] === "'") oct2 += 7;
681
+ // }
682
+ staffInfo.clef = token.token.replace(/[',]/g, ""); //'//comment for emacs formatting of regexp
683
+ staffInfo.verticalPos = calcMiddle(staffInfo.clef, oct2);
684
+ break;
685
+ case 'staves':
686
+ case 'stave':
687
+ case 'stv':
688
+ addNextTokenToStaffInfo('staves');
689
+ break;
690
+ case 'brace':
691
+ case 'brc':
692
+ addNextTokenToStaffInfo('brace');
693
+ break;
694
+ case 'bracket':
695
+ case 'brk':
696
+ addNextTokenToStaffInfo('bracket');
697
+ break;
698
+ case 'name':
699
+ case 'nm':
700
+ addNextTokenToStaffInfo('name');
701
+ break;
702
+ case 'subname':
703
+ case 'sname':
704
+ case 'snm':
705
+ addNextTokenToStaffInfo('subname');
706
+ break;
707
+ case 'merge':
708
+ staffInfo.startStaff = false;
709
+ break;
710
+ case 'stems':
711
+ attr = tokenizer.getVoiceToken(line, start, end);
712
+ if (attr.warn !== undefined)
713
+ warn("Expected value for stems in voice: " + attr.warn, line, start);
714
+ else if (attr.token === 'up' || attr.token === 'down')
715
+ multilineVars.voices[id].stem = attr.token;
716
+ else
717
+ warn("Expected up or down for voice stem", line, start);
718
+ start += attr.len;
719
+ break;
720
+ case 'up':
721
+ case 'down':
722
+ multilineVars.voices[id].stem = token.token;
723
+ break;
724
+ case 'middle':
725
+ case 'm':
726
+ addNextTokenToStaffInfo('verticalPos');
727
+ staffInfo.verticalPos = parseMiddle(staffInfo.verticalPos).mid;
728
+ break;
729
+ case 'gchords':
730
+ case 'gch':
731
+ multilineVars.voices[id].suppressChords = true;
732
+ break;
733
+ case 'space':
734
+ case 'spc':
735
+ addNextTokenToStaffInfo('spacing');
736
+ break;
737
+ case 'scale':
738
+ addNextTokenToVoiceInfo(id, 'scale', 'number');
739
+ break;
740
+ case 'transpose':
741
+ addNextTokenToVoiceInfo(id, 'transpose', 'number');
742
+ break;
743
+ }
744
+ }
745
+ start += tokenizer.eatWhiteSpace(line, start);
746
+ }
747
+
748
+ // now we've filled up staffInfo, figure out what to do with this voice
749
+ // TODO-PER: It is unclear from the standard and the examples what to do with brace, bracket, and staves, so they are ignored for now.
750
+ if (staffInfo.startStaff || multilineVars.staves.length === 0) {
751
+ multilineVars.staves.push({index: multilineVars.staves.length, meter: multilineVars.origMeter});
752
+ if (!multilineVars.score_is_present)
753
+ multilineVars.staves[multilineVars.staves.length-1].numVoices = 0;
754
+ }
755
+ if (multilineVars.voices[id].staffNum === undefined) {
756
+ // store where to write this for quick access later.
757
+ multilineVars.voices[id].staffNum = multilineVars.staves.length-1;
758
+ var vi = 0;
759
+ for(var v in multilineVars.voices) {
760
+ if(multilineVars.voices.hasOwnProperty(v)) {
761
+ if (multilineVars.voices[v].staffNum === multilineVars.voices[id].staffNum)
762
+ vi++;
763
+ }
764
+ }
765
+ multilineVars.voices[id].index = vi-1;
766
+ }
767
+ var s = multilineVars.staves[multilineVars.voices[id].staffNum];
768
+ if (!multilineVars.score_is_present)
769
+ s.numVoices++;
770
+ if (staffInfo.clef) s.clef = {type: staffInfo.clef, verticalPos: staffInfo.verticalPos};
771
+ if (staffInfo.spacing) s.spacing_below_offset = staffInfo.spacing;
772
+ if (staffInfo.verticalPos) s.verticalPos = staffInfo.verticalPos;
773
+
774
+ if (staffInfo.name) {if (s.name) s.name.push(staffInfo.name); else s.name = [ staffInfo.name ];}
775
+ if (staffInfo.subname) {if (s.subname) s.subname.push(staffInfo.subname); else s.subname = [ staffInfo.subname ];}
776
+
777
+ setCurrentVoice(id);
778
+ };
779
+
780
+ })();
781
+