UNPKG

11.4 kBJavaScriptView Raw
1// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
2//
3// ## Description
4//
5// This file implements an abstract interface for notes and chords that
6// are rendered on a stave. Notes have some common properties: All of them
7// have a value (e.g., pitch, fret, etc.) and a duration (quarter, half, etc.)
8//
9// Some notes have stems, heads, dots, etc. Most notational elements that
10// surround a note are called *modifiers*, and every note has an associated
11// array of them. All notes also have a rendering context and belong to a stave.
12
13Vex.Flow.Note = (function() {
14 // To create a new note you need to provide a `note_struct`, which consists
15 // of the following fields:
16 //
17 // `type`: The note type (e.g., `r` for rest, `s` for slash notes, etc.)
18 // `dots`: The number of dots, which affects the duration.
19 // `duration`: The time length (e.g., `q` for quarter, `h` for half, `8` for eighth etc.)
20 //
21 // The range of values for these parameters are available in `src/tables.js`.
22 function Note(note_struct) {
23 if (arguments.length > 0) this.init(note_struct);
24 }
25 Note.CATEGORY = "note";
26
27 // ## Prototype Methods
28 //
29 // Every note is a tickable, i.e., it can be mutated by the `Formatter` class for
30 // positioning and layout.
31 Vex.Inherit(Note, Vex.Flow.Tickable, {
32 // See constructor above for how to create a note.
33 init: function(note_struct) {
34 Note.superclass.init.call(this);
35
36 if (!note_struct) {
37 throw new Vex.RuntimeError("BadArguments",
38 "Note must have valid initialization data to identify " +
39 "duration and type.");
40 }
41
42 // Parse `note_struct` and get note properties.
43 var initData = Vex.Flow.parseNoteData(note_struct);
44 if (!initData) {
45 throw new Vex.RuntimeError("BadArguments",
46 "Invalid note initialization object: " + JSON.stringify(note_struct));
47 }
48
49 // Set note properties from parameters.
50 this.duration = initData.duration;
51 this.dots = initData.dots;
52 this.noteType = initData.type;
53
54 if (note_struct.duration_override) {
55 // Custom duration
56 this.setDuration(note_struct.duration_override);
57 } else {
58 // Default duration
59 this.setIntrinsicTicks(initData.ticks);
60 }
61
62 this.modifiers = [];
63
64 // Get the glyph code for this note from the font.
65 this.glyph = Vex.Flow.durationToGlyph(this.duration, this.noteType);
66
67 if (this.positions &&
68 (typeof(this.positions) != "object" || !this.positions.length)) {
69 throw new Vex.RuntimeError(
70 "BadArguments", "Note keys must be array type.");
71 }
72
73 // Note to play for audio players.
74 this.playNote = null;
75
76 // Positioning contexts used by the Formatter.
77 this.tickContext = null; // The current tick context.
78 this.modifierContext = null;
79 this.ignore_ticks = false;
80
81 // Positioning variables
82 this.width = 0; // Width in pixels calculated after preFormat
83 this.extraLeftPx = 0; // Extra room on left for offset note head
84 this.extraRightPx = 0; // Extra room on right for offset note head
85 this.x_shift = 0; // X shift from tick context X
86 this.left_modPx = 0; // Max width of left modifiers
87 this.right_modPx = 0; // Max width of right modifiers
88 this.voice = null; // The voice that this note is in
89 this.preFormatted = false; // Is this note preFormatted?
90 this.ys = []; // list of y coordinates for each note
91 // we need to hold on to these for ties and beams.
92
93 if (note_struct.align_center) {
94 this.setCenterAlignment(note_struct.align_center);
95 }
96
97 // The render surface.
98 this.context = null;
99 this.stave = null;
100 this.render_options = {
101 annotation_spacing: 5,
102 stave_padding: 12
103 };
104 },
105
106 // Get and set the play note, which is arbitrary data that can be used by an
107 // audio player.
108 getPlayNote: function() { return this.playNote; },
109 setPlayNote: function(note) { this.playNote = note; return this; },
110
111 // Don't play notes by default, call them rests. This is also used by things like
112 // beams and dots for positioning.
113 isRest: function() { return false; },
114
115 // TODO(0xfe): Why is this method here?
116 addStroke: function(index, stroke) {
117 stroke.setNote(this);
118 stroke.setIndex(index);
119 this.modifiers.push(stroke);
120 this.setPreFormatted(false);
121 return this;
122 },
123
124 // Get and set the target stave.
125 getStave: function() { return this.stave; },
126 setStave: function(stave) {
127 this.stave = stave;
128 this.setYs([stave.getYForLine(0)]); // Update Y values if the stave is changed.
129 this.context = this.stave.context;
130 return this;
131 },
132
133
134 // `Note` is not really a modifier, but is used in
135 // a `ModifierContext`.
136 getCategory: function() { return this.constructor.CATEGORY; },
137
138 // Set the rendering context for the note.
139 setContext: function(context) { this.context = context; return this; },
140
141 // Get and set spacing to the left and right of the notes.
142 getExtraLeftPx: function() { return this.extraLeftPx; },
143 getExtraRightPx: function() { return this.extraRightPx; },
144 setExtraLeftPx: function(x) { this.extraLeftPx = x; return this; },
145 setExtraRightPx: function(x) { this.extraRightPx = x; return this; },
146
147 // Returns true if this note has no duration (e.g., bar notes, spacers, etc.)
148 shouldIgnoreTicks: function() { return this.ignore_ticks; },
149
150 // Get the stave line number for the note.
151 getLineNumber: function() { return 0; },
152
153 // Get the stave line number for rest.
154 getLineForRest: function() { return 0; },
155
156 // Get the glyph associated with this note.
157 getGlyph: function() { return this.glyph; },
158
159 // Set and get Y positions for this note. Each Y value is associated with
160 // an individual pitch/key within the note/chord.
161 setYs: function(ys) { this.ys = ys; return this; },
162 getYs: function() {
163 if (this.ys.length === 0) throw new Vex.RERR("NoYValues",
164 "No Y-values calculated for this note.");
165 return this.ys;
166 },
167
168 // Get the Y position of the space above the stave onto which text can
169 // be rendered.
170 getYForTopText: function(text_line) {
171 if (!this.stave) throw new Vex.RERR("NoStave",
172 "No stave attached to this note.");
173 return this.stave.getYForTopText(text_line);
174 },
175
176 // Get a `BoundingBox` for this note.
177 getBoundingBox: function() { return null; },
178
179 // Returns the voice that this note belongs in.
180 getVoice: function() {
181 if (!this.voice) throw new Vex.RERR("NoVoice", "Note has no voice.");
182 return this.voice;
183 },
184
185 // Attach this note to `voice`.
186 setVoice: function(voice) {
187 this.voice = voice;
188 this.preFormatted = false;
189 return this;
190 },
191
192 // Get and set the `TickContext` for this note.
193 getTickContext: function() { return this.tickContext; },
194 setTickContext: function(tc) {
195 this.tickContext = tc;
196 this.preFormatted = false;
197 return this;
198 },
199
200 // Accessors for the note type.
201 getDuration: function() { return this.duration; },
202 isDotted: function() { return (this.dots > 0); },
203 hasStem: function() { return false; },
204 getDots: function() { return this.dots; },
205 getNoteType: function() { return this.noteType; },
206 setBeam: function() { return this; }, // ignore parameters
207
208 // Attach this note to a modifier context.
209 setModifierContext: function(mc) { this.modifierContext = mc; return this; },
210
211 // Attach a modifier to this note.
212 addModifier: function(modifier, index) {
213 modifier.setNote(this);
214 modifier.setIndex(index || 0);
215 this.modifiers.push(modifier);
216 this.setPreFormatted(false);
217 return this;
218 },
219
220 // Get the coordinates for where modifiers begin.
221 getModifierStartXY: function() {
222 if (!this.preFormatted) throw new Vex.RERR("UnformattedNote",
223 "Can't call GetModifierStartXY on an unformatted note");
224 return {x: this.getAbsoluteX(), y: this.ys[0]};
225 },
226
227 // Get bounds and metrics for this note.
228 //
229 // Returns a struct with fields:
230 // `width`: The total width of the note (including modifiers.)
231 // `noteWidth`: The width of the note head only.
232 // `left_shift`: The horizontal displacement of the note.
233 // `modLeftPx`: Start `X` for left modifiers.
234 // `modRightPx`: Start `X` for right modifiers.
235 // `extraLeftPx`: Extra space on left of note.
236 // `extraRightPx`: Extra space on right of note.
237 getMetrics: function() {
238 if (!this.preFormatted) throw new Vex.RERR("UnformattedNote",
239 "Can't call getMetrics on an unformatted note.");
240 var modLeftPx = 0;
241 var modRightPx = 0;
242 if (this.modifierContext != null) {
243 modLeftPx = this.modifierContext.state.left_shift;
244 modRightPx = this.modifierContext.state.right_shift;
245 }
246
247 var width = this.getWidth();
248 return { width: width,
249 noteWidth: width -
250 modLeftPx - modRightPx - // used by accidentals and modifiers
251 this.extraLeftPx - this.extraRightPx,
252 left_shift: this.x_shift, // TODO(0xfe): Make style consistent
253 modLeftPx: modLeftPx,
254 modRightPx: modRightPx,
255 extraLeftPx: this.extraLeftPx,
256 extraRightPx: this.extraRightPx };
257 },
258
259 // Get and set width of note. Used by the formatter for positioning.
260 setWidth: function(width) { this.width = width; },
261 getWidth: function() {
262 if (!this.preFormatted) throw new Vex.RERR("UnformattedNote",
263 "Can't call GetWidth on an unformatted note.");
264 return this.width +
265 (this.modifierContext ? this.modifierContext.getWidth() : 0);
266 },
267
268 // Displace note by `x` pixels.
269 setXShift: function(x) {
270 this.x_shift = x;
271 return this;
272 },
273
274 // Get `X` position of this tick context.
275 getX: function() {
276 if (!this.tickContext) throw new Vex.RERR("NoTickContext",
277 "Note needs a TickContext assigned for an X-Value");
278 return this.tickContext.getX() + this.x_shift;
279 },
280
281 // Get the absolute `X` position of this note relative to the stave.
282 getAbsoluteX: function() {
283 if (!this.tickContext) throw new Vex.RERR("NoTickContext",
284 "Note needs a TickContext assigned for an X-Value");
285
286 // Position note to left edge of tick context.
287 var x = this.tickContext.getX();
288 if (this.stave) {
289 x += this.stave.getNoteStartX() + this.render_options.stave_padding;
290 }
291
292 if (this.isCenterAligned()){
293 x += this.getCenterXShift();
294 }
295
296 return x;
297 },
298
299 setPreFormatted: function(value) {
300 this.preFormatted = value;
301
302 // Maintain the width of left and right modifiers in pixels.
303 if (this.preFormatted) {
304 var extra = this.tickContext.getExtraPx();
305 this.left_modPx = Math.max(this.left_modPx, extra.left);
306 this.right_modPx = Math.max(this.right_modPx, extra.right);
307 }
308 }
309 });
310
311 return Note;
312}());
\No newline at end of file