UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

1,297 lines (832 loc) 96.1 kB
<!DOCTYPE html> <html> <head> <title>stavenote.js</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <link rel="stylesheet" media="all" href="public/stylesheets/normalize.css" /> <link rel="stylesheet" media="all" href="docco.css" /> </head> <body> <div class="container"> <div class="page"> <div class="header"> <h1>stavenote.js</h1> <div class="toc"> <h3>Table of Contents</h3> <ol> <li> <a class="source" href="accidental.html"> accidental.js </a> </li> <li> <a class="source" href="annotation.html"> annotation.js </a> </li> <li> <a class="source" href="articulation.html"> articulation.js </a> </li> <li> <a class="source" href="barnote.html"> barnote.js </a> </li> <li> <a class="source" href="beam.html"> beam.js </a> </li> <li> <a class="source" href="bend.html"> bend.js </a> </li> <li> <a class="source" href="boundingbox.html"> boundingbox.js </a> </li> <li> <a class="source" href="boundingboxcomputation.html"> boundingboxcomputation.js </a> </li> <li> <a class="source" href="canvascontext.html"> canvascontext.js </a> </li> <li> <a class="source" href="clef.html"> clef.js </a> </li> <li> <a class="source" href="clefnote.html"> clefnote.js </a> </li> <li> <a class="source" href="crescendo.html"> crescendo.js </a> </li> <li> <a class="source" href="curve.html"> curve.js </a> </li> <li> <a class="source" href="dot.html"> dot.js </a> </li> <li> <a class="source" href="easyscore.html"> easyscore.js </a> </li> <li> <a class="source" href="element.html"> element.js </a> </li> <li> <a class="source" href="factory.html"> factory.js </a> </li> <li> <a class="source" href="formatter.html"> formatter.js </a> </li> <li> <a class="source" href="fraction.html"> fraction.js </a> </li> <li> <a class="source" href="frethandfinger.html"> frethandfinger.js </a> </li> <li> <a class="source" href="ghostnote.html"> ghostnote.js </a> </li> <li> <a class="source" href="glyph.html"> glyph.js </a> </li> <li> <a class="source" href="glyphnote.html"> glyphnote.js </a> </li> <li> <a class="source" href="gracenote.html"> gracenote.js </a> </li> <li> <a class="source" href="gracenotegroup.html"> gracenotegroup.js </a> </li> <li> <a class="source" href="gracetabnote.html"> gracetabnote.js </a> </li> <li> <a class="source" href="index.html"> index.js </a> </li> <li> <a class="source" href="keymanager.html"> keymanager.js </a> </li> <li> <a class="source" href="keysignature.html"> keysignature.js </a> </li> <li> <a class="source" href="keysignote.html"> keysignote.js </a> </li> <li> <a class="source" href="modifier.html"> modifier.js </a> </li> <li> <a class="source" href="modifiercontext.html"> modifiercontext.js </a> </li> <li> <a class="source" href="multimeasurerest.html"> multimeasurerest.js </a> </li> <li> <a class="source" href="music.html"> music.js </a> </li> <li> <a class="source" href="note.html"> note.js </a> </li> <li> <a class="source" href="notehead.html"> notehead.js </a> </li> <li> <a class="source" href="notesubgroup.html"> notesubgroup.js </a> </li> <li> <a class="source" href="ornament.html"> ornament.js </a> </li> <li> <a class="source" href="parser.html"> parser.js </a> </li> <li> <a class="source" href="pedalmarking.html"> pedalmarking.js </a> </li> <li> <a class="source" href="raphaelcontext.html"> raphaelcontext.js </a> </li> <li> <a class="source" href="registry.html"> registry.js </a> </li> <li> <a class="source" href="renderer.html"> renderer.js </a> </li> <li> <a class="source" href="repeatnote.html"> repeatnote.js </a> </li> <li> <a class="source" href="smufl.html"> smufl.js </a> </li> <li> <a class="source" href="stave.html"> stave.js </a> </li> <li> <a class="source" href="stavebarline.html"> stavebarline.js </a> </li> <li> <a class="source" href="staveconnector.html"> staveconnector.js </a> </li> <li> <a class="source" href="stavehairpin.html"> stavehairpin.js </a> </li> <li> <a class="source" href="staveline.html"> staveline.js </a> </li> <li> <a class="source" href="stavemodifier.html"> stavemodifier.js </a> </li> <li> <a class="source" href="stavenote.html"> stavenote.js </a> </li> <li> <a class="source" href="staverepetition.html"> staverepetition.js </a> </li> <li> <a class="source" href="stavesection.html"> stavesection.js </a> </li> <li> <a class="source" href="stavetempo.html"> stavetempo.js </a> </li> <li> <a class="source" href="stavetext.html"> stavetext.js </a> </li> <li> <a class="source" href="stavetie.html"> stavetie.js </a> </li> <li> <a class="source" href="stavevolta.html"> stavevolta.js </a> </li> <li> <a class="source" href="stem.html"> stem.js </a> </li> <li> <a class="source" href="stemmablenote.html"> stemmablenote.js </a> </li> <li> <a class="source" href="stringnumber.html"> stringnumber.js </a> </li> <li> <a class="source" href="strokes.html"> strokes.js </a> </li> <li> <a class="source" href="svgcontext.html"> svgcontext.js </a> </li> <li> <a class="source" href="system.html"> system.js </a> </li> <li> <a class="source" href="tables.html"> tables.js </a> </li> <li> <a class="source" href="tabnote.html"> tabnote.js </a> </li> <li> <a class="source" href="tabslide.html"> tabslide.js </a> </li> <li> <a class="source" href="tabstave.html"> tabstave.js </a> </li> <li> <a class="source" href="tabtie.html"> tabtie.js </a> </li> <li> <a class="source" href="textbracket.html"> textbracket.js </a> </li> <li> <a class="source" href="textdynamics.html"> textdynamics.js </a> </li> <li> <a class="source" href="textnote.html"> textnote.js </a> </li> <li> <a class="source" href="tickable.html"> tickable.js </a> </li> <li> <a class="source" href="tickcontext.html"> tickcontext.js </a> </li> <li> <a class="source" href="timesignature.html"> timesignature.js </a> </li> <li> <a class="source" href="timesignote.html"> timesignote.js </a> </li> <li> <a class="source" href="tremolo.html"> tremolo.js </a> </li> <li> <a class="source" href="tuning.html"> tuning.js </a> </li> <li> <a class="source" href="tuplet.html"> tuplet.js </a> </li> <li> <a class="source" href="vex.html"> vex.js </a> </li> <li> <a class="source" href="vibrato.html"> vibrato.js </a> </li> <li> <a class="source" href="vibratobracket.html"> vibratobracket.js </a> </li> <li> <a class="source" href="voice.html"> voice.js </a> </li> <li> <a class="source" href="voicegroup.html"> voicegroup.js </a> </li> </ol> </div> </div> <p><a href="http://vexflow.com">VexFlow</a> - Copyright (c) Mohit Muthanna 2010.</p> <h2 id="description">Description</h2> <p>This file implements notes for standard notation. This consists of one or more <code>NoteHeads</code>, an optional stem, and an optional flag.</p> <p><em>Throughout these comments, a “note” refers to the entire <code>StaveNote</code>, and a “key” refers to a specific pitch/notehead within a note.</em></p> <p>See <code>tests/stavenote_tests.js</code> for usage examples.</p> <div class='highlight'><pre> <span class="hljs-keyword">import</span> { Vex } <span class="hljs-keyword">from</span> <span class="hljs-string">'./vex'</span>; <span class="hljs-keyword">import</span> { Flow } <span class="hljs-keyword">from</span> <span class="hljs-string">'./tables'</span>; <span class="hljs-keyword">import</span> { BoundingBox } <span class="hljs-keyword">from</span> <span class="hljs-string">'./boundingbox'</span>; <span class="hljs-keyword">import</span> { Stem } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stem'</span>; <span class="hljs-keyword">import</span> { NoteHead } <span class="hljs-keyword">from</span> <span class="hljs-string">'./notehead'</span>; <span class="hljs-keyword">import</span> { StemmableNote } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stemmablenote'</span>; <span class="hljs-keyword">import</span> { Modifier } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modifier'</span>; <span class="hljs-keyword">import</span> { Dot } <span class="hljs-keyword">from</span> <span class="hljs-string">'./dot'</span>;</pre></div> <p>To enable logging for this class. Set <code>Vex.Flow.StaveNote.DEBUG</code> to <code>true</code>.</p> <div class='highlight'><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">L</span>(<span class="hljs-params">...args</span>) </span>{ <span class="hljs-keyword">if</span> (StaveNote.DEBUG) Vex.L(<span class="hljs-string">'Vex.Flow.StaveNote'</span>, args); } <span class="hljs-keyword">const</span> getStemAdjustment = <span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> Stem.WIDTH / (<span class="hljs-number">2</span> * -note.getStemDirection()); <span class="hljs-keyword">const</span> isInnerNoteIndex = <span class="hljs-function">(<span class="hljs-params">note, index</span>) =&gt;</span> index === (note.getStemDirection() === Stem.UP ? note.keyProps.length - <span class="hljs-number">1</span> : <span class="hljs-number">0</span>);</pre></div> <p>Helper methods for rest positioning in ModifierContext.</p> <div class='highlight'><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">shiftRestVertical</span>(<span class="hljs-params">rest, note, dir</span>) </span>{ <span class="hljs-keyword">const</span> delta = (note.isrest ? <span class="hljs-number">0.0</span> : <span class="hljs-number">1.0</span>) * dir; rest.line += delta; rest.maxLine += delta; rest.minLine += delta; rest.note.setKeyLine(<span class="hljs-number">0</span>, rest.note.getKeyLine(<span class="hljs-number">0</span>) + (delta)); }</pre></div> <p>Called from formatNotes :: center a rest between two notes</p> <div class='highlight'><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">centerRest</span>(<span class="hljs-params">rest, noteU, noteL</span>) </span>{ <span class="hljs-keyword">const</span> delta = rest.line - Vex.MidLine(noteU.minLine, noteL.maxLine); rest.note.setKeyLine(<span class="hljs-number">0</span>, rest.note.getKeyLine(<span class="hljs-number">0</span>) - delta); rest.line -= delta; rest.maxLine -= delta; rest.minLine -= delta; } <span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StaveNote</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StemmableNote</span> </span>{ <span class="hljs-keyword">static</span> get CATEGORY() { <span class="hljs-keyword">return</span> <span class="hljs-string">'stavenotes'</span>; } <span class="hljs-keyword">static</span> get STEM_UP() { <span class="hljs-keyword">return</span> Stem.UP; } <span class="hljs-keyword">static</span> get STEM_DOWN() { <span class="hljs-keyword">return</span> Stem.DOWN; } <span class="hljs-keyword">static</span> get DEFAULT_LEDGER_LINE_OFFSET() { <span class="hljs-keyword">return</span> <span class="hljs-number">3</span>; }</pre></div> <h2 id="static-methods">Static Methods</h2> <p>Format notes inside a ModifierContext.</p> <div class='highlight'><pre> <span class="hljs-keyword">static</span> format(notes, state) { <span class="hljs-keyword">if</span> (!notes || notes.length &lt; <span class="hljs-number">2</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;</pre></div> <p>FIXME: VexFlow will soon require that a stave be set before formatting. Which, according to the below condition, means that following branch will always be taken and the rest of this function is dead code.</p> <p>Problematically, <code>Formatter#formatByY</code> was not designed to work for more than 2 voices (although, doesn’t throw on this condition, just tries to power through).</p> <p>Based on the above:</p> <ul> <li>2 voices can be formatted <em>with or without</em> a stave being set but the output will be different</li> <li>3 voices can only be formatted <em>without</em> a stave</li> </ul> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (notes[<span class="hljs-number">0</span>].getStave()) { <span class="hljs-keyword">return</span> StaveNote.formatByY(notes, state); } <span class="hljs-keyword">const</span> notesList = []; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; notes.length; i++) { <span class="hljs-keyword">const</span> props = notes[i].getKeyProps(); <span class="hljs-keyword">const</span> line = props[<span class="hljs-number">0</span>].line; <span class="hljs-keyword">let</span> minL = props[props.length - <span class="hljs-number">1</span>].line; <span class="hljs-keyword">const</span> stemDirection = notes[i].getStemDirection(); <span class="hljs-keyword">const</span> stemMax = notes[i].getStemLength() / <span class="hljs-number">10</span>; <span class="hljs-keyword">const</span> stemMin = notes[i].getStemMinumumLength() / <span class="hljs-number">10</span>; <span class="hljs-keyword">let</span> maxL; <span class="hljs-keyword">if</span> (notes[i].isRest()) { maxL = line + notes[i].glyph.line_above; minL = line - notes[i].glyph.line_below; } <span class="hljs-keyword">else</span> { maxL = stemDirection === <span class="hljs-number">1</span> ? props[props.length - <span class="hljs-number">1</span>].line + stemMax : props[props.length - <span class="hljs-number">1</span>].line; minL = stemDirection === <span class="hljs-number">1</span> ? props[<span class="hljs-number">0</span>].line : props[<span class="hljs-number">0</span>].line - stemMax; } notesList.push({ <span class="hljs-attr">line</span>: props[<span class="hljs-number">0</span>].line, <span class="hljs-comment">// note/rest base line</span> maxLine: maxL, <span class="hljs-comment">// note/rest upper bounds line</span> minLine: minL, <span class="hljs-comment">// note/rest lower bounds line</span> isrest: notes[i].isRest(), stemDirection, stemMax, <span class="hljs-comment">// Maximum (default) note stem length;</span> stemMin, <span class="hljs-comment">// minimum note stem length</span> voice_shift: notes[i].getVoiceShiftWidth(), <span class="hljs-attr">is_displaced</span>: notes[i].isDisplaced(), <span class="hljs-comment">// note manually displaced</span> note: notes[i], }); } <span class="hljs-keyword">const</span> voices = notesList.length; <span class="hljs-keyword">let</span> noteU = notesList[<span class="hljs-number">0</span>]; <span class="hljs-keyword">const</span> noteM = voices &gt; <span class="hljs-number">2</span> ? notesList[<span class="hljs-number">1</span>] : <span class="hljs-literal">null</span>; <span class="hljs-keyword">let</span> noteL = voices &gt; <span class="hljs-number">2</span> ? notesList[<span class="hljs-number">2</span>] : notesList[<span class="hljs-number">1</span>];</pre></div> <p>for two voice backward compatibility, ensure upper voice is stems up for three voices, the voices must be in order (upper, middle, lower)</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (voices === <span class="hljs-number">2</span> &amp;&amp; noteU.stemDirection === <span class="hljs-number">-1</span> &amp;&amp; noteL.stemDirection === <span class="hljs-number">1</span>) { noteU = notesList[<span class="hljs-number">1</span>]; noteL = notesList[<span class="hljs-number">0</span>]; } <span class="hljs-keyword">const</span> voiceXShift = <span class="hljs-built_in">Math</span>.max(noteU.voice_shift, noteL.voice_shift); <span class="hljs-keyword">let</span> xShift = <span class="hljs-number">0</span>; <span class="hljs-keyword">let</span> stemDelta;</pre></div> <p>Test for two voice note intersection</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (voices === <span class="hljs-number">2</span>) { <span class="hljs-keyword">const</span> lineSpacing = noteU.stemDirection === noteL.stemDirection ? <span class="hljs-number">0.0</span> : <span class="hljs-number">0.5</span>;</pre></div> <p>if top voice is a middle voice, check stem intersection with lower voice</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteU.stemDirection === noteL.stemDirection &amp;&amp; noteU.minLine &lt;= noteL.maxLine) { <span class="hljs-keyword">if</span> (!noteU.isrest) { stemDelta = <span class="hljs-built_in">Math</span>.abs(noteU.line - (noteL.maxLine + <span class="hljs-number">0.5</span>)); stemDelta = <span class="hljs-built_in">Math</span>.max(stemDelta, noteU.stemMin); noteU.minLine = noteU.line - stemDelta; noteU.note.setStemLength(stemDelta * <span class="hljs-number">10</span>); } } <span class="hljs-keyword">if</span> (noteU.minLine &lt;= noteL.maxLine + lineSpacing) { <span class="hljs-keyword">if</span> (noteU.isrest) {</pre></div> <p>shift rest up</p> <div class='highlight'><pre> shiftRestVertical(noteU, noteL, <span class="hljs-number">1</span>); } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (noteL.isrest) {</pre></div> <p>shift rest down</p> <div class='highlight'><pre> shiftRestVertical(noteL, noteU, <span class="hljs-number">-1</span>); } <span class="hljs-keyword">else</span> { xShift = voiceXShift; <span class="hljs-keyword">if</span> (noteU.stemDirection === noteL.stemDirection) {</pre></div> <p>upper voice is middle voice, so shift it right</p> <div class='highlight'><pre> noteU.note.setXShift(xShift + <span class="hljs-number">3</span>); } <span class="hljs-keyword">else</span> {</pre></div> <p>shift lower voice right</p> <div class='highlight'><pre> noteL.note.setXShift(xShift); } } }</pre></div> <p>format complete</p> <div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; }</pre></div> <p>Check middle voice stem intersection with lower voice</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteM !== <span class="hljs-literal">null</span> &amp;&amp; noteM.minLine &lt; noteL.maxLine + <span class="hljs-number">0.5</span>) { <span class="hljs-keyword">if</span> (!noteM.isrest) { stemDelta = <span class="hljs-built_in">Math</span>.abs(noteM.line - (noteL.maxLine + <span class="hljs-number">0.5</span>)); stemDelta = <span class="hljs-built_in">Math</span>.max(stemDelta, noteM.stemMin); noteM.minLine = noteM.line - stemDelta; noteM.note.setStemLength(stemDelta * <span class="hljs-number">10</span>); } }</pre></div> <p>For three voices, test if rests can be repositioned</p> <p>Special case 1 :: middle voice rest between two notes</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteM.isrest &amp;&amp; !noteU.isrest &amp;&amp; !noteL.isrest) { <span class="hljs-keyword">if</span> (noteU.minLine &lt;= noteM.maxLine || noteM.minLine &lt;= noteL.maxLine) { <span class="hljs-keyword">const</span> restHeight = noteM.maxLine - noteM.minLine; <span class="hljs-keyword">const</span> space = noteU.minLine - noteL.maxLine; <span class="hljs-keyword">if</span> (restHeight &lt; space) {</pre></div> <p>center middle voice rest between the upper and lower voices</p> <div class='highlight'><pre> centerRest(noteM, noteU, noteL); } <span class="hljs-keyword">else</span> { xShift = voiceXShift + <span class="hljs-number">3</span>; <span class="hljs-comment">// shift middle rest right</span> noteM.note.setXShift(xShift); }</pre></div> <p>format complete</p> <div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; } }</pre></div> <p>Special case 2 :: all voices are rests</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteU.isrest &amp;&amp; noteM.isrest &amp;&amp; noteL.isrest) {</pre></div> <p>Shift upper voice rest up</p> <div class='highlight'><pre> shiftRestVertical(noteU, noteM, <span class="hljs-number">1</span>);</pre></div> <p>Shift lower voice rest down</p> <div class='highlight'><pre> shiftRestVertical(noteL, noteM, <span class="hljs-number">-1</span>);</pre></div> <p>format complete</p> <div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; }</pre></div> <p>Test if any other rests can be repositioned</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteM.isrest &amp;&amp; noteU.isrest &amp;&amp; noteM.minLine &lt;= noteL.maxLine) {</pre></div> <p>Shift middle voice rest up</p> <div class='highlight'><pre> shiftRestVertical(noteM, noteL, <span class="hljs-number">1</span>); } <span class="hljs-keyword">if</span> (noteM.isrest &amp;&amp; noteL.isrest &amp;&amp; noteU.minLine &lt;= noteM.maxLine) {</pre></div> <p>Shift middle voice rest down</p> <div class='highlight'><pre> shiftRestVertical(noteM, noteU, <span class="hljs-number">-1</span>); } <span class="hljs-keyword">if</span> (noteU.isrest &amp;&amp; noteU.minLine &lt;= noteM.maxLine) {</pre></div> <p>shift upper voice rest up;</p> <div class='highlight'><pre> shiftRestVertical(noteU, noteM, <span class="hljs-number">1</span>); } <span class="hljs-keyword">if</span> (noteL.isrest &amp;&amp; noteM.minLine &lt;= noteL.maxLine) {</pre></div> <p>shift lower voice rest down</p> <div class='highlight'><pre> shiftRestVertical(noteL, noteM, <span class="hljs-number">-1</span>); }</pre></div> <p>If middle voice intersects upper or lower voice</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> ((!noteU.isrest &amp;&amp; !noteM.isrest &amp;&amp; noteU.minLine &lt;= noteM.maxLine + <span class="hljs-number">0.5</span>) || (!noteM.isrest &amp;&amp; !noteL.isrest &amp;&amp; noteM.minLine &lt;= noteL.maxLine)) { xShift = voiceXShift + <span class="hljs-number">3</span>; <span class="hljs-comment">// shift middle note right</span> noteM.note.setXShift(xShift); } <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; } <span class="hljs-keyword">static</span> formatByY(notes, state) {</pre></div> <p>NOTE: this function does not support more than two voices per stave use with care.</p> <div class='highlight'><pre> <span class="hljs-keyword">let</span> hasStave = <span class="hljs-literal">true</span>; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; notes.length; i++) { hasStave = hasStave &amp;&amp; notes[i].getStave() != <span class="hljs-literal">null</span>; } <span class="hljs-keyword">if</span> (!hasStave) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR( <span class="hljs-string">'Stave Missing'</span>, <span class="hljs-string">'All notes must have a stave - Vex.Flow.ModifierContext.formatMultiVoice!'</span> ); } <span class="hljs-keyword">let</span> xShift = <span class="hljs-number">0</span>; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; notes.length - <span class="hljs-number">1</span>; i++) { <span class="hljs-keyword">let</span> topNote = notes[i]; <span class="hljs-keyword">let</span> bottomNote = notes[i + <span class="hljs-number">1</span>]; <span class="hljs-keyword">if</span> (topNote.getStemDirection() === Stem.DOWN) { topNote = notes[i + <span class="hljs-number">1</span>]; bottomNote = notes[i]; } <span class="hljs-keyword">const</span> topKeys = topNote.getKeyProps(); <span class="hljs-keyword">const</span> bottomKeys = bottomNote.getKeyProps(); <span class="hljs-keyword">const</span> HALF_NOTEHEAD_HEIGHT = <span class="hljs-number">0.5</span>;</pre></div> <p><code>keyProps</code> and <code>stave.getYForLine</code> have different notions of a <code>line</code> so we have to convert the keyProps value by subtracting 5. See <a href="https://github.com/0xfe/vexflow/wiki/Development-Gotchas">https://github.com/0xfe/vexflow/wiki/Development-Gotchas</a></p> <p>We also extend the y for each note by a half notehead because the notehead’s origin is centered</p> <div class='highlight'><pre> <span class="hljs-keyword">const</span> topNoteBottomY = topNote .getStave() .getYForLine(<span class="hljs-number">5</span> - topKeys[<span class="hljs-number">0</span>].line + HALF_NOTEHEAD_HEIGHT); <span class="hljs-keyword">const</span> bottomNoteTopY = bottomNote .getStave() .getYForLine(<span class="hljs-number">5</span> - bottomKeys[bottomKeys.length - <span class="hljs-number">1</span>].line - HALF_NOTEHEAD_HEIGHT); <span class="hljs-keyword">const</span> areNotesColliding = bottomNoteTopY - topNoteBottomY &lt; <span class="hljs-number">0</span>; <span class="hljs-keyword">if</span> (areNotesColliding) { xShift = topNote.getVoiceShiftWidth() + <span class="hljs-number">2</span>; bottomNote.setXShift(xShift); } } state.right_shift += xShift; } <span class="hljs-keyword">static</span> postFormat(notes) { <span class="hljs-keyword">if</span> (!notes) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; notes.forEach(<span class="hljs-function"><span class="hljs-params">note</span> =&gt;</span> note.postFormat()); <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; } <span class="hljs-keyword">constructor</span>(noteStruct) { <span class="hljs-keyword">super</span>(noteStruct); <span class="hljs-keyword">this</span>.setAttribute(<span class="hljs-string">'type'</span>, <span class="hljs-string">'StaveNote'</span>); <span class="hljs-keyword">this</span>.keys = noteStruct.keys; <span class="hljs-keyword">this</span>.clef = noteStruct.clef; <span class="hljs-keyword">this</span>.octave_shift = noteStruct.octave_shift; <span class="hljs-keyword">this</span>.beam = <span class="hljs-literal">null</span>;</pre></div> <p>Pull note rendering properties</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.glyph = Flow.getGlyphProps(<span class="hljs-keyword">this</span>.duration, <span class="hljs-keyword">this</span>.noteType); <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.glyph) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RuntimeError( <span class="hljs-string">'BadArguments'</span>, <span class="hljs-string">`Invalid note initialization data (No glyph found): <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(noteStruct)}</span>`</span> ); }</pre></div> <p>if true, displace note to right</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.displaced = <span class="hljs-literal">false</span>; <span class="hljs-keyword">this</span>.dot_shiftY = <span class="hljs-number">0</span>;</pre></div> <p>per-pitch properties</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.keyProps = [];</pre></div> <p>for displaced ledger lines</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.use_default_head_x = <span class="hljs-literal">false</span>;</pre></div> <p>Drawing</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.note_heads = []; <span class="hljs-keyword">this</span>.modifiers = []; Vex.Merge(<span class="hljs-keyword">this</span>.render_options, {</pre></div> <p>font size for note heads and rests</p> <div class='highlight'><pre> glyph_font_scale: noteStruct.glyph_font_scale || Flow.DEFAULT_NOTATION_FONT_SCALE,</pre></div> <p>number of stroke px to the left and right of head</p> <div class='highlight'><pre> stroke_px: noteStruct.stroke_px || StaveNote.DEFAULT_LEDGER_LINE_OFFSET, }); <span class="hljs-keyword">this</span>.calculateKeyProps(); <span class="hljs-keyword">this</span>.buildStem();</pre></div> <p>Set the stem direction</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (noteStruct.auto_stem) { <span class="hljs-keyword">this</span>.autoStem(); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">this</span>.setStemDirection(noteStruct.stem_direction); } <span class="hljs-keyword">this</span>.reset(); <span class="hljs-keyword">this</span>.buildFlag(); } reset() { <span class="hljs-keyword">super</span>.reset();</pre></div> <p>Save prior noteHead styles &amp; reapply them after making new noteheads.</p> <div class='highlight'><pre> <span class="hljs-keyword">const</span> noteHeadStyles = <span class="hljs-keyword">this</span>.note_heads.map(<span class="hljs-function"><span class="hljs-params">noteHead</span> =&gt;</span> noteHead.getStyle()); <span class="hljs-keyword">this</span>.buildNoteHeads(); <span class="hljs-keyword">this</span>.note_heads.forEach(<span class="hljs-function">(<span class="hljs-params">noteHead, index</span>) =&gt;</span> noteHead.setStyle(noteHeadStyles[index])); <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.stave) { <span class="hljs-keyword">this</span>.note_heads.forEach(<span class="hljs-function"><span class="hljs-params">head</span> =&gt;</span> head.setStave(<span class="hljs-keyword">this</span>.stave)); } <span class="hljs-keyword">this</span>.calcNoteDisplacements(); } setBeam(beam) { <span class="hljs-keyword">this</span>.beam = beam; <span class="hljs-keyword">this</span>.calcNoteDisplacements(); <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>; } getCategory() { <span class="hljs-keyword">return</span> StaveNote.CATEGORY; }</pre></div> <p>Builds a <code>Stem</code> for the note</p> <div class='highlight'><pre> buildStem() { <span class="hljs-keyword">this</span>.setStem(<span class="hljs-keyword">new</span> Stem({ <span class="hljs-attr">hide</span>: !!<span class="hljs-keyword">this</span>.isRest(), })); }</pre></div> <p>Builds a <code>NoteHead</code> for each key in the note</p> <div class='highlight'><pre> buildNoteHeads() { <span class="hljs-keyword">this</span>.note_heads = []; <span class="hljs-keyword">const</span> stemDirection = <span class="hljs-keyword">this</span>.getStemDirection(); <span class="hljs-keyword">const</span> keys = <span class="hljs-keyword">this</span>.getKeys(); <span class="hljs-keyword">let</span> lastLine = <span class="hljs-literal">null</span>; <span class="hljs-keyword">let</span> lineDiff = <span class="hljs-literal">null</span>; <span class="hljs-keyword">let</span> displaced = <span class="hljs-literal">false</span>;</pre></div> <p>Draw notes from bottom to top.</p> <p>For down-stem notes, we draw from top to bottom.</p> <div class='highlight'><pre> <span class="hljs-keyword">let</span> start; <span class="hljs-keyword">let</span> end; <span class="hljs-keyword">let</span> step; <span class="hljs-keyword">if</span> (stemDirection === Stem.UP) { start = <span class="hljs-number">0</span>; end = keys.length; step = <span class="hljs-number">1</span>; } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (stemDirection === Stem.DOWN) { start = keys.length - <span class="hljs-number">1</span>; end = <span class="hljs-number">-1</span>; step = <span class="hljs-number">-1</span>; } <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = start; i !== end; i += step) { <span class="hljs-keyword">const</span> noteProps = <span class="hljs-keyword">this</span>.keyProps[i]; <span class="hljs-keyword">const</span> line = noteProps.line;</pre></div> <p>Keep track of last line with a note head, so that consecutive heads are correctly displaced.</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (lastLine === <span class="hljs-literal">null</span>) { lastLine = line; } <span class="hljs-keyword">else</span> { lineDiff = <span class="hljs-built_in">Math</span>.abs(lastLine - line); <span class="hljs-keyword">if</span> (lineDiff === <span class="hljs-number">0</span> || lineDiff === <span class="hljs-number">0.5</span>) { displaced = !displaced; } <span class="hljs-keyword">else</span> { displaced = <span class="hljs-literal">false</span>; <span class="hljs-keyword">this</span>.use_default_head_x = <span class="hljs-literal">true</span>; } } lastLine = line; <span class="hljs-keyword">const</span> notehead = <span class="hljs-keyword">new</span> NoteHead({ <span class="hljs-attr">duration</span>: <span class="hljs-keyword">this</span>.duration, <span class="hljs-attr">note_type</span>: <span class="hljs-keyword">this</span>.noteType, displaced, <span class="hljs-attr">stem_direction</span>: stemDirection, <span class="hljs-attr">custom_glyph_code</span>: noteProps.code, <span class="hljs-attr">glyph_font_scale</span>: <span class="hljs-keyword">this</span>.render_options.glyph_font_scale, <span class="hljs-attr">x_shift</span>: noteProps.shift_right, <span class="hljs-attr">stem_up_x_offset</span>: noteProps.stem_up_x_offset, <span class="hljs-attr">stem_down_x_offset</span>: noteProps.stem_down_x_offset, <span class="hljs-attr">line</span>: noteProps.line, }); <span class="hljs-keyword">this</span>.note_heads[i] = notehead; } }</pre></div> <p>Automatically sets the stem direction based on the keys in the note</p> <div class='highlight'><pre> autoStem() {</pre></div> <p>Figure out optimal stem direction based on given notes</p> <div class='highlight'><pre> <span class="hljs-keyword">this</span>.minLine = <span class="hljs-keyword">this</span>.keyProps[<span class="hljs-number">0</span>].line; <span class="hljs-keyword">this</span>.maxLine = <span class="hljs-keyword">this</span>.keyProps[<span class="hljs-keyword">this</span>.keyProps.length - <span class="hljs-number">1</span>].line; <span class="hljs-keyword">const</span> MIDDLE_LINE = <span class="hljs-number">3</span>; <span class="hljs-keyword">const</span> decider = (<span class="hljs-keyword">this</span>.minLine + <span class="hljs-keyword">this</span>.maxLine) / <span class="hljs-number">2</span>; <span class="hljs-keyword">const</span> stemDirection = decider &lt; MIDDLE_LINE ? Stem.UP : Stem.DOWN; <span class="hljs-keyword">this</span>.setStemDirection(stemDirection); }</pre></div> <p>Calculates and stores the properties for each key in the note</p> <div class='highlight'><pre> calculateKeyProps() { <span class="hljs-keyword">let</span> lastLine = <span class="hljs-literal">null</span>; <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-keyword">this</span>.keys.length; ++i) { <span class="hljs-keyword">const</span> key = <span class="hljs-keyword">this</span>.keys[i];</pre></div> <p>All rests use the same position on the line. if (this.glyph.rest) key = this.glyph.position;</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.glyph.rest) <span class="hljs-keyword">this</span>.glyph.position = key; <span class="hljs-keyword">const</span> options = { <span class="hljs-attr">octave_shift</span>: <span class="hljs-keyword">this</span>.octave_shift || <span class="hljs-number">0</span> }; <span class="hljs-keyword">const</span> props = Flow.keyProperties(key, <span class="hljs-keyword">this</span>.clef, options); <span class="hljs-keyword">if</span> (!props) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RuntimeError(<span class="hljs-string">'BadArguments'</span>, <span class="hljs-string">`Invalid key for note properties: <span class="hljs-subst">${key}</span>`</span>); }</pre></div> <p>Override line placement for default rests</p> <div class='highlight'><pre> <span class="hljs-keyword">if</span> (props.key === <span class="hljs-string">'R'</span>) { <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.duration === <span class="hljs-string">'1'</span> || <span class="hljs-keyword">this</span>.duration === <span class="hljs-string">'w'</span>) { props.line = <span cla