@fretfall/player 0.2.2 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fretfall/player",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "An arcade-style 3D note highway for Guitar Pro and MusicXML tabs, in the browser. Plays along with the synth or the band's recording.",
5
5
  "keywords": [
6
6
  "guitar",
@@ -20,5 +20,18 @@
20
20
  "url": "git+https://github.com/fretfall/player.git"
21
21
  },
22
22
  "bugs": "https://github.com/fretfall/player/issues",
23
- "type": "module"
23
+ "type": "module",
24
+ "main": "./mount.js",
25
+ "types": "./mount.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./mount.d.ts",
29
+ "default": "./mount.js"
30
+ },
31
+ "./mount.js": {
32
+ "types": "./mount.d.ts",
33
+ "default": "./mount.js"
34
+ },
35
+ "./*": "./*"
36
+ }
24
37
  }
package/player.js CHANGED
@@ -1 +1 @@
1
- import{tempoMap as yn,tickToMs as kt,msToTick as $t,tuningName as vt,tuningReference as wn,noteName as Mt,markRepeats as bn,annotate as kn,lyricsShown as $n,markArpeggios as vn}from"./music.js";import{suggestPositions as xt,fingerFor as Mn}from"./fingering.js";import{LOOKS as St,COLORS as $e,FONTS as Tt,STRINGS as Ne,DEFAULT_STYLE as Ct,theme as xn}from"./themes.js";import{drawHighway as Sn,drawTab as Tn,lookAhead as Lt,HEADSTOCKS as He,headstockParts as Cn,spline as Ln}from"./highway.js";import{onsetEnvelope as An,align as Pn}from"./sync.js";import{startProfiler as En,startFrame as On,lap as At,endFrame as Bn,benchTime as Fn}from"./perf.js";const a=e=>document.getElementById(e),Pt="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.8.4/dist/",Dn={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]},Rn='<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path d="M6.5 4.2v11.6L16 10z"/></svg>',Nn='<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>',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="M3.5 7.5v-4h4M12.5 3.5h4v4M16.5 12.5v4h-4M7.5 16.5h-4v-4"/></svg>',jn='<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)},Ie=e=>`${e<0?"-":""}${Math.floor(Math.abs(e)/60)}:${String(Math.floor(Math.abs(e)%60)).padStart(2,"0")}`,Et=()=>new Promise(e=>{requestAnimationFrame(()=>setTimeout(e)),setTimeout(e,100)}),Ot={gradient:{label:"Gradient"},box:{label:"Box"}},Bt={frame:{label:"Frame only"},grey:{label:"Greyed marks"},hide:{label:"No marks"}},Ft={scroll:{label:"Scroll"},pages:{label:"Pages"}},Dt={black:{label:"Black"},white:{label:"White"}},Rt={low:{label:"Low E on top"},high:{label:"High e on top"}},In=8,Nt={viewAngle:30,sideAngle:5,noteSpeed:1,drawDistance:1,fretWidth:1,boardHeight:1},c={...Ct,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:.5,songIntro:!0,noteLines:.5,notes3d:!0,...Nt,viewDefaults:In,panel:"view"},Vn={...c};try{const e=JSON.parse(localStorage.getItem("fretfall")??"{}"),t=e.viewDefaults??0;if(t<4)for(const n of Object.keys(Nt))delete e[n];t<5&&delete e.repeatMarks,t<6&&delete e.sideAngle,t<8&&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",St],["colors",$e],["fonts",Tt],["strings",Ne],["frames",Ot,"gradient"],["repeatMarks",Bt,"frame"],["markings",Dt,"black"],["tabLayout",Ft,"scroll"],["stringOrder",Rt,"low"],["headstock",He,"inline"]])t[c[e]]||(c[e]=n??Ct[e]);const z=()=>{try{localStorage.setItem("fretfall",JSON.stringify(c))}catch{}},ue=()=>c.volumeMuted?0:c.volume;let L;function se(){L={...xn(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=L.href;const e={bg:L.bg,ink:L.ink,text:L.text,muted:L.muted,accent:L.accent,chip:L.chip,"chip-border":L.chipBorder,done:L.phraseDone,todo:L.phraseTodo,ui:L.ui,num:L.num,lyric:L.lyric,"lyric-style":L.lyricStyle};Object.assign(e,{lane:L.anchorLane},Object.fromEntries(L.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(...Gn(t.dataset.setting,n.value).map(o=>Object.assign(document.createElement("i"),{style:`background: ${o}`})))}const qn=e=>{const t=He[e],n=Array.from({length:6},(h,$)=>.83*(1-2*$/5)),{outline:o,ends:r,keys:l}=Cn(t,n),i=h=>(96-h*12).toFixed(1),d=h=>(28-h*12).toFixed(1),p=h=>`M${h.map(([$,H])=>`${i($)} ${d(H)}`).join("L")}`;return`<svg class="headstock" viewBox="8 -2 114 60" preserveAspectRatio="xMinYMid meet" aria-hidden="true" fill="currentColor" stroke="currentColor">${[...l.map(({u:h,side:$,edge:H})=>`<ellipse cx="${i(h)}" cy="${d(H+$*.36)}" rx="1.5" ry="2.6" fill-opacity=".35" stroke="none"/>`),`<path d="${p(o)}Z" fill-opacity=".1" stroke-width="1.2"/>`,t.cover&&`<path d="${p(Ln(t.cover))}Z" fill="none" stroke-width=".6"/>`,`<path d="${p([[0,1],[-2.1,1]])} ${p([[0,-1],[-2.1,-1]])}" fill="none" stroke-width="1.2"/>`,`<path d="${p([[0,1.05],[0,-1.05]])}" stroke-width="2.4"/>`,...n.map((h,$)=>`<path d="${p([r[$],[0,h],[-2.1,h]])}" fill="none" stroke-width=".7"/>`),...r.map(([h,$])=>`<circle cx="${i(h)}" cy="${d($)}" r="1.1" stroke="none"/>`)].filter(Boolean).join("")}</svg>`},Gn=(e,t)=>{if(e==="strings")return(Ne[t].str??$e[c.colors].str).slice(0,6);const n=$e[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:St,colors:$e,fonts:Tt,strings:Ne,frames:Ot,repeatMarks:Bt,markings:Dt,tabLayout:Ft,stringOrder:Rt,headstock:He}[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",qn(o)):e.classList.contains("cards")&&l.prepend(l.dots=Object.assign(document.createElement("span"),{className:"dots",ariaHidden:"true"})),l.onclick=()=>{c[t]=o,z(),se()},e.append(l)}}const ve=e=>e.style.setProperty("--fill",`${(e.value-e.min)/(e.max-e.min)*100}%`),Ve={viewAngle:e=>`${e}°`,sideAngle:e=>e?`${Math.abs(e)}° ${e<0?"left":"right"}`:"Straight",noteSpeed:e=>`${Math.round(e*100)}%`,drawDistance:()=>`${Lt(c).toFixed(1)} s`,fretWidth:e=>`${Math.round(e*100)}%`,boardHeight:e=>`${Math.round(e*100)}%`,noteLines:e=>`${Math.round(e*100)}%`,fretNumbers:e=>`${Math.round(e*100)}%`},Ht=()=>{for(const[e,t]of Object.entries(Ve))a(`${e}Out`).value=t(c[e])},jt=e=>{for(const[t,n]of Object.entries(e))L&&(L[t]=n),c[t]=a(t).value=n,ve(a(t));Ht(),z()};for(const e in Ve)a(e).value=c[e],ve(a(e)),a(e).oninput=t=>jt({[e]:+t.target.value});Ht(),a("resetSettings").onclick=()=>{const{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l}=c;Object.assign(c,Vn,{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l});for(const i of["guides","notes3d","songIntro","mute","autoplay"])a(i).checked=c[i];f?.track&&x.changeTrackMute([f.track],c.mute),Me(c.offset),se(),jt(Object.fromEntries(Object.keys(Ve).map(i=>[i,c[i]])))};const Me=e=>{c.offset=a("offset").value=e,a("offsetOut").value=`${e>0?"+":""}${e} ms`,ve(a("offset")),z()};Me(c.offset),a("offset").oninput=e=>Me(+e.target.value),a("resetOffset").onclick=()=>Me(0);const Un=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 xe(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)}%`,ve(a("volume"));const n=Un(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"),m?.setVolume(ue()),z()}const qe=(e=a("volumePanel").hidden)=>{a("volumePanel").hidden=!e,a("volumeButton").setAttribute("aria-expanded",String(e))};a("volume").oninput=e=>xe(+e.target.value,!1),a("volumeButton").onclick=()=>qe(),a("volumeMute").onclick=()=>xe(c.volume||.8,!c.volumeMuted);for(const e of["guides","notes3d","songIntro"])a(e).checked=c[e],a(e).onchange=t=>{c[e]=t.target.checked,z(),se()};a("autoplay").checked=c.autoplay,a("autoplay").onchange=e=>{c.autoplay=e.target.checked,z()},a("mute").checked=c.mute,a("mute").onchange=e=>{c.mute=e.target.checked,z(),f?.track&&x.changeTrackMute([f.track],c.mute)};const Ge=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,z()};for(const e of document.querySelectorAll("[data-tab]"))e.onclick=()=>Ge(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];Ge(r.dataset.tab),r.focus()};Ge(document.querySelector(`[data-tab="${c.panel}"]`)?c.panel:"view");const It={settings:"settingsButton",legend:"legendButton"};function ne(e,t=a(e).hidden){for(const[n,o]of Object.entries(It)){const r=n===e&&t,l=a(n).hidden===r;a(n).hidden=!r,a(o).setAttribute("aria-expanded",String(r)),l&&he("sheet",{name:n,open:r})}}for(const[e,t]of Object.entries(It))a(t).onclick=()=>ne(e),a(`${e}Close`).onclick=()=>{ne(e,!1),a(t).focus()};const Wn=matchMedia("(max-width: 900px)"),Vt=matchMedia("(max-width: 560px)"),pe=(e=a("menuButton").ariaExpanded!=="true")=>a("menuButton").setAttribute("aria-expanded",String(e)),qt=()=>{Vt.matches?document.querySelector(".speed").before(a("arrangements")):document.querySelector(".wordmark").after(a("arrangements")),pe(!1)};Wn.onchange=Vt.onchange=qt,qt(),a("menuButton").onclick=()=>pe(),a("menu").onclick=e=>{e.target.closest("button")&&pe(!1)},addEventListener("pointerdown",e=>{e.target.closest(".sheet, #settingsButton, #legendButton")||ne("settings",!1),e.target.closest(".volume")||qe(!1),e.target.closest("#menu, #menuButton")||pe(!1)});const s=e=>`<svg viewBox="0 0 56 32" aria-hidden="true">${e}</svg>`,T=(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)=>`${T(16,17,24,11)}<text x="28" y="9" class="ft" font-size="${t}">${e}</text>`,zn=[["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(`${T(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(`${T(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"/>${T(14,16,12,5,0)}${T(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"/>${T(14,16,12,5,0)}${T(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"/>${T(14,20,12,5,1)}<path d="M20 20V8" class="l1" stroke-width="1.6" stroke-dasharray="3 2"/>${T(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>')],["Capo","Clamped across the neck: from there the strings play open, and the dark frets behind it are out of play.",s('<path d="M4 9h14M4 14h14M4 19h14M4 24h14" class="lt" stroke-width="1" opacity=".15"/><path d="M18 9h32M18 14h32M18 19h32M18 24h32" class="lt" stroke-width="1" opacity=".5"/><path d="M32 6v20M46 6v20" class="lt" stroke-width="1" opacity=".3"/><path d="M18 4v24" class="lt" stroke-width="4.5"/>')],["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(`${T(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(`${T(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(`${T(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(`${T(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(`${T(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(`${T(4,19,16,8)}${T(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(`${T(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"/>${T(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(`${T(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(`${T(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"/>${T(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"/>${T(16,18,24,10)}`)],["Vibrato","Shake the note. A taller wave means a wider vibrato.",s(`${T(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(`${T(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(`${T(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(`${T(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"))]]]],b=(...e)=>e.map(t=>`<path d="M2 ${t}h52" class="lt" stroke-width=".8" opacity=".3"/>`).join(""),A=(e,t,n,o=8,r="fi",l="")=>`<text x="${e}" y="${t+.5}" class="${r}" font-size="${o}" ${l}>${n}</text>`,y=(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?A(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="")=>`${b(23)}${y(22,23,12,1,"5")}${A(28,10,e,t,"ft",n)}`,me=(e,t=8)=>`<path d="M8 12v18" class="lt" stroke-width="1" opacity=".4"/>${A(12,6,"5",7,"fm")}${A(34,6,e,t,"fa")}`,Kn=[["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(`${b(16)}${y(10,16,36,1,"7")}${A(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(`${b(16)}${y(4,16,12,1,"3",'opacity=".4"')}<path d="M21 3v26" class="la" stroke-width="2.5"/>${y(26,16,24,1,"5")}`)],["Open string","Play the string without fretting it.",s(`${b(16)}${y(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(`${b(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="l1" stroke-width="1.5"/>${A(28,16,"5",8,"f1")}`)],["Chord","Play these notes together: a bracket runs down the side of them.",s(`${b(9,23)}${ge(3,29)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Accented chord","Strum it harder: its bracket is white, and thicker for a heavy accent.",s(`${b(9,23)}${ge(3,29,'class="lw" stroke-width="2.5"')}${y(16,9,14,1,"5")}${y(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(`${b(10,22)}<rect x="5" y="3" width="46" height="26" rx="4" class="lt" stroke-width="1" stroke-dasharray="3 2" opacity=".55"/>${y(9,22,10,2,"2")}${y(24,10,10,1,"1")}${y(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(A(28,16,"G5",16,"ft"))],["Barre","Lay one finger across several strings at the fret shown. ½B is a half barre.",s(`${b(14,25)}${ge(9,30)}${A(21,4.5,"½B5",7,"ft")}${y(16,14,14,1,"5")}${y(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(`${b(8)}<path d="M14 13v12" class="ll" stroke-width="1.5"/>${A(31,22,"fret 5",9,"fl")}`)],["String number","Play it on this string (1 is the high e).",s(`${b(16)}<circle cx="15" cy="16" r="6.5" class="l2" stroke-width="1.2"/>${A(15,16,"3",7,"f2")}${y(26,16,16,2,"5")}`)],["Bar number","Each bar line is numbered along the top of the tab.",s(`${b(20)}<path d="M20 9v22" class="lt" stroke-width="1" opacity=".5"/>${A(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(`${b(16)}<rect x="20" y="11" width="16" height="10" rx="2" class="ft" opacity=".22"/>${A(28,16,"×",10,"ft")}`)],["Down and up strokes","Pick down (⊓) or up (V).",s(`${b(23)}${y(8,23,12,1,"5")}${y(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(`${b(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)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Rolled chord","Roll the chord: sound the strings one after another, quickly. The arrow beside it is wavy.",s(`${b(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)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Tremolo picking","Pick the note as fast as you can for its whole length: three slashes over it.",s(`${b(24)}${y(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(`${b(23)}${y(4,23,12,1,"5")}${y(38,23,12,1,"7")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${A(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(`${b(23)}${y(4,23,12,1,"7")}${y(38,23,12,1,"5")}<path d="M10 16Q27 5 44 16" class="lt" stroke-width="1.4"/>${A(27,7,"P",8,"ft")}`)],["Tie","Hold the note on into the next one without playing it again: a dashed arc under them.",s(`${b(12)}${y(4,12,12,1,"5")}${y(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(`${b(16)}${y(4,16,22,1,"5")}<path d="M29 20l8-8" class="lt" stroke-width="1.6"/>${y(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(`${b(16)}<path d="M8 20l8-8" class="lt" stroke-width="1.6"/>${y(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(`${b(16)}<rect x="8" y="12.4" width="9" height="7.2" rx="1.5" class="f2"/>${A(12.5,16,"5",6)}${y(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(`${b(16)}${y(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(`${b(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"/>${y(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(`${b(22)}${y(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(`${b(23)}${y(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"/>${A(36,8,"full",8,"ft")}`)],["Bend and release","Bend up, then let the string back down: an arrow up, then one down.",s(`${b(23)}${y(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"/>${A(40,8,"full",8,"ft")}`)],["Pre-bend","Bend the string before you pick it, then play: a straight arrow up.",s(`${b(23)}${y(6,23,34,1,"7")}<path d="M16 18V5M13.5 8l2.5-3 2.5 3" class="lt" stroke-width="1.4"/>${A(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(`${b(16)}${y(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(`${b(23)}${y(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(`${b(16)}${y(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(`${b(8)}${A(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(`${b(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(`${b(24)}${y(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))]]]],Gt=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 Ut=()=>[];const Wt=()=>{a("legendList").replaceChildren(...Gt(c.tabView?Kn:zn),...Ut(),Object.assign(document.createElement("p"),{className:"legend-empty",textContent:"Nothing matches.",hidden:!0})),a("legendFind").oninput({target:a("legendFind")})};se();let zt;function C(e,t=!1){clearTimeout(zt),a("statusText").textContent=e??"",a("status").hidden=!e,a("status").classList.toggle("loading",t),e&&!t&&(zt=setTimeout(()=>a("status").hidden=!0,5e3))}let k=null,f=null,m=null,D=1,S=null,Kt=!1;const ye=new URLSearchParams(location.search).get("band"),R=ye!==null;function _t(e,{offset:t=0,ratio:n=1}={}){const o=new Audio(URL.createObjectURL(e));return o.preservesPitch=!0,o.onplay=o.onpause=Te,o.onended=()=>he("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 x=new alphaTab.AlphaTabApi(a("tab"),{core:{fontDirectory:Pt+"font/"},display:{staveProfile:"Tab"},player:{playerMode:R?"Disabled":"EnabledAutomatic",soundFont:Pt+"soundfont/sonivox.sf2",enableCursor:!1,enableUserInteraction:!1}}),v={tick:0,at:0,playing:!1,heard:!1,tempo:[],paused:null};x.playerPositionChanged.on(e=>{v.paused===null&&([v.tick,v.at]=[e.currentTick,performance.now()],e.isSeek||(v.heard=!0))}),x.playerStateChanged.on(e=>{v.playing=e.state===alphaTab.synth.PlayerState.Playing,v.heard=!1,v.at=performance.now(),Te()}),x.playerFinished.on(()=>he("ended")),x.soundFontLoad.on(e=>C(e.total?`Loading instrument sounds ${Math.round(e.loaded/e.total*100)}%`:"Loading instrument sounds",!0)),x.playerReady.on(()=>C(null)),x.error.on(e=>C(`Could not read that file: ${e.message}`));const we={get time(){return v.tempo.length?(kt(v.tempo,v.tick)+(v.playing&&v.heard&&v.paused===null?Math.min(performance.now()-v.at,100)*x.playbackSpeed:0))/1e3:0},get playing(){return v.playing},play:()=>(v.paused!==null&&(x.tickPosition=v.paused),v.paused=null,x.play()),pause:()=>{v.tick=v.paused=$t(v.tempo,we.time*1e3),v.at=performance.now(),x.pause()},seek:e=>{v.tick=$t(v.tempo,Math.max(0,e)*1e3),v.at=performance.now(),v.heard=!1,v.paused!==null&&(v.paused=v.tick),x.tickPosition=v.tick},setSpeed:e=>{x.playbackSpeed=e},setVolume:e=>{x.masterVolume=e},stop:()=>{v.paused=null,x.stop()}};function Se(e){m&&m!==e&&m.stop(),m=e,m.setSpeed(D),m.setVolume(ue()),Te()}function Te(){je(a("play"),m?.playing?Nn:Rn),a("play").dataset.label=a("play").ariaLabel=m?.playing?"Pause":"Play",!!m?.playing!==Kt&&he("playing",{playing:Kt=!!m?.playing})}const K=alphaTab.model,Ue=(e="")=>e.replace(/([a-z])([A-Z0-9])/g,"$1 $2").toLowerCase(),Xt=e=>e?.length?e.map(t=>[t.offset/60,t.value/4]):null,_n=[null,"natural","artificial","pinch","tap","semi","feedback"],Jt=[null,"accent","heavy","tenuto"],Xn=[null,"inverted turn","turn","upper mordent","lower mordent"],Yt=["15ma","8va",null,"8vb","15mb"],Jn={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♯"]},Yn=e=>K.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 Zn(e,t){const n=yn(t.masterBars.flatMap(i=>i.tempoChanges),e.tempo),o=i=>kt(n,i)/1e3,r=e.tracks.filter(i=>i.staves[0].isStringed&&!i.staves[0].isPercussion).map(i=>{const d=i.staves[0],p=[],h=[],$=[],H=[],ot=[],st=[],ke=[],rt={},it=[],lt=[],ct=d.capo?[{time:0,fret:d.capo}]:[],ht={};let dt="4/4",ut="0:0",Oe=e.tempo,pt=0,Be=null,Q=null;for(const M of t.masterBars){const u=M.masterBar,de=d.bars[u.index],Fe=o(M.start),gt=o(M.end),gn=(M.end-M.start)/u.timeSignatureNumerator;for(let E=0;E<u.timeSignatureNumerator;E++)ot.push({time:o(M.start+E*gn),measure:E?-1:u.index+1});u.section&&st.push({time:Fe,name:u.section.text});const j=(E,De=Fe)=>it.push({time:De,text:E}),mn=ht[u.index]=(ht[u.index]??0)+1,mt=`${u.timeSignatureNumerator}/${u.timeSignatureDenominator}`;mt!==dt&&j(dt=mt);const yt=`${de.keySignature}:${de.keySignatureType}`,wt=de.keySignatureType?"minor":"major";yt!==ut&&(j(`${Jn[wt][de.keySignature+7]} ${wt}`),ut=yt);for(const E of M.tempoChanges)Math.round(E.tempo)!==Math.round(Oe)&&(Oe=E.tempo,j(`♩ = ${Math.round(Oe)}`,o(E.tick)));u.tripletFeel!==pt&&(j(u.tripletFeel?`${Ue(K.TripletFeel[u.tripletFeel])} feel`:"straight"),pt=u.tripletFeel),u.isRepeatStart&&j(`repeat · pass ${mn}`),u.repeatCount>0&&j(`repeat ×${u.repeatCount}`,gt),u.alternateEndings&&j(`ending ${[0,1,2,3,4,5,6,7].filter(E=>u.alternateEndings&1<<E).map(E=>E+1).join(", ")}`);for(const E of u.directions??[])j(Yn(E));u.isFreeTime&&j("free time");const bt=[];de.voices.forEach((E,De)=>{for(const w of E.beats){const q=o(M.start+w.playbackStart),ee=o(M.start+w.playbackStart+w.playbackDuration);if(!De){if(w.text){j(w.text,q);const I=/^\s*capo\s*(\d+|off)\s*$/i.exec(w.text);I&&ct.push({time:q,fret:/off/i.test(I[1])?0:+I[1]})}Yt[w.ottava]!==Be&&(Be=Yt[w.ottava],j(Be??"loco",q));const g=[null,"cresc","dim"][w.crescendo];Q&&Q.kind!==g&&(Q=null),g&&!Q&&lt.push(Q={time:q,endTime:ee,kind:g}),Q&&(Q.endTime=ee)}let Re=null;if(w.notes.length>1){const g=Array(d.tuning.length).fill(-1),I=[...g];for(const F of w.notes)[g[F.string-1],I[F.string-1]]=[F.fret,Math.max(-1,F.leftHandFinger)];const W=ke.at(-1),te=!!W&&W.frets.join()===g.join()&&Math.abs(W.endTime-q)<.01,oe=w.barreShape?w.notes.filter(F=>F.fret===w.barreFret).map(F=>F.string-1):[];Re=h.push({time:q,name:w.chord?.name??"",notes:[],frets:g,fingers:I,highDensity:te,palmMute:w.notes.every(F=>F.isPalmMute),fretHandMute:w.notes.every(F=>F.isDead),accent:Jt[Math.max(...w.notes.map(F=>F.accentuated===K.AccentuationType.Tenuto?0:F.accentuated))],strum:[null,"up","down",null,null][w.brushType]??[null,"up","down"][w.pickStroke]??null,roll:[null,null,null,"up","down"][w.brushType]??null,barre:oe.length?{fret:w.barreFret,from:Math.min(...oe),to:Math.max(...oe),half:w.barreShape===K.BarreShape.Half}:void 0})-1,te?W.endTime=ee:ke.push({startTime:q,endTime:ee,name:h[Re].name,frets:g,fingers:I})}for(const g of w.notes){const I=g.isTieDestination&&rt[g.string];if(I){I.sustain=ee-I.time;continue}const W=g.hammerPullOrigin,te=_n[g.harmonicType],oe={time:q,sustain:g.slideTarget||g.hasBend||ee-q>=.4?ee-q:0,string:g.string-1,fret:g.fret,chord:Re,hammerOn:!!W&&g.fret>W.fret,pullOff:!!W&&g.fret<W.fret,slideTo:g.slideTarget&&(g.fret||g.slideTarget.fret)?g.slideTarget.fret:null,slideUnpitchTo:null,slideIn:[null,"below","above"][g.slideInType]??null,slideOut:[null,null,null,"up","down","down","up"][g.slideOutType]??null,bend:g.hasBend?(g.maxBendPoint?.value??0)/4:0,bendCurve:g.hasBend?Xt(g.bendPoints):null,whammy:Xt(w.whammyBarPoints),harmonic:!!te&&te!=="pinch",harmonicPinch:te==="pinch",harmonicType:te,palmMute:g.isPalmMute,mute:g.isDead||w.deadSlapped,ghost:g.isGhost,staccato:g.isStaccato,letRing:g.isLetRing,accent:Jt[g.accentuated]??null,dynamic:Ue(K.DynamicValue[g.dynamics]),vibrato:g.vibrato>0||w.vibrato>0,vibratoWide:g.vibrato===K.VibratoType.Wide||w.vibrato===K.VibratoType.Wide,trill:g.trillValue>=0?g.trillValue:null,ornament:Xn[g.ornament]??null,grace:w.graceType!==K.GraceType.None,tremolo:w.isTremolo,tap:w.tap,tapLeft:g.isLeftHandTapped,slap:w.slap,pop:w.pop,golpe:[null,"thumb","finger"][w.golpe]??null,rasgueado:w.rasgueado?Ue(K.Rasgueado[w.rasgueado]):null,fade:[null,"fade in","fade out","swell"][w.fade]??null,wah:[null,"open","closed"][w.wahPedal]??null,fermata:!!w.fermata,pick:w.notes.length>1?null:[null,"up","down"][w.pickStroke]??null,showString:g.showStringNumber,finger:g.leftHandFinger>=0?g.leftHandFinger:null,rightFinger:"pimac"[g.rightHandFinger]??null};p.push(rt[g.string]=oe),bt.push(oe)}}}),H.push({time:Fe,endTime:gt,name:"",maxDifficulty:bt.length})}if(p.sort((M,u)=>M.time-u.time||M.string-u.string),p.forEach((M,u)=>M.chord!==null&&h[M.chord].notes.push(u)),d.capo){const M=u=>u>0?u+d.capo:u;for(const u of p)u.fret=M(u.fret),u.slideTo!==null&&(u.slideTo=M(u.slideTo)),u.slideUnpitchTo!==null&&(u.slideUnpitchTo=M(u.slideUnpitchTo)),typeof u.trill=="number"&&(u.trill=M(u.trill));for(const u of h)u.frets=u.frets.map(M),u.barre&&(u.barre.fret=M(u.barre.fret));for(const u of ke)u.frets=u.frets.map(M)}const ft=xt(p);$.push(...ft.anchors);for(const M of p)M.finger??=ft.fingers.get(M)??null;for(const M of h)for(const u of M.notes)M.fingers[p[u].string]<0&&p[u].finger!==null&&(M.fingers[p[u].string]=p[u].finger);return{name:(i.name||`Track ${i.index+1}`).replace(/\s+-\s+.*$/,""),program:i.playbackInfo.program,strings:d.tuning.length,open:[...d.tuning].reverse(),notes:p,chords:h,anchors:$,phrases:H,sections:st,beats:ot,handShapes:ke,markers:it,hairpins:lt,capos:ct,track:i}}),l=[];for(const i of e.tracks){for(const d of t.masterBars)for(const p of i.staves[0].bars[d.masterBar.index].voices[0].beats){if(!p.lyrics?.[0])continue;const h=o(d.start+p.playbackStart);l.push({time:h,length:o(d.start+p.playbackStart+p.playbackDuration)-h,text:p.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 Qn(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(/\+$/,""),d=i.endsWith("-");n.push({time:o,end:o+r,text:d?i.slice(0,-1):`${i} `});const p=!d&&n.reduce((h,$)=>h+$.text.length,0)>34;(l.endsWith("+")||p)&&(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 ze(e){for(const r of e.arrangements)bn(r.notes,r.chords),kn(r.notes,r.chords),vn(r.notes,r.handShapes??[]);k={...e,lines:Qn(e.lyrics)},R||(S=null),ce&&V.postMessage({song:dn()}),document.title=`${k.title||"Untitled"} – Fretfall`;const t=k.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=()=>re(+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=()=>re(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?re(t[0].program===void 0?0:o):(f=null,C("No guitar or bass parts in this file")),he("song",{file:Le,title:k.title,artist:k.artist,parts:t.map(r=>r.name),part:f?t.indexOf(f):-1}),Le=null}let G=[],_={};const N=(e,t,...n)=>{const o=Object.assign(document.createElement(e),t);return o.append(...n),o};function ea(e,t){const n=wn(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(N("span",{className:"tip",id:"tuningTip",role:"tooltip"},N("b",{textContent:vt(t)}),l,N("span",{className:"pegs",ariaHidden:"true"},...t.map((i,d)=>N("span",{className:n.shift[d]?"peg":"peg same",style:`--c: var(--s${d}); --d: ${Math.min(12,Math.abs(n.shift[d]))}`},N("i",{textContent:Mt(n.open[d])}),N("i",{className:"move",textContent:n.shift[d]?`${n.shift[d]<0?"−":"+"}${Math.abs(n.shift[d])}`:"0"}),N("strong",{textContent:Mt(i)})))),N("small",{textContent:`${n.reference} on top, this song underneath, lowest string first`}))),e}function re(e){f?.track&&x.changeTrackMute([f.track],!1),f=k.arrangements[e],f.track&&x.changeTrackMute([f.track],c.mute);for(const h in tt)delete tt[h];const t=a("arrangements");t.firstElementChild?.tagName==="SELECT"?t.firstElementChild.value=e:[...t.children].forEach((h,$)=>h.setAttribute("aria-pressed",String(e===$)));const n=[["Tuning",f.open?.length?vt(f.open):null],["Capo",f.capos?.[0]?.time===0&&f.capos[0].fret?`fret ${f.capos[0].fret}`:null],["Tempo",k.bpm?`${Math.round(k.bpm)} bpm`:null]].filter(([,h])=>h);a("info").replaceChildren(N("span",{},N("b",{textContent:k.title||"Untitled"}),k.artist?` ${k.artist}`:""),...n.map(([h,$])=>{const H=N("span",{},`${h} `,N("b",{textContent:$}));return h==="Tuning"?ea(H,f.open):H})),a("introTitle").textContent=k.title||"Untitled",a("introFacts").textContent=[k.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));G=f.phrases.map(h=>{const $=document.createElement("div");return $.style.left=J(h.time),$.style.width=`calc(${J(h.endTime)} - ${J(h.time)} - 3px)`,$.style.height=`${Math.max(8,h.maxDifficulty/i*100)}%`,$}),a("phrases").replaceChildren(...G,a("loopBox"),a("phraseTime")),U();const d=f.phrases.map(h=>f.notes.filter($=>$.time>=h.time&&$.time<h.endTime).length/Math.max(.5,h.endTime-h.time)),p=Math.max(1e-6,...d);a("progress").style.background=`linear-gradient(90deg, ${f.phrases.map((h,$)=>`color-mix(in srgb, var(--accent) ${Math.round(8+62*(d[$]/p))}%, var(--todo)) ${J(h.time)} ${J(h.endTime)}`).join(", ")})`,_={}}const J=e=>`${Math.min(100,Math.max(0,e/k.length*100)).toFixed(2)}%`,ta=e=>{const t=(n,o)=>!!e&&f.phrases[o].time>=e.start&&f.phrases[o].endTime<=e.end;return[G.findIndex(t),G.findLastIndex(t)]};function U(e=S){const t=a("loopBox");t.hidden=!e||!k,t.hidden||Object.assign(t.style,{left:`calc(${J(e.start)} - 3px)`,width:`calc(${J(e.end)} - ${J(e.start)} + 3px)`}),a("loop").setAttribute("aria-pressed",String(!!e)),R&&e===S&&JSON.stringify(S)!==JSON.stringify(X.loop)&&V.postMessage({do:"loop",loop:S}),et()}addEventListener("resize",()=>U());let ie=null;const Ce=e=>Math.min(1,Math.max(0,(e-ie.left)/ie.width))*k.length;a("phrases").onpointerenter=()=>ie=a("phrases").getBoundingClientRect(),a("phrases").onpointerleave=()=>a("phraseTime").hidden=!0;const Zt=e=>{const t=G.findIndex(n=>e<n.getBoundingClientRect().right);return t<0?G.length-1:t};let P=null;a("phrases").onpointerdown=e=>{if(!G.length||e.button!==0)return;const t=Zt(e.clientX),[n,o]=ta(S),r=a("loopBox").getBoundingClientRect(),l=!S||r.width===0?null:Math.abs(e.clientX-r.left)<6?"start":Math.abs(e.clientX-r.right)<6?"end":null,i=l==="start"&&o>=0?o:l==="end"&&n>=0?n:t,d=l?S[l==="start"?"end":"start"]:Ce(e.clientX);P={at:t,from:i,pivot:d,x:e.clientX,range:null},a("phrases").setPointerCapture(e.pointerId)},a("phrases").onpointermove=e=>{if(k&&ie&&(Object.assign(a("phraseTime"),{hidden:!1,textContent:Ie(Ce(e.clientX))}),a("phraseTime").style.left=`${Math.min(ie.width,Math.max(0,e.clientX-ie.left))}px`),!P||!P.range&&Math.abs(e.clientX-P.x)<4)return;const t=Zt(e.clientX),n=Ce(e.clientX);P.range=e.shiftKey?{start:Math.min(P.pivot,n),end:Math.max(P.pivot,n,Math.min(P.pivot,n)+.25)}:{start:f.phrases[Math.min(P.from,t)].time,end:f.phrases[Math.max(P.from,t)].endTime},U(P.range)},a("phrases").onpointerup=e=>{if(!P)return;const t=f.phrases[P.at];P.range?(S=P.range,m?.seek(S.start)):P.from===P.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},m?.seek(t.time)):m?.seek(Ce(e.clientX))),P=null,U()},a("phrases").onpointercancel=a("phrases").onlostpointercapture=()=>{P=null,U()};const Ke=/\.(mp3|m4a|aac|ogg|oga|wav|flac)$/i,_e="Opening song…";let Qt=Promise.resolve(),en=()=>{},Le=null;const Xe=[];async function Je(e,t){if(R)return C("Open songs in the window the band started from");for(const n of[...e].sort((o,r)=>Ke.test(o.name)-Ke.test(r.name)))await na(n,t)}async function na(e,t={}){if(Ke.test(e.name))return aa(e);Le=e.name;const n=Xe.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 Se(we),v.paused=null,We=x.tickCache,Qt=new Promise(l=>en=l),C(_e,!0),o?x.tex(r):x.load(r)}C(_e,!0),await Et();try{const{song:o,audio:r}=await n.open(e,{...t,status:l=>C(l,!0)});for(const l of o.arrangements){l.open=Array.from({length:l.strings},(d,p)=>(Dn[l.strings]?.[p]??40)+(l.tuning[p]??0)),l.anchors.length||(l.anchors=xt(l.notes).anchors);let i=0;for(const d of l.notes){for(;l.anchors[i+1]?.time<=d.time;)i++;const p=d.chord===null?-1:l.chords[d.chord].fingers[d.string]??-1;d.fret>0&&(d.finger??=p>=0?p:l.anchors[i]?Mn(d.fret,l.anchors[i].fret):null)}}Se(_t(r)),ze(o),C(null),tn()}catch(o){Le=null,C(`Could not open ${e.name}: ${o.message}`)}}async function aa(e){if(await Qt,!k)return C("Open the tab first, then its recording");C("Lining the recording up with the tab…",!0),await Et();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,d)=>n[d]+=i/t.numberOfChannels);const o=[...new Set(k.arrangements.flatMap(l=>l.notes.map(i=>Math.round(i.time*100)/100)))].sort((l,i)=>l-i),r=Pn(An(n,t.sampleRate),o);Se(_t(e,r)),C(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){C(`Could not use ${e.name}: ${t.message}`)}}a("open").onclick=()=>a("file").click(),a("file").onchange=e=>{Je(e.target.files),e.target.value=""};let Ae=0;addEventListener("dragenter",e=>{e.preventDefault(),Ae++,a("drop").hidden=!1}),addEventListener("dragleave",()=>{--Ae<=0&&(Ae=0,a("drop").hidden=!0)}),addEventListener("dragover",e=>e.preventDefault()),addEventListener("drop",e=>{e.preventDefault(),Ae=0,a("drop").hidden=!0,Je(e.dataTransfer.files)});let Y=null,le=null;const Ye=()=>(Y??=new AudioContext,Y.resume(),Y);function oa(e,t){if(!c.volume)return;const n=Y.createOscillator(),o=Y.createGain();n.frequency.value=t?1600:1e3,o.gain.setValueAtTime(.6*c.volume,e),o.gain.exponentialRampToValueAtTime(.001,e+.05),n.connect(o).connect(Y.destination),n.start(e),n.stop(e+.05)}function sa(e){if(!c.metronome||!m?.playing||!f||Y?.state!=="running")return void(le=null);(le===null||Math.abs(e-le)>.5)&&(le=e-.05);const t=e+.1*D;for(const n of f.beats)if(!(n.time<=le)){if(n.time>t)break;oa(Y.currentTime+Math.max(0,n.time-e)/D,n.measure>=0)}le=t}let Ze=!1;const Qe=()=>{if(!(!m||m.playing)){if(m===we&&!x.isReadyForPlayback){if(Ze)return;Ze=!0;const e=()=>{x.playerReady.off(e),Ze=!1,Qe()};return x.playerReady.on(e)}Ye(),Promise.resolve(m.play()).catch(e=>e.name!=="AbortError"&&e.name!=="NotAllowedError"&&C(e.message))}},tn=()=>{!c.autoplay||R||navigator.userActivation?.hasBeenActive!==!1&&Qe()},nn=()=>{if(m){if(Ye(),m.playing)return m.pause();Promise.resolve(m.play()).catch(e=>e.name!=="AbortError"&&C(e.message))}};function Pe(e){c[e]=!c[e],z(),Ye(),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=()=>Pe(e);const an=()=>{Pe("minimal"),se(),U()};a("minimal").onclick=an;const on=()=>{Pe("tabView"),se(),Wt()};a("tabView").onclick=on;let be=!1;function et(){const e=!!S&&D<=.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=D>.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,et()},a("clearLoop").onclick=()=>{S=null,U()};function Z(e){D=Math.min(1.5,Math.max(.1,Math.round(e*10)/10)),m?.setSpeed(D),a("speed").textContent=`${Math.round(D*100)}%`,et()}function sn(){const e=f?.phrases.findLast(t=>t.time<=nt());S=S||!e?null:{start:e.time,end:e.endTime},U()}a("play").onclick=nn,a("slower").onclick=()=>Z(D-.1),a("faster").onclick=()=>Z(D+.1),a("speed").onclick=()=>Z(1),a("loop").onclick=sn;const rn=()=>document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen(),ln=()=>{je(a("fullscreen"),document.fullscreenElement?jn:Hn)};a("fullscreen").onclick=rn,a("fullscreen").hidden=!document.fullscreenEnabled,document.onfullscreenchange=ln,ln();const ra=e=>{const t=m?.time??0,n=e.key==="ArrowRight";if(!e.shiftKey)return m?.seek(nt()+(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];m?.seek(l?.time??(n?t:0))},ia=e=>xe(Math.min(1,Math.max(0,Math.round((ue()+e)*10)/10)),!1),cn=[["Playing",[[[" "],"Space","Play or pause","",nn],[["ArrowLeft","ArrowRight"],"← →","Back or ahead 5 seconds","With Shift, a phrase at a time",ra],[["Home","0"],"Home 0","Back to the start","Of the loop, when there is one",()=>m?.seek(S?.start??0)],[["-","+","="],"− +","Slower or faster","In 10% steps",e=>Z(D+(e.key==="-"?-.1:.1))],[["r"],"R","Back to 100% speed","",()=>Z(1)],[["l"],"L","Loop the phrase you are in","Or clear the loop",sn],[["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=>k?.arrangements[e.key-1]&&re(e.key-1)]]],["Practice tools",[[["k"],"K","Metronome","",()=>Pe("metronome")],[["ArrowUp","ArrowDown"],"↑ ↓","Volume up or down","",e=>ia(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",()=>!R&&a("band").click()]]],["View",[[["h"],"H","Minimal view","Just the highway and the lyrics",an],[["d"],"D","2D tab view","Or back to the 3D highway",on],[["f"],"F","Full screen","",rn],[["o"],"O","Open a song","",()=>a("open").click()],[["s"],"S","Settings","",()=>ne("settings")],[["?"],"?","Notation and shortcuts","This sheet",()=>ne("legend")],[["Escape"],"Esc","Close what is open","Settings, this sheet, the volume or the menu",()=>{ne("settings",!1),qe(!1),pe(!1)}]]]];Ut=()=>Gt(cn.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 la=new Map(cn.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=la.get(e.key.length===1?e.key.toLowerCase():e.key);t&&(e.preventDefault(),t(e))});const V=new BroadcastChannel("fretfall-band"),ae=[],X={t:0,at:0,playing:!1,speed:1,loop:null},hn=()=>performance.timeOrigin+performance.now();let ce=!1;const dn=()=>({...k,audio:null,arrangements:k.arrangements.map(({track:e,...t})=>t)}),ca={get time(){return X.t+(X.playing?(hn()-X.at)/1e3*X.speed:0)},get playing(){return X.playing},play:()=>V.postMessage({do:"play"}),pause:()=>V.postMessage({do:"pause"}),seek:e=>V.postMessage({do:"seek",t:e}),setSpeed:e=>e!==X.speed&&V.postMessage({do:"speed",v:e}),setVolume:()=>{},stop:()=>{}};V.onmessage=({data:e})=>{if(R){if(e.bye)return close();e.song&&(e.part??ye)===ye&&(ze(e.song),f&&(re(Math.min(+ye,k.arrangements.length-1)),document.title=`${f.name} · ${document.title}`),C(null));const t=e.clock;if(!t)return;const n=t.playing!==X.playing;Object.assign(X,t),n&&Te(),t.speed!==D&&Z(t.speed),JSON.stringify(t.loop)!==JSON.stringify(S)&&(S=t.loop,U());return}ce&&(e.hello!==void 0&&V.postMessage({song:dn(),part:e.hello}),e.do==="play"&&!m.playing&&Promise.resolve(m.play()).catch(t=>t.name!=="AbortError"&&C(t.message)),e.do==="pause"&&m.playing&&m.pause(),e.do==="seek"&&m.seek(e.t),e.do==="speed"&&Z(e.v),e.do==="loop"&&(S=e.loop,U()))},a("band").onclick=()=>{ce=!0;const e=k.arrangements,t=Math.floor(screen.availWidth/e.length);let n=0;e.forEach((o,r)=>{if(ae[r]&&!ae[r].closed)return ae[r].focus();const l=new URL(location.href);l.searchParams.set("band",r),ae[r]=window.open(l,`fretfall-band-${r}`,`popup,left=${(screen.availLeft??0)+r*t},top=${screen.availTop??0},width=${t},height=${screen.availHeight}`),ae[r]||n++}),n&&C(`The browser blocked ${n} ${n>1?"windows":"window"}: allow pop-ups for this page, then press again`)},a("band").hidden=R,addEventListener("pagehide",()=>ce&&V.postMessage({bye:!0}));const un=a("highway"),tt={},nt=()=>m?m.time-c.offset/1e3:0,Ee=new Map;function ha(e){const t=k.lines,{top:n,bottom:o}=$n(t,e),r=new Map([n,o].filter(i=>i>=0).map(i=>[t[i],i===o]));let l=!1;for(const[i,d]of Ee){if(r.has(i))continue;Ee.delete(i);const p=d.at===n-1;if(!p&&n>=0){d.remove();continue}l||=p,d.classList.add("gone"),d.classList.toggle("up",p),setTimeout(()=>d.remove(),800)}for(const[i,d]of r){let p=Ee.get(i);p?p.low!==d&&(p.style.transitionDelay=""):(p=Object.assign(document.createElement("div"),{className:`gone${d?" low":""}`,at:t.indexOf(i)}),p.append(...i.syllables.map($=>Object.assign(document.createElement("span"),{textContent:$.text}))),a("lines").append(p),p.getBoundingClientRect(),l&&(p.style.transitionDelay="0.2s",setTimeout(()=>p.style.transitionDelay="",700)),Ee.set(i,p)),p.low=d,p.classList.remove("gone"),p.classList.toggle("low",d);const h=i.syllables.filter($=>$.time<=e).length;p.sung!==h&&(p.sung=h,[...p.children].forEach(($,H)=>$.classList.toggle("sung",H<h)))}}function da(e){const t=`${Ie(e)} / ${Ie(k.length)}`;_.time!==t&&(a("time").textContent=_.time=t),a("progress").firstElementChild.style.width=`${Math.min(100,e/k.length*100)}%`;const n=f.phrases.findLastIndex(i=>i.time<=e);if(_.phrase!==n&&(_.phrase=n,G.forEach((i,d)=>{i.classList.toggle("done",d<n),i.style.background=""})),G[n]){const i=Math.min(100,(e-f.phrases[n].time)/(f.phrases[n].endTime-f.phrases[n].time)*100);G[n].style.background=`linear-gradient(90deg, var(--accent) ${i}%, var(--todo) ${i}%)`}const o=f.sectionLabels[f.sections.findLastIndex(i=>i.time<=e)]??"";_.section!==o&&(a("section").textContent=_.section=o),ha(e);const r=f.notes[0]?.time??0,l=c.songIntro&&r-e>Lt(c)&&a("status").hidden?`${!!m?.playing}|${Math.ceil((r-e)/D)}`:"";_.intro!==l&&(_.intro=l,a("intro").classList.toggle("show",!!l),a("introBars").classList.toggle("moving",!!l&&!!m?.playing),l&&(a("introText").textContent=m?.playing?`Get ready · first notes in ${l.split("|")[1]}`:"Press Space to play"))}function pn(){if(m===we&&x.tickCache&&x.tickCache!==We){We=x.tickCache;const t=Zn(x.score,x.tickCache);v.tempo=t.tempo,ze(t),en(),tn(),x.score.backingTrack?.rawAudioFile?C("Playing the recording that comes with this file"):a("statusText").textContent===_e&&C(null)}const e=nt();return S&&m?.playing&&e>=S.end&&!R&&(m.seek(Math.max(0,S.start-1)),be&&Z(D+.1)),sa(e),ce&&V.postMessage({clock:{t:m.time,at:hn(),playing:m.playing,speed:D,loop:S}}),e}function at(){if(fn())return ua();requestAnimationFrame(at),On();const e=pn();At("song");const t=Fn()??e;(c.tabView?Tn:Sn)(un,f,t,L,tt),k&&f&&da(t),At("overlay"),Bn()}const fn=()=>ce&&ae.some(e=>e&&!e.closed);function ua(){document.documentElement.classList.add("banding"),a("bandNote").hidden=!1;const e=setInterval(()=>{if(fn())return pn();clearInterval(e),document.documentElement.classList.remove("banding"),a("bandNote").hidden=!0,requestAnimationFrame(at)},50)}a("bandBack").onclick=()=>{for(const e of ae)e?.close()};function he(e,t){dispatchEvent(new CustomEvent(`fretfall:${e}`,{detail:t}))}function pa(){const e=[...Xe.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)=>Je(e,t),pick:()=>a("file").click(),get playing(){return!!m?.playing},set playing(e){e?R||Qe():m?.pause()},get time(){return m?.time??0},get length(){return k?.length??0},get parts(){return k?.arrangements.map(e=>e.name)??[]},get part(){return k&&f?k.arrangements.indexOf(f):-1},set part(e){k?.arrangements[e]&&k.arrangements[e]!==f&&re(e)},closeSheets:()=>ne(null,!1),addFormat:e=>{Xe.push(e),a("file").accept+=e.extensions.map(t=>`,${t}`).join(""),pa()}},he("ready"),Se(R?ca:we),xe(),R?(V.postMessage({hello:ye}),C("Waiting for the window the band started from",!0)):C(null),new URLSearchParams(location.search).has("perf")&&En(un,()=>k?.length??0),requestAnimationFrame(at);
1
+ import{tempoMap as Mn,tickToMs as Pt,msToTick as Et,tuningName as Ot,tuningReference as xn,noteName as Bt,markRepeats as Sn,annotate as Tn,lyricsShown as Ln,markArpeggios as An}from"./music.js";import{suggestPositions as Ft,fingerFor as Cn}from"./fingering.js";import{LOOKS as We,COLORS as fe,FONTS as ze,STRINGS as xe,DEFAULT_STYLE as Dt,theme as Pn}from"./themes.js";import{drawHighway as En,drawTab as On,lookAhead as Rt,HEADSTOCKS as Se,headstockParts as Bn,spline as Fn}from"./highway.js";import{onsetEnvelope as Dn,align as Rn}from"./sync.js";import{startProfiler as Nn,startFrame as jn,lap as Nt,endFrame as Hn,benchTime as In}from"./perf.js";const a=e=>document.getElementById(e),L=document.querySelector("[data-fretfall]")??document.documentElement,Te=L!==document.documentElement,jt="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.8.4/dist/",Vn={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]},qn='<svg width="18" height="18" viewBox="0 0 20 20" fill="currentColor"><path d="M6.5 4.2v11.6L16 10z"/></svg>',Gn='<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>',Un='<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>',Wn='<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>',Ke=(e,t)=>{e.querySelector("svg")?.remove(),e.insertAdjacentHTML("afterbegin",t)},_e=e=>`${e<0?"-":""}${Math.floor(Math.abs(e)/60)}:${String(Math.floor(Math.abs(e)%60)).padStart(2,"0")}`,Ht=()=>new Promise(e=>{requestAnimationFrame(()=>setTimeout(e)),setTimeout(e,100)}),It={gradient:{label:"Gradient"},box:{label:"Box"}},Vt={frame:{label:"Frame only"},grey:{label:"Greyed marks"},hide:{label:"No marks"}},qt={scroll:{label:"Scroll"},pages:{label:"Pages"}},Gt={black:{label:"Black"},white:{label:"White"}},Ut={low:{label:"Low E on top"},high:{label:"High e on top"}},zn=8,Wt={viewAngle:30,sideAngle:5,noteSpeed:1,drawDistance:1,fretWidth:1,boardHeight:1},c={...Dt,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:.5,songIntro:!0,noteLines:.5,notes3d:!0,...Wt,viewDefaults:zn,panel:"view"},Kn={...c};try{const e=JSON.parse(localStorage.getItem("fretfall")??"{}"),t=e.viewDefaults??0;if(t<4)for(const n of Object.keys(Wt))delete e[n];t<5&&delete e.repeatMarks,t<6&&delete e.sideAngle,t<8&&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",We],["colors",fe],["fonts",ze],["strings",xe],["frames",It,"gradient"],["repeatMarks",Vt,"frame"],["markings",Gt,"black"],["tabLayout",qt,"scroll"],["stringOrder",Ut,"low"],["headstock",Se,"inline"]])t[c[e]]||(c[e]=n??Dt[e]);const U=()=>{try{localStorage.setItem("fretfall",JSON.stringify(c))}catch{}},ge=()=>c.volumeMuted?0:c.volume;let C;function oe(){C={...Pn(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},L.classList.toggle("minimal",c.minimal),L.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))L.style.setProperty(`--${t}`,n);for(const t of L.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(...Xn(t.dataset.setting,n.value).map(o=>Object.assign(document.createElement("i"),{style:`background: ${o}`})))}const _n=e=>{const t=Se[e],n=Array.from({length:6},(h,$)=>.83*(1-2*$/5)),{outline:o,ends:r,keys:l}=Bn(t,n),i=h=>(96-h*12).toFixed(1),d=h=>(28-h*12).toFixed(1),p=h=>`M${h.map(([$,H])=>`${i($)} ${d(H)}`).join("L")}`;return`<svg class="headstock" viewBox="8 -2 114 60" preserveAspectRatio="xMinYMid meet" aria-hidden="true" fill="currentColor" stroke="currentColor">${[...l.map(({u:h,side:$,edge:H})=>`<ellipse cx="${i(h)}" cy="${d(H+$*.36)}" rx="1.5" ry="2.6" fill-opacity=".35" stroke="none"/>`),`<path d="${p(o)}Z" fill-opacity=".1" stroke-width="1.2"/>`,t.cover&&`<path d="${p(Fn(t.cover))}Z" fill="none" stroke-width=".6"/>`,`<path d="${p([[0,1],[-2.1,1]])} ${p([[0,-1],[-2.1,-1]])}" fill="none" stroke-width="1.2"/>`,`<path d="${p([[0,1.05],[0,-1.05]])}" stroke-width="2.4"/>`,...n.map((h,$)=>`<path d="${p([r[$],[0,h],[-2.1,h]])}" fill="none" stroke-width=".7"/>`),...r.map(([h,$])=>`<circle cx="${i(h)}" cy="${d($)}" r="1.1" stroke="none"/>`)].filter(Boolean).join("")}</svg>`},Xn=(e,t)=>{if(e==="strings")return(xe[t].str??fe[c.colors].str).slice(0,6);const n=fe[t];return[n.floor0,n.lane,n.accent,n.text]};for(const e of L.querySelectorAll("[data-setting]")){const t=e.dataset.setting,n={look:We,colors:fe,fonts:ze,strings:xe,frames:It,repeatMarks:Vt,markings:Gt,tabLayout:qt,stringOrder:Ut,headstock:Se}[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",_n(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 Le=e=>e.style.setProperty("--fill",`${(e.value-e.min)/(e.max-e.min)*100}%`),Xe={viewAngle:e=>`${e}°`,sideAngle:e=>e?`${Math.abs(e)}° ${e<0?"left":"right"}`:"Straight",noteSpeed:e=>`${Math.round(e*100)}%`,drawDistance:()=>`${Rt(c).toFixed(1)} s`,fretWidth:e=>`${Math.round(e*100)}%`,boardHeight:e=>`${Math.round(e*100)}%`,noteLines:e=>`${Math.round(e*100)}%`,fretNumbers:e=>`${Math.round(e*100)}%`},zt=()=>{for(const[e,t]of Object.entries(Xe))a(`${e}Out`).value=t(c[e])},Kt=e=>{for(const[t,n]of Object.entries(e))C&&(C[t]=n),c[t]=a(t).value=n,Le(a(t));zt(),U()};for(const e in Xe)a(e).value=c[e],Le(a(e)),a(e).oninput=t=>Kt({[e]:+t.target.value});zt(),a("resetSettings").onclick=()=>{const{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l}=c;Object.assign(c,Kn,{volume:e,volumeMuted:t,metronome:n,minimal:o,tabView:r,panel:l});for(const i of["guides","notes3d","songIntro","mute","autoplay"])a(i).checked=c[i];f?.track&&M.changeTrackMute([f.track],c.mute),Ae(c.offset),oe(),Kt(Object.fromEntries(Object.keys(Xe).map(i=>[i,c[i]])))};const Ae=e=>{c.offset=a("offset").value=e,a("offsetOut").value=`${e>0?"+":""}${e} ms`,Le(a("offset")),U()};Ae(c.offset),a("offset").oninput=e=>Ae(+e.target.value),a("resetOffset").onclick=()=>Ae(0);const Jn=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 Ce(e=c.volume,t=c.volumeMuted){Object.assign(c,{volume:e,volumeMuted:t||e===0}),a("volume").value=ge(),a("volumeOut").value=`${Math.round(ge()*100)}%`,Le(a("volume"));const n=Jn(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"/>');Ke(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"),m?.setVolume(ge()),U()}const Je=(e=a("volumePanel").hidden)=>{a("volumePanel").hidden=!e,a("volumeButton").setAttribute("aria-expanded",String(e))};a("volume").oninput=e=>Ce(+e.target.value,!1),a("volumeButton").onclick=()=>Je(),a("volumeMute").onclick=()=>Ce(c.volume||.8,!c.volumeMuted);for(const e of["guides","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 Ye=e=>{for(const t of L.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 L.querySelectorAll("[data-tab]"))e.onclick=()=>Ye(e.dataset.tab),e.onkeydown=t=>{const n=[...L.querySelectorAll("[data-tab]")],o={ArrowRight:1,ArrowLeft:-1}[t.key];if(!o)return;const r=n[(n.indexOf(e)+o+n.length)%n.length];Ye(r.dataset.tab),r.focus()};Ye(L.querySelector(`[data-tab="${c.panel}"]`)?c.panel:"view");const _t={settings:"settingsButton",legend:"legendButton"};function se(e,t=a(e).hidden){for(const[n,o]of Object.entries(_t)){const r=n===e&&t,l=a(n).hidden===r;a(n).hidden=!r,a(o).setAttribute("aria-expanded",String(r)),l&&ue("sheet",{name:n,open:r})}}for(const[e,t]of Object.entries(_t))a(t).onclick=()=>se(e),a(`${e}Close`).onclick=()=>{se(e,!1),a(t).focus()};const Yn=matchMedia("(max-width: 900px)"),Xt=matchMedia("(max-width: 560px)"),me=(e=a("menuButton").ariaExpanded!=="true")=>a("menuButton").setAttribute("aria-expanded",String(e)),Ze=()=>{Z.length||(Xt.matches?L.querySelector(".speed").before(a("arrangements")):L.querySelector(".wordmark").after(a("arrangements")),me(!1))},Qe=[a("arrangements"),...L.querySelectorAll(".bar > .group:last-child > :not(#menuButton, #menu), #menu > *")],Zn=new Set(["legendButton","settingsButton","fullscreen"]);let Z=[],Pe=[];function Qn(e,t=e){for(let n=Pe.length;n--;)Pe[n][0].insertBefore(Qe[n],Pe[n][1]);for(const n of Z)for(const o of["data-fretfall","data-fretfall-dock"])n.removeAttribute(o);Z=e?[...new Set([e,t])]:[],Pe=Z.length?Qe.map(n=>[n.parentNode,n.nextSibling]):[];for(const n of Z)for(const o of["data-fretfall","data-fretfall-dock"])n.setAttribute(o,"");if(Z.length)for(const n of Qe)(Zn.has(n.id)?t:e).append(n);L.querySelector(".bar").hidden=Z.length>0,L.toggleAttribute("data-docked",Z.length>0),Ze()}Yn.onchange=Xt.onchange=Ze,Ze(),a("menuButton").onclick=()=>me(),a("menu").onclick=e=>{e.target.closest("button")&&me(!1)},addEventListener("pointerdown",e=>{e.target.closest(".sheet, #settingsButton, #legendButton")||se("settings",!1),e.target.closest(".volume")||Je(!1),e.target.closest("#menu, #menuButton")||me(!1)});const s=e=>`<svg viewBox="0 0 56 32" aria-hidden="true">${e}</svg>`,T=(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>`:""}`,B=(e,t=13,n="ft",o="")=>`<text x="28" y="16.5" class="${n}" font-size="${t}" ${o}>${e}</text>`,ye=(e,t=11)=>`${T(16,17,24,11)}<text x="28" y="9" class="ft" font-size="${t}">${e}</text>`,ea=[["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(`${T(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(`${T(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"/>${T(14,16,12,5,0)}${T(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"/>${T(14,16,12,5,0)}${T(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"/>${T(14,20,12,5,1)}<path d="M20 20V8" class="l1" stroke-width="1.6" stroke-dasharray="3 2"/>${T(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(B("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>')],["Capo","Clamped across the neck: from there the strings play open, and the dark frets behind it are out of play.",s('<path d="M4 9h14M4 14h14M4 19h14M4 24h14" class="lt" stroke-width="1" opacity=".15"/><path d="M18 9h32M18 14h32M18 19h32M18 24h32" class="lt" stroke-width="1" opacity=".5"/><path d="M32 6v20M46 6v20" class="lt" stroke-width="1" opacity=".3"/><path d="M18 4v24" class="lt" stroke-width="4.5"/>')],["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(`${T(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(`${T(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(B("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(`${T(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(B("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(B("slap",12))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(B("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(`${T(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(`${T(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(`${T(4,19,16,8)}${T(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(`${T(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"/>${T(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(`${T(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(`${T(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(B("tr 7",13,"ft",'font-style="italic"'))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(B("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"/>${T(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"/>${T(16,18,24,10)}`)],["Vibrato","Shake the note. A taller wave means a wider vibrato.",s(`${T(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(`${T(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(`${T(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(B("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(ye("PH",10))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(ye("AH",10))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(ye("TH",10))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(ye("SH",10))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(ye("&gt;",12))],["Staccato","Short: stop the note right after you play it.",s(`${T(16,17,24,11)}<circle cx="28" cy="9" r="2.2" class="ft"/>`)],["Ghost note","Barely audible: felt more than heard.",s(B("(7)",14,"f1"))],["Dynamics","How loud, from very soft (pp) to very loud (ff). Shown where it changes.",s(B("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(B("swell",12))],["Wah","Wah pedal open (o) or closed (+).",s(B("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(B("♩ = 120",11,"fa"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(B("repeat ×2",10,"fa"))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(B("D.S. al coda",8,"fa"))],["8va and loco","Play an octave higher, until loco.",s(B("8va",13,"fa"))],["Text","Instructions written in the file, like pizz. or bass stop.",s(B("pizz.",13,"fa"))]]]],k=(...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>`,y=(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):""}`,we=(e,t,n='class="lt" stroke-width="1.5" opacity=".7"')=>`<path d="M14 ${e}h-3v${t-e}h3" ${n}/>`,F=(e,t=9,n="")=>`${k(23)}${y(22,23,12,1,"5")}${P(28,10,e,t,"ft",n)}`,be=(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")}`,ta=[["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(`${k(16)}${y(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(`${k(16)}${y(4,16,12,1,"3",'opacity=".4"')}<path d="M21 3v26" class="la" stroke-width="2.5"/>${y(26,16,24,1,"5")}`)],["Open string","Play the string without fretting it.",s(`${k(16)}${y(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(`${k(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(`${k(9,23)}${we(3,29)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Accented chord","Strum it harder: its bracket is white, and thicker for a heavy accent.",s(`${k(9,23)}${we(3,29,'class="lw" stroke-width="2.5"')}${y(16,9,14,1,"5")}${y(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(`${k(10,22)}<rect x="5" y="3" width="46" height="26" rx="4" class="lt" stroke-width="1" stroke-dasharray="3 2" opacity=".55"/>${y(9,22,10,2,"2")}${y(24,10,10,1,"1")}${y(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(`${k(14,25)}${we(9,30)}${P(21,4.5,"½B5",7,"ft")}${y(16,14,14,1,"5")}${y(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(`${k(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(`${k(16)}<circle cx="15" cy="16" r="6.5" class="l2" stroke-width="1.2"/>${P(15,16,"3",7,"f2")}${y(26,16,16,2,"5")}`)],["Bar number","Each bar line is numbered along the top of the tab.",s(`${k(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(F("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(`${k(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(`${k(23)}${y(8,23,12,1,"5")}${y(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(F("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(`${k(9,23)}<path d="M5 4v22M2.5 22.5l2.5 3.5 2.5-3.5" class="lt" stroke-width="1.4"/>${we(3,29)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Rolled chord","Roll the chord: sound the strings one after another, quickly. The arrow beside it is wavy.",s(`${k(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"/>${we(3,29)}${y(16,9,14,1,"5")}${y(16,23,14,2,"7")}`)],["Tremolo picking","Pick the note as fast as you can for its whole length: three slashes over it.",s(`${k(24)}${y(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(F("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(F("slap",8))],["Golpe","Tap the body of the guitar with your thumb or a finger.",s(F("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(`${k(23)}${y(4,23,12,1,"5")}${y(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(`${k(23)}${y(4,23,12,1,"7")}${y(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(`${k(12)}${y(4,12,12,1,"5")}${y(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(`${k(16)}${y(4,16,22,1,"5")}<path d="M29 20l8-8" class="lt" stroke-width="1.6"/>${y(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(`${k(16)}<path d="M8 20l8-8" class="lt" stroke-width="1.6"/>${y(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(F("T"))],["Grace note","A quick note played just before the beat: a smaller note.",s(`${k(16)}<rect x="8" y="12.4" width="9" height="7.2" rx="1.5" class="f2"/>${P(12.5,16,"5",6)}${y(22,16,24,2,"7")}`)],["Trill","Alternate quickly between the note and the fret shown.",s(F("tr 7",8))],["Turn and mordent","Ornaments: a quick flick to the neighbouring notes and back.",s(F("turn",8))]]],["Sustain and pitch",[["Sustain","Keep the note ringing for as long as its bar.",s(`${k(16)}${y(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(`${k(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"/>${y(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(`${k(22)}${y(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(`${k(23)}${y(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(`${k(23)}${y(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(`${k(23)}${y(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(F("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(`${k(16)}${y(12,16,32,4,"&lt;12&gt;","",28)}`)],["Pinch harmonic","Catch the string with the side of your thumb just after the pick.",s(F("PH",8))],["Artificial harmonic","Fret the note and touch the string 12 frets higher while you pick.",s(F("AH",8))],["Tapped harmonic","Fret the note and tap the string 12 frets higher.",s(F("TH",8))],["Semi and feedback harmonics","SH: half harmonic, half note. FH: hold it until it feeds back.",s(F("SH",8))]]],["Articulation and expression",[["Accent","Play it louder. ^ is a strong accent; – means hold it for its full length.",s(F("&gt;",11))],["Staccato","Short: stop the note right after you play it: a dot over it.",s(`${k(23)}${y(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(`${k(16)}${y(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(`${k(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(`${k(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(F("swell",8))],["Wah","Wah pedal open (o) or closed (+).",s(F("o +",9))],["Fermata","Hold the note longer than written.",s(`${k(24)}${y(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(be("♩ = 120"))],["Repeats and endings","Where a repeat starts, how many times, and which ending you are on.",s(be("repeat ×2",7))],["Jumps","Road signs to follow: segno, coda, D.C. (from the start), D.S. (from the segno), fine.",s(be("D.S. al coda",6))],["8va and loco","Play an octave higher, until loco.",s(be("8va",9))],["Text","Instructions written in the file, like pizz. or bass stop.",s(be("pizz.",9))]]]],Jt=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 Yt=()=>[];const Zt=()=>{a("legendList").replaceChildren(...Jt(c.tabView?ta:ea),...Yt(),Object.assign(document.createElement("p"),{className:"legend-empty",textContent:"Nothing matches.",hidden:!0})),a("legendFind").oninput({target:a("legendFind")})};oe();let Qt;function A(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,m=null,D=1,S=null,en=!1;const ke=new URLSearchParams(location.search).get("band"),N=ke!==null;function tn(e,{offset:t=0,ratio:n=1}={}){const o=new Audio(URL.createObjectURL(e));return o.preservesPitch=!0,o.onplay=o.onpause=Be,o.onended=()=>ue("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:jt+"font/"},display:{staveProfile:"Tab"},player:{playerMode:N?"Disabled":"EnabledAutomatic",soundFont:jt+"soundfont/sonivox.sf2",enableCursor:!1,enableUserInteraction:!1,scrollMode:"Off"}}),v={tick:0,at:0,playing:!1,heard:!1,tempo:[],paused:null},Ee=e=>e.staves.find(t=>t.isStringed&&!t.isPercussion);M.scoreLoaded.on(e=>{for(const n of e.tracks.filter(Ee))n.unplayed=n.staves.filter(o=>!o.isStringed),n.staves=n.staves.filter(o=>o.isStringed),n.staves.forEach((o,r)=>o.index=r);const t=e.tracks.find(Ee);t?.index&&M.renderTracks([t])}),M.playerPositionChanged.on(e=>{v.paused===null&&([v.tick,v.at]=[e.currentTick,performance.now()],e.isSeek||(v.heard=!0))}),M.playerStateChanged.on(e=>{v.playing=e.state===alphaTab.synth.PlayerState.Playing,v.heard=!1,v.at=performance.now(),Be()}),M.playerFinished.on(()=>ue("ended")),M.soundFontLoad.on(e=>A(e.total?`Loading instrument sounds ${Math.round(e.loaded/e.total*100)}%`:"Loading instrument sounds",!0)),M.playerReady.on(()=>A(null)),M.error.on(e=>A(`Could not read that file: ${e.message}`));const $e={get time(){return v.tempo.length?(Pt(v.tempo,v.tick)+(v.playing&&v.heard&&v.paused===null?Math.min(performance.now()-v.at,100)*M.playbackSpeed:0))/1e3:0},get playing(){return v.playing},play:()=>(v.paused!==null&&(M.tickPosition=v.paused),v.paused=null,M.play()),pause:()=>{v.tick=v.paused=Et(v.tempo,$e.time*1e3),v.at=performance.now(),M.pause()},seek:e=>{v.tick=Et(v.tempo,Math.max(0,e)*1e3),v.at=performance.now(),v.heard=!1,v.paused!==null&&(v.paused=v.tick),M.tickPosition=v.tick},setSpeed:e=>{M.playbackSpeed=e},setVolume:e=>{M.masterVolume=e},stop:()=>{v.paused=null,M.stop()}};function Oe(e){m&&m!==e&&m.stop(),m=e,m.setSpeed(D),m.setVolume(ge()),Be()}function Be(){Ke(a("play"),m?.playing?Gn:qn),a("play").dataset.label=a("play").ariaLabel=m?.playing?"Pause":"Play",!!m?.playing!==en&&ue("playing",{playing:en=!!m?.playing})}const _=alphaTab.model,et=(e="")=>e.replace(/([a-z])([A-Z0-9])/g,"$1 $2").toLowerCase(),nn=e=>e?.length?e.map(t=>[t.offset/60,t.value/4]):null,na=[null,"natural","artificial","pinch","tap","semi","feedback"],an=[null,"accent","heavy","tenuto"],aa=[null,"inverted turn","turn","upper mordent","lower mordent"],on=["15ma","8va",null,"8vb","15mb"],oa={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♯"]},sa=e=>_.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 tt=null;function ra(e,t){const n=Mn(t.masterBars.flatMap(i=>i.tempoChanges),e.tempo),o=i=>Pt(n,i)/1e3,r=e.tracks.filter(Ee).map(i=>{const d=Ee(i),p=[],h=[],$=[],H=[],ft=[],gt=[],Me=[],mt={},yt=[],wt=[],bt=d.capo?[{time:0,fret:d.capo}]:[],kt={};let $t="4/4",vt="0:0",Ie=e.tempo,Mt=0,Ve=null,te=null;for(const x of t.masterBars){const u=x.masterBar,pe=d.bars[u.index],qe=o(x.start),St=o(x.end),$n=(x.end-x.start)/u.timeSignatureNumerator;for(let O=0;O<u.timeSignatureNumerator;O++)ft.push({time:o(x.start+O*$n),measure:O?-1:u.index+1});u.section&&gt.push({time:qe,name:u.section.text});const I=(O,Ge=qe)=>yt.push({time:Ge,text:O}),vn=kt[u.index]=(kt[u.index]??0)+1,Tt=`${u.timeSignatureNumerator}/${u.timeSignatureDenominator}`;Tt!==$t&&I($t=Tt);const Lt=`${pe.keySignature}:${pe.keySignatureType}`,At=pe.keySignatureType?"minor":"major";Lt!==vt&&(I(`${oa[At][pe.keySignature+7]} ${At}`),vt=Lt);for(const O of x.tempoChanges)Math.round(O.tempo)!==Math.round(Ie)&&(Ie=O.tempo,I(`♩ = ${Math.round(Ie)}`,o(O.tick)));u.tripletFeel!==Mt&&(I(u.tripletFeel?`${et(_.TripletFeel[u.tripletFeel])} feel`:"straight"),Mt=u.tripletFeel),u.isRepeatStart&&I(`repeat · pass ${vn}`),u.repeatCount>0&&I(`repeat ×${u.repeatCount}`,St),u.alternateEndings&&I(`ending ${[0,1,2,3,4,5,6,7].filter(O=>u.alternateEndings&1<<O).map(O=>O+1).join(", ")}`);for(const O of u.directions??[])I(sa(O));u.isFreeTime&&I("free time");const Ct=[];pe.voices.forEach((O,Ge)=>{for(const w of O.beats){const G=o(x.start+w.playbackStart),ne=o(x.start+w.playbackStart+w.playbackDuration);if(!Ge){if(w.text){I(w.text,G);const V=/^\s*capo\s*(\d+|off)\s*$/i.exec(w.text);V&&bt.push({time:G,fret:/off/i.test(V[1])?0:+V[1]})}on[w.ottava]!==Ve&&(Ve=on[w.ottava],I(Ve??"loco",G));const g=[null,"cresc","dim"][w.crescendo];te&&te.kind!==g&&(te=null),g&&!te&&wt.push(te={time:G,endTime:ne,kind:g}),te&&(te.endTime=ne)}let Ue=null;if(w.notes.length>1){const g=Array(d.tuning.length).fill(-1),V=[...g];for(const R of w.notes)[g[R.string-1],V[R.string-1]]=[R.fret,Math.max(-1,R.leftHandFinger)];const K=Me.at(-1),ae=!!K&&K.frets.join()===g.join()&&Math.abs(K.endTime-G)<.01,ie=w.barreShape?w.notes.filter(R=>R.fret===w.barreFret).map(R=>R.string-1):[];Ue=h.push({time:G,name:w.chord?.name??"",notes:[],frets:g,fingers:V,highDensity:ae,palmMute:w.notes.every(R=>R.isPalmMute),fretHandMute:w.notes.every(R=>R.isDead),accent:an[Math.max(...w.notes.map(R=>R.accentuated===_.AccentuationType.Tenuto?0:R.accentuated))],strum:[null,"up","down",null,null][w.brushType]??[null,"up","down"][w.pickStroke]??null,roll:[null,null,null,"up","down"][w.brushType]??null,barre:ie.length?{fret:w.barreFret,from:Math.min(...ie),to:Math.max(...ie),half:w.barreShape===_.BarreShape.Half}:void 0})-1,ae?K.endTime=ne:Me.push({startTime:G,endTime:ne,name:h[Ue].name,frets:g,fingers:V})}for(const g of w.notes){const V=g.isTieDestination&&mt[g.string];if(V){V.sustain=ne-V.time;continue}const K=g.hammerPullOrigin,ae=na[g.harmonicType],ie={time:G,sustain:g.slideTarget||g.hasBend||ne-G>=.4?ne-G:0,string:g.string-1,fret:g.fret,chord:Ue,hammerOn:!!K&&g.fret>K.fret,pullOff:!!K&&g.fret<K.fret,slideTo:g.slideTarget&&(g.fret||g.slideTarget.fret)?g.slideTarget.fret:null,slideUnpitchTo:null,slideIn:[null,"below","above"][g.slideInType]??null,slideOut:[null,null,null,"up","down","down","up"][g.slideOutType]??null,bend:g.hasBend?(g.maxBendPoint?.value??0)/4:0,bendCurve:g.hasBend?nn(g.bendPoints):null,whammy:nn(w.whammyBarPoints),harmonic:!!ae&&ae!=="pinch",harmonicPinch:ae==="pinch",harmonicType:ae,palmMute:g.isPalmMute,mute:g.isDead||w.deadSlapped,ghost:g.isGhost,staccato:g.isStaccato,letRing:g.isLetRing,accent:an[g.accentuated]??null,dynamic:et(_.DynamicValue[g.dynamics]),vibrato:g.vibrato>0||w.vibrato>0,vibratoWide:g.vibrato===_.VibratoType.Wide||w.vibrato===_.VibratoType.Wide,trill:g.trillValue>=0?g.trillValue:null,ornament:aa[g.ornament]??null,grace:w.graceType!==_.GraceType.None,tremolo:w.isTremolo,tap:w.tap,tapLeft:g.isLeftHandTapped,slap:w.slap,pop:w.pop,golpe:[null,"thumb","finger"][w.golpe]??null,rasgueado:w.rasgueado?et(_.Rasgueado[w.rasgueado]):null,fade:[null,"fade in","fade out","swell"][w.fade]??null,wah:[null,"open","closed"][w.wahPedal]??null,fermata:!!w.fermata,pick:w.notes.length>1?null:[null,"up","down"][w.pickStroke]??null,showString:g.showStringNumber,finger:g.leftHandFinger>=0?g.leftHandFinger:null,rightFinger:"pimac"[g.rightHandFinger]??null};p.push(mt[g.string]=ie),Ct.push(ie)}}}),H.push({time:qe,endTime:St,name:"",maxDifficulty:Ct.length})}if(p.sort((x,u)=>x.time-u.time||x.string-u.string),p.forEach((x,u)=>x.chord!==null&&h[x.chord].notes.push(u)),d.capo){const x=u=>u>0?u+d.capo:u;for(const u of p)u.fret=x(u.fret),u.slideTo!==null&&(u.slideTo=x(u.slideTo)),u.slideUnpitchTo!==null&&(u.slideUnpitchTo=x(u.slideUnpitchTo)),typeof u.trill=="number"&&(u.trill=x(u.trill));for(const u of h)u.frets=u.frets.map(x),u.barre&&(u.barre.fret=x(u.barre.fret));for(const u of Me)u.frets=u.frets.map(x)}const xt=Ft(p);$.push(...xt.anchors);for(const x of p)x.finger??=xt.fingers.get(x)??null;for(const x of h)for(const u of x.notes)x.fingers[p[u].string]<0&&p[u].finger!==null&&(x.fingers[p[u].string]=p[u].finger);return{name:(i.name||`Track ${i.index+1}`).replace(/\s+-\s+.*$/,""),program:i.playbackInfo.program,strings:d.tuning.length,open:[...d.tuning].reverse(),notes:p,chords:h,anchors:$,phrases:H,sections:gt,beats:ft,handShapes:Me,markers:yt,hairpins:wt,capos:bt,track:i}}),l=[];for(const i of e.tracks.flatMap(d=>[...d.staves,...d.unplayed??[]])){for(const d of t.masterBars)for(const p of i.bars[d.masterBar.index].voices.flatMap(h=>h.beats)){if(!p.lyrics?.[0])continue;const h=o(d.start+p.playbackStart);l.push({time:h,length:o(d.start+p.playbackStart+p.playbackDuration)-h,text:p.lyrics[0]})}if(l.length)break}return l.sort((i,d)=>i.time-d.time),{title:e.title,artist:e.artist,bpm:e.tempo,length:o(t.masterBars.at(-1)?.end??0),arrangements:r,lyrics:l,tempo:n}}function ia(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(/\+$/,""),d=i.endsWith("-");n.push({time:o,end:o+r,text:d?i.slice(0,-1):`${i} `});const p=!d&&n.reduce((h,$)=>h+$.text.length,0)>34;(l.endsWith("+")||p)&&(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 nt(e){for(const r of e.arrangements)Sn(r.notes,r.chords),Tn(r.notes,r.chords),An(r.notes,r.handShapes??[]);b={...e,lines:ia(e.lyrics)},N||(S=null),de&&q.postMessage({song:wn()}),Te||(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=()=>le(+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=()=>le(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?le(t[0].program===void 0?0:o):(f=null,A("No guitar or bass parts in this file")),ue("song",{file:De,title:b.title,artist:b.artist,parts:t.map(r=>r.name),part:f?t.indexOf(f):-1}),De=null}let W=[],X={};const j=(e,t,...n)=>{const o=Object.assign(document.createElement(e),t);return o.append(...n),o};function la(e,t){const n=xn(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:Ot(t)}),l,j("span",{className:"pegs",ariaHidden:"true"},...t.map((i,d)=>j("span",{className:n.shift[d]?"peg":"peg same",style:`--c: var(--s${d}); --d: ${Math.min(12,Math.abs(n.shift[d]))}`},j("i",{textContent:Bt(n.open[d])}),j("i",{className:"move",textContent:n.shift[d]?`${n.shift[d]<0?"−":"+"}${Math.abs(n.shift[d])}`:"0"}),j("strong",{textContent:Bt(i)})))),j("small",{textContent:`${n.reference} on top, this song underneath, lowest string first`}))),e}function le(e){f?.track&&M.changeTrackMute([f.track],!1),f=b.arrangements[e],f.track&&M.changeTrackMute([f.track],c.mute);for(const h in dt)delete dt[h];const t=a("arrangements");t.firstElementChild?.tagName==="SELECT"?t.firstElementChild.value=e:[...t.children].forEach((h,$)=>h.setAttribute("aria-pressed",String(e===$)));const n=[["Tuning",f.open?.length?Ot(f.open):null],["Capo",f.capos?.[0]?.time===0&&f.capos[0].fret?`fret ${f.capos[0].fret}`: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,$])=>{const H=j("span",{},`${h} `,j("b",{textContent:$}));return h==="Tuning"?la(H,f.open):H})),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));W=f.phrases.map(h=>{const $=document.createElement("div");return $.style.left=Q(h.time),$.style.width=`calc(${Q(h.endTime)} - ${Q(h.time)} - 3px)`,$.style.height=`${Math.max(8,h.maxDifficulty/i*100)}%`,$}),a("phrases").replaceChildren(...W,a("loopBox"),a("phraseTime")),z();const d=f.phrases.map(h=>f.notes.filter($=>$.time>=h.time&&$.time<h.endTime).length/Math.max(.5,h.endTime-h.time)),p=Math.max(1e-6,...d);a("progress").style.background=`linear-gradient(90deg, ${f.phrases.map((h,$)=>`color-mix(in srgb, var(--accent) ${Math.round(8+62*(d[$]/p))}%, var(--todo)) ${Q(h.time)} ${Q(h.endTime)}`).join(", ")})`,X={}}const Q=e=>`${Math.min(100,Math.max(0,e/b.length*100)).toFixed(2)}%`,ca=e=>{const t=(n,o)=>!!e&&f.phrases[o].time>=e.start&&f.phrases[o].endTime<=e.end;return[W.findIndex(t),W.findLastIndex(t)]};function z(e=S){const t=a("loopBox");t.hidden=!e||!b,t.hidden||Object.assign(t.style,{left:`calc(${Q(e.start)} - 3px)`,width:`calc(${Q(e.end)} - ${Q(e.start)} + 3px)`}),a("loop").setAttribute("aria-pressed",String(!!e)),N&&e===S&&JSON.stringify(S)!==JSON.stringify(Y.loop)&&q.postMessage({do:"loop",loop:S}),ht()}addEventListener("resize",()=>z());let ce=null;const Fe=e=>Math.min(1,Math.max(0,(e-ce.left)/ce.width))*b.length;a("phrases").onpointerenter=()=>ce=a("phrases").getBoundingClientRect(),a("phrases").onpointerleave=()=>a("phraseTime").hidden=!0;const sn=e=>{const t=W.findIndex(n=>e<n.getBoundingClientRect().right);return t<0?W.length-1:t};let E=null;a("phrases").onpointerdown=e=>{if(!W.length||e.button!==0)return;const t=sn(e.clientX),[n,o]=ca(S),r=a("loopBox").getBoundingClientRect(),l=!S||r.width===0?null:Math.abs(e.clientX-r.left)<6?"start":Math.abs(e.clientX-r.right)<6?"end":null,i=l==="start"&&o>=0?o:l==="end"&&n>=0?n:t,d=l?S[l==="start"?"end":"start"]:Fe(e.clientX);E={at:t,from:i,pivot:d,x:e.clientX,range:null},a("phrases").setPointerCapture(e.pointerId)},a("phrases").onpointermove=e=>{if(b&&ce&&(Object.assign(a("phraseTime"),{hidden:!1,textContent:_e(Fe(e.clientX))}),a("phraseTime").style.left=`${Math.min(ce.width,Math.max(0,e.clientX-ce.left))}px`),!E||!E.range&&Math.abs(e.clientX-E.x)<4)return;const t=sn(e.clientX),n=Fe(e.clientX);E.range=e.shiftKey?{start:Math.min(E.pivot,n),end:Math.max(E.pivot,n,Math.min(E.pivot,n)+.25)}:{start:f.phrases[Math.min(E.from,t)].time,end:f.phrases[Math.max(E.from,t)].endTime},z(E.range)},a("phrases").onpointerup=e=>{if(!E)return;const t=f.phrases[E.at];E.range?(S=E.range,m?.seek(S.start)):E.from===E.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},m?.seek(t.time)):m?.seek(Fe(e.clientX))),E=null,z()},a("phrases").onpointercancel=a("phrases").onlostpointercapture=()=>{E=null,z()};const at=/\.(mp3|m4a|aac|ogg|oga|wav|flac)$/i,ot="Opening song…";let rn=Promise.resolve(),ln=()=>{},De=null;const st=[];async function rt(e,t){if(N)return A("Open songs in the window the band started from");for(const n of[...e].sort((o,r)=>at.test(o.name)-at.test(r.name)))await ha(n,t)}async function ha(e,t={}){if(at.test(e.name))return da(e);De=e.name;const n=st.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 Oe($e),v.paused=null,tt=M.tickCache,rn=new Promise(l=>ln=l),A(ot,!0),o?M.tex(r):M.load(r)}A(ot,!0),await Ht();try{const{song:o,audio:r}=await n.open(e,{...t,status:l=>A(l,!0)});for(const l of o.arrangements){l.open=Array.from({length:l.strings},(d,p)=>(Vn[l.strings]?.[p]??40)+(l.tuning[p]??0)),l.anchors.length||(l.anchors=Ft(l.notes).anchors);let i=0;for(const d of l.notes){for(;l.anchors[i+1]?.time<=d.time;)i++;const p=d.chord===null?-1:l.chords[d.chord].fingers[d.string]??-1;d.fret>0&&(d.finger??=p>=0?p:l.anchors[i]?Cn(d.fret,l.anchors[i].fret):null)}}Oe(tn(r)),nt(o),A(null),cn()}catch(o){De=null,A(`Could not open ${e.name}: ${o.message}`)}}async function da(e){if(await rn,!b)return A("Open the tab first, then its recording");A("Lining the recording up with the tab…",!0),await Ht();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,d)=>n[d]+=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=Rn(Dn(n,t.sampleRate),o);Oe(tn(e,r)),A(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){A(`Could not use ${e.name}: ${t.message}`)}}a("open").onclick=()=>a("file").click(),a("file").onchange=e=>{rt(e.target.files),e.target.value=""};let Re=0;L.addEventListener("dragenter",e=>{e.preventDefault(),Re++,a("drop").hidden=!1}),L.addEventListener("dragleave",()=>{--Re<=0&&(Re=0,a("drop").hidden=!0)}),L.addEventListener("dragover",e=>e.preventDefault()),L.addEventListener("drop",e=>{e.preventDefault(),Re=0,a("drop").hidden=!0,rt(e.dataTransfer.files)});let ee=null,he=null;const it=()=>(ee??=new AudioContext,ee.resume(),ee);function ua(e,t){if(!c.volume)return;const n=ee.createOscillator(),o=ee.createGain();n.frequency.value=t?1600:1e3,o.gain.setValueAtTime(.6*c.volume,e),o.gain.exponentialRampToValueAtTime(.001,e+.05),n.connect(o).connect(ee.destination),n.start(e),n.stop(e+.05)}function pa(e){if(!c.metronome||!m?.playing||!f||ee?.state!=="running")return void(he=null);(he===null||Math.abs(e-he)>.5)&&(he=e-.05);const t=e+.1*D;for(const n of f.beats)if(!(n.time<=he)){if(n.time>t)break;ua(ee.currentTime+Math.max(0,n.time-e)/D,n.measure>=0)}he=t}let lt=!1;const ct=()=>{if(!(!m||m.playing)){if(m===$e&&!M.isReadyForPlayback){if(lt)return;lt=!0;const e=()=>{M.playerReady.off(e),lt=!1,ct()};return M.playerReady.on(e)}it(),Promise.resolve(m.play()).catch(e=>e.name!=="AbortError"&&e.name!=="NotAllowedError"&&A(e.message))}},cn=()=>{!c.autoplay||N||navigator.userActivation?.hasBeenActive!==!1&&ct()},hn=()=>{if(m){if(it(),m.playing)return m.pause();Promise.resolve(m.play()).catch(e=>e.name!=="AbortError"&&A(e.message))}};function Ne(e){c[e]=!c[e],U(),it(),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=()=>Ne(e);const dn=()=>{Ne("minimal"),oe(),z()};a("minimal").onclick=dn;const un=()=>{Ne("tabView"),oe(),Zt()};a("tabView").onclick=un;let ve=!1;function ht(){const e=!!S&&D<=.9,t=a("loopTools"),n=a("loopBox");if(e||(ve=!1),a("trainer").disabled=!e,a("trainer").setAttribute("aria-pressed",String(ve)),a("trainerHint").textContent=D>.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=L.getBoundingClientRect(),l=t.offsetWidth/2,i=Math.min(r.width-l-8,Math.max(l+8,o.left-r.left+o.width/2));Object.assign(t.style,{left:`${i}px`,top:`${o.top-r.top}px`}),t.style.setProperty("--tip",i<140?"0%":i>r.width-140?"100%":"50%")}a("trainer").onclick=()=>{ve=!ve,ht()},a("clearLoop").onclick=()=>{S=null,z()};function J(e){D=Math.min(1.5,Math.max(.1,Math.round(e*10)/10)),m?.setSpeed(D),a("speed").textContent=`${Math.round(D*100)}%`,ht()}function pn(){const e=f?.phrases.findLast(t=>t.time<=ut());S=S||!e?null:{start:e.time,end:e.endTime},z()}a("play").onclick=hn,a("slower").onclick=()=>J(D-.1),a("faster").onclick=()=>J(D+.1),a("speed").onclick=()=>J(1),a("loop").onclick=pn;const fn=()=>document.fullscreenElement?document.exitFullscreen():L.requestFullscreen(),gn=()=>{Ke(a("fullscreen"),document.fullscreenElement?Wn:Un)};a("fullscreen").onclick=fn,a("fullscreen").hidden=!document.fullscreenEnabled,document.addEventListener("fullscreenchange",gn),gn();const fa=e=>{const t=m?.time??0,n=e.key==="ArrowRight";if(!e.shiftKey)return m?.seek(ut()+(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];m?.seek(l?.time??(n?t:0))},ga=e=>Ce(Math.min(1,Math.max(0,Math.round((ge()+e)*10)/10)),!1),mn=[["Playing",[[[" "],"Space","Play or pause","",hn],[["ArrowLeft","ArrowRight"],"← →","Back or ahead 5 seconds","With Shift, a phrase at a time",fa],[["Home","0"],"Home 0","Back to the start","Of the loop, when there is one",()=>m?.seek(S?.start??0)],[["-","+","="],"− +","Slower or faster","In 10% steps",e=>J(D+(e.key==="-"?-.1:.1))],[["r"],"R","Back to 100% speed","",()=>J(1)],[["l"],"L","Loop the phrase you are in","Or clear the loop",pn],[["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]&&le(e.key-1)]]],["Practice tools",[[["k"],"K","Metronome","",()=>Ne("metronome")],[["ArrowUp","ArrowDown"],"↑ ↓","Volume up or down","",e=>ga(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",()=>!N&&a("band").click()]]],["View",[[["h"],"H","Minimal view","Just the highway and the lyrics",dn],[["d"],"D","2D tab view","Or back to the 3D highway",un],[["f"],"F","Full screen","",fn],[["o"],"O","Open a song","",()=>a("open").click()],[["s"],"S","Settings","",()=>se("settings")],[["?"],"?","Notation and shortcuts","This sheet",()=>se("legend")],[["Escape"],"Esc","Close what is open","Settings, this sheet, the volume or the menu",()=>{se("settings",!1),Je(!1),me(!1)}]]]];Yt=()=>Jt(mn.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>`])])),Zt();const ma=new Map(mn.flatMap(([,e])=>e.flatMap(([t,,,,n])=>t.map(o=>[o,n]))));addEventListener("keydown",e=>{if(Te&&(!je.clientWidth||!(e.target.closest?.("[data-fretfall]")||e.target===document.body))||e.key!=="Escape"&&(e.target.tagName==="INPUT"||e.target.closest?.(".sheet"))||e.metaKey||e.ctrlKey||e.altKey)return;const t=ma.get(e.key.length===1?e.key.toLowerCase():e.key);t&&(e.preventDefault(),t(e))});const q=new BroadcastChannel("fretfall-band"),re=[],Y={t:0,at:0,playing:!1,speed:1,loop:null},yn=()=>performance.timeOrigin+performance.now();let de=!1;const wn=()=>({...b,audio:null,arrangements:b.arrangements.map(({track:e,...t})=>t)}),ya={get time(){return Y.t+(Y.playing?(yn()-Y.at)/1e3*Y.speed:0)},get playing(){return Y.playing},play:()=>q.postMessage({do:"play"}),pause:()=>q.postMessage({do:"pause"}),seek:e=>q.postMessage({do:"seek",t:e}),setSpeed:e=>e!==Y.speed&&q.postMessage({do:"speed",v:e}),setVolume:()=>{},stop:()=>{}};q.onmessage=({data:e})=>{if(N){if(e.bye)return close();e.song&&(e.part??ke)===ke&&(nt(e.song),f&&(le(Math.min(+ke,b.arrangements.length-1)),Te||(document.title=`${f.name} · ${document.title}`)),A(null));const t=e.clock;if(!t)return;const n=t.playing!==Y.playing;Object.assign(Y,t),n&&Be(),t.speed!==D&&J(t.speed),JSON.stringify(t.loop)!==JSON.stringify(S)&&(S=t.loop,z());return}de&&(e.hello!==void 0&&q.postMessage({song:wn(),part:e.hello}),e.do==="play"&&!m.playing&&Promise.resolve(m.play()).catch(t=>t.name!=="AbortError"&&A(t.message)),e.do==="pause"&&m.playing&&m.pause(),e.do==="seek"&&m.seek(e.t),e.do==="speed"&&J(e.v),e.do==="loop"&&(S=e.loop,z()))},a("band").onclick=()=>{de=!0;const e=b.arrangements,t=Math.floor(screen.availWidth/e.length);let n=0;e.forEach((o,r)=>{if(re[r]&&!re[r].closed)return re[r].focus();const l=new URL(location.href);l.searchParams.set("band",r),re[r]=window.open(l,`fretfall-band-${r}`,`popup,left=${(screen.availLeft??0)+r*t},top=${screen.availTop??0},width=${t},height=${screen.availHeight}`),re[r]||n++}),n&&A(`The browser blocked ${n} ${n>1?"windows":"window"}: allow pop-ups for this page, then press again`)},a("band").hidden=N,addEventListener("pagehide",()=>de&&q.postMessage({bye:!0}));const je=a("highway"),dt={},ut=()=>m?m.time-c.offset/1e3:0,He=new Map;function wa(e){const t=b.lines,{top:n,bottom:o}=Ln(t,e),r=new Map([n,o].filter(i=>i>=0).map(i=>[t[i],i===o]));let l=!1;for(const[i,d]of He){if(r.has(i))continue;He.delete(i);const p=d.at===n-1;if(!p&&n>=0){d.remove();continue}l||=p,d.classList.add("gone"),d.classList.toggle("up",p),setTimeout(()=>d.remove(),800)}for(const[i,d]of r){let p=He.get(i);p?p.low!==d&&(p.style.transitionDelay=""):(p=Object.assign(document.createElement("div"),{className:`gone${d?" low":""}`,at:t.indexOf(i)}),p.append(...i.syllables.map($=>Object.assign(document.createElement("span"),{textContent:$.text}))),a("lines").append(p),p.getBoundingClientRect(),l&&(p.style.transitionDelay="0.2s",setTimeout(()=>p.style.transitionDelay="",700)),He.set(i,p)),p.low=d,p.classList.remove("gone"),p.classList.toggle("low",d);const h=i.syllables.filter($=>$.time<=e).length;p.sung!==h&&(p.sung=h,[...p.children].forEach(($,H)=>$.classList.toggle("sung",H<h)))}}function ba(e){const t=`${_e(e)} / ${_e(b.length)}`;X.time!==t&&(a("time").textContent=X.time=t),a("progress").firstElementChild.style.width=`${Math.min(100,e/b.length*100)}%`;const n=f.phrases.findLastIndex(i=>i.time<=e);if(X.phrase!==n&&(X.phrase=n,W.forEach((i,d)=>{i.classList.toggle("done",d<n),i.style.background=""})),W[n]){const i=Math.min(100,(e-f.phrases[n].time)/(f.phrases[n].endTime-f.phrases[n].time)*100);W[n].style.background=`linear-gradient(90deg, var(--accent) ${i}%, var(--todo) ${i}%)`}const o=f.sectionLabels[f.sections.findLastIndex(i=>i.time<=e)]??"";X.section!==o&&(a("section").textContent=X.section=o),wa(e);const r=f.notes[0]?.time??0,l=c.songIntro&&r-e>Rt(c)&&a("status").hidden?`${!!m?.playing}|${Math.ceil((r-e)/D)}`:"";X.intro!==l&&(X.intro=l,a("intro").classList.toggle("show",!!l),a("introBars").classList.toggle("moving",!!l&&!!m?.playing),l&&(a("introText").textContent=m?.playing?`Get ready · first notes in ${l.split("|")[1]}`:"Press Space to play"))}function bn(){if(m===$e&&M.tickCache&&M.tickCache!==tt){tt=M.tickCache;const t=ra(M.score,M.tickCache);v.tempo=t.tempo,nt(t),ln(),cn(),M.score.backingTrack?.rawAudioFile?A("Playing the recording that comes with this file"):a("statusText").textContent===ot&&A(null)}const e=ut();return S&&m?.playing&&e>=S.end&&!N&&(m.seek(Math.max(0,S.start-1)),ve&&J(D+.1)),pa(e),de&&q.postMessage({clock:{t:m.time,at:yn(),playing:m.playing,speed:D,loop:S}}),e}function pt(){if(kn())return ka();requestAnimationFrame(pt),jn();const e=bn();Nt("song");const t=In()??e;(!Te||je.clientWidth)&&(c.tabView?On:En)(je,f,t,C,dt),b&&f&&ba(t),Nt("overlay"),Hn()}const kn=()=>de&&re.some(e=>e&&!e.closed);function ka(){L.classList.add("banding"),a("bandNote").hidden=!1;const e=setInterval(()=>{if(kn())return bn();clearInterval(e),L.classList.remove("banding"),a("bandNote").hidden=!0,requestAnimationFrame(pt)},50)}a("bandBack").onclick=()=>{for(const e of re)e?.close()};function ue(e,t){dispatchEvent(new CustomEvent(`fretfall:${e}`,{detail:t}))}function $a(){const e=[...st.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)=>rt(e,t),pick:()=>a("file").click(),get playing(){return!!m?.playing},set playing(e){e?N||ct():m?.pause()},get time(){return m?.time??0},set time(e){b&&!N&&m.seek(Math.min(b.length,Math.max(0,+e||0)))},get length(){return b?.length??0},get speed(){return D},get style(){return{look:c.look,colors:c.colors,fonts:c.fonts,strings:c.strings,headstock:c.headstock}},set style(e){for(const[t,n]of Object.entries({look:We,colors:fe,fonts:ze,strings:xe,headstock:Se}))e?.[t]&&n[e[t]]&&(c[t]=e[t]);U(),oe()},set speed(e){J(+e||1)},get notes(){if(!b||!f)return null;const e=[...f.open??[]];return{open:e,notes:f.notes.map(t=>({time:t.time,sustain:t.sustain,string:t.string,fret:t.fret,midi:(e[t.string]??0)+t.fret}))}},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&&le(e)},closeSheets:()=>se(null,!1),dock:Qn,addFormat:e=>{st.push(e),a("file").accept+=e.extensions.map(t=>`,${t}`).join(""),$a()}},ue("ready"),Oe(N?ya:$e),Ce(),N?(q.postMessage({hello:ke}),A("Waiting for the window the band started from",!0)):A(null),new URLSearchParams(location.search).has("perf")&&Nn(je,()=>b?.length??0),requestAnimationFrame(pt);