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,521 @@
|
|
1
|
+
// abc_parse_header.js: parses a the header fields from a string representing ABC Music Notation into a usable internal structure.
|
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
|
+
window.ABCJS.parse.ParseHeader = function(tokenizer, warn, multilineVars, tune) {
|
26
|
+
window.ABCJS.parse.parseKeyVoice.initialize(tokenizer, warn, multilineVars, tune);
|
27
|
+
window.ABCJS.parse.parseDirective.initialize(tokenizer, warn, multilineVars, tune);
|
28
|
+
|
29
|
+
|
30
|
+
this.setTitle = function(title) {
|
31
|
+
if (multilineVars.hasMainTitle)
|
32
|
+
tune.addSubtitle(tokenizer.translateString(tokenizer.stripComment(title))); // display secondary title
|
33
|
+
else
|
34
|
+
{
|
35
|
+
tune.addMetaText("title", tokenizer.translateString(tokenizer.theReverser(tokenizer.stripComment(title))));
|
36
|
+
multilineVars.hasMainTitle = true;
|
37
|
+
}
|
38
|
+
};
|
39
|
+
|
40
|
+
this.setMeter = function(line) {
|
41
|
+
line = tokenizer.stripComment(line);
|
42
|
+
if (line === 'C') {
|
43
|
+
if (multilineVars.havent_set_length === true)
|
44
|
+
multilineVars.default_length = 0.125;
|
45
|
+
return {type: 'common_time'};
|
46
|
+
} else if (line === 'C|') {
|
47
|
+
if (multilineVars.havent_set_length === true)
|
48
|
+
multilineVars.default_length = 0.125;
|
49
|
+
return {type: 'cut_time'};
|
50
|
+
} else if (line === 'o') {
|
51
|
+
if (multilineVars.havent_set_length === true)
|
52
|
+
multilineVars.default_length = 0.125;
|
53
|
+
return {type: 'tempus_perfectum'};
|
54
|
+
} else if (line === 'c') {
|
55
|
+
if (multilineVars.havent_set_length === true)
|
56
|
+
multilineVars.default_length = 0.125;
|
57
|
+
return {type: 'tempus_imperfectum'};
|
58
|
+
} else if (line === 'o.') {
|
59
|
+
if (multilineVars.havent_set_length === true)
|
60
|
+
multilineVars.default_length = 0.125;
|
61
|
+
return {type: 'tempus_perfectum_prolatio'};
|
62
|
+
} else if (line === 'c.') {
|
63
|
+
if (multilineVars.havent_set_length === true)
|
64
|
+
multilineVars.default_length = 0.125;
|
65
|
+
return {type: 'tempus_imperfectum_prolatio'};
|
66
|
+
} else if (line.length === 0 || line.toLowerCase() === 'none') {
|
67
|
+
if (multilineVars.havent_set_length === true)
|
68
|
+
multilineVars.default_length = 0.125;
|
69
|
+
return null;
|
70
|
+
}
|
71
|
+
else
|
72
|
+
{
|
73
|
+
var tokens = tokenizer.tokenize(line, 0, line.length);
|
74
|
+
// the form is [open_paren] decimal [ plus|dot decimal ]... [close_paren] slash decimal [plus same_as_before]
|
75
|
+
try {
|
76
|
+
var parseNum = function() {
|
77
|
+
// handles this much: [open_paren] decimal [ plus|dot decimal ]... [close_paren]
|
78
|
+
var ret = {value: 0, num: ""};
|
79
|
+
|
80
|
+
var tok = tokens.shift();
|
81
|
+
if (tok.token === '(')
|
82
|
+
tok = tokens.shift();
|
83
|
+
while (1) {
|
84
|
+
if (tok.type !== 'number') throw "Expected top number of meter";
|
85
|
+
ret.value += parseInt(tok.token);
|
86
|
+
ret.num += tok.token;
|
87
|
+
if (tokens.length === 0 || tokens[0].token === '/') return ret;
|
88
|
+
tok = tokens.shift();
|
89
|
+
if (tok.token === ')') {
|
90
|
+
if (tokens.length === 0 || tokens[0].token === '/') return ret;
|
91
|
+
throw "Unexpected paren in meter";
|
92
|
+
}
|
93
|
+
if (tok.token !== '.' && tok.token !== '+') throw "Expected top number of meter";
|
94
|
+
ret.num += tok.token;
|
95
|
+
if (tokens.length === 0) throw "Expected top number of meter";
|
96
|
+
tok = tokens.shift();
|
97
|
+
}
|
98
|
+
return ret; // just to suppress warning
|
99
|
+
};
|
100
|
+
|
101
|
+
var parseFraction = function() {
|
102
|
+
// handles this much: parseNum slash decimal
|
103
|
+
var ret = parseNum();
|
104
|
+
if (tokens.length === 0) return ret;
|
105
|
+
var tok = tokens.shift();
|
106
|
+
if (tok.token !== '/') throw "Expected slash in meter";
|
107
|
+
tok = tokens.shift();
|
108
|
+
if (tok.type !== 'number') throw "Expected bottom number of meter";
|
109
|
+
ret.den = tok.token;
|
110
|
+
ret.value = ret.value / parseInt(ret.den);
|
111
|
+
return ret;
|
112
|
+
};
|
113
|
+
|
114
|
+
if (tokens.length === 0) throw "Expected meter definition in M: line";
|
115
|
+
var meter = {type: 'specified', value: [ ]};
|
116
|
+
var totalLength = 0;
|
117
|
+
while (1) {
|
118
|
+
var ret = parseFraction();
|
119
|
+
totalLength += ret.value;
|
120
|
+
var mv = { num: ret.num };
|
121
|
+
if (ret.den !== undefined)
|
122
|
+
mv.den = ret.den;
|
123
|
+
meter.value.push(mv);
|
124
|
+
if (tokens.length === 0) break;
|
125
|
+
//var tok = tokens.shift();
|
126
|
+
//if (tok.token !== '+') throw "Extra characters in M: line";
|
127
|
+
}
|
128
|
+
|
129
|
+
if (multilineVars.havent_set_length === true) {
|
130
|
+
multilineVars.default_length = totalLength < 0.75 ? 0.0625 : 0.125;
|
131
|
+
}
|
132
|
+
return meter;
|
133
|
+
} catch (e) {
|
134
|
+
warn(e, line, 0);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
return null;
|
138
|
+
};
|
139
|
+
|
140
|
+
this.calcTempo = function(relTempo) {
|
141
|
+
var dur = 1/4;
|
142
|
+
if (multilineVars.meter && multilineVars.meter.type === 'specified') {
|
143
|
+
dur = 1 / parseInt(multilineVars.meter.value[0].den);
|
144
|
+
} else if (multilineVars.origMeter && multilineVars.origMeter.type === 'specified') {
|
145
|
+
dur = 1 / parseInt(multilineVars.origMeter.value[0].den);
|
146
|
+
}
|
147
|
+
//var dur = multilineVars.default_length ? multilineVars.default_length : 1;
|
148
|
+
for (var i = 0; i < relTempo.duration; i++)
|
149
|
+
relTempo.duration[i] = dur * relTempo.duration[i];
|
150
|
+
return relTempo;
|
151
|
+
};
|
152
|
+
|
153
|
+
this.resolveTempo = function() {
|
154
|
+
if (multilineVars.tempo) { // If there's a tempo waiting to be resolved
|
155
|
+
this.calcTempo(multilineVars.tempo);
|
156
|
+
tune.metaText.tempo = multilineVars.tempo;
|
157
|
+
delete multilineVars.tempo;
|
158
|
+
}
|
159
|
+
};
|
160
|
+
|
161
|
+
this.addUserDefinition = function(line, start, end) {
|
162
|
+
var equals = line.indexOf('=', start);
|
163
|
+
if (equals === -1) {
|
164
|
+
warn("Need an = in a macro definition", line, start);
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
|
168
|
+
var before = window.ABCJS.parse.strip(line.substring(start, equals));
|
169
|
+
var after = window.ABCJS.parse.strip(line.substring(equals+1));
|
170
|
+
|
171
|
+
if (before.length !== 1) {
|
172
|
+
warn("Macro definitions can only be one character", line, start);
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
var legalChars = "HIJKLMNOPQRSTUVWXYhijklmnopqrstuvw~";
|
176
|
+
if (legalChars.indexOf(before) === -1) {
|
177
|
+
warn("Macro definitions must be H-Y, h-w, or tilde", line, start);
|
178
|
+
return;
|
179
|
+
}
|
180
|
+
if (after.length === 0) {
|
181
|
+
warn("Missing macro definition", line, start);
|
182
|
+
return;
|
183
|
+
}
|
184
|
+
if (multilineVars.macros === undefined)
|
185
|
+
multilineVars.macros = {};
|
186
|
+
multilineVars.macros[before] = after;
|
187
|
+
};
|
188
|
+
|
189
|
+
this.setDefaultLength = function(line, start, end) {
|
190
|
+
var len = window.ABCJS.parse.gsub(line.substring(start, end), " ", "");
|
191
|
+
var len_arr = len.split('/');
|
192
|
+
if (len_arr.length === 2) {
|
193
|
+
var n = parseInt(len_arr[0]);
|
194
|
+
var d = parseInt(len_arr[1]);
|
195
|
+
if (d > 0) {
|
196
|
+
multilineVars.default_length = n / d; // a whole note is 1
|
197
|
+
multilineVars.havent_set_length = false;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
};
|
201
|
+
|
202
|
+
this.setTempo = function(line, start, end) {
|
203
|
+
//Q - tempo; can be used to specify the notes per minute, e.g. If
|
204
|
+
//the meter denominator is a 4 note then Q:120 or Q:C=120
|
205
|
+
//is 120 quarter notes per minute. Similarly Q:C3=40 would be 40
|
206
|
+
//dotted half notes per minute. An absolute tempo may also be
|
207
|
+
//set, e.g. Q:1/8=120 is 120 eighth notes per minute,
|
208
|
+
//irrespective of the meter's denominator.
|
209
|
+
//
|
210
|
+
// This is either a number, "C=number", "Cnumber=number", or fraction [fraction...]=number
|
211
|
+
// It depends on the M: field, which may either not be present, or may appear after this.
|
212
|
+
// If M: is not present, an eighth note is used.
|
213
|
+
// That means that this field can't be calculated until the end, if it is the first three types, since we don't know if we'll see an M: field.
|
214
|
+
// So, if it is the fourth type, set it here, otherwise, save the info in the multilineVars.
|
215
|
+
// The temporary variables we keep are the duration and the bpm. In the first two forms, the duration is 1.
|
216
|
+
// In addition, a quoted string may both precede and follow. If a quoted string is present, then the duration part is optional.
|
217
|
+
try {
|
218
|
+
var tokens = tokenizer.tokenize(line, start, end);
|
219
|
+
|
220
|
+
if (tokens.length === 0) throw "Missing parameter in Q: field";
|
221
|
+
|
222
|
+
var tempo = {};
|
223
|
+
var delaySet = true;
|
224
|
+
var token = tokens.shift();
|
225
|
+
if (token.type === 'quote') {
|
226
|
+
tempo.preString = token.token;
|
227
|
+
token = tokens.shift();
|
228
|
+
if (tokens.length === 0) { // It's ok to just get a string for the tempo
|
229
|
+
return {type: 'immediate', tempo: tempo};
|
230
|
+
}
|
231
|
+
}
|
232
|
+
if (token.type === 'alpha' && token.token === 'C') { // either type 2 or type 3
|
233
|
+
if (tokens.length === 0) throw "Missing tempo after C in Q: field";
|
234
|
+
token = tokens.shift();
|
235
|
+
if (token.type === 'punct' && token.token === '=') {
|
236
|
+
// This is a type 2 format. The duration is an implied 1
|
237
|
+
if (tokens.length === 0) throw "Missing tempo after = in Q: field";
|
238
|
+
token = tokens.shift();
|
239
|
+
if (token.type !== 'number') throw "Expected number after = in Q: field";
|
240
|
+
tempo.duration = [1];
|
241
|
+
tempo.bpm = parseInt(token.token);
|
242
|
+
} else if (token.type === 'number') {
|
243
|
+
// This is a type 3 format.
|
244
|
+
tempo.duration = [parseInt(token.token)];
|
245
|
+
if (tokens.length === 0) throw "Missing = after duration in Q: field";
|
246
|
+
token = tokens.shift();
|
247
|
+
if (token.type !== 'punct' || token.token !== '=') throw "Expected = after duration in Q: field";
|
248
|
+
if (tokens.length === 0) throw "Missing tempo after = in Q: field";
|
249
|
+
token = tokens.shift();
|
250
|
+
if (token.type !== 'number') throw "Expected number after = in Q: field";
|
251
|
+
tempo.bpm = parseInt(token.token);
|
252
|
+
} else throw "Expected number or equal after C in Q: field";
|
253
|
+
|
254
|
+
} else if (token.type === 'number') { // either type 1 or type 4
|
255
|
+
var num = parseInt(token.token);
|
256
|
+
if (tokens.length === 0 || tokens[0].type === 'quote') {
|
257
|
+
// This is type 1
|
258
|
+
tempo.duration = [1];
|
259
|
+
tempo.bpm = num;
|
260
|
+
} else { // This is type 4
|
261
|
+
delaySet = false;
|
262
|
+
token = tokens.shift();
|
263
|
+
if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field";
|
264
|
+
token = tokens.shift();
|
265
|
+
if (token.type !== 'number') throw "Expected fraction in Q: field";
|
266
|
+
var den = parseInt(token.token);
|
267
|
+
tempo.duration = [num/den];
|
268
|
+
// We got the first fraction, keep getting more as long as we find them.
|
269
|
+
while (tokens.length > 0 && tokens[0].token !== '=' && tokens[0].type !== 'quote') {
|
270
|
+
token = tokens.shift();
|
271
|
+
if (token.type !== 'number') throw "Expected fraction in Q: field";
|
272
|
+
num = parseInt(token.token);
|
273
|
+
token = tokens.shift();
|
274
|
+
if (token.type !== 'punct' && token.token !== '/') throw "Expected fraction in Q: field";
|
275
|
+
token = tokens.shift();
|
276
|
+
if (token.type !== 'number') throw "Expected fraction in Q: field";
|
277
|
+
den = parseInt(token.token);
|
278
|
+
tempo.duration.push(num/den);
|
279
|
+
}
|
280
|
+
token = tokens.shift();
|
281
|
+
if (token.type !== 'punct' && token.token !== '=') throw "Expected = in Q: field";
|
282
|
+
token = tokens.shift();
|
283
|
+
if (token.type !== 'number') throw "Expected tempo in Q: field";
|
284
|
+
tempo.bpm = parseInt(token.token);
|
285
|
+
}
|
286
|
+
} else throw "Unknown value in Q: field";
|
287
|
+
if (tokens.length !== 0) {
|
288
|
+
token = tokens.shift();
|
289
|
+
if (token.type === 'quote') {
|
290
|
+
tempo.postString = token.token;
|
291
|
+
token = tokens.shift();
|
292
|
+
}
|
293
|
+
if (tokens.length !== 0) throw "Unexpected string at end of Q: field";
|
294
|
+
}
|
295
|
+
if (multilineVars.printTempo === false)
|
296
|
+
tempo.suppress = true;
|
297
|
+
return {type: delaySet?'delaySet':'immediate', tempo: tempo};
|
298
|
+
} catch (msg) {
|
299
|
+
warn(msg, line, start);
|
300
|
+
return {type: 'none'};
|
301
|
+
}
|
302
|
+
};
|
303
|
+
|
304
|
+
this.letter_to_inline_header = function(line, i)
|
305
|
+
{
|
306
|
+
var ws = tokenizer.eatWhiteSpace(line, i);
|
307
|
+
i +=ws;
|
308
|
+
if (line.length >= i+5 && line.charAt(i) === '[' && line.charAt(i+2) === ':') {
|
309
|
+
var e = line.indexOf(']', i);
|
310
|
+
switch(line.substring(i, i+3))
|
311
|
+
{
|
312
|
+
case "[I:":
|
313
|
+
var err = window.ABCJS.parse.parseDirective.addDirective(line.substring(i+3, e));
|
314
|
+
if (err) warn(err, line, i);
|
315
|
+
return [ e-i+1+ws ];
|
316
|
+
case "[M:":
|
317
|
+
var meter = this.setMeter(line.substring(i+3, e));
|
318
|
+
if (tune.hasBeginMusic() && meter)
|
319
|
+
tune.appendStartingElement('meter', -1, -1, meter);
|
320
|
+
else
|
321
|
+
multilineVars.meter = meter;
|
322
|
+
return [ e-i+1+ws ];
|
323
|
+
case "[K:":
|
324
|
+
var result = window.ABCJS.parse.parseKeyVoice.parseKey(line.substring(i+3, e));
|
325
|
+
if (result.foundClef && tune.hasBeginMusic())
|
326
|
+
tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
|
327
|
+
if (result.foundKey && tune.hasBeginMusic())
|
328
|
+
tune.appendStartingElement('key', -1, -1, window.ABCJS.parse.parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
|
329
|
+
return [ e-i+1+ws ];
|
330
|
+
case "[P:":
|
331
|
+
tune.appendElement('part', -1, -1, {title: line.substring(i+3, e)});
|
332
|
+
return [ e-i+1+ws ];
|
333
|
+
case "[L:":
|
334
|
+
this.setDefaultLength(line, i+3, e);
|
335
|
+
return [ e-i+1+ws ];
|
336
|
+
case "[Q:":
|
337
|
+
if (e > 0) {
|
338
|
+
var tempo = this.setTempo(line, i+3, e);
|
339
|
+
if (tempo.type === 'delaySet') tune.appendElement('tempo', -1, -1, this.calcTempo(tempo.tempo));
|
340
|
+
else if (tempo.type === 'immediate') tune.appendElement('tempo', -1, -1, tempo.tempo);
|
341
|
+
return [ e-i+1+ws, line.charAt(i+1), line.substring(i+3, e)];
|
342
|
+
}
|
343
|
+
break;
|
344
|
+
case "[V:":
|
345
|
+
if (e > 0) {
|
346
|
+
window.ABCJS.parse.parseKeyVoice.parseVoice(line, i+3, e);
|
347
|
+
//startNewLine();
|
348
|
+
return [ e-i+1+ws, line.charAt(i+1), line.substring(i+3, e)];
|
349
|
+
}
|
350
|
+
break;
|
351
|
+
|
352
|
+
default:
|
353
|
+
// TODO: complain about unhandled header
|
354
|
+
}
|
355
|
+
}
|
356
|
+
return [ 0 ];
|
357
|
+
};
|
358
|
+
|
359
|
+
this.letter_to_body_header = function(line, i)
|
360
|
+
{
|
361
|
+
if (line.length >= i+3) {
|
362
|
+
switch(line.substring(i, i+2))
|
363
|
+
{
|
364
|
+
case "I:":
|
365
|
+
var err = window.ABCJS.parse.parseDirective.addDirective(line.substring(i+2));
|
366
|
+
if (err) warn(err, line, i);
|
367
|
+
return [ line.length ];
|
368
|
+
case "M:":
|
369
|
+
var meter = this.setMeter(line.substring(i+2));
|
370
|
+
if (tune.hasBeginMusic() && meter)
|
371
|
+
tune.appendStartingElement('meter', -1, -1, meter);
|
372
|
+
return [ line.length ];
|
373
|
+
case "K:":
|
374
|
+
var result = window.ABCJS.parse.parseKeyVoice.parseKey(line.substring(i+2));
|
375
|
+
if (result.foundClef && tune.hasBeginMusic())
|
376
|
+
tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
|
377
|
+
if (result.foundKey && tune.hasBeginMusic())
|
378
|
+
tune.appendStartingElement('key', -1, -1, window.ABCJS.parse.parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
|
379
|
+
return [ line.length ];
|
380
|
+
case "P:":
|
381
|
+
if (tune.hasBeginMusic())
|
382
|
+
tune.appendElement('part', -1, -1, {title: line.substring(i+2)});
|
383
|
+
return [ line.length ];
|
384
|
+
case "L:":
|
385
|
+
this.setDefaultLength(line, i+2, line.length);
|
386
|
+
return [ line.length ];
|
387
|
+
case "Q:":
|
388
|
+
var e = line.indexOf('\x12', i+2);
|
389
|
+
if (e === -1) e = line.length;
|
390
|
+
var tempo = this.setTempo(line, i+2, e);
|
391
|
+
if (tempo.type === 'delaySet') tune.appendElement('tempo', -1, -1, this.calcTempo(tempo.tempo));
|
392
|
+
else if (tempo.type === 'immediate') tune.appendElement('tempo', -1, -1, tempo.tempo);
|
393
|
+
return [ e, line.charAt(i), window.ABCJS.parse.strip(line.substring(i+2))];
|
394
|
+
case "V:":
|
395
|
+
window.ABCJS.parse.parseKeyVoice.parseVoice(line, 2, line.length);
|
396
|
+
// startNewLine();
|
397
|
+
return [ line.length, line.charAt(i), window.ABCJS.parse(line.substring(i+2))];
|
398
|
+
default:
|
399
|
+
// TODO: complain about unhandled header
|
400
|
+
}
|
401
|
+
}
|
402
|
+
return [ 0 ];
|
403
|
+
};
|
404
|
+
|
405
|
+
var metaTextHeaders = {
|
406
|
+
A: 'author',
|
407
|
+
B: 'book',
|
408
|
+
C: 'composer',
|
409
|
+
D: 'discography',
|
410
|
+
F: 'url',
|
411
|
+
G: 'group',
|
412
|
+
I: 'instruction',
|
413
|
+
N: 'notes',
|
414
|
+
O: 'origin',
|
415
|
+
R: 'rhythm',
|
416
|
+
S: 'source',
|
417
|
+
W: 'unalignedWords',
|
418
|
+
Z: 'transcription'
|
419
|
+
};
|
420
|
+
|
421
|
+
this.parseHeader = function(line) {
|
422
|
+
if (window.ABCJS.parse.startsWith(line, '%%')) {
|
423
|
+
var err = window.ABCJS.parse.parseDirective.addDirective(line.substring(2));
|
424
|
+
if (err) warn(err, line, 2);
|
425
|
+
return {};
|
426
|
+
}
|
427
|
+
line = tokenizer.stripComment(line);
|
428
|
+
if (line.length === 0)
|
429
|
+
return {};
|
430
|
+
|
431
|
+
if (line.length >= 2) {
|
432
|
+
if (line.charAt(1) === ':') {
|
433
|
+
var nextLine = "";
|
434
|
+
if (line.indexOf('\x12') >= 0 && line.charAt(0) !== 'w') { // w: is the only header field that can have a continuation.
|
435
|
+
nextLine = line.substring(line.indexOf('\x12')+1);
|
436
|
+
line = line.substring(0, line.indexOf('\x12')); //This handles a continuation mark on a header field
|
437
|
+
}
|
438
|
+
var field = metaTextHeaders[line.charAt(0)];
|
439
|
+
if (field !== undefined) {
|
440
|
+
if (field === 'unalignedWords')
|
441
|
+
tune.addMetaTextArray(field, window.ABCJS.parse.parseDirective.parseFontChangeLine(tokenizer.translateString(tokenizer.stripComment(line.substring(2)))));
|
442
|
+
else
|
443
|
+
tune.addMetaText(field, tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
444
|
+
return {};
|
445
|
+
} else {
|
446
|
+
switch(line.charAt(0))
|
447
|
+
{
|
448
|
+
case 'H':
|
449
|
+
tune.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
450
|
+
multilineVars.is_in_history = true;
|
451
|
+
break;
|
452
|
+
case 'K':
|
453
|
+
// since the key is the last thing that can happen in the header, we can resolve the tempo now
|
454
|
+
this.resolveTempo();
|
455
|
+
var result = window.ABCJS.parse.parseKeyVoice.parseKey(line.substring(2));
|
456
|
+
if (!multilineVars.is_in_header && tune.hasBeginMusic()) {
|
457
|
+
if (result.foundClef)
|
458
|
+
tune.appendStartingElement('clef', -1, -1, multilineVars.clef);
|
459
|
+
if (result.foundKey)
|
460
|
+
tune.appendStartingElement('key', -1, -1, window.ABCJS.parse.parseKeyVoice.fixKey(multilineVars.clef, multilineVars.key));
|
461
|
+
}
|
462
|
+
multilineVars.is_in_header = false; // The first key signifies the end of the header.
|
463
|
+
break;
|
464
|
+
case 'L':
|
465
|
+
this.setDefaultLength(line, 2, line.length);
|
466
|
+
break;
|
467
|
+
case 'M':
|
468
|
+
multilineVars.origMeter = multilineVars.meter = this.setMeter(line.substring(2));
|
469
|
+
break;
|
470
|
+
case 'P':
|
471
|
+
// TODO-PER: There is more to do with parts, but the writer doesn't care.
|
472
|
+
if (multilineVars.is_in_header)
|
473
|
+
tune.addMetaText("partOrder", tokenizer.translateString(tokenizer.stripComment(line.substring(2))));
|
474
|
+
else
|
475
|
+
multilineVars.partForNextLine = tokenizer.translateString(tokenizer.stripComment(line.substring(2)));
|
476
|
+
break;
|
477
|
+
case 'Q':
|
478
|
+
var tempo = this.setTempo(line, 2, line.length);
|
479
|
+
if (tempo.type === 'delaySet') multilineVars.tempo = tempo.tempo;
|
480
|
+
else if (tempo.type === 'immediate') tune.metaText.tempo = tempo.tempo;
|
481
|
+
break;
|
482
|
+
case 'T':
|
483
|
+
this.setTitle(line.substring(2));
|
484
|
+
break;
|
485
|
+
case 'U':
|
486
|
+
this.addUserDefinition(line, 2, line.length);
|
487
|
+
break;
|
488
|
+
case 'V':
|
489
|
+
window.ABCJS.parse.parseKeyVoice.parseVoice(line, 2, line.length);
|
490
|
+
if (!multilineVars.is_in_header)
|
491
|
+
return {newline: true};
|
492
|
+
break;
|
493
|
+
case 's':
|
494
|
+
return {symbols: true};
|
495
|
+
case 'w':
|
496
|
+
return {words: true};
|
497
|
+
case 'X':
|
498
|
+
break;
|
499
|
+
case 'E':
|
500
|
+
case 'm':
|
501
|
+
warn("Ignored header", line, 0);
|
502
|
+
break;
|
503
|
+
default:
|
504
|
+
// It wasn't a recognized header value, so parse it as music.
|
505
|
+
if (nextLine.length)
|
506
|
+
nextLine = "\x12" + nextLine;
|
507
|
+
//parseRegularMusicLine(line+nextLine);
|
508
|
+
//nextLine = "";
|
509
|
+
return {regular: true, str: line+nextLine};
|
510
|
+
}
|
511
|
+
}
|
512
|
+
if (nextLine.length > 0)
|
513
|
+
return {recurse: true, str: nextLine};
|
514
|
+
return {};
|
515
|
+
}
|
516
|
+
}
|
517
|
+
|
518
|
+
// If we got this far, we have a regular line of mulsic
|
519
|
+
return {regular: true, str: line};
|
520
|
+
};
|
521
|
+
};
|