@fretfall/player 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/player.js CHANGED
@@ -1 +1 @@
1
- import{tempoMap as gn,tickToMs as yt,msToTick as wt,tuningName as bt,tuningReference as mn,noteName as kt,markRepeats as yn,annotate as wn,lyricsShown as bn,markArpeggios as kn}from"./music.js";import{suggestPositions as $t,fingerFor as $n}from"./fingering.js";import{LOOKS as vt,COLORS as ke,FONTS as Mt,STRINGS as Re,DEFAULT_STYLE as xt,theme as vn}from"./themes.js";import{drawHighway as Mn,drawTab as xn,lookAhead as St,HEADSTOCKS as Ne,headstockParts as Sn,spline as Tn}from"./highway.js";import{onsetEnvelope as Ln,align as An}from"./sync.js";import{startProfiler as Cn,startFrame as Pn,lap as Tt,endFrame as En,benchTime as On}from"./perf.js";const a=e=>document.getElementById(e),Lt="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.8.4/dist/",Bn={4:[28,33,38,43],5:[23,28,33,38,43],6:[40,45,50,55,59,64],7:[35,40,45,50,55,59,64]},Fn='<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path d="M6.5 4.2v11.6L16 10z"/></svg>',Dn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M7.5 4.5v11M12.5 4.5v11"/></svg>',Rn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5v-4h4M12.5 3.5h4v4M16.5 12.5v4h-4M7.5 16.5h-4v-4"/></svg>',Nn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 3.5v4h-4M16.5 7.5h-4v-4M12.5 16.5v-4h4M3.5 12.5h4v4"/></svg>',je=(e,t)=>{e.querySelector("svg")?.remove(),e.insertAdjacentHTML("afterbegin",t)},He=e=>`${e<0?"-":""}${Math.floor(Math.abs(e)/60)}:${String(Math.floor(Math.abs(e)%60)).padStart(2,"0")}`,At=()=>new Promise(e=>{requestAnimationFrame(()=>setTimeout(e)),setTimeout(e,100)}),Ct={gradient:{label:"Gradient"},box:{label:"Box"}},Pt={frame:{label:"Frame only"},grey:{label:"Greyed marks"},hide:{label:"No marks"}},Et={scroll:{label:"Scroll"},pages:{label:"Pages"}},Ot={black:{label:"Black"},white:{label:"White"}},Bt={low:{label:"Low E on top"},high:{label:"High e on top"}},jn=7,Ft={viewAngle:30,sideAngle:5,noteSpeed:1,drawDistance:1,fretWidth:1,boardHeight:1},c={...xt,volume:.8,volumeMuted:!1,frames:"gradient",repeatMarks:"frame",markings:"black",stringOrder:"low",headstock:"inline",offset:0,mute:!1,autoplay:!1,metronome:!1,minimal:!1,tabView:!1,tabLayout:"scroll",guides:!0,fretNumbers:!0,songIntro:!0,noteLines:.5,notes3d:!0,...Ft,viewDefaults:jn,panel:"view"},Hn={...c};try{const e=JSON.parse(localStorage.getItem("fretfall")??"{}"),t=e.viewDefaults??0;if(t<4)for(const n of Object.keys(Ft))delete e[n];t<5&&delete e.repeatMarks,t<6&&delete e.sideAngle,t<7&&delete e.fretNumbers,delete e.viewDefaults,typeof e.noteLines!="number"&&delete e.noteLines,Object.assign(c,e)}catch{}for(const[e,t,n]of[["look",vt],["colors",ke],["fonts",Mt],["strings",Re],["frames",Ct,"gradient"],["repeatMarks",Pt,"frame"],["markings",Ot,"black"],["tabLayout",Et,"scroll"],["stringOrder",Bt,"low"],["headstock",Ne,"inline"]])t[c[e]]||(c[e]=n??xt[e]);const U=()=>{try{localStorage.setItem("fretfall",JSON.stringify(c))}catch{}},ue=()=>c.volumeMuted?0:c.volume;let C;function oe(){C={...vn(c),guides:c.guides,fretNumbers:c.fretNumbers,noteLines:c.noteLines,notes3d:c.notes3d,frames:c.frames,repeatMarks:c.repeatMarks,markings:c.markings,tabLayout:c.tabLayout,stringOrder:c.stringOrder,headstock:c.headstock,viewAngle:c.viewAngle,sideAngle:c.sideAngle,noteSpeed:c.noteSpeed,drawDistance:c.drawDistance,fretWidth:c.fretWidth,boardHeight:c.boardHeight,fill:c.minimal},document.documentElement.classList.toggle("minimal",c.minimal),document.documentElement.classList.toggle("tab2d",c.tabView),a("fonts").href=C.href;const e={bg:C.bg,ink:C.ink,text:C.text,muted:C.muted,accent:C.accent,chip:C.chip,"chip-border":C.chipBorder,done:C.phraseDone,todo:C.phraseTodo,ui:C.ui,num:C.num,lyric:C.lyric,"lyric-style":C.lyricStyle};Object.assign(e,{lane:C.anchorLane},Object.fromEntries(C.str.map((t,n)=>[`s${n}`,t])));for(const[t,n]of Object.entries(e))document.documentElement.style.setProperty(`--${t}`,n);for(const t of document.querySelectorAll("[data-setting]"))for(const n of t.children)n.setAttribute("aria-pressed",String(n.value===c[t.dataset.setting])),n.dots&&n.dots.replaceChildren(...Vn(t.dataset.setting,n.value).map(o=>Object.assign(document.createElement("i"),{style:`background: ${o}`})))}const In=e=>{const t=Ne[e],n=Array.from({length:6},(v,h)=>.83*(1-2*h/5)),{outline:o,ends:r,keys:l}=Sn(t,n),i=v=>(96-v*12).toFixed(1),u=v=>(28-v*12).toFixed(1),d=v=>`M${v.map(([h,L])=>`${i(h)} ${u(L)}`).join("L")}`;return`<svg class="headstock" viewBox="8 -2 114 60" preserveAspectRatio="xMinYMid meet" aria-hidden="true" fill="currentColor" stroke="currentColor">${[...l.map(({u:v,side:h,edge:L})=>`<ellipse cx="${i(v)}" cy="${u(L+h*.36)}" rx="1.5" ry="2.6" fill-opacity=".35" stroke="none"/>`),`<path d="${d(o)}Z" fill-opacity=".1" stroke-width="1.2"/>`,t.cover&&`<path d="${d(Tn(t.cover))}Z" fill="none" stroke-width=".6"/>`,`<path d="${d([[0,1],[-2.1,1]])} ${d([[0,-1],[-2.1,-1]])}" fill="none" stroke-width="1.2"/>`,`<path d="${d([[0,1.05],[0,-1.05]])}" stroke-width="2.4"/>`,...n.map((v,h)=>`<path d="${d([r[h],[0,v],[-2.1,v]])}" fill="none" stroke-width=".7"/>`),...r.map(([v,h])=>`<circle cx="${i(v)}" cy="${u(h)}" r="1.1" stroke="none"/>`)].filter(Boolean).join("")}</svg>`},Vn=(e,t)=>{if(e==="strings")return(Re[t].str??ke[c.colors].str).slice(0,6);const n=ke[t];return[n.floor0,n.lane,n.accent,n.text]};for(const e of document.querySelectorAll("[data-setting]")){const t=e.dataset.setting,n={look:vt,colors:ke,fonts:Mt,strings:Re,frames:Ct,repeatMarks:Pt,markings:Ot,tabLayout:Et,stringOrder:Bt,headstock:Ne}[t];for(const[o,{label:r}]of Object.entries(n)){const l=Object.assign(document.createElement("button"),{value:o,textContent:r});t==="stringOrder"?l.prepend(Object.assign(document.createElement("span"),{className:"order",textContent:o==="low"?"E A D G B e":"e B G D A E"})):t==="headstock"?l.insertAdjacentHTML("afterbegin",In(o)):e.classList.contains("cards")&&l.prepend(l.dots=Object.assign(document.createElement("span"),{className:"dots",ariaHidden:"true"})),l.onclick=()=>{c[t]=o,U(),oe()},e.append(l)}}const $e=e=>e.style.setProperty("--fill",`${(e.value-e.min)/(e.max-e.min)*100}%`),Ie={viewAngle:e=>`${e}°`,sideAngle:e=>e?`${Math.abs(e)}° ${e<0?"left":"right"}`:"Straight",noteSpeed:e=>`${Math.round(e*100)}%`,drawDistance:()=>`${St(c).toFixed(1)} s`,fretWidth:e=>`${Math.round(e*100)}%`,boardHeight:e=>`${Math.round(e*100)}%`,noteLines:e=>`${Math.round(e*100)}%`},Dt=()=>{for(const[e,t]of Object.entries(Ie))a(`${e}Out`).value=t(c[e])},Rt=e=>{for(const[t,n]of Object.entries(e))C&&(C[t]=n),c[t]=a(t).value=n,$e(a(t));Dt(),U()};for(const e in Ie)a(e).value=c[e],$e(a(e)),a(e).oninput=t=>Rt({[e]:+t.target.value});Dt(),a("resetSettings").onclick=()=>{const{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l}=c;Object.assign(c,Hn,{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l});for(const i of["guides","fretNumbers","notes3d","songIntro","mute","autoplay"])a(i).checked=c[i];f?.track&&M.changeTrackMute([f.track],c.mute),ve(c.offset),oe(),Rt(Object.fromEntries(Object.keys(Ie).map(i=>[i,c[i]])))};const ve=e=>{c.offset=a("offset").value=e,a("offsetOut").value=`${e>0?"+":""}${e} ms`,$e(a("offset")),U()};ve(c.offset),a("offset").oninput=e=>ve(+e.target.value),a("resetOffset").onclick=()=>ve(0);const qn=e=>`<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5h3L10.5 4v12l-4-3.5h-3z"/>${e}</svg>`;function Me(e=c.volume,t=c.volumeMuted){Object.assign(c,{volume:e,volumeMuted:t||e===0}),a("volume").value=ue(),a("volumeOut").value=`${Math.round(ue()*100)}%`,$e(a("volume"));const n=qn(c.volumeMuted?'<path d="M13.5 8l4 4M17.5 8l-4 4"/>':e<.5?'<path d="M13.5 8a3 3 0 0 1 0 4"/>':'<path d="M13.5 8a3 3 0 0 1 0 4M15.5 5.5a6.5 6.5 0 0 1 0 9"/>');je(a("volumeButton"),n),a("volumeMute").innerHTML=n,a("volumeButton").classList.toggle("muted",c.volumeMuted),a("volumeMute").setAttribute("aria-pressed",String(c.volumeMuted)),a("volumeMute").setAttribute("aria-label",c.volumeMuted?"Unmute":"Mute"),g?.setVolume(ue()),U()}const Ve=(e=a("volumePanel").hidden)=>{a("volumePanel").hidden=!e,a("volumeButton").setAttribute("aria-expanded",String(e))};a("volume").oninput=e=>Me(+e.target.value,!1),a("volumeButton").onclick=()=>Ve(),a("volumeMute").onclick=()=>Me(c.volume||.8,!c.volumeMuted);for(const e of["guides","fretNumbers","notes3d","songIntro"])a(e).checked=c[e],a(e).onchange=t=>{c[e]=t.target.checked,U(),oe()};a("autoplay").checked=c.autoplay,a("autoplay").onchange=e=>{c.autoplay=e.target.checked,U()},a("mute").checked=c.mute,a("mute").onchange=e=>{c.mute=e.target.checked,U(),f?.track&&M.changeTrackMute([f.track],c.mute)};const qe=e=>{for(const t of document.querySelectorAll("[data-tab]")){const n=t.dataset.tab===e;t.setAttribute("aria-selected",String(n)),t.tabIndex=n?0:-1,a(`pane-${t.dataset.tab}`).hidden=!n}c.panel=e,U()};for(const e of document.querySelectorAll("[data-tab]"))e.onclick=()=>qe(e.dataset.tab),e.onkeydown=t=>{const n=[...document.querySelectorAll("[data-tab]")],o={ArrowRight:1,ArrowLeft:-1}[t.key];if(!o)return;const r=n[(n.indexOf(e)+o+n.length)%n.length];qe(r.dataset.tab),r.focus()};qe(document.querySelector(`[data-tab="${c.panel}"]`)?c.panel:"view");const Nt={settings:"settingsButton",legend:"legendButton"};function te(e,t=a(e).hidden){for(const[n,o]of Object.entries(Nt)){const r=n===e&&t,l=a(n).hidden===r;a(n).hidden=!r,a(o).setAttribute("aria-expanded",String(r)),l&&ce("sheet",{name:n,open:r})}}for(const[e,t]of Object.entries(Nt))a(t).onclick=()=>te(e),a(`${e}Close`).onclick=()=>{te(e,!1),a(t).focus()};const Gn=matchMedia("(max-width: 900px)"),jt=matchMedia("(max-width: 560px)"),pe=(e=a("menuButton").ariaExpanded!=="true")=>a("menuButton").setAttribute("aria-expanded",String(e)),Ht=()=>{jt.matches?document.querySelector(".speed").before(a("arrangements")):document.querySelector(".wordmark").after(a("arrangements")),pe(!1)};Gn.onchange=jt.onchange=Ht,Ht(),a("menuButton").onclick=()=>pe(),a("menu").onclick=e=>{e.target.closest("button")&&pe(!1)},addEventListener("pointerdown",e=>{e.target.closest(".sheet, #settingsButton, #legendButton")||te("settings",!1),e.target.closest(".volume")||Ve(!1),e.target.closest("#menu, #menuButton")||pe(!1)});const s=e=>`<svg viewBox="0 0 56 32" aria-hidden="true">${e}</svg>`,x=(e,t,n=24,o=12,r=1,l="")=>`<rect x="${e}" y="${t}" width="${n}" height="${o}" class="f${r}"/><rect x="${e}" y="${t}" width="${n}" height="${o/4}" class="fw" opacity=".35"/>${l?`<text x="${e+n/2}" y="${t+o/2+.5}" class="fi" font-size="${o*.75}">${l}</text>`:""}`,O=(e,t=13,n="ft",o="")=>`<text x="28" y="16.5" class="${n}" font-size="${t}" ${o}>${e}</text>`,fe=(e,t=11)=>`${x(16,17,24,11)}<text x="28" y="9" class="ft" font-size="${t}">${e}</text>`,Wn=[["Notes and chords",[["Note","Play this string at this fret when its white line on the floor reaches the fretboard. The number on it is the finger to use (T = thumb).",s(`${x(16,8,24,12,1,"1")}<path d="M16 27h24" class="lw" stroke-width="2" opacity=".6"/>`)],["Fret number","The fret to play, on the floor under the note.",s(`${x(20,3,16,7)}<path d="M28 10v8" class="l1" stroke-width="1.5"/><text x="28" y="25" class="f1" font-size="11">7</text>`)],["Open string","Play the string without fretting it. The bar spans your hand position; held, it becomes a lane that wide.",s('<rect x="6" y="11" width="44" height="5" class="f3"/><path d="M6 25h44" class="lw" stroke-width="2" opacity=".6"/>')],["Played again","The same chord as just before: strum it again on this beat. It shows as its frame alone, or as outlines with its marks greyed out or hidden (Settings). Single notes always show in full.",s('<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/><path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>')],["Chord","Play these notes together. The white line marks the beat; the chord name shows when it changes. Held or played again, its edges run on as rails until the shape changes.",s(`<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/>${x(14,16,12,5,0)}${x(30,10,12,5,1)}<path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>`)],["Accented chord","Strum it harder: the top corners of its frame shine white, bolder for a heavy accent.",s(`<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".3"/><path d="M6 12V4h12M38 4h12v8" class="lw" stroke-width="3" fill="none" stroke-linecap="square"/>${x(14,16,12,5,0)}${x(30,10,12,5,1)}<path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>`)],["Arpeggio","Hold the chord shape and play its strings one at a time: the shape's rails run as long as you hold it, every note rings on as a dashed trail, and the board outlines every spot of the shape.",s(`<path d="M8 30V6M48 30V6" class="ll" stroke-width="2.5"/><path d="M8 6h40" class="ll" stroke-width="1" opacity=".6"/>${x(14,20,12,5,1)}<path d="M20 20V8" class="l1" stroke-width="1.6" stroke-dasharray="3 2"/>${x(32,12,12,5,3)}`)],["Chord name","The chord you are playing, big, to the right of your hand. It shows up a second before the chord.",s(O("G5",16))],["Barre","Lay one finger across several strings at this fret. ½B is a half barre.",s('<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/><path d="M24 8v16" class="lt" stroke-width="4" opacity=".8"/><text x="36" y="10" class="ft" font-size="9">B</text>')],["Hand position","Where your index finger sits. A new band with a number means move your hand there.",s('<rect x="16" y="2" width="34" height="28" class="fl" opacity=".2"/><path d="M16 2v28M50 2v28" class="ll" stroke-width="1.5"/><text x="9" y="17" class="fl" font-size="10">5</text>')],["Where to press","The spot on the fretboard lights up as the note comes close and fills in when you play it.",s('<path d="M2 16h52" class="l2" stroke-width="2"/><rect x="18" y="10" width="20" height="12" class="f2" opacity=".35"/><rect x="18" y="10" width="20" height="12" class="l2" stroke-width="1.6"/>')],["String number","Play it on this string (1 is the high e).",s('<circle cx="28" cy="16" r="9" class="l2" stroke-width="1.5"/><text x="28" y="16.5" class="f2" font-size="10">3</text>')]]],["Picking hand",[["Palm mute","Rest the side of your picking hand on the strings near the bridge: a big dark X across the note.",s(`${x(16,10,24,12,0)}<path d="M18 11l20 10M18 21l20-10" class="li" stroke-width="2" opacity=".85"/>`)],["Fret-hand mute","Touch the string with your fretting hand without pressing it down, for a percussive click: a small white X on the note.",s(`${x(16,10,24,12,0)}<path d="M24.5 13l7 6M24.5 19l7-6" class="li" stroke-width="3.5" opacity=".7"/><path d="M24.5 13l7 6M24.5 19l7-6" class="lw" stroke-width="1.8"/>`)],["Down and up strokes","Pick down (⊓) or up (V).",s('<path d="M10 22V10h12v12" class="lt" stroke-width="1.8"/><path d="M34 10l6 12 6-12" class="lt" stroke-width="1.8"/>')],["Picking-hand fingers","p thumb, i index, m middle, a ring, c little finger.",s(O("p i m a",12,"ft",'font-style="italic"'))],["Strum","Strum the chord in the direction of the arrow, where a Guitar Pro file marks it.",s('<path d="M28 5v22M23 21l5 6 5-6" class="lt" stroke-width="1.8"/>')],["Rolled chord","Roll the chord: sound the strings one after another, quickly.",s('<path d="M28 4q4 3 0 5.5t0 5.5 0 5.5 0 5.5M23 21l5 6 5-6" class="lt" stroke-width="1.6"/>')],["Tremolo picking","Pick the note as fast as you can for its whole length.",s(`${x(16,20,24,9)}<path d="M22 12l12-4M22 16l12-4M22 8l12-4" class="lt" stroke-width="1.6"/>`)],["Rasgueado","Flamenco strum: flick the fingers out one after another in the pattern shown.",s(O("rasg.",12))],["Slap and pop","Bass: hit the string with your thumb, or pull it away with a finger and let it snap back.",s(O("slap",12))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(O("golpe",12))]]],["Fretting hand and legato",[["Hammer-on","Sound the note by hammering a finger down, without picking: a white triangle on the note, pointing down.",s(`${x(16,14,24,12)}<path d="M22 7h12l-6 10z" class="fw li" stroke-width="1.2"/>`)],["Pull-off","Pull your finger off the string to sound the lower note: a white triangle on the note, pointing up.",s(`${x(16,14,24,12)}<path d="M22 17h12l-6-10z" class="fw li" stroke-width="1.2"/>`)],["Tie","Hold the note on into the next one without playing it again.",s(`${x(4,19,16,8)}${x(36,19,16,8)}<path d="M12 18Q28 4 44 18" class="lt" stroke-width="1.4" stroke-dasharray="3 3"/>`)],["Slide","Slide to the fret where the trail ends. A dashed end means slide off without a clear target. From an open string, an arch runs from its bar to the fret you land on.",s(`${x(4,20,16,8)}<path d="M12 20L40 8" class="l1" stroke-width="3" opacity=".6"/><rect x="32" y="3" width="16" height="7" class="l1" stroke-width="1.5"/>`)],["Slide in and out","Slide into the note from below or above, or off it at the end.",s(`<path d="M6 27L22 17" class="l1" stroke-width="2" stroke-dasharray="3 3"/>${x(22,10,22,12)}`)],["Tapping","Tap the note with a finger of your picking hand: an arrow in its colour on the note, pointing down. m.g. marks a tap with the fretting hand.",s(`${x(16,17,24,11)}<path d="M20 4L28 16L36 4L28 8.5Z" class="f1" opacity=".45"/><path d="M20 4L28 16L36 4L28 8.5Z" class="l1" stroke-width="1.6"/>`)],["Grace note","A quick note played just before the beat.",s(`${x(22,12,12,7,2)}<path d="M18 24L38 7" class="lt" stroke-width="1.4"/>`)],["Trill","Alternate quickly between the note and the fret shown.",s(O("tr 7",13,"ft",'font-style="italic"'))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(O("turn",12))]]],["Sustain and pitch",[["Sustain","Keep the note ringing for as long as its trail.",s(`<path d="M24 20L26 2h4l2 18z" class="f1" opacity=".5"/>${x(16,18,24,10)}`)],["Let ring","Let the note ring on over the notes after it.",s(`<path d="M28 18V2" class="l1" stroke-width="2" stroke-dasharray="3 3"/>${x(16,18,24,10)}`)],["Vibrato","Shake the note. A taller wave means a wider vibrato.",s(`${x(16,19,24,9)}<path d="M13 9q2.5-4 5 0t5 0 5 0 5 0 5 0 5 0" class="lt" stroke-width="1.6"/>`)],["Bend","Bend the string up by the amount shown: ½ is one fret higher, full is two. The chevrons on the note point up; its trail rises where the bend happens; on the fretboard the string bends up while you hold it.",s(`${x(6,21,20,8)}<path d="M10 16l6-4 6 4M10 11l6-4 6 4" class="lt" stroke-width="1.6"/><text x="40" y="11" class="ft" font-size="9">full</text>`)],["Bend and release","Bend up, then let the string back down: chevrons up, then down, and the trail rises and comes back down.",s(`${x(4,21,20,8)}<path d="M8 16l6-4 6 4M24 8l6 4 6-4" class="lt" stroke-width="1.6"/><text x="46" y="11" class="ft" font-size="9">full</text>`)],["Pre-bend","Bend the string before you pick it, then play.",s(O("pre ½",12))],["Whammy bar","Push or pull the whammy bar; the trail dips and rises with it.",s('<text x="28" y="8" class="ft" font-size="9">w/bar</text><path d="M6 20q11 12 22 0t22 0" class="l1" stroke-width="2"/>')]]],["Harmonics",[["Natural harmonic","Touch the string lightly right above the fret wire, without pressing, and pick.",s('<path d="M20 4l7 7-7 7-7-7z" class="f4"/><text x="40" y="12" class="f4" font-size="10">&lt;12&gt;</text>')],["Pinch harmonic","Catch the string with the side of your thumb just after the pick.",s(fe("PH",10))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(fe("AH",10))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(fe("TH",10))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(fe("SH",10))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(fe("&gt;",12))],["Staccato","Short: stop the note right after you play it.",s(`${x(16,17,24,11)}<circle cx="28" cy="9" r="2.2" class="ft"/>`)],["Ghost note","Barely audible: felt more than heard.",s(O("(7)",14,"f1"))],["Dynamics","How loud, from very soft (pp) to very loud (ff). Shown where it changes.",s(O("mf",15,"ft",'font-style="italic"'))],["Crescendo and diminuendo","Get louder along an opening wedge, softer along a closing one. It shows left of the neck where it starts, beside the bar markings.",s('<path d="M8 16L48 8M8 16l40 8" class="lt" stroke-width="1.6"/>')],["Swell and fade","Fade the note in with the volume knob, or fade it out.",s(O("swell",12))],["Wah","Wah pedal open (o) or closed (+).",s(O("o +",14))],["Fermata","Hold the note longer than written.",s('<path d="M16 22a12 12 0 0 1 24 0" class="lt" stroke-width="1.8"/><circle cx="28" cy="19" r="2.2" class="ft"/>')]]],["On the floor",[["Tempo, time and key","From this bar on, a new tempo, time signature, key or swing feel.",s(O("♩ = 120",11,"fa"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(O("repeat ×2",10,"fa"))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(O("D.S. al coda",8,"fa"))],["8va and loco","Play an octave higher, until loco.",s(O("8va",13,"fa"))],["Text","Instructions written in the file, like pizz. or bass stop.",s(O("pizz.",13,"fa"))]]]],w=(...e)=>e.map(t=>`<path d="M2 ${t}h52" class="lt" stroke-width=".8" opacity=".3"/>`).join(""),P=(e,t,n,o=8,r="fi",l="")=>`<text x="${e}" y="${t+.5}" class="${r}" font-size="${o}" ${l}>${n}</text>`,m=(e,t,n,o,r,l="",i=e+Math.min(n,12)/2)=>`<rect x="${e}" y="${t-5}" width="${n}" height="10" rx="2" class="f${o}" ${l}/>${r?P(i,t,r):""}`,ge=(e,t,n='class="lt" stroke-width="1.5" opacity=".7"')=>`<path d="M14 ${e}h-3v${t-e}h3" ${n}/>`,B=(e,t=9,n="")=>`${w(23)}${m(22,23,12,1,"5")}${P(28,10,e,t,"ft",n)}`,me=(e,t=8)=>`<path d="M8 12v18" class="lt" stroke-width="1" opacity=".4"/>${P(12,6,"5",7,"fm")}${P(34,6,e,t,"fa")}`,Un=[["Notes and chords",[["Note","Play this string at this fret when the start of its bar reaches the play line. The small number beside the fret is the finger to use (T = thumb).",s(`${w(16)}${m(10,16,36,1,"7")}${P(22,16.5,"1",5.5,"fi",'opacity=".7"')}`)],["Play line","Play each note as it reaches the line. Scrolling, played notes dim and fade out to the left; in pages (Settings), the page holds still and the line moves across it.",s(`${w(16)}${m(4,16,12,1,"3",'opacity=".4"')}<path d="M21 3v26" class="la" stroke-width="2.5"/>${m(26,16,24,1,"5")}`)],["Open string","Play the string without fretting it.",s(`${w(16)}${m(22,16,12,3,"0")}`)],["Played again","The same chord as just before: strum it again on this beat. Its notes show as outlines, their marks greyed out.",s(`${w(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="l1" stroke-width="1.5"/>${P(28,16,"5",8,"f1")}`)],["Chord","Play these notes together: a bracket runs down the side of them.",s(`${w(9,23)}${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Accented chord","Strum it harder: its bracket is white, and thicker for a heavy accent.",s(`${w(9,23)}${ge(3,29,'class="lw" stroke-width="2.5"')}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Arpeggio","Hold the chord shape and play its strings one at a time, for as long as the dashed outline runs. Its name is over it.",s(`${w(10,22)}<rect x="5" y="3" width="46" height="26" rx="4" class="lt" stroke-width="1" stroke-dasharray="3 2" opacity=".55"/>${m(9,22,10,2,"2")}${m(24,10,10,1,"1")}${m(38,22,10,2,"2")}`)],["Chord name","The chord you are playing, over the strings where it starts. It shows when the chord changes.",s(P(28,16,"G5",16,"ft"))],["Barre","Lay one finger across several strings at the fret shown. ½B is a half barre.",s(`${w(14,25)}${ge(9,30)}${P(21,4.5,"½B5",7,"ft")}${m(16,14,14,1,"5")}${m(16,25,14,2,"5")}`)],["Hand position","Move your hand so your index finger is at this fret. Under the tab, where the hand moves (Hand position guides in Settings).",s(`${w(8)}<path d="M14 13v12" class="ll" stroke-width="1.5"/>${P(31,22,"fret 5",9,"fl")}`)],["String number","Play it on this string (1 is the high e).",s(`${w(16)}<circle cx="15" cy="16" r="6.5" class="l2" stroke-width="1.2"/>${P(15,16,"3",7,"f2")}${m(26,16,16,2,"5")}`)],["Bar number","Each bar line is numbered along the top of the tab.",s(`${w(20)}<path d="M20 9v22" class="lt" stroke-width="1" opacity=".5"/>${P(28,5,"12",8,"fm")}`)]]],["Picking hand",[["Palm mute","Rest the side of your picking hand on the strings near the bridge: PM over the note.",s(B("PM",8))],["Fret-hand mute","Touch the string with your fretting hand without pressing it down, for a percussive click: a grey bar with an ×.",s(`${w(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="ft" opacity=".22"/>${P(28,16,"×",10,"ft")}`)],["Down and up strokes","Pick down (⊓) or up (V).",s(`${w(23)}${m(8,23,12,1,"5")}${m(36,23,12,1,"5")}<path d="M10 14V6h8v8" class="lt" stroke-width="1.4"/><path d="M38 6l4 8 4-8" class="lt" stroke-width="1.4"/>`)],["Picking-hand fingers","p thumb, i index, m middle, a ring, c little finger.",s(B("i",10,'font-style="italic"'))],["Strum","Strum the chord in the direction of the arrow beside it, where a Guitar Pro file marks it.",s(`${w(9,23)}<path d="M5 4v22M2.5 22.5l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.4"/>${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Rolled chord","Roll the chord: sound the strings one after another, quickly. The arrow beside it is wavy.",s(`${w(9,23)}<path d="M5 4q2 2 0 4.5t0 4.5 0 4.5 0 4.5 0 4M2.5 22.5l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.3"/>${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Tremolo picking","Pick the note as fast as you can for its whole length: three slashes over it.",s(`${w(24)}${m(22,24,24,1,"5")}<path d="M23 16l10-3M23 11.5l10-3M23 7l10-3" class="lt" stroke-width="1.4"/>`)],["Rasgueado","Flamenco strum: flick the fingers out one after another in the pattern shown.",s(B("rasg.",8))],["Slap and pop","Bass: hit the string with your thumb, or pull it away with a finger and let it snap back.",s(B("slap",8))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(B("golpe",8))]]],["Fretting hand and legato",[["Hammer-on","Sound the note by hammering a finger down, without picking: an arc from the note before, with H on it.",s(`${w(23)}${m(4,23,12,1,"5")}${m(38,23,12,1,"7")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${P(27,7,"H",8,"ft")}`)],["Pull-off","Pull your finger off the string to sound the lower note: an arc from the note before, with P on it.",s(`${w(23)}${m(4,23,12,1,"7")}${m(38,23,12,1,"5")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${P(27,7,"P",8,"ft")}`)],["Tie","Hold the note on into the next one without playing it again: a dashed arc under them.",s(`${w(12)}${m(4,12,12,1,"5")}${m(38,12,12,1,"5")}<path d="M10 19Q27 30 44 19" class="lt" stroke-width="1.3" stroke-dasharray="3 2"/>`)],["Slide","Slide to the next note, up or down the way the slash after the bar points. A dashed slash means slide off without a clear target.",s(`${w(16)}${m(4,16,22,1,"5")}<path d="M29 20l8-8" class="lt" stroke-width="1.6"/>${m(40,16,12,1,"7")}`)],["Slide in and out","Slide into the note from below or above (the slash before it), or off it at the end (the slash after).",s(`${w(16)}<path d="M8 20l8-8" class="lt" stroke-width="1.6"/>${m(20,16,16,1,"5")}<path d="M40 12l8 8" class="lt" stroke-width="1.6"/>`)],["Tapping","Tap the note with a finger of your picking hand: T over it. m.g. marks a tap with the fretting hand.",s(B("T"))],["Grace note","A quick note played just before the beat: a smaller note.",s(`${w(16)}<rect x="8" y="12.4" width="9" height="7.2" rx="1.5" class="f2"/>${P(12.5,16,"5",6)}${m(22,16,24,2,"7")}`)],["Trill","Alternate quickly between the note and the fret shown.",s(B("tr 7",8))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(B("turn",8))]]],["Sustain and pitch",[["Sustain","Keep the note ringing for as long as its bar.",s(`${w(16)}${m(6,16,44,1,"5")}`)],["Let ring","Let the note ring on over the notes after it: its bar runs on faint and dashed.",s(`${w(16)}<rect x="6" y="11" width="44" height="10" rx="2" class="f1" opacity=".3"/><path d="M22 16h24" class="l1" stroke-width="1.5" stroke-dasharray="3 3"/>${m(6,16,12,1,"5")}`)],["Vibrato","Shake the note for as long as the wave over its bar. A taller wave means a wider vibrato.",s(`${w(22)}${m(6,22,44,1,"5")}<path d="M8 12q2.5-3 5 0t5 0 5 0 5 0 5 0 5 0 5 0 5 0" class="lt" stroke-width="1.4"/>`)],["Bend","Bend the string up by the amount shown: ½ is one fret higher, full is two. The arrow rises from the note's number.",s(`${w(23)}${m(6,23,34,1,"7")}<path d="M14 18q4-1 5-12" class="lt" stroke-width="1.4"/><path d="M16.5 9l2.5-3.5 2 3.8" class="lt" stroke-width="1.4"/>${P(36,8,"full",8,"ft")}`)],["Bend and release","Bend up, then let the string back down: an arrow up, then one down.",s(`${w(23)}${m(4,23,40,1,"7")}<path d="M12 18q4-1 5-12M17 6q6 2 6 12" class="lt" stroke-width="1.4"/><path d="M14.5 9l2.5-3.5 2 3.8M20.5 15l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.4"/>${P(40,8,"full",8,"ft")}`)],["Pre-bend","Bend the string before you pick it, then play: a straight arrow up.",s(`${w(23)}${m(6,23,34,1,"7")}<path d="M16 18V5M13.5 8l2.5-3 2.5 3" class="lt" stroke-width="1.4"/>${P(36,8,"pre ½",8,"ft")}`)],["Whammy bar","Push or pull the whammy bar.",s(B("w/bar",8))]]],["Harmonics",[["Natural harmonic","Touch the string lightly right above the fret wire, without pressing, and pick: the fret in angle brackets.",s(`${w(16)}${m(12,16,32,4,"&lt;12&gt;","",28)}`)],["Pinch harmonic","Catch the string with the side of your thumb just after the pick.",s(B("PH",8))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(B("AH",8))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(B("TH",8))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(B("SH",8))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(B("&gt;",11))],["Staccato","Short: stop the note right after you play it: a dot over it.",s(`${w(23)}${m(22,23,12,1,"5")}<circle cx="28" cy="12" r="1.8" class="ft"/>`)],["Ghost note","Barely audible: felt more than heard. Faint, in brackets.",s(`${w(16)}${m(16,16,24,1,"(7)",'opacity=".55"',28)}`)],["Dynamics","How loud, from very soft (pp) to very loud (ff), under the tab where it changes.",s(`${w(8)}${P(28,21,"mf",14,"ft",'font-style="italic"')}`)],["Crescendo and diminuendo","Get louder along an opening wedge, softer along a closing one, under the tab.",s(`${w(8)}<path d="M48 15L8 21L48 27" class="lt" stroke-width="1.5"/>`)],["Swell and fade","Fade the note in with the volume knob, or fade it out.",s(B("swell",8))],["Wah","Wah pedal open (o) or closed (+).",s(B("o +",9))],["Fermata","Hold the note longer than written.",s(`${w(24)}${m(22,24,12,1,"5")}<path d="M21 15a7 7 0 0 1 14 0" class="lt" stroke-width="1.5"/><circle cx="28" cy="13.5" r="1.5" class="ft"/>`)]]],["Along the top",[["Tempo, time and key","From this bar on, a new tempo, time signature, key or swing feel, beside the bar's number.",s(me("♩ = 120"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(me("repeat ×2",7))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(me("D.S. al coda",6))],["8va and loco","Play an octave higher, until loco.",s(me("8va",9))],["Text","Instructions written in the file, like pizz. or bass stop.",s(me("pizz.",9))]]]],It=e=>e.flatMap(([t,n])=>[Object.assign(document.createElement("h3"),{textContent:t}),...n.map(([o,r,l])=>{const i=Object.assign(document.createElement("div"),{className:"legend-item",innerHTML:l});return i.append(Object.assign(document.createElement("div"),{innerHTML:"<b></b><span></span>"})),i.querySelector("b").textContent=o,i.querySelector("span").textContent=r,i})]);a("legendFind").oninput=e=>{const t=e.target.value.trim().toLowerCase();let n=null,o=0,r=0;const l=()=>n&&(n.hidden=!r);for(const i of a("legendList").children){if(i.tagName==="H3"){l(),[n,r]=[i,0];continue}i.classList.contains("legend-item")&&(i.hidden=!!t&&!i.textContent.toLowerCase().includes(t),i.hidden||(r++,o++))}l(),a("legendList").lastElementChild.hidden=o>0};let Vt=()=>[];const qt=()=>{a("legendList").replaceChildren(...It(c.tabView?Un:Wn),...Vt(),Object.assign(document.createElement("p"),{className:"legend-empty",textContent:"Nothing matches.",hidden:!0})),a("legendFind").oninput({target:a("legendFind")})};oe();let Gt;function T(e,t=!1){clearTimeout(Gt),a("statusText").textContent=e??"",a("status").hidden=!e,a("status").classList.toggle("loading",t),e&&!t&&(Gt=setTimeout(()=>a("status").hidden=!0,5e3))}let b=null,f=null,g=null,R=1,S=null,Wt=!1;const ye=new URLSearchParams(location.search).get("band"),I=ye!==null;function Ut(e,{offset:t=0,ratio:n=1}={}){const o=new Audio(URL.createObjectURL(e));return o.preservesPitch=!0,o.onplay=o.onpause=Se,o.onended=()=>ce("ended"),{get time(){return(o.currentTime-t)/n},get playing(){return!o.paused},play:()=>o.play(),pause:()=>o.pause(),seek:r=>{o.currentTime=Math.max(0,t+n*r)},setSpeed:r=>{o.playbackRate=r},setVolume:r=>{o.volume=r},stop:()=>{o.pause(),URL.revokeObjectURL(o.src)}}}const M=new alphaTab.AlphaTabApi(a("tab"),{core:{fontDirectory:Lt+"font/"},display:{staveProfile:"Tab"},player:{playerMode:I?"Disabled":"EnabledAutomatic",soundFont:Lt+"soundfont/sonivox.sf2",enableCursor:!1,enableUserInteraction:!1}}),k={tick:0,at:0,playing:!1,heard:!1,tempo:[],paused:null};M.playerPositionChanged.on(e=>{k.paused===null&&([k.tick,k.at]=[e.currentTick,performance.now()],e.isSeek||(k.heard=!0))}),M.playerStateChanged.on(e=>{k.playing=e.state===alphaTab.synth.PlayerState.Playing,k.heard=!1,k.at=performance.now(),Se()}),M.playerFinished.on(()=>ce("ended")),M.soundFontLoad.on(e=>T(`Loading instrument sounds ${Math.round(e.loaded/e.total*100)}%`,!0)),M.playerReady.on(()=>T(null)),M.error.on(e=>T(`Could not read that file: ${e.message}`));const we={get time(){return k.tempo.length?(yt(k.tempo,k.tick)+(k.playing&&k.heard&&k.paused===null?Math.min(performance.now()-k.at,100)*M.playbackSpeed:0))/1e3:0},get playing(){return k.playing},play:()=>(k.paused!==null&&(M.tickPosition=k.paused),k.paused=null,M.play()),pause:()=>{k.tick=k.paused=wt(k.tempo,we.time*1e3),k.at=performance.now(),M.pause()},seek:e=>{k.tick=wt(k.tempo,Math.max(0,e)*1e3),k.at=performance.now(),k.heard=!1,k.paused!==null&&(k.paused=k.tick),M.tickPosition=k.tick},setSpeed:e=>{M.playbackSpeed=e},setVolume:e=>{M.masterVolume=e},stop:()=>{k.paused=null,M.stop()}};function xe(e){g&&g!==e&&g.stop(),g=e,g.setSpeed(R),g.setVolume(ue()),Se()}function Se(){je(a("play"),g?.playing?Dn:Fn),a("play").dataset.label=a("play").ariaLabel=g?.playing?"Pause":"Play",!!g?.playing!==Wt&&ce("playing",{playing:Wt=!!g?.playing})}const z=alphaTab.model,Ge=(e="")=>e.replace(/([a-z])([A-Z0-9])/g,"$1 $2").toLowerCase(),zt=e=>e?.length?e.map(t=>[t.offset/60,t.value/4]):null,zn=[null,"natural","artificial","pinch","tap","semi","feedback"],Kt=[null,"accent","heavy","tenuto"],Kn=[null,"inverted turn","turn","upper mordent","lower mordent"],_t=["15ma","8va",null,"8vb","15mb"],_n={major:["C♭","G♭","D♭","A♭","E♭","B♭","F","C","G","D","A","E","B","F♯","C♯"],minor:["A♭","E♭","B♭","F","C","G","D","A","E","B","F♯","C♯","G♯","D♯","A♯"]},Xn=e=>z.Direction[e].replace(/^(Target|Jump)/,"").replace("DaCapo","D.C.").replace("DalSegnoSegno","D.S.S.").replace("DalSegno","D.S.").replace(/^DaDoubleCoda$/,"To double coda").replace(/^DaCoda$/,"To coda").replace("SegnoSegno","Segno segno").replace("DoubleCoda","Double coda").replace(/Al(.*)$/," al $1").replace(" al Double"," al double");let We=null;function Jn(e,t){const n=gn(t.masterBars.flatMap(i=>i.tempoChanges),e.tempo),o=i=>yt(n,i)/1e3,r=e.tracks.filter(i=>i.staves[0].isStringed&&!i.staves[0].isPercussion).map(i=>{const u=i.staves[0],d=[],v=[],h=[],L=[],he=[],at=[],Pe=[],ot={},st=[],rt=[],it={};let lt="4/4",ct="0:0",Ee=e.tempo,ht=0,Oe=null,Y=null;for(const A of t.masterBars){const $=A.masterBar,de=u.bars[$.index],Be=o(A.start),ut=o(A.end),pn=(A.end-A.start)/$.timeSignatureNumerator;for(let E=0;E<$.timeSignatureNumerator;E++)he.push({time:o(A.start+E*pn),measure:E?-1:$.index+1});$.section&&at.push({time:Be,name:$.section.text});const H=(E,Fe=Be)=>st.push({time:Fe,text:E}),fn=it[$.index]=(it[$.index]??0)+1,pt=`${$.timeSignatureNumerator}/${$.timeSignatureDenominator}`;pt!==lt&&H(lt=pt);const ft=`${de.keySignature}:${de.keySignatureType}`,gt=de.keySignatureType?"minor":"major";ft!==ct&&(H(`${_n[gt][de.keySignature+7]} ${gt}`),ct=ft);for(const E of A.tempoChanges)Math.round(E.tempo)!==Math.round(Ee)&&(Ee=E.tempo,H(`♩ = ${Math.round(Ee)}`,o(E.tick)));$.tripletFeel!==ht&&(H($.tripletFeel?`${Ge(z.TripletFeel[$.tripletFeel])} feel`:"straight"),ht=$.tripletFeel),$.isRepeatStart&&H(`repeat · pass ${fn}`),$.repeatCount>0&&H(`repeat ×${$.repeatCount}`,ut),$.alternateEndings&&H(`ending ${[0,1,2,3,4,5,6,7].filter(E=>$.alternateEndings&1<<E).map(E=>E+1).join(", ")}`);for(const E of $.directions??[])H(Xn(E));$.isFreeTime&&H("free time");const mt=[];de.voices.forEach((E,Fe)=>{for(const y of E.beats){const G=o(A.start+y.playbackStart),Z=o(A.start+y.playbackStart+y.playbackDuration);if(!Fe){y.text&&H(y.text,G),_t[y.ottava]!==Oe&&(Oe=_t[y.ottava],H(Oe??"loco",G));const p=[null,"cresc","dim"][y.crescendo];Y&&Y.kind!==p&&(Y=null),p&&!Y&&rt.push(Y={time:G,endTime:Z,kind:p}),Y&&(Y.endTime=Z)}let De=null;if(y.notes.length>1){const p=Array(u.tuning.length).fill(-1),Q=[...p];for(const D of y.notes)[p[D.string-1],Q[D.string-1]]=[D.fret,Math.max(-1,D.leftHandFinger)];const W=Pe.at(-1),ee=!!W&&W.frets.join()===p.join()&&Math.abs(W.endTime-G)<.01,ae=y.barreShape?y.notes.filter(D=>D.fret===y.barreFret).map(D=>D.string-1):[];De=v.push({time:G,name:y.chord?.name??"",notes:[],frets:p,fingers:Q,highDensity:ee,palmMute:y.notes.every(D=>D.isPalmMute),fretHandMute:y.notes.every(D=>D.isDead),accent:Kt[Math.max(...y.notes.map(D=>D.accentuated===z.AccentuationType.Tenuto?0:D.accentuated))],strum:[null,"up","down",null,null][y.brushType]??[null,"up","down"][y.pickStroke]??null,roll:[null,null,null,"up","down"][y.brushType]??null,barre:ae.length?{fret:y.barreFret,from:Math.min(...ae),to:Math.max(...ae),half:y.barreShape===z.BarreShape.Half}:void 0})-1,ee?W.endTime=Z:Pe.push({startTime:G,endTime:Z,name:v[De].name,frets:p,fingers:Q})}for(const p of y.notes){const Q=p.isTieDestination&&ot[p.string];if(Q){Q.sustain=Z-Q.time;continue}const W=p.hammerPullOrigin,ee=zn[p.harmonicType],ae={time:G,sustain:p.slideTarget||p.hasBend||Z-G>=.4?Z-G:0,string:p.string-1,fret:p.fret,chord:De,hammerOn:!!W&&p.fret>W.fret,pullOff:!!W&&p.fret<W.fret,slideTo:p.slideTarget&&(p.fret||p.slideTarget.fret)?p.slideTarget.fret:null,slideUnpitchTo:null,slideIn:[null,"below","above"][p.slideInType]??null,slideOut:[null,null,null,"up","down","down","up"][p.slideOutType]??null,bend:p.hasBend?(p.maxBendPoint?.value??0)/4:0,bendCurve:p.hasBend?zt(p.bendPoints):null,whammy:zt(y.whammyBarPoints),harmonic:!!ee&&ee!=="pinch",harmonicPinch:ee==="pinch",harmonicType:ee,palmMute:p.isPalmMute,mute:p.isDead||y.deadSlapped,ghost:p.isGhost,staccato:p.isStaccato,letRing:p.isLetRing,accent:Kt[p.accentuated]??null,dynamic:Ge(z.DynamicValue[p.dynamics]),vibrato:p.vibrato>0||y.vibrato>0,vibratoWide:p.vibrato===z.VibratoType.Wide||y.vibrato===z.VibratoType.Wide,trill:p.trillValue>=0?p.trillValue:null,ornament:Kn[p.ornament]??null,grace:y.graceType!==z.GraceType.None,tremolo:y.isTremolo,tap:y.tap,tapLeft:p.isLeftHandTapped,slap:y.slap,pop:y.pop,golpe:[null,"thumb","finger"][y.golpe]??null,rasgueado:y.rasgueado?Ge(z.Rasgueado[y.rasgueado]):null,fade:[null,"fade in","fade out","swell"][y.fade]??null,wah:[null,"open","closed"][y.wahPedal]??null,fermata:!!y.fermata,pick:y.notes.length>1?null:[null,"up","down"][y.pickStroke]??null,showString:p.showStringNumber,finger:p.leftHandFinger>=0?p.leftHandFinger:null,rightFinger:"pimac"[p.rightHandFinger]??null};d.push(ot[p.string]=ae),mt.push(ae)}}}),L.push({time:Be,endTime:ut,name:"",maxDifficulty:mt.length})}d.sort((A,$)=>A.time-$.time||A.string-$.string),d.forEach((A,$)=>A.chord!==null&&v[A.chord].notes.push($));const dt=$t(d);h.push(...dt.anchors);for(const A of d)A.finger??=dt.fingers.get(A)??null;for(const A of v)for(const $ of A.notes)A.fingers[d[$].string]<0&&d[$].finger!==null&&(A.fingers[d[$].string]=d[$].finger);return{name:(i.name||`Track ${i.index+1}`).replace(/\s+-\s+.*$/,""),program:i.playbackInfo.program,strings:u.tuning.length,open:[...u.tuning].reverse(),notes:d,chords:v,anchors:h,phrases:L,sections:at,beats:he,handShapes:Pe,markers:st,hairpins:rt,track:i}}),l=[];for(const i of e.tracks){for(const u of t.masterBars)for(const d of i.staves[0].bars[u.masterBar.index].voices[0].beats){if(!d.lyrics?.[0])continue;const v=o(u.start+d.playbackStart);l.push({time:v,length:o(u.start+d.playbackStart+d.playbackDuration)-v,text:d.lyrics[0]})}if(l.length)break}return{title:e.title,artist:e.artist,bpm:e.tempo,length:o(t.masterBars.at(-1)?.end??0),arrangements:r,lyrics:l,tempo:n}}function Yn(e){const t=[];let n=[];for(const{time:o,length:r,text:l}of e){n.length&&o-n.at(-1).end>2&&(t.push(n),n=[]);const i=l.replace(/\+$/,""),u=i.endsWith("-");n.push({time:o,end:o+r,text:u?i.slice(0,-1):`${i} `});const d=!u&&n.reduce((v,h)=>v+h.text.length,0)>34;(l.endsWith("+")||d)&&(t.push(n),n=[])}return n.length&&t.push(n),t.map(o=>({time:o[0].time,end:o.at(-1).end,syllables:o}))}function Ue(e){for(const r of e.arrangements)yn(r.notes,r.chords),wn(r.notes,r.chords),kn(r.notes,r.handShapes??[]);b={...e,lines:Yn(e.lyrics)},I||(S=null),le&&V.postMessage({song:cn()}),document.title=`${b.title||"Untitled"} – Fretfall`;const t=b.arrangements;if(a("band").disabled=t.length<2,t.length>4){const r=Object.assign(document.createElement("select"),{ariaLabel:"Part"});r.append(...t.map((l,i)=>new Option(l.name,i))),r.onchange=()=>se(+r.value),a("arrangements").replaceChildren(r)}else a("arrangements").replaceChildren(...t.map((r,l)=>{const i=Object.assign(document.createElement("button"),{textContent:r.name});return i.onclick=()=>se(l),i}));const n=t.filter(r=>r.program===void 0||r.program>=24&&r.program<=31),o=n.length?t.indexOf(n.reduce((r,l)=>l.notes.length>r.notes.length?l:r)):0;t.length?se(t[0].program===void 0?0:o):(f=null,T("No guitar or bass parts in this file")),ce("song",{file:Te,title:b.title,artist:b.artist,parts:t.map(r=>r.name),part:f?t.indexOf(f):-1}),Te=null}let N=[],K={};const j=(e,t,...n)=>{const o=Object.assign(document.createElement(e),t);return o.append(...n),o};function Zn(e,t){const n=mn(t);if(!n)return e;const o=n.shift.filter(Boolean),r=i=>`${Math.abs(i)} semitone${Math.abs(i)===1?"":"s"} ${i<0?"lower":"higher"}`,l=o.length?o.length===t.length&&o.every(i=>i===o[0])?`${r(o[0])} than ${n.reference}`:o.length===1&&n.shift[0]?`The lowest string ${r(n.shift[0])} than in ${n.reference}`:`${o.length} of ${t.length} strings retuned from ${n.reference}`:"Every string at its usual pitch";return e.classList.add("tuning"),e.tabIndex=0,e.setAttribute("aria-describedby","tuningTip"),e.append(j("span",{className:"tip",id:"tuningTip",role:"tooltip"},j("b",{textContent:bt(t)}),l,j("span",{className:"pegs",ariaHidden:"true"},...t.map((i,u)=>j("span",{className:n.shift[u]?"peg":"peg same",style:`--c: var(--s${u}); --d: ${Math.min(12,Math.abs(n.shift[u]))}`},j("i",{textContent:kt(n.open[u])}),j("i",{className:"move",textContent:n.shift[u]?`${n.shift[u]<0?"−":"+"}${Math.abs(n.shift[u])}`:"0"}),j("strong",{textContent:kt(i)})))),j("small",{textContent:`${n.reference} on top, this song underneath, lowest string first`}))),e}function se(e){f?.track&&M.changeTrackMute([f.track],!1),f=b.arrangements[e],f.track&&M.changeTrackMute([f.track],c.mute);for(const h in et)delete et[h];const t=a("arrangements");t.firstElementChild?.tagName==="SELECT"?t.firstElementChild.value=e:[...t.children].forEach((h,L)=>h.setAttribute("aria-pressed",String(e===L)));const n=[["Tuning",f.open?.length?bt(f.open):null],["Tempo",b.bpm?`${Math.round(b.bpm)} bpm`:null]].filter(([,h])=>h);a("info").replaceChildren(j("span",{},j("b",{textContent:b.title||"Untitled"}),b.artist?` ${b.artist}`:""),...n.map(([h,L])=>{const he=j("span",{},`${h} `,j("b",{textContent:L}));return h==="Tuning"?Zn(he,f.open):he})),a("introTitle").textContent=b.title||"Untitled",a("introFacts").textContent=[b.artist,f.name,...n.map(([,h])=>h)].filter(Boolean).join(" · ");const o={},r={},l=f.sections.map(h=>h.name.replace(/[_-]+/g," ").toUpperCase());l.forEach(h=>o[h]=(o[h]??0)+1),f.sectionLabels=l.map(h=>o[h]>1?`${h} ${r[h]=(r[h]??0)+1}`:h);const i=Math.max(1,...f.phrases.map(h=>h.maxDifficulty)),u=h=>`${Math.min(100,Math.max(0,h/b.length*100)).toFixed(2)}%`;N=f.phrases.map(h=>{const L=document.createElement("div");return L.style.left=u(h.time),L.style.width=`calc(${u(h.endTime)} - ${u(h.time)} - 3px)`,L.style.height=`${Math.max(8,h.maxDifficulty/i*100)}%`,L}),a("phrases").replaceChildren(...N,a("loopBox"),a("phraseTime")),q();const d=f.phrases.map(h=>f.notes.filter(L=>L.time>=h.time&&L.time<h.endTime).length/Math.max(.5,h.endTime-h.time)),v=Math.max(1e-6,...d);a("progress").style.background=`linear-gradient(90deg, ${f.phrases.map((h,L)=>`color-mix(in srgb, var(--accent) ${Math.round(8+62*(d[L]/v))}%, var(--todo)) ${u(h.time)} ${u(h.endTime)}`).join(", ")})`,K={}}const Xt=e=>{const t=(n,o)=>!!e&&f.phrases[o].time>=e.start&&f.phrases[o].endTime<=e.end;return[N.findIndex(t),N.findLastIndex(t)]};function q(e=S){const[t,n]=Xt(e),o=a("loopBox");o.hidden=t<0,t>=0&&Object.assign(o.style,{left:`${N[t].offsetLeft-3}px`,width:`${N[n].offsetLeft+N[n].offsetWidth-N[t].offsetLeft+6}px`}),a("loop").setAttribute("aria-pressed",String(!!e)),I&&e===S&&JSON.stringify(S)!==JSON.stringify(_.loop)&&V.postMessage({do:"loop",loop:S}),Qe()}addEventListener("resize",()=>q());let re=null;const Jt=e=>Math.min(1,Math.max(0,(e-re.left)/re.width))*b.length;a("phrases").onpointerenter=()=>re=a("phrases").getBoundingClientRect(),a("phrases").onpointerleave=()=>a("phraseTime").hidden=!0;const Yt=e=>{const t=N.findIndex(n=>e<n.getBoundingClientRect().right);return t<0?N.length-1:t};let F=null;a("phrases").onpointerdown=e=>{if(!N.length||e.button!==0)return;const t=Yt(e.clientX),[n,o]=Xt(S),r=a("loopBox").getBoundingClientRect(),l=n<0?t:Math.abs(e.clientX-r.left)<6?o:Math.abs(e.clientX-r.right)<6?n:t;F={at:t,from:l,x:e.clientX,range:null},a("phrases").setPointerCapture(e.pointerId)},a("phrases").onpointermove=e=>{if(b&&re&&(Object.assign(a("phraseTime"),{hidden:!1,textContent:He(Jt(e.clientX))}),a("phraseTime").style.left=`${Math.min(re.width,Math.max(0,e.clientX-re.left))}px`),!F||!F.range&&Math.abs(e.clientX-F.x)<4)return;const t=Yt(e.clientX);F.range={start:f.phrases[Math.min(F.from,t)].time,end:f.phrases[Math.max(F.from,t)].endTime},q(F.range)},a("phrases").onpointerup=e=>{if(!F)return;const t=f.phrases[F.at];F.range?(S=F.range,g?.seek(S.start)):F.from===F.at&&(S?(S=e.shiftKey?{start:Math.min(S.start,t.time),end:Math.max(S.end,t.endTime)}:{start:t.time,end:t.endTime},g?.seek(t.time)):g?.seek(Jt(e.clientX))),F=null,q()},a("phrases").onpointercancel=a("phrases").onlostpointercapture=()=>{F=null,q()};const ze=/\.(mp3|m4a|aac|ogg|oga|wav|flac)$/i,Ke="Opening song…";let Zt=Promise.resolve(),Qt=()=>{},Te=null;const _e=[];async function Xe(e,t){if(I)return T("Open songs in the window the band started from");for(const n of[...e].sort((o,r)=>ze.test(o.name)-ze.test(r.name)))await Qn(n,t)}async function Qn(e,t={}){if(ze.test(e.name))return ea(e);Te=e.name;const n=_e.find(o=>o.extensions.some(r=>e.name.toLowerCase().endsWith(r)));if(!n){const o=await e.arrayBuffer();return xe(we),k.paused=null,We=M.tickCache,Zt=new Promise(r=>Qt=r),T(Ke,!0),M.load(new Uint8Array(o))}T(Ke,!0),await At();try{const{song:o,audio:r}=await n.open(e,{...t,status:l=>T(l,!0)});for(const l of o.arrangements){l.open=Array.from({length:l.strings},(u,d)=>(Bn[l.strings]?.[d]??40)+(l.tuning[d]??0)),l.anchors.length||(l.anchors=$t(l.notes).anchors);let i=0;for(const u of l.notes){for(;l.anchors[i+1]?.time<=u.time;)i++;const d=u.chord===null?-1:l.chords[u.chord].fingers[u.string]??-1;u.fret>0&&(u.finger??=d>=0?d:l.anchors[i]?$n(u.fret,l.anchors[i].fret):null)}}xe(Ut(r)),Ue(o),T(null),Ze()}catch(o){Te=null,T(`Could not open ${e.name}: ${o.message}`)}}async function ea(e){if(await Zt,!b)return T("Open the tab first, then its recording");T("Lining the recording up with the tab…",!0),await At();try{const t=await new OfflineAudioContext(1,1,11025).decodeAudioData(await e.arrayBuffer()),n=new Float32Array(t.length);for(let l=0;l<t.numberOfChannels;l++)t.getChannelData(l).forEach((i,u)=>n[u]+=i/t.numberOfChannels);const o=[...new Set(b.arrangements.flatMap(l=>l.notes.map(i=>Math.round(i.time*100)/100)))].sort((l,i)=>l-i),r=An(Ln(n,t.sampleRate),o);xe(Ut(e,r)),T(r.confidence>=2?`Playing the recording: the tab starts ${r.offset.toFixed(1)} s in`:"Could not line the recording up with the tab. Nudge it with Audio delay in settings.")}catch(t){T(`Could not use ${e.name}: ${t.message}`)}}a("open").onclick=()=>a("file").click(),a("file").onchange=e=>{Xe(e.target.files),e.target.value=""};let Le=0;addEventListener("dragenter",e=>{e.preventDefault(),Le++,a("drop").hidden=!1}),addEventListener("dragleave",()=>{--Le<=0&&(Le=0,a("drop").hidden=!0)}),addEventListener("dragover",e=>e.preventDefault()),addEventListener("drop",e=>{e.preventDefault(),Le=0,a("drop").hidden=!0,Xe(e.dataTransfer.files)});let X=null,ie=null;const Je=()=>(X??=new AudioContext,X.resume(),X);function ta(e,t){if(!c.volume)return;const n=X.createOscillator(),o=X.createGain();n.frequency.value=t?1600:1e3,o.gain.setValueAtTime(.6*c.volume,e),o.gain.exponentialRampToValueAtTime(.001,e+.05),n.connect(o).connect(X.destination),n.start(e),n.stop(e+.05)}function na(e){if(!c.metronome||!g?.playing||!f||X?.state!=="running")return void(ie=null);(ie===null||Math.abs(e-ie)>.5)&&(ie=e-.05);const t=e+.1*R;for(const n of f.beats)if(!(n.time<=ie)){if(n.time>t)break;ta(X.currentTime+Math.max(0,n.time-e)/R,n.measure>=0)}ie=t}let Ye=!1;const Ze=()=>{if(!(!c.autoplay||I||!g||g.playing)&&navigator.userActivation?.hasBeenActive!==!1){if(g===we&&!M.isReadyForPlayback){if(Ye)return;Ye=!0;const e=()=>{M.playerReady.off(e),Ye=!1,Ze()};return M.playerReady.on(e)}Je(),Promise.resolve(g.play()).catch(e=>e.name!=="AbortError"&&e.name!=="NotAllowedError"&&T(e.message))}},en=()=>{if(g){if(Je(),g.playing)return g.pause();Promise.resolve(g.play()).catch(e=>e.name!=="AbortError"&&T(e.message))}};function Ae(e){c[e]=!c[e],U(),Je(),a(e).setAttribute("aria-pressed",String(c[e]))}for(const e of["metronome","minimal","tabView"])a(e).setAttribute("aria-pressed",String(c[e])),a(e).onclick=()=>Ae(e);const tn=()=>{Ae("minimal"),oe(),q()};a("minimal").onclick=tn;const nn=()=>{Ae("tabView"),oe(),qt()};a("tabView").onclick=nn;let be=!1;function Qe(){const e=!!S&&R<=.9,t=a("loopTools"),n=a("loopBox");if(e||(be=!1),a("trainer").disabled=!e,a("trainer").setAttribute("aria-pressed",String(be)),a("trainerHint").textContent=R>.9?"Slow down to 90% or less to use it":"10% faster each time the loop comes round, up to 100%",t.hidden=!S||n.hidden,t.hidden)return;const o=n.getBoundingClientRect(),r=t.offsetWidth/2,l=Math.min(innerWidth-r-8,Math.max(r+8,o.left+o.width/2));Object.assign(t.style,{left:`${l}px`,top:`${o.top}px`}),t.style.setProperty("--tip",l<140?"0%":l>innerWidth-140?"100%":"50%")}a("trainer").onclick=()=>{be=!be,Qe()},a("clearLoop").onclick=()=>{S=null,q()};function J(e){R=Math.min(1.5,Math.max(.1,Math.round(e*10)/10)),g?.setSpeed(R),a("speed").textContent=`${Math.round(R*100)}%`,Qe()}function an(){const e=f?.phrases.findLast(t=>t.time<=tt());S=S||!e?null:{start:e.time,end:e.endTime},q()}a("play").onclick=en,a("slower").onclick=()=>J(R-.1),a("faster").onclick=()=>J(R+.1),a("speed").onclick=()=>J(1),a("loop").onclick=an;const on=()=>document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen(),sn=()=>{je(a("fullscreen"),document.fullscreenElement?Nn:Rn)};a("fullscreen").onclick=on,a("fullscreen").hidden=!document.fullscreenEnabled,document.onfullscreenchange=sn,sn();const aa=e=>{const t=g?.time??0,n=e.key==="ArrowRight";if(!e.shiftKey)return g?.seek(tt()+(n?5:-5));const o=f?.phrases??[],r=o.findLastIndex(i=>i.time<=t+.05),l=n?o[r+1]:o[t-(o[r]?.time??0)>1?r:r-1];g?.seek(l?.time??(n?t:0))},oa=e=>Me(Math.min(1,Math.max(0,Math.round((ue()+e)*10)/10)),!1),rn=[["Playing",[[[" "],"Space","Play or pause","",en],[["ArrowLeft","ArrowRight"],"← →","Back or ahead 5 seconds","With Shift, a phrase at a time",aa],[["Home"],"Home","Back to the start","Of the loop, when there is one",()=>g?.seek(S?.start??0)],[["-","+","="],"− +","Slower or faster","In 10% steps",e=>J(R+(e.key==="-"?-.1:.1))],[["0"],"0","Back to 100% speed","",()=>J(1)],[["l"],"L","Loop the phrase you are in","Or clear the loop",an],[["t"],"T","Speed trainer","On a loop at 90% or slower: 10% faster each time round",()=>a("trainer").click()],[[..."123456789"],"1–9","Switch part","In the order they are in the header",e=>b?.arrangements[e.key-1]&&se(e.key-1)]]],["Practice tools",[[["k"],"K","Metronome","",()=>Ae("metronome")],[["ArrowUp","ArrowDown"],"↑ ↓","Volume up or down","",e=>oa(e.key==="ArrowUp"?.1:-.1)],[["m"],"M","Mute or unmute","",()=>a("volumeMute").click()],[["b"],"B","Play as a band","Every part in a window of its own, side by side",()=>!I&&a("band").click()]]],["View",[[["h"],"H","Minimal view","Just the highway and the lyrics",tn],[["d"],"D","2D tab view","Or back to the 3D highway",nn],[["f"],"F","Full screen","",on],[["o"],"O","Open a song","",()=>a("open").click()],[["s"],"S","Settings","",()=>te("settings")],[["?"],"?","Notation and shortcuts","This sheet",()=>te("legend")],[["Escape"],"Esc","Close what is open","Settings, this sheet, the volume or the menu",()=>{te("settings",!1),Ve(!1),pe(!1)}]]]];Vt=()=>It(rn.map(([e,t])=>[`Keyboard shortcuts: ${e.toLowerCase()}`,t.map(([,n,o,r])=>[o,r,`<div class="keys">${n.split(" ").map(l=>`<kbd>${l}</kbd>`).join("")}</div>`])])),qt();const sa=new Map(rn.flatMap(([,e])=>e.flatMap(([t,,,,n])=>t.map(o=>[o,n]))));addEventListener("keydown",e=>{if(e.key!=="Escape"&&(e.target.tagName==="INPUT"||e.target.closest?.(".sheet"))||e.metaKey||e.ctrlKey||e.altKey)return;const t=sa.get(e.key.length===1?e.key.toLowerCase():e.key);t&&(e.preventDefault(),t(e))});const V=new BroadcastChannel("fretfall-band"),ne=[],_={t:0,at:0,playing:!1,speed:1,loop:null},ln=()=>performance.timeOrigin+performance.now();let le=!1;const cn=()=>({...b,audio:null,arrangements:b.arrangements.map(({track:e,...t})=>t)}),ra={get time(){return _.t+(_.playing?(ln()-_.at)/1e3*_.speed:0)},get playing(){return _.playing},play:()=>V.postMessage({do:"play"}),pause:()=>V.postMessage({do:"pause"}),seek:e=>V.postMessage({do:"seek",t:e}),setSpeed:e=>e!==_.speed&&V.postMessage({do:"speed",v:e}),setVolume:()=>{},stop:()=>{}};V.onmessage=({data:e})=>{if(I){if(e.bye)return close();e.song&&(e.part??ye)===ye&&(Ue(e.song),f&&(se(Math.min(+ye,b.arrangements.length-1)),document.title=`${f.name} · ${document.title}`),T(null));const t=e.clock;if(!t)return;const n=t.playing!==_.playing;Object.assign(_,t),n&&Se(),t.speed!==R&&J(t.speed),JSON.stringify(t.loop)!==JSON.stringify(S)&&(S=t.loop,q());return}le&&(e.hello!==void 0&&V.postMessage({song:cn(),part:e.hello}),e.do==="play"&&!g.playing&&Promise.resolve(g.play()).catch(t=>t.name!=="AbortError"&&T(t.message)),e.do==="pause"&&g.playing&&g.pause(),e.do==="seek"&&g.seek(e.t),e.do==="speed"&&J(e.v),e.do==="loop"&&(S=e.loop,q()))},a("band").onclick=()=>{le=!0;const e=b.arrangements,t=Math.floor(screen.availWidth/e.length);let n=0;e.forEach((o,r)=>{if(ne[r]&&!ne[r].closed)return ne[r].focus();const l=new URL(location.href);l.searchParams.set("band",r),ne[r]=window.open(l,`fretfall-band-${r}`,`popup,left=${(screen.availLeft??0)+r*t},top=${screen.availTop??0},width=${t},height=${screen.availHeight}`),ne[r]||n++}),n&&T(`The browser blocked ${n} ${n>1?"windows":"window"}: allow pop-ups for this page, then press again`)},a("band").hidden=I,addEventListener("pagehide",()=>le&&V.postMessage({bye:!0}));const hn=a("highway"),et={},tt=()=>g?g.time-c.offset/1e3:0,Ce=new Map;function ia(e){const t=b.lines,{top:n,bottom:o}=bn(t,e),r=new Map([n,o].filter(i=>i>=0).map(i=>[t[i],i===o]));let l=!1;for(const[i,u]of Ce){if(r.has(i))continue;Ce.delete(i);const d=u.at===n-1;if(!d&&n>=0){u.remove();continue}l||=d,u.classList.add("gone"),u.classList.toggle("up",d),setTimeout(()=>u.remove(),800)}for(const[i,u]of r){let d=Ce.get(i);d?d.low!==u&&(d.style.transitionDelay=""):(d=Object.assign(document.createElement("div"),{className:`gone${u?" low":""}`,at:t.indexOf(i)}),d.append(...i.syllables.map(h=>Object.assign(document.createElement("span"),{textContent:h.text}))),a("lines").append(d),d.getBoundingClientRect(),l&&(d.style.transitionDelay="0.2s",setTimeout(()=>d.style.transitionDelay="",700)),Ce.set(i,d)),d.low=u,d.classList.remove("gone"),d.classList.toggle("low",u);const v=i.syllables.filter(h=>h.time<=e).length;d.sung!==v&&(d.sung=v,[...d.children].forEach((h,L)=>h.classList.toggle("sung",L<v)))}}function la(e){const t=`${He(e)} / ${He(b.length)}`;K.time!==t&&(a("time").textContent=K.time=t),a("progress").firstElementChild.style.width=`${Math.min(100,e/b.length*100)}%`;const n=f.phrases.findLastIndex(i=>i.time<=e);if(K.phrase!==n&&(K.phrase=n,N.forEach((i,u)=>{i.classList.toggle("done",u<n),i.style.background=""})),N[n]){const i=Math.min(100,(e-f.phrases[n].time)/(f.phrases[n].endTime-f.phrases[n].time)*100);N[n].style.background=`linear-gradient(90deg, var(--accent) ${i}%, var(--todo) ${i}%)`}const o=f.sectionLabels[f.sections.findLastIndex(i=>i.time<=e)]??"";K.section!==o&&(a("section").textContent=K.section=o),ia(e);const r=f.notes[0]?.time??0,l=c.songIntro&&r-e>St(c)&&a("status").hidden?`${!!g?.playing}|${Math.ceil((r-e)/R)}`:"";K.intro!==l&&(K.intro=l,a("intro").classList.toggle("show",!!l),a("introBars").classList.toggle("moving",!!l&&!!g?.playing),l&&(a("introText").textContent=g?.playing?`Get ready · first notes in ${l.split("|")[1]}`:"Press Space to play"))}function dn(){if(g===we&&M.tickCache&&M.tickCache!==We){We=M.tickCache;const t=Jn(M.score,M.tickCache);k.tempo=t.tempo,Ue(t),Qt(),Ze(),M.score.backingTrack?.rawAudioFile?T("Playing the recording that comes with this file"):a("statusText").textContent===Ke&&T(null)}const e=tt();return S&&g?.playing&&e>=S.end&&!I&&(g.seek(Math.max(0,S.start-1)),be&&J(R+.1)),na(e),le&&V.postMessage({clock:{t:g.time,at:ln(),playing:g.playing,speed:R,loop:S}}),e}function nt(){if(un())return ca();requestAnimationFrame(nt),Pn();const e=dn();Tt("song");const t=On()??e;(c.tabView?xn:Mn)(hn,f,t,C,et),b&&f&&la(t),Tt("overlay"),En()}const un=()=>le&&ne.some(e=>e&&!e.closed);function ca(){document.documentElement.classList.add("banding"),a("bandNote").hidden=!1;const e=setInterval(()=>{if(un())return dn();clearInterval(e),document.documentElement.classList.remove("banding"),a("bandNote").hidden=!0,requestAnimationFrame(nt)},50)}a("bandBack").onclick=()=>{for(const e of ne)e?.close()};function ce(e,t){dispatchEvent(new CustomEvent(`fretfall:${e}`,{detail:t}))}function ha(){const e=[..._e.map(t=>t.name),"a Guitar Pro file","or a tab with its recording"].join(", ");a("openTip").lastChild.textContent=`${e[0].toUpperCase()}${e.slice(1)}. You can also drop them anywhere`,a("drop").textContent=`Drop ${e}`}window.fretfall={open:(e,t)=>Xe(e,t),pick:()=>a("file").click(),get playing(){return!!g?.playing},get time(){return g?.time??0},get length(){return b?.length??0},get parts(){return b?.arrangements.map(e=>e.name)??[]},get part(){return b&&f?b.arrangements.indexOf(f):-1},set part(e){b?.arrangements[e]&&b.arrangements[e]!==f&&se(e)},closeSheets:()=>te(null,!1),addFormat:e=>{_e.push(e),a("file").accept+=e.extensions.map(t=>`,${t}`).join(""),ha()}},ce("ready"),xe(I?ra:we),Me(),I?(V.postMessage({hello:ye}),T("Waiting for the window the band started from",!0)):window.fretfallDemo!==!1?fetch("demo.atex").then(e=>e.ok?e.text():Promise.reject(new Error(e.statusText))).then(e=>M.tex(e)).catch(e=>T(`Could not load the demo song: ${e.message}`)):T(null),new URLSearchParams(location.search).has("perf")&&Cn(hn,()=>b?.length??0),requestAnimationFrame(nt);
1
+ import{tempoMap as mn,tickToMs as yt,msToTick as wt,tuningName as bt,tuningReference as yn,noteName as kt,markRepeats as wn,annotate as bn,lyricsShown as kn,markArpeggios as $n}from"./music.js";import{suggestPositions as $t,fingerFor as vn}from"./fingering.js";import{LOOKS as vt,COLORS as ke,FONTS as Mt,STRINGS as Re,DEFAULT_STYLE as xt,theme as Mn}from"./themes.js";import{drawHighway as xn,drawTab as Sn,lookAhead as St,HEADSTOCKS as Ne,headstockParts as Tn,spline as Ln}from"./highway.js";import{onsetEnvelope as An,align as Cn}from"./sync.js";import{startProfiler as Pn,startFrame as En,lap as Tt,endFrame as On,benchTime as Bn}from"./perf.js";const a=e=>document.getElementById(e),Lt="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.8.4/dist/",Fn={4:[28,33,38,43],5:[23,28,33,38,43],6:[40,45,50,55,59,64],7:[35,40,45,50,55,59,64]},Dn='<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path d="M6.5 4.2v11.6L16 10z"/></svg>',Rn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="M7.5 4.5v11M12.5 4.5v11"/></svg>',Nn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5v-4h4M12.5 3.5h4v4M16.5 12.5v4h-4M7.5 16.5h-4v-4"/></svg>',Hn='<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M7.5 3.5v4h-4M16.5 7.5h-4v-4M12.5 16.5v-4h4M3.5 12.5h4v4"/></svg>',He=(e,t)=>{e.querySelector("svg")?.remove(),e.insertAdjacentHTML("afterbegin",t)},je=e=>`${e<0?"-":""}${Math.floor(Math.abs(e)/60)}:${String(Math.floor(Math.abs(e)%60)).padStart(2,"0")}`,At=()=>new Promise(e=>{requestAnimationFrame(()=>setTimeout(e)),setTimeout(e,100)}),Ct={gradient:{label:"Gradient"},box:{label:"Box"}},Pt={frame:{label:"Frame only"},grey:{label:"Greyed marks"},hide:{label:"No marks"}},Et={scroll:{label:"Scroll"},pages:{label:"Pages"}},Ot={black:{label:"Black"},white:{label:"White"}},Bt={low:{label:"Low E on top"},high:{label:"High e on top"}},jn=7,Ft={viewAngle:30,sideAngle:5,noteSpeed:1,drawDistance:1,fretWidth:1,boardHeight:1},c={...xt,volume:.8,volumeMuted:!1,frames:"gradient",repeatMarks:"frame",markings:"black",stringOrder:"low",headstock:"inline",offset:0,mute:!1,autoplay:!1,metronome:!1,minimal:!1,tabView:!1,tabLayout:"scroll",guides:!0,fretNumbers:!0,songIntro:!0,noteLines:.5,notes3d:!0,...Ft,viewDefaults:jn,panel:"view"},In={...c};try{const e=JSON.parse(localStorage.getItem("fretfall")??"{}"),t=e.viewDefaults??0;if(t<4)for(const n of Object.keys(Ft))delete e[n];t<5&&delete e.repeatMarks,t<6&&delete e.sideAngle,t<7&&delete e.fretNumbers,delete e.viewDefaults,typeof e.noteLines!="number"&&delete e.noteLines,Object.assign(c,e)}catch{}for(const[e,t,n]of[["look",vt],["colors",ke],["fonts",Mt],["strings",Re],["frames",Ct,"gradient"],["repeatMarks",Pt,"frame"],["markings",Ot,"black"],["tabLayout",Et,"scroll"],["stringOrder",Bt,"low"],["headstock",Ne,"inline"]])t[c[e]]||(c[e]=n??xt[e]);const U=()=>{try{localStorage.setItem("fretfall",JSON.stringify(c))}catch{}},ue=()=>c.volumeMuted?0:c.volume;let C;function oe(){C={...Mn(c),guides:c.guides,fretNumbers:c.fretNumbers,noteLines:c.noteLines,notes3d:c.notes3d,frames:c.frames,repeatMarks:c.repeatMarks,markings:c.markings,tabLayout:c.tabLayout,stringOrder:c.stringOrder,headstock:c.headstock,viewAngle:c.viewAngle,sideAngle:c.sideAngle,noteSpeed:c.noteSpeed,drawDistance:c.drawDistance,fretWidth:c.fretWidth,boardHeight:c.boardHeight,fill:c.minimal},document.documentElement.classList.toggle("minimal",c.minimal),document.documentElement.classList.toggle("tab2d",c.tabView),a("fonts").href=C.href;const e={bg:C.bg,ink:C.ink,text:C.text,muted:C.muted,accent:C.accent,chip:C.chip,"chip-border":C.chipBorder,done:C.phraseDone,todo:C.phraseTodo,ui:C.ui,num:C.num,lyric:C.lyric,"lyric-style":C.lyricStyle};Object.assign(e,{lane:C.anchorLane},Object.fromEntries(C.str.map((t,n)=>[`s${n}`,t])));for(const[t,n]of Object.entries(e))document.documentElement.style.setProperty(`--${t}`,n);for(const t of document.querySelectorAll("[data-setting]"))for(const n of t.children)n.setAttribute("aria-pressed",String(n.value===c[t.dataset.setting])),n.dots&&n.dots.replaceChildren(...Wn(t.dataset.setting,n.value).map(o=>Object.assign(document.createElement("i"),{style:`background: ${o}`})))}const Vn=e=>{const t=Ne[e],n=Array.from({length:6},(v,h)=>.83*(1-2*h/5)),{outline:o,ends:r,keys:l}=Tn(t,n),i=v=>(96-v*12).toFixed(1),u=v=>(28-v*12).toFixed(1),d=v=>`M${v.map(([h,L])=>`${i(h)} ${u(L)}`).join("L")}`;return`<svg class="headstock" viewBox="8 -2 114 60" preserveAspectRatio="xMinYMid meet" aria-hidden="true" fill="currentColor" stroke="currentColor">${[...l.map(({u:v,side:h,edge:L})=>`<ellipse cx="${i(v)}" cy="${u(L+h*.36)}" rx="1.5" ry="2.6" fill-opacity=".35" stroke="none"/>`),`<path d="${d(o)}Z" fill-opacity=".1" stroke-width="1.2"/>`,t.cover&&`<path d="${d(Ln(t.cover))}Z" fill="none" stroke-width=".6"/>`,`<path d="${d([[0,1],[-2.1,1]])} ${d([[0,-1],[-2.1,-1]])}" fill="none" stroke-width="1.2"/>`,`<path d="${d([[0,1.05],[0,-1.05]])}" stroke-width="2.4"/>`,...n.map((v,h)=>`<path d="${d([r[h],[0,v],[-2.1,v]])}" fill="none" stroke-width=".7"/>`),...r.map(([v,h])=>`<circle cx="${i(v)}" cy="${u(h)}" r="1.1" stroke="none"/>`)].filter(Boolean).join("")}</svg>`},Wn=(e,t)=>{if(e==="strings")return(Re[t].str??ke[c.colors].str).slice(0,6);const n=ke[t];return[n.floor0,n.lane,n.accent,n.text]};for(const e of document.querySelectorAll("[data-setting]")){const t=e.dataset.setting,n={look:vt,colors:ke,fonts:Mt,strings:Re,frames:Ct,repeatMarks:Pt,markings:Ot,tabLayout:Et,stringOrder:Bt,headstock:Ne}[t];for(const[o,{label:r}]of Object.entries(n)){const l=Object.assign(document.createElement("button"),{value:o,textContent:r});t==="stringOrder"?l.prepend(Object.assign(document.createElement("span"),{className:"order",textContent:o==="low"?"E A D G B e":"e B G D A E"})):t==="headstock"?l.insertAdjacentHTML("afterbegin",Vn(o)):e.classList.contains("cards")&&l.prepend(l.dots=Object.assign(document.createElement("span"),{className:"dots",ariaHidden:"true"})),l.onclick=()=>{c[t]=o,U(),oe()},e.append(l)}}const $e=e=>e.style.setProperty("--fill",`${(e.value-e.min)/(e.max-e.min)*100}%`),Ie={viewAngle:e=>`${e}°`,sideAngle:e=>e?`${Math.abs(e)}° ${e<0?"left":"right"}`:"Straight",noteSpeed:e=>`${Math.round(e*100)}%`,drawDistance:()=>`${St(c).toFixed(1)} s`,fretWidth:e=>`${Math.round(e*100)}%`,boardHeight:e=>`${Math.round(e*100)}%`,noteLines:e=>`${Math.round(e*100)}%`},Dt=()=>{for(const[e,t]of Object.entries(Ie))a(`${e}Out`).value=t(c[e])},Rt=e=>{for(const[t,n]of Object.entries(e))C&&(C[t]=n),c[t]=a(t).value=n,$e(a(t));Dt(),U()};for(const e in Ie)a(e).value=c[e],$e(a(e)),a(e).oninput=t=>Rt({[e]:+t.target.value});Dt(),a("resetSettings").onclick=()=>{const{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l}=c;Object.assign(c,In,{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l});for(const i of["guides","fretNumbers","notes3d","songIntro","mute","autoplay"])a(i).checked=c[i];f?.track&&M.changeTrackMute([f.track],c.mute),ve(c.offset),oe(),Rt(Object.fromEntries(Object.keys(Ie).map(i=>[i,c[i]])))};const ve=e=>{c.offset=a("offset").value=e,a("offsetOut").value=`${e>0?"+":""}${e} ms`,$e(a("offset")),U()};ve(c.offset),a("offset").oninput=e=>ve(+e.target.value),a("resetOffset").onclick=()=>ve(0);const qn=e=>`<svg width="18" height="18" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M3.5 7.5h3L10.5 4v12l-4-3.5h-3z"/>${e}</svg>`;function Me(e=c.volume,t=c.volumeMuted){Object.assign(c,{volume:e,volumeMuted:t||e===0}),a("volume").value=ue(),a("volumeOut").value=`${Math.round(ue()*100)}%`,$e(a("volume"));const n=qn(c.volumeMuted?'<path d="M13.5 8l4 4M17.5 8l-4 4"/>':e<.5?'<path d="M13.5 8a3 3 0 0 1 0 4"/>':'<path d="M13.5 8a3 3 0 0 1 0 4M15.5 5.5a6.5 6.5 0 0 1 0 9"/>');He(a("volumeButton"),n),a("volumeMute").innerHTML=n,a("volumeButton").classList.toggle("muted",c.volumeMuted),a("volumeMute").setAttribute("aria-pressed",String(c.volumeMuted)),a("volumeMute").setAttribute("aria-label",c.volumeMuted?"Unmute":"Mute"),g?.setVolume(ue()),U()}const Ve=(e=a("volumePanel").hidden)=>{a("volumePanel").hidden=!e,a("volumeButton").setAttribute("aria-expanded",String(e))};a("volume").oninput=e=>Me(+e.target.value,!1),a("volumeButton").onclick=()=>Ve(),a("volumeMute").onclick=()=>Me(c.volume||.8,!c.volumeMuted);for(const e of["guides","fretNumbers","notes3d","songIntro"])a(e).checked=c[e],a(e).onchange=t=>{c[e]=t.target.checked,U(),oe()};a("autoplay").checked=c.autoplay,a("autoplay").onchange=e=>{c.autoplay=e.target.checked,U()},a("mute").checked=c.mute,a("mute").onchange=e=>{c.mute=e.target.checked,U(),f?.track&&M.changeTrackMute([f.track],c.mute)};const We=e=>{for(const t of document.querySelectorAll("[data-tab]")){const n=t.dataset.tab===e;t.setAttribute("aria-selected",String(n)),t.tabIndex=n?0:-1,a(`pane-${t.dataset.tab}`).hidden=!n}c.panel=e,U()};for(const e of document.querySelectorAll("[data-tab]"))e.onclick=()=>We(e.dataset.tab),e.onkeydown=t=>{const n=[...document.querySelectorAll("[data-tab]")],o={ArrowRight:1,ArrowLeft:-1}[t.key];if(!o)return;const r=n[(n.indexOf(e)+o+n.length)%n.length];We(r.dataset.tab),r.focus()};We(document.querySelector(`[data-tab="${c.panel}"]`)?c.panel:"view");const Nt={settings:"settingsButton",legend:"legendButton"};function te(e,t=a(e).hidden){for(const[n,o]of Object.entries(Nt)){const r=n===e&&t,l=a(n).hidden===r;a(n).hidden=!r,a(o).setAttribute("aria-expanded",String(r)),l&&ce("sheet",{name:n,open:r})}}for(const[e,t]of Object.entries(Nt))a(t).onclick=()=>te(e),a(`${e}Close`).onclick=()=>{te(e,!1),a(t).focus()};const Gn=matchMedia("(max-width: 900px)"),Ht=matchMedia("(max-width: 560px)"),pe=(e=a("menuButton").ariaExpanded!=="true")=>a("menuButton").setAttribute("aria-expanded",String(e)),jt=()=>{Ht.matches?document.querySelector(".speed").before(a("arrangements")):document.querySelector(".wordmark").after(a("arrangements")),pe(!1)};Gn.onchange=Ht.onchange=jt,jt(),a("menuButton").onclick=()=>pe(),a("menu").onclick=e=>{e.target.closest("button")&&pe(!1)},addEventListener("pointerdown",e=>{e.target.closest(".sheet, #settingsButton, #legendButton")||te("settings",!1),e.target.closest(".volume")||Ve(!1),e.target.closest("#menu, #menuButton")||pe(!1)});const s=e=>`<svg viewBox="0 0 56 32" aria-hidden="true">${e}</svg>`,x=(e,t,n=24,o=12,r=1,l="")=>`<rect x="${e}" y="${t}" width="${n}" height="${o}" class="f${r}"/><rect x="${e}" y="${t}" width="${n}" height="${o/4}" class="fw" opacity=".35"/>${l?`<text x="${e+n/2}" y="${t+o/2+.5}" class="fi" font-size="${o*.75}">${l}</text>`:""}`,O=(e,t=13,n="ft",o="")=>`<text x="28" y="16.5" class="${n}" font-size="${t}" ${o}>${e}</text>`,fe=(e,t=11)=>`${x(16,17,24,11)}<text x="28" y="9" class="ft" font-size="${t}">${e}</text>`,Un=[["Notes and chords",[["Note","Play this string at this fret when its white line on the floor reaches the fretboard. The number on it is the finger to use (T = thumb).",s(`${x(16,8,24,12,1,"1")}<path d="M16 27h24" class="lw" stroke-width="2" opacity=".6"/>`)],["Fret number","The fret to play, on the floor under the note.",s(`${x(20,3,16,7)}<path d="M28 10v8" class="l1" stroke-width="1.5"/><text x="28" y="25" class="f1" font-size="11">7</text>`)],["Open string","Play the string without fretting it. The bar spans your hand position; held, it becomes a lane that wide.",s('<rect x="6" y="11" width="44" height="5" class="f3"/><path d="M6 25h44" class="lw" stroke-width="2" opacity=".6"/>')],["Played again","The same chord as just before: strum it again on this beat. It shows as its frame alone, or as outlines with its marks greyed out or hidden (Settings). Single notes always show in full.",s('<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/><path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>')],["Chord","Play these notes together. The white line marks the beat; the chord name shows when it changes. Held or played again, its edges run on as rails until the shape changes.",s(`<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/>${x(14,16,12,5,0)}${x(30,10,12,5,1)}<path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>`)],["Accented chord","Strum it harder: the top corners of its frame shine white, bolder for a heavy accent.",s(`<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".3"/><path d="M6 12V4h12M38 4h12v8" class="lw" stroke-width="3" fill="none" stroke-linecap="square"/>${x(14,16,12,5,0)}${x(30,10,12,5,1)}<path d="M6 26h44" class="lw" stroke-width="2" opacity=".6"/>`)],["Arpeggio","Hold the chord shape and play its strings one at a time: the shape's rails run as long as you hold it, every note rings on as a dashed trail, and the board outlines every spot of the shape.",s(`<path d="M8 30V6M48 30V6" class="ll" stroke-width="2.5"/><path d="M8 6h40" class="ll" stroke-width="1" opacity=".6"/>${x(14,20,12,5,1)}<path d="M20 20V8" class="l1" stroke-width="1.6" stroke-dasharray="3 2"/>${x(32,12,12,5,3)}`)],["Chord name","The chord you are playing, big, to the right of your hand. It shows up a second before the chord.",s(O("G5",16))],["Barre","Lay one finger across several strings at this fret. ½B is a half barre.",s('<rect x="6" y="4" width="44" height="22" class="lt" stroke-width="1" opacity=".5"/><path d="M24 8v16" class="lt" stroke-width="4" opacity=".8"/><text x="36" y="10" class="ft" font-size="9">B</text>')],["Hand position","Where your index finger sits. A new band with a number means move your hand there.",s('<rect x="16" y="2" width="34" height="28" class="fl" opacity=".2"/><path d="M16 2v28M50 2v28" class="ll" stroke-width="1.5"/><text x="9" y="17" class="fl" font-size="10">5</text>')],["Where to press","The spot on the fretboard lights up as the note comes close and fills in when you play it.",s('<path d="M2 16h52" class="l2" stroke-width="2"/><rect x="18" y="10" width="20" height="12" class="f2" opacity=".35"/><rect x="18" y="10" width="20" height="12" class="l2" stroke-width="1.6"/>')],["String number","Play it on this string (1 is the high e).",s('<circle cx="28" cy="16" r="9" class="l2" stroke-width="1.5"/><text x="28" y="16.5" class="f2" font-size="10">3</text>')]]],["Picking hand",[["Palm mute","Rest the side of your picking hand on the strings near the bridge: a big dark X across the note.",s(`${x(16,10,24,12,0)}<path d="M18 11l20 10M18 21l20-10" class="li" stroke-width="2" opacity=".85"/>`)],["Fret-hand mute","Touch the string with your fretting hand without pressing it down, for a percussive click: a small white X on the note.",s(`${x(16,10,24,12,0)}<path d="M24.5 13l7 6M24.5 19l7-6" class="li" stroke-width="3.5" opacity=".7"/><path d="M24.5 13l7 6M24.5 19l7-6" class="lw" stroke-width="1.8"/>`)],["Down and up strokes","Pick down (⊓) or up (V).",s('<path d="M10 22V10h12v12" class="lt" stroke-width="1.8"/><path d="M34 10l6 12 6-12" class="lt" stroke-width="1.8"/>')],["Picking-hand fingers","p thumb, i index, m middle, a ring, c little finger.",s(O("p i m a",12,"ft",'font-style="italic"'))],["Strum","Strum the chord in the direction of the arrow, where a Guitar Pro file marks it.",s('<path d="M28 5v22M23 21l5 6 5-6" class="lt" stroke-width="1.8"/>')],["Rolled chord","Roll the chord: sound the strings one after another, quickly.",s('<path d="M28 4q4 3 0 5.5t0 5.5 0 5.5 0 5.5M23 21l5 6 5-6" class="lt" stroke-width="1.6"/>')],["Tremolo picking","Pick the note as fast as you can for its whole length.",s(`${x(16,20,24,9)}<path d="M22 12l12-4M22 16l12-4M22 8l12-4" class="lt" stroke-width="1.6"/>`)],["Rasgueado","Flamenco strum: flick the fingers out one after another in the pattern shown.",s(O("rasg.",12))],["Slap and pop","Bass: hit the string with your thumb, or pull it away with a finger and let it snap back.",s(O("slap",12))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(O("golpe",12))]]],["Fretting hand and legato",[["Hammer-on","Sound the note by hammering a finger down, without picking: a white triangle on the note, pointing down.",s(`${x(16,14,24,12)}<path d="M22 7h12l-6 10z" class="fw li" stroke-width="1.2"/>`)],["Pull-off","Pull your finger off the string to sound the lower note: a white triangle on the note, pointing up.",s(`${x(16,14,24,12)}<path d="M22 17h12l-6-10z" class="fw li" stroke-width="1.2"/>`)],["Tie","Hold the note on into the next one without playing it again.",s(`${x(4,19,16,8)}${x(36,19,16,8)}<path d="M12 18Q28 4 44 18" class="lt" stroke-width="1.4" stroke-dasharray="3 3"/>`)],["Slide","Slide to the fret where the trail ends. A dashed end means slide off without a clear target. From an open string, an arch runs from its bar to the fret you land on.",s(`${x(4,20,16,8)}<path d="M12 20L40 8" class="l1" stroke-width="3" opacity=".6"/><rect x="32" y="3" width="16" height="7" class="l1" stroke-width="1.5"/>`)],["Slide in and out","Slide into the note from below or above, or off it at the end.",s(`<path d="M6 27L22 17" class="l1" stroke-width="2" stroke-dasharray="3 3"/>${x(22,10,22,12)}`)],["Tapping","Tap the note with a finger of your picking hand: an arrow in its colour on the note, pointing down. m.g. marks a tap with the fretting hand.",s(`${x(16,17,24,11)}<path d="M20 4L28 16L36 4L28 8.5Z" class="f1" opacity=".45"/><path d="M20 4L28 16L36 4L28 8.5Z" class="l1" stroke-width="1.6"/>`)],["Grace note","A quick note played just before the beat.",s(`${x(22,12,12,7,2)}<path d="M18 24L38 7" class="lt" stroke-width="1.4"/>`)],["Trill","Alternate quickly between the note and the fret shown.",s(O("tr 7",13,"ft",'font-style="italic"'))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(O("turn",12))]]],["Sustain and pitch",[["Sustain","Keep the note ringing for as long as its trail.",s(`<path d="M24 20L26 2h4l2 18z" class="f1" opacity=".5"/>${x(16,18,24,10)}`)],["Let ring","Let the note ring on over the notes after it.",s(`<path d="M28 18V2" class="l1" stroke-width="2" stroke-dasharray="3 3"/>${x(16,18,24,10)}`)],["Vibrato","Shake the note. A taller wave means a wider vibrato.",s(`${x(16,19,24,9)}<path d="M13 9q2.5-4 5 0t5 0 5 0 5 0 5 0 5 0" class="lt" stroke-width="1.6"/>`)],["Bend","Bend the string up by the amount shown: ½ is one fret higher, full is two. The chevrons on the note point up; its trail rises where the bend happens; on the fretboard the string bends up while you hold it.",s(`${x(6,21,20,8)}<path d="M10 16l6-4 6 4M10 11l6-4 6 4" class="lt" stroke-width="1.6"/><text x="40" y="11" class="ft" font-size="9">full</text>`)],["Bend and release","Bend up, then let the string back down: chevrons up, then down, and the trail rises and comes back down.",s(`${x(4,21,20,8)}<path d="M8 16l6-4 6 4M24 8l6 4 6-4" class="lt" stroke-width="1.6"/><text x="46" y="11" class="ft" font-size="9">full</text>`)],["Pre-bend","Bend the string before you pick it, then play.",s(O("pre ½",12))],["Whammy bar","Push or pull the whammy bar; the trail dips and rises with it.",s('<text x="28" y="8" class="ft" font-size="9">w/bar</text><path d="M6 20q11 12 22 0t22 0" class="l1" stroke-width="2"/>')]]],["Harmonics",[["Natural harmonic","Touch the string lightly right above the fret wire, without pressing, and pick.",s('<path d="M20 4l7 7-7 7-7-7z" class="f4"/><text x="40" y="12" class="f4" font-size="10">&lt;12&gt;</text>')],["Pinch harmonic","Catch the string with the side of your thumb just after the pick.",s(fe("PH",10))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(fe("AH",10))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(fe("TH",10))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(fe("SH",10))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(fe("&gt;",12))],["Staccato","Short: stop the note right after you play it.",s(`${x(16,17,24,11)}<circle cx="28" cy="9" r="2.2" class="ft"/>`)],["Ghost note","Barely audible: felt more than heard.",s(O("(7)",14,"f1"))],["Dynamics","How loud, from very soft (pp) to very loud (ff). Shown where it changes.",s(O("mf",15,"ft",'font-style="italic"'))],["Crescendo and diminuendo","Get louder along an opening wedge, softer along a closing one. It shows left of the neck where it starts, beside the bar markings.",s('<path d="M8 16L48 8M8 16l40 8" class="lt" stroke-width="1.6"/>')],["Swell and fade","Fade the note in with the volume knob, or fade it out.",s(O("swell",12))],["Wah","Wah pedal open (o) or closed (+).",s(O("o +",14))],["Fermata","Hold the note longer than written.",s('<path d="M16 22a12 12 0 0 1 24 0" class="lt" stroke-width="1.8"/><circle cx="28" cy="19" r="2.2" class="ft"/>')]]],["On the floor",[["Tempo, time and key","From this bar on, a new tempo, time signature, key or swing feel.",s(O("♩ = 120",11,"fa"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(O("repeat ×2",10,"fa"))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(O("D.S. al coda",8,"fa"))],["8va and loco","Play an octave higher, until loco.",s(O("8va",13,"fa"))],["Text","Instructions written in the file, like pizz. or bass stop.",s(O("pizz.",13,"fa"))]]]],w=(...e)=>e.map(t=>`<path d="M2 ${t}h52" class="lt" stroke-width=".8" opacity=".3"/>`).join(""),P=(e,t,n,o=8,r="fi",l="")=>`<text x="${e}" y="${t+.5}" class="${r}" font-size="${o}" ${l}>${n}</text>`,m=(e,t,n,o,r,l="",i=e+Math.min(n,12)/2)=>`<rect x="${e}" y="${t-5}" width="${n}" height="10" rx="2" class="f${o}" ${l}/>${r?P(i,t,r):""}`,ge=(e,t,n='class="lt" stroke-width="1.5" opacity=".7"')=>`<path d="M14 ${e}h-3v${t-e}h3" ${n}/>`,B=(e,t=9,n="")=>`${w(23)}${m(22,23,12,1,"5")}${P(28,10,e,t,"ft",n)}`,me=(e,t=8)=>`<path d="M8 12v18" class="lt" stroke-width="1" opacity=".4"/>${P(12,6,"5",7,"fm")}${P(34,6,e,t,"fa")}`,zn=[["Notes and chords",[["Note","Play this string at this fret when the start of its bar reaches the play line. The small number beside the fret is the finger to use (T = thumb).",s(`${w(16)}${m(10,16,36,1,"7")}${P(22,16.5,"1",5.5,"fi",'opacity=".7"')}`)],["Play line","Play each note as it reaches the line. Scrolling, played notes dim and fade out to the left; in pages (Settings), the page holds still and the line moves across it.",s(`${w(16)}${m(4,16,12,1,"3",'opacity=".4"')}<path d="M21 3v26" class="la" stroke-width="2.5"/>${m(26,16,24,1,"5")}`)],["Open string","Play the string without fretting it.",s(`${w(16)}${m(22,16,12,3,"0")}`)],["Played again","The same chord as just before: strum it again on this beat. Its notes show as outlines, their marks greyed out.",s(`${w(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="l1" stroke-width="1.5"/>${P(28,16,"5",8,"f1")}`)],["Chord","Play these notes together: a bracket runs down the side of them.",s(`${w(9,23)}${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Accented chord","Strum it harder: its bracket is white, and thicker for a heavy accent.",s(`${w(9,23)}${ge(3,29,'class="lw" stroke-width="2.5"')}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Arpeggio","Hold the chord shape and play its strings one at a time, for as long as the dashed outline runs. Its name is over it.",s(`${w(10,22)}<rect x="5" y="3" width="46" height="26" rx="4" class="lt" stroke-width="1" stroke-dasharray="3 2" opacity=".55"/>${m(9,22,10,2,"2")}${m(24,10,10,1,"1")}${m(38,22,10,2,"2")}`)],["Chord name","The chord you are playing, over the strings where it starts. It shows when the chord changes.",s(P(28,16,"G5",16,"ft"))],["Barre","Lay one finger across several strings at the fret shown. ½B is a half barre.",s(`${w(14,25)}${ge(9,30)}${P(21,4.5,"½B5",7,"ft")}${m(16,14,14,1,"5")}${m(16,25,14,2,"5")}`)],["Hand position","Move your hand so your index finger is at this fret. Under the tab, where the hand moves (Hand position guides in Settings).",s(`${w(8)}<path d="M14 13v12" class="ll" stroke-width="1.5"/>${P(31,22,"fret 5",9,"fl")}`)],["String number","Play it on this string (1 is the high e).",s(`${w(16)}<circle cx="15" cy="16" r="6.5" class="l2" stroke-width="1.2"/>${P(15,16,"3",7,"f2")}${m(26,16,16,2,"5")}`)],["Bar number","Each bar line is numbered along the top of the tab.",s(`${w(20)}<path d="M20 9v22" class="lt" stroke-width="1" opacity=".5"/>${P(28,5,"12",8,"fm")}`)]]],["Picking hand",[["Palm mute","Rest the side of your picking hand on the strings near the bridge: PM over the note.",s(B("PM",8))],["Fret-hand mute","Touch the string with your fretting hand without pressing it down, for a percussive click: a grey bar with an ×.",s(`${w(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="ft" opacity=".22"/>${P(28,16,"×",10,"ft")}`)],["Down and up strokes","Pick down (⊓) or up (V).",s(`${w(23)}${m(8,23,12,1,"5")}${m(36,23,12,1,"5")}<path d="M10 14V6h8v8" class="lt" stroke-width="1.4"/><path d="M38 6l4 8 4-8" class="lt" stroke-width="1.4"/>`)],["Picking-hand fingers","p thumb, i index, m middle, a ring, c little finger.",s(B("i",10,'font-style="italic"'))],["Strum","Strum the chord in the direction of the arrow beside it, where a Guitar Pro file marks it.",s(`${w(9,23)}<path d="M5 4v22M2.5 22.5l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.4"/>${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Rolled chord","Roll the chord: sound the strings one after another, quickly. The arrow beside it is wavy.",s(`${w(9,23)}<path d="M5 4q2 2 0 4.5t0 4.5 0 4.5 0 4.5 0 4M2.5 22.5l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.3"/>${ge(3,29)}${m(16,9,14,1,"5")}${m(16,23,14,2,"7")}`)],["Tremolo picking","Pick the note as fast as you can for its whole length: three slashes over it.",s(`${w(24)}${m(22,24,24,1,"5")}<path d="M23 16l10-3M23 11.5l10-3M23 7l10-3" class="lt" stroke-width="1.4"/>`)],["Rasgueado","Flamenco strum: flick the fingers out one after another in the pattern shown.",s(B("rasg.",8))],["Slap and pop","Bass: hit the string with your thumb, or pull it away with a finger and let it snap back.",s(B("slap",8))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(B("golpe",8))]]],["Fretting hand and legato",[["Hammer-on","Sound the note by hammering a finger down, without picking: an arc from the note before, with H on it.",s(`${w(23)}${m(4,23,12,1,"5")}${m(38,23,12,1,"7")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${P(27,7,"H",8,"ft")}`)],["Pull-off","Pull your finger off the string to sound the lower note: an arc from the note before, with P on it.",s(`${w(23)}${m(4,23,12,1,"7")}${m(38,23,12,1,"5")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${P(27,7,"P",8,"ft")}`)],["Tie","Hold the note on into the next one without playing it again: a dashed arc under them.",s(`${w(12)}${m(4,12,12,1,"5")}${m(38,12,12,1,"5")}<path d="M10 19Q27 30 44 19" class="lt" stroke-width="1.3" stroke-dasharray="3 2"/>`)],["Slide","Slide to the next note, up or down the way the slash after the bar points. A dashed slash means slide off without a clear target.",s(`${w(16)}${m(4,16,22,1,"5")}<path d="M29 20l8-8" class="lt" stroke-width="1.6"/>${m(40,16,12,1,"7")}`)],["Slide in and out","Slide into the note from below or above (the slash before it), or off it at the end (the slash after).",s(`${w(16)}<path d="M8 20l8-8" class="lt" stroke-width="1.6"/>${m(20,16,16,1,"5")}<path d="M40 12l8 8" class="lt" stroke-width="1.6"/>`)],["Tapping","Tap the note with a finger of your picking hand: T over it. m.g. marks a tap with the fretting hand.",s(B("T"))],["Grace note","A quick note played just before the beat: a smaller note.",s(`${w(16)}<rect x="8" y="12.4" width="9" height="7.2" rx="1.5" class="f2"/>${P(12.5,16,"5",6)}${m(22,16,24,2,"7")}`)],["Trill","Alternate quickly between the note and the fret shown.",s(B("tr 7",8))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(B("turn",8))]]],["Sustain and pitch",[["Sustain","Keep the note ringing for as long as its bar.",s(`${w(16)}${m(6,16,44,1,"5")}`)],["Let ring","Let the note ring on over the notes after it: its bar runs on faint and dashed.",s(`${w(16)}<rect x="6" y="11" width="44" height="10" rx="2" class="f1" opacity=".3"/><path d="M22 16h24" class="l1" stroke-width="1.5" stroke-dasharray="3 3"/>${m(6,16,12,1,"5")}`)],["Vibrato","Shake the note for as long as the wave over its bar. A taller wave means a wider vibrato.",s(`${w(22)}${m(6,22,44,1,"5")}<path d="M8 12q2.5-3 5 0t5 0 5 0 5 0 5 0 5 0 5 0 5 0" class="lt" stroke-width="1.4"/>`)],["Bend","Bend the string up by the amount shown: ½ is one fret higher, full is two. The arrow rises from the note's number.",s(`${w(23)}${m(6,23,34,1,"7")}<path d="M14 18q4-1 5-12" class="lt" stroke-width="1.4"/><path d="M16.5 9l2.5-3.5 2 3.8" class="lt" stroke-width="1.4"/>${P(36,8,"full",8,"ft")}`)],["Bend and release","Bend up, then let the string back down: an arrow up, then one down.",s(`${w(23)}${m(4,23,40,1,"7")}<path d="M12 18q4-1 5-12M17 6q6 2 6 12" class="lt" stroke-width="1.4"/><path d="M14.5 9l2.5-3.5 2 3.8M20.5 15l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.4"/>${P(40,8,"full",8,"ft")}`)],["Pre-bend","Bend the string before you pick it, then play: a straight arrow up.",s(`${w(23)}${m(6,23,34,1,"7")}<path d="M16 18V5M13.5 8l2.5-3 2.5 3" class="lt" stroke-width="1.4"/>${P(36,8,"pre ½",8,"ft")}`)],["Whammy bar","Push or pull the whammy bar.",s(B("w/bar",8))]]],["Harmonics",[["Natural harmonic","Touch the string lightly right above the fret wire, without pressing, and pick: the fret in angle brackets.",s(`${w(16)}${m(12,16,32,4,"&lt;12&gt;","",28)}`)],["Pinch harmonic","Catch the string with the side of your thumb just after the pick.",s(B("PH",8))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(B("AH",8))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(B("TH",8))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(B("SH",8))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(B("&gt;",11))],["Staccato","Short: stop the note right after you play it: a dot over it.",s(`${w(23)}${m(22,23,12,1,"5")}<circle cx="28" cy="12" r="1.8" class="ft"/>`)],["Ghost note","Barely audible: felt more than heard. Faint, in brackets.",s(`${w(16)}${m(16,16,24,1,"(7)",'opacity=".55"',28)}`)],["Dynamics","How loud, from very soft (pp) to very loud (ff), under the tab where it changes.",s(`${w(8)}${P(28,21,"mf",14,"ft",'font-style="italic"')}`)],["Crescendo and diminuendo","Get louder along an opening wedge, softer along a closing one, under the tab.",s(`${w(8)}<path d="M48 15L8 21L48 27" class="lt" stroke-width="1.5"/>`)],["Swell and fade","Fade the note in with the volume knob, or fade it out.",s(B("swell",8))],["Wah","Wah pedal open (o) or closed (+).",s(B("o +",9))],["Fermata","Hold the note longer than written.",s(`${w(24)}${m(22,24,12,1,"5")}<path d="M21 15a7 7 0 0 1 14 0" class="lt" stroke-width="1.5"/><circle cx="28" cy="13.5" r="1.5" class="ft"/>`)]]],["Along the top",[["Tempo, time and key","From this bar on, a new tempo, time signature, key or swing feel, beside the bar's number.",s(me("♩ = 120"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(me("repeat ×2",7))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(me("D.S. al coda",6))],["8va and loco","Play an octave higher, until loco.",s(me("8va",9))],["Text","Instructions written in the file, like pizz. or bass stop.",s(me("pizz.",9))]]]],It=e=>e.flatMap(([t,n])=>[Object.assign(document.createElement("h3"),{textContent:t}),...n.map(([o,r,l])=>{const i=Object.assign(document.createElement("div"),{className:"legend-item",innerHTML:l});return i.append(Object.assign(document.createElement("div"),{innerHTML:"<b></b><span></span>"})),i.querySelector("b").textContent=o,i.querySelector("span").textContent=r,i})]);a("legendFind").oninput=e=>{const t=e.target.value.trim().toLowerCase();let n=null,o=0,r=0;const l=()=>n&&(n.hidden=!r);for(const i of a("legendList").children){if(i.tagName==="H3"){l(),[n,r]=[i,0];continue}i.classList.contains("legend-item")&&(i.hidden=!!t&&!i.textContent.toLowerCase().includes(t),i.hidden||(r++,o++))}l(),a("legendList").lastElementChild.hidden=o>0};let Vt=()=>[];const Wt=()=>{a("legendList").replaceChildren(...It(c.tabView?zn:Un),...Vt(),Object.assign(document.createElement("p"),{className:"legend-empty",textContent:"Nothing matches.",hidden:!0})),a("legendFind").oninput({target:a("legendFind")})};oe();let qt;function T(e,t=!1){clearTimeout(qt),a("statusText").textContent=e??"",a("status").hidden=!e,a("status").classList.toggle("loading",t),e&&!t&&(qt=setTimeout(()=>a("status").hidden=!0,5e3))}let b=null,f=null,g=null,R=1,S=null,Gt=!1;const ye=new URLSearchParams(location.search).get("band"),H=ye!==null;function Ut(e,{offset:t=0,ratio:n=1}={}){const o=new Audio(URL.createObjectURL(e));return o.preservesPitch=!0,o.onplay=o.onpause=Se,o.onended=()=>ce("ended"),{get time(){return(o.currentTime-t)/n},get playing(){return!o.paused},play:()=>o.play(),pause:()=>o.pause(),seek:r=>{o.currentTime=Math.max(0,t+n*r)},setSpeed:r=>{o.playbackRate=r},setVolume:r=>{o.volume=r},stop:()=>{o.pause(),URL.revokeObjectURL(o.src)}}}const M=new alphaTab.AlphaTabApi(a("tab"),{core:{fontDirectory:Lt+"font/"},display:{staveProfile:"Tab"},player:{playerMode:H?"Disabled":"EnabledAutomatic",soundFont:Lt+"soundfont/sonivox.sf2",enableCursor:!1,enableUserInteraction:!1}}),k={tick:0,at:0,playing:!1,heard:!1,tempo:[],paused:null};M.playerPositionChanged.on(e=>{k.paused===null&&([k.tick,k.at]=[e.currentTick,performance.now()],e.isSeek||(k.heard=!0))}),M.playerStateChanged.on(e=>{k.playing=e.state===alphaTab.synth.PlayerState.Playing,k.heard=!1,k.at=performance.now(),Se()}),M.playerFinished.on(()=>ce("ended")),M.soundFontLoad.on(e=>T(e.total?`Loading instrument sounds ${Math.round(e.loaded/e.total*100)}%`:"Loading instrument sounds",!0)),M.playerReady.on(()=>T(null)),M.error.on(e=>T(`Could not read that file: ${e.message}`));const we={get time(){return k.tempo.length?(yt(k.tempo,k.tick)+(k.playing&&k.heard&&k.paused===null?Math.min(performance.now()-k.at,100)*M.playbackSpeed:0))/1e3:0},get playing(){return k.playing},play:()=>(k.paused!==null&&(M.tickPosition=k.paused),k.paused=null,M.play()),pause:()=>{k.tick=k.paused=wt(k.tempo,we.time*1e3),k.at=performance.now(),M.pause()},seek:e=>{k.tick=wt(k.tempo,Math.max(0,e)*1e3),k.at=performance.now(),k.heard=!1,k.paused!==null&&(k.paused=k.tick),M.tickPosition=k.tick},setSpeed:e=>{M.playbackSpeed=e},setVolume:e=>{M.masterVolume=e},stop:()=>{k.paused=null,M.stop()}};function xe(e){g&&g!==e&&g.stop(),g=e,g.setSpeed(R),g.setVolume(ue()),Se()}function Se(){He(a("play"),g?.playing?Rn:Dn),a("play").dataset.label=a("play").ariaLabel=g?.playing?"Pause":"Play",!!g?.playing!==Gt&&ce("playing",{playing:Gt=!!g?.playing})}const z=alphaTab.model,qe=(e="")=>e.replace(/([a-z])([A-Z0-9])/g,"$1 $2").toLowerCase(),zt=e=>e?.length?e.map(t=>[t.offset/60,t.value/4]):null,Kn=[null,"natural","artificial","pinch","tap","semi","feedback"],Kt=[null,"accent","heavy","tenuto"],_n=[null,"inverted turn","turn","upper mordent","lower mordent"],_t=["15ma","8va",null,"8vb","15mb"],Xn={major:["C♭","G♭","D♭","A♭","E♭","B♭","F","C","G","D","A","E","B","F♯","C♯"],minor:["A♭","E♭","B♭","F","C","G","D","A","E","B","F♯","C♯","G♯","D♯","A♯"]},Jn=e=>z.Direction[e].replace(/^(Target|Jump)/,"").replace("DaCapo","D.C.").replace("DalSegnoSegno","D.S.S.").replace("DalSegno","D.S.").replace(/^DaDoubleCoda$/,"To double coda").replace(/^DaCoda$/,"To coda").replace("SegnoSegno","Segno segno").replace("DoubleCoda","Double coda").replace(/Al(.*)$/," al $1").replace(" al Double"," al double");let Ge=null;function Yn(e,t){const n=mn(t.masterBars.flatMap(i=>i.tempoChanges),e.tempo),o=i=>yt(n,i)/1e3,r=e.tracks.filter(i=>i.staves[0].isStringed&&!i.staves[0].isPercussion).map(i=>{const u=i.staves[0],d=[],v=[],h=[],L=[],he=[],at=[],Pe=[],ot={},st=[],rt=[],it={};let lt="4/4",ct="0:0",Ee=e.tempo,ht=0,Oe=null,Y=null;for(const A of t.masterBars){const $=A.masterBar,de=u.bars[$.index],Be=o(A.start),ut=o(A.end),fn=(A.end-A.start)/$.timeSignatureNumerator;for(let E=0;E<$.timeSignatureNumerator;E++)he.push({time:o(A.start+E*fn),measure:E?-1:$.index+1});$.section&&at.push({time:Be,name:$.section.text});const I=(E,Fe=Be)=>st.push({time:Fe,text:E}),gn=it[$.index]=(it[$.index]??0)+1,pt=`${$.timeSignatureNumerator}/${$.timeSignatureDenominator}`;pt!==lt&&I(lt=pt);const ft=`${de.keySignature}:${de.keySignatureType}`,gt=de.keySignatureType?"minor":"major";ft!==ct&&(I(`${Xn[gt][de.keySignature+7]} ${gt}`),ct=ft);for(const E of A.tempoChanges)Math.round(E.tempo)!==Math.round(Ee)&&(Ee=E.tempo,I(`♩ = ${Math.round(Ee)}`,o(E.tick)));$.tripletFeel!==ht&&(I($.tripletFeel?`${qe(z.TripletFeel[$.tripletFeel])} feel`:"straight"),ht=$.tripletFeel),$.isRepeatStart&&I(`repeat · pass ${gn}`),$.repeatCount>0&&I(`repeat ×${$.repeatCount}`,ut),$.alternateEndings&&I(`ending ${[0,1,2,3,4,5,6,7].filter(E=>$.alternateEndings&1<<E).map(E=>E+1).join(", ")}`);for(const E of $.directions??[])I(Jn(E));$.isFreeTime&&I("free time");const mt=[];de.voices.forEach((E,Fe)=>{for(const y of E.beats){const q=o(A.start+y.playbackStart),Z=o(A.start+y.playbackStart+y.playbackDuration);if(!Fe){y.text&&I(y.text,q),_t[y.ottava]!==Oe&&(Oe=_t[y.ottava],I(Oe??"loco",q));const p=[null,"cresc","dim"][y.crescendo];Y&&Y.kind!==p&&(Y=null),p&&!Y&&rt.push(Y={time:q,endTime:Z,kind:p}),Y&&(Y.endTime=Z)}let De=null;if(y.notes.length>1){const p=Array(u.tuning.length).fill(-1),Q=[...p];for(const D of y.notes)[p[D.string-1],Q[D.string-1]]=[D.fret,Math.max(-1,D.leftHandFinger)];const G=Pe.at(-1),ee=!!G&&G.frets.join()===p.join()&&Math.abs(G.endTime-q)<.01,ae=y.barreShape?y.notes.filter(D=>D.fret===y.barreFret).map(D=>D.string-1):[];De=v.push({time:q,name:y.chord?.name??"",notes:[],frets:p,fingers:Q,highDensity:ee,palmMute:y.notes.every(D=>D.isPalmMute),fretHandMute:y.notes.every(D=>D.isDead),accent:Kt[Math.max(...y.notes.map(D=>D.accentuated===z.AccentuationType.Tenuto?0:D.accentuated))],strum:[null,"up","down",null,null][y.brushType]??[null,"up","down"][y.pickStroke]??null,roll:[null,null,null,"up","down"][y.brushType]??null,barre:ae.length?{fret:y.barreFret,from:Math.min(...ae),to:Math.max(...ae),half:y.barreShape===z.BarreShape.Half}:void 0})-1,ee?G.endTime=Z:Pe.push({startTime:q,endTime:Z,name:v[De].name,frets:p,fingers:Q})}for(const p of y.notes){const Q=p.isTieDestination&&ot[p.string];if(Q){Q.sustain=Z-Q.time;continue}const G=p.hammerPullOrigin,ee=Kn[p.harmonicType],ae={time:q,sustain:p.slideTarget||p.hasBend||Z-q>=.4?Z-q:0,string:p.string-1,fret:p.fret,chord:De,hammerOn:!!G&&p.fret>G.fret,pullOff:!!G&&p.fret<G.fret,slideTo:p.slideTarget&&(p.fret||p.slideTarget.fret)?p.slideTarget.fret:null,slideUnpitchTo:null,slideIn:[null,"below","above"][p.slideInType]??null,slideOut:[null,null,null,"up","down","down","up"][p.slideOutType]??null,bend:p.hasBend?(p.maxBendPoint?.value??0)/4:0,bendCurve:p.hasBend?zt(p.bendPoints):null,whammy:zt(y.whammyBarPoints),harmonic:!!ee&&ee!=="pinch",harmonicPinch:ee==="pinch",harmonicType:ee,palmMute:p.isPalmMute,mute:p.isDead||y.deadSlapped,ghost:p.isGhost,staccato:p.isStaccato,letRing:p.isLetRing,accent:Kt[p.accentuated]??null,dynamic:qe(z.DynamicValue[p.dynamics]),vibrato:p.vibrato>0||y.vibrato>0,vibratoWide:p.vibrato===z.VibratoType.Wide||y.vibrato===z.VibratoType.Wide,trill:p.trillValue>=0?p.trillValue:null,ornament:_n[p.ornament]??null,grace:y.graceType!==z.GraceType.None,tremolo:y.isTremolo,tap:y.tap,tapLeft:p.isLeftHandTapped,slap:y.slap,pop:y.pop,golpe:[null,"thumb","finger"][y.golpe]??null,rasgueado:y.rasgueado?qe(z.Rasgueado[y.rasgueado]):null,fade:[null,"fade in","fade out","swell"][y.fade]??null,wah:[null,"open","closed"][y.wahPedal]??null,fermata:!!y.fermata,pick:y.notes.length>1?null:[null,"up","down"][y.pickStroke]??null,showString:p.showStringNumber,finger:p.leftHandFinger>=0?p.leftHandFinger:null,rightFinger:"pimac"[p.rightHandFinger]??null};d.push(ot[p.string]=ae),mt.push(ae)}}}),L.push({time:Be,endTime:ut,name:"",maxDifficulty:mt.length})}d.sort((A,$)=>A.time-$.time||A.string-$.string),d.forEach((A,$)=>A.chord!==null&&v[A.chord].notes.push($));const dt=$t(d);h.push(...dt.anchors);for(const A of d)A.finger??=dt.fingers.get(A)??null;for(const A of v)for(const $ of A.notes)A.fingers[d[$].string]<0&&d[$].finger!==null&&(A.fingers[d[$].string]=d[$].finger);return{name:(i.name||`Track ${i.index+1}`).replace(/\s+-\s+.*$/,""),program:i.playbackInfo.program,strings:u.tuning.length,open:[...u.tuning].reverse(),notes:d,chords:v,anchors:h,phrases:L,sections:at,beats:he,handShapes:Pe,markers:st,hairpins:rt,track:i}}),l=[];for(const i of e.tracks){for(const u of t.masterBars)for(const d of i.staves[0].bars[u.masterBar.index].voices[0].beats){if(!d.lyrics?.[0])continue;const v=o(u.start+d.playbackStart);l.push({time:v,length:o(u.start+d.playbackStart+d.playbackDuration)-v,text:d.lyrics[0]})}if(l.length)break}return{title:e.title,artist:e.artist,bpm:e.tempo,length:o(t.masterBars.at(-1)?.end??0),arrangements:r,lyrics:l,tempo:n}}function Zn(e){const t=[];let n=[];for(const{time:o,length:r,text:l}of e){n.length&&o-n.at(-1).end>2&&(t.push(n),n=[]);const i=l.replace(/\+$/,""),u=i.endsWith("-");n.push({time:o,end:o+r,text:u?i.slice(0,-1):`${i} `});const d=!u&&n.reduce((v,h)=>v+h.text.length,0)>34;(l.endsWith("+")||d)&&(t.push(n),n=[])}return n.length&&t.push(n),t.map(o=>({time:o[0].time,end:o.at(-1).end,syllables:o}))}function Ue(e){for(const r of e.arrangements)wn(r.notes,r.chords),bn(r.notes,r.chords),$n(r.notes,r.handShapes??[]);b={...e,lines:Zn(e.lyrics)},H||(S=null),le&&V.postMessage({song:hn()}),document.title=`${b.title||"Untitled"} – Fretfall`;const t=b.arrangements;if(a("band").disabled=t.length<2,t.length>4){const r=Object.assign(document.createElement("select"),{ariaLabel:"Part"});r.append(...t.map((l,i)=>new Option(l.name,i))),r.onchange=()=>se(+r.value),a("arrangements").replaceChildren(r)}else a("arrangements").replaceChildren(...t.map((r,l)=>{const i=Object.assign(document.createElement("button"),{textContent:r.name});return i.onclick=()=>se(l),i}));const n=t.filter(r=>r.program===void 0||r.program>=24&&r.program<=31),o=n.length?t.indexOf(n.reduce((r,l)=>l.notes.length>r.notes.length?l:r)):0;t.length?se(t[0].program===void 0?0:o):(f=null,T("No guitar or bass parts in this file")),ce("song",{file:Te,title:b.title,artist:b.artist,parts:t.map(r=>r.name),part:f?t.indexOf(f):-1}),Te=null}let N=[],K={};const j=(e,t,...n)=>{const o=Object.assign(document.createElement(e),t);return o.append(...n),o};function Qn(e,t){const n=yn(t);if(!n)return e;const o=n.shift.filter(Boolean),r=i=>`${Math.abs(i)} semitone${Math.abs(i)===1?"":"s"} ${i<0?"lower":"higher"}`,l=o.length?o.length===t.length&&o.every(i=>i===o[0])?`${r(o[0])} than ${n.reference}`:o.length===1&&n.shift[0]?`The lowest string ${r(n.shift[0])} than in ${n.reference}`:`${o.length} of ${t.length} strings retuned from ${n.reference}`:"Every string at its usual pitch";return e.classList.add("tuning"),e.tabIndex=0,e.setAttribute("aria-describedby","tuningTip"),e.append(j("span",{className:"tip",id:"tuningTip",role:"tooltip"},j("b",{textContent:bt(t)}),l,j("span",{className:"pegs",ariaHidden:"true"},...t.map((i,u)=>j("span",{className:n.shift[u]?"peg":"peg same",style:`--c: var(--s${u}); --d: ${Math.min(12,Math.abs(n.shift[u]))}`},j("i",{textContent:kt(n.open[u])}),j("i",{className:"move",textContent:n.shift[u]?`${n.shift[u]<0?"−":"+"}${Math.abs(n.shift[u])}`:"0"}),j("strong",{textContent:kt(i)})))),j("small",{textContent:`${n.reference} on top, this song underneath, lowest string first`}))),e}function se(e){f?.track&&M.changeTrackMute([f.track],!1),f=b.arrangements[e],f.track&&M.changeTrackMute([f.track],c.mute);for(const h in et)delete et[h];const t=a("arrangements");t.firstElementChild?.tagName==="SELECT"?t.firstElementChild.value=e:[...t.children].forEach((h,L)=>h.setAttribute("aria-pressed",String(e===L)));const n=[["Tuning",f.open?.length?bt(f.open):null],["Tempo",b.bpm?`${Math.round(b.bpm)} bpm`:null]].filter(([,h])=>h);a("info").replaceChildren(j("span",{},j("b",{textContent:b.title||"Untitled"}),b.artist?` ${b.artist}`:""),...n.map(([h,L])=>{const he=j("span",{},`${h} `,j("b",{textContent:L}));return h==="Tuning"?Qn(he,f.open):he})),a("introTitle").textContent=b.title||"Untitled",a("introFacts").textContent=[b.artist,f.name,...n.map(([,h])=>h)].filter(Boolean).join(" · ");const o={},r={},l=f.sections.map(h=>h.name.replace(/[_-]+/g," ").toUpperCase());l.forEach(h=>o[h]=(o[h]??0)+1),f.sectionLabels=l.map(h=>o[h]>1?`${h} ${r[h]=(r[h]??0)+1}`:h);const i=Math.max(1,...f.phrases.map(h=>h.maxDifficulty)),u=h=>`${Math.min(100,Math.max(0,h/b.length*100)).toFixed(2)}%`;N=f.phrases.map(h=>{const L=document.createElement("div");return L.style.left=u(h.time),L.style.width=`calc(${u(h.endTime)} - ${u(h.time)} - 3px)`,L.style.height=`${Math.max(8,h.maxDifficulty/i*100)}%`,L}),a("phrases").replaceChildren(...N,a("loopBox"),a("phraseTime")),W();const d=f.phrases.map(h=>f.notes.filter(L=>L.time>=h.time&&L.time<h.endTime).length/Math.max(.5,h.endTime-h.time)),v=Math.max(1e-6,...d);a("progress").style.background=`linear-gradient(90deg, ${f.phrases.map((h,L)=>`color-mix(in srgb, var(--accent) ${Math.round(8+62*(d[L]/v))}%, var(--todo)) ${u(h.time)} ${u(h.endTime)}`).join(", ")})`,K={}}const Xt=e=>{const t=(n,o)=>!!e&&f.phrases[o].time>=e.start&&f.phrases[o].endTime<=e.end;return[N.findIndex(t),N.findLastIndex(t)]};function W(e=S){const[t,n]=Xt(e),o=a("loopBox");o.hidden=t<0,t>=0&&Object.assign(o.style,{left:`${N[t].offsetLeft-3}px`,width:`${N[n].offsetLeft+N[n].offsetWidth-N[t].offsetLeft+6}px`}),a("loop").setAttribute("aria-pressed",String(!!e)),H&&e===S&&JSON.stringify(S)!==JSON.stringify(_.loop)&&V.postMessage({do:"loop",loop:S}),Qe()}addEventListener("resize",()=>W());let re=null;const Jt=e=>Math.min(1,Math.max(0,(e-re.left)/re.width))*b.length;a("phrases").onpointerenter=()=>re=a("phrases").getBoundingClientRect(),a("phrases").onpointerleave=()=>a("phraseTime").hidden=!0;const Yt=e=>{const t=N.findIndex(n=>e<n.getBoundingClientRect().right);return t<0?N.length-1:t};let F=null;a("phrases").onpointerdown=e=>{if(!N.length||e.button!==0)return;const t=Yt(e.clientX),[n,o]=Xt(S),r=a("loopBox").getBoundingClientRect(),l=n<0?t:Math.abs(e.clientX-r.left)<6?o:Math.abs(e.clientX-r.right)<6?n:t;F={at:t,from:l,x:e.clientX,range:null},a("phrases").setPointerCapture(e.pointerId)},a("phrases").onpointermove=e=>{if(b&&re&&(Object.assign(a("phraseTime"),{hidden:!1,textContent:je(Jt(e.clientX))}),a("phraseTime").style.left=`${Math.min(re.width,Math.max(0,e.clientX-re.left))}px`),!F||!F.range&&Math.abs(e.clientX-F.x)<4)return;const t=Yt(e.clientX);F.range={start:f.phrases[Math.min(F.from,t)].time,end:f.phrases[Math.max(F.from,t)].endTime},W(F.range)},a("phrases").onpointerup=e=>{if(!F)return;const t=f.phrases[F.at];F.range?(S=F.range,g?.seek(S.start)):F.from===F.at&&(S?(S=e.shiftKey?{start:Math.min(S.start,t.time),end:Math.max(S.end,t.endTime)}:{start:t.time,end:t.endTime},g?.seek(t.time)):g?.seek(Jt(e.clientX))),F=null,W()},a("phrases").onpointercancel=a("phrases").onlostpointercapture=()=>{F=null,W()};const ze=/\.(mp3|m4a|aac|ogg|oga|wav|flac)$/i,Ke="Opening song…";let Zt=Promise.resolve(),Qt=()=>{},Te=null;const _e=[];async function Xe(e,t){if(H)return T("Open songs in the window the band started from");for(const n of[...e].sort((o,r)=>ze.test(o.name)-ze.test(r.name)))await ea(n,t)}async function ea(e,t={}){if(ze.test(e.name))return ta(e);Te=e.name;const n=_e.find(o=>o.extensions.some(r=>e.name.toLowerCase().endsWith(r)));if(!n){const o=e.name.toLowerCase().endsWith(".atex"),r=o?await e.text():new Uint8Array(await e.arrayBuffer());return xe(we),k.paused=null,Ge=M.tickCache,Zt=new Promise(l=>Qt=l),T(Ke,!0),o?M.tex(r):M.load(r)}T(Ke,!0),await At();try{const{song:o,audio:r}=await n.open(e,{...t,status:l=>T(l,!0)});for(const l of o.arrangements){l.open=Array.from({length:l.strings},(u,d)=>(Fn[l.strings]?.[d]??40)+(l.tuning[d]??0)),l.anchors.length||(l.anchors=$t(l.notes).anchors);let i=0;for(const u of l.notes){for(;l.anchors[i+1]?.time<=u.time;)i++;const d=u.chord===null?-1:l.chords[u.chord].fingers[u.string]??-1;u.fret>0&&(u.finger??=d>=0?d:l.anchors[i]?vn(u.fret,l.anchors[i].fret):null)}}xe(Ut(r)),Ue(o),T(null),en()}catch(o){Te=null,T(`Could not open ${e.name}: ${o.message}`)}}async function ta(e){if(await Zt,!b)return T("Open the tab first, then its recording");T("Lining the recording up with the tab…",!0),await At();try{const t=await new OfflineAudioContext(1,1,11025).decodeAudioData(await e.arrayBuffer()),n=new Float32Array(t.length);for(let l=0;l<t.numberOfChannels;l++)t.getChannelData(l).forEach((i,u)=>n[u]+=i/t.numberOfChannels);const o=[...new Set(b.arrangements.flatMap(l=>l.notes.map(i=>Math.round(i.time*100)/100)))].sort((l,i)=>l-i),r=Cn(An(n,t.sampleRate),o);xe(Ut(e,r)),T(r.confidence>=2?`Playing the recording: the tab starts ${r.offset.toFixed(1)} s in`:"Could not line the recording up with the tab. Nudge it with Audio delay in settings.")}catch(t){T(`Could not use ${e.name}: ${t.message}`)}}a("open").onclick=()=>a("file").click(),a("file").onchange=e=>{Xe(e.target.files),e.target.value=""};let Le=0;addEventListener("dragenter",e=>{e.preventDefault(),Le++,a("drop").hidden=!1}),addEventListener("dragleave",()=>{--Le<=0&&(Le=0,a("drop").hidden=!0)}),addEventListener("dragover",e=>e.preventDefault()),addEventListener("drop",e=>{e.preventDefault(),Le=0,a("drop").hidden=!0,Xe(e.dataTransfer.files)});let X=null,ie=null;const Je=()=>(X??=new AudioContext,X.resume(),X);function na(e,t){if(!c.volume)return;const n=X.createOscillator(),o=X.createGain();n.frequency.value=t?1600:1e3,o.gain.setValueAtTime(.6*c.volume,e),o.gain.exponentialRampToValueAtTime(.001,e+.05),n.connect(o).connect(X.destination),n.start(e),n.stop(e+.05)}function aa(e){if(!c.metronome||!g?.playing||!f||X?.state!=="running")return void(ie=null);(ie===null||Math.abs(e-ie)>.5)&&(ie=e-.05);const t=e+.1*R;for(const n of f.beats)if(!(n.time<=ie)){if(n.time>t)break;na(X.currentTime+Math.max(0,n.time-e)/R,n.measure>=0)}ie=t}let Ye=!1;const Ze=()=>{if(!(!g||g.playing)){if(g===we&&!M.isReadyForPlayback){if(Ye)return;Ye=!0;const e=()=>{M.playerReady.off(e),Ye=!1,Ze()};return M.playerReady.on(e)}Je(),Promise.resolve(g.play()).catch(e=>e.name!=="AbortError"&&e.name!=="NotAllowedError"&&T(e.message))}},en=()=>{!c.autoplay||H||navigator.userActivation?.hasBeenActive!==!1&&Ze()},tn=()=>{if(g){if(Je(),g.playing)return g.pause();Promise.resolve(g.play()).catch(e=>e.name!=="AbortError"&&T(e.message))}};function Ae(e){c[e]=!c[e],U(),Je(),a(e).setAttribute("aria-pressed",String(c[e]))}for(const e of["metronome","minimal","tabView"])a(e).setAttribute("aria-pressed",String(c[e])),a(e).onclick=()=>Ae(e);const nn=()=>{Ae("minimal"),oe(),W()};a("minimal").onclick=nn;const an=()=>{Ae("tabView"),oe(),Wt()};a("tabView").onclick=an;let be=!1;function Qe(){const e=!!S&&R<=.9,t=a("loopTools"),n=a("loopBox");if(e||(be=!1),a("trainer").disabled=!e,a("trainer").setAttribute("aria-pressed",String(be)),a("trainerHint").textContent=R>.9?"Slow down to 90% or less to use it":"10% faster each time the loop comes round, up to 100%",t.hidden=!S||n.hidden,t.hidden)return;const o=n.getBoundingClientRect(),r=t.offsetWidth/2,l=Math.min(innerWidth-r-8,Math.max(r+8,o.left+o.width/2));Object.assign(t.style,{left:`${l}px`,top:`${o.top}px`}),t.style.setProperty("--tip",l<140?"0%":l>innerWidth-140?"100%":"50%")}a("trainer").onclick=()=>{be=!be,Qe()},a("clearLoop").onclick=()=>{S=null,W()};function J(e){R=Math.min(1.5,Math.max(.1,Math.round(e*10)/10)),g?.setSpeed(R),a("speed").textContent=`${Math.round(R*100)}%`,Qe()}function on(){const e=f?.phrases.findLast(t=>t.time<=tt());S=S||!e?null:{start:e.time,end:e.endTime},W()}a("play").onclick=tn,a("slower").onclick=()=>J(R-.1),a("faster").onclick=()=>J(R+.1),a("speed").onclick=()=>J(1),a("loop").onclick=on;const sn=()=>document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen(),rn=()=>{He(a("fullscreen"),document.fullscreenElement?Hn:Nn)};a("fullscreen").onclick=sn,a("fullscreen").hidden=!document.fullscreenEnabled,document.onfullscreenchange=rn,rn();const oa=e=>{const t=g?.time??0,n=e.key==="ArrowRight";if(!e.shiftKey)return g?.seek(tt()+(n?5:-5));const o=f?.phrases??[],r=o.findLastIndex(i=>i.time<=t+.05),l=n?o[r+1]:o[t-(o[r]?.time??0)>1?r:r-1];g?.seek(l?.time??(n?t:0))},sa=e=>Me(Math.min(1,Math.max(0,Math.round((ue()+e)*10)/10)),!1),ln=[["Playing",[[[" "],"Space","Play or pause","",tn],[["ArrowLeft","ArrowRight"],"← →","Back or ahead 5 seconds","With Shift, a phrase at a time",oa],[["Home"],"Home","Back to the start","Of the loop, when there is one",()=>g?.seek(S?.start??0)],[["-","+","="],"− +","Slower or faster","In 10% steps",e=>J(R+(e.key==="-"?-.1:.1))],[["0"],"0","Back to 100% speed","",()=>J(1)],[["l"],"L","Loop the phrase you are in","Or clear the loop",on],[["t"],"T","Speed trainer","On a loop at 90% or slower: 10% faster each time round",()=>a("trainer").click()],[[..."123456789"],"1–9","Switch part","In the order they are in the header",e=>b?.arrangements[e.key-1]&&se(e.key-1)]]],["Practice tools",[[["k"],"K","Metronome","",()=>Ae("metronome")],[["ArrowUp","ArrowDown"],"↑ ↓","Volume up or down","",e=>sa(e.key==="ArrowUp"?.1:-.1)],[["m"],"M","Mute or unmute","",()=>a("volumeMute").click()],[["b"],"B","Play as a band","Every part in a window of its own, side by side",()=>!H&&a("band").click()]]],["View",[[["h"],"H","Minimal view","Just the highway and the lyrics",nn],[["d"],"D","2D tab view","Or back to the 3D highway",an],[["f"],"F","Full screen","",sn],[["o"],"O","Open a song","",()=>a("open").click()],[["s"],"S","Settings","",()=>te("settings")],[["?"],"?","Notation and shortcuts","This sheet",()=>te("legend")],[["Escape"],"Esc","Close what is open","Settings, this sheet, the volume or the menu",()=>{te("settings",!1),Ve(!1),pe(!1)}]]]];Vt=()=>It(ln.map(([e,t])=>[`Keyboard shortcuts: ${e.toLowerCase()}`,t.map(([,n,o,r])=>[o,r,`<div class="keys">${n.split(" ").map(l=>`<kbd>${l}</kbd>`).join("")}</div>`])])),Wt();const ra=new Map(ln.flatMap(([,e])=>e.flatMap(([t,,,,n])=>t.map(o=>[o,n]))));addEventListener("keydown",e=>{if(e.key!=="Escape"&&(e.target.tagName==="INPUT"||e.target.closest?.(".sheet"))||e.metaKey||e.ctrlKey||e.altKey)return;const t=ra.get(e.key.length===1?e.key.toLowerCase():e.key);t&&(e.preventDefault(),t(e))});const V=new BroadcastChannel("fretfall-band"),ne=[],_={t:0,at:0,playing:!1,speed:1,loop:null},cn=()=>performance.timeOrigin+performance.now();let le=!1;const hn=()=>({...b,audio:null,arrangements:b.arrangements.map(({track:e,...t})=>t)}),ia={get time(){return _.t+(_.playing?(cn()-_.at)/1e3*_.speed:0)},get playing(){return _.playing},play:()=>V.postMessage({do:"play"}),pause:()=>V.postMessage({do:"pause"}),seek:e=>V.postMessage({do:"seek",t:e}),setSpeed:e=>e!==_.speed&&V.postMessage({do:"speed",v:e}),setVolume:()=>{},stop:()=>{}};V.onmessage=({data:e})=>{if(H){if(e.bye)return close();e.song&&(e.part??ye)===ye&&(Ue(e.song),f&&(se(Math.min(+ye,b.arrangements.length-1)),document.title=`${f.name} · ${document.title}`),T(null));const t=e.clock;if(!t)return;const n=t.playing!==_.playing;Object.assign(_,t),n&&Se(),t.speed!==R&&J(t.speed),JSON.stringify(t.loop)!==JSON.stringify(S)&&(S=t.loop,W());return}le&&(e.hello!==void 0&&V.postMessage({song:hn(),part:e.hello}),e.do==="play"&&!g.playing&&Promise.resolve(g.play()).catch(t=>t.name!=="AbortError"&&T(t.message)),e.do==="pause"&&g.playing&&g.pause(),e.do==="seek"&&g.seek(e.t),e.do==="speed"&&J(e.v),e.do==="loop"&&(S=e.loop,W()))},a("band").onclick=()=>{le=!0;const e=b.arrangements,t=Math.floor(screen.availWidth/e.length);let n=0;e.forEach((o,r)=>{if(ne[r]&&!ne[r].closed)return ne[r].focus();const l=new URL(location.href);l.searchParams.set("band",r),ne[r]=window.open(l,`fretfall-band-${r}`,`popup,left=${(screen.availLeft??0)+r*t},top=${screen.availTop??0},width=${t},height=${screen.availHeight}`),ne[r]||n++}),n&&T(`The browser blocked ${n} ${n>1?"windows":"window"}: allow pop-ups for this page, then press again`)},a("band").hidden=H,addEventListener("pagehide",()=>le&&V.postMessage({bye:!0}));const dn=a("highway"),et={},tt=()=>g?g.time-c.offset/1e3:0,Ce=new Map;function la(e){const t=b.lines,{top:n,bottom:o}=kn(t,e),r=new Map([n,o].filter(i=>i>=0).map(i=>[t[i],i===o]));let l=!1;for(const[i,u]of Ce){if(r.has(i))continue;Ce.delete(i);const d=u.at===n-1;if(!d&&n>=0){u.remove();continue}l||=d,u.classList.add("gone"),u.classList.toggle("up",d),setTimeout(()=>u.remove(),800)}for(const[i,u]of r){let d=Ce.get(i);d?d.low!==u&&(d.style.transitionDelay=""):(d=Object.assign(document.createElement("div"),{className:`gone${u?" low":""}`,at:t.indexOf(i)}),d.append(...i.syllables.map(h=>Object.assign(document.createElement("span"),{textContent:h.text}))),a("lines").append(d),d.getBoundingClientRect(),l&&(d.style.transitionDelay="0.2s",setTimeout(()=>d.style.transitionDelay="",700)),Ce.set(i,d)),d.low=u,d.classList.remove("gone"),d.classList.toggle("low",u);const v=i.syllables.filter(h=>h.time<=e).length;d.sung!==v&&(d.sung=v,[...d.children].forEach((h,L)=>h.classList.toggle("sung",L<v)))}}function ca(e){const t=`${je(e)} / ${je(b.length)}`;K.time!==t&&(a("time").textContent=K.time=t),a("progress").firstElementChild.style.width=`${Math.min(100,e/b.length*100)}%`;const n=f.phrases.findLastIndex(i=>i.time<=e);if(K.phrase!==n&&(K.phrase=n,N.forEach((i,u)=>{i.classList.toggle("done",u<n),i.style.background=""})),N[n]){const i=Math.min(100,(e-f.phrases[n].time)/(f.phrases[n].endTime-f.phrases[n].time)*100);N[n].style.background=`linear-gradient(90deg, var(--accent) ${i}%, var(--todo) ${i}%)`}const o=f.sectionLabels[f.sections.findLastIndex(i=>i.time<=e)]??"";K.section!==o&&(a("section").textContent=K.section=o),la(e);const r=f.notes[0]?.time??0,l=c.songIntro&&r-e>St(c)&&a("status").hidden?`${!!g?.playing}|${Math.ceil((r-e)/R)}`:"";K.intro!==l&&(K.intro=l,a("intro").classList.toggle("show",!!l),a("introBars").classList.toggle("moving",!!l&&!!g?.playing),l&&(a("introText").textContent=g?.playing?`Get ready · first notes in ${l.split("|")[1]}`:"Press Space to play"))}function un(){if(g===we&&M.tickCache&&M.tickCache!==Ge){Ge=M.tickCache;const t=Yn(M.score,M.tickCache);k.tempo=t.tempo,Ue(t),Qt(),en(),M.score.backingTrack?.rawAudioFile?T("Playing the recording that comes with this file"):a("statusText").textContent===Ke&&T(null)}const e=tt();return S&&g?.playing&&e>=S.end&&!H&&(g.seek(Math.max(0,S.start-1)),be&&J(R+.1)),aa(e),le&&V.postMessage({clock:{t:g.time,at:cn(),playing:g.playing,speed:R,loop:S}}),e}function nt(){if(pn())return ha();requestAnimationFrame(nt),En();const e=un();Tt("song");const t=Bn()??e;(c.tabView?Sn:xn)(dn,f,t,C,et),b&&f&&ca(t),Tt("overlay"),On()}const pn=()=>le&&ne.some(e=>e&&!e.closed);function ha(){document.documentElement.classList.add("banding"),a("bandNote").hidden=!1;const e=setInterval(()=>{if(pn())return un();clearInterval(e),document.documentElement.classList.remove("banding"),a("bandNote").hidden=!0,requestAnimationFrame(nt)},50)}a("bandBack").onclick=()=>{for(const e of ne)e?.close()};function ce(e,t){dispatchEvent(new CustomEvent(`fretfall:${e}`,{detail:t}))}function da(){const e=[..._e.map(t=>t.name),"a Guitar Pro file","or a tab with its recording"].join(", ");a("openTip").lastChild.textContent=`${e[0].toUpperCase()}${e.slice(1)}. You can also drop them anywhere`,a("drop").textContent=`Drop ${e}`}window.fretfall={open:(e,t)=>Xe(e,t),pick:()=>a("file").click(),get playing(){return!!g?.playing},set playing(e){e?H||Ze():g?.pause()},get time(){return g?.time??0},get length(){return b?.length??0},get parts(){return b?.arrangements.map(e=>e.name)??[]},get part(){return b&&f?b.arrangements.indexOf(f):-1},set part(e){b?.arrangements[e]&&b.arrangements[e]!==f&&se(e)},closeSheets:()=>te(null,!1),addFormat:e=>{_e.push(e),a("file").accept+=e.extensions.map(t=>`,${t}`).join(""),da()}},ce("ready"),xe(H?ia:we),Me(),H?(V.postMessage({hello:ye}),T("Waiting for the window the band started from",!0)):T(null),new URLSearchParams(location.search).has("perf")&&Pn(dn,()=>b?.length??0),requestAnimationFrame(nt);
package/demo.atex DELETED
@@ -1,113 +0,0 @@
1
- // Fretfall's demo: an original practice piece, so there's something to play before you open a file. About two
2
- // minutes, and it uses every notation the highway draws: mutes, repeats, slides (legato, shift, in and out), hammer-ons
3
- // and pull-offs, bends (pre-bend, release), whammy bar, vibrato (wide too), ties, let ring, tremolo picking, tapping with
4
- // either hand, every harmonic type, accents, staccato, ghost and grace notes, trills and ornaments, dynamics and
5
- // hairpins, fades, wah, fermata, ottava, pick strokes, picking-hand fingers, strums and rolled chords, barres, slap, pop,
6
- // golpe, rasgueado, string numbers, free text, lyrics, and at the bar lines: repeats with endings, segno and coda, key,
7
- // tempo, meter and feel. Written in alphaTex, alphaTab's text format. Lyrics go on beats as
8
- // {lyrics "syllable"}: a "-" at the end joins a syllable to the next, a "+" ends the line.
9
- \title "Fretfall Demo"
10
- \subtitle "Original practice piece"
11
- \tempo 100
12
- .
13
- \track "Guitar"
14
- \staff {tabs}
15
- \instrument distortionguitar
16
- :1 r |
17
- :1 r |
18
- \section "Chugs" \ro :8 (0.6{pm} 2.5{pm}){ch "E5" lyrics "Plug"} (0.6{pm} 2.5{pm}){lyrics "it"} (0.6{pm} 2.5{pm}){lyrics "in"} (0.6{pm} 2.5{pm}){lyrics "and"} (3.6{sl} 5.5{sl}){ch "G5" lyrics "turn"} (5.6{ac} 7.5{ac}){ch "A5" lyrics "it"} (x.6 x.5){lyrics "up+"} (x.6 x.5) |
19
- \rc 2 :8 0.6{pm lyrics "watch"} 0.6{pm lyrics "the"} 0.6{pm lyrics "high-"} 0.6{pm lyrics "way"} (7.6{hac} 9.5{hac}){ch "B5" lyrics "come"} (7.6 9.5){lyrics "a-"} (5.5{pm} 7.4{pm}){ch "D5" lyrics "live+"} (5.5{pm} 7.4{pm}) |
20
- :16 0.6{pm} 0.6{pm} :8 0.6{pm} :16 0.6{pm} 0.6{pm} :8 0.6{pm} :4 (5.6 7.5 7.4){ch "A5" lyrics "Rea-"} (5.6 7.5 7.4){lyrics "dy,"} |
21
- :8 (x.6 x.5 x.4) (x.6 x.5 x.4) :4 (3.6 5.5 5.4){ch "G5" lyrics "set,"} :2 (5.6 7.5 7.4){ch "A5" lyrics "go!+"} |
22
- \section "Riff" :16 5.6{h lf 2} 7.6{lf 4} 5.5{h lf 2} 7.5{lf 4} 7.4{h lf 4} 5.4{lf 2} 7.4{h lf 4} 5.4{lf 2} :8 7.3{sl lf 4} 9.3{lf 5} 7.4{lf 3} 5.4{lf 2} |
23
- :8 5.3{ss lf 2} 12.3{lf 2} :16 12.2{h lf 2} 15.2{lf 5} 12.1{h lf 2} 15.1{lf 5} 15.1{h lf 5} 12.1{lf 2} 14.2{h lf 4} 12.2{lf 2} :4 14.3{lf 4} |
24
- \section "Lead" :4 14.3{b (0 4) lf 4} 14.3{b (0 2) lf 4} :8 12.2{lf 2} 15.2{b (0 4 4 0) lf 5} :4 14.2{v lf 4} |
25
- :2 17.1{v lf 4} :4 -.1 12.1{tp 16 lf 2} |
26
- :16 19.1{tt h} 12.1{h} 15.1{h} 12.1 19.1{tt h} 12.1{h} 15.1{h} 12.1 19.2{tt h} 12.2{h} 15.2{h} 12.2 19.2{tt h} 12.2{h} 15.2{h} 12.2 |
27
- \section "Harmonics" :4 (12.3{nh} 12.2{nh} 12.1{nh}) 7.3{nh} :8 5.2{nh} 5.1{nh} :4 5.3{ph lf 2} |
28
- \section "Chords" :2 (3.6 2.5 0.4 0.3 0.2 3.1){ch "G" lyrics "Watch"} (0.5 2.4 2.3 1.2 0.1){ch "Am" lyrics "the"} |
29
- :2 (3.5 2.4 0.3 1.2 0.1){ch "C" lyrics "notes"} (0.4 2.3 3.2 2.1){ch "D" lyrics "fall+"} |
30
- :4 (1.6{lf 2} 3.5{lf 4} 3.4{lf 5} 2.3{lf 3} 1.2{lf 2} 1.1{lf 2}){ch "F" lyrics "Play"} (1.6 3.5 3.4 2.3 1.2 1.1){lyrics "them"} (1.6 3.5 3.4 2.3 1.2 1.1){lyrics "on"} (1.6 3.5 3.4 2.3 1.2 1.1){lyrics "time+"} |
31
- :8 (2.5 4.4 4.3 3.2 2.1){ch "Bm" lyrics "let"} (2.5 4.4 4.3 3.2 2.1){lyrics "the"} (2.5 4.4 4.3 3.2 2.1){lyrics "strings"} (2.5 4.4 4.3 3.2 2.1){lyrics "ring"} :2 (0.6 2.5 2.4 1.3 0.2 0.1){ch "E" lyrics "out+"} |
32
- \section "Double stops" :8 (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) (4.3{sl} 5.2{sl}) (6.3 7.2) :4 (8.3 10.2) |
33
- :16 (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) (6.3 7.2) :8 (4.3 5.2) (4.3 5.2) :4 (6.3 7.2) |
34
- \ts 3 4 \tempo 120 \section "Waltz" :4 0.5{lyrics "Waltz"} (2.4 2.3 1.2){ch "Am" lyrics "a-"} (2.4 2.3 1.2){lyrics "long"} |
35
- :4 3.6{lyrics "in"} (0.4 0.3 3.1){ch "G" lyrics "three,+"} (0.4 0.3 3.1){lyrics "and"} |
36
- :4 0.4{lyrics "round"} (2.3 3.2 2.1){ch "D" lyrics "and"} (2.3 3.2 2.1){lyrics "round+"} |
37
- \ts 4 4 \tempo 100 \section "Solo" :8 12.3{h lf 2} 14.3{lf 4} 12.2{lf 2} 15.2{b (0 4) lf 5} 15.2{lf 5} 12.2{lf 2} 14.3{lf 4} 12.3{lf 2} |
38
- :16 17.1{sl lf 3} 19.1{lf 5} 17.1{h lf 3} 15.1{lf 2} 17.2{h lf 4} 15.2{lf 2} 16.3{h lf 3} 14.3{lf 2} :4 17.2{b (0 4) v lf 4} 15.1{v lf 2} |
39
- :4 12.2{tp 32 lf 2} 12.2{tp 32 lf 2} :2 7.3{ph v lf 2} |
40
- \section "Articulation" :4 5.3{ac dy p} 7.3{hac} 5.3{ten st} 7.3{g} |
41
- :8 5.2{cre} 7.2{cre} 8.2{cre} 10.2{cre} :4 12.2{dy ff} 10.2{dec} |
42
- :2 (3.6{lr} 2.5{lr} 0.4{lr}){ch "G"} :4 0.3{lr} 0.2{lr dy mf} |
43
- \section "Slides and harmonics" :4 7.3{sib} 9.3{sou} 7.2{sia} 5.2{sod} |
44
- :4 12.3{ah} 7.2{th} 5.3{sh} 7.4{fh} |
45
- \section "Bends and whammy" :4 7.3{b (4 4 0)} 7.3{b (0 4 4 0)} 0.6{tb (0 -4 0)} 0.6{tb (0 -8)} |
46
- \section "Ornaments" :4 5.2{tr 7 16} 5.2{turn} 5.2{iturn} 5.2{umordent} |
47
- :4 3.3{gr} 5.3 5.3{lmordent} :2 7.3{vw} |
48
- \section "Picking" :8 5.4{sd rf 2} 7.4{su rf 3} 5.3{sd rf 2} 7.3{su rf 3} 5.2{sd rf 4} 7.2{su rf 3} 5.1{sd rf 5} 7.1{su rf 1} |
49
- :4 (0.6 2.5 2.4 1.3 0.2 0.1){bd ch "E"} (0.6 2.5 2.4 1.3 0.2 0.1){bu} (0.6 2.5 2.4 1.3 0.2 0.1){ad} (0.6 2.5 2.4 1.3 0.2 0.1){au} |
50
- \section "Barres" :2 (5.6 7.5 7.4 6.3 5.2 5.1){barre 5 full ch "A"} (7.5 9.4 9.3 8.2 7.1){barre 7 half ch "E/B"} |
51
- \section "Percussive" :8 0.4{s} 0.3{p} 5.4{s} 7.3{p} :4 0.6{glpt} 0.6{glpf} |
52
- :4 (0.6 2.5 2.4 1.3 0.2 0.1){rasg amii ch "E"} 12.1{lht} 15.1{tt} 12.1{string} |
53
- \section "Colour" :4 5.3{f} 7.3{vs} 5.3{fo} 7.3{waho} |
54
- :4 5.3{wahc} 7.3{fermata medium} 12.2{ot 8va} 12.2{ot regular} |
55
- :4 3.5{txt "pizz."} 3.5 3.5{txt "bass stop"} r |
56
- \ks g \tf triplet8th \jump segno \section "Swing" \ro :8 3.6{lyrics "Swing"} 5.6{lyrics "it,"} 3.6{lyrics "swing"} 5.6{lyrics "it,"} 7.6{lyrics "let"} 5.6{lyrics "the"} 3.6{lyrics "notes"} 5.6{lyrics "roll+"} |
57
- \ae 1 \rc 2 :4 (3.6 5.5 5.4){ch "G5" lyrics "Once"} (5.6 7.5 7.4){ch "A5" lyrics "a-"} :2 (7.6 9.5 9.4){ch "B5" lyrics "gain+"} |
58
- \ae 2 \tf none \ks c \jump coda :4 (8.6 10.5 10.4){ch "C5" lyrics "Take"} (10.6 12.5 12.4){ch "D5" lyrics "it"} :2 (0.6 2.5 2.4){ch "E5" lyrics "home+"} |
59
- \section "Outro" :8 0.6{pm} x.6 0.6{pm} x.6 (0.6{ac} 2.5{ac} 2.4{ac}){ch "E5"} (x.6 x.5 x.4) (3.6 5.5){ch "G5"} (x.6 x.5) |
60
- :8 0.6{pm} 0.6{pm} 0.6{pm} 0.6{pm} :4 (5.6{ac} 7.5{ac}){ch "A5" lyrics "That's"} (7.6{hac} 9.5{hac}){ch "B5" lyrics "a"} |
61
- :1 (0.6 2.5 2.4 1.3 0.2 0.1){ch "E" lyrics "wrap+"} |
62
- :2 (-.6 -.5 -.4 -.3 -.2 -.1) r |
63
- \track "Bass"
64
- \staff {tabs}
65
- \tuning (G2 D2 A1 E1)
66
- \instrument electricbassfinger
67
- :1 r |
68
- :1 r |
69
- \ro :8 0.4 0.4 0.4 0.4 3.4 3.4 5.4 5.4 |
70
- \rc 2 :8 0.4 0.4 0.4 0.4 7.4 7.4 5.3 5.3 |
71
- :16 0.4 0.4 :8 0.4 :16 0.4 0.4 :8 0.4 :4 5.4 5.4 |
72
- :4 r 3.4 :2 5.4 |
73
- :8 5.4 5.4 5.4 5.4 7.4 7.4 5.4 5.4 |
74
- :4 5.4 3.4 2.3 0.3 |
75
- :2 7.3 7.3 |
76
- :1 0.4 |
77
- :4 0.4 0.4 0.4 0.4 |
78
- :1 r |
79
- :2 3.4 0.3 |
80
- :2 3.3 0.2 |
81
- :4 1.4 1.4 1.4 1.4 |
82
- :2 2.3 0.4 |
83
- :4 3.4 3.4 5.4 5.4 |
84
- :4 3.4 3.4 5.4 5.4 |
85
- \ts 3 4 :4 0.3 r r |
86
- :4 3.4 r r |
87
- :4 0.2 r r |
88
- \ts 4 4 :2 0.4 0.4 |
89
- :2 5.4 7.4 |
90
- :2 0.4 5.3 |
91
- :2 0.3 0.3 |
92
- :2 0.2 5.3 |
93
- :1 3.4 |
94
- :2 0.3 0.2 |
95
- :1 0.3 |
96
- :2 0.4 0.4 |
97
- :1 0.2 |
98
- :1 0.3 |
99
- :2 0.4 0.4 |
100
- :1 0.4 |
101
- :2 5.4 7.4 |
102
- :8 0.4{s} 0.4{p} 0.4{s} 0.4{p} :2 0.4 |
103
- :1 0.4 |
104
- :1 0.3 |
105
- :1 0.3 |
106
- :1 r |
107
- \ro :8 3.4 3.4 5.4 5.4 7.4 7.4 5.4 5.4 |
108
- \ae 1 \rc 2 :4 3.4 5.4 :2 7.4 |
109
- \ae 2 :4 8.4 10.4 :2 0.4 |
110
- :8 0.4 0.4 0.4 0.4 0.4 0.4 3.4 3.4 |
111
- :8 0.4 0.4 0.4 0.4 :4 5.4 7.4 |
112
- :1 0.4 |
113
- :2 -.4 r |