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,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
|
+
|