vexflow
Version:
A JavaScript library for rendering music notation and guitar tablature
1,324 lines (848 loc) • 76 kB
HTML
<!DOCTYPE html>
<html>
<head>
<title>formatter.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>formatter.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 the formatting and layout algorithms that are used
to position notes in a voice. The algorithm can align multiple voices both
within a stave, and across multiple staves.</p>
<p>To do this, the formatter breaks up voices into a grid of rational-valued
<code>ticks</code>, to which each note is assigned. Then, minimum widths are assigned
to each tick based on the widths of the notes and modifiers in that tick. This
establishes the smallest amount of space required for each tick.</p>
<p>Finally, the formatter distributes the left over space proportionally to
all the ticks, setting the <code>x</code> values of the notes in each tick.</p>
<p>See <code>tests/formatter_tests.js</code> for usage examples. The helper functions included
here (<code>FormatAndDraw</code>, <code>FormatAndDrawTab</code>) also serve as useful 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> { Beam } <span class="hljs-keyword">from</span> <span class="hljs-string">'./beam'</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> { Fraction } <span class="hljs-keyword">from</span> <span class="hljs-string">'./fraction'</span>;
<span class="hljs-keyword">import</span> { Voice } <span class="hljs-keyword">from</span> <span class="hljs-string">'./voice'</span>;
<span class="hljs-keyword">import</span> { StaveConnector } <span class="hljs-keyword">from</span> <span class="hljs-string">'./staveconnector'</span>;
<span class="hljs-keyword">import</span> { StaveNote } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stavenote'</span>;
<span class="hljs-keyword">import</span> { ModifierContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'./modifiercontext'</span>;
<span class="hljs-keyword">import</span> { TickContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'./tickcontext'</span>;</pre></div>
<p>To enable logging for this class. Set <code>Vex.Flow.Formatter.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> (Formatter.DEBUG) Vex.L(<span class="hljs-string">'Vex.Flow.Formatter'</span>, args); }</pre></div>
<p>Helper function to locate the next non-rest note(s).</p>
<div class='highlight'><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">lookAhead</span>(<span class="hljs-params">notes, restLine, i, compare</span>) </span>{</pre></div>
<p>If no valid next note group, nextRestLine is same as current.</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> nextRestLine = restLine;</pre></div>
<p>Get the rest line for next valid non-rest note group.</p>
<div class='highlight'><pre> <span class="hljs-keyword">for</span> (i += <span class="hljs-number">1</span>; i < notes.length; i += <span class="hljs-number">1</span>) {
<span class="hljs-keyword">const</span> note = notes[i];
<span class="hljs-keyword">if</span> (!note.isRest() && !note.shouldIgnoreTicks()) {
nextRestLine = note.getLineForRest();
<span class="hljs-keyword">break</span>;
}
}</pre></div>
<p>Locate the mid point between two lines.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (compare && restLine !== nextRestLine) {
<span class="hljs-keyword">const</span> top = <span class="hljs-built_in">Math</span>.max(restLine, nextRestLine);
<span class="hljs-keyword">const</span> bot = <span class="hljs-built_in">Math</span>.min(restLine, nextRestLine);
nextRestLine = Vex.MidLine(top, bot);
}
<span class="hljs-keyword">return</span> nextRestLine;
}</pre></div>
<p>Take an array of <code>voices</code> and place aligned tickables in the same context. Returns
a mapping from <code>tick</code> to <code>ContextType</code>, a list of <code>tick</code>s, and the resolution
multiplier.</p>
<p>Params:</p>
<ul>
<li><code>voices</code>: Array of <code>Voice</code> instances.</li>
<li><code>ContextType</code>: A context class (e.g., <code>ModifierContext</code>, <code>TickContext</code>)</li>
<li><code>addToContext</code>: Function to add tickable to context.</li>
</ul>
<div class='highlight'><pre><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createContexts</span>(<span class="hljs-params">voices, ContextType, addToContext</span>) </span>{
<span class="hljs-keyword">if</span> (!voices || !voices.length) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(<span class="hljs-string">'BadArgument'</span>, <span class="hljs-string">'No voices to format'</span>);
}</pre></div>
<p>Find out highest common multiple of resolution multipliers.
The purpose of this is to find out a common denominator
for all fractional tick values in all tickables of all voices,
so that the values can be expanded and the numerator used
as an integer tick value.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> totalTicks = voices[<span class="hljs-number">0</span>].getTotalTicks();
<span class="hljs-keyword">const</span> resolutionMultiplier = voices.reduce(<span class="hljs-function">(<span class="hljs-params">resolutionMultiplier, voice</span>) =></span> {
<span class="hljs-keyword">if</span> (!voice.getTotalTicks().equals(totalTicks)) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(
<span class="hljs-string">'TickMismatch'</span>, <span class="hljs-string">'Voices should have same total note duration in ticks.'</span>
);
}
<span class="hljs-keyword">if</span> (voice.getMode() === Voice.Mode.STRICT && !voice.isComplete()) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(
<span class="hljs-string">'IncompleteVoice'</span>, <span class="hljs-string">'Voice does not have enough notes.'</span>
);
}
<span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.max(
resolutionMultiplier,
Fraction.LCM(resolutionMultiplier, voice.getResolutionMultiplier())
);
}, <span class="hljs-number">1</span>);</pre></div>
<p>Initialize tick maps.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> tickToContextMap = {};
<span class="hljs-keyword">const</span> tickList = [];
<span class="hljs-keyword">const</span> contexts = [];</pre></div>
<p>For each voice, extract notes and create a context for every
new tick that hasn’t been seen before.</p>
<div class='highlight'><pre> voices.forEach(<span class="hljs-function">(<span class="hljs-params">voice, voiceIndex</span>) =></span> {</pre></div>
<p>Use resolution multiplier as denominator to expand ticks
to suitable integer values, so that no additional expansion
of fractional tick values is needed.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> ticksUsed = <span class="hljs-keyword">new</span> Fraction(<span class="hljs-number">0</span>, resolutionMultiplier);
voice.getTickables().forEach(<span class="hljs-function"><span class="hljs-params">tickable</span> =></span> {
<span class="hljs-keyword">const</span> integerTicks = ticksUsed.numerator;</pre></div>
<p>If we have no tick context for this tick, create one.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (!tickToContextMap[integerTicks]) {
<span class="hljs-keyword">const</span> newContext = <span class="hljs-keyword">new</span> ContextType({ <span class="hljs-attr">tickID</span>: integerTicks });
contexts.push(newContext);
tickToContextMap[integerTicks] = newContext;
}</pre></div>
<p>Add this tickable to the TickContext.</p>
<div class='highlight'><pre> addToContext(tickable, tickToContextMap[integerTicks], voiceIndex);</pre></div>
<p>Maintain a sorted list of tick contexts.</p>
<div class='highlight'><pre> tickList.push(integerTicks);
ticksUsed.add(tickable.getTicks());
});
});
<span class="hljs-keyword">return</span> {
<span class="hljs-attr">map</span>: tickToContextMap,
<span class="hljs-attr">array</span>: contexts,
<span class="hljs-attr">list</span>: Vex.SortAndUnique(tickList, (a, b) => a - b, (a, b) => a === b),
resolutionMultiplier,
};
}
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Formatter</span> </span>{</pre></div>
<p>Helper function to layout “notes” one after the other without
regard for proportions. Useful for tests and debugging.</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> SimpleFormat(notes, x = <span class="hljs-number">0</span>, { paddingBetween = <span class="hljs-number">10</span> } = {}) {
notes.reduce(<span class="hljs-function">(<span class="hljs-params">x, note</span>) =></span> {
note.addToModifierContext(<span class="hljs-keyword">new</span> ModifierContext());
<span class="hljs-keyword">const</span> tick = <span class="hljs-keyword">new</span> TickContext().addTickable(note).preFormat();
<span class="hljs-keyword">const</span> metrics = tick.getMetrics();
tick.setX(x + metrics.totalLeftPx);
<span class="hljs-keyword">return</span> x + tick.getWidth() + metrics.totalRightPx + paddingBetween;
}, x);
}</pre></div>
<p>Helper function to plot formatter debug info.</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> plotDebugging(ctx, formatter, xPos, y1, y2, options) {
options = {
<span class="hljs-attr">stavePadding</span>: Vex.Flow.DEFAULT_FONT_STACK[<span class="hljs-number">0</span>].lookupMetric(<span class="hljs-string">'stave.padding'</span>),
...options,
};
<span class="hljs-keyword">const</span> x = xPos + options.stavePadding;
<span class="hljs-keyword">const</span> contextGaps = formatter.contextGaps;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stroke</span>(<span class="hljs-params">x1, x2, color</span>) </span>{
ctx.beginPath();
ctx.setStrokeStyle(color);
ctx.setFillStyle(color);
ctx.setLineWidth(<span class="hljs-number">1</span>);
ctx.fillRect(x1, y1, <span class="hljs-built_in">Math</span>.max(x2 - x1, <span class="hljs-number">0</span>), y2 - y1);
}
ctx.save();
ctx.setFont(<span class="hljs-string">'Arial'</span>, <span class="hljs-number">8</span>, <span class="hljs-string">''</span>);
contextGaps.gaps.forEach(<span class="hljs-function"><span class="hljs-params">gap</span> =></span> {
stroke(x + gap.x1, x + gap.x2, <span class="hljs-string">'rgba(100,200,100,0.4)'</span>);
ctx.setFillStyle(<span class="hljs-string">'green'</span>);
ctx.fillText(<span class="hljs-built_in">Math</span>.round(gap.x2 - gap.x1), x + gap.x1, y2 + <span class="hljs-number">12</span>);
});
ctx.setFillStyle(<span class="hljs-string">'red'</span>);
ctx.fillText(<span class="hljs-string">`Loss: <span class="hljs-subst">${(formatter.totalCost || <span class="hljs-number">0</span>).toFixed(<span class="hljs-number">2</span>)}</span> Shift: <span class="hljs-subst">${(formatter.totalShift || <span class="hljs-number">0</span>).toFixed(<span class="hljs-number">2</span>)}</span> Gap: <span class="hljs-subst">${contextGaps.total.toFixed(<span class="hljs-number">2</span>)}</span>`</span>, x - <span class="hljs-number">20</span>, y2 + <span class="hljs-number">27</span>);
ctx.restore();
}</pre></div>
<p>Helper function to format and draw a single voice. Returns a bounding
box for the notation.</p>
<p>Parameters:</p>
<ul>
<li><code>ctx</code> - The rendering context</li>
<li><code>stave</code> - The stave to which to draw (<code>Stave</code> or <code>TabStave</code>)</li>
<li><code>notes</code> - Array of <code>Note</code> instances (<code>StaveNote</code>, <code>TextNote</code>, <code>TabNote</code>, etc.)</li>
<li><code>params</code> - One of below:<ul>
<li>Setting <code>autobeam</code> only <code>(context, stave, notes, true)</code> or
<code>(ctx, stave, notes, {autobeam: true})</code></li>
<li>Setting <code>align_rests</code> a struct is needed <code>(context, stave, notes, {align_rests: true})</code></li>
<li>Setting both a struct is needed <code>(context, stave, notes, {
autobeam: true, align_rests: true})</code></li>
</ul>
</li>
</ul>
<p><code>autobeam</code> automatically generates beams for the notes.
<code>align_rests</code> aligns rests with nearby notes.</p>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> FormatAndDraw(ctx, stave, notes, params) {
<span class="hljs-keyword">const</span> options = {
<span class="hljs-attr">auto_beam</span>: <span class="hljs-literal">false</span>,
<span class="hljs-attr">align_rests</span>: <span class="hljs-literal">false</span>,
};
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> params === <span class="hljs-string">'object'</span>) {
Vex.Merge(options, params);
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> params === <span class="hljs-string">'boolean'</span>) {
options.auto_beam = params;
}</pre></div>
<p>Start by creating a voice and adding all the notes to it.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> voice = <span class="hljs-keyword">new</span> Voice(Flow.TIME4_4)
.setMode(Voice.Mode.SOFT)
.addTickables(notes);</pre></div>
<p>Then create beams, if requested.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> beams = options.auto_beam ? Beam.applyAndGetBeams(voice) : [];</pre></div>
<p>Instantiate a <code>Formatter</code> and format the notes.</p>
<div class='highlight'><pre> <span class="hljs-keyword">new</span> Formatter()
.joinVoices([voice], { <span class="hljs-attr">align_rests</span>: options.align_rests })
.formatToStave([voice], stave, { <span class="hljs-attr">align_rests</span>: options.align_rests, stave });</pre></div>
<p>Render the voice and beams to the stave.</p>
<div class='highlight'><pre> voice.setStave(stave).draw(ctx, stave);
beams.forEach(<span class="hljs-function"><span class="hljs-params">beam</span> =></span> beam.setContext(ctx).draw());</pre></div>
<p>Return the bounding box of the voice.</p>
<div class='highlight'><pre> <span class="hljs-keyword">return</span> voice.getBoundingBox();
}</pre></div>
<p>Helper function to format and draw aligned tab and stave notes in two
separate staves.</p>
<p>Parameters:</p>
<ul>
<li><code>ctx</code> - The rendering context</li>
<li><code>tabstave</code> - A <code>TabStave</code> instance on which to render <code>TabNote</code>s.</li>
<li><code>stave</code> - A <code>Stave</code> instance on which to render <code>Note</code>s.</li>
<li><code>notes</code> - Array of <code>Note</code> instances for the stave (<code>StaveNote</code>, <code>BarNote</code>, etc.)</li>
<li><code>tabnotes</code> - Array of <code>Note</code> instances for the tab stave (<code>TabNote</code>, <code>BarNote</code>, etc.)</li>
<li><code>autobeam</code> - Automatically generate beams.</li>
<li><code>params</code> - A configuration object:<ul>
<li><code>autobeam</code> automatically generates beams for the notes.</li>
<li><code>align_rests</code> aligns rests with nearby notes.</li>
</ul>
</li>
</ul>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> FormatAndDrawTab(ctx, tabstave, stave, tabnotes, notes, autobeam, params) {
<span class="hljs-keyword">const</span> opts = {
<span class="hljs-attr">auto_beam</span>: autobeam,
<span class="hljs-attr">align_rests</span>: <span class="hljs-literal">false</span>,
};
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> params === <span class="hljs-string">'object'</span>) {
Vex.Merge(opts, params);
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> params === <span class="hljs-string">'boolean'</span>) {
opts.auto_beam = params;
}</pre></div>
<p>Create a <code>4/4</code> voice for <code>notes</code>.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> notevoice = <span class="hljs-keyword">new</span> Voice(Flow.TIME4_4)
.setMode(Voice.Mode.SOFT)
.addTickables(notes);</pre></div>
<p>Create a <code>4/4</code> voice for <code>tabnotes</code>.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> tabvoice = <span class="hljs-keyword">new</span> Voice(Flow.TIME4_4)
.setMode(Voice.Mode.SOFT)
.addTickables(tabnotes);</pre></div>
<p>Then create beams, if requested.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> beams = opts.auto_beam ? Beam.applyAndGetBeams(notevoice) : [];</pre></div>
<p>Instantiate a <code>Formatter</code> and align tab and stave notes.</p>
<div class='highlight'><pre> <span class="hljs-keyword">new</span> Formatter()
.joinVoices([notevoice], { <span class="hljs-attr">align_rests</span>: opts.align_rests })
.joinVoices([tabvoice])
.formatToStave([notevoice, tabvoice], stave, { <span class="hljs-attr">align_rests</span>: opts.align_rests });</pre></div>
<p>Render voices and beams to staves.</p>
<div class='highlight'><pre> notevoice.draw(ctx, stave);
tabvoice.draw(ctx, tabstave);
beams.forEach(<span class="hljs-function"><span class="hljs-params">beam</span> =></span> beam.setContext(ctx).draw());</pre></div>
<p>Draw a connector between tab and note staves.</p>
<div class='highlight'><pre> <span class="hljs-keyword">new</span> StaveConnector(stave, tabstave).setContext(ctx).draw();
}</pre></div>
<p>Auto position rests based on previous/next note positions.</p>
<p>Params:</p>
<ul>
<li><code>notes</code>: An array of notes.</li>
<li><code>alignAllNotes</code>: If set to false, only aligns non-beamed notes.</li>
<li><code>alignTuplets</code>: If set to false, ignores tuplets.</li>
</ul>
<div class='highlight'><pre> <span class="hljs-keyword">static</span> AlignRestsToNotes(notes, alignAllNotes, alignTuplets) {
notes.forEach(<span class="hljs-function">(<span class="hljs-params">note, index</span>) =></span> {
<span class="hljs-keyword">if</span> (note <span class="hljs-keyword">instanceof</span> StaveNote && note.isRest()) {
<span class="hljs-keyword">if</span> (note.tuplet && !alignTuplets) <span class="hljs-keyword">return</span>;</pre></div>
<p>If activated rests not on default can be rendered as specified.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> position = note.getGlyph().position.toUpperCase();
<span class="hljs-keyword">if</span> (position !== <span class="hljs-string">'R/4'</span> && position !== <span class="hljs-string">'B/4'</span>) <span class="hljs-keyword">return</span>;
<span class="hljs-keyword">if</span> (alignAllNotes || note.beam != <span class="hljs-literal">null</span>) {</pre></div>
<p>Align rests with previous/next notes.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> props = note.getKeyProps()[<span class="hljs-number">0</span>];
<span class="hljs-keyword">if</span> (index === <span class="hljs-number">0</span>) {
props.line = lookAhead(notes, props.line, index, <span class="hljs-literal">false</span>);
note.setKeyLine(<span class="hljs-number">0</span>, props.line);
} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (index > <span class="hljs-number">0</span> && index < notes.length) {</pre></div>
<p>If previous note is a rest, use its line number.</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> restLine;
<span class="hljs-keyword">if</span> (notes[index - <span class="hljs-number">1</span>].isRest()) {
restLine = notes[index - <span class="hljs-number">1</span>].getKeyProps()[<span class="hljs-number">0</span>].line;
props.line = restLine;
} <span class="hljs-keyword">else</span> {
restLine = notes[index - <span class="hljs-number">1</span>].getLineForRest();</pre></div>
<p>Get the rest line for next valid non-rest note group.</p>
<div class='highlight'><pre> props.line = lookAhead(notes, restLine, index, <span class="hljs-literal">true</span>);
}
note.setKeyLine(<span class="hljs-number">0</span>, props.line);
}
}
}
});
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>;
}
<span class="hljs-keyword">constructor</span>(options) {
<span class="hljs-keyword">this</span>.options = {
<span class="hljs-attr">softmaxFactor</span>: <span class="hljs-literal">null</span>,
<span class="hljs-attr">maxIterations</span>: <span class="hljs-number">2</span>,
...options
};</pre></div>
<p>Minimum width required to render all the notes in the voices.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.minTotalWidth = <span class="hljs-number">0</span>;</pre></div>
<p>This is set to <code>true</code> after <code>minTotalWidth</code> is calculated.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.hasMinTotalWidth = <span class="hljs-literal">false</span>;</pre></div>
<p>Total number of ticks in the voice.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.totalTicks = <span class="hljs-keyword">new</span> Fraction(<span class="hljs-number">0</span>, <span class="hljs-number">1</span>);</pre></div>
<p>Arrays of tick and modifier contexts.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.tickContexts = <span class="hljs-literal">null</span>;
<span class="hljs-keyword">this</span>.modiferContexts = <span class="hljs-literal">null</span>;</pre></div>
<p>Gaps between contexts, for free movement of notes post
formatting.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.contextGaps = {
<span class="hljs-attr">total</span>: <span class="hljs-number">0</span>,
<span class="hljs-attr">gaps</span>: [],
};
<span class="hljs-keyword">this</span>.voices = [];
<span class="hljs-keyword">this</span>.iterationsCompleted = <span class="hljs-number">0</span>;
<span class="hljs-keyword">this</span>.lossHistory = [];
}</pre></div>
<p>Find all the rests in each of the <code>voices</code> and align them
to neighboring notes. If <code>alignAllNotes</code> is <code>false</code>, then only
align non-beamed notes.</p>
<div class='highlight'><pre> alignRests(voices, alignAllNotes) {
<span class="hljs-keyword">if</span> (!voices || !voices.length) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(<span class="hljs-string">'BadArgument'</span>, <span class="hljs-string">'No voices to format rests'</span>);
}
voices.forEach(<span class="hljs-function"><span class="hljs-params">voice</span> =></span>
Formatter.AlignRestsToNotes(voice.getTickables(), alignAllNotes));
}</pre></div>
<p>Calculate the minimum width required to align and format <code>voices</code>.</p>
<div class='highlight'><pre> preCalculateMinTotalWidth(voices) {</pre></div>
<p>Cache results.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.hasMinTotalWidth) <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.minTotalWidth;</pre></div>
<p>Create tick contexts if not already created.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.tickContexts) {
<span class="hljs-keyword">if</span> (!voices) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(
<span class="hljs-string">'BadArgument'</span>, <span class="hljs-string">"'voices' required to run preCalculateMinTotalWidth"</span>
);
}
<span class="hljs-keyword">this</span>.createTickContexts(voices);
}
<span class="hljs-keyword">const</span> { <span class="hljs-attr">list</span>: contextList, <span class="hljs-attr">map</span>: contextMap } = <span class="hljs-keyword">this</span>.tickContexts;</pre></div>
<p>Go through each tick context and calculate total width.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.minTotalWidth = contextList
.map(<span class="hljs-function"><span class="hljs-params">tick</span> =></span> {
<span class="hljs-keyword">const</span> context = contextMap[tick];
context.preFormat();
<span class="hljs-keyword">return</span> context.getWidth();
})
.reduce(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =></span> a + b, <span class="hljs-number">0</span>);
<span class="hljs-keyword">this</span>.hasMinTotalWidth = <span class="hljs-literal">true</span>;
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.minTotalWidth;
}</pre></div>
<p>Get minimum width required to render all voices. Either <code>format</code> or
<code>preCalculateMinTotalWidth</code> must be called before this method.</p>
<div class='highlight'><pre> getMinTotalWidth() {
<span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.hasMinTotalWidth) {
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(
<span class="hljs-string">'NoMinTotalWidth'</span>,
<span class="hljs-string">"Call 'preCalculateMinTotalWidth' or 'preFormat' before calling 'getMinTotalWidth'"</span>
);
}
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.minTotalWidth;
}</pre></div>
<p>Create <code>ModifierContext</code>s for each tick in <code>voices</code>.</p>
<div class='highlight'><pre> createModifierContexts(voices) {
<span class="hljs-keyword">const</span> contexts = createContexts(
voices,
ModifierContext,
(tickable, context) => tickable.addToModifierContext(context)
);
<span class="hljs-keyword">this</span>.modiferContexts = contexts;
<span class="hljs-keyword">return</span> contexts;
}</pre></div>
<p>Create <code>TickContext</code>s for each tick in <code>voices</code>. Also calculate the
total number of ticks in voices.</p>
<div class='highlight'><pre> createTickContexts(voices) {
<span class="hljs-keyword">const</span> contexts = createContexts(
voices,
TickContext,
(tickable, context, voiceIndex) => context.addTickable(tickable, voiceIndex)
);
contexts.array.forEach(<span class="hljs-function"><span class="hljs-params">context</span> =></span> {
context.tContexts = contexts.array;
});
<span class="hljs-keyword">this</span>.totalTicks = voices[<span class="hljs-number">0</span>].getTicksUsed().clone();
<span class="hljs-keyword">this</span>.tickContexts = contexts;
<span class="hljs-keyword">return</span> contexts;
}</pre></div>
<p>This is the core formatter logic. Format voices and justify them
to <code>justifyWidth</code> pixels. <code>renderingContext</code> is required to justify elements
that can’t retreive widths without a canvas. This method sets the <code>x</code> positions
of all the tickables/notes in the formatter.</p>
<div class='highlight'><pre> preFormat(justifyWidth = <span class="hljs-number">0</span>, renderingContext, voices, stave) {</pre></div>
<p>Initialize context maps.</p>
<div class='highlight'><pre> <span class="hljs-keyword">const</span> contexts = <span class="hljs-keyword">this</span>.tickContexts;
<span class="hljs-keyword">const</span> { <span class="hljs-attr">list</span>: contextList, <span class="hljs-attr">map</span>: contextMap } = contexts;</pre></div>
<p>Reset loss history for evaluator.</p>
<div class='highlight'><pre> <span class="hljs-keyword">this</span>.lossHistory = [];</pre></div>
<p>If voices and a stave were provided, set the Stave for each voice
and preFormat to apply Y values to the notes;</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> (voices && stave) {
voices.forEach(<span class="hljs-function"><span class="hljs-params">voice</span> =></span> voice.setStave(stave).preFormat());
}</pre></div>
<p>Now distribute the ticks to each tick context, and assign them their
own X positions.</p>
<div class='highlight'><pre> <span class="hljs-keyword">let</span> x = <span class="hljs-number">0</span>;
<span class="hljs-keyword">let</span> shift = <span class="hljs-number">0</span>;
<span class="hljs-keyword">this</span>.minTotalWidth = <span class="hljs-number">0</span>;</pre></div>
<p>Pass 1: Give each note maximum width requested by context.</p>
<div class='highlight'><pre> contextList.forEach(<span class="hljs-function">(<span class="hljs-params">tick</span>) =></span> {
<span class="hljs-keyword">const</span> context = contextMap[tick];
<span class="hljs-keyword">if</span> (renderingContext) context.setContext(renderingContext);</pre></div>
<p>Make sure that all tickables in this context have calculated their
space requirements.</p>
<div class='highlight'><pre> context.preFormat();
<span class="hljs-keyword">const</span> width = context.getWidth();
<span class="hljs-keyword">this</span>.minTotalWidth += width;
<span class="hljs-keyword">const</span> metrics = context.getMetrics();
x = x + shift + metrics.totalLeftPx;
context.setX(x);</pre></div>