1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | Vex.Flow.Stave = (function() {
|
8 | function Stave(x, y, width, options) {
|
9 | if (arguments.length > 0) this.init(x, y, width, options);
|
10 | }
|
11 |
|
12 | var THICKNESS = (Vex.Flow.STAVE_LINE_THICKNESS > 1 ?
|
13 | Vex.Flow.STAVE_LINE_THICKNESS : 0);
|
14 | Stave.prototype = {
|
15 | init: function(x, y, width, options) {
|
16 | this.x = x;
|
17 | this.y = y;
|
18 | this.width = width;
|
19 | this.glyph_start_x = x + 5;
|
20 | this.glyph_end_x = x + width;
|
21 | this.start_x = this.glyph_start_x;
|
22 | this.end_x = this.glyph_end_x;
|
23 | this.context = null;
|
24 | this.glyphs = [];
|
25 | this.end_glyphs = [];
|
26 | this.modifiers = [];
|
27 | this.measure = 0;
|
28 | this.clef = "treble";
|
29 | this.font = {
|
30 | family: "sans-serif",
|
31 | size: 8,
|
32 | weight: ""
|
33 | };
|
34 | this.options = {
|
35 | vertical_bar_width: 10,
|
36 | glyph_spacing_px: 10,
|
37 | num_lines: 5,
|
38 | fill_style: "#999999",
|
39 | spacing_between_lines_px: 10,
|
40 | space_above_staff_ln: 4,
|
41 | space_below_staff_ln: 4,
|
42 | top_text_position: 1
|
43 | };
|
44 | this.bounds = {x: this.x, y: this.y, w: this.width, h: 0};
|
45 | Vex.Merge(this.options, options);
|
46 |
|
47 | this.resetLines();
|
48 |
|
49 | this.modifiers.push(
|
50 | new Vex.Flow.Barline(Vex.Flow.Barline.type.SINGLE, this.x));
|
51 | this.modifiers.push(
|
52 | new Vex.Flow.Barline(Vex.Flow.Barline.type.SINGLE,
|
53 | this.x + this.width));
|
54 | },
|
55 |
|
56 | resetLines: function() {
|
57 | this.options.line_config = [];
|
58 | for (var i = 0; i < this.options.num_lines; i++) {
|
59 | this.options.line_config.push({visible: true});
|
60 | }
|
61 | this.height = (this.options.num_lines + this.options.space_above_staff_ln) *
|
62 | this.options.spacing_between_lines_px;
|
63 | this.options.bottom_text_position = this.options.num_lines + 1;
|
64 | },
|
65 |
|
66 | setNoteStartX: function(x) { this.start_x = x; return this; },
|
67 | getNoteStartX: function() {
|
68 | var start_x = this.start_x;
|
69 |
|
70 |
|
71 |
|
72 | if (this.modifiers[0].barline == Vex.Flow.Barline.type.REPEAT_BEGIN &&
|
73 | this.modifiers.length > 2) {
|
74 | start_x += 20;
|
75 | }
|
76 |
|
77 | return start_x;
|
78 | },
|
79 |
|
80 | getNoteEndX: function() { return this.end_x; },
|
81 | getTieStartX: function() { return this.start_x; },
|
82 | getTieEndX: function() { return this.x + this.width; },
|
83 | setContext: function(context) {
|
84 | this.context = context;
|
85 | for(var i=0; i<this.glyphs.length; i++){
|
86 | if(typeof(this.glyphs[i].setContext) === "function"){
|
87 | this.glyphs[i].setContext(context);
|
88 | }
|
89 | }
|
90 | return this;
|
91 | },
|
92 | getContext: function() { return this.context; },
|
93 | getX: function() { return this.x; },
|
94 | getNumLines: function() { return this.options.num_lines; },
|
95 | setNumLines: function(lines) {
|
96 | this.options.num_lines = parseInt(lines, 10);
|
97 | this.resetLines();
|
98 | return this;
|
99 | },
|
100 | setY: function(y) { this.y = y; return this; },
|
101 |
|
102 | setX: function(x){
|
103 | var shift = x - this.x;
|
104 | this.x = x;
|
105 | this.glyph_start_x += shift;
|
106 | this.glyph_end_x += shift;
|
107 | this.start_x += shift;
|
108 | this.end_x += shift;
|
109 | for(var i=0; i<this.modifiers.length; i++) {
|
110 | var mod = this.modifiers[i];
|
111 | if (mod.x !== undefined) {
|
112 | mod.x += shift;
|
113 | }
|
114 | }
|
115 | return this;
|
116 | },
|
117 |
|
118 | setWidth: function(width) {
|
119 | this.width = width;
|
120 | this.glyph_end_x = this.x + width;
|
121 | this.end_x = this.glyph_end_x;
|
122 |
|
123 |
|
124 |
|
125 | return this;
|
126 | },
|
127 |
|
128 | getWidth: function() {
|
129 | return this.width;
|
130 | },
|
131 |
|
132 | setMeasure: function(measure) { this.measure = measure; return this; },
|
133 |
|
134 |
|
135 | setBegBarType: function(type) {
|
136 |
|
137 | if (type == Vex.Flow.Barline.type.SINGLE ||
|
138 | type == Vex.Flow.Barline.type.REPEAT_BEGIN ||
|
139 | type == Vex.Flow.Barline.type.NONE) {
|
140 | this.modifiers[0] = new Vex.Flow.Barline(type, this.x);
|
141 | }
|
142 | return this;
|
143 | },
|
144 |
|
145 | setEndBarType: function(type) {
|
146 |
|
147 | if (type != Vex.Flow.Barline.type.REPEAT_BEGIN)
|
148 | this.modifiers[1] = new Vex.Flow.Barline(type, this.x + this.width);
|
149 | return this;
|
150 | },
|
151 |
|
152 | |
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 | getModifierXShift: function(index) {
|
159 | if (typeof index === 'undefined') index = this.glyphs.length -1;
|
160 | if (typeof index !== 'number') new Vex.RERR("InvalidIndex",
|
161 | "Must be of number type");
|
162 |
|
163 | var x = this.glyph_start_x;
|
164 | var bar_x_shift = 0;
|
165 |
|
166 | for (var i = 0; i < index + 1; ++i) {
|
167 | var glyph = this.glyphs[i];
|
168 | x += glyph.getMetrics().width;
|
169 | bar_x_shift += glyph.getMetrics().width;
|
170 | }
|
171 |
|
172 |
|
173 | if (bar_x_shift > 0) bar_x_shift += this.options.vertical_bar_width + 10;
|
174 |
|
175 | return bar_x_shift;
|
176 | },
|
177 |
|
178 |
|
179 | setRepetitionTypeLeft: function(type, y) {
|
180 | this.modifiers.push(new Vex.Flow.Repetition(type, this.x, y));
|
181 | return this;
|
182 | },
|
183 |
|
184 | setRepetitionTypeRight: function(type, y) {
|
185 | this.modifiers.push(new Vex.Flow.Repetition(type, this.x, y) );
|
186 | return this;
|
187 | },
|
188 |
|
189 |
|
190 | setVoltaType: function(type, number_t, y) {
|
191 | this.modifiers.push(new Vex.Flow.Volta(type, number_t, this.x, y));
|
192 | return this;
|
193 | },
|
194 |
|
195 |
|
196 | setSection: function(section, y) {
|
197 | this.modifiers.push(new Vex.Flow.StaveSection(section, this.x, y));
|
198 | return this;
|
199 | },
|
200 |
|
201 |
|
202 | setTempo: function(tempo, y) {
|
203 | this.modifiers.push(new Vex.Flow.StaveTempo(tempo, this.x, y));
|
204 | return this;
|
205 | },
|
206 |
|
207 |
|
208 | setText: function(text, position, options) {
|
209 | this.modifiers.push(new Vex.Flow.StaveText(text, position, options));
|
210 | return this;
|
211 | },
|
212 |
|
213 | getHeight: function() {
|
214 | return this.height;
|
215 | },
|
216 |
|
217 | getSpacingBetweenLines: function() {
|
218 | return this.options.spacing_between_lines_px;
|
219 | },
|
220 |
|
221 | getBoundingBox: function() {
|
222 | return new Vex.Flow.BoundingBox(this.x, this.y, this.width, this.getBottomY() - this.y);
|
223 |
|
224 | },
|
225 |
|
226 | getBottomY: function() {
|
227 | var options = this.options;
|
228 | var spacing = options.spacing_between_lines_px;
|
229 | var score_bottom = this.getYForLine(options.num_lines) +
|
230 | (options.space_below_staff_ln * spacing);
|
231 |
|
232 | return score_bottom;
|
233 | },
|
234 |
|
235 | getBottomLineY: function() {
|
236 | return this.getYForLine(this.options.num_lines);
|
237 | },
|
238 |
|
239 | getYForLine: function(line) {
|
240 | var options = this.options;
|
241 | var spacing = options.spacing_between_lines_px;
|
242 | var headroom = options.space_above_staff_ln;
|
243 |
|
244 | var y = this.y + ((line * spacing) + (headroom * spacing)) -
|
245 | (THICKNESS / 2);
|
246 |
|
247 | return y;
|
248 | },
|
249 |
|
250 | getYForTopText: function(line) {
|
251 | var l = line || 0;
|
252 | return this.getYForLine(-l - this.options.top_text_position);
|
253 | },
|
254 |
|
255 | getYForBottomText: function(line) {
|
256 | var l = line || 0;
|
257 | return this.getYForLine(this.options.bottom_text_position + l);
|
258 | },
|
259 |
|
260 | getYForNote: function(line) {
|
261 | var options = this.options;
|
262 | var spacing = options.spacing_between_lines_px;
|
263 | var headroom = options.space_above_staff_ln;
|
264 | var y = this.y + (headroom * spacing) + (5 * spacing) - (line * spacing);
|
265 |
|
266 | return y;
|
267 | },
|
268 |
|
269 | getYForGlyphs: function() {
|
270 | return this.getYForLine(3);
|
271 | },
|
272 |
|
273 | addGlyph: function(glyph) {
|
274 | glyph.setStave(this);
|
275 | this.glyphs.push(glyph);
|
276 | this.start_x += glyph.getMetrics().width;
|
277 | return this;
|
278 | },
|
279 |
|
280 | addEndGlyph: function(glyph) {
|
281 | glyph.setStave(this);
|
282 | this.end_glyphs.push(glyph);
|
283 | this.end_x -= glyph.getMetrics().width;
|
284 | return this;
|
285 | },
|
286 |
|
287 | addModifier: function(modifier) {
|
288 | this.modifiers.push(modifier);
|
289 | modifier.addToStave(this, (this.glyphs.length === 0));
|
290 | return this;
|
291 | },
|
292 |
|
293 | addEndModifier: function(modifier) {
|
294 | this.modifiers.push(modifier);
|
295 | modifier.addToStaveEnd(this, (this.end_glyphs.length === 0));
|
296 | return this;
|
297 | },
|
298 |
|
299 | addKeySignature: function(keySpec) {
|
300 | this.addModifier(new Vex.Flow.KeySignature(keySpec));
|
301 | return this;
|
302 | },
|
303 |
|
304 | addClef: function(clef, size, annotation) {
|
305 | this.clef = clef;
|
306 | this.addModifier(new Vex.Flow.Clef(clef, size, annotation));
|
307 | return this;
|
308 | },
|
309 |
|
310 | addEndClef: function(clef, size, annotation) {
|
311 | this.addEndModifier(new Vex.Flow.Clef(clef, size, annotation));
|
312 | return this;
|
313 | },
|
314 |
|
315 | addTimeSignature: function(timeSpec, customPadding) {
|
316 | this.addModifier(new Vex.Flow.TimeSignature(timeSpec, customPadding));
|
317 | return this;
|
318 | },
|
319 |
|
320 | addEndTimeSignature: function(timeSpec, customPadding) {
|
321 | this.addEndModifier(new Vex.Flow.TimeSignature(timeSpec, customPadding));
|
322 | },
|
323 |
|
324 | addTrebleGlyph: function() {
|
325 | this.clef = "treble";
|
326 | this.addGlyph(new Vex.Flow.Glyph("v83", 40));
|
327 | return this;
|
328 | },
|
329 |
|
330 | |
331 |
|
332 |
|
333 | draw: function() {
|
334 | if (!this.context) throw new Vex.RERR("NoCanvasContext",
|
335 | "Can't draw stave without canvas context.");
|
336 |
|
337 | var num_lines = this.options.num_lines;
|
338 | var width = this.width;
|
339 | var x = this.x;
|
340 | var y;
|
341 | var glyph;
|
342 |
|
343 |
|
344 | for (var line=0; line < num_lines; line++) {
|
345 | y = this.getYForLine(line);
|
346 |
|
347 | this.context.save();
|
348 | this.context.setFillStyle(this.options.fill_style);
|
349 | this.context.setStrokeStyle(this.options.fill_style);
|
350 | if (this.options.line_config[line].visible) {
|
351 | this.context.fillRect(x, y, width, Vex.Flow.STAVE_LINE_THICKNESS);
|
352 | }
|
353 | this.context.restore();
|
354 | }
|
355 |
|
356 |
|
357 | x = this.glyph_start_x;
|
358 | for (var i = 0; i < this.glyphs.length; ++i) {
|
359 | glyph = this.glyphs[i];
|
360 | if (!glyph.getContext()) {
|
361 | glyph.setContext(this.context);
|
362 | }
|
363 | glyph.renderToStave(x);
|
364 | x += glyph.getMetrics().width;
|
365 | }
|
366 |
|
367 |
|
368 | x = this.glyph_end_x;
|
369 | for (i = 0; i < this.end_glyphs.length; ++i) {
|
370 | glyph = this.end_glyphs[i];
|
371 | if (!glyph.getContext()) {
|
372 | glyph.setContext(this.context);
|
373 | }
|
374 | x -= glyph.getMetrics().width;
|
375 | glyph.renderToStave(x);
|
376 | }
|
377 |
|
378 |
|
379 | for (i = 0; i < this.modifiers.length; i++) {
|
380 |
|
381 | if (typeof this.modifiers[i].draw == "function")
|
382 | this.modifiers[i].draw(this, this.getModifierXShift());
|
383 | }
|
384 |
|
385 |
|
386 | if (this.measure > 0) {
|
387 | this.context.save();
|
388 | this.context.setFont(this.font.family, this.font.size, this.font.weight);
|
389 | var text_width = this.context.measureText("" + this.measure).width;
|
390 | y = this.getYForTopText(0) + 3;
|
391 | this.context.fillText("" + this.measure, this.x - text_width / 2, y);
|
392 | this.context.restore();
|
393 | }
|
394 |
|
395 | return this;
|
396 | },
|
397 |
|
398 |
|
399 |
|
400 | drawVertical: function(x, isDouble) {
|
401 | this.drawVerticalFixed(this.x + x, isDouble);
|
402 | },
|
403 |
|
404 | drawVerticalFixed: function(x, isDouble) {
|
405 | if (!this.context) throw new Vex.RERR("NoCanvasContext",
|
406 | "Can't draw stave without canvas context.");
|
407 |
|
408 | var top_line = this.getYForLine(0);
|
409 | var bottom_line = this.getYForLine(this.options.num_lines - 1);
|
410 | if (isDouble)
|
411 | this.context.fillRect(x - 3, top_line, 1, bottom_line - top_line + 1);
|
412 | this.context.fillRect(x, top_line, 1, bottom_line - top_line + 1);
|
413 | },
|
414 |
|
415 | drawVerticalBar: function(x) {
|
416 | this.drawVerticalBarFixed(this.x + x, false);
|
417 | },
|
418 |
|
419 | drawVerticalBarFixed: function(x) {
|
420 | if (!this.context) throw new Vex.RERR("NoCanvasContext",
|
421 | "Can't draw stave without canvas context.");
|
422 |
|
423 | var top_line = this.getYForLine(0);
|
424 | var bottom_line = this.getYForLine(this.options.num_lines - 1);
|
425 | this.context.fillRect(x, top_line, 1, bottom_line - top_line + 1);
|
426 | },
|
427 |
|
428 | |
429 |
|
430 |
|
431 |
|
432 | getConfigForLines: function() {
|
433 | return this.options.line_config;
|
434 | },
|
435 |
|
436 | |
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 | setConfigForLine: function(line_number, line_config) {
|
444 | if (line_number >= this.options.num_lines || line_number < 0) {
|
445 | throw new Vex.RERR("StaveConfigError",
|
446 | "The line number must be within the range of the number of lines in the Stave.");
|
447 | }
|
448 | if (!line_config.hasOwnProperty('visible')) {
|
449 | throw new Vex.RERR("StaveConfigError",
|
450 | "The line configuration object is missing the 'visible' property.");
|
451 | }
|
452 | if (typeof(line_config.visible) !== 'boolean') {
|
453 | throw new Vex.RERR("StaveConfigError",
|
454 | "The line configuration objects 'visible' property must be true or false.");
|
455 | }
|
456 |
|
457 | this.options.line_config[line_number] = line_config;
|
458 |
|
459 | return this;
|
460 | },
|
461 |
|
462 | |
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | setConfigForLines: function(lines_configuration) {
|
472 | if (lines_configuration.length !== this.options.num_lines) {
|
473 | throw new Vex.RERR("StaveConfigError",
|
474 | "The length of the lines configuration array must match the number of lines in the Stave");
|
475 | }
|
476 |
|
477 |
|
478 |
|
479 | for (var line_config in lines_configuration) {
|
480 |
|
481 | if (!lines_configuration[line_config]) {
|
482 | lines_configuration[line_config] = this.options.line_config[line_config];
|
483 | }
|
484 | Vex.Merge(this.options.line_config[line_config], lines_configuration[line_config]);
|
485 | }
|
486 |
|
487 | this.options.line_config = lines_configuration;
|
488 |
|
489 | return this;
|
490 | }
|
491 | };
|
492 |
|
493 | return Stave;
|
494 | }());
|