Skip to content

Instantly share code, notes, and snippets.

@jonaro00
Created March 8, 2024 17:25
alphaTab-issue-1400 - change the version on line 4 between 933 and 938
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.3.0-alpha.938/dist/alphaTab.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/@coderline/alphatab@1.3.0-alpha.933/dist/alphaTab.js"></script> -->
<meta charset="utf-8" />
<title>Tab Portal</title>
<style>
:root {
--main-bg-color: #5d9150;
--main-bg-color-dark: #548348;
--main-text-color: #fff;
--main-link-color: #00d9ef;
--brighter1: #ffffff40;
--brighterb: #ffffffb0;
--darker1: #00000040;
--dark-text-color: #606060;
--accent-bg-color: #e6e7b5;
--page-bg-color: #f9f9f5;
--page-text-color: #000;
--page-link-color: #002ae6;
}
body {
background-color: #bbbbbb;
margin: 0;
}
.tp-main, .tp-main *, .tp-main *::after, .tp-main *::before {
box-sizing: border-box;
}
.tp-main {
display: flex;
width: 100%;
max-width: 1800px;
margin: auto;
box-shadow: 0px 0px 0px 1px #00000062;
background-color: #f3f3f3;
font-family: sans-serif;
}
.tp-main a, .tp-main a:visited {
text-decoration: none;
font-weight: 500;
color: var(--page-link-color);
}
.tp-main a:hover {
text-decoration: underline;
}
.tp-sidebar {
flex: 0 0 222px;
background-color: var(--main-bg-color-dark);
color: var(--main-text-color);
display: flex;
flex-direction: column;
}
.tp-sidebar>* {
padding: 10px;
}
.tp-sidebar .tp-header {
background-color: var(--accent-bg-color);
color: var(--dark-text-color);
}
.tp-sidebar .tp-logo {
font-size: 1.8em;
font-weight: bold;
margin-bottom: .5em;
border-bottom: 1px solid var(--darker1);
}
.tp-sidebar .tp-section-link {
font-size: 1.6em;
}
.tp-sidebar .tp-section-link:not(:last-child) {
margin-bottom: .25em;
}
.tp-sidebar .tp-menu {
flex-grow: 1;
}
.tp-sidebar .tp-menu .tp-help {
background-color: var(--brighterb);
color: var(--page-text-color);
padding: 4px;
border-radius: 4px;
}
.tp-sidebar .tp-menu .tp-help:not(:first-child) {
margin-top: 10px;
}
.tp-sidebar .tp-menu .tp-help .tp-help-title {
font-weight: bold;
border-bottom: 1px solid var(--darker1);
margin-bottom: .2em;
}
.tp-sidebar .tp-credits {
font-size: 60%;
}
.tp-content {
flex: 1;
display: flex;
height: 100vh;
}
.tp-content>* {
flex: 1;
}
.tp-editor-container {
display: flex;
flex-direction: column;
}
.tp-editor {
flex: 4 1;
display: flex;
}
.tp-editor.hidden {
display: none;
}
#tp-text-editor {
flex: 1;
height: 100%;
width: 100%;
background-color: #333;
color: #eee;
outline: none;
font-size: 16px;
font-family: Consolas, 'Courier New', monospace;
display: block;
resize: none;
border: none;
padding: 6px;
padding-bottom: calc((100vh - 48px) * 0.3);
overflow-wrap: normal;
}
.tp-editor-menu {
flex: 0 0 70px;
background-color: var(--main-bg-color);
color: var(--main-text-color);
display: flex;
flex-direction: column-reverse;
}
.tp-editor-menu>*{
height: 56px;
text-align: center;
padding: 4px;
}
.tp-editor-menu a.btn {
font-size: 16px;
color: var(--main-text-color, #fff);
cursor: pointer;
text-decoration: none;
}
.tp-editor-menu a.btn.disabled {
color: #888;
}
.tp-editor-bar {
flex: 0 0 19px;
background-color: var(--main-bg-color);
color: var(--main-text-color);
font-size: 14px;
text-align: center;
cursor: pointer;
}
.tp-viewer {
flex: 6 1 auto;
display: flex;
flex-direction: column;
}
.tp-viewer>* {
flex: 1;
}
.tp-explorer {
padding: 20px;
}
</style>
<style>
</style>
<link rel="apple-touch-icon" sizes="180x180" href="/static/icon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/icon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/static/icon/favicon-16x16.png">
<link rel="manifest" href="/static/icon/site.webmanifest">
<script async src="https://kit.fontawesome.com/067013981a.js" crossorigin="anonymous"></script>
<style>.at-wrap, .at-wrap *, .at-wrap *::after, .at-wrap *::before {
box-sizing: border-box;
}
.at-wrap {
font-family: sans-serif;
font-size: 12px;
background-color: var(--page-bg-color, #f9f9f5);
display: flex;
flex-direction: column;
position: relative;
}
.at-content {
position: relative;
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; */
left: 0;
right: 0;
bottom: 0;
/* padding-right: 20px; */
}
/** 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#at-overlay-error {
box-shadow: inset 0px 0px 8px 2px #9d0000;
}
.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: var(--main-bg-color, rgb(67, 109, 157));
color: var(--main-text-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;
padding: 4px;
}
.at-controls a.btn {
width: 44px;
height: 48px;
font-size: 16px;
cursor: pointer;
color: var(--main-text-color, #fff);
text-decoration: none;
}
.at-controls a.btn.at-stepper {
width: 25px;
}
.at-controls a.btn.iconHelper {
justify-content: right;
width: 28px;
}
.at-controls a.btn.disabled {
cursor: progress;
opacity: 0.5;
}
.at-controls a.active {
background: var(--brighter1, rgb(85, 136, 199));
text-decoration: none;
}
/* .at-controls .btn i {
vertical-align: top;
} */
.at-controls select {
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
border: none;
height: 40px;
background: var(--main-bg-color, rgb(67, 109, 157));
padding: 4px 4px;
color: var(--main-text-color, #fff);
font-size: 16px;
text-align-last: left;
text-align: left;
-moz-text-align-last: left;
cursor: pointer;
}
.at-controls input[type=range] {
-webkit-appearance: none;
width: 100%;
max-width: 80px;
/* outline: none; */
height: 4px;
}
.at-controls input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 14px;
width: 14px;
border-radius: 42%;
background: #ffffff;
cursor: pointer;
margin-top: -1px;
border: 1px solid #666;
}
.at-song-info {
height: 48px;
overflow-y: auto;
}
.at-song-title {
font-weight: bold;
}
.at-song-album {
font-size: 90%;
}
.fs14 {
font-size: 14px;
}
.at-logo {
background: var(--accent-bg-color, #e4e4e4);
color: var(--dark-text-color, #838383);
font-size: 14px;
height: 100%;
}
.at-logo img {
height: 30px;
}
.at-cursor-bar {
/* Defines the color of the bar background when a bar is played */
background: rgba(255, 242, 0, 0.2);
}
.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(73, 161, 51, 0.55);
width: 2px;
transform: translateX(2px);
}
.at-highlight * {
/* Defines the color of the music symbols when they are being played (svg) */
fill: #5d9150;
stroke: #5d9150;
}</style>
</head>
<body>
<div class="tp-main">
<div class="tp-sidebar">
<div class="tp-header">
<div class="tp-logo">Tab Portal</div>
<div class="tp-section-link"><a href="/">Editor</a></div>
<div class="tp-section-link"><a href="/tabs">Explore Tabs</a></div>
</div>
<div class="tp-menu">
<div class="tp-help">
<div class="tp-help-title">Editor: How to use</div>
Write <a href="https://www.alphatab.net/docs/alphaTex/introduction/" target="_blank">alphaTex</a> notation in the text editor
and press <b>Ctrl+Enter</b> to render it. Press <b>Enter</b> to re-focus on the editor.
</div>
<div class="tp-help">
<div class="tp-help-title">Player: How to use</div>
Press <b>Space</b> to Play/Pause.
<b>M</b> to mute.
<b>Backspace</b> to reset.
</div>
<div class="tp-help">
<div class="tp-help-title">BPM counter</div>
<button id="tp-bpm-btn">Click</button>
<span id="tp-bpm-result"></span>
</div>
</div>
<div class="tp-credits">
alphaTab-issue-1400 -
<a target="_blank" href="https://github.com/jonaro00/TabPortal">GitHub</a>
</div>
</div>
<div class="tp-content">
<div class="tp-editor-container">
<div class="tp-editor">
<textarea id="tp-text-editor" spellcheck="false">3.3 3.3</textarea>
<div class="tp-editor-menu">
<a class="btn" id="tp-render">
<i class="fas fa-file-download"></i>
<br>Render
</a>
<a class="btn disabled" id="tp-save">
<i class="fas fa-save"></i>
<br>Save
</a>
<a class="btn disabled" id="tp-save-new">
<i class="fas fa-save"></i>
<br>Save As
</a>
<div>API Password: <input id="tp-api-password" type="password"></div>
<div>Name: <input id="tp-name" type="text" value="{{ name }}"></div>
</div>
</div>
<div class="tp-editor-bar">
{% if readonly %}
<i class="fas fa-chevron-down"></i>
Source
<i class="fas fa-chevron-down"></i>
{% else %}
<i class="fas fa-chevron-up"></i>
Editor
<i class="fas fa-chevron-up"></i>
{% endif %}
</div>
<div class="tp-viewer">
<div class="at-wrap" data-file="">
<div class="at-content">
<div id="at-overlay-loading" class="at-overlay">
<div class="at-overlay-content">
Music sheet is loading
</div>
</div>
<div id="at-overlay-error" class="at-overlay" style="display:none">
<div id="at-overlay-error-text" class="at-overlay-content">
Error loading music sheet
</div>
</div>
<!-- <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-reset disabled" title="Reset [Backspace]">
<i class="fas fa-step-backward"></i>
</a>
<a class="btn at-player-play-pause disabled" title="Play/Pause [Space]">
<i class="fas fa-play"></i>
</a>
<a class="btn at-player-step-backward at-stepper disabled" title="Step backward (100 MIDI ticks)">
<i class="fas fa-caret-left"></i>
</a>
<a class="btn at-player-step-forward at-stepper disabled" title="Step forward (100 MIDI ticks)">
<i class="fas fa-caret-right"></i>
</a>
<div class="at-song-position">00:00 / 00:00</div>
<a class="btn iconHelper at-mute" title="Mute [M]">
<i class="fas fa-volume-up"></i>
</a>
<div class="at-volume">
<input type="range" title="Volume"/>
</div>
<div class="at-speed">
<i class="fas fa-angle-double-right fs14"></i>
<select title="Speed">
<option value="0.3">0.3x</option>
<option value="0.5">0.5x</option>
<option value="0.7">0.7x</option>
<option value="0.8">0.8x</option>
<option value="0.9">0.9x</option>
<option value="1" selected>1x</option>
<option value="1.1">1.1x</option>
<option value="1.2">1.2x</option>
<option value="1.3">1.3x</option>
<option value="1.5">1.5x</option>
<option value="2" >2x</option>
</select>
</div>
<a class="btn toggle at-count-in" title="Count-in">
<i class="fas fa-hourglass-half"></i>
</a>
<a class="btn at-metronome" title="Metronome">
<i class="fas fa-tachometer-alt"></i>
</a>
<a class="btn at-loop" title="Loop">
<i class="fas fa-retweet"></i>
</a>
</div>
<div class="at-song-info">
<div>
<div>
<span class="at-song-artist"></span>&nbsp;-&nbsp;<span class="at-song-title"></span><br><span class="at-song-album"></span>
</div>
</div>
</div>
<div class="at-controls-right">
<a class="btn at-print" title="Print">
<i class="fas fa-print"></i>
</a>
<a class="btn at-show-notation active" title="Show music notation">
<i class="fas fa-music"></i>
</a>
<a class="btn at-layout active" title="Page/Horizontal layout">
<i class="fas fa-file-alt"></i>
</a>
<div class="at-zoom">
<i class="fas fa-search fs14"></i>
<select title="Zoom">
<option value="0.25">25%</option>
<option value="0.5" >50%</option>
<option value="0.75">75%</option>
<option value="0.9" >90%</option>
<option value="1" selected>100%</option>
<option value="1.1" >110%</option>
<option value="1.25">125%</option>
<option value="1.5" >150%</option>
<option value="2">200%</option>
</select>
</div>
<div class="at-logo">powered by<a href="https://www.alphatab.net/" target="_blank"><img src="/static/alphatab.png"></a></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>
const MAX_VOLUME = 40;
const STEP_LENTGH = 100;
const Settings = {
defaults: {
muted: false,
volume: Math.round(MAX_VOLUME * 0.4),
speed: 1.0,
countIn: 0,
metronome: 0,
loop: false,
showNotation: true,
layout: alphaTab.LayoutMode.Page,
zoom: 1.0,
},
values: {},
load() {
this.values = {...this.defaults, ...JSON.parse(localStorage.getItem("tp-settings") || "{}")};
},
save() {
localStorage.setItem("tp-settings", JSON.stringify(this.values));
},
getApiSettings() {
return {
core: {
file: wrapper.getAttribute("data-file") || null,
},
player: {
enablePlayer: true,
soundFont: "https://cdn.jsdelivr.net/npm/@coderline/alphatab@latest/dist/soundfont/sonivox.sf2",
scrollElement: viewport,
scrollOffsetX: -6,
scrollOffsetY: -10,
slide: {
shiftSlideDurationRatio: 0.2,
},
},
display: {
stretchForce: 0.75,
staveProfile: this.values.showNotation ? 'default' : 'tab',
layoutMode: this.values.layout,
scale: this.values.zoom / 100,
},
notation: {
rhythmMode: this.values.showNotation ? 'hidden' : 'showwithbars',
},
}
},
}
Settings.load();
// load elements
const wrapper = document.querySelector(".at-wrap");
const main = wrapper.querySelector(".at-main");
const viewport = wrapper.querySelector('.at-viewport');
// initialize alphatab
const at = new alphaTab.AlphaTabApi(main, Settings.getApiSettings());
// overlay logic
const overlay = wrapper.querySelector("#at-overlay-loading");
const overlayError = wrapper.querySelector("#at-overlay-error");
const overlayErrorText = wrapper.querySelector("#at-overlay-error-text");
// const overlayContent = wrapper.querySelector(".at-overlay-content");
at.renderStarted.on(() => {
overlay.style.display = "flex";
});
at.renderFinished.on(() => {
overlay.style.display = "none";
overlayError.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");
// }
// });
// });
// main player controls
const playPause = wrapper.querySelector(".at-controls .at-player-play-pause");
const reset = wrapper.querySelector(".at-controls .at-player-reset");
const stepBackward = wrapper.querySelector(".at-controls .at-player-step-backward");
const stepForward = wrapper.querySelector(".at-controls .at-player-step-forward");
playPause.onclick = (e) => {
if (e.target.classList.contains("disabled")) return;
at.playPause();
};
reset.onclick = (e) => {
if (e.target.classList.contains("disabled")) return;
at.stop();
viewport.scrollTo(0, 0);
};
stepBackward.onclick = (e) => {
if (e.target.classList.contains("disabled")) return;
at.tickPosition -= STEP_LENTGH;
};
stepForward.onclick = (e) => {
if (e.target.classList.contains("disabled")) return;
at.tickPosition += STEP_LENTGH;
};
at.playerReady.on(() => {
playPause.classList.remove("disabled");
reset.classList.remove("disabled");
stepBackward.classList.remove("disabled");
stepForward.classList.remove("disabled");
});
at.playerStateChanged.on((e) => {
const icon = playPause.querySelector("i.fas");
const playing = e.state === alphaTab.synth.PlayerState.Playing;
icon.classList.toggle("fa-play", !playing);
icon.classList.toggle("fa-pause", playing);
});
// 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;
at.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);
});
const mute = wrapper.querySelector(".at-controls .at-mute");
const muteIcon = wrapper.querySelector(".at-controls .at-mute i");
mute.onclick = () => {
Settings.values.muted = !Settings.values.muted;
setMuted(Settings.values.muted);
Settings.save();
};
function setMuted(b) {
muteIcon.classList.toggle("fa-volume-mute", b);
muteIcon.classList.toggle("fa-volume-up", !b);
at.changeTrackMute(at.score.tracks, b);
}
const volume = wrapper.querySelector(".at-controls .at-volume input");
volume.max = MAX_VOLUME;
volume.oninput = () => { // when moving slider
setVolume(parseInt(volume.value));
};
volume.onchange = () => { // when releasing slider
Settings.values.volume = volume.value;
Settings.save();
}
function setVolume(v) {
at.masterVolume = v / MAX_VOLUME;
}
const speed = wrapper.querySelector(".at-controls .at-speed select");
speed.onchange = () => {
setSpeed(parseFloat(speed.value));
Settings.values.speed = speed.value;
Settings.save();
};
function setSpeed(f) {
at.playbackSpeed = f;
}
const countIn = wrapper.querySelector('.at-controls .at-count-in');
countIn.onclick = () => {
countIn.classList.toggle('active');
const counting = countIn.classList.contains('active');
setCountIn(counting);
Settings.values.countIn = counting;
Settings.save();
};
function setCountIn(b) {
at.countInVolume = b ? 1 : 0;
}
const metronome = wrapper.querySelector(".at-controls .at-metronome");
metronome.onclick = () => {
metronome.classList.toggle('active');
const counting = metronome.classList.contains('active');
setMetronome(counting);
Settings.values.metronome = counting;
Settings.save();
};
function setMetronome(b) {
at.metronomeVolume = b ? 1 : 0;
}
const loop = wrapper.querySelector(".at-controls .at-loop");
loop.onclick = () => {
loop.classList.toggle('active');
const looping = loop.classList.contains('active');
setLoop(looping);
Settings.values.loop = looping;
Settings.save();
};
function setLoop(b) {
at.isLooping = b;
}
at.scoreLoaded.on((score) => {
wrapper.querySelector(".at-song-title").innerText = score.title;
wrapper.querySelector(".at-song-artist").innerText = score.artist;
wrapper.querySelector(".at-song-album").innerText = score.album ? `(${score.album})` : '';
// set Document title
document.title = score.title || score.artist || score.album ? `${score.title} - ${score.artist} | ${score.album}` : "Tab Portal";
});
wrapper.querySelector(".at-controls .at-print").onclick = () => {
at.print();
};
const showNotation = wrapper.querySelector(".at-controls .at-show-notation");
showNotation.onclick = () => {
showNotation.classList.toggle("active");
if(showNotation.classList.contains("active")){
at.settings.display.staveProfile = 'default';
at.settings.notation.rhythmMode = 'hidden';
Settings.values.showNotation = true;
} else {
at.settings.display.staveProfile = 'tab';
at.settings.notation.rhythmMode = 'showwithbars';
Settings.values.showNotation = false;
}
Settings.save();
at.updateSettings();
at.render();
};
const layout = wrapper.querySelector(".at-controls .at-layout");
layout.onclick = () => {
layout.classList.toggle("active");
at.settings.display.layoutMode = layout.classList.contains("active")
? alphaTab.LayoutMode.Page
: alphaTab.LayoutMode.Horizontal;
Settings.values.layout = at.settings.display.layoutMode;
Settings.save();
at.updateSettings();
at.render();
};
const zoom = wrapper.querySelector(".at-controls .at-zoom select");
zoom.onchange = () => {
setZoom(parseFloat(zoom.value));
Settings.values.zoom = zoom.value;
Settings.save();
};
function setZoom(f) {
at.settings.display.scale = f;
at.updateSettings();
at.render();
}
viewport.onclick = () => {
document.activeElement.blur();
}
// keyboard
document.addEventListener("keydown", (event) => {
if (event.defaultPrevented) {
return; // Do nothing if event already handled
}
let bodyFocused = document.activeElement == document.body;
switch(event.key) {
case " ":
if (!bodyFocused && !event.ctrlKey) return;
playPause.click();
event.preventDefault(); // Consume the event so it doesn't get handled twice
break;
case "Backspace":
if (!bodyFocused) return;
reset.click()
event.preventDefault();
break;
case "m":
if (!bodyFocused) return;
mute.click()
event.preventDefault();
break;
}
}, true);
// load settings
document.addEventListener('DOMContentLoaded', () => {
setMuted(Settings.values.muted);
volume.value = Settings.values.volume;
setVolume(Settings.values.volume);
speed.value = Settings.values.speed;
setSpeed(parseFloat(Settings.values.speed));
Settings.values.countIn && countIn.classList.toggle("active");
setCountIn(Settings.values.countIn);
Settings.values.metronome && metronome.classList.toggle("active");
setMetronome(Settings.values.metronome);
Settings.values.loop && loop.classList.toggle("active");
setLoop(Settings.values.loop);
Settings.values.showNotation || showNotation.classList.toggle("active");
Settings.values.layout === alphaTab.LayoutMode.Page || layout.classList.toggle("active");
zoom.value = Settings.values.zoom;
setZoom(parseFloat(Settings.values.zoom));
});
</script>
</div>
</div>
<script>
const cont = document.querySelector(".tp-editor-container")
const editor = cont.querySelector(".tp-editor")
const editorTextArea = cont.querySelector("#tp-text-editor")
const editorEditBtn = cont.querySelector("#tp-edit")
const editorRenderBtn = cont.querySelector("#tp-render")
const editorSaveBtn = cont.querySelector("#tp-save")
const editorSaveNewBtn = cont.querySelector("#tp-save-new")
const editorBar = cont.querySelector(".tp-editor-bar")
const editorBarIcons = cont.querySelectorAll(".tp-editor-bar i")
const editorName = cont.querySelector("#tp-name");
const editorApiPassword = cont.querySelector("#tp-api-password");
const menuBpmBtn = document.querySelector("#tp-bpm-btn");
const menuBpmSpan = document.querySelector("#tp-bpm-result");
function toggleEditor() {
editor.classList.toggle("hidden");
editorBarIcons.forEach((e) => {
e.classList.toggle("fa-chevron-up");
e.classList.toggle("fa-chevron-down");
});
}
editorBar.onclick = toggleEditor;
editorEditBtn && (editorEditBtn.onclick = () => {
window.location.assign('?edit=true')
});
editorRenderBtn && (editorRenderBtn.onclick = () => {
render();
});
tabId = window.location.pathname.match(/\/tabs\/(\w+)/)?.[1];
editorSaveBtn && (editorSaveBtn.onclick = () => {
if (editorSaveBtn.classList.contains("disabled")) return;
fetch(
`/api/tabs/${tabId}`,
{
method: "PUT",
headers: new Headers({
"Content-Type": "application/json",
"X-Tp-Pass": editorApiPassword?.value || "",
}),
body: JSON.stringify({
name: editorName?.value || "",
tex: editorTextArea.value,
}),
},
).then(res => {
if (res.ok) {
edits_made = false;
updateEditorState();
}
});
});
editorSaveNewBtn && (editorSaveNewBtn.onclick = () => {
if (editorSaveNewBtn.classList.contains("disabled")) return;
fetch(
"/api/tabs",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Tp-Pass": editorApiPassword?.value || "",
},
body: JSON.stringify({
name: editorName?.value || "",
tex: editorTextArea.value,
}),
},
).then(res => {
if (res.ok) {
edits_made = false;
updateEditorState();
}
});
});
function render() {
let pos = at.tickPosition;
at.tex(editorTextArea.value);
at.tickPosition = pos;
editorTextArea.blur();
viewport.focus();
}
at.error.on((error) => {
overlayErrorText.innerText = error.message;
overlayError.style.display = "flex";
})
// Warn user when leaving unsaved changes.
let edits_made = false;
editorTextArea.oninput = () => {
edits_made = true;
updateEditorState();
}
editorName && (editorName.oninput = () => {
edits_made = true;
updateEditorState();
});
function updateEditorState() {
if (tabId) editorSaveBtn.classList.toggle("disabled", !edits_made);
editorSaveNewBtn.classList.toggle("disabled", !edits_made);
}
window.addEventListener("beforeunload", (e) => {
if (!edits_made) return undefined;
e.preventDefault();
return e.returnValue = `Changes made will be lost.`;
});
// keyboard
document.addEventListener("keydown", (event) => {
if (event.defaultPrevented) {
return; // Do nothing if event already handled
}
switch(event.key) {
case "Enter":
if (event.ctrlKey) { // Ctrl+Enter renders the alphaTex
render();
event.preventDefault(); // Consume the event so it doesn't get handled twice
}
else if (document.activeElement == document.body) { // Enter focuses textarea
if(editor.classList.contains("hidden")){
toggleEditor();
}
editorTextArea.focus();
event.preventDefault();
}
break;
case "s" | "S":
if (event.ctrlKey) {
if (event.shiftKey) {
// Ctrl+Shift+S
editorSaveNewBtn && editorSaveNewBtn.click();
} else {
// Ctrl+S
editorSaveBtn && editorSaveBtn.click();
}
event.preventDefault(); // Consume the event so it doesn't get handled twice
}
break;
}
}, true);
const BPM_RESET_MS = 2000;
let firstClick = -BPM_RESET_MS;
let lastClick = -BPM_RESET_MS;
let clickCount = 0;
menuBpmBtn && menuBpmSpan && (menuBpmBtn.onclick = (e) => {
let t = e.timeStamp;
if (t - lastClick > BPM_RESET_MS) {
clickCount = 1;
firstClick = lastClick = t;
menuBpmSpan.innerHTML = "-";
return;
}
clickCount++;
let int = (t - firstClick) / (clickCount - 1);
let ps = 1000 / int;
let bpm = 60 * ps;
console.log(bpm);
menuBpmSpan.innerHTML = Math.floor(bpm + 0.5);
lastClick = t;
});
editorTextArea.value = editorTextArea.value || `\\title ""
\\artist ""
\\album ""
\\instrument 33
\\tuning g2 d2 a1 e1
\\tempo 120
.
\\clef F4
\\ks C
3.3 3.3 3.3 3.3 | 2.3 2.3 2.3 2.3`;
at.tex(editorTextArea.value);
</script>
</div>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment