UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature

978 lines (711 loc) 38.3 kB
<!DOCTYPE html> <html> <head> <title>articulation.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>articulation.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: Larry Kuhns.</p> <h2 id="description">Description</h2> <p>This file implements articulations and accents as modifiers that can be attached to notes. The complete list of articulations is available in <code>tables.js</code> under <code>Vex.Flow.articulationCodes</code>.</p> <p>See <code>tests/articulation_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> { 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>; <span class="hljs-keyword">import</span> { Stem } <span class="hljs-keyword">from</span> <span class="hljs-string">'./stem'</span>;</pre></div> <p>To enable logging for this class. Set <code>Vex.Flow.Articulation.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> (Articulation.DEBUG) Vex.L(<span class="hljs-string">'Vex.Flow.Articulation'</span>, args); } <span class="hljs-keyword">const</span> { ABOVE, BELOW } = Modifier.Position; <span class="hljs-keyword">const</span> roundToNearestHalf = <span class="hljs-function">(<span class="hljs-params">mathFn, value</span>) =&gt;</span> mathFn(value / <span class="hljs-number">0.5</span>) * <span class="hljs-number">0.5</span>;</pre></div> <p>This includes both staff and ledger lines</p> <div class='highlight'><pre><span class="hljs-keyword">const</span> isWithinLines = <span class="hljs-function">(<span class="hljs-params">line, position</span>) =&gt;</span> position === ABOVE ? line &lt;= <span class="hljs-number">5</span> : line &gt;= <span class="hljs-number">1</span>; <span class="hljs-keyword">const</span> getRoundingFunction = <span class="hljs-function">(<span class="hljs-params">line, position</span>) =&gt;</span> { <span class="hljs-keyword">if</span> (isWithinLines(line, position)) { <span class="hljs-keyword">if</span> (position === ABOVE) { <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.ceil; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.floor; } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.round; } }; <span class="hljs-keyword">const</span> snapLineToStaff = <span class="hljs-function">(<span class="hljs-params">canSitBetweenLines, line, position, offsetDirection</span>) =&gt;</span> {</pre></div> <p>Initially, snap to nearest staff line or space</p> <div class='highlight'><pre> <span class="hljs-keyword">const</span> snappedLine = roundToNearestHalf(getRoundingFunction(line, position), line); <span class="hljs-keyword">const</span> canSnapToStaffSpace = canSitBetweenLines &amp;&amp; isWithinLines(snappedLine, position); <span class="hljs-keyword">const</span> onStaffLine = snappedLine % <span class="hljs-number">1</span> === <span class="hljs-number">0</span>; <span class="hljs-keyword">if</span> (canSnapToStaffSpace &amp;&amp; onStaffLine) { <span class="hljs-keyword">const</span> HALF_STAFF_SPACE = <span class="hljs-number">0.5</span>; <span class="hljs-keyword">return</span> snappedLine + (HALF_STAFF_SPACE * -offsetDirection); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> snappedLine; } }; <span class="hljs-keyword">const</span> isStaveNote = <span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> { <span class="hljs-keyword">const</span> noteCategory = note.getCategory(); <span class="hljs-keyword">return</span> noteCategory === <span class="hljs-string">'stavenotes'</span> || noteCategory === <span class="hljs-string">'gracenotes'</span>; }; <span class="hljs-keyword">const</span> getTopY = <span class="hljs-function">(<span class="hljs-params">note, textLine</span>) =&gt;</span> { <span class="hljs-keyword">const</span> stave = note.getStave(); <span class="hljs-keyword">const</span> stemDirection = note.getStemDirection(); <span class="hljs-keyword">const</span> { <span class="hljs-attr">topY</span>: stemTipY, <span class="hljs-attr">baseY</span>: stemBaseY } = note.getStemExtents(); <span class="hljs-keyword">if</span> (isStaveNote(note)) { <span class="hljs-keyword">if</span> (note.hasStem()) { <span class="hljs-keyword">if</span> (stemDirection === Stem.UP) { <span class="hljs-keyword">return</span> stemTipY; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stemBaseY; } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.min(...note.getYs()); } } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (note.getCategory() === <span class="hljs-string">'tabnotes'</span>) { <span class="hljs-keyword">if</span> (note.hasStem()) { <span class="hljs-keyword">if</span> (stemDirection === Stem.UP) { <span class="hljs-keyword">return</span> stemTipY; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stave.getYForTopText(textLine); } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stave.getYForTopText(textLine); } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR( <span class="hljs-string">'UnknownCategory'</span>, <span class="hljs-string">'Only can get the top and bottom ys of stavenotes and tabnotes'</span> ); } }; <span class="hljs-keyword">const</span> getBottomY = <span class="hljs-function">(<span class="hljs-params">note, textLine</span>) =&gt;</span> { <span class="hljs-keyword">const</span> stave = note.getStave(); <span class="hljs-keyword">const</span> stemDirection = note.getStemDirection(); <span class="hljs-keyword">const</span> { <span class="hljs-attr">topY</span>: stemTipY, <span class="hljs-attr">baseY</span>: stemBaseY } = note.getStemExtents(); <span class="hljs-keyword">if</span> (isStaveNote(note)) { <span class="hljs-keyword">if</span> (note.hasStem()) { <span class="hljs-keyword">if</span> (stemDirection === Stem.UP) { <span class="hljs-keyword">return</span> stemBaseY; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stemTipY; } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-built_in">Math</span>.max(...note.getYs()); } } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (note.getCategory() === <span class="hljs-string">'tabnotes'</span>) { <span class="hljs-keyword">if</span> (note.hasStem()) { <span class="hljs-keyword">if</span> (stemDirection === Stem.UP) { <span class="hljs-keyword">return</span> stave.getYForBottomText(textLine); } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stemTipY; } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> stave.getYForBottomText(textLine); } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR( <span class="hljs-string">'UnknownCategory'</span>, <span class="hljs-string">'Only can get the top and bottom ys of stavenotes and tabnotes'</span> ); } };</pre></div> <p>Gets the initial offset of the articulation from the y value of the starting position. This is required because the top/bottom text positions already have spacing applied to provide a “visually pleasent” default position. However the y values provided from the stavenote’s top/bottom do <em>not</em> have any pre-applied spacing. This function normalizes this asymmetry.</p> <div class='highlight'><pre><span class="hljs-keyword">const</span> getInitialOffset = <span class="hljs-function">(<span class="hljs-params">note, position</span>) =&gt;</span> { <span class="hljs-keyword">const</span> isOnStemTip = ( (position === ABOVE &amp;&amp; note.getStemDirection() === Stem.UP) || (position === BELOW &amp;&amp; note.getStemDirection() === Stem.DOWN) ); <span class="hljs-keyword">if</span> (isStaveNote(note)) { <span class="hljs-keyword">if</span> (note.hasStem() &amp;&amp; isOnStemTip) { <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span>; } <span class="hljs-keyword">else</span> {</pre></div> <p>this amount is larger than the stem-tip offset because we start from the center of the notehead</p> <div class='highlight'><pre> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>; } } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">if</span> (note.hasStem() &amp;&amp; isOnStemTip) { <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>; } <span class="hljs-keyword">else</span> { <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } } }; <span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Articulation</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">'articulations'</span>; } <span class="hljs-keyword">static</span> get INITIAL_OFFSET() { <span class="hljs-keyword">return</span> <span class="hljs-number">-0.5</span>; }</pre></div> <p>FIXME: Most of the complex formatting logic (ie: snapping to space) is actually done in .render(). But that logic belongs in this method.</p> <p>Unfortunately, this isn’t possible because, by this point, stem lengths have not yet been finalized. Finalized stem lengths are required to determine the initial position of any stem-side articulation.</p> <p>This indicates that all objects should have their stave set before being formatted. It can’t be an optional if you want accurate vertical positioning. Consistently positioned articulations that play nice with other modifiers won’t be possible until we stop relying on render-time formatting.</p> <p>Ideally, when this function has completed, the vertical articulation positions should be ready to render without further adjustment. But the current state is far from this ideal.</p> <div class='highlight'><pre> <span class="hljs-keyword">static</span> format(articulations, state) { <span class="hljs-keyword">if</span> (!articulations || articulations.length === <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-keyword">const</span> isAbove = <span class="hljs-function"><span class="hljs-params">artic</span> =&gt;</span> artic.getPosition() === ABOVE; <span class="hljs-keyword">const</span> isBelow = <span class="hljs-function"><span class="hljs-params">artic</span> =&gt;</span> artic.getPosition() === BELOW; <span class="hljs-keyword">const</span> margin = <span class="hljs-number">0.5</span>; <span class="hljs-keyword">const</span> getIncrement = <span class="hljs-function">(<span class="hljs-params">articulation, line, position</span>) =&gt;</span> roundToNearestHalf( getRoundingFunction(line, position), (articulation.glyph.getMetrics().height / <span class="hljs-number">10</span>) + margin ); articulations .filter(isAbove) .forEach(<span class="hljs-function"><span class="hljs-params">articulation</span> =&gt;</span> { articulation.setTextLine(state.top_text_line); state.top_text_line += getIncrement(articulation, state.top_text_line, ABOVE); }); articulations .filter(isBelow) .forEach(<span class="hljs-function"><span class="hljs-params">articulation</span> =&gt;</span> { articulation.setTextLine(state.text_line); state.text_line += getIncrement(articulation, state.text_line, BELOW); }); <span class="hljs-keyword">const</span> width = articulations .map(<span class="hljs-function"><span class="hljs-params">articulation</span> =&gt;</span> articulation.getWidth()) .reduce(<span class="hljs-function">(<span class="hljs-params">maxWidth, articWidth</span>) =&gt;</span> <span class="hljs-built_in">Math</span>.max(articWidth, maxWidth)); state.left_shift += width / <span class="hljs-number">2</span>; state.right_shift += width / <span class="hljs-number">2</span>; <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; } <span class="hljs-keyword">static</span> easyScoreHook({ articulations }, note, builder) { <span class="hljs-keyword">if</span> (!articulations) <span class="hljs-keyword">return</span>; <span class="hljs-keyword">const</span> articNameToCode = { <span class="hljs-attr">staccato</span>: <span class="hljs-string">'a.'</span>, <span class="hljs-attr">tenuto</span>: <span class="hljs-string">'a-'</span>, }; articulations .split(<span class="hljs-string">','</span>) .map(<span class="hljs-function"><span class="hljs-params">articString</span> =&gt;</span> articString.trim().split(<span class="hljs-string">'.'</span>)) .map(<span class="hljs-function">(<span class="hljs-params">[name, position]</span>) =&gt;</span> { <span class="hljs-keyword">const</span> artic = { <span class="hljs-attr">type</span>: articNameToCode[name] }; <span class="hljs-keyword">if</span> (position) artic.position = Modifier.PositionString[position]; <span class="hljs-keyword">return</span> builder.getFactory().Articulation(artic); }) .map(<span class="hljs-function"><span class="hljs-params">artic</span> =&gt;</span> note.addModifier(<span class="hljs-number">0</span>, artic)); }</pre></div> <p>Create a new articulation of type <code>type</code>, which is an entry in <code>Vex.Flow.articulationCodes</code> in <code>tables.js</code>.</p> <div class='highlight'><pre> <span class="hljs-keyword">constructor</span>(type) { <span class="hljs-keyword">super</span>(); <span class="hljs-keyword">this</span>.setAttribute(<span class="hljs-string">'type'</span>, <span class="hljs-string">'Articulation'</span>); <span class="hljs-keyword">this</span>.note = <span class="hljs-literal">null</span>; <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 = BELOW; <span class="hljs-keyword">this</span>.render_options = { <span class="hljs-attr">font_scale</span>: <span class="hljs-number">38</span>, }; <span class="hljs-keyword">this</span>.reset(); } reset() { <span class="hljs-keyword">this</span>.articulation = Flow.articulationCodes(<span class="hljs-keyword">this</span>.type); <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">this</span>.articulation) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(<span class="hljs-string">'ArgumentError'</span>, <span class="hljs-string">`Articulation not found: <span class="hljs-subst">${<span class="hljs-keyword">this</span>.type}</span>`</span>); } <span class="hljs-keyword">const</span> code = (<span class="hljs-keyword">this</span>.position === ABOVE ? <span class="hljs-keyword">this</span>.articulation.aboveCode : <span class="hljs-keyword">this</span>.articulation.belowCode) || <span class="hljs-keyword">this</span>.articulation.code; <span class="hljs-keyword">this</span>.glyph = <span class="hljs-keyword">new</span> Glyph(code, <span class="hljs-keyword">this</span>.render_options.font_scale); <span class="hljs-keyword">this</span>.setWidth(<span class="hljs-keyword">this</span>.glyph.getMetrics().width); } getCategory() { <span class="hljs-keyword">return</span> Articulation.CATEGORY; }</pre></div> <p>Render articulation in position next to note.</p> <div class='highlight'><pre> draw() { <span class="hljs-keyword">const</span> { note, index, position, glyph, <span class="hljs-attr">articulation</span>: { <span class="hljs-attr">between_lines</span>: canSitBetweenLines }, <span class="hljs-attr">text_line</span>: textLine, <span class="hljs-attr">context</span>: ctx, } = <span class="hljs-keyword">this</span>; <span class="hljs-keyword">this</span>.checkContext(); <span class="hljs-keyword">if</span> (!note || index == <span class="hljs-literal">null</span>) { <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> Vex.RERR(<span class="hljs-string">'NoAttachedNote'</span>, <span class="hljs-string">"Can't draw Articulation without a note and index."</span>); } <span class="hljs-keyword">this</span>.setRendered(); <span class="hljs-keyword">const</span> stave = note.getStave(); <span class="hljs-keyword">const</span> staffSpace = stave.getSpacingBetweenLines(); <span class="hljs-keyword">const</span> isTab = note.getCategory() === <span class="hljs-string">'tabnotes'</span>;</pre></div> <p>Articulations are centered over/under the note head.</p> <div class='highlight'><pre> <span class="hljs-keyword">const</span> { x } = note.getModifierStartXY(position, index); <span class="hljs-keyword">const</span> shouldSitOutsideStaff = !canSitBetweenLines || isTab; <span class="hljs-keyword">const</span> initialOffset = getInitialOffset(note, position); <span class="hljs-keyword">const</span> padding = <span class="hljs-keyword">this</span>.musicFont.lookupMetric(<span class="hljs-string">`articulation.<span class="hljs-subst">${glyph.getCode()}</span>.padding`</span>, <span class="hljs-number">0</span>); <span class="hljs-keyword">let</span> y = { [ABOVE]: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> { glyph.setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">1</span>); <span class="hljs-keyword">const</span> y = getTopY(note, textLine) - ((textLine + initialOffset) * staffSpace); <span class="hljs-keyword">return</span> shouldSitOutsideStaff ? <span class="hljs-built_in">Math</span>.min(stave.getYForTopText(Articulation.INITIAL_OFFSET), y) : y; }, [BELOW]: <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> { glyph.setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0</span>); <span class="hljs-keyword">const</span> y = getBottomY(note, textLine) + ((textLine + initialOffset) * staffSpace); <span class="hljs-keyword">return</span> shouldSitOutsideStaff ? <span class="hljs-built_in">Math</span>.max(stave.getYForBottomText(Articulation.INITIAL_OFFSET), y) : y; }, }[position](); <span class="hljs-keyword">if</span> (!isTab) { <span class="hljs-keyword">const</span> offsetDirection = position === ABOVE ? <span class="hljs-number">-1</span> : +<span class="hljs-number">1</span>; <span class="hljs-keyword">const</span> noteLine = isTab ? note.positions[index].str : note.getKeyProps()[index].line; <span class="hljs-keyword">const</span> distanceFromNote = (note.getYs()[index] - y) / staffSpace; <span class="hljs-keyword">const</span> articLine = distanceFromNote + noteLine; <span class="hljs-keyword">const</span> snappedLine = snapLineToStaff(canSitBetweenLines, articLine, position, offsetDirection); <span class="hljs-keyword">if</span> (isWithinLines(snappedLine, position)) glyph.setOrigin(<span class="hljs-number">0.5</span>, <span class="hljs-number">0.5</span>); y += <span class="hljs-built_in">Math</span>.abs(snappedLine - articLine) * staffSpace * offsetDirection + (padding * offsetDirection); } L(<span class="hljs-string">`Rendering articulation at (x: <span class="hljs-subst">${x}</span>, y: <span class="hljs-subst">${y}</span>)`</span>); glyph.render(ctx, x, y); } }</pre></div> <div class="fleur">h</div> </div> </div> </body> </html>