Spaces:
Runtime error
Runtime error
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>AlphaTab Tutorial</title> | |
<script src="https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/alphaTab.js"></script> | |
<script src="https://kit.fontawesome.com/b43f0e512e.js"></script> | |
<style type="text/css"> | |
body { | |
font-family: Arial, Helvetica, sans-serif; | |
font-size: 12px; | |
} | |
.at-wrap { | |
width: 80vw; | |
height: 80vh; | |
margin: 0 auto; | |
border: 1px solid rgba(0, 0, 0, 0.12); | |
display: flex; | |
flex-direction: column; | |
overflow: hidden; | |
position: relative; | |
} | |
.at-content { | |
position: relative; | |
overflow: hidden; | |
flex: 1 1 auto; | |
} | |
/** Sidebar **/ | |
.at-sidebar { | |
position: absolute; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
max-width: 70px; | |
width: auto; | |
display: flex; | |
align-content: stretch; | |
z-index: 1001; | |
overflow: hidden; | |
border-right: 1px solid rgba(0, 0, 0, 0.12); | |
background: #f7f7f7; | |
} | |
.at-sidebar:hover { | |
max-width: 400px; | |
transition: max-width 0.2s; | |
overflow-y: auto; | |
} | |
.at-viewport { | |
overflow-y: auto; | |
position: absolute; | |
top: 0; | |
left: 70px; | |
right: 0; | |
bottom: 0; | |
padding-right: 20px; | |
} | |
.at-footer { | |
flex: 0 0 auto; | |
background: #436d9d; | |
color: #fff; | |
} | |
/** Overlay **/ | |
.at-overlay { | |
/** Fill Parent */ | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
z-index: 1002; | |
/* Blurry dark shade */ | |
backdrop-filter: blur(3px); | |
background: rgba(0, 0, 0, 0.5); | |
/** center content */ | |
display: flex; | |
justify-content: center; | |
align-items: flex-start; | |
} | |
.at-overlay-content { | |
/* white box with drop-shadow */ | |
margin-top: 20px; | |
background: #fff; | |
box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3); | |
padding: 10px; | |
} | |
/** Track selector **/ | |
.at-track { | |
display: flex; | |
position: relative; | |
padding: 5px; | |
transition: background 0.2s; | |
cursor: pointer; | |
} | |
.at-track:hover { | |
background: rgba(0, 0, 0, 0.1); | |
} | |
.at-track > .at-track-icon, | |
.at-track > .at-track-details { | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
} | |
.at-track > .at-track-icon { | |
flex-shrink: 0; | |
font-size: 32px; | |
opacity: 0.5; | |
transition: opacity 0.2s; | |
width: 64px; | |
height: 64px; | |
margin-right: 5px; | |
align-items: center; | |
} | |
.at-track-name { | |
font-weight: bold; | |
margin-bottom: 5px; | |
} | |
.at-track:hover > .at-track-icon { | |
opacity: 0.8; | |
} | |
.at-track.active { | |
background: rgba(0, 0, 0, 0.03); | |
} | |
.at-track.active > .at-track-icon { | |
color: #4972a1; | |
opacity: 1; | |
} | |
.at-track > .at-track-name { | |
font-weight: 500; | |
} | |
/** Footer **/ | |
.at-controls { | |
flex: 0 0 auto; | |
display: flex; | |
justify-content: space-between; | |
background: #436d9d; | |
color: #fff; | |
} | |
.at-controls > div { | |
display: flex; | |
justify-content: flex-start; | |
align-content: center; | |
align-items: center; | |
} | |
.at-controls > div > * { | |
display: flex; | |
text-align: center; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
padding: 4px; | |
margin: 0 3px; | |
} | |
.at-controls .btn { | |
color: #fff; | |
border-radius: 0; | |
height: 40px; | |
width: 40px; | |
height: 40px; | |
font-size: 16px; | |
} | |
.at-controls .btn.disabled { | |
cursor: progress; | |
opacity: 0.5; | |
} | |
.at-controls a.active { | |
background: #5588c7; | |
text-decoration: none; | |
} | |
.at-controls .btn i { | |
vertical-align: top; | |
} | |
.at-controls select { | |
-moz-appearance: none; | |
-webkit-appearance: none; | |
appearance: none; | |
border: none; | |
width: 100%; | |
height: 40px; | |
background: #436d9d; | |
padding: 4px 10px; | |
color: #fff; | |
font-size: 16px; | |
text-align-last: center; | |
text-align: center; | |
-ms-text-align-last: center; | |
-moz-text-align-last: center; | |
cursor: pointer; | |
} | |
.at-song-title { | |
font-weight: bold; | |
} | |
.at-cursor-bar { | |
/* Defines the color of the bar background when a bar is played */ | |
background: rgba(255, 242, 0, 0.25); | |
} | |
.at-selection div { | |
/* Defines the color of the selection background */ | |
background: rgba(64, 64, 255, 0.1); | |
} | |
.at-cursor-beat { | |
/* Defines the beat cursor */ | |
background: rgba(64, 64, 255, 0.75); | |
width: 3px; | |
} | |
.at-highlight * { | |
/* Defines the color of the music symbols when they are being played (svg) */ | |
fill: #0078ff; | |
stroke: #0078ff; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="at-wrap"> | |
<div class="at-overlay"> | |
<div class="at-overlay-content"> | |
Music sheet is loading | |
</div> | |
</div> | |
<div class="at-content"> | |
<div class="at-sidebar"> | |
<div class="at-sidebar-content"> | |
<div class="at-track-list"></div> | |
</div> | |
</div> | |
<div class="at-viewport"> | |
<div class="at-main"></div> | |
</div> | |
</div> | |
<div class="at-controls"> | |
<div class="at-controls-left"> | |
<a class="btn at-player-stop disabled"> | |
<i class="fas fa-step-backward"></i> | |
</a> | |
<a class="btn at-player-play-pause disabled"> | |
<i class="fas fa-play"></i> | |
</a> | |
<span class="at-player-progress">0%</span> | |
<div class="at-song-info"> | |
<span class="at-song-title"></span> - | |
<span class="at-song-artist"></span> | |
</div> | |
<div class="at-song-position">00:00 / 00:00</div> | |
</div> | |
<div class="at-controls-right"> | |
<a class="btn toggle at-count-in"> | |
<i class="fas fa-hourglass-half"></i> | |
</a> | |
<a class="btn at-metronome"> | |
<i class="fas fa-edit"></i> | |
</a> | |
<a class="btn at-loop"> | |
<i class="fas fa-retweet"></i> | |
</a> | |
<a class="btn at-print"> | |
<i class="fas fa-print"></i> | |
</a> | |
<div class="at-zoom"> | |
<i class="fas fa-search"></i> | |
<select> | |
<option value="25">25%</option> | |
<option value="50">50%</option> | |
<option value="75">75%</option> | |
<option value="90">90%</option> | |
<option value="100" selected>100%</option> | |
<option value="110">110%</option> | |
<option value="125">125%</option> | |
<option value="150">150%</option> | |
<option value="200">200%</option> | |
</select> | |
</div> | |
<div class="at-layout"> | |
<select> | |
<option value="horizontal">Horizontal</option> | |
<option value="page" selected>Page</option> | |
</select> | |
</div> | |
</div> | |
</div> | |
</div> | |
<template id="at-track-template"> | |
<div class="at-track"> | |
<div class="at-track-icon"> | |
<i class="fas fa-guitar"></i> | |
</div> | |
<div class="at-track-details"> | |
<div class="at-track-name"></div> | |
</div> | |
</div> | |
</template> | |
<script type="text/javascript"> | |
// load elements | |
const wrapper = document.querySelector(".at-wrap"); | |
const main = wrapper.querySelector(".at-main"); | |
// initialize alphatab | |
const settings = { | |
file: "https://www.alphatab.net/files/canon.gp", | |
player: { | |
enablePlayer: true, | |
soundFont: "https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2", | |
scrollElement: wrapper.querySelector('.at-viewport') | |
}, | |
}; | |
const api = new alphaTab.AlphaTabApi(main, settings); | |
// overlay logic | |
const overlay = wrapper.querySelector(".at-overlay"); | |
api.renderStarted.on(() => { | |
overlay.style.display = "flex"; | |
}); | |
api.renderFinished.on(() => { | |
overlay.style.display = "none"; | |
}); | |
// track selector | |
function createTrackItem(track) { | |
const trackItem = document | |
.querySelector("#at-track-template") | |
.content.cloneNode(true).firstElementChild; | |
trackItem.querySelector(".at-track-name").innerText = track.name; | |
trackItem.track = track; | |
trackItem.onclick = (e) => { | |
e.stopPropagation(); | |
api.renderTracks([track]); | |
}; | |
return trackItem; | |
} | |
const trackList = wrapper.querySelector(".at-track-list"); | |
api.scoreLoaded.on((score) => { | |
// clear items | |
trackList.innerHTML = ""; | |
// generate a track item for all tracks of the score | |
score.tracks.forEach((track) => { | |
trackList.appendChild(createTrackItem(track)); | |
}); | |
}); | |
api.renderStarted.on(() => { | |
// collect tracks being rendered | |
const tracks = new Map(); | |
api.tracks.forEach((t) => { | |
tracks.set(t.index, t); | |
}); | |
// mark the item as active or not | |
const trackItems = trackList.querySelectorAll(".at-track"); | |
trackItems.forEach((trackItem) => { | |
if (tracks.has(trackItem.track.index)) { | |
trackItem.classList.add("active"); | |
} else { | |
trackItem.classList.remove("active"); | |
} | |
}); | |
}); | |
/** Controls **/ | |
api.scoreLoaded.on((score) => { | |
wrapper.querySelector(".at-song-title").innerText = score.title; | |
wrapper.querySelector(".at-song-artist").innerText = score.artist; | |
}); | |
const countIn = wrapper.querySelector('.at-controls .at-count-in'); | |
countIn.onclick = () => { | |
countIn.classList.toggle('active'); | |
if (countIn.classList.contains('active')) { | |
api.countInVolume = 1; | |
} else { | |
api.countInVolume = 0; | |
} | |
}; | |
const metronome = wrapper.querySelector(".at-controls .at-metronome"); | |
metronome.onclick = () => { | |
metronome.classList.toggle("active"); | |
if (metronome.classList.contains("active")) { | |
api.metronomeVolume = 1; | |
} else { | |
api.metronomeVolume = 0; | |
} | |
}; | |
const loop = wrapper.querySelector(".at-controls .at-loop"); | |
loop.onclick = () => { | |
loop.classList.toggle("active"); | |
api.isLooping = loop.classList.contains("active"); | |
}; | |
wrapper.querySelector(".at-controls .at-print").onclick = () => { | |
api.print(); | |
}; | |
const zoom = wrapper.querySelector(".at-controls .at-zoom select"); | |
zoom.onchange = () => { | |
const zoomLevel = parseInt(zoom.value) / 100; | |
api.settings.display.scale = zoomLevel; | |
api.updateSettings(); | |
api.render(); | |
}; | |
const layout = wrapper.querySelector(".at-controls .at-layout select"); | |
layout.onchange = () => { | |
switch (layout.value) { | |
case "horizontal": | |
api.settings.display.layoutMode = alphaTab.LayoutMode.Horizontal; | |
break; | |
case "page": | |
api.settings.display.layoutMode = alphaTab.LayoutMode.Page; | |
break; | |
} | |
api.updateSettings(); | |
api.render(); | |
}; | |
// player loading indicator | |
const playerIndicator = wrapper.querySelector( | |
".at-controls .at-player-progress" | |
); | |
api.soundFontLoad.on((e) => { | |
const percentage = Math.floor((e.loaded / e.total) * 100); | |
playerIndicator.innerText = percentage + "%"; | |
}); | |
api.playerReady.on(() => { | |
playerIndicator.style.display = "none"; | |
}); | |
// main player controls | |
const playPause = wrapper.querySelector( | |
".at-controls .at-player-play-pause" | |
); | |
const stop = wrapper.querySelector(".at-controls .at-player-stop"); | |
playPause.onclick = (e) => { | |
if (e.target.classList.contains("disabled")) { | |
return; | |
} | |
api.playPause(); | |
}; | |
stop.onclick = (e) => { | |
if (e.target.classList.contains("disabled")) { | |
return; | |
} | |
api.stop(); | |
}; | |
api.playerReady.on(() => { | |
playPause.classList.remove("disabled"); | |
stop.classList.remove("disabled"); | |
}); | |
api.playerStateChanged.on((e) => { | |
const icon = playPause.querySelector("i.fas"); | |
if (e.state === alphaTab.synth.PlayerState.Playing) { | |
icon.classList.remove("fa-play"); | |
icon.classList.add("fa-pause"); | |
} else { | |
icon.classList.remove("fa-pause"); | |
icon.classList.add("fa-play"); | |
} | |
}); | |
// song position | |
function formatDuration(milliseconds) { | |
let seconds = milliseconds / 1000; | |
const minutes = (seconds / 60) | 0; | |
seconds = (seconds - minutes * 60) | 0; | |
return ( | |
String(minutes).padStart(2, "0") + | |
":" + | |
String(seconds).padStart(2, "0") | |
); | |
} | |
const songPosition = wrapper.querySelector(".at-song-position"); | |
let previousTime = -1; | |
api.playerPositionChanged.on((e) => { | |
// reduce number of UI updates to second changes. | |
const currentSeconds = (e.currentTime / 1000) | 0; | |
if (currentSeconds == previousTime) { | |
return; | |
} | |
songPosition.innerText = | |
formatDuration(e.currentTime) + " / " + formatDuration(e.endTime); | |
}); | |
</script> | |
</body> | |
</html> | |