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 @@
|
|
1
|
+
//= require_tree ./abcjs
|
@@ -0,0 +1,158 @@
|
|
1
|
+
// abc_tunebook.js: splits a string representing ABC Music Notation into individual tunes.
|
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 document, Raphael */
|
18
|
+
/*global window */
|
19
|
+
|
20
|
+
if (!window.ABCJS)
|
21
|
+
window.ABCJS = {};
|
22
|
+
|
23
|
+
(function() {
|
24
|
+
ABCJS.numberOfTunes = function(abc) {
|
25
|
+
var tunes = abc.split("\nX:");
|
26
|
+
var num = tunes.length;
|
27
|
+
if (num === 0) num = 1;
|
28
|
+
return num;
|
29
|
+
};
|
30
|
+
|
31
|
+
ABCJS.TuneBook = function(book) {
|
32
|
+
var This = this;
|
33
|
+
var directives = "";
|
34
|
+
book = window.ABCJS.parse.strip(book);
|
35
|
+
var tunes = book.split("\nX:");
|
36
|
+
for (var i = 1; i < tunes.length; i++) // Put back the X: that we lost when splitting the tunes.
|
37
|
+
tunes[i] = "X:" + tunes[i];
|
38
|
+
// Keep track of the character position each tune starts with.
|
39
|
+
var pos = 0;
|
40
|
+
This.tunes = [];
|
41
|
+
window.ABCJS.parse.each(tunes, function(tune) {
|
42
|
+
This.tunes.push({ abc: tune, startPos: pos});
|
43
|
+
pos += tune.length;
|
44
|
+
});
|
45
|
+
if (This.tunes.length > 1 && !window.ABCJS.parse.startsWith(This.tunes[0].abc, 'X:')) { // If there is only one tune, the X: might be missing, otherwise assume the top of the file is "intertune"
|
46
|
+
// There could be file-wide directives in this, if so, we need to insert it into each tune. We can probably get away with
|
47
|
+
// just looking for file-wide directives here (before the first tune) and inserting them at the bottom of each tune, since
|
48
|
+
// the tune is parsed all at once. The directives will be seen before the printer begins processing.
|
49
|
+
var dir = This.tunes.shift();
|
50
|
+
var arrDir = dir.abc.split('\n');
|
51
|
+
window.ABCJS.parse.each(arrDir, function(line) {
|
52
|
+
if (window.ABCJS.parse.startsWith(line, '%%'))
|
53
|
+
directives += line + '\n';
|
54
|
+
});
|
55
|
+
}
|
56
|
+
// Now, the tune ends at a blank line, so truncate it if needed. There may be "intertune" stuff.
|
57
|
+
window.ABCJS.parse.each(This.tunes, function(tune) {
|
58
|
+
var end = tune.abc.indexOf('\n\n');
|
59
|
+
if (end > 0)
|
60
|
+
tune.abc = tune.abc.substring(0, end);
|
61
|
+
tune.abc = directives + tune.abc;
|
62
|
+
});
|
63
|
+
};
|
64
|
+
|
65
|
+
function renderEngine(callback, output, abc, parserParams, renderParams) {
|
66
|
+
var isArray = function(testObject) {
|
67
|
+
return testObject && !(testObject.propertyIsEnumerable('length')) && typeof testObject === 'object' && typeof testObject.length === 'number';
|
68
|
+
};
|
69
|
+
|
70
|
+
// check and normalize input parameters
|
71
|
+
if (output === undefined || abc === undefined)
|
72
|
+
return;
|
73
|
+
if (!isArray(output))
|
74
|
+
output = [ output ];
|
75
|
+
if (parserParams === undefined)
|
76
|
+
parserParams = {};
|
77
|
+
if (renderParams === undefined)
|
78
|
+
renderParams = {};
|
79
|
+
var currentTune = renderParams.startingTune ? renderParams.startingTune : 0;
|
80
|
+
|
81
|
+
// parse the abc string
|
82
|
+
var book = new ABCJS.TuneBook(abc);
|
83
|
+
var abcParser = new window.ABCJS.parse.Parse();
|
84
|
+
|
85
|
+
// output each tune, if it exists. Otherwise clear the div.
|
86
|
+
for (var i = 0; i < output.length; i++) {
|
87
|
+
var div = output[i];
|
88
|
+
if (typeof(div) === "string")
|
89
|
+
div = document.getElementById(div);
|
90
|
+
if (div) {
|
91
|
+
div.innerHTML = "";
|
92
|
+
if (currentTune < book.tunes.length) {
|
93
|
+
abcParser.parse(book.tunes[currentTune].abc, parserParams);
|
94
|
+
var tune = abcParser.getTune();
|
95
|
+
callback(div, tune);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
currentTune++;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
// A quick way to render a tune from javascript when interactivity is not required.
|
103
|
+
// This is used when a javascript routine has some abc text that it wants to render
|
104
|
+
// in a div or collection of divs. One tune or many can be rendered.
|
105
|
+
//
|
106
|
+
// parameters:
|
107
|
+
// output: an array of divs that the individual tunes are rendered to.
|
108
|
+
// If the number of tunes exceeds the number of divs in the array, then
|
109
|
+
// only the first tunes are rendered. If the number of divs exceeds the number
|
110
|
+
// of tunes, then the unused divs are cleared. The divs can be passed as either
|
111
|
+
// elements or strings of ids. If ids are passed, then the div MUST exist already.
|
112
|
+
// (if a single element is passed, then it is an implied array of length one.)
|
113
|
+
// (if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
|
114
|
+
// abc: text representing a tune or an entire tune book in ABC notation.
|
115
|
+
// renderParams: hash of:
|
116
|
+
// startingTune: an index, starting at zero, representing which tune to start rendering at.
|
117
|
+
// (If this element is not present, then rendering starts at zero.)
|
118
|
+
// width: 800 by default. The width in pixels of the output paper
|
119
|
+
ABCJS.renderAbc = function(output, abc, parserParams, printerParams, renderParams) {
|
120
|
+
function callback(div, tune) {
|
121
|
+
var width = renderParams ? renderParams.width ? renderParams.width : 800 : 800;
|
122
|
+
var paper = Raphael(div, width, 400);
|
123
|
+
if (printerParams === undefined)
|
124
|
+
printerParams = {};
|
125
|
+
var printer = new ABCJS.write.Printer(paper, printerParams);
|
126
|
+
printer.printABC(tune);
|
127
|
+
}
|
128
|
+
|
129
|
+
renderEngine(callback, output, abc, parserParams, renderParams);
|
130
|
+
};
|
131
|
+
|
132
|
+
// A quick way to render a tune from javascript when interactivity is not required.
|
133
|
+
// This is used when a javascript routine has some abc text that it wants to render
|
134
|
+
// in a div or collection of divs. One tune or many can be rendered.
|
135
|
+
//
|
136
|
+
// parameters:
|
137
|
+
// output: an array of divs that the individual tunes are rendered to.
|
138
|
+
// If the number of tunes exceeds the number of divs in the array, then
|
139
|
+
// only the first tunes are rendered. If the number of divs exceeds the number
|
140
|
+
// of tunes, then the unused divs are cleared. The divs can be passed as either
|
141
|
+
// elements or strings of ids. If ids are passed, then the div MUST exist already.
|
142
|
+
// (if a single element is passed, then it is an implied array of length one.)
|
143
|
+
// (if a null is passed for an element, or the element doesn't exist, then that tune is skipped.)
|
144
|
+
// abc: text representing a tune or an entire tune book in ABC notation.
|
145
|
+
// renderParams: hash of:
|
146
|
+
// startingTune: an index, starting at zero, representing which tune to start rendering at.
|
147
|
+
// (If this element is not present, then rendering starts at zero.)
|
148
|
+
ABCJS.renderMidi = function(output, abc, parserParams, midiParams, renderParams) {
|
149
|
+
function callback(div, tune) {
|
150
|
+
if (midiParams === undefined)
|
151
|
+
midiParams = {};
|
152
|
+
var midiwriter = new ABCJS.midi.MidiWriter(div, midiParams);
|
153
|
+
midiwriter.writeABC(tune);
|
154
|
+
}
|
155
|
+
|
156
|
+
renderEngine(callback, output, abc, parserParams, renderParams);
|
157
|
+
};
|
158
|
+
})();
|
@@ -0,0 +1,686 @@
|
|
1
|
+
// abc_tune.js: a computer usable internal structure representing one tune.
|
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.data)
|
23
|
+
window.ABCJS.data = {};
|
24
|
+
|
25
|
+
// This is the data for a single ABC tune. It is created and populated by the window.ABCJS.parse.Parse class.
|
26
|
+
window.ABCJS.data.Tune = function() {
|
27
|
+
// The structure consists of a hash with the following two items:
|
28
|
+
// metaText: a hash of {key, value}, where key is one of: title, author, rhythm, source, transcription, unalignedWords, etc...
|
29
|
+
// tempo: { noteLength: number (e.g. .125), bpm: number }
|
30
|
+
// lines: an array of elements, or one of the following:
|
31
|
+
//
|
32
|
+
// STAFF: array of elements
|
33
|
+
// SUBTITLE: string
|
34
|
+
//
|
35
|
+
// TODO: actually, the start and end char should modify each part of the note type
|
36
|
+
// The elements all have a type field and a start and end char
|
37
|
+
// field. The rest of the fields depend on the type and are listed below:
|
38
|
+
// REST: duration=1,2,4,8; chord: string
|
39
|
+
// NOTE: accidental=none,dbl_flat,flat,natural,sharp,dbl_sharp
|
40
|
+
// pitch: "C" is 0. The numbers refer to the pitch letter.
|
41
|
+
// duration: .5 (sixteenth), .75 (dotted sixteenth), 1 (eighth), 1.5 (dotted eighth)
|
42
|
+
// 2 (quarter), 3 (dotted quarter), 4 (half), 6 (dotted half) 8 (whole)
|
43
|
+
// chord: { name:chord, position: one of 'default', 'above', 'below' }
|
44
|
+
// end_beam = true or undefined if this is the last note in a beam.
|
45
|
+
// lyric: array of { syllable: xxx, divider: one of " -_" }
|
46
|
+
// startTie = true|undefined
|
47
|
+
// endTie = true|undefined
|
48
|
+
// startTriplet = num <- that is the number to print
|
49
|
+
// endTriplet = true|undefined (the last note of the triplet)
|
50
|
+
// TODO: actually, decoration should be an array.
|
51
|
+
// decoration: upbow, downbow, accent
|
52
|
+
// BAR: type=bar_thin, bar_thin_thick, bar_thin_thin, bar_thick_thin, bar_right_repeat, bar_left_repeat, bar_double_repeat
|
53
|
+
// number: 1 or 2: if it is the start of a first or second ending
|
54
|
+
// CLEF: type=treble,bass
|
55
|
+
// KEY-SIG:
|
56
|
+
// accidentals[]: { acc:sharp|dblsharp|natural|flat|dblflat, note:a|b|c|d|e|f|g }
|
57
|
+
// METER: type: common_time,cut_time,specified
|
58
|
+
// if specified, { num: 99, den: 99 }
|
59
|
+
this.reset = function () {
|
60
|
+
this.version = "1.0.1";
|
61
|
+
this.media = "screen";
|
62
|
+
this.metaText = {};
|
63
|
+
this.formatting = {};
|
64
|
+
this.lines = [];
|
65
|
+
this.staffNum = 0;
|
66
|
+
this.voiceNum = 0;
|
67
|
+
this.lineNum = 0;
|
68
|
+
};
|
69
|
+
|
70
|
+
this.cleanUp = function(defWidth, defLength, barsperstaff, staffnonote) {
|
71
|
+
this.closeLine(); // Close the last line.
|
72
|
+
|
73
|
+
// Remove any blank lines
|
74
|
+
var anyDeleted = false;
|
75
|
+
var i, s, v;
|
76
|
+
for (i = 0; i < this.lines.length; i++) {
|
77
|
+
if (this.lines[i].staff !== undefined) {
|
78
|
+
var hasAny = false;
|
79
|
+
for (s = 0; s < this.lines[i].staff.length; s++) {
|
80
|
+
if (this.lines[i].staff[s] === undefined) {
|
81
|
+
anyDeleted = true;
|
82
|
+
this.lines[i].staff[s] = null;
|
83
|
+
//this.lines[i].staff[s] = { voices: []}; // TODO-PER: There was a part missing in the abc music. How should we recover?
|
84
|
+
} else {
|
85
|
+
for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
|
86
|
+
if (this.lines[i].staff[s].voices[v] === undefined)
|
87
|
+
this.lines[i].staff[s].voices[v] = []; // TODO-PER: There was a part missing in the abc music. How should we recover?
|
88
|
+
else
|
89
|
+
if (this.containsNotes(this.lines[i].staff[s].voices[v])) hasAny = true;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
if (!hasAny) {
|
94
|
+
this.lines[i] = null;
|
95
|
+
anyDeleted = true;
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
if (anyDeleted) {
|
100
|
+
this.lines = window.ABCJS.parse.compact(this.lines);
|
101
|
+
window.ABCJS.parse.each(this.lines, function(line) {
|
102
|
+
if (line.staff)
|
103
|
+
line.staff = window.ABCJS.parse.compact(line.staff);
|
104
|
+
});
|
105
|
+
}
|
106
|
+
|
107
|
+
// if we exceeded the number of bars allowed on a line, then force a new line
|
108
|
+
if (barsperstaff) {
|
109
|
+
for (i = 0; i < this.lines.length; i++) {
|
110
|
+
if (this.lines[i].staff !== undefined) {
|
111
|
+
for (s = 0; s < this.lines[i].staff.length; s++) {
|
112
|
+
for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
|
113
|
+
var barNumThisLine = 0;
|
114
|
+
for (var n = 0; n < this.lines[i].staff[s].voices[v].length; n++) {
|
115
|
+
if (this.lines[i].staff[s].voices[v][n].el_type === 'bar') {
|
116
|
+
barNumThisLine++;
|
117
|
+
if (barNumThisLine >= barsperstaff) {
|
118
|
+
// push everything else to the next line, if there is anything else,
|
119
|
+
// and there is a next line. If there isn't a next line, create one.
|
120
|
+
if (n < this.lines[i].staff[s].voices[v].length - 1) {
|
121
|
+
if (i === this.lines.length - 1) {
|
122
|
+
var cp = JSON.parse(JSON.stringify(this.lines[i]));
|
123
|
+
this.lines.push(window.ABCJS.parse.clone(cp));
|
124
|
+
for (var ss = 0; ss < this.lines[i+1].staff.length; ss++) {
|
125
|
+
for (var vv = 0; vv < this.lines[i+1].staff[ss].voices.length; vv++)
|
126
|
+
this.lines[i+1].staff[ss].voices[vv] = [];
|
127
|
+
}
|
128
|
+
}
|
129
|
+
var startElement = n + 1;
|
130
|
+
var section = this.lines[i].staff[s].voices[v].slice(startElement);
|
131
|
+
this.lines[i].staff[s].voices[v] = this.lines[i].staff[s].voices[v].slice(0, startElement);
|
132
|
+
this.lines[i+1].staff[s].voices[v] = section.concat(this.lines[i+1].staff[s].voices[v]);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}
|
137
|
+
|
138
|
+
}
|
139
|
+
}
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
// If we were passed staffnonote, then we want to get rid of all staffs that contain only rests.
|
145
|
+
if (barsperstaff) {
|
146
|
+
anyDeleted = false;
|
147
|
+
for (i = 0; i < this.lines.length; i++) {
|
148
|
+
if (this.lines[i].staff !== undefined) {
|
149
|
+
for (s = 0; s < this.lines[i].staff.length; s++) {
|
150
|
+
var keepThis = false;
|
151
|
+
for (v = 0; v < this.lines[i].staff[s].voices.length; v++) {
|
152
|
+
if (this.containsNotesStrict(this.lines[i].staff[s].voices[v])) {
|
153
|
+
keepThis = true;
|
154
|
+
}
|
155
|
+
}
|
156
|
+
if (!keepThis) {
|
157
|
+
anyDeleted = true;
|
158
|
+
this.lines[i].staff[s] = null;
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
}
|
163
|
+
if (anyDeleted) {
|
164
|
+
window.ABCJS.parse.each(this.lines, function(line) {
|
165
|
+
if (line.staff)
|
166
|
+
line.staff = window.ABCJS.parse.compact(line.staff);
|
167
|
+
});
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
function cleanUpSlursInLine(line) {
|
172
|
+
var currSlur = [];
|
173
|
+
var x;
|
174
|
+
// var lyr = null; // TODO-PER: debugging.
|
175
|
+
|
176
|
+
var addEndSlur = function(obj, num, chordPos) {
|
177
|
+
if (currSlur[chordPos] === undefined) {
|
178
|
+
// There isn't an exact match for note position, but we'll take any other open slur.
|
179
|
+
for (x = 0; x < currSlur.length; x++) {
|
180
|
+
if (currSlur[x] !== undefined) {
|
181
|
+
chordPos = x;
|
182
|
+
break;
|
183
|
+
}
|
184
|
+
}
|
185
|
+
if (currSlur[chordPos] === undefined) {
|
186
|
+
var offNum = chordPos*100;
|
187
|
+
window.ABCJS.parse.each(obj.endSlur, function(x) { if (offNum === x) --offNum; });
|
188
|
+
currSlur[chordPos] = [offNum];
|
189
|
+
}
|
190
|
+
}
|
191
|
+
var slurNum;
|
192
|
+
for (var i = 0; i < num; i++) {
|
193
|
+
slurNum = currSlur[chordPos].pop();
|
194
|
+
obj.endSlur.push(slurNum);
|
195
|
+
// lyr.syllable += '<' + slurNum; // TODO-PER: debugging
|
196
|
+
}
|
197
|
+
if (currSlur[chordPos].length === 0)
|
198
|
+
delete currSlur[chordPos];
|
199
|
+
return slurNum;
|
200
|
+
};
|
201
|
+
|
202
|
+
var addStartSlur = function(obj, num, chordPos, usedNums) {
|
203
|
+
obj.startSlur = [];
|
204
|
+
if (currSlur[chordPos] === undefined) {
|
205
|
+
currSlur[chordPos] = [];
|
206
|
+
}
|
207
|
+
var nextNum = chordPos*100+1;
|
208
|
+
for (var i = 0; i < num; i++) {
|
209
|
+
if (usedNums) {
|
210
|
+
window.ABCJS.parse.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
211
|
+
window.ABCJS.parse.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
212
|
+
window.ABCJS.parse.each(usedNums, function(x) { if (nextNum === x) ++nextNum; });
|
213
|
+
}
|
214
|
+
window.ABCJS.parse.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
|
215
|
+
window.ABCJS.parse.each(currSlur[chordPos], function(x) { if (nextNum === x) ++nextNum; });
|
216
|
+
|
217
|
+
currSlur[chordPos].push(nextNum);
|
218
|
+
obj.startSlur.push({ label: nextNum });
|
219
|
+
// lyr.syllable += ' ' + nextNum + '>'; // TODO-PER:debugging
|
220
|
+
nextNum++;
|
221
|
+
}
|
222
|
+
};
|
223
|
+
|
224
|
+
for (var i = 0; i < line.length; i++) {
|
225
|
+
var el = line[i];
|
226
|
+
// if (el.lyric === undefined) // TODO-PER: debugging
|
227
|
+
// el.lyric = [{ divider: '-' }]; // TODO-PER: debugging
|
228
|
+
// lyr = el.lyric[0]; // TODO-PER: debugging
|
229
|
+
// lyr.syllable = ''; // TODO-PER: debugging
|
230
|
+
if (el.el_type === 'note') {
|
231
|
+
if (el.gracenotes) {
|
232
|
+
for (var g = 0; g < el.gracenotes.length; g++) {
|
233
|
+
if (el.gracenotes[g].endSlur) {
|
234
|
+
var gg = el.gracenotes[g].endSlur;
|
235
|
+
el.gracenotes[g].endSlur = [];
|
236
|
+
for (var ggg = 0; ggg < gg; ggg++)
|
237
|
+
addEndSlur(el.gracenotes[g], 1, 20);
|
238
|
+
}
|
239
|
+
if (el.gracenotes[g].startSlur) {
|
240
|
+
x = el.gracenotes[g].startSlur;
|
241
|
+
addStartSlur(el.gracenotes[g], x, 20);
|
242
|
+
}
|
243
|
+
}
|
244
|
+
}
|
245
|
+
if (el.endSlur) {
|
246
|
+
x = el.endSlur;
|
247
|
+
el.endSlur = [];
|
248
|
+
addEndSlur(el, x, 0);
|
249
|
+
}
|
250
|
+
if (el.startSlur) {
|
251
|
+
x = el.startSlur;
|
252
|
+
addStartSlur(el, x, 0);
|
253
|
+
}
|
254
|
+
if (el.pitches) {
|
255
|
+
var usedNums = [];
|
256
|
+
for (var p = 0; p < el.pitches.length; p++) {
|
257
|
+
if (el.pitches[p].endSlur) {
|
258
|
+
var k = el.pitches[p].endSlur;
|
259
|
+
el.pitches[p].endSlur = [];
|
260
|
+
for (var j = 0; j < k; j++) {
|
261
|
+
var slurNum = addEndSlur(el.pitches[p], 1, p+1);
|
262
|
+
usedNums.push(slurNum);
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
for (p = 0; p < el.pitches.length; p++) {
|
267
|
+
if (el.pitches[p].startSlur) {
|
268
|
+
x = el.pitches[p].startSlur;
|
269
|
+
addStartSlur(el.pitches[p], x, p+1, usedNums);
|
270
|
+
}
|
271
|
+
}
|
272
|
+
// Correct for the weird gracenote case where ({g}a) should match.
|
273
|
+
// The end slur was already assigned to the note, and needs to be moved to the first note of the graces.
|
274
|
+
if (el.gracenotes && el.pitches[0].endSlur && el.pitches[0].endSlur[0] === 100 && el.pitches[0].startSlur) {
|
275
|
+
if (el.gracenotes[0].endSlur)
|
276
|
+
el.gracenotes[0].endSlur.push(el.pitches[0].startSlur[0].label);
|
277
|
+
else
|
278
|
+
el.gracenotes[0].endSlur = [el.pitches[0].startSlur[0].label];
|
279
|
+
if (el.pitches[0].endSlur.length === 1)
|
280
|
+
delete el.pitches[0].endSlur;
|
281
|
+
else if (el.pitches[0].endSlur[0] === 100)
|
282
|
+
el.pitches[0].endSlur.shift();
|
283
|
+
else if (el.pitches[0].endSlur[el.pitches[0].endSlur.length-1] === 100)
|
284
|
+
el.pitches[0].endSlur.pop();
|
285
|
+
if (currSlur[1].length === 1)
|
286
|
+
delete currSlur[1];
|
287
|
+
else
|
288
|
+
currSlur[1].pop();
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
|
295
|
+
// TODO-PER: This could be done faster as we go instead of as the last step.
|
296
|
+
function fixClefPlacement(el) {
|
297
|
+
//if (el.el_type === 'clef') {
|
298
|
+
var min = -2;
|
299
|
+
var max = 5;
|
300
|
+
switch(el.type) {
|
301
|
+
case 'treble+8':
|
302
|
+
case 'treble-8':
|
303
|
+
break;
|
304
|
+
case 'bass':
|
305
|
+
case 'bass+8':
|
306
|
+
case 'bass-8':
|
307
|
+
el.verticalPos = 20 + el.verticalPos; min += 6; max += 6;
|
308
|
+
break;
|
309
|
+
case 'tenor':
|
310
|
+
case 'tenor+8':
|
311
|
+
case 'tenor-8':
|
312
|
+
el.verticalPos = - el.verticalPos; min = -40; max = 40;
|
313
|
+
// el.verticalPos+=2; min += 6; max += 6;
|
314
|
+
break;
|
315
|
+
case 'alto':
|
316
|
+
case 'alto+8':
|
317
|
+
case 'alto-8':
|
318
|
+
el.verticalPos = - el.verticalPos; min = -40; max = 40;
|
319
|
+
// el.verticalPos-=2; min += 4; max += 4;
|
320
|
+
break;
|
321
|
+
}
|
322
|
+
if (el.verticalPos < min) {
|
323
|
+
while (el.verticalPos < min)
|
324
|
+
el.verticalPos += 7;
|
325
|
+
} else if (el.verticalPos > max) {
|
326
|
+
while (el.verticalPos > max)
|
327
|
+
el.verticalPos -= 7;
|
328
|
+
}
|
329
|
+
//}
|
330
|
+
}
|
331
|
+
|
332
|
+
for (this.lineNum = 0; this.lineNum < this.lines.length; this.lineNum++) {
|
333
|
+
if (this.lines[this.lineNum].staff) for (this.staffNum = 0; this.staffNum < this.lines[this.lineNum].staff.length; this.staffNum++) {
|
334
|
+
if (this.lines[this.lineNum].staff[this.staffNum].clef)
|
335
|
+
fixClefPlacement(this.lines[this.lineNum].staff[this.staffNum].clef);
|
336
|
+
for (this.voiceNum = 0; this.voiceNum < this.lines[this.lineNum].staff[this.staffNum].voices.length; this.voiceNum++) {
|
337
|
+
// var el = this.getLastNote();
|
338
|
+
// if (el) el.end_beam = true;
|
339
|
+
cleanUpSlursInLine(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]);
|
340
|
+
for (var j = 0; j < this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length; j++)
|
341
|
+
if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][j].el_type === 'clef')
|
342
|
+
fixClefPlacement(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][j]);
|
343
|
+
}
|
344
|
+
}
|
345
|
+
}
|
346
|
+
|
347
|
+
if (!this.formatting.pagewidth)
|
348
|
+
this.formatting.pagewidth = defWidth;
|
349
|
+
if (!this.formatting.pageheight)
|
350
|
+
this.formatting.pageheight = defLength;
|
351
|
+
|
352
|
+
// Remove temporary variables that the outside doesn't need to know about
|
353
|
+
delete this.staffNum;
|
354
|
+
delete this.voiceNum;
|
355
|
+
delete this.lineNum;
|
356
|
+
delete this.potentialStartBeam;
|
357
|
+
delete this.potentialEndBeam;
|
358
|
+
delete this.vskipPending;
|
359
|
+
};
|
360
|
+
|
361
|
+
this.reset();
|
362
|
+
|
363
|
+
this.getLastNote = function() {
|
364
|
+
if (this.lines[this.lineNum] && this.lines[this.lineNum].staff && this.lines[this.lineNum].staff[this.staffNum] &&
|
365
|
+
this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum]) {
|
366
|
+
for (var i = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum].length-1; i >= 0; i--) {
|
367
|
+
var el = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum][i];
|
368
|
+
if (el.el_type === 'note') {
|
369
|
+
return el;
|
370
|
+
}
|
371
|
+
}
|
372
|
+
}
|
373
|
+
return null;
|
374
|
+
};
|
375
|
+
|
376
|
+
this.addTieToLastNote = function() {
|
377
|
+
// TODO-PER: if this is a chord, which note?
|
378
|
+
var el = this.getLastNote();
|
379
|
+
if (el && el.pitches && el.pitches.length > 0) {
|
380
|
+
el.pitches[0].startTie = {};
|
381
|
+
return true;
|
382
|
+
}
|
383
|
+
return false;
|
384
|
+
};
|
385
|
+
|
386
|
+
this.getDuration = function(el) {
|
387
|
+
if (el.duration) return el.duration;
|
388
|
+
//if (el.pitches && el.pitches.length > 0) return el.pitches[0].duration;
|
389
|
+
return 0;
|
390
|
+
};
|
391
|
+
|
392
|
+
this.closeLine = function() {
|
393
|
+
if (this.potentialStartBeam && this.potentialEndBeam) {
|
394
|
+
this.potentialStartBeam.startBeam = true;
|
395
|
+
this.potentialEndBeam.endBeam = true;
|
396
|
+
}
|
397
|
+
delete this.potentialStartBeam;
|
398
|
+
delete this.potentialEndBeam;
|
399
|
+
};
|
400
|
+
|
401
|
+
this.appendElement = function(type, startChar, endChar, hashParams)
|
402
|
+
{
|
403
|
+
var This = this;
|
404
|
+
var pushNote = function(hp) {
|
405
|
+
if (hp.pitches !== undefined) {
|
406
|
+
var mid = This.lines[This.lineNum].staff[This.staffNum].clef.verticalPos;
|
407
|
+
window.ABCJS.parse.each(hp.pitches, function(p) { p.verticalPos = p.pitch - mid; });
|
408
|
+
}
|
409
|
+
if (hp.gracenotes !== undefined) {
|
410
|
+
var mid2 = This.lines[This.lineNum].staff[This.staffNum].clef.verticalPos;
|
411
|
+
window.ABCJS.parse.each(hp.gracenotes, function(p) { p.verticalPos = p.pitch - mid2; });
|
412
|
+
}
|
413
|
+
This.lines[This.lineNum].staff[This.staffNum].voices[This.voiceNum].push(hp);
|
414
|
+
};
|
415
|
+
hashParams.el_type = type;
|
416
|
+
if (startChar !== null)
|
417
|
+
hashParams.startChar = startChar;
|
418
|
+
if (endChar !== null)
|
419
|
+
hashParams.endChar = endChar;
|
420
|
+
var endBeamHere = function() {
|
421
|
+
This.potentialStartBeam.startBeam = true;
|
422
|
+
hashParams.endBeam = true;
|
423
|
+
delete This.potentialStartBeam;
|
424
|
+
delete This.potentialEndBeam;
|
425
|
+
};
|
426
|
+
var endBeamLast = function() {
|
427
|
+
if (This.potentialStartBeam !== undefined && This.potentialEndBeam !== undefined) { // Do we have a set of notes to beam?
|
428
|
+
This.potentialStartBeam.startBeam = true;
|
429
|
+
This.potentialEndBeam.endBeam = true;
|
430
|
+
}
|
431
|
+
delete This.potentialStartBeam;
|
432
|
+
delete This.potentialEndBeam;
|
433
|
+
};
|
434
|
+
if (type === 'note') { // && (hashParams.rest !== undefined || hashParams.end_beam === undefined)) {
|
435
|
+
// Now, add the startBeam and endBeam where it is needed.
|
436
|
+
// end_beam is already set on the places where there is a forced end_beam. We'll remove that here after using that info.
|
437
|
+
// this.potentialStartBeam either points to null or the start beam.
|
438
|
+
// this.potentialEndBeam either points to null or the start beam.
|
439
|
+
// If we have a beam break (note is longer than a quarter, or an end_beam is on this element), then set the beam if we have one.
|
440
|
+
// reset the variables for the next notes.
|
441
|
+
var dur = This.getDuration(hashParams);
|
442
|
+
if (dur >= 0.25) { // The beam ends on the note before this.
|
443
|
+
endBeamLast();
|
444
|
+
} else if (hashParams.force_end_beam_last && This.potentialStartBeam !== undefined) {
|
445
|
+
endBeamLast();
|
446
|
+
} else if (hashParams.end_beam && This.potentialStartBeam !== undefined) { // the beam is forced to end on this note, probably because of a space in the ABC
|
447
|
+
if (hashParams.rest === undefined)
|
448
|
+
endBeamHere();
|
449
|
+
else
|
450
|
+
endBeamLast();
|
451
|
+
} else if (hashParams.rest === undefined) { // this a short note and we aren't about to end the beam
|
452
|
+
if (This.potentialStartBeam === undefined) { // We aren't collecting notes for a beam, so start here.
|
453
|
+
if (!hashParams.end_beam) {
|
454
|
+
This.potentialStartBeam = hashParams;
|
455
|
+
delete This.potentialEndBeam;
|
456
|
+
}
|
457
|
+
} else {
|
458
|
+
This.potentialEndBeam = hashParams; // Continue the beaming, look for the end next note.
|
459
|
+
}
|
460
|
+
}
|
461
|
+
|
462
|
+
// end_beam goes on rests and notes which precede rests _except_ when a rest (or set of adjacent rests) has normal notes on both sides (no spaces)
|
463
|
+
// if (hashParams.rest !== undefined)
|
464
|
+
// {
|
465
|
+
// hashParams.end_beam = true;
|
466
|
+
// var el2 = this.getLastNote();
|
467
|
+
// if (el2) el2.end_beam = true;
|
468
|
+
// // TODO-PER: implement exception mentioned in the comment.
|
469
|
+
// }
|
470
|
+
} else { // It's not a note, so there definitely isn't beaming after it.
|
471
|
+
endBeamLast();
|
472
|
+
}
|
473
|
+
delete hashParams.end_beam; // We don't want this temporary variable hanging around.
|
474
|
+
delete hashParams.force_end_beam_last; // We don't want this temporary variable hanging around.
|
475
|
+
pushNote(hashParams);
|
476
|
+
};
|
477
|
+
|
478
|
+
this.appendStartingElement = function(type, startChar, endChar, hashParams2)
|
479
|
+
{
|
480
|
+
// We only ever want implied naturals the first time.
|
481
|
+
var impliedNaturals;
|
482
|
+
if (type === 'key') {
|
483
|
+
impliedNaturals = hashParams2.impliedNaturals;
|
484
|
+
delete hashParams2.impliedNaturals;
|
485
|
+
}
|
486
|
+
|
487
|
+
// Clone the object because it will be sticking around for the next line and we don't want the extra fields in it.
|
488
|
+
var hashParams = window.ABCJS.parse.clone(hashParams2);
|
489
|
+
|
490
|
+
// These elements should not be added twice, so if the element exists on this line without a note or bar before it, just replace the staff version.
|
491
|
+
var voice = this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
|
492
|
+
for (var i = 0; i < voice.length; i++) {
|
493
|
+
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar') {
|
494
|
+
hashParams.el_type = type;
|
495
|
+
hashParams.startChar = startChar;
|
496
|
+
hashParams.endChar = endChar;
|
497
|
+
if (impliedNaturals)
|
498
|
+
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
|
499
|
+
voice.push(hashParams);
|
500
|
+
return;
|
501
|
+
}
|
502
|
+
if (voice[i].el_type === type) {
|
503
|
+
hashParams.el_type = type;
|
504
|
+
hashParams.startChar = startChar;
|
505
|
+
hashParams.endChar = endChar;
|
506
|
+
if (impliedNaturals)
|
507
|
+
hashParams.accidentals = impliedNaturals.concat(hashParams.accidentals);
|
508
|
+
voice[i] = hashParams;
|
509
|
+
return;
|
510
|
+
}
|
511
|
+
}
|
512
|
+
// We didn't see either that type or a note, so replace the element to the staff.
|
513
|
+
this.lines[this.lineNum].staff[this.staffNum][type] = hashParams2;
|
514
|
+
};
|
515
|
+
|
516
|
+
this.getNumLines = function() {
|
517
|
+
return this.lines.length;
|
518
|
+
};
|
519
|
+
|
520
|
+
this.pushLine = function(hash) {
|
521
|
+
if (this.vskipPending) {
|
522
|
+
hash.vskip = this.vskipPending;
|
523
|
+
delete this.vskipPending;
|
524
|
+
}
|
525
|
+
this.lines.push(hash);
|
526
|
+
};
|
527
|
+
|
528
|
+
this.addSubtitle = function(str) {
|
529
|
+
this.pushLine({subtitle: str});
|
530
|
+
};
|
531
|
+
|
532
|
+
this.addSpacing = function(num) {
|
533
|
+
this.vskipPending = num;
|
534
|
+
};
|
535
|
+
|
536
|
+
this.addNewPage = function(num) {
|
537
|
+
this.pushLine({newpage: num});
|
538
|
+
};
|
539
|
+
|
540
|
+
this.addSeparator = function(spaceAbove, spaceBelow, lineLength) {
|
541
|
+
this.pushLine({separator: {spaceAbove: spaceAbove, spaceBelow: spaceBelow, lineLength: lineLength}});
|
542
|
+
};
|
543
|
+
|
544
|
+
this.addText = function(str) {
|
545
|
+
this.pushLine({text: str});
|
546
|
+
};
|
547
|
+
|
548
|
+
this.addCentered = function(str) {
|
549
|
+
this.pushLine({text: [{text: str, center: true }]});
|
550
|
+
};
|
551
|
+
|
552
|
+
this.containsNotes = function(voice) {
|
553
|
+
for (var i = 0; i < voice.length; i++) {
|
554
|
+
if (voice[i].el_type === 'note' || voice[i].el_type === 'bar')
|
555
|
+
return true;
|
556
|
+
}
|
557
|
+
return false;
|
558
|
+
};
|
559
|
+
|
560
|
+
this.containsNotesStrict = function(voice) {
|
561
|
+
for (var i = 0; i < voice.length; i++) {
|
562
|
+
if (voice[i].el_type === 'note' && voice[i].rest === undefined)
|
563
|
+
return true;
|
564
|
+
}
|
565
|
+
return false;
|
566
|
+
};
|
567
|
+
|
568
|
+
// anyVoiceContainsNotes: function(line) {
|
569
|
+
// for (var i = 0; i < line.staff.voices.length; i++) {
|
570
|
+
// if (this.containsNotes(line.staff.voices[i]))
|
571
|
+
// return true;
|
572
|
+
// }
|
573
|
+
// return false;
|
574
|
+
// },
|
575
|
+
|
576
|
+
this.startNewLine = function(params) {
|
577
|
+
// If the pointed to line doesn't exist, just create that. If the line does exist, but doesn't have any music on it, just use it.
|
578
|
+
// If it does exist and has music, then increment the line number. If the new element doesn't exist, create it.
|
579
|
+
var This = this;
|
580
|
+
this.closeLine(); // Close the previous line.
|
581
|
+
var createVoice = function(params) {
|
582
|
+
This.lines[This.lineNum].staff[This.staffNum].voices[This.voiceNum] = [];
|
583
|
+
if (This.isFirstLine(This.lineNum)) {
|
584
|
+
if (params.name) {if (!This.lines[This.lineNum].staff[This.staffNum].title) This.lines[This.lineNum].staff[This.staffNum].title = [];This.lines[This.lineNum].staff[This.staffNum].title[This.voiceNum] = params.name;}
|
585
|
+
} else {
|
586
|
+
if (params.subname) {if (!This.lines[This.lineNum].staff[This.staffNum].title) This.lines[This.lineNum].staff[This.staffNum].title = [];This.lines[This.lineNum].staff[This.staffNum].title[This.voiceNum] = params.subname;}
|
587
|
+
}
|
588
|
+
if (params.style)
|
589
|
+
This.appendElement('style', null, null, {head: params.style});
|
590
|
+
if (params.stem)
|
591
|
+
This.appendElement('stem', null, null, {direction: params.stem});
|
592
|
+
else if (This.voiceNum > 0) {
|
593
|
+
if (This.lines[This.lineNum].staff[This.staffNum].voices[0]!== undefined) {
|
594
|
+
var found = false;
|
595
|
+
for (var i = 0; i < This.lines[This.lineNum].staff[This.staffNum].voices[0].length; i++) {
|
596
|
+
if (This.lines[This.lineNum].staff[This.staffNum].voices[0].el_type === 'stem')
|
597
|
+
found = true;
|
598
|
+
}
|
599
|
+
if (!found) {
|
600
|
+
var stem = { el_type: 'stem', direction: 'up' };
|
601
|
+
This.lines[This.lineNum].staff[This.staffNum].voices[0].splice(0,0,stem);
|
602
|
+
}
|
603
|
+
}
|
604
|
+
This.appendElement('stem', null, null, {direction: 'down'});
|
605
|
+
}
|
606
|
+
if (params.scale)
|
607
|
+
This.appendElement('scale', null, null, { size: params.scale} );
|
608
|
+
};
|
609
|
+
var createStaff = function(params) {
|
610
|
+
This.lines[This.lineNum].staff[This.staffNum] = {voices: [ ], clef: params.clef, key: params.key};
|
611
|
+
if (params.vocalfont) This.lines[This.lineNum].staff[This.staffNum].vocalfont = params.vocalfont;
|
612
|
+
if (params.bracket) This.lines[This.lineNum].staff[This.staffNum].bracket = params.bracket;
|
613
|
+
if (params.brace) This.lines[This.lineNum].staff[This.staffNum].brace = params.brace;
|
614
|
+
if (params.connectBarLines) This.lines[This.lineNum].staff[This.staffNum].connectBarLines = params.connectBarLines;
|
615
|
+
createVoice(params);
|
616
|
+
// Some stuff just happens for the first voice
|
617
|
+
if (params.part)
|
618
|
+
This.appendElement('part', params.startChar, params.endChar, {title: params.part});
|
619
|
+
if (params.meter !== undefined) This.lines[This.lineNum].staff[This.staffNum].meter = params.meter;
|
620
|
+
};
|
621
|
+
var createLine = function(params) {
|
622
|
+
This.lines[This.lineNum] = {staff: []};
|
623
|
+
createStaff(params);
|
624
|
+
};
|
625
|
+
if (this.lines[this.lineNum] === undefined) createLine(params);
|
626
|
+
else if (this.lines[this.lineNum].staff === undefined) {
|
627
|
+
this.lineNum++;
|
628
|
+
this.startNewLine(params);
|
629
|
+
} else if (this.lines[this.lineNum].staff[this.staffNum] === undefined) createStaff(params);
|
630
|
+
else if (this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] === undefined) createVoice(params);
|
631
|
+
else if (!this.containsNotes(this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum])) return;
|
632
|
+
else {
|
633
|
+
this.lineNum++;
|
634
|
+
this.startNewLine(params);
|
635
|
+
}
|
636
|
+
};
|
637
|
+
|
638
|
+
this.hasBeginMusic = function() {
|
639
|
+
return this.lines.length > 0;
|
640
|
+
};
|
641
|
+
|
642
|
+
this.isFirstLine = function(index) {
|
643
|
+
for (var i = index-1; i >= 0; i--) {
|
644
|
+
if (this.lines[i].staff !== undefined) return false;
|
645
|
+
}
|
646
|
+
return true;
|
647
|
+
};
|
648
|
+
|
649
|
+
this.getCurrentVoice = function() {
|
650
|
+
if (this.lines[this.lineNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum] !== undefined && this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum] !== undefined)
|
651
|
+
return this.lines[this.lineNum].staff[this.staffNum].voices[this.voiceNum];
|
652
|
+
else return null;
|
653
|
+
};
|
654
|
+
|
655
|
+
this.setCurrentVoice = function(staffNum, voiceNum) {
|
656
|
+
this.staffNum = staffNum;
|
657
|
+
this.voiceNum = voiceNum;
|
658
|
+
for (var i = 0; i < this.lines.length; i++) {
|
659
|
+
if (this.lines[i].staff) {
|
660
|
+
if (this.lines[i].staff[staffNum] === undefined || this.lines[i].staff[staffNum].voices[voiceNum] === undefined ||
|
661
|
+
!this.containsNotes(this.lines[i].staff[staffNum].voices[voiceNum] )) {
|
662
|
+
this.lineNum = i;
|
663
|
+
return;
|
664
|
+
}
|
665
|
+
}
|
666
|
+
}
|
667
|
+
this.lineNum = i;
|
668
|
+
};
|
669
|
+
|
670
|
+
this.addMetaText = function(key, value) {
|
671
|
+
if (this.metaText[key] === undefined)
|
672
|
+
this.metaText[key] = value;
|
673
|
+
else
|
674
|
+
this.metaText[key] += "\n" + value;
|
675
|
+
};
|
676
|
+
|
677
|
+
this.addMetaTextArray = function(key, value) {
|
678
|
+
if (this.metaText[key] === undefined)
|
679
|
+
this.metaText[key] = [value];
|
680
|
+
else
|
681
|
+
this.metaText[key].push(value);
|
682
|
+
};
|
683
|
+
this.addMetaTextObj = function(key, value) {
|
684
|
+
this.metaText[key] = value;
|
685
|
+
};
|
686
|
+
}
|