abcjs-rails 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,76 @@
|
|
1
|
+
// abc_parse.js: parses 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.clone = function(source) {
|
26
|
+
var destination = {};
|
27
|
+
for (var property in source)
|
28
|
+
if (source.hasOwnProperty(property))
|
29
|
+
destination[property] = source[property];
|
30
|
+
return destination;
|
31
|
+
};
|
32
|
+
|
33
|
+
window.ABCJS.parse.gsub = function(source, pattern, replacement) {
|
34
|
+
return source.split(pattern).join(replacement);
|
35
|
+
};
|
36
|
+
|
37
|
+
window.ABCJS.parse.strip = function(str) {
|
38
|
+
return str.replace(/^\s+/, '').replace(/\s+$/, '');
|
39
|
+
};
|
40
|
+
|
41
|
+
window.ABCJS.parse.startsWith = function(str, pattern) {
|
42
|
+
return str.indexOf(pattern) === 0;
|
43
|
+
};
|
44
|
+
|
45
|
+
window.ABCJS.parse.endsWith = function(str, pattern) {
|
46
|
+
var d = str.length - pattern.length;
|
47
|
+
return d >= 0 && str.lastIndexOf(pattern) === d;
|
48
|
+
};
|
49
|
+
|
50
|
+
window.ABCJS.parse.each = function(arr, iterator, context) {
|
51
|
+
for (var i = 0, length = arr.length; i < length; i++)
|
52
|
+
iterator.apply(context, [arr[i],i]);
|
53
|
+
};
|
54
|
+
|
55
|
+
window.ABCJS.parse.last = function(arr) {
|
56
|
+
if (arr.length === 0)
|
57
|
+
return null;
|
58
|
+
return arr[arr.length-1];
|
59
|
+
};
|
60
|
+
|
61
|
+
window.ABCJS.parse.compact = function(arr) {
|
62
|
+
var output = [];
|
63
|
+
for (var i = 0; i < arr.length; i++) {
|
64
|
+
if (arr[i])
|
65
|
+
output.push(arr[i]);
|
66
|
+
}
|
67
|
+
return output;
|
68
|
+
};
|
69
|
+
|
70
|
+
window.ABCJS.parse.detect = function(arr, iterator) {
|
71
|
+
for (var i = 0; i < arr.length; i++) {
|
72
|
+
if (iterator(arr[i]))
|
73
|
+
return true;
|
74
|
+
}
|
75
|
+
return false;
|
76
|
+
};
|
@@ -0,0 +1,1385 @@
|
|
1
|
+
// abc_parse.js: parses 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.Parse = function() {
|
26
|
+
var tune = new window.ABCJS.data.Tune();
|
27
|
+
var tokenizer = new window.ABCJS.parse.tokenizer();
|
28
|
+
|
29
|
+
this.getTune = function() {
|
30
|
+
return tune;
|
31
|
+
};
|
32
|
+
|
33
|
+
var multilineVars = {
|
34
|
+
reset: function() {
|
35
|
+
for (var property in this) {
|
36
|
+
if (this.hasOwnProperty(property) && typeof this[property] !== "function") {
|
37
|
+
delete this[property];
|
38
|
+
}
|
39
|
+
}
|
40
|
+
this.iChar = 0;
|
41
|
+
this.key = {accidentals: [], root: 'none', acc: '', mode: '' };
|
42
|
+
this.meter = {type: 'specified', value: [{num: '4', den: '4'}]}; // if no meter is specified, there is an implied one.
|
43
|
+
this.origMeter = {type: 'specified', value: [{num: '4', den: '4'}]}; // this is for new voices that are created after we set the meter.
|
44
|
+
this.hasMainTitle = false;
|
45
|
+
this.default_length = 0.125;
|
46
|
+
this.clef = { type: 'treble', verticalPos: 0 };
|
47
|
+
this.next_note_duration = 0;
|
48
|
+
this.start_new_line = true;
|
49
|
+
this.is_in_header = true;
|
50
|
+
this.is_in_history = false;
|
51
|
+
this.partForNextLine = "";
|
52
|
+
this.havent_set_length = true;
|
53
|
+
this.voices = {};
|
54
|
+
this.staves = [];
|
55
|
+
this.macros = {};
|
56
|
+
this.currBarNumber = 1;
|
57
|
+
this.inTextBlock = false;
|
58
|
+
this.inPsBlock = false;
|
59
|
+
this.ignoredDecorations = [];
|
60
|
+
this.textBlock = "";
|
61
|
+
this.score_is_present = false; // Can't have original V: lines when there is the score directive
|
62
|
+
this.inEnding = false;
|
63
|
+
this.inTie = false;
|
64
|
+
this.inTieChord = {};
|
65
|
+
}
|
66
|
+
};
|
67
|
+
|
68
|
+
var addWarning = function(str) {
|
69
|
+
if (!multilineVars.warnings)
|
70
|
+
multilineVars.warnings = [];
|
71
|
+
multilineVars.warnings.push(str);
|
72
|
+
};
|
73
|
+
|
74
|
+
var encode = function(str) {
|
75
|
+
var ret = window.ABCJS.parse.gsub(str, '\x12', ' ');
|
76
|
+
ret = window.ABCJS.parse.gsub(ret, '&', '&');
|
77
|
+
ret = window.ABCJS.parse.gsub(ret, '<', '<');
|
78
|
+
return window.ABCJS.parse.gsub(ret, '>', '>');
|
79
|
+
};
|
80
|
+
|
81
|
+
var warn = function(str, line, col_num) {
|
82
|
+
var bad_char = line.charAt(col_num);
|
83
|
+
if (bad_char === ' ')
|
84
|
+
bad_char = "SPACE";
|
85
|
+
var clean_line = encode(line.substring(0, col_num)) +
|
86
|
+
'<span style="text-decoration:underline;font-size:1.3em;font-weight:bold;">' + bad_char + '</span>' +
|
87
|
+
encode(line.substring(col_num+1));
|
88
|
+
addWarning("Music Line:" + tune.getNumLines() + ":" + (col_num+1) + ': ' + str + ": " + clean_line);
|
89
|
+
};
|
90
|
+
var header = new window.ABCJS.parse.ParseHeader(tokenizer, warn, multilineVars, tune);
|
91
|
+
|
92
|
+
this.getWarnings = function() {
|
93
|
+
return multilineVars.warnings;
|
94
|
+
};
|
95
|
+
|
96
|
+
var letter_to_chord = function(line, i)
|
97
|
+
{
|
98
|
+
if (line.charAt(i) === '"')
|
99
|
+
{
|
100
|
+
var chord = tokenizer.getBrackettedSubstring(line, i, 5);
|
101
|
+
if (!chord[2])
|
102
|
+
warn("Missing the closing quote while parsing the chord symbol", line , i);
|
103
|
+
// If it starts with ^, then the chord appears above.
|
104
|
+
// If it starts with _ then the chord appears below.
|
105
|
+
// (note that the 2.0 draft standard defines them as not chords, but annotations and also defines @.)
|
106
|
+
if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '^') {
|
107
|
+
chord[1] = chord[1].substring(1);
|
108
|
+
chord[2] = 'above';
|
109
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '_') {
|
110
|
+
chord[1] = chord[1].substring(1);
|
111
|
+
chord[2] = 'below';
|
112
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '<') {
|
113
|
+
chord[1] = chord[1].substring(1);
|
114
|
+
chord[2] = 'left';
|
115
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '>') {
|
116
|
+
chord[1] = chord[1].substring(1);
|
117
|
+
chord[2] = 'right';
|
118
|
+
} else if (chord[0] > 0 && chord[1].length > 0 && chord[1].charAt(0) === '@') {
|
119
|
+
// @-15,5.7
|
120
|
+
chord[1] = chord[1].substring(1);
|
121
|
+
var x = tokenizer.getFloat(chord[1]);
|
122
|
+
if (x.digits === 0)
|
123
|
+
warn("Missing first position in absolutely positioned annotation.", line , i);
|
124
|
+
chord[1] = chord[1].substring(x.digits);
|
125
|
+
if (chord[1][0] !== ',')
|
126
|
+
warn("Missing comma absolutely positioned annotation.", line , i);
|
127
|
+
chord[1] = chord[1].substring(1);
|
128
|
+
var y = tokenizer.getFloat(chord[1]);
|
129
|
+
if (y.digits === 0)
|
130
|
+
warn("Missing second position in absolutely positioned annotation.", line , i);
|
131
|
+
chord[1] = chord[1].substring(y.digits);
|
132
|
+
var ws = tokenizer.skipWhiteSpace(chord[1]);
|
133
|
+
chord[1] = chord[1].substring(ws);
|
134
|
+
chord[2] = null;
|
135
|
+
chord[3] = { x: x.value, y: y.value };
|
136
|
+
} else {
|
137
|
+
chord[1] = chord[1].replace(/([ABCDEFG])b/g, "$1♭");
|
138
|
+
chord[1] = chord[1].replace(/([ABCDEFG])#/g, "$1♯");
|
139
|
+
chord[2] = 'default';
|
140
|
+
}
|
141
|
+
return chord;
|
142
|
+
}
|
143
|
+
return [0, ""];
|
144
|
+
};
|
145
|
+
|
146
|
+
var legalAccents = [ "trill", "lowermordent", "uppermordent", "mordent", "pralltriller", "accent",
|
147
|
+
"fermata", "invertedfermata", "tenuto", "0", "1", "2", "3", "4", "5", "+", "wedge",
|
148
|
+
"open", "thumb", "snap", "turn", "roll", "breath", "shortphrase", "mediumphrase", "longphrase",
|
149
|
+
"segno", "coda", "D.S.", "D.C.", "fine", "crescendo(", "crescendo)", "diminuendo(", "diminuendo)",
|
150
|
+
"p", "pp", "f", "ff", "mf", "mp", "ppp", "pppp", "fff", "ffff", "sfz", "repeatbar", "repeatbar2", "slide",
|
151
|
+
"upbow", "downbow", "/", "//", "///", "////", "trem1", "trem2", "trem3", "trem4",
|
152
|
+
"turnx", "invertedturn", "invertedturnx", "trill(", "trill)", "arpeggio", "xstem", "mark",
|
153
|
+
"style=normal", "style=harmonic", "style=rhythm", "style=x"
|
154
|
+
];
|
155
|
+
var accentPsuedonyms = [ ["<", "accent"], [">", "accent"], ["tr", "trill"], ["<(", "crescendo("], ["<)", "crescendo)"],
|
156
|
+
[">(", "diminuendo("], [">)", "diminuendo)"], ["plus", "+"], [ "emphasis", "accent"] ];
|
157
|
+
var letter_to_accent = function(line, i)
|
158
|
+
{
|
159
|
+
var macro = multilineVars.macros[line.charAt(i)];
|
160
|
+
|
161
|
+
if (macro !== undefined) {
|
162
|
+
if (macro.charAt(0) === '!' || macro.charAt(0) === '+')
|
163
|
+
macro = macro.substring(1);
|
164
|
+
if (macro.charAt(macro.length-1) === '!' || macro.charAt(macro.length-1) === '+')
|
165
|
+
macro = macro.substring(0, macro.length-1);
|
166
|
+
if (window.ABCJS.parse.detect(legalAccents, function(acc) {
|
167
|
+
return (macro === acc);
|
168
|
+
}))
|
169
|
+
return [ 1, macro ];
|
170
|
+
else {
|
171
|
+
if (!window.ABCJS.parse.detect(multilineVars.ignoredDecorations, function(dec) {
|
172
|
+
return (macro === dec);
|
173
|
+
}))
|
174
|
+
warn("Unknown macro: " + macro, line, i);
|
175
|
+
return [1, '' ];
|
176
|
+
}
|
177
|
+
}
|
178
|
+
switch (line.charAt(i))
|
179
|
+
{
|
180
|
+
case '.':return [1, 'staccato'];
|
181
|
+
case 'u':return [1, 'upbow'];
|
182
|
+
case 'v':return [1, 'downbow'];
|
183
|
+
case '~':return [1, 'irishroll'];
|
184
|
+
case '!':
|
185
|
+
case '+':
|
186
|
+
var ret = tokenizer.getBrackettedSubstring(line, i, 5);
|
187
|
+
// Be sure that the accent is recognizable.
|
188
|
+
if (ret[1].length > 0 && (ret[1].charAt(0) === '^' || ret[1].charAt(0) ==='_'))
|
189
|
+
ret[1] = ret[1].substring(1); // TODO-PER: The test files have indicators forcing the ornament to the top or bottom, but that isn't in the standard. We'll just ignore them.
|
190
|
+
if (window.ABCJS.parse.detect(legalAccents, function(acc) {
|
191
|
+
return (ret[1] === acc);
|
192
|
+
}))
|
193
|
+
return ret;
|
194
|
+
|
195
|
+
if (window.ABCJS.parse.detect(accentPsuedonyms, function(acc) {
|
196
|
+
if (ret[1] === acc[0]) {
|
197
|
+
ret[1] = acc[1];
|
198
|
+
return true;
|
199
|
+
} else
|
200
|
+
return false;
|
201
|
+
}))
|
202
|
+
return ret;
|
203
|
+
|
204
|
+
// We didn't find the accent in the list, so consume the space, but don't return an accent.
|
205
|
+
// Although it is possible that ! was used as a line break, so accept that.
|
206
|
+
if (line.charAt(i) === '!' && (ret[0] === 1 || line.charAt(i+ret[0]-1) !== '!'))
|
207
|
+
return [1, null ];
|
208
|
+
warn("Unknown decoration: " + ret[1], line, i);
|
209
|
+
ret[1] = "";
|
210
|
+
return ret;
|
211
|
+
case 'H':return [1, 'fermata'];
|
212
|
+
case 'J':return [1, 'slide'];
|
213
|
+
case 'L':return [1, 'accent'];
|
214
|
+
case 'M':return [1, 'mordent'];
|
215
|
+
case 'O':return[1, 'coda'];
|
216
|
+
case 'P':return[1, 'pralltriller'];
|
217
|
+
case 'R':return [1, 'roll'];
|
218
|
+
case 'S':return [1, 'segno'];
|
219
|
+
case 'T':return [1, 'trill'];
|
220
|
+
}
|
221
|
+
return [0, 0];
|
222
|
+
};
|
223
|
+
|
224
|
+
var letter_to_spacer = function(line, i)
|
225
|
+
{
|
226
|
+
var start = i;
|
227
|
+
while (tokenizer.isWhiteSpace(line.charAt(i)))
|
228
|
+
i++;
|
229
|
+
return [ i-start ];
|
230
|
+
};
|
231
|
+
|
232
|
+
// returns the class of the bar line
|
233
|
+
// the number of the repeat
|
234
|
+
// and the number of characters used up
|
235
|
+
// if 0 is returned, then the next element was not a bar line
|
236
|
+
var letter_to_bar = function(line, curr_pos)
|
237
|
+
{
|
238
|
+
var ret = tokenizer.getBarLine(line, curr_pos);
|
239
|
+
if (ret.len === 0)
|
240
|
+
return [0,""];
|
241
|
+
if (ret.warn) {
|
242
|
+
warn(ret.warn, line, curr_pos);
|
243
|
+
return [ret.len,""];
|
244
|
+
}
|
245
|
+
|
246
|
+
// Now see if this is a repeated ending
|
247
|
+
// A repeated ending is all of the characters 1,2,3,4,5,6,7,8,9,0,-, and comma
|
248
|
+
// It can also optionally start with '[', which is ignored.
|
249
|
+
// Also, it can have white space before the '['.
|
250
|
+
for (var ws = 0; ws < line.length; ws++)
|
251
|
+
if (line.charAt(curr_pos+ret.len+ws) !== ' ')
|
252
|
+
break;
|
253
|
+
var orig_bar_len = ret.len;
|
254
|
+
if (line.charAt(curr_pos+ret.len+ws) === '[') {
|
255
|
+
ret.len += ws + 1;
|
256
|
+
}
|
257
|
+
|
258
|
+
// It can also be a quoted string. It is unclear whether that construct requires '[', but it seems like it would. otherwise it would be confused with a regular chord.
|
259
|
+
if (line.charAt(curr_pos+ret.len) === '"' && line.charAt(curr_pos+ret.len-1) === '[') {
|
260
|
+
var ending = tokenizer.getBrackettedSubstring(line, curr_pos+ret.len, 5);
|
261
|
+
return [ret.len+ending[0], ret.token, ending[1]];
|
262
|
+
}
|
263
|
+
var retRep = tokenizer.getTokenOf(line.substring(curr_pos+ret.len), "1234567890-,");
|
264
|
+
if (retRep.len === 0 || retRep.token[0] === '-')
|
265
|
+
return [orig_bar_len, ret.token];
|
266
|
+
|
267
|
+
return [ret.len+retRep.len, ret.token, retRep.token];
|
268
|
+
};
|
269
|
+
|
270
|
+
var letter_to_open_slurs_and_triplets = function(line, i) {
|
271
|
+
// consume spaces, and look for all the open parens. If there is a number after the open paren,
|
272
|
+
// that is a triplet. Otherwise that is a slur. Collect all the slurs and the first triplet.
|
273
|
+
var ret = {};
|
274
|
+
var start = i;
|
275
|
+
while (line.charAt(i) === '(' || tokenizer.isWhiteSpace(line.charAt(i))) {
|
276
|
+
if (line.charAt(i) === '(') {
|
277
|
+
if (i+1 < line.length && (line.charAt(i+1) >= '2' && line.charAt(i+1) <= '9')) {
|
278
|
+
if (ret.triplet !== undefined)
|
279
|
+
warn("Can't nest triplets", line, i);
|
280
|
+
else {
|
281
|
+
ret.triplet = line.charAt(i+1) - '0';
|
282
|
+
if (i+2 < line.length && line.charAt(i+2) === ':') {
|
283
|
+
// We are expecting "(p:q:r" or "(p:q" or "(p::r" we are only interested in the first number (p) and the number of notes (r)
|
284
|
+
// if r is missing, then it is equal to p.
|
285
|
+
if (i+3 < line.length && line.charAt(i+3) === ':') {
|
286
|
+
if (i+4 < line.length && (line.charAt(i+4) >= '1' && line.charAt(i+4) <= '9')) {
|
287
|
+
ret.num_notes = line.charAt(i+4) - '0';
|
288
|
+
i += 3;
|
289
|
+
} else
|
290
|
+
warn("expected number after the two colons after the triplet to mark the duration", line, i);
|
291
|
+
} else if (i+3 < line.length && (line.charAt(i+3) >= '1' && line.charAt(i+3) <= '9')) {
|
292
|
+
// ignore this middle number
|
293
|
+
if (i+4 < line.length && line.charAt(i+4) === ':') {
|
294
|
+
if (i+5 < line.length && (line.charAt(i+5) >= '1' && line.charAt(i+5) <= '9')) {
|
295
|
+
ret.num_notes = line.charAt(i+5) - '0';
|
296
|
+
i += 4;
|
297
|
+
}
|
298
|
+
} else {
|
299
|
+
ret.num_notes = ret.triplet;
|
300
|
+
i += 3;
|
301
|
+
}
|
302
|
+
} else
|
303
|
+
warn("expected number after the triplet to mark the duration", line, i);
|
304
|
+
}
|
305
|
+
}
|
306
|
+
i++;
|
307
|
+
}
|
308
|
+
else {
|
309
|
+
if (ret.startSlur === undefined)
|
310
|
+
ret.startSlur = 1;
|
311
|
+
else
|
312
|
+
ret.startSlur++;
|
313
|
+
}
|
314
|
+
}
|
315
|
+
i++;
|
316
|
+
}
|
317
|
+
ret.consumed = i-start;
|
318
|
+
return ret;
|
319
|
+
};
|
320
|
+
|
321
|
+
var addWords = function(line, words) {
|
322
|
+
if (!line) { warn("Can't add words before the first line of mulsic", line, 0); return; }
|
323
|
+
words = window.ABCJS.parse.strip(words);
|
324
|
+
if (words.charAt(words.length-1) !== '-')
|
325
|
+
words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it.
|
326
|
+
var word_list = [];
|
327
|
+
// first make a list of words from the string we are passed. A word is divided on either a space or dash.
|
328
|
+
var last_divider = 0;
|
329
|
+
var replace = false;
|
330
|
+
var addWord = function(i) {
|
331
|
+
var word = window.ABCJS.parse.strip(words.substring(last_divider, i));
|
332
|
+
last_divider = i+1;
|
333
|
+
if (word.length > 0) {
|
334
|
+
if (replace)
|
335
|
+
word = window.ABCJS.parse.gsub(word,'~', ' ');
|
336
|
+
var div = words.charAt(i);
|
337
|
+
if (div !== '_' && div !== '-')
|
338
|
+
div = ' ';
|
339
|
+
word_list.push({syllable: tokenizer.translateString(word), divider: div});
|
340
|
+
replace = false;
|
341
|
+
return true;
|
342
|
+
}
|
343
|
+
return false;
|
344
|
+
};
|
345
|
+
for (var i = 0; i < words.length; i++) {
|
346
|
+
switch (words.charAt(i)) {
|
347
|
+
case ' ':
|
348
|
+
case '\x12':
|
349
|
+
addWord(i);
|
350
|
+
break;
|
351
|
+
case '-':
|
352
|
+
if (!addWord(i) && word_list.length > 0) {
|
353
|
+
window.ABCJS.parse.last(word_list).divider = '-';
|
354
|
+
word_list.push({skip: true, to: 'next'});
|
355
|
+
}
|
356
|
+
break;
|
357
|
+
case '_':
|
358
|
+
addWord(i);
|
359
|
+
word_list.push({skip: true, to: 'slur'});
|
360
|
+
break;
|
361
|
+
case '*':
|
362
|
+
addWord(i);
|
363
|
+
word_list.push({skip: true, to: 'next'});
|
364
|
+
break;
|
365
|
+
case '|':
|
366
|
+
addWord(i);
|
367
|
+
word_list.push({skip: true, to: 'bar'});
|
368
|
+
break;
|
369
|
+
case '~':
|
370
|
+
replace = true;
|
371
|
+
break;
|
372
|
+
}
|
373
|
+
}
|
374
|
+
|
375
|
+
var inSlur = false;
|
376
|
+
window.ABCJS.parse.each(line, function(el) {
|
377
|
+
if (word_list.length !== 0) {
|
378
|
+
if (word_list[0].skip) {
|
379
|
+
switch (word_list[0].to) {
|
380
|
+
case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break;
|
381
|
+
case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break;
|
382
|
+
case 'bar': if (el.el_type === 'bar') word_list.shift(); break;
|
383
|
+
}
|
384
|
+
} else {
|
385
|
+
if (el.el_type === 'note' && el.rest === undefined && !inSlur) {
|
386
|
+
var lyric = word_list.shift();
|
387
|
+
if (el.lyric === undefined)
|
388
|
+
el.lyric = [ lyric ];
|
389
|
+
else
|
390
|
+
el.lyric.push(lyric);
|
391
|
+
}
|
392
|
+
}
|
393
|
+
}
|
394
|
+
});
|
395
|
+
};
|
396
|
+
|
397
|
+
var addSymbols = function(line, words) {
|
398
|
+
// TODO-PER: Currently copied from w: line. This needs to be read as symbols instead.
|
399
|
+
if (!line) { warn("Can't add symbols before the first line of mulsic", line, 0); return; }
|
400
|
+
words = window.ABCJS.parse.strip(words);
|
401
|
+
if (words.charAt(words.length-1) !== '-')
|
402
|
+
words = words + ' '; // Just makes it easier to parse below, since every word has a divider after it.
|
403
|
+
var word_list = [];
|
404
|
+
// first make a list of words from the string we are passed. A word is divided on either a space or dash.
|
405
|
+
var last_divider = 0;
|
406
|
+
var replace = false;
|
407
|
+
var addWord = function(i) {
|
408
|
+
var word = window.ABCJS.parse.strip(words.substring(last_divider, i));
|
409
|
+
last_divider = i+1;
|
410
|
+
if (word.length > 0) {
|
411
|
+
if (replace)
|
412
|
+
word = window.ABCJS.parse.gsub(word, '~', ' ');
|
413
|
+
var div = words.charAt(i);
|
414
|
+
if (div !== '_' && div !== '-')
|
415
|
+
div = ' ';
|
416
|
+
word_list.push({syllable: tokenizer.translateString(word), divider: div});
|
417
|
+
replace = false;
|
418
|
+
return true;
|
419
|
+
}
|
420
|
+
return false;
|
421
|
+
};
|
422
|
+
for (var i = 0; i < words.length; i++) {
|
423
|
+
switch (words.charAt(i)) {
|
424
|
+
case ' ':
|
425
|
+
case '\x12':
|
426
|
+
addWord(i);
|
427
|
+
break;
|
428
|
+
case '-':
|
429
|
+
if (!addWord(i) && word_list.length > 0) {
|
430
|
+
window.ABCJS.parse.last(word_list).divider = '-';
|
431
|
+
word_list.push({skip: true, to: 'next'});
|
432
|
+
}
|
433
|
+
break;
|
434
|
+
case '_':
|
435
|
+
addWord(i);
|
436
|
+
word_list.push({skip: true, to: 'slur'});
|
437
|
+
break;
|
438
|
+
case '*':
|
439
|
+
addWord(i);
|
440
|
+
word_list.push({skip: true, to: 'next'});
|
441
|
+
break;
|
442
|
+
case '|':
|
443
|
+
addWord(i);
|
444
|
+
word_list.push({skip: true, to: 'bar'});
|
445
|
+
break;
|
446
|
+
case '~':
|
447
|
+
replace = true;
|
448
|
+
break;
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
452
|
+
var inSlur = false;
|
453
|
+
window.ABCJS.parse.each(line, function(el) {
|
454
|
+
if (word_list.length !== 0) {
|
455
|
+
if (word_list[0].skip) {
|
456
|
+
switch (word_list[0].to) {
|
457
|
+
case 'next': if (el.el_type === 'note' && el.pitches !== null && !inSlur) word_list.shift(); break;
|
458
|
+
case 'slur': if (el.el_type === 'note' && el.pitches !== null) word_list.shift(); break;
|
459
|
+
case 'bar': if (el.el_type === 'bar') word_list.shift(); break;
|
460
|
+
}
|
461
|
+
} else {
|
462
|
+
if (el.el_type === 'note' && el.rest === undefined && !inSlur) {
|
463
|
+
var lyric = word_list.shift();
|
464
|
+
if (el.lyric === undefined)
|
465
|
+
el.lyric = [ lyric ];
|
466
|
+
else
|
467
|
+
el.lyric.push(lyric);
|
468
|
+
}
|
469
|
+
}
|
470
|
+
}
|
471
|
+
});
|
472
|
+
};
|
473
|
+
|
474
|
+
var getBrokenRhythm = function(line, index) {
|
475
|
+
switch (line.charAt(index)) {
|
476
|
+
case '>':
|
477
|
+
if (index < line.length - 1 && line.charAt(index+1) === '>') // double >>
|
478
|
+
return [2, 1.75, 0.25];
|
479
|
+
else
|
480
|
+
return [1, 1.5, 0.5];
|
481
|
+
break;
|
482
|
+
case '<':
|
483
|
+
if (index < line.length - 1 && line.charAt(index+1) === '<') // double <<
|
484
|
+
return [2, 0.25, 1.75];
|
485
|
+
else
|
486
|
+
return [1, 0.5, 1.5];
|
487
|
+
break;
|
488
|
+
}
|
489
|
+
return null;
|
490
|
+
};
|
491
|
+
|
492
|
+
// TODO-PER: make this a method in el.
|
493
|
+
var addEndBeam = function(el) {
|
494
|
+
if (el.duration !== undefined && el.duration < 0.25)
|
495
|
+
el.end_beam = true;
|
496
|
+
return el;
|
497
|
+
};
|
498
|
+
|
499
|
+
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};
|
500
|
+
var rests = {x: 'invisible', y: 'spacer', z: 'rest', Z: 'multimeasure' };
|
501
|
+
var getCoreNote = function(line, index, el, canHaveBrokenRhythm) {
|
502
|
+
//var el = { startChar: index };
|
503
|
+
var isComplete = function(state) {
|
504
|
+
return (state === 'octave' || state === 'duration' || state === 'Zduration' || state === 'broken_rhythm' || state === 'end_slur');
|
505
|
+
};
|
506
|
+
var state = 'startSlur';
|
507
|
+
var durationSetByPreviousNote = false;
|
508
|
+
while (1) {
|
509
|
+
switch(line.charAt(index)) {
|
510
|
+
case '(':
|
511
|
+
if (state === 'startSlur') {
|
512
|
+
if (el.startSlur === undefined) el.startSlur = 1; else el.startSlur++;
|
513
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
514
|
+
else return null;
|
515
|
+
break;
|
516
|
+
case ')':
|
517
|
+
if (isComplete(state)) {
|
518
|
+
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
519
|
+
} else return null;
|
520
|
+
break;
|
521
|
+
case '^':
|
522
|
+
if (state === 'startSlur') {el.accidental = 'sharp';state = 'sharp2';}
|
523
|
+
else if (state === 'sharp2') {el.accidental = 'dblsharp';state = 'pitch';}
|
524
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
525
|
+
else return null;
|
526
|
+
break;
|
527
|
+
case '_':
|
528
|
+
if (state === 'startSlur') {el.accidental = 'flat';state = 'flat2';}
|
529
|
+
else if (state === 'flat2') {el.accidental = 'dblflat';state = 'pitch';}
|
530
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
531
|
+
else return null;
|
532
|
+
break;
|
533
|
+
case '=':
|
534
|
+
if (state === 'startSlur') {el.accidental = 'natural';state = 'pitch';}
|
535
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
536
|
+
else return null;
|
537
|
+
break;
|
538
|
+
case 'A':
|
539
|
+
case 'B':
|
540
|
+
case 'C':
|
541
|
+
case 'D':
|
542
|
+
case 'E':
|
543
|
+
case 'F':
|
544
|
+
case 'G':
|
545
|
+
case 'a':
|
546
|
+
case 'b':
|
547
|
+
case 'c':
|
548
|
+
case 'd':
|
549
|
+
case 'e':
|
550
|
+
case 'f':
|
551
|
+
case 'g':
|
552
|
+
if (state === 'startSlur' || state === 'sharp2' || state === 'flat2' || state === 'pitch') {
|
553
|
+
el.pitch = pitches[line.charAt(index)];
|
554
|
+
state = 'octave';
|
555
|
+
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
556
|
+
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
557
|
+
el.duration = multilineVars.next_note_duration;
|
558
|
+
multilineVars.next_note_duration = 0;
|
559
|
+
durationSetByPreviousNote = true;
|
560
|
+
} else
|
561
|
+
el.duration = multilineVars.default_length;
|
562
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
563
|
+
else return null;
|
564
|
+
break;
|
565
|
+
case ',':
|
566
|
+
if (state === 'octave') {el.pitch -= 7;}
|
567
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
568
|
+
else return null;
|
569
|
+
break;
|
570
|
+
case '\'':
|
571
|
+
if (state === 'octave') {el.pitch += 7;}
|
572
|
+
else if (isComplete(state)) {el.endChar = index;return el;}
|
573
|
+
else return null;
|
574
|
+
break;
|
575
|
+
case 'x':
|
576
|
+
case 'y':
|
577
|
+
case 'z':
|
578
|
+
case 'Z':
|
579
|
+
if (state === 'startSlur') {
|
580
|
+
el.rest = { type: rests[line.charAt(index)] };
|
581
|
+
// There shouldn't be some of the properties that notes have. If some sneak in due to bad syntax in the abc file,
|
582
|
+
// just nix them here.
|
583
|
+
delete el.accidental;
|
584
|
+
delete el.startSlur;
|
585
|
+
delete el.startTie;
|
586
|
+
delete el.endSlur;
|
587
|
+
delete el.endTie;
|
588
|
+
delete el.end_beam;
|
589
|
+
delete el.grace_notes;
|
590
|
+
// At this point we have a valid note. The rest is optional. Set the duration in case we don't get one below
|
591
|
+
if (el.rest.type === 'multimeasure') {
|
592
|
+
el.duration = 1;
|
593
|
+
state = 'Zduration';
|
594
|
+
} else {
|
595
|
+
if (canHaveBrokenRhythm && multilineVars.next_note_duration !== 0) {
|
596
|
+
el.duration = multilineVars.next_note_duration;
|
597
|
+
multilineVars.next_note_duration = 0;
|
598
|
+
durationSetByPreviousNote = true;
|
599
|
+
} else
|
600
|
+
el.duration = multilineVars.default_length;
|
601
|
+
state = 'duration';
|
602
|
+
}
|
603
|
+
} else if (isComplete(state)) {el.endChar = index;return el;}
|
604
|
+
else return null;
|
605
|
+
break;
|
606
|
+
case '1':
|
607
|
+
case '2':
|
608
|
+
case '3':
|
609
|
+
case '4':
|
610
|
+
case '5':
|
611
|
+
case '6':
|
612
|
+
case '7':
|
613
|
+
case '8':
|
614
|
+
case '9':
|
615
|
+
case '0':
|
616
|
+
case '/':
|
617
|
+
if (state === 'octave' || state === 'duration') {
|
618
|
+
var fraction = tokenizer.getFraction(line, index);
|
619
|
+
if (!durationSetByPreviousNote)
|
620
|
+
el.duration = el.duration * fraction.value;
|
621
|
+
// TODO-PER: We can test the returned duration here and give a warning if it isn't the one expected.
|
622
|
+
el.endChar = fraction.index;
|
623
|
+
while (fraction.index < line.length && (tokenizer.isWhiteSpace(line.charAt(fraction.index)) || line.charAt(fraction.index) === '-')) {
|
624
|
+
if (line.charAt(fraction.index) === '-')
|
625
|
+
el.startTie = {};
|
626
|
+
else
|
627
|
+
el = addEndBeam(el);
|
628
|
+
fraction.index++;
|
629
|
+
}
|
630
|
+
index = fraction.index-1;
|
631
|
+
state = 'broken_rhythm';
|
632
|
+
} else if (state === 'sharp2') {
|
633
|
+
el.accidental = 'quartersharp';state = 'pitch';
|
634
|
+
} else if (state === 'flat2') {
|
635
|
+
el.accidental = 'quarterflat';state = 'pitch';
|
636
|
+
} else if (state === 'Zduration') {
|
637
|
+
var num = tokenizer.getNumber(line, index);
|
638
|
+
el.duration = num.num;
|
639
|
+
el.endChar = num.index;
|
640
|
+
return el;
|
641
|
+
} else return null;
|
642
|
+
break;
|
643
|
+
case '-':
|
644
|
+
if (state === 'startSlur') {
|
645
|
+
// This is the first character, so it must have been meant for the previous note. Correct that here.
|
646
|
+
tune.addTieToLastNote();
|
647
|
+
el.endTie = true;
|
648
|
+
} else if (state === 'octave' || state === 'duration' || state === 'end_slur') {
|
649
|
+
el.startTie = {};
|
650
|
+
if (!durationSetByPreviousNote && canHaveBrokenRhythm)
|
651
|
+
state = 'broken_rhythm';
|
652
|
+
else {
|
653
|
+
// Peek ahead to the next character. If it is a space, then we have an end beam.
|
654
|
+
if (tokenizer.isWhiteSpace(line.charAt(index+1)))
|
655
|
+
addEndBeam(el);
|
656
|
+
el.endChar = index+1;
|
657
|
+
return el;
|
658
|
+
}
|
659
|
+
} else if (state === 'broken_rhythm') {el.endChar = index;return el;}
|
660
|
+
else return null;
|
661
|
+
break;
|
662
|
+
case ' ':
|
663
|
+
case '\t':
|
664
|
+
if (isComplete(state)) {
|
665
|
+
el.end_beam = true;
|
666
|
+
// look ahead to see if there is a tie
|
667
|
+
do {
|
668
|
+
if (line.charAt(index) === '-')
|
669
|
+
el.startTie = {};
|
670
|
+
index++;
|
671
|
+
} while (index < line.length && (tokenizer.isWhiteSpace(line.charAt(index)) || line.charAt(index) === '-'));
|
672
|
+
el.endChar = index;
|
673
|
+
if (!durationSetByPreviousNote && canHaveBrokenRhythm && (line.charAt(index) === '<' || line.charAt(index) === '>')) { // TODO-PER: Don't need the test for < and >, but that makes the endChar work out for the regression test.
|
674
|
+
index--;
|
675
|
+
state = 'broken_rhythm';
|
676
|
+
} else
|
677
|
+
return el;
|
678
|
+
}
|
679
|
+
else return null;
|
680
|
+
break;
|
681
|
+
case '>':
|
682
|
+
case '<':
|
683
|
+
if (isComplete(state)) {
|
684
|
+
if (canHaveBrokenRhythm) {
|
685
|
+
var br2 = getBrokenRhythm(line, index);
|
686
|
+
index += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
687
|
+
multilineVars.next_note_duration = br2[2]*el.duration;
|
688
|
+
el.duration = br2[1]*el.duration;
|
689
|
+
state = 'end_slur';
|
690
|
+
} else {
|
691
|
+
el.endChar = index;
|
692
|
+
return el;
|
693
|
+
}
|
694
|
+
} else
|
695
|
+
return null;
|
696
|
+
break;
|
697
|
+
default:
|
698
|
+
if (isComplete(state)) {
|
699
|
+
el.endChar = index;
|
700
|
+
return el;
|
701
|
+
}
|
702
|
+
return null;
|
703
|
+
}
|
704
|
+
index++;
|
705
|
+
if (index === line.length) {
|
706
|
+
if (isComplete(state)) {el.endChar = index;return el;}
|
707
|
+
else return null;
|
708
|
+
}
|
709
|
+
}
|
710
|
+
return null;
|
711
|
+
};
|
712
|
+
|
713
|
+
function startNewLine() {
|
714
|
+
var params = { startChar: -1, endChar: -1};
|
715
|
+
if (multilineVars.partForNextLine.length)
|
716
|
+
params.part = multilineVars.partForNextLine;
|
717
|
+
params.clef = multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].clef !== undefined ? window.ABCJS.parse.clone(multilineVars.staves[multilineVars.currentVoice.staffNum].clef) : window.ABCJS.parse.clone(multilineVars.clef) ;
|
718
|
+
params.key = window.ABCJS.parse.parseKeyVoice.deepCopyKey(multilineVars.key);
|
719
|
+
window.ABCJS.parse.parseKeyVoice.addPosToKey(params.clef, params.key);
|
720
|
+
if (multilineVars.meter !== null) {
|
721
|
+
if (multilineVars.currentVoice) {
|
722
|
+
window.ABCJS.parse.each(multilineVars.staves, function(st) {
|
723
|
+
st.meter = multilineVars.meter;
|
724
|
+
});
|
725
|
+
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
726
|
+
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
727
|
+
} else
|
728
|
+
params.meter = multilineVars.meter;
|
729
|
+
multilineVars.meter = null;
|
730
|
+
} else if (multilineVars.currentVoice && multilineVars.staves[multilineVars.currentVoice.staffNum].meter) {
|
731
|
+
// Make sure that each voice gets the meter marking.
|
732
|
+
params.meter = multilineVars.staves[multilineVars.currentVoice.staffNum].meter;
|
733
|
+
multilineVars.staves[multilineVars.currentVoice.staffNum].meter = null;
|
734
|
+
}
|
735
|
+
if (multilineVars.currentVoice && multilineVars.currentVoice.name)
|
736
|
+
params.name = multilineVars.currentVoice.name;
|
737
|
+
if (multilineVars.vocalfont)
|
738
|
+
params.vocalfont = multilineVars.vocalfont;
|
739
|
+
if (multilineVars.style)
|
740
|
+
params.style = multilineVars.style;
|
741
|
+
if (multilineVars.currentVoice) {
|
742
|
+
var staff = multilineVars.staves[multilineVars.currentVoice.staffNum];
|
743
|
+
if (staff.brace) params.brace = staff.brace;
|
744
|
+
if (staff.bracket) params.bracket = staff.bracket;
|
745
|
+
if (staff.connectBarLines) params.connectBarLines = staff.connectBarLines;
|
746
|
+
if (staff.name) params.name = staff.name[multilineVars.currentVoice.index];
|
747
|
+
if (staff.subname) params.subname = staff.subname[multilineVars.currentVoice.index];
|
748
|
+
if (multilineVars.currentVoice.stem)
|
749
|
+
params.stem = multilineVars.currentVoice.stem;
|
750
|
+
if (multilineVars.currentVoice.scale)
|
751
|
+
params.scale = multilineVars.currentVoice.scale;
|
752
|
+
if (multilineVars.currentVoice.style)
|
753
|
+
params.style = multilineVars.currentVoice.style;
|
754
|
+
}
|
755
|
+
tune.startNewLine(params);
|
756
|
+
|
757
|
+
multilineVars.partForNextLine = "";
|
758
|
+
if (multilineVars.currentVoice === undefined || (multilineVars.currentVoice.staffNum === multilineVars.staves.length-1 && multilineVars.staves[multilineVars.currentVoice.staffNum].numVoices-1 === multilineVars.currentVoice.index)) {
|
759
|
+
//multilineVars.meter = null;
|
760
|
+
if (multilineVars.barNumbers === 0)
|
761
|
+
multilineVars.barNumOnNextNote = multilineVars.currBarNumber;
|
762
|
+
}
|
763
|
+
}
|
764
|
+
|
765
|
+
var letter_to_grace = function(line, i) {
|
766
|
+
// Grace notes are an array of: startslur, note, endslur, space; where note is accidental, pitch, duration
|
767
|
+
if (line.charAt(i) === '{') {
|
768
|
+
// fetch the gracenotes string and consume that into the array
|
769
|
+
var gra = tokenizer.getBrackettedSubstring(line, i, 1, '}');
|
770
|
+
if (!gra[2])
|
771
|
+
warn("Missing the closing '}' while parsing grace note", line, i);
|
772
|
+
// If there is a slur after the grace construction, then move it to the last note inside the grace construction
|
773
|
+
if (line[i+gra[0]] === ')') {
|
774
|
+
gra[0]++;
|
775
|
+
gra[1] += ')';
|
776
|
+
}
|
777
|
+
|
778
|
+
var gracenotes = [];
|
779
|
+
var ii = 0;
|
780
|
+
var inTie = false;
|
781
|
+
while (ii < gra[1].length) {
|
782
|
+
var acciaccatura = false;
|
783
|
+
if (gra[1].charAt(ii) === '/') {
|
784
|
+
acciaccatura = true;
|
785
|
+
ii++;
|
786
|
+
}
|
787
|
+
var note = getCoreNote(gra[1], ii, {}, false);
|
788
|
+
if (note !== null) {
|
789
|
+
if (acciaccatura)
|
790
|
+
note.acciaccatura = true;
|
791
|
+
gracenotes.push(note);
|
792
|
+
|
793
|
+
if (inTie) {
|
794
|
+
note.endTie = true;
|
795
|
+
inTie = false;
|
796
|
+
}
|
797
|
+
if (note.startTie)
|
798
|
+
inTie = true;
|
799
|
+
|
800
|
+
ii = note.endChar;
|
801
|
+
delete note.endChar;
|
802
|
+
}
|
803
|
+
else {
|
804
|
+
// We shouldn't get anything but notes or a space here, so report an error
|
805
|
+
if (gra[1].charAt(ii) === ' ') {
|
806
|
+
if (gracenotes.length > 0)
|
807
|
+
gracenotes[gracenotes.length-1].end_beam = true;
|
808
|
+
} else
|
809
|
+
warn("Unknown character '" + gra[1].charAt(ii) + "' while parsing grace note", line, i);
|
810
|
+
ii++;
|
811
|
+
}
|
812
|
+
}
|
813
|
+
if (gracenotes.length)
|
814
|
+
return [gra[0], gracenotes];
|
815
|
+
// for (var ret = letter_to_pitch(gra[1], ii); ret[0]>0 && ii<gra[1].length;
|
816
|
+
// ret = letter_to_pitch(gra[1], ii)) {
|
817
|
+
// //todo get other stuff that could be in a grace note
|
818
|
+
// ii += ret[0];
|
819
|
+
// gracenotes.push({el_type:"gracenote",pitch:ret[1]});
|
820
|
+
// }
|
821
|
+
// return [ gra[0], gracenotes ];
|
822
|
+
}
|
823
|
+
return [ 0 ];
|
824
|
+
};
|
825
|
+
|
826
|
+
//
|
827
|
+
// Parse line of music
|
828
|
+
//
|
829
|
+
// This is a stream of <(bar-marking|header|note-group)...> in any order, with optional spaces between each element
|
830
|
+
// core-note is <open-slur, accidental, pitch:required, octave, duration, close-slur&|tie> with no spaces within that
|
831
|
+
// chord is <open-bracket:required, core-note:required... close-bracket:required duration> with no spaces within that
|
832
|
+
// grace-notes is <open-brace:required, (open-slur|core-note:required|close-slur)..., close-brace:required> spaces are allowed
|
833
|
+
// note-group is <grace-notes, chord symbols&|decorations..., grace-notes, slur&|triplet, chord|core-note, end-slur|tie> spaces are allowed between items
|
834
|
+
// bar-marking is <ampersand> or <chord symbols&|decorations..., bar:required> spaces allowed
|
835
|
+
// header is <open-bracket:required, K|M|L|V:required, colon:required, field:required, close-bracket:required> spaces can occur between the colon, in the field, and before the close bracket
|
836
|
+
// header can also be the only thing on a line. This is true even if it is a continuation line. In this case the brackets are not required.
|
837
|
+
// a space is a back-tick, a space, or a tab. If it is a back-tick, then there is no end-beam.
|
838
|
+
|
839
|
+
// Line preprocessing: anything after a % is ignored (the double %% should have been taken care of before this)
|
840
|
+
// Then, all leading and trailing spaces are ignored.
|
841
|
+
// If there was a line continuation, the \n was replaced by a \r and the \ was replaced by a space. This allows the construct
|
842
|
+
// of having a header mid-line conceptually, but actually be at the start of the line. This is equivolent to putting the header in [ ].
|
843
|
+
|
844
|
+
// TODO-PER: How to handle ! for line break?
|
845
|
+
// TODO-PER: dots before bar, dots before slur
|
846
|
+
// TODO-PER: U: redefinable symbols.
|
847
|
+
|
848
|
+
// Ambiguous symbols:
|
849
|
+
// "[" can be the start of a chord, the start of a header element or part of a bar line.
|
850
|
+
// --- if it is immediately followed by "|", it is a bar line
|
851
|
+
// --- if it is immediately followed by K: L: M: V: it is a header (note: there are other headers mentioned in the standard, but I'm not sure how they would be used.)
|
852
|
+
// --- otherwise it is the beginning of a chord
|
853
|
+
// "(" can be the start of a slur or a triplet
|
854
|
+
// --- if it is followed by a number from 2-9, then it is a triplet
|
855
|
+
// --- otherwise it is a slur
|
856
|
+
// "]"
|
857
|
+
// --- if there is a chord open, then this is the close
|
858
|
+
// --- if it is after a [|, then it is an invisible bar line
|
859
|
+
// --- otherwise, it is par of a bar
|
860
|
+
// "." can be a bar modifier or a slur modifier, or a decoration
|
861
|
+
// --- if it comes immediately before a bar, it is a bar modifier
|
862
|
+
// --- if it comes immediately before a slur, it is a slur modifier
|
863
|
+
// --- otherwise it is a decoration for the next note.
|
864
|
+
// number:
|
865
|
+
// --- if it is after a bar, with no space, it is an ending marker
|
866
|
+
// --- if it is after a ( with no space, it is a triplet count
|
867
|
+
// --- if it is after a pitch or octave or slash, then it is a duration
|
868
|
+
|
869
|
+
// Unambiguous symbols (except inside quoted strings):
|
870
|
+
// vertical-bar, colon: part of a bar
|
871
|
+
// ABCDEFGabcdefg: pitch
|
872
|
+
// xyzZ: rest
|
873
|
+
// comma, prime: octave
|
874
|
+
// close-paren: end-slur
|
875
|
+
// hyphen: tie
|
876
|
+
// tilde, v, u, bang, plus, THLMPSO: decoration
|
877
|
+
// carat, underscore, equal: accidental
|
878
|
+
// ampersand: time reset
|
879
|
+
// open-curly, close-curly: grace notes
|
880
|
+
// double-quote: chord symbol
|
881
|
+
// less-than, greater-than, slash: duration
|
882
|
+
// back-tick, space, tab: space
|
883
|
+
var nonDecorations = "ABCDEFGabcdefgxyzZ[]|^_{"; // use this to prescreen so we don't have to look for a decoration at every note.
|
884
|
+
|
885
|
+
var parseRegularMusicLine = function(line) {
|
886
|
+
header.resolveTempo();
|
887
|
+
//multilineVars.havent_set_length = false; // To late to set this now.
|
888
|
+
multilineVars.is_in_header = false; // We should have gotten a key header by now, but just in case, this is definitely out of the header.
|
889
|
+
var i = 0;
|
890
|
+
var startOfLine = multilineVars.iChar;
|
891
|
+
// see if there is nothing but a comment on this line. If so, just ignore it. A full line comment is optional white space followed by %
|
892
|
+
while (tokenizer.isWhiteSpace(line.charAt(i)) && i < line.length)
|
893
|
+
i++;
|
894
|
+
if (i === line.length || line.charAt(i) === '%')
|
895
|
+
return;
|
896
|
+
|
897
|
+
// Start with the standard staff, clef and key symbols on each line
|
898
|
+
var delayStartNewLine = multilineVars.start_new_line;
|
899
|
+
// if (multilineVars.start_new_line) {
|
900
|
+
// startNewLine();
|
901
|
+
// }
|
902
|
+
if (multilineVars.continueall === undefined)
|
903
|
+
multilineVars.start_new_line = true;
|
904
|
+
else
|
905
|
+
multilineVars.start_new_line = false;
|
906
|
+
var tripletNotesLeft = 0;
|
907
|
+
//var tripletMultiplier = 0;
|
908
|
+
// var inTie = false;
|
909
|
+
// var inTieChord = {};
|
910
|
+
|
911
|
+
// See if the line starts with a header field
|
912
|
+
var retHeader = header.letter_to_body_header(line, i);
|
913
|
+
if (retHeader[0] > 0) {
|
914
|
+
i += retHeader[0];
|
915
|
+
// TODO-PER: Handle inline headers
|
916
|
+
}
|
917
|
+
var el = { };
|
918
|
+
|
919
|
+
while (i < line.length)
|
920
|
+
{
|
921
|
+
var startI = i;
|
922
|
+
if (line.charAt(i) === '%')
|
923
|
+
break;
|
924
|
+
|
925
|
+
var retInlineHeader = header.letter_to_inline_header(line, i);
|
926
|
+
if (retInlineHeader[0] > 0) {
|
927
|
+
i += retInlineHeader[0];
|
928
|
+
// TODO-PER: Handle inline headers
|
929
|
+
//multilineVars.start_new_line = false;
|
930
|
+
} else {
|
931
|
+
// Wait until here to actually start the line because we know we're past the inline statements.
|
932
|
+
if (delayStartNewLine) {
|
933
|
+
startNewLine();
|
934
|
+
delayStartNewLine = false;
|
935
|
+
}
|
936
|
+
// var el = { };
|
937
|
+
|
938
|
+
// We need to decide if the following characters are a bar-marking or a note-group.
|
939
|
+
// Unfortunately, that is ambiguous. Both can contain chord symbols and decorations.
|
940
|
+
// If there is a grace note either before or after the chord symbols and decorations, then it is definitely a note-group.
|
941
|
+
// If there is a bar marker, it is definitely a bar-marking.
|
942
|
+
// If there is either a core-note or chord, it is definitely a note-group.
|
943
|
+
// So, loop while we find grace-notes, chords-symbols, or decorations. [It is an error to have more than one grace-note group in a row; the others can be multiple]
|
944
|
+
// Then, if there is a grace-note, we know where to go.
|
945
|
+
// Else see if we have a chord, core-note, slur, triplet, or bar.
|
946
|
+
|
947
|
+
var ret;
|
948
|
+
while (1) {
|
949
|
+
ret = tokenizer.eatWhiteSpace(line, i);
|
950
|
+
if (ret > 0) {
|
951
|
+
i += ret;
|
952
|
+
}
|
953
|
+
if (i > 0 && line.charAt(i-1) === '\x12') {
|
954
|
+
// there is one case where a line continuation isn't the same as being on the same line, and that is if the next character after it is a header.
|
955
|
+
ret = header.letter_to_body_header(line, i);
|
956
|
+
if (ret[0] > 0) {
|
957
|
+
// TODO: insert header here
|
958
|
+
i = ret[0];
|
959
|
+
multilineVars.start_new_line = false;
|
960
|
+
}
|
961
|
+
}
|
962
|
+
// gather all the grace notes, chord symbols and decorations
|
963
|
+
ret = letter_to_spacer(line, i);
|
964
|
+
if (ret[0] > 0) {
|
965
|
+
i += ret[0];
|
966
|
+
}
|
967
|
+
|
968
|
+
ret = letter_to_chord(line, i);
|
969
|
+
if (ret[0] > 0) {
|
970
|
+
// There could be more than one chord here if they have different positions.
|
971
|
+
// If two chords have the same position, then connect them with newline.
|
972
|
+
if (!el.chord)
|
973
|
+
el.chord = [];
|
974
|
+
var chordName = tokenizer.translateString(ret[1]);
|
975
|
+
chordName = chordName.replace(/;/g, "\n");
|
976
|
+
var addedChord = false;
|
977
|
+
for (var ci = 0; ci < el.chord.length; ci++) {
|
978
|
+
if (el.chord[ci].position === ret[2]) {
|
979
|
+
addedChord = true;
|
980
|
+
el.chord[ci].name += "\n" + chordName;
|
981
|
+
}
|
982
|
+
}
|
983
|
+
if (addedChord === false) {
|
984
|
+
if (ret[2] === null && ret[3])
|
985
|
+
el.chord.push({name: chordName, rel_position: ret[3]});
|
986
|
+
else
|
987
|
+
el.chord.push({name: chordName, position: ret[2]});
|
988
|
+
}
|
989
|
+
|
990
|
+
i += ret[0];
|
991
|
+
var ii = tokenizer.skipWhiteSpace(line.substring(i));
|
992
|
+
if (ii > 0)
|
993
|
+
el.force_end_beam_last = true;
|
994
|
+
i += ii;
|
995
|
+
} else {
|
996
|
+
if (nonDecorations.indexOf(line.charAt(i)) === -1)
|
997
|
+
ret = letter_to_accent(line, i);
|
998
|
+
else ret = [ 0 ];
|
999
|
+
if (ret[0] > 0) {
|
1000
|
+
if (ret[1] === null) {
|
1001
|
+
if (i+1 < line.length)
|
1002
|
+
startNewLine(); // There was a ! in the middle of the line. Start a new line if there is anything after it.
|
1003
|
+
} else if (ret[1].length > 0) {
|
1004
|
+
if (el.decoration === undefined)
|
1005
|
+
el.decoration = [];
|
1006
|
+
el.decoration.push(ret[1]);
|
1007
|
+
}
|
1008
|
+
i += ret[0];
|
1009
|
+
} else {
|
1010
|
+
ret = letter_to_grace(line, i);
|
1011
|
+
// TODO-PER: Be sure there aren't already grace notes defined. That is an error.
|
1012
|
+
if (ret[0] > 0) {
|
1013
|
+
el.gracenotes = ret[1];
|
1014
|
+
i += ret[0];
|
1015
|
+
} else
|
1016
|
+
break;
|
1017
|
+
}
|
1018
|
+
}
|
1019
|
+
}
|
1020
|
+
|
1021
|
+
ret = letter_to_bar(line, i);
|
1022
|
+
if (ret[0] > 0) {
|
1023
|
+
// This is definitely a bar
|
1024
|
+
if (el.gracenotes !== undefined) {
|
1025
|
+
// Attach the grace note to an invisible note
|
1026
|
+
el.rest = { type: 'spacer' };
|
1027
|
+
el.duration = 0.125; // TODO-PER: I don't think the duration of this matters much, but figure out if it does.
|
1028
|
+
tune.appendElement('note', startOfLine+i, startOfLine+i+ret[0], el);
|
1029
|
+
multilineVars.measureNotEmpty = true;
|
1030
|
+
el = {};
|
1031
|
+
}
|
1032
|
+
var bar = {type: ret[1]};
|
1033
|
+
if (bar.type.length === 0)
|
1034
|
+
warn("Unknown bar type", line, i);
|
1035
|
+
else {
|
1036
|
+
if (multilineVars.inEnding && bar.type !== 'bar_thin') {
|
1037
|
+
bar.endEnding = true;
|
1038
|
+
multilineVars.inEnding = false;
|
1039
|
+
}
|
1040
|
+
if (ret[2]) {
|
1041
|
+
bar.startEnding = ret[2];
|
1042
|
+
if (multilineVars.inEnding)
|
1043
|
+
bar.endEnding = true;
|
1044
|
+
multilineVars.inEnding = true;
|
1045
|
+
}
|
1046
|
+
if (el.decoration !== undefined)
|
1047
|
+
bar.decoration = el.decoration;
|
1048
|
+
if (el.chord !== undefined)
|
1049
|
+
bar.chord = el.chord;
|
1050
|
+
if (bar.startEnding && multilineVars.barFirstEndingNum === undefined)
|
1051
|
+
multilineVars.barFirstEndingNum = multilineVars.currBarNumber;
|
1052
|
+
else if (bar.startEnding && bar.endEnding && multilineVars.barFirstEndingNum)
|
1053
|
+
multilineVars.currBarNumber = multilineVars.barFirstEndingNum;
|
1054
|
+
else if (bar.endEnding)
|
1055
|
+
multilineVars.barFirstEndingNum = undefined;
|
1056
|
+
if (bar.type !== 'bar_invisible' && multilineVars.measureNotEmpty) {
|
1057
|
+
multilineVars.currBarNumber++;
|
1058
|
+
if (multilineVars.barNumbers && multilineVars.currBarNumber % multilineVars.barNumbers === 0)
|
1059
|
+
multilineVars.barNumOnNextNote = multilineVars.currBarNumber;
|
1060
|
+
}
|
1061
|
+
tune.appendElement('bar', startOfLine+i, startOfLine+i+ret[0], bar);
|
1062
|
+
multilineVars.measureNotEmpty = false;
|
1063
|
+
el = {};
|
1064
|
+
}
|
1065
|
+
i += ret[0];
|
1066
|
+
} else if (line[i] === '&') { // backtrack to beginning of measure
|
1067
|
+
warn("Overlay not yet supported", line, i);
|
1068
|
+
i++;
|
1069
|
+
|
1070
|
+
} else {
|
1071
|
+
// This is definitely a note group
|
1072
|
+
//
|
1073
|
+
// Look for as many open slurs and triplets as there are. (Note: only the first triplet is valid.)
|
1074
|
+
ret = letter_to_open_slurs_and_triplets(line, i);
|
1075
|
+
if (ret.consumed > 0) {
|
1076
|
+
if (ret.startSlur !== undefined)
|
1077
|
+
el.startSlur = ret.startSlur;
|
1078
|
+
if (ret.triplet !== undefined) {
|
1079
|
+
if (tripletNotesLeft > 0)
|
1080
|
+
warn("Can't nest triplets", line, i);
|
1081
|
+
else {
|
1082
|
+
el.startTriplet = ret.triplet;
|
1083
|
+
tripletNotesLeft = ret.num_notes === undefined ? ret.triplet : ret.num_notes;
|
1084
|
+
}
|
1085
|
+
}
|
1086
|
+
i += ret.consumed;
|
1087
|
+
}
|
1088
|
+
|
1089
|
+
// handle chords.
|
1090
|
+
if (line.charAt(i) === '[') {
|
1091
|
+
i++;
|
1092
|
+
var chordDuration = null;
|
1093
|
+
|
1094
|
+
var done = false;
|
1095
|
+
while (!done) {
|
1096
|
+
var chordNote = getCoreNote(line, i, {}, false);
|
1097
|
+
if (chordNote !== null) {
|
1098
|
+
if (chordNote.end_beam) {
|
1099
|
+
el.end_beam = true;
|
1100
|
+
delete chordNote.end_beam;
|
1101
|
+
}
|
1102
|
+
if (el.pitches === undefined) {
|
1103
|
+
el.duration = chordNote.duration;
|
1104
|
+
el.pitches = [ chordNote ];
|
1105
|
+
} else // Just ignore the note lengths of all but the first note. The standard isn't clear here, but this seems less confusing.
|
1106
|
+
el.pitches.push(chordNote);
|
1107
|
+
delete chordNote.duration;
|
1108
|
+
|
1109
|
+
if (multilineVars.inTieChord[el.pitches.length]) {
|
1110
|
+
chordNote.endTie = true;
|
1111
|
+
multilineVars.inTieChord[el.pitches.length] = undefined;
|
1112
|
+
}
|
1113
|
+
if (chordNote.startTie)
|
1114
|
+
multilineVars.inTieChord[el.pitches.length] = true;
|
1115
|
+
|
1116
|
+
i = chordNote.endChar;
|
1117
|
+
delete chordNote.endChar;
|
1118
|
+
} else if (line.charAt(i) === ' ') {
|
1119
|
+
// Spaces are not allowed in chords, but we can recover from it by ignoring it.
|
1120
|
+
warn("Spaces are not allowed in chords", line, i);
|
1121
|
+
i++;
|
1122
|
+
} else {
|
1123
|
+
if (i < line.length && line.charAt(i) === ']') {
|
1124
|
+
// consume the close bracket
|
1125
|
+
i++;
|
1126
|
+
|
1127
|
+
if (multilineVars.next_note_duration !== 0) {
|
1128
|
+
el.duration = el.duration * multilineVars.next_note_duration;
|
1129
|
+
// window.ABCJS.parse.each(el.pitches, function(p) {
|
1130
|
+
// p.duration = p.duration * multilineVars.next_note_duration;
|
1131
|
+
// });
|
1132
|
+
multilineVars.next_note_duration = 0;
|
1133
|
+
}
|
1134
|
+
|
1135
|
+
if (multilineVars.inTie) {
|
1136
|
+
window.ABCJS.parse.each(el.pitches, function(pitch) { pitch.endTie = true; });
|
1137
|
+
multilineVars.inTie = false;
|
1138
|
+
}
|
1139
|
+
|
1140
|
+
if (tripletNotesLeft > 0) {
|
1141
|
+
tripletNotesLeft--;
|
1142
|
+
if (tripletNotesLeft === 0) {
|
1143
|
+
el.endTriplet = true;
|
1144
|
+
}
|
1145
|
+
}
|
1146
|
+
|
1147
|
+
// if (el.startSlur !== undefined) {
|
1148
|
+
// window.ABCJS.parse.each(el.pitches, function(pitch) { if (pitch.startSlur === undefined) pitch.startSlur = el.startSlur; else pitch.startSlur += el.startSlur; });
|
1149
|
+
// delete el.startSlur;
|
1150
|
+
// }
|
1151
|
+
|
1152
|
+
var postChordDone = false;
|
1153
|
+
while (i < line.length && !postChordDone) {
|
1154
|
+
switch (line.charAt(i)) {
|
1155
|
+
case ' ':
|
1156
|
+
case '\t':
|
1157
|
+
addEndBeam(el);
|
1158
|
+
break;
|
1159
|
+
case ')':
|
1160
|
+
if (el.endSlur === undefined) el.endSlur = 1; else el.endSlur++;
|
1161
|
+
//window.ABCJS.parse.each(el.pitches, function(pitch) { if (pitch.endSlur === undefined) pitch.endSlur = 1; else pitch.endSlur++; });
|
1162
|
+
break;
|
1163
|
+
case '-':
|
1164
|
+
window.ABCJS.parse.each(el.pitches, function(pitch) { pitch.startTie = {}; });
|
1165
|
+
multilineVars.inTie = true;
|
1166
|
+
break;
|
1167
|
+
case '>':
|
1168
|
+
case '<':
|
1169
|
+
var br2 = getBrokenRhythm(line, i);
|
1170
|
+
i += br2[0] - 1; // index gets incremented below, so we'll let that happen
|
1171
|
+
multilineVars.next_note_duration = br2[2];
|
1172
|
+
chordDuration = br2[1];
|
1173
|
+
break;
|
1174
|
+
case '1':
|
1175
|
+
case '2':
|
1176
|
+
case '3':
|
1177
|
+
case '4':
|
1178
|
+
case '5':
|
1179
|
+
case '6':
|
1180
|
+
case '7':
|
1181
|
+
case '8':
|
1182
|
+
case '9':
|
1183
|
+
case '/':
|
1184
|
+
var fraction = tokenizer.getFraction(line, i);
|
1185
|
+
chordDuration = fraction.value;
|
1186
|
+
i = fraction.index;
|
1187
|
+
postChordDone = true;
|
1188
|
+
break;
|
1189
|
+
default:
|
1190
|
+
postChordDone = true;
|
1191
|
+
break;
|
1192
|
+
}
|
1193
|
+
if (!postChordDone) {
|
1194
|
+
i++;
|
1195
|
+
}
|
1196
|
+
}
|
1197
|
+
} else
|
1198
|
+
warn("Expected ']' to end the chords", line, i);
|
1199
|
+
|
1200
|
+
if (el.pitches !== undefined) {
|
1201
|
+
if (chordDuration !== null) {
|
1202
|
+
el.duration = el.duration * chordDuration;
|
1203
|
+
// window.ABCJS.parse.each(el.pitches, function(p) {
|
1204
|
+
// p.duration = p.duration * chordDuration;
|
1205
|
+
// });
|
1206
|
+
}
|
1207
|
+
if (multilineVars.barNumOnNextNote) {
|
1208
|
+
el.barNumber = multilineVars.barNumOnNextNote;
|
1209
|
+
multilineVars.barNumOnNextNote = null;
|
1210
|
+
}
|
1211
|
+
tune.appendElement('note', startOfLine+i, startOfLine+i, el);
|
1212
|
+
multilineVars.measureNotEmpty = true;
|
1213
|
+
el = {};
|
1214
|
+
}
|
1215
|
+
done = true;
|
1216
|
+
}
|
1217
|
+
}
|
1218
|
+
|
1219
|
+
} else {
|
1220
|
+
// Single pitch
|
1221
|
+
var el2 = {};
|
1222
|
+
var core = getCoreNote(line, i, el2, true);
|
1223
|
+
if (el2.endTie !== undefined) multilineVars.inTie = true;
|
1224
|
+
if (core !== null) {
|
1225
|
+
if (core.pitch !== undefined) {
|
1226
|
+
el.pitches = [ { } ];
|
1227
|
+
// TODO-PER: straighten this out so there is not so much copying: getCoreNote shouldn't change e'
|
1228
|
+
if (core.accidental !== undefined) el.pitches[0].accidental = core.accidental;
|
1229
|
+
el.pitches[0].pitch = core.pitch;
|
1230
|
+
if (core.endSlur !== undefined) el.pitches[0].endSlur = core.endSlur;
|
1231
|
+
if (core.endTie !== undefined) el.pitches[0].endTie = core.endTie;
|
1232
|
+
if (core.startSlur !== undefined) el.pitches[0].startSlur = core.startSlur;
|
1233
|
+
if (el.startSlur !== undefined) el.pitches[0].startSlur = el.startSlur;
|
1234
|
+
if (core.startTie !== undefined) el.pitches[0].startTie = core.startTie;
|
1235
|
+
if (el.startTie !== undefined) el.pitches[0].startTie = el.startTie;
|
1236
|
+
} else {
|
1237
|
+
el.rest = core.rest;
|
1238
|
+
if (core.endSlur !== undefined) el.endSlur = core.endSlur;
|
1239
|
+
if (core.endTie !== undefined) el.rest.endTie = core.endTie;
|
1240
|
+
if (core.startSlur !== undefined) el.startSlur = core.startSlur;
|
1241
|
+
//if (el.startSlur !== undefined) el.startSlur = el.startSlur;
|
1242
|
+
if (core.startTie !== undefined) el.rest.startTie = core.startTie;
|
1243
|
+
if (el.startTie !== undefined) el.rest.startTie = el.startTie;
|
1244
|
+
}
|
1245
|
+
|
1246
|
+
if (core.chord !== undefined) el.chord = core.chord;
|
1247
|
+
if (core.duration !== undefined) el.duration = core.duration;
|
1248
|
+
if (core.decoration !== undefined) el.decoration = core.decoration;
|
1249
|
+
if (core.graceNotes !== undefined) el.graceNotes = core.graceNotes;
|
1250
|
+
delete el.startSlur;
|
1251
|
+
if (multilineVars.inTie) {
|
1252
|
+
if (el.pitches !== undefined)
|
1253
|
+
el.pitches[0].endTie = true;
|
1254
|
+
else
|
1255
|
+
el.rest.endTie = true;
|
1256
|
+
multilineVars.inTie = false;
|
1257
|
+
}
|
1258
|
+
if (core.startTie || el.startTie)
|
1259
|
+
multilineVars.inTie = true;
|
1260
|
+
i = core.endChar;
|
1261
|
+
|
1262
|
+
if (tripletNotesLeft > 0) {
|
1263
|
+
tripletNotesLeft--;
|
1264
|
+
if (tripletNotesLeft === 0) {
|
1265
|
+
el.endTriplet = true;
|
1266
|
+
}
|
1267
|
+
}
|
1268
|
+
|
1269
|
+
if (core.end_beam)
|
1270
|
+
addEndBeam(el);
|
1271
|
+
|
1272
|
+
if (multilineVars.barNumOnNextNote) {
|
1273
|
+
el.barNumber = multilineVars.barNumOnNextNote;
|
1274
|
+
multilineVars.barNumOnNextNote = null;
|
1275
|
+
}
|
1276
|
+
tune.appendElement('note', startOfLine+startI, startOfLine+i, el);
|
1277
|
+
multilineVars.measureNotEmpty = true;
|
1278
|
+
el = {};
|
1279
|
+
}
|
1280
|
+
}
|
1281
|
+
|
1282
|
+
if (i === startI) { // don't know what this is, so ignore it.
|
1283
|
+
if (line.charAt(i) !== ' ' && line.charAt(i) !== '`')
|
1284
|
+
warn("Unknown character ignored", line, i);
|
1285
|
+
// warn("Unknown character ignored (" + line.charCodeAt(i) + ")", line, i);
|
1286
|
+
i++;
|
1287
|
+
}
|
1288
|
+
}
|
1289
|
+
}
|
1290
|
+
}
|
1291
|
+
};
|
1292
|
+
|
1293
|
+
var parseLine = function(line) {
|
1294
|
+
var ret = header.parseHeader(line);
|
1295
|
+
if (ret.regular)
|
1296
|
+
parseRegularMusicLine(ret.str);
|
1297
|
+
if (ret.newline && multilineVars.continueall === undefined)
|
1298
|
+
startNewLine();
|
1299
|
+
if (ret.words)
|
1300
|
+
addWords(tune.getCurrentVoice(), line.substring(2));
|
1301
|
+
if (ret.symbols)
|
1302
|
+
addSymbols(tune.getCurrentVoice(), line.substring(2));
|
1303
|
+
if (ret.recurse)
|
1304
|
+
parseLine(ret.str);
|
1305
|
+
};
|
1306
|
+
|
1307
|
+
this.parse = function(strTune, switches) {
|
1308
|
+
// the switches are optional and cause a difference in the way the tune is parsed.
|
1309
|
+
// switches.header_only : stop parsing when the header is finished
|
1310
|
+
// switches.stop_on_warning : stop at the first warning encountered.
|
1311
|
+
// switches.print: format for the page instead of the browser.
|
1312
|
+
tune.reset();
|
1313
|
+
if (switches && switches.print)
|
1314
|
+
tune.media = 'print';
|
1315
|
+
multilineVars.reset();
|
1316
|
+
// Take care of whatever line endings come our way
|
1317
|
+
strTune = window.ABCJS.parse.gsub(strTune, '\r\n', '\n');
|
1318
|
+
strTune = window.ABCJS.parse.gsub(strTune, '\r', '\n');
|
1319
|
+
strTune += '\n'; // Tacked on temporarily to make the last line continuation work
|
1320
|
+
strTune = strTune.replace(/\n\\.*\n/g, "\n"); // get rid of latex commands.
|
1321
|
+
var continuationReplacement = function(all, backslash, comment){
|
1322
|
+
var spaces = " ";
|
1323
|
+
var padding = comment ? spaces.substring(0, comment.length) : "";
|
1324
|
+
return backslash + " \x12" + padding;
|
1325
|
+
};
|
1326
|
+
strTune = strTune.replace(/\\([ \t]*)(%.*)*\n/g, continuationReplacement); // take care of line continuations right away, but keep the same number of characters
|
1327
|
+
var lines = strTune.split('\n');
|
1328
|
+
if (window.ABCJS.parse.last(lines).length === 0) // remove the blank line we added above.
|
1329
|
+
lines.pop();
|
1330
|
+
try {
|
1331
|
+
window.ABCJS.parse.each(lines, function(line) {
|
1332
|
+
if (switches) {
|
1333
|
+
if (switches.header_only && multilineVars.is_in_header === false)
|
1334
|
+
throw "normal_abort";
|
1335
|
+
if (switches.stop_on_warning && multilineVars.warnings)
|
1336
|
+
throw "normal_abort";
|
1337
|
+
}
|
1338
|
+
if (multilineVars.is_in_history) {
|
1339
|
+
if (line.charAt(1) === ':') {
|
1340
|
+
multilineVars.is_in_history = false;
|
1341
|
+
parseLine(line);
|
1342
|
+
} else
|
1343
|
+
tune.addMetaText("history", tokenizer.translateString(tokenizer.stripComment(line)));
|
1344
|
+
} else if (multilineVars.inTextBlock) {
|
1345
|
+
if (window.ABCJS.parse.startsWith(line, "%%endtext")) {
|
1346
|
+
//tune.addMetaText("textBlock", multilineVars.textBlock);
|
1347
|
+
tune.addText(multilineVars.textBlock);
|
1348
|
+
multilineVars.inTextBlock = false;
|
1349
|
+
}
|
1350
|
+
else {
|
1351
|
+
if (window.ABCJS.parse.startsWith(line, "%%"))
|
1352
|
+
multilineVars.textBlock += ' ' + line.substring(2);
|
1353
|
+
else
|
1354
|
+
multilineVars.textBlock += ' ' + line;
|
1355
|
+
}
|
1356
|
+
} else if (multilineVars.inPsBlock) {
|
1357
|
+
if (window.ABCJS.parse.startsWith(line, "%%endps")) {
|
1358
|
+
// Just ignore postscript
|
1359
|
+
multilineVars.inPsBlock = false;
|
1360
|
+
}
|
1361
|
+
else
|
1362
|
+
multilineVars.textBlock += ' ' + line;
|
1363
|
+
} else
|
1364
|
+
parseLine(line);
|
1365
|
+
multilineVars.iChar += line.length + 1;
|
1366
|
+
});
|
1367
|
+
var ph = 11*72;
|
1368
|
+
var pl = 8.5*72;
|
1369
|
+
switch (multilineVars.papersize) {
|
1370
|
+
//case "letter": ph = 11*72; pl = 8.5*72; break;
|
1371
|
+
case "legal": ph = 14*72; pl = 8.5*72; break;
|
1372
|
+
case "A4": ph = 11.7*72; pl = 8.3*72; break;
|
1373
|
+
}
|
1374
|
+
if (multilineVars.landscape) {
|
1375
|
+
var x = ph;
|
1376
|
+
ph = pl;
|
1377
|
+
pl = x;
|
1378
|
+
}
|
1379
|
+
tune.cleanUp(pl, ph, multilineVars.barsperstaff, multilineVars.staffnonote);
|
1380
|
+
} catch (err) {
|
1381
|
+
if (err !== "normal_abort")
|
1382
|
+
throw err;
|
1383
|
+
}
|
1384
|
+
};
|
1385
|
+
};
|