vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature
1,297 lines (832 loc) • 96.1 kB
HTML
<!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>) =></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>) =></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 < <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 < 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 > <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 > <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> && noteU.stemDirection === <span class="hljs-number">-1</span> && 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 &&
noteU.minLine <= 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 <= 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> && noteM.minLine < 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 && !noteU.isrest && !noteL.isrest) {
<span class="hljs-keyword">if</span> (noteU.minLine <= noteM.maxLine || noteM.minLine <= 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 < 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 && noteM.isrest && 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 && noteU.isrest && noteM.minLine <= 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 && noteL.isrest && noteU.minLine <= 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 && noteU.minLine <= 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 && noteM.minLine <= 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 && !noteM.isrest && noteU.minLine <= noteM.maxLine + <span class="hljs-number">0.5</span>) ||
(!noteM.isrest && !noteL.isrest && noteM.minLine <= 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 < notes.length; i++) {
hasStave = hasStave && 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 < 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 < <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> =></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 & 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> =></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>) =></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> =></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 < 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 < <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