UNPKG

15.9 kBJavaScriptView Raw
1// Vex Flow
2// Mohit Muthanna <mohit@muthanna.com>
3//
4// Copyright Mohit Cheppudira 2010
5
6/** @constructor */
7Vex.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 = []; // non-glyph stave items (barlines, coda, segno, etc.)
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, // Width around vertical bar end-marker
36 glyph_spacing_px: 10,
37 num_lines: 5,
38 fill_style: "#999999",
39 spacing_between_lines_px: 10, // in pixels
40 space_above_staff_ln: 4, // in staff lines
41 space_below_staff_ln: 4, // in staff lines
42 top_text_position: 1 // in staff lines
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)); // beg bar
51 this.modifiers.push(
52 new Vex.Flow.Barline(Vex.Flow.Barline.type.SINGLE,
53 this.x + this.width)); // end bar
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 // Add additional space if left barline is REPEAT_BEGIN and there are other
71 // start modifiers than barlines
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 // reset the x position of the end barline (TODO(0xfe): This makes no sense)
124 // this.modifiers[1].setX(this.end_x);
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 // Bar Line functions
135 setBegBarType: function(type) {
136 // Only valid bar types at beginning of stave is none, single or begin repeat
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 // Repeat end not valid at end of stave
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 * Gets the pixels to shift from the beginning of the stave
154 * following the modifier at the provided index
155 * @param {Number} index The index from which to determine the shift
156 * @return {Number} The amount of pixels shifted
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 // Add padding after clef, time sig, key sig
173 if (bar_x_shift > 0) bar_x_shift += this.options.vertical_bar_width + 10;
174
175 return bar_x_shift;
176 },
177
178 // Coda & Segno Symbol functions
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 // Volta functions
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 // Section functions
196 setSection: function(section, y) {
197 this.modifiers.push(new Vex.Flow.StaveSection(section, this.x, y));
198 return this;
199 },
200
201 // Tempo functions
202 setTempo: function(tempo, y) {
203 this.modifiers.push(new Vex.Flow.StaveTempo(tempo, this.x, y));
204 return this;
205 },
206
207 // Text functions
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 // body...
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 * All drawing functions below need the context to be set.
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 // Render lines
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 // Render glyphs
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 // Render end glyphs
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 // Draw the modifiers (bar lines, coda, segno, repeat brackets, etc.)
379 for (i = 0; i < this.modifiers.length; i++) {
380 // Only draw modifier if it has a draw function
381 if (typeof this.modifiers[i].draw == "function")
382 this.modifiers[i].draw(this, this.getModifierXShift());
383 }
384
385 // Render measure numbers
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 // Draw Simple barlines for backward compatability
399 // Do not delete - draws the beginning bar of the stave
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 * Get the current configuration for the Stave.
430 * @return {Array} An array of configuration objects.
431 */
432 getConfigForLines: function() {
433 return this.options.line_config;
434 },
435
436 /**
437 * Configure properties of the lines in the Stave
438 * @param line_number The index of the line to configure.
439 * @param line_config An configuration object for the specified line.
440 * @throws Vex.RERR "StaveConfigError" When the specified line number is out of
441 * range of the number of lines specified in the constructor.
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 * Set the staff line configuration array for all of the lines at once.
464 * @param lines_configuration An array of line configuration objects. These objects
465 * are of the same format as the single one passed in to setLineConfiguration().
466 * The caller can set null for any line config entry if it is desired that the default be used
467 * @throws Vex.RERR "StaveConfigError" When the lines_configuration array does not have
468 * exactly the same number of elements as the num_lines configuration object set in
469 * the constructor.
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 // Make sure the defaults are present in case an incomplete set of
478 // configuration options were supplied.
479 for (var line_config in lines_configuration) {
480 // Allow 'null' to be used if the caller just wants the default for a particular node.
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}());