| 
					
				 | 
			
			
				@@ -0,0 +1,368 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+/** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * ## Description 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * Create a new tuplet from the specified notes. The notes must 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * be part of the same voice. If they are of different rhythmic 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * values, then options.num_notes must be set. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @constructor 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @param {Array.<Vex.Flow.StaveNote>} A set of notes: staveNotes, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   notes, etc... any class that inherits stemmableNote at some 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   point in its prototype chain. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * @param options: object { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   num_notes: fit this many notes into... 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   notes_occupied: ...the space of this many notes 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *       Together, these two properties make up the tuplet ratio 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     in the form of num_notes : notes_occupied. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *       num_notes defaults to the number of notes passed in, so 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     it is important that if you omit this property, all of 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     the notes passed should be of the same note value. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *       notes_occupied defaults to 2 -- so you should almost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     certainly pass this parameter for anything other than 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     a basic triplet. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   location: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     default 1, which is above the notes: ┌─── 3 ───┐ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *      -1 is below the notes └─── 3 ───┘ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   bracketed: boolean, draw a bracket around the tuplet number 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     when true: ┌─── 3 ───┐   when false: 3 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     defaults to true if notes are not beamed, false otherwise 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   ratioed: boolean 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     when true: ┌─── 7:8 ───┐, when false: ┌─── 7 ───┐ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     defaults to true if the difference between num_notes and 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     notes_occupied is greater than 1. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *   y_offset: int, default 0 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     manually offset a tuplet, for instance to avoid collisions 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ *     with articulations, etc... 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ * } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ import { Vex } from './vex'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ import { Element } from './element'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ import { Formatter } from './formatter'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ import { Glyph } from './glyph'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ import { Stem } from './stem'; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ export class Tuplet extends Element { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   static get LOCATION_TOP() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return 1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   static get LOCATION_BOTTOM() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return -1; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   static get NESTING_OFFSET() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return 15; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   constructor(notes, options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     super(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.setAttribute('type', 'Tuplet'); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (!notes || !notes.length) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       throw new Vex.RuntimeError('BadArguments', 'No notes provided for tuplet.'); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.options = Vex.Merge({}, options); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.notes = notes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.num_notes = 'num_notes' in this.options ? 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.options.num_notes : notes.length; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // We accept beats_occupied, but warn that it's deprecated: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // the preferred property name is now notes_occupied. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.options.beats_occupied) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.beatsOccupiedDeprecationWarning(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.notes_occupied = this.options.notes_occupied || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.options.beats_occupied || 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if ('bracketed' in this.options) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.bracketed = this.options.bracketed; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.bracketed = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         notes.some(note => note.beam === null); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.ratioed = 'ratioed' in this.options ? 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.options.ratioed : 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       (Math.abs(this.notes_occupied - this.num_notes) > 1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.point = 28; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.y_pos = 16; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.x_pos = 100; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.width = 200; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.location = this.options.location || Tuplet.LOCATION_TOP; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     Formatter.AlignRestsToNotes(notes, true, true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.resolveGlyphs(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.attach(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   attach() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     for (let i = 0; i < this.notes.length; i++) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const note = this.notes[i]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       note.setTuplet(this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   detach() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     for (let i = 0; i < this.notes.length; i++) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const note = this.notes[i]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       note.resetTuplet(this); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    * Set whether or not the bracket is drawn. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   setBracketed(bracketed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.bracketed = !!bracketed; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    * Set whether or not the ratio is shown. 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   setRatioed(ratioed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.ratioed = !!ratioed; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    * Set the tuplet to be displayed either on the top or bottom of the stave 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   setTupletLocation(location) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (!location) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       location = Tuplet.LOCATION_TOP; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } else if (location !== Tuplet.LOCATION_TOP && location !== Tuplet.LOCATION_BOTTOM) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       throw new Vex.RERR('BadArgument', 'Invalid tuplet location: ' + location); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.location = location; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getNotes() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this.notes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getNoteCount() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this.num_notes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   beatsOccupiedDeprecationWarning() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const msg = [ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       'beats_occupied has been deprecated as an ', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       'option for tuplets. Please use notes_occupied ', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       'instead. Calls to getBeatsOccupied and ', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       'setBeatsOccupied should now be routed to ', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       'getNotesOccupied and setNotesOccupied instead', 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     ].join(''); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (console && console.warn) { // eslint-disable-line no-console 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       console.warn(msg); // eslint-disable-line no-console 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } else if (console) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       console.log(msg); // eslint-disable-line no-console 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getBeatsOccupied() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.beatsOccupiedDeprecationWarning(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this.getNotesOccupied(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   setBeatsOccupied(beats) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.beatsOccupiedDeprecationWarning(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this.setNotesOccupied(beats); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getNotesOccupied() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return this.notes_occupied; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   setNotesOccupied(notes) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.detach(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.notes_occupied = notes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.resolveGlyphs(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.attach(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   resolveGlyphs() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.numerator_glyphs = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     let n = this.num_notes; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     while (n >= 1) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.numerator_glyphs.unshift(new Glyph('v' + (n % 10), this.point)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       n = parseInt(n / 10, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.denom_glyphs = []; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     n = this.notes_occupied; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     while (n >= 1) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.denom_glyphs.unshift(new Glyph('v' + (n % 10), this.point)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       n = parseInt(n / 10, 10); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   // determine how many tuplets are nested within this tuplet 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   // on the same side (above/below), to calculate a y 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   // offset for this tuplet: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getNestedTupletCount() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const location = this.location; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const first_note = this.notes[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     let maxTupletCount = countTuplets(first_note, location); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     let minTupletCount = countTuplets(first_note, location); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // Count the tuplets that are on the same side (above/below) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // as this tuplet: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     function countTuplets(note, location) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       return note.tupletStack.filter(tuplet => tuplet.location === location).length; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.notes.forEach(note => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const tupletCount = countTuplets(note, location); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       maxTupletCount = tupletCount > maxTupletCount ? tupletCount : maxTupletCount; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       minTupletCount = tupletCount < minTupletCount ? tupletCount : minTupletCount; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return maxTupletCount - minTupletCount; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   // determine the y position of the tuplet: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   getYPosition() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // offset the tuplet for any nested tuplets between 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // it and the notes: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const nested_tuplet_y_offset = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.getNestedTupletCount() * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       Tuplet.NESTING_OFFSET * 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       -this.location; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // offset the tuplet for any manual y_offset: 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const y_offset = this.options.y_offset || 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // now iterate through the notes and find our highest 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // or lowest locations, to form a base y_pos 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const first_note = this.notes[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     let y_pos; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.location === Tuplet.LOCATION_TOP) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       y_pos = first_note.getStave().getYForLine(0) - 15; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       // y_pos = first_note.getStemExtents().topY - 10; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       for (let i = 0; i < this.notes.length; ++i) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         const top_y = this.notes[i].getStemDirection() === Stem.UP 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           ? this.notes[i].getStemExtents().topY - 10 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           : this.notes[i].getStemExtents().baseY - 20; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         if (top_y < y_pos) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           y_pos = top_y; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       y_pos = first_note.getStave().getYForLine(4) + 20; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       for (let i = 0; i < this.notes.length; ++i) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         const bottom_y = this.notes[i].getStemDirection() === Stem.UP 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           ? this.notes[i].getStemExtents().baseY + 20 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           : this.notes[i].getStemExtents().topY + 10; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         if (bottom_y > y_pos) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           y_pos = bottom_y; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     return y_pos + nested_tuplet_y_offset + y_offset; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   draw() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.checkContext(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.setRendered(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // determine x value of left bound of tuplet 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const first_note = this.notes[0]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const last_note = this.notes[this.notes.length - 1]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (!this.bracketed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.x_pos = first_note.getStemX(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.width = last_note.getStemX() - this.x_pos; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } else { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.x_pos = first_note.getTieLeftX() - 5; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.width = last_note.getTieRightX() - this.x_pos + 5; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // determine y value for tuplet 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     this.y_pos = this.getYPosition(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const addGlyphWidth = (width, glyph) => width + glyph.getMetrics().width; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // calculate total width of tuplet notation 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     let width = this.numerator_glyphs.reduce(addGlyphWidth, 0); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.ratioed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       width = this.denom_glyphs.reduce(addGlyphWidth, width); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       width += this.point * 0.32; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const notation_center_x = this.x_pos + (this.width / 2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     const notation_start_x = notation_center_x - (width / 2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // draw bracket if the tuplet is not beamed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.bracketed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const line_width = this.width / 2 - width / 2 - 5; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       // only draw the bracket if it has positive length 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       if (line_width > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this.context.fillRect(this.x_pos, this.y_pos, line_width, 1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this.context.fillRect( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.x_pos + this.width / 2 + width / 2 + 5, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.y_pos, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           line_width, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           1 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this.context.fillRect( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.x_pos, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.y_pos + (this.location === Tuplet.LOCATION_BOTTOM), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.location * 10 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this.context.fillRect( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.x_pos + this.width, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.y_pos + (this.location === Tuplet.LOCATION_BOTTOM), 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           1, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           this.location * 10 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         ); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // VexFlowPatch: add option to not render tuplet numbers 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.RenderTupletNumber !== false) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         // draw numerator glyphs 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         let x_offset = 0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         this.numerator_glyphs.forEach(glyph => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           glyph.render(this.context, notation_start_x + x_offset, this.y_pos + (this.point / 3) - 2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+           x_offset += glyph.getMetrics().width; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     // display colon and denominator if the ratio is to be shown 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     if (this.ratioed) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const colon_x = notation_start_x + x_offset + this.point * 0.16; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       const colon_radius = this.point * 0.06; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.beginPath(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.arc(colon_x, this.y_pos - this.point * 0.08, colon_radius, 0, Math.PI * 2, true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.closePath(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.fill(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.beginPath(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.arc(colon_x, this.y_pos + this.point * 0.12, colon_radius, 0, Math.PI * 2, true); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.closePath(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.context.fill(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       x_offset += this.point * 0.32; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       this.denom_glyphs.forEach(glyph => { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         glyph.render(this.context, notation_start_x + x_offset, this.y_pos + (this.point / 3) - 2); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+         x_offset += glyph.getMetrics().width; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+       }); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+   } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+  
			 |