Compare commits

...

3 Commits

Author SHA1 Message Date
SArpnt 1291779d54
create dom.ts to get elements 2023-12-28 15:34:32 -05:00
SArpnt bf138faef3
add typescript as dev dep 2023-12-28 15:33:40 -05:00
SArpnt 655465265d
type annotate library 2023-12-28 15:33:05 -05:00
10 changed files with 228 additions and 193 deletions

View File

@ -29,5 +29,6 @@
devDependencies: {
'@types/pako': '^2.0.3',
prettier: '^3.1.1',
typescript: '^5.3.3',
},
}

View File

@ -70,6 +70,9 @@ devDependencies:
prettier:
specifier: ^3.1.1
version: 3.1.1
typescript:
specifier: ^5.3.3
version: 5.3.3
packages:
@ -1871,6 +1874,12 @@ packages:
resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==}
dev: false
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
/uc.micro@1.0.6:
resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==}
dev: false

View File

@ -1,5 +1,6 @@
import { inflateRaw, deflateRaw } from "pako";
import { domLoaded, isPlainObject } from "./common.ts";
import { isPlainObject } from "./common.ts";
import { elements as elementsPromise } from "./dom.ts";
const searchParams = new URLSearchParams(location.search);
@ -17,7 +18,7 @@ let nextErr = null;
let nextErrPriority = undefined;
let errorPriority = -Infinity;
let canvasCtx = null;
let ctx = null;
let drawSettings = { scale: null };
let drawBuffer = [];
let drawImageData = null;
@ -33,22 +34,31 @@ let timeUnit = null;
let animationFrameId = null;
let canvasElem = null;
let codeEditor = null;
let errorElem = null;
let timeCursorElem = null;
let contentElem = null;
let elements = null; // TODO remove
let controlTimeUnit = null;
let controlTimeUnitLabel = null;
let controlTimeValue = null;
let controlScaleUp = null;
let controlScaleDown = null;
let controlPlaybackMode = null;
let controlSampleRate = null;
let controlVolume = null;
let canvasTogglePlay = null;
(async function init() {
await initAudioContext();
elements = await elementsPromise;
ctx = elements.canvas.getContext("2d", { alpha: false });
initTextarea(document.getElementById("code-editor"));
import("./codemirror.ts").then(o => initCodemirror(o.default));
if (globalThis.loadLibrary !== false) {
import("./load-library.ts");
}
handleWindowResize(true);
document.defaultView.addEventListener("resize", () => handleWindowResize(false));
loadSettings();
const urlData = getUrlData();
setSong(urlData, false);
updateCounterValue();
})();
async function initAudioContext() {
let audioContextSampleRate = Number(searchParams.get("audioContextSampleRate"));
@ -95,7 +105,7 @@ function handleMessage(e) {
if (Array.isArray(data.drawBuffer)) {
drawBuffer = drawBuffer.concat(data.drawBuffer);
// prevent buffer accumulation when tab inactive
const maxDrawBufferSize = getTimeFromXpos(canvasElem.width) - 1;
const maxDrawBufferSize = getTimeFromXpos(elements.canvas.width) - 1;
if (byteSample - drawBuffer[drawBuffer.length >> 1].t > maxDrawBufferSize) {
// reasonable lazy cap
drawBuffer = drawBuffer.slice(drawBuffer.length >> 1);
@ -271,24 +281,6 @@ function setUrlData() {
window.location.hash = `#v4${btoa(dataString).replaceAll("=", "")}`;
}
}
function initControls() {
controlTimeUnit = document.getElementById("control-time-unit");
controlTimeUnitLabel = document.getElementById("control-time-unit-label");
controlTimeValue = document.getElementById("control-time-value");
controlScaleDown = document.getElementById("control-scaledown");
controlScaleUp = document.getElementById("control-scaleup");
controlPlaybackMode = document.getElementById("control-song-mode");
controlSampleRate = document.getElementById("control-samplerate");
controlVolume = document.getElementById("control-volume");
errorElem = document.getElementById("error");
canvasTogglePlay = document.getElementById("canvas-toggleplay");
timeCursorElem = document.getElementById("canvas-timecursor");
canvasElem = document.getElementById("canvas-main");
canvasCtx = canvasElem.getContext("2d", { alpha: false });
}
function refreshCode() {
if (audioWorklet) {
audioWorklet.port.postMessage({ code: getCodeEditorText().trim() });
@ -298,7 +290,7 @@ function handleWindowResize(force: boolean) {
autoSizeCanvas(force);
}
function autoSizeCanvas(force: boolean) {
if (!canvasElem.dataset.forcedWidth) {
if (!elements.canvas.dataset.forcedWidth) {
const innerWidth = window.innerWidth;
// 768 is halfway between 512 and 1024, 3 added for outline
if (innerWidth >= 768 + 3) {
@ -314,11 +306,11 @@ function autoSizeCanvas(force: boolean) {
}
}
function setCanvasWidth(width: number, force: boolean = false) {
if (canvasElem) {
if (width !== canvasElem.width || force) {
canvasElem.width = width;
if (elements.canvas) {
if (width !== elements.canvas.width || force) {
elements.canvas.width = width;
// TODO: see if it's possible to get rid of this
contentElem.style.maxWidth = `${width + 4}px`;
elements.main.style.maxWidth = `${width + 4}px`;
}
}
}
@ -362,11 +354,11 @@ function setSong(songData, play = true) {
}
function applySampleRate(rate) {
setSampleRate(rate);
controlSampleRate.value = rate;
elements.sampleRate.value = rate;
}
function applyPlaybackMode(playbackMode) {
setPlaybackMode(playbackMode);
controlPlaybackMode.value = playbackMode;
elements.playbackMode.value = playbackMode;
}
function changeScale(amount) {
@ -374,9 +366,9 @@ function changeScale(amount) {
drawSettings.scale = Math.max(drawSettings.scale + amount, 0);
clearCanvas(false);
if (drawSettings.scale <= 0) {
controlScaleDown.setAttribute("disabled", true);
elements.scaleDown.setAttribute("disabled", true);
} else {
controlScaleDown.removeAttribute("disabled");
elements.scaleDown.removeAttribute("disabled");
}
toggleTimeCursor();
@ -387,9 +379,9 @@ function changeScale(amount) {
function setVolume(save = true, value?: number) {
if (value !== undefined) {
volume = value;
controlVolume.value = volume;
elements.volume.value = volume;
} else {
volume = controlVolume.valueAsNumber;
volume = elements.volume.valueAsNumber;
}
if (audioGain !== null) {
@ -402,8 +394,8 @@ function setVolume(save = true, value?: number) {
}
function clearCanvas(clear = true) {
if (canvasCtx) {
canvasCtx.fillRect(0, 0, canvasElem.width, canvasElem.height);
if (ctx) {
ctx.fillRect(0, 0, elements.canvas.width, elements.canvas.height);
if (clear) {
clearDrawBuffer();
}
@ -423,7 +415,7 @@ function getTimeFromXpos(x) {
return x * (1 << drawSettings.scale);
}
function drawGraphics() {
const { width, height } = canvasElem;
const { width, height } = elements.canvas;
const bufferLen = drawBuffer.length;
if (!bufferLen) {
@ -472,7 +464,7 @@ function drawGraphics() {
const imagePos = Math.min(drawStartX, drawEndX);
// create imageData
let imageData = canvasCtx.createImageData(drawLenX, height);
let imageData = ctx.createImageData(drawLenX, height);
// create / add drawimageData
if (drawSettings.scale) {
// full zoom can't have multiple samples on one pixel
@ -487,7 +479,7 @@ function drawGraphics() {
}
}
} else {
drawImageData = canvasCtx.createImageData(1, height);
drawImageData = ctx.createImageData(1, height);
}
} else {
drawImageData = null;
@ -576,11 +568,11 @@ function drawGraphics() {
}
}
// put imageData
canvasCtx.putImageData(imageData, imagePos, 0);
ctx.putImageData(imageData, imagePos, 0);
if (endXPos >= width) {
canvasCtx.putImageData(imageData, imagePos - width, 0);
ctx.putImageData(imageData, imagePos - width, 0);
} else if (endXPos < 0) {
canvasCtx.putImageData(imageData, imagePos + width, 0);
ctx.putImageData(imageData, imagePos + width, 0);
}
// write to drawImageData
if (drawSettings.scale) {
@ -601,22 +593,22 @@ function drawGraphics() {
drawBuffer = [{ t: endTime, value: drawBuffer[bufferLen - 1].value, carry: true }];
}
function moveTimeCursor(time = byteSample) {
if (timeCursorElem && timeCursorVisible()) {
const width = canvasElem.width;
if (elements.timeCursor && timeCursorVisible()) {
const width = elements.canvas.width;
if (playSpeed > 0) {
timeCursorElem.style.removeProperty("right");
timeCursorElem.style.left = `${(fmod(Math.ceil(getXpos(time)), width) / width) * 100}%`;
elements.timeCursor.style.removeProperty("right");
elements.timeCursor.style.left = `${(fmod(Math.ceil(getXpos(time)), width) / width) * 100}%`;
} else {
timeCursorElem.style.removeProperty("left");
timeCursorElem.style.right = `${
elements.timeCursor.style.removeProperty("left");
elements.timeCursor.style.right = `${
(1 - (fmod(Math.ceil(getXpos(time)), width) + 1) / width) * 100
}%`;
}
}
}
function hideErrorMessage() {
if (errorElem) {
errorElem.innerText = "";
if (elements.error) {
elements.error.innerText = "";
nextErr = null;
nextErrType = null;
@ -625,9 +617,9 @@ function hideErrorMessage() {
}
}
function showErrorMessage(errType, err, priority = 0) {
if (errorElem && (errorPriority < 2 || priority > 0)) {
errorElem.dataset.errType = errType;
errorElem.innerText = err.toString();
if (elements.error && (errorPriority < 2 || priority > 0)) {
elements.error.dataset.errType = errType;
elements.error.innerText = err.toString();
nextErr = null;
nextErrType = null;
@ -643,7 +635,7 @@ function showErrorMessage(errType, err, priority = 0) {
function resetTime() {
setByteSample(0, true, true);
if (!isPlaying) {
canvasTogglePlay.classList.add("canvas-toggleplay-show");
elements.canvasTogglePlay.classList.add("canvas-toggleplay-show");
}
}
function setByteSample(value, send = true, clear = false) {
@ -682,8 +674,8 @@ function setPlaySpeed(value: number) {
}
}
function toggleTimeCursor() {
if (timeCursorElem) {
timeCursorElem.classList.toggle("disabled", !timeCursorVisible());
if (elements.timeCursor) {
elements.timeCursor.classList.toggle("disabled", !timeCursorVisible());
}
}
function timeCursorVisible() {
@ -691,9 +683,9 @@ function timeCursorVisible() {
}
function togglePlay(play: boolean = !isPlaying) {
if (audioWorklet && play !== isPlaying) {
canvasTogglePlay.classList.toggle("canvas-toggleplay-pause", play);
elements.canvasTogglePlay.classList.toggle("canvas-toggleplay-pause", play);
if (play) {
canvasTogglePlay.classList.remove("canvas-toggleplay-show");
elements.canvasTogglePlay.classList.remove("canvas-toggleplay-show");
if (audioCtx?.resume) {
audioCtx.resume();
}
@ -707,7 +699,7 @@ function togglePlay(play: boolean = !isPlaying) {
}
function updateCounterValue() {
controlTimeValue.placeholder = convertToUnit(byteSample);
elements.timeValue.placeholder = convertToUnit(byteSample);
}
function convertFromUnit(value, unit = timeUnit) {
switch (unit) {
@ -733,9 +725,9 @@ function setTimeUnit(value, userInput = true) {
} else {
timeUnit = value;
}
controlTimeUnitLabel.innerText = timeUnit;
elements.timeUnitLabel.innerText = timeUnit;
} else {
timeUnit = controlTimeUnitLabel.innerText;
timeUnit = elements.timeUnitLabel.innerText;
}
if (userInput) {
@ -745,7 +737,7 @@ function setTimeUnit(value, userInput = true) {
}
function changeTimeUnit() {
timeUnit = timeUnits[(timeUnits.indexOf(timeUnit) + 1) % timeUnits.length];
controlTimeUnitLabel.innerText = timeUnit;
elements.timeUnitLabel.innerText = timeUnit;
updateCounterValue();
saveSettings();
@ -817,25 +809,4 @@ Object.defineProperty(globalThis, "bytebeat", {
},
});
(async function init() {
await initAudioContext();
await domLoaded;
contentElem = document.getElementById("content");
initControls();
initTextarea(document.getElementById("code-editor"));
import("./codemirror.ts").then(o => initCodemirror(o.default));
if (globalThis.loadLibrary !== false) {
import("./load-library.ts");
}
handleWindowResize(true);
document.defaultView.addEventListener("resize", () => handleWindowResize(false));
loadSettings();
const urlData = getUrlData();
setSong(urlData, false);
updateCounterValue();
})();
export { canvasElem, setCanvasWidth, autoSizeCanvas, getSong, setSong };
export { setCanvasWidth, autoSizeCanvas, getSong, setSong };

View File

@ -1,11 +1,3 @@
const domLoaded = new Promise(resolve => {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => resolve(undefined));
} else {
resolve(undefined);
}
});
function isPlainObject(value: any) {
if (value && typeof value === "object") {
const proto = Object.getPrototypeOf(value);
@ -16,4 +8,4 @@ function isPlainObject(value: any) {
return false;
}
export { domLoaded, isPlainObject };
export { isPlainObject };

35
src/dom.ts Normal file
View File

@ -0,0 +1,35 @@
const domLoaded = new Promise(resolve => {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => resolve(undefined));
} else {
resolve(undefined);
}
});
const elements = (async () => {
await domLoaded;
return {
main: document.getElementsByTagName("main")[0],
error: document.getElementById("error"),
canvas: document.getElementById("canvas-main"),
timeCursor: document.getElementById("canvas-timecursor"),
timeUnit: document.getElementById("control-time-unit"),
timeUnitLabel: document.getElementById("control-time-unit-label"),
timeValue: document.getElementById("control-time-value"),
scaleUp: document.getElementById("control-scaleup"),
scaleDown: document.getElementById("control-scaledown"),
playbackMode: document.getElementById("control-song-mode"),
sampleRate: document.getElementById("control-sample-rate"),
volume: document.getElementById("control-volume"),
canvasTogglePlay: document.getElementById("canvas-toggleplay"),
controls: document.getElementById("controls"),
canvasContainer: document.getElementById("canvas-container"),
};
})();
export { elements };

View File

@ -151,7 +151,7 @@
<span class="control-group">
<input
title="Sample rate"
id="control-samplerate"
id="control-sample-rate"
class="control-song control-round-right"
type="number"
min="0"
@ -163,17 +163,17 @@
onblur="this.value||=this.placeholder;this.placeholder=''"
onchange="bytebeat.setSampleRate(+this.value); bytebeat.refreshCode();"
/><span class="text"
><label for="control-samplerate" class="control-song"
><label for="control-sample-rate" class="control-song"
>Hz</label
>
<label
for="control-samplerate-divisor"
for="control-sample-rate-divisor"
class="control-playback"
>/</label
></span
><input
title="Sample rate divisor"
id="control-samplerate-divisor"
id="control-sample-rate-divisor"
class="control-playback control-round-left control-round-right"
type="number"
min="1"
@ -182,7 +182,7 @@
style="width: 3em"
onchange="bytebeat.setSampleRateDivisor(+this.value); bytebeat.refreshCode();"
/>
<datalist id="samplerates">
<datalist id="sample-rates">
<option value="8000"></option>
<option value="11025"></option>
<option value="16000"></option>

View File

@ -1,88 +1,83 @@
import { isPlainObject } from "./common.ts";
import { canvasElem, setCanvasWidth, autoSizeCanvas, getSong, setSong } from "./bytebeat.ts";
import { elements } from "./dom.ts";
import { setCanvasWidth, autoSizeCanvas, getSong, setSong } from "./bytebeat.ts";
const nameids: [string, ...(string | ((show: boolean) => void))[]][] = [
["codeEditor", "code-editor-container"],
[
"timeControls",
show => {
document.getElementById("controls").dataset.timeControlsDisabled = !show;
},
"canvas-toggleplay",
show => {
document.getElementById("canvas-container").dataset.disabled = !show;
},
],
[
"playbackControls",
show => {
document.getElementById("controls").dataset.playbackControlsDisabled = !show;
},
],
[
"viewControls",
show => {
document.getElementById("controls").dataset.viewControlsDisabled = !show;
},
],
[
"songControls",
show => {
document.getElementById("controls").dataset.songControlsDisabled = !show;
},
],
["error", "error"],
["scope", "canvas-container"],
];
elements.then(elements => {
const nameids: [string, (show: boolean) => void, ...string[]][] = [
["codeEditor", () => {}, "code-editor-container"],
[
"timeControls",
show => {
elements.controls.dataset.timeControlsDisabled = !show;
elements.canvasContainer.dataset.disabled = !show;
},
"canvas-toggleplay",
],
[
"playbackControls",
show => {
elements.controls.dataset.playbackControlsDisabled = !show;
},
],
[
"viewControls",
show => {
elements.controls.dataset.viewControlsDisabled = !show;
},
],
[
"songControls",
show => {
elements.controls.dataset.songControlsDisabled = !show;
},
],
["error", () => {}, "error"],
["scope", () => {}, "canvas-container"],
];
window.addEventListener(
"message",
e => {
console.info("recieved message", e);
if (isPlainObject(e.data)) {
const data = e.data;
// show/hide elements
if (isPlainObject(data.show)) {
for (const [name, ...ids] of nameids) {
if (data.show[name] !== undefined) {
if (data.show[name]) {
for (const id of ids) {
if (typeof id === "function") {
id(true);
} else {
window.addEventListener(
"message",
e => {
console.info("recieved message", e);
if (isPlainObject(e.data)) {
const data = e.data;
// show/hide elements
if (isPlainObject(data.show)) {
for (const [name, fn, ...ids] of nameids) {
if (data.show[name] !== undefined) {
if (data.show[name]) {
fn(true);
for (const id of ids) {
document.getElementById(id).classList.remove("disabled");
}
}
} else {
for (const id of ids) {
if (typeof id === "function") {
id(false);
} else {
} else {
fn(false);
for (const id of ids) {
document.getElementById(id).classList.add("disabled");
}
}
}
}
}
}
if (data.forceScopeWidth !== undefined && canvasElem) {
if (typeof data.forceScopeWidth === "number") {
canvasElem.dataset.forcedWidth = true;
setCanvasWidth(data.forceScopeWidth);
} else {
delete canvasElem.dataset.forcedWidth;
autoSizeCanvas();
if (data.forceScopeWidth !== undefined && elements.canvas) {
if (typeof data.forceScopeWidth === "number") {
elements.canvas.dataset.forcedWidth = true;
setCanvasWidth(data.forceScopeWidth);
} else {
delete elements.canvas.dataset.forcedWidth;
autoSizeCanvas();
}
}
if (data.getSong) {
window.parent.postMessage({ song: getSong(true) }, "*");
}
if (isPlainObject(data.setSong)) {
setSong(data.setSong, false);
}
}
if (data.getSong) {
window.parent.postMessage({ song: getSong(true) }, "*");
}
if (isPlainObject(data.setSong)) {
setSong(data.setSong, false);
}
}
},
false,
);
},
false,
);
});

View File

@ -93,7 +93,7 @@ html(data-embed lang="en")
option(value="Floatbeat") Floatbeat
option(value="Funcbeat") Funcbeat
span.control-group
input#control-samplerate.control-song.control-round-right(
input#control-sample-rate.control-song.control-round-right(
onchange="bytebeat.setSampleRate(+this.value); bytebeat.refreshCode();"
onblur="this.value||=this.placeholder;this.placeholder=''"
onfocus="this.placeholder=this.value;this.value=''"
@ -106,9 +106,9 @@ html(data-embed lang="en")
title="Sample rate"
)
span.text
label.control-song(for="control-samplerate") Hz
label.control-playback(for="control-samplerate-divisor") /
input#control-samplerate-divisor.control-playback.control-round-left.control-round-right(
label.control-song(for="control-sample-rate") Hz
label.control-playback(for="control-sample-rate-divisor") /
input#control-sample-rate-divisor.control-playback.control-round-left.control-round-right(
onchange="bytebeat.setSampleRateDivisor(+this.value); bytebeat.refreshCode();"
style="width: 3em"
value="1"

View File

@ -94,7 +94,7 @@ html(lang="en")
option(value="Floatbeat") Floatbeat
option(value="Funcbeat") Funcbeat
span.control-group
input#control-samplerate.control-song.control-round-right(
input#control-sample-rate.control-song.control-round-right(
onchange="bytebeat.setSampleRate(+this.value); bytebeat.refreshCode();"
onblur="this.value||=this.placeholder;this.placeholder=''"
onfocus="this.placeholder=this.value;this.value=''"
@ -107,9 +107,9 @@ html(lang="en")
title="Sample rate"
)
span.text
label.control-song(for="control-samplerate") Hz
label.control-playback(for="control-samplerate-divisor") /
input#control-samplerate-divisor.control-playback.control-round-left.control-round-right(
label.control-song(for="control-sample-rate") Hz
label.control-playback(for="control-sample-rate-divisor") /
input#control-sample-rate-divisor.control-playback.control-round-left.control-round-right(
onchange="bytebeat.setSampleRateDivisor(+this.value); bytebeat.refreshCode();"
style="width: 3em"
value="1"

View File

@ -1,4 +1,34 @@
export default {
// name, url
type Author = string | [string, string];
type RemixedEntry = {
author?: string,
description: string,
url?: string,
}
type Entry = {
children?: Entry[],
author?: Author | Author[],
description?: string,
url?: string,
date?: string,
remixed?: RemixedEntry,
sampleRate?: number,
mode?: "Signed Bytebeat" | "Floatbeat" | "Funcbeat",
codeOriginal?: string | string[], // TODO remove string[]
codeMinified?: string,
file?: string,
fileFormatted?: true,
fileMinified?: true,
fileOriginal?: true,
fileOptimized?: true,
};
const library: {
[index: string]: Entry[],
} = {
"classic": [
{
"author": "tejeez",
@ -1599,7 +1629,7 @@ export default {
},
"url": "https://www.reddit.com/r/bytebeat/comments/ry4ikf/comment/hrmh2m3/?utm_source=reddit&utm_medium=web2x&context=3",
"date": "2022-01-07",
"mode": "Sighned Bytebeat",
"mode": "Signed Bytebeat",
"sampleRate": 11025,
"codeOriginal": "(t>>4|t*(t&16384?7:5)*(3-(3&t>>9)+(3&t>>8))>>(3&-t>>(t%65536<59392?(t&4096?2:16):2))&96)+(64*random(t)*(1&t>>11))+(44444/(t%pow(4,(3+(2+(1)))))&128)",
"codeMinified": "(t>>4|t*(t&16384?7:5)*(3-(3&t>>9)+(3&t>>8))>>(3&-t>>(59392>t%65536?t&4096?2:16:2))&96)+64*random()*(1&t>>11)+(44444/(t%4096)&128)"
@ -2886,3 +2916,5 @@ export default {
}
]
};
export {library, Entry};