vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature
1,294 lines (799 loc) • 56.2 kB
HTML
<!DOCTYPE html>
<html>
<head>
<title>accidental.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>accidental.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.
@author Mohit Cheppudira
@author Greg Ristow (modifications)</p>
<h2 id="description">Description</h2>
<p>This file implements accidentals as modifiers that can be attached to
notes. Support is included for both western and microtonal accidentals.</p>
<p>See <code>tests/accidental_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> { Fraction } <span class="hljs-keyword">from</span> <span class="hljs-string">'./fraction'</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> { Music } <span class="hljs-keyword">from</span> <span class="hljs-string">'./music'</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> { Glyph } <span class="hljs-keyword">from</span> <span class="hljs-string">'./glyph'</span>;</pre></div>
<p>To enable logging for this class. Set <code>Vex.Flow.Accidental.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> (Accidental.DEBUG) Vex.L(<span class="hljs-string">'Vex.Flow.Accidental'</span>, args); }
<span class="hljs-keyword">const</span> getGlyphWidth = <span class="hljs-function"><span class="hljs-params">glyph</span> =></span> glyph.getMetrics().width;</pre></div>
<p>An <code>Accidental</code> inherits from <code>Modifier</code>, and is formatted within a
<code>ModifierContext</code>.</p>
<div class='highlight'><pre><span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Accidental</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Modifier</span> </span>{
<span class="hljs-keyword">static</span> get CATEGORY() { <span class="hljs-keyword">return</span> <span class="hljs-string">'accidentals'</span>; }</pre></div>
<p>Arrange accidentals inside a ModifierContext.</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> format(accidentals, state) {
<span class="hljs-keyword">const</span> noteheadAccidentalPadding = <span class="hljs-number">1</span>;
<span class="hljs-keyword">const</span> leftShift = state.left_shift + noteheadAccidentalPadding;
<span class="hljs-keyword">const</span> accidentalSpacing = <span class="hljs-number">3</span>;</pre></div>
<p>If there are no accidentals, we needn’t format their positions</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (!accidentals || accidentals.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">const</span> accList = [];
<span class="hljs-keyword">let</span> prevNote = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">let</span> shiftL = <span class="hljs-number">0</span>;</pre></div>
<p>First determine the accidentals’ Y positions from the note.keys</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < accidentals.length; ++i) {
<span class="hljs-keyword">const</span> acc = accidentals[i];
<span class="hljs-keyword">const</span> note = acc.getNote();
<span class="hljs-keyword">const</span> stave = note.getStave();
<span class="hljs-keyword">const</span> props = note.getKeyProps()[acc.getIndex()];
<span class="hljs-keyword">if</span> (note !== prevNote) {</pre></div>
<p>Iterate through all notes to get the displaced pixels</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> n = <span class="hljs-number">0</span>; n < note.keys.length; ++n) {
shiftL = <span class="hljs-built_in">Math</span>.max(note.getLeftDisplacedHeadPx(), shiftL);
}
prevNote = note;
}
<span class="hljs-keyword">if</span> (stave !== <span class="hljs-literal">null</span>) {
<span class="hljs-keyword">const</span> lineSpace = stave.options.spacing_between_lines_px;
<span class="hljs-keyword">const</span> y = stave.getYForLine(props.line);
<span class="hljs-keyword">const</span> accLine = <span class="hljs-built_in">Math</span>.round(y / lineSpace * <span class="hljs-number">2</span>) / <span class="hljs-number">2</span>;
accList.push({ y, <span class="hljs-attr">line</span>: accLine, <span class="hljs-attr">shift</span>: shiftL, acc, lineSpace });
} <span class="hljs-keyword">else</span> {
accList.push({ <span class="hljs-attr">line</span>: props.line, <span class="hljs-attr">shift</span>: shiftL, acc });
}
}</pre></div>
<p>Sort accidentals by line number.</p>
<div class='highlight'><pre> accList.sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> b.line - a.line);</pre></div>
<p>FIXME: Confusing name. Each object in this array has a property called <code>line</code>.
So if this is a list of lines, you end up with: <code>line.line</code> which is very awkward.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> lineList = [];</pre></div>
<p>amount by which all accidentals must be shifted right or left for
stem flipping, notehead shifting concerns.</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> accShift = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> previousLine = <span class="hljs-literal">null</span>;</pre></div>
<p>Create an array of unique line numbers (lineList) from accList</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < accList.length; i++) {
<span class="hljs-keyword">const</span> acc = accList[i];</pre></div>
<p>if this is the first line, or a new line, add a lineList</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (previousLine === <span class="hljs-literal">null</span> || previousLine !== acc.line) {
lineList.push({
<span class="hljs-attr">line</span>: acc.line,
<span class="hljs-attr">flatLine</span>: <span class="hljs-literal">true</span>,
<span class="hljs-attr">dblSharpLine</span>: <span class="hljs-literal">true</span>,
<span class="hljs-attr">numAcc</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">width</span>: <span class="hljs-number">0</span>,
});
}</pre></div>
<p>if this accidental is not a flat, the accidental needs 3.0 lines lower
clearance instead of 2.5 lines for b or bb.
FIXME: Naming could use work. acc.acc is very awkward</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (acc.acc.type !== <span class="hljs-string">'b'</span> && acc.acc.type !== <span class="hljs-string">'bb'</span>) {
lineList[lineList.length - <span class="hljs-number">1</span>].flatLine = <span class="hljs-literal">false</span>;
}</pre></div>
<p>if this accidental is not a double sharp, the accidental needs 3.0 lines above</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (acc.acc.type !== <span class="hljs-string">'##'</span>) {
lineList[lineList.length - <span class="hljs-number">1</span>].dblSharpLine = <span class="hljs-literal">false</span>;
}</pre></div>
<p>Track how many accidentals are on this line:</p>
<div class='highlight'><pre> lineList[lineList.length - <span class="hljs-number">1</span>].numAcc++;</pre></div>
<p>Track the total x_offset needed for this line which will be needed
for formatting lines w/ multiple accidentals:</p>
<p>width = accidental width + universal spacing between accidentals</p>
<div class='highlight'><pre> lineList[lineList.length - <span class="hljs-number">1</span>].width += acc.acc.getWidth() + accidentalSpacing;</pre></div>
<p>if this accShift is larger, use it to keep first column accidentals in the same line</p>
<div class='highlight'><pre> accShift = acc.shift > accShift ? acc.shift : accShift;
previousLine = acc.line;
}</pre></div>
<h3 id="place-accidentals-in-columns">Place Accidentals in Columns</h3>
<p>Default to a classic triangular layout (middle accidental farthest left),
but follow exceptions as outlined in G. Read’s <em>Music Notation</em> and
Elaine Gould’s <em>Behind Bars</em>.</p>
<p>Additionally, this implements different vertical collision rules for
flats (only need 2.5 lines clearance below) and double sharps (only
need 2.5 lines of clearance above or below).</p>
<p>Classic layouts and exception patterns are found in the ‘tables.js’
in ‘Vex.Flow.accidentalColumnsTable’</p>
<p>Beyond 6 vertical accidentals, default to the parallel ascending lines approach,
using as few columns as possible for the verticle structure.</p>
<p>TODO (?): Allow column to be specified for an accidental at run-time?</p>
<div class='highlight'><pre>
<span class="hljs-keyword">let</span> totalColumns = <span class="hljs-number">0</span>;</pre></div>
<p>establish the boundaries for a group of notes with clashing accidentals:</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < lineList.length; i++) {
<span class="hljs-keyword">let</span> noFurtherConflicts = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">const</span> groupStart = i;
<span class="hljs-keyword">let</span> groupEnd = i;
<span class="hljs-keyword">while</span> (groupEnd + <span class="hljs-number">1</span> < lineList.length && !noFurtherConflicts) {</pre></div>
<p>if this note conflicts with the next:</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.checkCollision(lineList[groupEnd], lineList[groupEnd + <span class="hljs-number">1</span>])) {</pre></div>
<p>include the next note in the group:</p>
<div class='highlight'><pre> groupEnd++;
} <span class="hljs-keyword">else</span> {
noFurtherConflicts = <span class="hljs-literal">true</span>;
}
}</pre></div>
<p>Gets an a line from the <code>lineList</code>, relative to the current group</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> getGroupLine = <span class="hljs-function">(<span class="hljs-params">index</span>) =></span> lineList[groupStart + index];
<span class="hljs-keyword">const</span> getGroupLines = <span class="hljs-function">(<span class="hljs-params">indexes</span>) =></span> indexes.map(getGroupLine);
<span class="hljs-keyword">const</span> lineDifference = <span class="hljs-function">(<span class="hljs-params">indexA, indexB</span>) =></span> {
<span class="hljs-keyword">const</span> [a, b] = getGroupLines([indexA, indexB]).map(<span class="hljs-function"><span class="hljs-params">item</span> =></span> item.line);
<span class="hljs-keyword">return</span> a - b;
};
<span class="hljs-keyword">const</span> notColliding = <span class="hljs-function">(<span class="hljs-params">...indexPairs</span>) =></span>
indexPairs
.map(getGroupLines)
.every(<span class="hljs-function"><span class="hljs-params">lines</span> =></span> !<span class="hljs-keyword">this</span>.checkCollision(...lines));</pre></div>
<p>Set columns for the lines in this group:</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> groupLength = groupEnd - groupStart + <span class="hljs-number">1</span>;</pre></div>
<p>Set the accidental column for each line of the group</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> endCase = <span class="hljs-keyword">this</span>.checkCollision(lineList[groupStart], lineList[groupEnd]) ? <span class="hljs-string">'a'</span> : <span class="hljs-string">'b'</span>;
<span class="hljs-keyword">switch</span> (groupLength) {
<span class="hljs-keyword">case</span> <span class="hljs-number">3</span>:
<span class="hljs-keyword">if</span> (endCase === <span class="hljs-string">'a'</span> && lineDifference(<span class="hljs-number">1</span>, <span class="hljs-number">2</span>) === <span class="hljs-number">0.5</span> && lineDifference(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>) !== <span class="hljs-number">0.5</span>) {
endCase = <span class="hljs-string">'second_on_bottom'</span>;
}
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">4</span>:
<span class="hljs-keyword">if</span> (notColliding([<span class="hljs-number">0</span>, <span class="hljs-number">2</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>])) {
endCase = <span class="hljs-string">'spaced_out_tetrachord'</span>;
}
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">5</span>:
<span class="hljs-keyword">if</span> (endCase === <span class="hljs-string">'b'</span> && notColliding([<span class="hljs-number">1</span>, <span class="hljs-number">3</span>])) {
endCase = <span class="hljs-string">'spaced_out_pentachord'</span>;
<span class="hljs-keyword">if</span> (notColliding([<span class="hljs-number">0</span>, <span class="hljs-number">2</span>], [<span class="hljs-number">2</span>, <span class="hljs-number">4</span>])) {
endCase = <span class="hljs-string">'very_spaced_out_pentachord'</span>;
}
}
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> <span class="hljs-number">6</span>:
<span class="hljs-keyword">if</span> (notColliding([<span class="hljs-number">0</span>, <span class="hljs-number">3</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">4</span>], [<span class="hljs-number">2</span>, <span class="hljs-number">5</span>])) {
endCase = <span class="hljs-string">'spaced_out_hexachord'</span>;
}
<span class="hljs-keyword">if</span> (notColliding([<span class="hljs-number">0</span>, <span class="hljs-number">2</span>], [<span class="hljs-number">2</span>, <span class="hljs-number">4</span>], [<span class="hljs-number">1</span>, <span class="hljs-number">3</span>], [<span class="hljs-number">3</span>, <span class="hljs-number">5</span>])) {
endCase = <span class="hljs-string">'very_spaced_out_hexachord'</span>;
}
<span class="hljs-keyword">break</span>;
<span class="hljs-keyword">default</span>:
<span class="hljs-keyword">break</span>;
}
<span class="hljs-keyword">let</span> groupMember;
<span class="hljs-keyword">let</span> column;</pre></div>
<p>If the group contains more than seven members, use ascending parallel lines
of accidentals, using as few columns as possible while avoiding collisions.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (groupLength >= <span class="hljs-number">7</span>) {</pre></div>
<p>First, determine how many columns to use:</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> patternLength = <span class="hljs-number">2</span>;
<span class="hljs-keyword">let</span> collisionDetected = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">while</span> (collisionDetected === <span class="hljs-literal">true</span>) {
collisionDetected = <span class="hljs-literal">false</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> line = <span class="hljs-number">0</span>; line + patternLength < lineList.length; line++) {
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.checkCollision(lineList[line], lineList[line + patternLength])) {
collisionDetected = <span class="hljs-literal">true</span>;
patternLength++;
<span class="hljs-keyword">break</span>;
}
}
}</pre></div>
<p>Then, assign a column to each line of accidentals</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (groupMember = i; groupMember <= groupEnd; groupMember++) {
column = ((groupMember - i) % patternLength) + <span class="hljs-number">1</span>;
lineList[groupMember].column = column;
totalColumns = (totalColumns > column) ? totalColumns : column;
}</pre></div>
<p>Otherwise, if the group contains fewer than seven members, use the layouts from
the accidentalsColumnsTable housed in tables.js.</p>
<div class='highlight'><pre> } <span class="hljs-keyword">else</span> {
<span class="hljs-keyword">for</span> (groupMember = i; groupMember <= groupEnd; groupMember++) {
column = Flow.accidentalColumnsTable[groupLength][endCase][groupMember - i];
lineList[groupMember].column = column;
totalColumns = (totalColumns > column) ? totalColumns : column;
}
}</pre></div>
<p>Increment i to the last note that was set, so that if a lower set of notes
does not conflict at all with this group, it can have its own classic shape.</p>
<div class='highlight'><pre> i = groupEnd;
}</pre></div>
<h3 id="convert-columns-to-x_offsets">Convert Columns to x_offsets</h3>
<p>This keeps columns aligned, even if they have different accidentals within them
which sometimes results in a larger x_offset than is an accidental might need
to preserve the symmetry of the accidental shape.</p>
<p>Neither A.C. Vinci nor G. Read address this, and it typically only happens in
music with complex chord clusters.</p>
<p>TODO (?): Optionally allow closer compression of accidentals, instead of forcing
parallel columns.</p>
<p>track each column’s max width, which will be used as initial shift of later columns:</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> columnWidths = [];
<span class="hljs-keyword">const</span> columnXOffsets = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i <= totalColumns; i++) {
columnWidths[i] = <span class="hljs-number">0</span>;
columnXOffsets[i] = <span class="hljs-number">0</span>;
}
columnWidths[<span class="hljs-number">0</span>] = accShift + leftShift;
columnXOffsets[<span class="hljs-number">0</span>] = accShift + leftShift;</pre></div>
<p>Fill columnWidths with widest needed x-space;
this is what keeps the columns parallel.</p>
<div class='highlight'><pre> lineList.forEach(<span class="hljs-function"><span class="hljs-params">line</span> =></span> {
<span class="hljs-keyword">if</span> (line.width > columnWidths[line.column]) columnWidths[line.column] = line.width;
});
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i < columnWidths.length; i++) {</pre></div>
<p>this column’s offset = this column’s width + previous column’s offset</p>
<div class='highlight'><pre> columnXOffsets[i] = columnWidths[i] + columnXOffsets[i - <span class="hljs-number">1</span>];
}
<span class="hljs-keyword">const</span> totalShift = columnXOffsets[columnXOffsets.length - <span class="hljs-number">1</span>];</pre></div>
<p>Set the xShift for each accidental according to column offsets:</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> accCount = <span class="hljs-number">0</span>;
lineList.forEach(<span class="hljs-function"><span class="hljs-params">line</span> =></span> {
<span class="hljs-keyword">let</span> lineWidth = <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> lastAccOnLine = accCount + line.numAcc;</pre></div>
<p>handle all of the accidentals on a given line:</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (accCount; accCount < lastAccOnLine; accCount++) {
<span class="hljs-keyword">const</span> xShift = (columnXOffsets[line.column - <span class="hljs-number">1</span>] + lineWidth);
accList[accCount].acc.setXShift(xShift);</pre></div>
<p>keep track of the width of accidentals we’ve added so far, so that when
we loop, we add space for them.</p>
<div class='highlight'><pre> lineWidth += accList[accCount].acc.getWidth() + accidentalSpacing;
L(<span class="hljs-string">'Line, accCount, shift: '</span>, line.line, accCount, xShift);
}
});</pre></div>
<p>update the overall layout with the full width of the accidental shapes:</p>
<div class='highlight'><pre> state.left_shift += totalShift;
}</pre></div>
<p>Helper function to determine whether two lines of accidentals collide vertically</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> checkCollision(line1, line2) {
<span class="hljs-keyword">let</span> clearance = line2.line - line1.line;
<span class="hljs-keyword">let</span> clearanceRequired = <span class="hljs-number">3</span>;</pre></div>
<p>But less clearance is required for certain accidentals: b, bb and ##.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (clearance > <span class="hljs-number">0</span>) { <span class="hljs-comment">// then line 2 is on top</span>
clearanceRequired = (line2.flatLine || line2.dblSharpLine) ? <span class="hljs-number">2.5</span> : <span class="hljs-number">3.0</span>;
<span class="hljs-keyword">if</span> (line1.dblSharpLine) clearance -= <span class="hljs-number">0.5</span>;
} <span class="hljs-keyword">else</span> { <span class="hljs-comment">// line 1 is on top</span>
clearanceRequired = (line1.flatLine || line1.dblSharpLine) ? <span class="hljs-number">2.5</span> : <span class="hljs-number">3.0</span>;
<span class="hljs-keyword">if</span> (line2.dblSharpLine) clearance -= <span class="hljs-number">0.5</span>;
}
<span class="hljs-keyword">const</span> collision = <span class="hljs-built_in">Math</span>.abs(clearance) < clearanceRequired;
L(<span class="hljs-string">'Line_1, Line_2, Collision: '</span>, line1.line, line2.line, collision);
<span class="hljs-keyword">return</span> collision;
}</pre></div>
<p>Use this method to automatically apply accidentals to a set of <code>voices</code>.
The accidentals will be remembered between all the voices provided.
Optionally, you can also provide an initial <code>keySignature</code>.</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> applyAccidentals(voices, keySignature) {
<span class="hljs-keyword">const</span> tickPositions = [];
<span class="hljs-keyword">const</span> tickNoteMap = {};</pre></div>
<p>Sort the tickables in each voice by their tick position in the voice</p>
<div class='highlight'><pre> voices.forEach(<span class="hljs-function"><span class="hljs-params">voice</span> =></span> {
<span class="hljs-keyword">const</span> tickPosition = <span class="hljs-keyword">new</span> Fraction(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>);
<span class="hljs-keyword">const</span> notes = voice.getTickables();
notes.forEach(<span class="hljs-function"><span class="hljs-params">note</span> =></span> {
<span class="hljs-keyword">if</span> (note.shouldIgnoreTicks()) <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">const</span> notesAtPosition = tickNoteMap[tickPosition.value()];
<span class="hljs-keyword">if</span> (!notesAtPosition) {
tickPositions.push(tickPosition.value());
tickNoteMap[tickPosition.value()] = [note];
} <span class="hljs-keyword">else</span> {
notesAtPosition.push(note);
}
tickPosition.add(note.getTicks());
});
});
<span class="hljs-keyword">const</span> music = <span class="hljs-keyword">new</span> Music();</pre></div>
<p>Default key signature is C major</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (!keySignature) keySignature = <span class="hljs-string">'C'</span>;</pre></div>
<p>Get the scale map, which represents the current state of each pitch</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> scaleMap = music.createScaleMap(keySignature);
tickPositions.forEach(<span class="hljs-function"><span class="hljs-params">tick</span> =></span> {
<span class="hljs-keyword">const</span> notes = tickNoteMap[tick];</pre></div>
<p>Array to store all pitches that modified accidental states
at this tick position</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> modifiedPitches = [];
<span class="hljs-keyword">const</span> processNote = <span class="hljs-function">(<span class="hljs-params">note</span>) =></span> {
<span class="hljs-keyword">if</span> (note.isRest() || note.shouldIgnoreTicks()) <span class="hljs-keyword">return</span>;</pre></div>
<p>Go through each key and determine if an accidental should be
applied</p>
<div class='highlight'><pre> note.keys.forEach(<span class="hljs-function">(<span class="hljs-params">keyString, keyIndex</span>) =></span> {
<span class="hljs-keyword">const</span> key = music.getNoteParts(keyString.split(<span class="hljs-string">'/'</span>)[<span class="hljs-number">0</span>]);</pre></div>
<p>Force a natural for every key without an accidental</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> accidentalString = key.accidental || <span class="hljs-string">'n'</span>;
<span class="hljs-keyword">const</span> pitch = key.root + accidentalString;</pre></div>
<p>Determine if the current pitch has the same accidental
as the scale state</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> sameAccidental = scaleMap[key.root] === pitch;</pre></div>
<p>Determine if an identical pitch in the chord already
modified the accidental state</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> previouslyModified = modifiedPitches.indexOf(pitch) > <span class="hljs-number">-1</span>;</pre></div>
<p>Add the accidental to the StaveNote</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (!sameAccidental || (sameAccidental && previouslyModified)) {</pre></div>
<p>Modify the scale map so that the root pitch has an
updated state</p>
<div class='highlight'><pre> scaleMap[key.root] = pitch;</pre></div>
<p>Create the accidental</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> accidental = <span class="hljs-keyword">new</span> Accidental(accidentalString);</pre></div>
<p>Attach the accidental to the StaveNote</p>
<div class='highlight'><pre> note.addAccidental(keyIndex, accidental);</pre></div>
<p>Add the pitch to list of pitches that modified accidentals</p>
<div class='highlight'><pre> modifiedPitches.push(pitch);
}
});</pre></div>
<p>process grace notes</p>
<div class='highlight'><pre> note.getModifiers().forEach(<span class="hljs-function"><span class="hljs-params">modifier</span> =></span> {
<span class="hljs-keyword">if</span> (modifier.getCategory() === <span class="hljs-string">'gracenotegroups'</span>) {
modifier.getGraceNotes().forEach(processNote);
}
});
};
notes.forEach(processNote);
});
}</pre></div>
<p>Create accidental. <code>type</code> can be a value from the
<code>Vex.Flow.accidentalCodes.accidentals</code> table in <code>tables.js</code>. For
example: <code>#</code>, <code>##</code>, <code>b</code>, <code>n</code>, etc.</p>
<div class='highlight'><pre> <span class="hljs-keyword">constructor</span>(type = null) {
<span class="hljs-keyword">super</span>();
<span class="hljs-keyword">this</span>.setAttribute(<span class="hljs-string">'type'</span>, <span class="hljs-string">'Accidental'</span>);
L(<span class="hljs-string">'New accidental: '</span>, type);
<span class="hljs-keyword">this</span>.note = <span class="hljs-literal">null</span>;</pre></div>
<p>The <code>index</code> points to a specific note in a chord.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.index = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">this</span>.type = type;
<span class="hljs-keyword">this</span>.position = Modifier.Position.LEFT;
<span class="hljs-keyword">this</span>.render_options = {</pre></div>
<p>Font size for glyphs</p>
<div class='highlight'><pre> font_scale: <span class="hljs-number">38</span>,</pre></div>
<p>Length of stroke across heads above or below the stave.</p>
<div class='highlight'><pre> stroke_px: <span class="hljs-number">3</span>,</pre></div>
<p>Padding between accidental and parentheses on each side</p>
<div class='highlight'><pre> parenLeftPadding: <span class="hljs-number">2</span>,
<span class="hljs-attr">parenRightPadding</span>: <span class="hljs-number">2</span>,
};
<span class="hljs-keyword">this</span>.accidental = Flow.accidentalCodes(<span class="hljs-keyword">this</span>.type);
<span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.accidental) {
<span class="hljs-keyword">throw</s