UNPKG

30.1 kBJavaScriptView Raw
1// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
2//
3// ## Description
4//
5// This file implements notes for standard notation. This consists of one or
6// more `NoteHeads`, an optional stem, and an optional flag.
7//
8// *Throughout these comments, a "note" refers to the entire `StaveNote`,
9// and a "key" refers to a specific pitch/notehead within a note.*
10//
11// See `tests/stavenote_tests.js` for usage examples.
12Vex.Flow.StaveNote = (function() {
13 var StaveNote = function(note_struct) {
14 if (arguments.length > 0) this.init(note_struct);
15 };
16 StaveNote.CATEGORY = "stavenotes";
17
18 // To enable logging for this class. Set `Vex.Flow.StaveNote.DEBUG` to `true`.
19 function L() { if (StaveNote.DEBUG) Vex.L("Vex.Flow.StaveNote", arguments); }
20
21 var Stem = Vex.Flow.Stem;
22 var NoteHead = Vex.Flow.NoteHead;
23
24 // Stem directions
25 StaveNote.STEM_UP = Stem.UP;
26 StaveNote.STEM_DOWN = Stem.DOWN;
27
28 // Helper methods for rest positioning in ModifierContext.
29 var shiftRestVertical = function(rest, note, dir) {
30 var delta = (note.isrest ? 0.0 : 1.0) * dir;
31
32 rest.line += delta;
33 rest.max_line += delta;
34 rest.min_line += delta;
35 rest.note.setKeyLine(0, rest.note.getKeyLine(0) + (delta));
36 };
37
38 // Called from formatNotes :: center a rest between two notes
39 var centerRest = function(rest, noteU, noteL) {
40 var delta = rest.line - Vex.MidLine(noteU.min_line, noteL.max_line);
41 rest.note.setKeyLine(0, rest.note.getKeyLine(0) - delta);
42 rest.line -= delta;
43 rest.max_line -= delta;
44 rest.min_line -= delta;
45 };
46
47 // ## Static Methods
48 //
49 // Format notes inside a ModifierContext.
50 StaveNote.format = function(notes, state) {
51 if (!notes || notes.length < 2) return false;
52
53 if (notes[0].getStave() != null) return StaveNote.formatByY(notes, state);
54
55 var notes_list= [];
56
57 for (var i = 0; i < notes.length; i++) {
58 var props = notes[i].getKeyProps();
59 var line = props[0].line;
60 var minL = props[props.length -1].line;
61 var stem_dir = notes[i].getStemDirection();
62 var stem_max = notes[i].getStemLength() / 10;
63 var stem_min = notes[i].getStemMinumumLength() / 10;
64
65 var maxL;
66 if (notes[i].isRest()) {
67 maxL = line + notes[i].glyph.line_above;
68 minL = line - notes[i].glyph.line_below;
69 } else {
70 maxL = stem_dir == 1 ? props[props.length -1].line + stem_max
71 : props[props.length -1].line;
72 minL = stem_dir == 1 ? props[0].line
73 : props[0].line - stem_max;
74 }
75 notes_list.push(
76 {line: props[0].line, // note/rest base line
77 max_line: maxL, // note/rest upper bounds line
78 min_line: minL, // note/rest lower bounds line
79 isrest: notes[i].isRest(),
80 stem_dir: stem_dir,
81 stem_max: stem_max, // Maximum (default) note stem length;
82 stem_min: stem_min, // minimum note stem length
83 voice_shift: notes[i].getVoiceShiftWidth(),
84 is_displaced: notes[i].isDisplaced(), // note manually displaced
85 note: notes[i]});
86 }
87
88 var voices = notes_list.length;
89
90 var noteU = notes_list[0];
91 var noteM = voices > 2 ? notes_list[1] : null;
92 var noteL = voices > 2 ? notes_list[2] : notes_list[1];
93
94 // for two voice backward compatibility, ensure upper voice is stems up
95 // for three voices, the voices must be in order (upper, middle, lower)
96 if (voices == 2 && noteU.stem_dir == -1 && noteL.stem_dir == 1) {
97 noteU = notes_list[1];
98 noteL = notes_list[0];
99 }
100
101 var voice_x_shift = Math.max(noteU.voice_shift, noteL.voice_shift);
102 var x_shift = 0;
103 var stem_delta;
104
105 // Test for two voice note intersection
106 if (voices == 2) {
107 var line_spacing = noteU.stem_dir == noteL.stem_dir ? 0.0 : 0.5;
108 // if top voice is a middle voice, check stem intersection with lower voice
109 if (noteU.stem_dir == noteL.stem_dir &&
110 noteU.min_line <= noteL.max_line) {
111 if (!noteU.isrest) {
112 stem_delta = Math.abs(noteU.line - (noteL.max_line + 0.5));
113 stem_delta = Math.max(stem_delta, noteU.stem_min);
114 noteU.min_line = noteU.line - stem_delta;
115 noteU.note.setStemLength(stem_delta * 10);
116 }
117 }
118 if (noteU.min_line <= noteL.max_line + line_spacing) {
119 if (noteU.isrest) {
120 // shift rest up
121 shiftRestVertical(noteU, noteL, 1);
122 } else if (noteL.isrest) {
123 // shift rest down
124 shiftRestVertical(noteL, noteU, -1);
125 } else {
126 x_shift = voice_x_shift;
127 if (noteU.stem_dir == noteL.stem_dir)
128 // upper voice is middle voice, so shift it right
129 noteU.note.setXShift(x_shift + 3);
130 else
131 // shift lower voice right
132 noteL.note.setXShift(x_shift);
133 }
134 }
135
136 // format complete
137 return true;
138 }
139
140 // Check middle voice stem intersection with lower voice
141 if (noteM != null && noteM.min_line < noteL.max_line + 0.5) {
142 if (!noteM.isrest) {
143 stem_delta = Math.abs(noteM.line - (noteL.max_line + 0.5));
144 stem_delta = Math.max(stem_delta, noteM.stem_min);
145 noteM.min_line = noteM.line - stem_delta;
146 noteM.note.setStemLength(stem_delta * 10);
147 }
148 }
149
150 // For three voices, test if rests can be repositioned
151 //
152 // Special case 1 :: middle voice rest between two notes
153 //
154 if (noteM.isrest && !noteU.isrest && !noteL.isrest) {
155 if (noteU.min_line <= noteM.max_line ||
156 noteM.min_line <= noteL.max_line) {
157 var rest_height = noteM.max_line - noteM.min_line;
158 var space = noteU.min_line - noteL.max_line;
159 if (rest_height < space)
160 // center middle voice rest between the upper and lower voices
161 centerRest(noteM, noteU, noteL);
162 else {
163 x_shift = voice_x_shift + 3; // shift middle rest right
164 noteM.note.setXShift(x_shift);
165 }
166 // format complete
167 return true;
168 }
169 }
170
171 // Special case 2 :: all voices are rests
172 if (noteU.isrest && noteM.isrest && noteL.isrest) {
173 // Shift upper voice rest up
174 shiftRestVertical(noteU, noteM, 1);
175 // Shift lower voice rest down
176 shiftRestVertical(noteL, noteM, -1);
177 // format complete
178 return true;
179 }
180
181 // Test if any other rests can be repositioned
182 if (noteM.isrest && noteU.isrest && noteM.min_line <= noteL.max_line)
183 // Shift middle voice rest up
184 shiftRestVertical(noteM, noteL, 1);
185 if (noteM.isrest && noteL.isrest && noteU.min_line <= noteM.max_line)
186 // Shift middle voice rest down
187 shiftRestVertical(noteM, noteU, -1);
188 if (noteU.isrest && noteU.min_line <= noteM.max_line)
189 // shift upper voice rest up;
190 shiftRestVertical(noteU, noteM, 1);
191 if (noteL.isrest && noteM.min_line <= noteL.max_line)
192 // shift lower voice rest down
193 shiftRestVertical(noteL, noteM, -1);
194
195 // If middle voice intersects upper or lower voice
196 if ((!noteU.isrest && !noteM.isrest && noteU.min_line <= noteM.max_line + 0.5) ||
197 (!noteM.isrest && !noteL.isrest && noteM.min_line <= noteL.max_line)) {
198 x_shift = voice_x_shift + 3; // shift middle note right
199 noteM.note.setXShift(x_shift);
200 }
201
202 return true;
203 };
204
205 StaveNote.formatByY = function(notes, state) {
206 // NOTE: this function does not support more than two voices per stave
207 // use with care.
208 var hasStave = true;
209 var i;
210
211 for (i = 0; i < notes.length; i++) {
212 hasStave = hasStave && notes[i].getStave() != null;
213 }
214
215 if (!hasStave) throw new Vex.RERR("Stave Missing",
216 "All notes must have a stave - Vex.Flow.ModifierContext.formatMultiVoice!");
217
218 var x_shift = 0;
219
220 for (i = 0; i < notes.length - 1; i++) {
221 var top_note = notes[i];
222 var bottom_note = notes[i + 1];
223
224 if (top_note.getStemDirection() == Vex.Flow.StaveNote.STEM_DOWN) {
225 top_note = notes[i + 1];
226 bottom_note = notes[i];
227 }
228
229 var top_keys = top_note.getKeyProps();
230 var bottom_keys = bottom_note.getKeyProps();
231
232 var topY = top_note.getStave().getYForLine(top_keys[0].line);
233 var bottomY = bottom_note.getStave().getYForLine(bottom_keys[bottom_keys.length - 1].line);
234
235 var line_space = top_note.getStave().options.spacing_between_lines_px;
236 if (Math.abs(topY - bottomY) == line_space / 2) {
237 x_shift = top_note.getVoiceShiftWidth();
238 bottom_note.setXShift(x_shift);
239 }
240 }
241
242 state.right_shift += x_shift;
243 };
244
245 StaveNote.postFormat = function(notes) {
246 if (!notes) return false;
247
248 notes.forEach(function(note) {
249 note.postFormat();
250 });
251
252 return true;
253 };
254
255 // ## Prototype Methods
256 //
257 Vex.Inherit(StaveNote, Vex.Flow.StemmableNote, {
258 init: function(note_struct) {
259 StaveNote.superclass.init.call(this, note_struct);
260
261 this.keys = note_struct.keys;
262 this.clef = note_struct.clef;
263 this.octave_shift = note_struct.octave_shift;
264 this.beam = null;
265
266 // Pull note rendering properties
267 this.glyph = Vex.Flow.durationToGlyph(this.duration, this.noteType);
268 if (!this.glyph) {
269 throw new Vex.RuntimeError("BadArguments",
270 "Invalid note initialization data (No glyph found): " +
271 JSON.stringify(note_struct));
272 }
273
274 // if true, displace note to right
275 this.displaced = false;
276 this.dot_shiftY = 0;
277 // per-pitch properties
278 this.keyProps = [];
279 // for displaced ledger lines
280 this.use_default_head_x = false;
281
282 // Drawing
283 this.note_heads = [];
284 this.modifiers = [];
285
286 Vex.Merge(this.render_options, {
287 // font size for note heads and rests
288 glyph_font_scale: 35,
289 // number of stroke px to the left and right of head
290 stroke_px: 3
291 });
292
293 this.calculateKeyProps();
294
295 this.buildStem();
296
297 // Set the stem direction
298 if (note_struct.auto_stem) {
299 this.autoStem();
300 } else {
301 this.setStemDirection(note_struct.stem_direction);
302 }
303
304 this.buildNoteHeads();
305
306 // Calculate left/right padding
307 this.calcExtraPx();
308 },
309
310 // Builds a `Stem` for the note
311 buildStem: function() {
312 var glyph = this.getGlyph();
313
314 var y_extend = 0;
315 if (glyph.code_head == "v95" || glyph.code_head == "v3e") {
316 y_extend = -4;
317 }
318
319 var stem = new Stem({
320 y_extend: y_extend
321 });
322
323 if (this.isRest()) {
324 stem.hide = true;
325 }
326
327 this.setStem(stem);
328 },
329
330 // Builds a `NoteHead` for each key in the note
331 buildNoteHeads: function() {
332 var stem_direction = this.getStemDirection();
333
334 var keys = this.getKeys();
335
336 var last_line = null;
337 var line_diff = null;
338 var displaced = false;
339
340 // Draw notes from bottom to top.
341 var start_i = 0;
342 var end_i = keys.length;
343 var step_i = 1;
344
345 // For down-stem notes, we draw from top to bottom.
346 if (stem_direction === Stem.DOWN) {
347 start_i = keys.length - 1;
348 end_i = -1;
349 step_i = -1;
350 }
351
352 for (var i = start_i; i != end_i; i += step_i) {
353 var note_props = this.keyProps[i];
354
355 var line = note_props.line;
356
357 // Keep track of last line with a note head, so that consecutive heads
358 // are correctly displaced.
359 if (last_line === null) {
360 last_line = line;
361 } else {
362 line_diff = Math.abs(last_line - line);
363 if (line_diff === 0 || line_diff === 0.5) {
364 displaced = !displaced;
365 } else {
366 displaced = false;
367 this.use_default_head_x = true;
368 }
369 }
370 last_line = line;
371
372 var note_head = new NoteHead({
373 duration: this.duration,
374 note_type: this.noteType,
375 displaced: displaced,
376 stem_direction: stem_direction,
377 custom_glyph_code: note_props.code,
378 glyph_font_scale: this.render_options.glyph_font_scale,
379 x_shift: note_props.shift_right,
380 line: note_props.line
381 });
382
383 this.note_heads[i] = note_head;
384 }
385 },
386
387 // Automatically sets the stem direction based on the keys in the note
388 autoStem: function() {
389 var auto_stem_direction;
390
391 // Figure out optimal stem direction based on given notes
392 this.min_line = this.keyProps[0].line;
393 this.max_line = this.keyProps[this.keyProps.length - 1].line;
394 var decider = (this.min_line + this.max_line) / 2;
395
396 if (decider < 3) {
397 auto_stem_direction = 1;
398 } else {
399 auto_stem_direction = -1;
400 }
401
402 this.setStemDirection(auto_stem_direction);
403 },
404
405 // Calculates and stores the properties for each key in the note
406 calculateKeyProps: function() {
407 var last_line = null;
408 for (var i = 0; i < this.keys.length; ++i) {
409 var key = this.keys[i];
410
411 // All rests use the same position on the line.
412 // if (this.glyph.rest) key = this.glyph.position;
413 if (this.glyph.rest) this.glyph.position = key;
414 var options = { octave_shift: this.octave_shift || 0 };
415 var props = Vex.Flow.keyProperties(key, this.clef, options);
416 if (!props) {
417 throw new Vex.RuntimeError("BadArguments",
418 "Invalid key for note properties: " + key);
419 }
420
421 // Override line placement for default rests
422 if (props.key === "R") {
423 if (this.duration === "1" || this.duration === "w") {
424 props.line = 4;
425 } else {
426 props.line = 3;
427 }
428 }
429
430 // Calculate displacement of this note
431 var line = props.line;
432 if (last_line === null) {
433 last_line = line;
434 } else {
435 if (Math.abs(last_line - line) == 0.5) {
436 this.displaced = true;
437 props.displaced = true;
438
439 // Have to mark the previous note as
440 // displaced as well, for modifier placement
441 if (this.keyProps.length > 0) {
442 this.keyProps[i-1].displaced = true;
443 }
444 }
445 }
446
447 last_line = line;
448 this.keyProps.push(props);
449 }
450
451 // Sort the notes from lowest line to highest line
452 this.keyProps.sort(function(a, b) { return a.line - b.line; });
453 },
454
455 // Get the `BoundingBox` for the entire note
456 getBoundingBox: function() {
457 if (!this.preFormatted) throw new Vex.RERR("UnformattedNote",
458 "Can't call getBoundingBox on an unformatted note.");
459
460 var metrics = this.getMetrics();
461
462 var w = metrics.width;
463 var x = this.getAbsoluteX() - metrics.modLeftPx - metrics.extraLeftPx;
464
465 var min_y = 0;
466 var max_y = 0;
467 var half_line_spacing = this.getStave().getSpacingBetweenLines() / 2;
468 var line_spacing = half_line_spacing * 2;
469
470 if (this.isRest()) {
471 var y = this.ys[0];
472 var frac = Vex.Flow.durationToFraction(this.duration);
473 if (frac.equals(1) || frac.equals(2)) {
474 min_y = y - half_line_spacing;
475 max_y = y + half_line_spacing;
476 } else {
477 min_y = y - (this.glyph.line_above * line_spacing);
478 max_y = y + (this.glyph.line_below * line_spacing);
479 }
480 } else if (this.glyph.stem) {
481 var ys = this.getStemExtents();
482 ys.baseY += half_line_spacing * this.stem_direction;
483 min_y = Vex.Min(ys.topY, ys.baseY);
484 max_y = Vex.Max(ys.topY, ys.baseY);
485 } else {
486 min_y = null;
487 max_y = null;
488
489 for (var i=0; i < this.ys.length; ++i) {
490 var yy = this.ys[i];
491 if (i === 0) {
492 min_y = yy;
493 max_y = yy;
494 } else {
495 min_y = Vex.Min(yy, min_y);
496 max_y = Vex.Max(yy, max_y);
497 }
498 min_y -= half_line_spacing;
499 max_y += half_line_spacing;
500 }
501 }
502
503 return new Vex.Flow.BoundingBox(x, min_y, w, max_y - min_y);
504 },
505
506 // Gets the line number of the top or bottom note in the chord.
507 // If `is_top_note` is `true` then get the top note
508 getLineNumber: function(is_top_note) {
509 if(!this.keyProps.length) throw new Vex.RERR("NoKeyProps",
510 "Can't get bottom note line, because note is not initialized properly.");
511 var result_line = this.keyProps[0].line;
512
513 // No precondition assumed for sortedness of keyProps array
514 for(var i=0; i<this.keyProps.length; i++){
515 var this_line = this.keyProps[i].line;
516 if(is_top_note)
517 if(this_line > result_line)
518 result_line = this_line;
519 else
520 if(this_line < result_line)
521 result_line = this_line;
522 }
523
524 return result_line;
525 },
526
527 // Determine if current note is a rest
528 isRest: function() { return this.glyph.rest; },
529
530 // Determine if the current note is a chord
531 isChord: function() { return !this.isRest() && this.keys.length > 1; },
532
533 // Determine if the `StaveNote` has a stem
534 hasStem: function() { return this.glyph.stem; },
535
536 // Get the `y` coordinate for text placed on the top/bottom of a
537 // note at a desired `text_line`
538 getYForTopText: function(text_line) {
539 var extents = this.getStemExtents();
540 return Vex.Min(this.stave.getYForTopText(text_line),
541 extents.topY - (this.render_options.annotation_spacing * (text_line + 1)));
542 },
543 getYForBottomText: function(text_line) {
544 var extents = this.getStemExtents();
545 return Vex.Max(this.stave.getYForTopText(text_line),
546 extents.baseY + (this.render_options.annotation_spacing * (text_line)));
547 },
548
549 // Sets the current note to the provided `stave`. This applies
550 // `y` values to the `NoteHeads`.
551 setStave: function(stave) {
552 var superclass = Vex.Flow.StaveNote.superclass;
553 superclass.setStave.call(this, stave);
554
555 var ys = this.note_heads.map(function(note_head) {
556 note_head.setStave(stave);
557 return note_head.getY();
558 });
559
560 this.setYs(ys);
561
562 var bounds = this.getNoteHeadBounds();
563 if(!this.beam){
564 this.stem.setYBounds(bounds.y_top, bounds.y_bottom);
565 }
566
567 return this;
568 },
569
570 // Get the pitches in the note
571 getKeys: function() { return this.keys; },
572
573 // Get the properties for all the keys in the note
574 getKeyProps: function() {
575 return this.keyProps;
576 },
577
578 // Check if note is shifted to the right
579 isDisplaced: function() {
580 return this.displaced;
581 },
582
583 // Sets whether shift note to the right. `displaced` is a `boolean`
584 setNoteDisplaced: function(displaced) {
585 this.displaced = displaced;
586 return this;
587 },
588
589 // Get the starting `x` coordinate for a `StaveTie`
590 getTieRightX: function() {
591 var tieStartX = this.getAbsoluteX();
592 tieStartX += this.glyph.head_width + this.x_shift + this.extraRightPx;
593 if (this.modifierContext) tieStartX += this.modifierContext.getExtraRightPx();
594 return tieStartX;
595 },
596
597 // Get the ending `x` coordinate for a `StaveTie`
598 getTieLeftX: function() {
599 var tieEndX = this.getAbsoluteX();
600 tieEndX += this.x_shift - this.extraLeftPx;
601 return tieEndX;
602 },
603
604 // Get the stave line on which to place a rest
605 getLineForRest: function() {
606 var rest_line = this.keyProps[0].line;
607 if (this.keyProps.length > 1) {
608 var last_line = this.keyProps[this.keyProps.length - 1].line;
609 var top = Vex.Max(rest_line, last_line);
610 var bot = Vex.Min(rest_line, last_line);
611 rest_line = Vex.MidLine(top, bot);
612 }
613
614 return rest_line;
615 },
616
617 // Get the default `x` and `y` coordinates for the provided `position`
618 // and key `index`
619 getModifierStartXY: function(position, index) {
620 if (!this.preFormatted) throw new Vex.RERR("UnformattedNote",
621 "Can't call GetModifierStartXY on an unformatted note");
622
623 if (this.ys.length === 0) throw new Vex.RERR("NoYValues",
624 "No Y-Values calculated for this note.");
625
626 var x = 0;
627 if (position == Vex.Flow.Modifier.Position.LEFT) {
628 // extra_left_px
629 x = -1 * 2;
630 } else if (position == Vex.Flow.Modifier.Position.RIGHT) {
631 // extra_right_px
632 x = this.glyph.head_width + this.x_shift + 2;
633 } else if (position == Vex.Flow.Modifier.Position.BELOW ||
634 position == Vex.Flow.Modifier.Position.ABOVE) {
635 x = this.glyph.head_width / 2;
636 }
637
638 return { x: this.getAbsoluteX() + x, y: this.ys[index] };
639 },
640
641 // Sets the notehead at `index` to the provided coloring `style`.
642 //
643 // `style` is an `object` with the following properties: `shadowColor`,
644 // `shadowBlur`, `fillStyle`, `strokeStyle`
645 setKeyStyle: function(index, style) {
646 this.note_heads[index].setStyle(style);
647 return this;
648 },
649
650 setKeyLine: function(index, line) {
651 this.keyProps[index].line = line;
652 this.note_heads[index].setLine(line);
653 return this;
654 },
655
656 getKeyLine: function(index) {
657 return this.keyProps[index].line;
658 },
659
660 // Add self to modifier context. `mContext` is the `ModifierContext`
661 // to be added to.
662 addToModifierContext: function(mContext) {
663 this.setModifierContext(mContext);
664 for (var i = 0; i < this.modifiers.length; ++i) {
665 this.modifierContext.addModifier(this.modifiers[i]);
666 }
667 this.modifierContext.addModifier(this);
668 this.setPreFormatted(false);
669 return this;
670 },
671
672 // Generic function to add modifiers to a note
673 //
674 // Parameters:
675 // * `index`: The index of the key that we're modifying
676 // * `modifier`: The modifier to add
677 addModifier: function(index, modifier) {
678 modifier.setNote(this);
679 modifier.setIndex(index);
680 this.modifiers.push(modifier);
681 this.setPreFormatted(false);
682 return this;
683 },
684
685 // Helper function to add an accidental to a key
686 addAccidental: function(index, accidental) {
687 return this.addModifier(index, accidental);
688 },
689
690 // Helper function to add an articulation to a key
691 addArticulation: function(index, articulation) {
692 return this.addModifier(index, articulation);
693 },
694
695 // Helper function to add an annotation to a key
696 addAnnotation: function(index, annotation) {
697 return this.addModifier(index, annotation);
698 },
699
700 // Helper function to add a dot on a specific key
701 addDot: function(index) {
702 var dot = new Vex.Flow.Dot();
703 dot.setDotShiftY(this.glyph.dot_shiftY);
704 this.dots++;
705 return this.addModifier(index, dot);
706 },
707
708 // Convenience method to add dot to all keys in note
709 addDotToAll: function() {
710 for (var i = 0; i < this.keys.length; ++i)
711 this.addDot(i);
712 return this;
713 },
714
715 // Get all accidentals in the `ModifierContext`
716 getAccidentals: function() {
717 return this.modifierContext.getModifiers("accidentals");
718 },
719
720 // Get all dots in the `ModifierContext`
721 getDots: function() {
722 return this.modifierContext.getModifiers("dots");
723 },
724
725 // Get the width of the note if it is displaced. Used for `Voice`
726 // formatting
727 getVoiceShiftWidth: function() {
728 // TODO: may need to accomodate for dot here.
729 return this.glyph.head_width * (this.displaced ? 2 : 1);
730 },
731
732 // Calculates and sets the extra pixels to the left or right
733 // if the note is displaced
734 calcExtraPx: function() {
735 this.setExtraLeftPx((this.displaced && this.stem_direction == -1) ?
736 this.glyph.head_width : 0);
737 this.setExtraRightPx((this.displaced && this.stem_direction == 1) ?
738 this.glyph.head_width : 0);
739 },
740
741 // Pre-render formatting
742 preFormat: function() {
743 if (this.preFormatted) return;
744 if (this.modifierContext) this.modifierContext.preFormat();
745
746 var width = this.glyph.head_width + this.extraLeftPx + this.extraRightPx;
747
748 // For upward flagged notes, the width of the flag needs to be added
749 if (this.glyph.flag && this.beam === null && this.stem_direction == 1) {
750 width += this.glyph.head_width;
751 }
752
753 this.setWidth(width);
754 this.setPreFormatted(true);
755 },
756
757 // Gets the staff line and y value for the highest and lowest noteheads
758 getNoteHeadBounds: function() {
759 // Top and bottom Y values for stem.
760 var y_top = null;
761 var y_bottom = null;
762
763 var highest_line = this.stave.getNumLines();
764 var lowest_line = 1;
765
766 this.note_heads.forEach(function(note_head) {
767 var line = note_head.getLine();
768 var y = note_head.getY();
769
770 if (y_top === null || y < y_top) {
771 y_top = y;
772 }
773
774 if (y_bottom === null || y > y_bottom) {
775 y_bottom = y;
776 }
777
778 highest_line = line > highest_line ? line : highest_line;
779 lowest_line = line < lowest_line ? line : lowest_line;
780
781 }, this);
782
783 return {
784 y_top: y_top,
785 y_bottom: y_bottom,
786 highest_line: highest_line,
787 lowest_line: lowest_line
788 };
789 },
790
791 // Get the starting `x` coordinate for the noteheads
792 getNoteHeadBeginX: function(){
793 return this.getAbsoluteX() + this.x_shift;
794 },
795
796 // Get the ending `x` coordinate for the noteheads
797 getNoteHeadEndX: function(){
798 var x_begin = this.getNoteHeadBeginX();
799 return x_begin + this.glyph.head_width - (Vex.Flow.STEM_WIDTH / 2);
800 },
801
802 // Draw the ledger lines between the stave and the highest/lowest keys
803 drawLedgerLines: function(){
804 if (this.isRest()) { return; }
805 if (!this.context) throw new Vex.RERR("NoCanvasContext",
806 "Can't draw without a canvas context.");
807 var ctx = this.context;
808
809 var bounds = this.getNoteHeadBounds();
810 var highest_line = bounds.highest_line;
811 var lowest_line = bounds.lowest_line;
812 var head_x = this.note_heads[0].getAbsoluteX();
813
814 var that = this;
815 function stroke(y) {
816 if (that.use_default_head_x === true) {
817 head_x = that.getAbsoluteX() + that.x_shift;
818 }
819 var x = head_x - that.render_options.stroke_px;
820 var length = ((head_x + that.glyph.head_width) - head_x) +
821 (that.render_options.stroke_px * 2);
822
823 ctx.fillRect(x, y, length, 1);
824 }
825
826 var line; // iterator
827 for (line = 6; line <= highest_line; ++line) {
828 stroke(this.stave.getYForNote(line));
829 }
830
831 for (line = 0; line >= lowest_line; --line) {
832 stroke(this.stave.getYForNote(line));
833 }
834 },
835
836 // Draw all key modifiers
837 drawModifiers: function(){
838 if (!this.context) throw new Vex.RERR("NoCanvasContext",
839 "Can't draw without a canvas context.");
840 var ctx = this.context;
841 for (var i = 0; i < this.modifiers.length; i++) {
842 var mod = this.modifiers[i];
843 var note_head = this.note_heads[mod.getIndex()];
844 var key_style = note_head.getStyle();
845 if(key_style) {
846 ctx.save();
847 note_head.applyStyle(ctx);
848 }
849 mod.setContext(ctx);
850 mod.draw();
851 if(key_style) {
852 ctx.restore();
853 }
854 }
855 },
856
857 // Draw the flag for the note
858 drawFlag: function(){
859 if (!this.context) throw new Vex.RERR("NoCanvasContext",
860 "Can't draw without a canvas context.");
861 var ctx = this.context;
862 var glyph = this.getGlyph();
863 var render_flag = this.beam === null;
864 var bounds = this.getNoteHeadBounds();
865
866 var x_begin = this.getNoteHeadBeginX();
867 var x_end = this.getNoteHeadEndX();
868
869 if (glyph.flag && render_flag) {
870 var note_stem_height = this.stem.getHeight();
871 var flag_x, flag_y, flag_code;
872
873 if (this.getStemDirection() === Stem.DOWN) {
874 // Down stems have flags on the left.
875 flag_x = x_begin + 1;
876 flag_y = bounds.y_top - note_stem_height + 2;
877 flag_code = glyph.code_flag_downstem;
878
879 } else {
880 // Up stems have flags on the left.
881 flag_x = x_end + 1;
882 flag_y = bounds.y_bottom - note_stem_height - 2;
883 flag_code = glyph.code_flag_upstem;
884 }
885
886 // Draw the Flag
887 Vex.Flow.renderGlyph(ctx, flag_x, flag_y,
888 this.render_options.glyph_font_scale, flag_code);
889 }
890 },
891
892 // Draw the NoteHeads
893 drawNoteHeads: function(){
894 this.note_heads.forEach(function(note_head) {
895 note_head.setContext(this.context).draw();
896 }, this);
897 },
898
899 // Render the stem onto the canvas
900 drawStem: function(stem_struct){
901 if (!this.context) throw new Vex.RERR("NoCanvasContext",
902 "Can't draw without a canvas context.");
903
904 if (stem_struct) {
905 this.setStem(new Stem(stem_struct));
906 }
907
908 this.stem.setContext(this.context).draw();
909 },
910
911 // Draws all the `StaveNote` parts. This is the main drawing method.
912 draw: function() {
913 if (!this.context) throw new Vex.RERR("NoCanvasContext",
914 "Can't draw without a canvas context.");
915 if (!this.stave) throw new Vex.RERR("NoStave",
916 "Can't draw without a stave.");
917 if (this.ys.length === 0) throw new Vex.RERR("NoYValues",
918 "Can't draw note without Y values.");
919
920 var x_begin = this.getNoteHeadBeginX();
921 var x_end = this.getNoteHeadEndX();
922
923 var render_stem = this.hasStem() && !this.beam;
924
925 // Format note head x positions
926 this.note_heads.forEach(function(note_head) {
927 note_head.setX(x_begin);
928 }, this);
929
930 // Format stem x positions
931 this.stem.setNoteHeadXBounds(x_begin, x_end);
932
933 L("Rendering ", this.isChord() ? "chord :" : "note :", this.keys);
934
935 // Draw each part of the note
936 this.drawLedgerLines();
937 if (render_stem) this.drawStem();
938 this.drawNoteHeads();
939 this.drawFlag();
940 this.drawModifiers();
941 }
942 });
943
944 return StaveNote;
945}());