abcjs-rails 1.1.0 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/app/assets/javascripts/abcjs-rails.js +1 -0
- data/app/assets/javascripts/abcjs/api/abc_tunebook.js +158 -0
- data/app/assets/javascripts/abcjs/data/abc_tune.js +686 -0
- data/app/assets/javascripts/abcjs/edit/abc_editor.js +414 -0
- data/app/assets/javascripts/abcjs/midi/abc_midiwriter.js +698 -0
- data/app/assets/javascripts/abcjs/parse/abc_common.js +76 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse.js +1385 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_directive.js +546 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_header.js +521 -0
- data/app/assets/javascripts/abcjs/parse/abc_parse_key_voice.js +781 -0
- data/app/assets/javascripts/abcjs/parse/abc_tokenizer.js +751 -0
- data/app/assets/javascripts/abcjs/write/abc_glyphs.js +105 -0
- data/app/assets/javascripts/abcjs/write/abc_graphelements.js +781 -0
- data/app/assets/javascripts/abcjs/write/abc_layout.js +959 -0
- data/app/assets/javascripts/abcjs/write/abc_write.js +487 -0
- data/app/assets/javascripts/abcjs/write/raphael.js +3395 -0
- data/app/assets/javascripts/abcjs/write/sprintf.js +61 -0
- data/lib/abcjs-rails/version.rb +1 -1
- metadata +18 -1
@@ -0,0 +1 @@
|
|
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
|
+
}
|