Compare commits

...

2 Commits

Author SHA1 Message Date
SArpnt 13ca78419b
improve typing 2024-04-23 17:16:19 -04:00
SArpnt 094ae74b05
improve readme build instructions 2024-04-17 02:57:39 -04:00
12 changed files with 82 additions and 75 deletions

View File

@ -296,6 +296,7 @@ something you might be wondering is why we're changing the phase and not the pit
1. changing the phase changes the pitch
pitch is just change of phase, so every part of the sin wave with a steep slope is an increase or decrease in pitch
2. trying to change the pitch doesn't even work
```js
(t*2*(1+sin(t/300)/64))%128
```

View File

@ -106,19 +106,11 @@ newer formats are complicated, so i'll only provide commit hashes and the code l
## building
- install pnpm
- ```sh
pnpm i --prod
pnpm run build
```
- `pnpm i --prod; pnpm run build`
output is in \_site, you can start a webserver there.
for actual development work:
```sh
pnpm i
pnpm run serve
```
for actual development work see the [web template](https://git.disroot.org/ficial/web-template)
please make some changes before uploading anywhere, and avoid github if you can.
microsoft has been scraping repositories to use for their github copilot crap,

View File

@ -1,4 +1,4 @@
import { f32ToRound10, isPlainObject, Song, SongMode } from "./common.ts";
import { f32ToRound10, isPlainObject, Song, SongMode, StrongPartial } from "./common.ts";
import elements from "./elements.ts";
import { fromUrlData, setUrlData } from "./url/mod.ts";
import { CodeEditor } from "./code-editor/mod.ts";
@ -18,7 +18,7 @@ if (globalThis.loadLibrary !== false) {
}
handleWindowResize(true);
document.defaultView.addEventListener("resize", handleWindowResize);
document.defaultView.addEventListener("resize", () => handleWindowResize(false));
let volume: number;
let timeUnit: TimeUnit;
@ -127,7 +127,6 @@ function handleMessage(e: MessageEvent<any>) {
}
}
}
let saveData = null;
function refreshCode() {
if (audioWorklet) {
@ -138,7 +137,7 @@ function handleWindowResize(force?: boolean) {
autoSizeCanvas(force);
}
function autoSizeCanvas(force: boolean = false) {
if (!elements.canvas.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) {
@ -179,7 +178,7 @@ function getSong(): Song {
return { code: codeEditor.getCode(), ...songData };
}
function setSong(songData?: Partial<Song>, play = true) {
function setSong(songData?: StrongPartial<Song>, play = true) {
let code, sampleRate, mode;
if (songData) {
({ code, sampleRate, mode } = songData);
@ -193,7 +192,7 @@ function setSong(songData?: Partial<Song>, play = true) {
togglePlay(true);
}
}
function applyPlaybackMode(playbackMode) {
function applyPlaybackMode(playbackMode: SongMode) {
setPlaybackMode(playbackMode);
elements.playbackMode.value = playbackMode;
}
@ -230,7 +229,7 @@ function resetTime() {
elements.canvasTogglePlay.classList.add("canvas-toggleplay-show");
}
}
function setByteSample(value, send = true, clear = false) {
function setByteSample(value: number, send = true, clear = false) {
if (audioWorklet && isFinite(value)) {
byteSample = value;
updateCounterValue();
@ -240,7 +239,7 @@ function setByteSample(value, send = true, clear = false) {
osc.moveTimeCursor(byteSample, playSpeed > 0);
}
}
function setPlaybackMode(playbackMode) {
function setPlaybackMode(playbackMode: SongMode) {
if (audioWorklet) {
songData.mode = playbackMode;
if (globalThis.useUrlData !== false) {
@ -252,7 +251,8 @@ function setPlaybackMode(playbackMode) {
function setSampleRate(sampleRate: number) {
if (audioWorklet) {
const rate = Math.min(Math.max(Math.fround(sampleRate), 2), 2 ** 24 - 1);
elements.sampleRate.value = f32ToRound10(rate);
// implicit cast
elements.sampleRate.value = f32ToRound10(rate) as unknown as string;
songData.sampleRate = rate;
// TODO if funcbeat change time so that sec stays the same
audioWorklet.port.postMessage({ songData, updateSampleRatio: true });
@ -290,9 +290,10 @@ function timeCursorShouldBeVisible(): boolean {
}
function updateCounterValue() {
elements.timeValue.placeholder = convertToUnit(byteSample);
// implicit cast
elements.timeValue.placeholder = convertToUnit(byteSample) as unknown as string;
}
function convertFromUnit(value, unit = timeUnit) {
function convertFromUnit(value: number, unit = timeUnit) {
switch (unit) {
case "t":
return value;
@ -309,7 +310,7 @@ function convertToUnit(value: number, unit = timeUnit) {
return (value / songData.sampleRate).toFixed(3);
}
}
function setTimeUnit(value: number, userInput = true) {
function setTimeUnit(value?: number | TimeUnit, userInput = true) {
if (value !== undefined) {
if (typeof value === "number") {
timeUnit = timeUnits[value];
@ -352,7 +353,8 @@ function changeScale(amount: number) {
function setVolume(save = true, value?: number) {
if (value !== undefined) {
volume = value;
elements.volume.value = volume;
// implicit cast
elements.volume.value = volume as unknown as string;
}
volume = elements.volume.valueAsNumber;
@ -373,22 +375,23 @@ function loadDefaultSettings() {
setTimeUnit(undefined, false);
}
function loadSettings(): Settings {
if (localStorage.settings && globalThis.useLocalStorage !== false) {
if (globalThis.useLocalStorage !== false && localStorage["settings"]) {
let settings: {
drawSettings: typeof osc.settings;
volume: typeof volume;
timeUnit: typeof timeUnit;
drawSettings?: typeof osc.settings;
volume?: typeof volume;
timeUnit?: typeof timeUnit;
};
try {
settings = JSON.parse(localStorage.settings);
// FIXME: this should be validated
settings = JSON.parse(localStorage["settings"]);
} catch (err) {
console.error("Couldn't load settings!", localStorage.settings);
console.error("Couldn't load settings!", localStorage["settings"]);
localStorage.clear();
loadDefaultSettings();
return;
}
if (Object.hasOwnProperty.call(settings, "drawSettings")) {
if (settings.drawSettings) {
osc.settings = settings.drawSettings;
} else {
osc.settings.scale = 5;
@ -396,7 +399,7 @@ function loadSettings(): Settings {
setVolume(false, settings.volume);
if (Object.hasOwnProperty.call(settings, "timeUnit")) {
if (settings.timeUnit) {
setTimeUnit(settings.timeUnit, false);
} else {
setTimeUnit(undefined, false);
@ -407,7 +410,7 @@ function loadSettings(): Settings {
}
function saveSettings() {
if (globalThis.useLocalStorage !== false) {
localStorage.settings = JSON.stringify({
localStorage["settings"] = JSON.stringify({
drawSettings: osc.settings,
volume,
timeUnit,

View File

@ -18,6 +18,9 @@ class CodeEditor {
const codemirror = c.replace(editCallback, textarea);
this.getCode = () => codemirror.state.doc.toString();
this.setCode = v =>
// codemirror type is wrong
// TODO pull request fix
// @ts-ignore
codemirror.dispatch({
changes: { from: 0, to: codemirror.state.doc.length, insert: v },
});

View File

@ -19,8 +19,8 @@ function initTextarea(editCallback: () => void, textarea: HTMLTextAreaElement) {
let line = 0;
for (let c = 0; ; line++) {
c += lines[line].length;
if (c > char) 1;
break;
if (c > char)
break;
}
return line;
};

View File

@ -1,3 +1,5 @@
type StrongPartial<T> = { [P in keyof T]?: T[P] | undefined };
function f32ToRound10(value: number): number {
// this is stupid but it works for numbers less
// than 10 digits on one side of the decimal point
@ -33,4 +35,4 @@ type Song = {
mode: SongMode;
};
export { f32ToRound10, isPlainObject, SmallSongMode, SongMode, Song };
export { StrongPartial, f32ToRound10, isPlainObject, SmallSongMode, SongMode, Song };

View File

@ -1,19 +1,19 @@
export default {
main: document.getElementsByTagName("main")[0],
error: document.getElementById("error")!,
canvas: document.getElementById("canvas-main")!, // this is the only shared element
canvas: document.getElementById("canvas-main") as HTMLCanvasElement, // this is the only shared element
timeCursor: document.getElementById("canvas-timecursor")!,
timeUnit: document.getElementById("control-time-unit")!,
timeUnitLabel: document.getElementById("control-time-unit-label")!,
timeValue: document.getElementById("control-time-value")!,
timeValue: document.getElementById("control-time-value") as HTMLInputElement,
scaleUp: document.getElementById("control-scaleup")!,
scaleDown: document.getElementById("control-scaledown")!,
scaleUp: document.getElementById("control-scaleup") as HTMLButtonElement,
scaleDown: document.getElementById("control-scaledown") as HTMLButtonElement,
playbackMode: document.getElementById("control-song-mode")!,
sampleRate: document.getElementById("control-sample-rate")!,
volume: document.getElementById("control-volume")!,
playbackMode: document.getElementById("control-song-mode") as HTMLSelectElement,
sampleRate: document.getElementById("control-sample-rate") as HTMLInputElement,
volume: document.getElementById("control-volume") as HTMLInputElement,
canvasTogglePlay: document.getElementById("canvas-toggleplay")!,

View File

@ -7,27 +7,27 @@ const nameids: [string, (show: boolean) => void, ...string[]][] = [
[
"timeControls",
show => {
elements.controls.dataset.timeControlsDisabled = !show as unknown as string;
elements.canvasContainer.dataset.disabled = !show as unknown as string;
elements.controls.dataset["timeControlsDisabled"] = !show as unknown as string;
elements.canvasContainer.dataset["disabled"] = !show as unknown as string;
},
"canvas-toggleplay",
],
[
"playbackControls",
show => {
elements.controls.dataset.playbackControlsDisabled = !show as unknown as string;
elements.controls.dataset["playbackControlsDisabled"] = !show as unknown as string;
},
],
[
"viewControls",
show => {
elements.controls.dataset.viewControlsDisabled = !show as unknown as string;
elements.controls.dataset["viewControlsDisabled"] = !show as unknown as string;
},
],
[
"songControls",
show => {
elements.controls.dataset.songControlsDisabled = !show as unknown as string;
elements.controls.dataset["songControlsDisabled"] = !show as unknown as string;
},
],
["error", () => {}, "error"],
@ -41,9 +41,9 @@ window.addEventListener(
if (isPlainObject(e.data)) {
const data: { [index: string]: any } = e.data;
// show/hide elements
if (isPlainObject(data.show)) {
if (isPlainObject(data["show"])) {
for (const [name, fn, ...ids] of nameids) {
const show: { [index: string]: any } = data.show;
const show: { [index: string]: any } = data["show"];
if (show[name] !== undefined) {
if (show[name]) {
fn(true);
@ -60,21 +60,21 @@ window.addEventListener(
}
}
if (data.forceScopeWidth !== undefined && elements.canvas) {
if (typeof data.forceScopeWidth === "number") {
elements.canvas.dataset.forcedWidth = true as unknown as string;
setCanvasWidth(data.forceScopeWidth);
if (data["forceScopeWidth"] !== undefined && elements.canvas) {
if (typeof data["forceScopeWidth"] === "number") {
elements.canvas.dataset["forcedWidth"] = true as unknown as string;
setCanvasWidth(data["forceScopeWidth"]);
} else {
delete elements.canvas.dataset.forcedWidth;
delete elements.canvas.dataset["forcedWidth"];
autoSizeCanvas();
}
}
if (data.getSong) {
if (data["getSong"]) {
window.parent.postMessage({ song: getSong() }, "*");
}
if (isPlainObject(data.setSong)) {
setSong(data.setSong, false);
if (isPlainObject(data["setSong"])) {
setSong(data["setSong"], false);
}
}
},

View File

@ -53,11 +53,11 @@ function createEntryElem(entry: Entry) {
) as HTMLElement[];
if (songElems.length) {
for (const elem of songElems) {
const songData = elem.dataset.songData ? JSON.parse(elem.dataset.songData) : {};
const songData = elem.dataset["songData"] ? JSON.parse(elem.dataset["songData"]) : {};
const onclick = elem.dataset.hasOwnProperty("codeFile")
? () =>
fetch(`/library/${elem.dataset.codeFile}`)
fetch(`/library/${elem.dataset["codeFile"]}`)
.then(response => response.text())
.then(code => setSong(Object.assign(songData, { code })))
: () => setSong(Object.assign({ code: elem.innerText }, songData));
@ -233,9 +233,8 @@ function createEntryElem(entry: Entry) {
if (entry.children) {
let childrenElem = document.createElement("ul");
for (let i = 0, len = entry.children.length; i < len; ++i) {
let childEntry = entry.children[i];
childrenElem.append(createEntryElem(childEntry));
for (const child of entry.children) {
childrenElem.append(createEntryElem(child));
}
entryElem.append(childrenElem);
}

View File

@ -28,10 +28,14 @@ function into89(data: Uint8Array, bitIndex = 0): string {
while (true) {
//const byteIndex = bitIndex >> 3;
//const bitOffset = bitIndex & 7;
let bigint = 0n;
// collects 123 bits of data
let chunkInt = 0n;
for (let i = 0; i < 123; i++) {
if (bitIndex >> 3 < data.length) {
bigint += BigInt(!!(data[bitIndex >> 3] & (1 << (bitIndex & 7)))) << BigInt(i);
// if bitIndex is negative, this will implicitly cast
const currentByte = data[bitIndex >> 3] as number;
const bitOfByte = 1 << (bitIndex & 7);
chunkInt += BigInt(!!(currentByte & bitOfByte)) << BigInt(i);
bitIndex++;
} else {
// length kept being wrong, i gave up on it
@ -45,17 +49,17 @@ function into89(data: Uint8Array, bitIndex = 0): string {
// bigint /= 89n;
//}
//// unreachable
while (bigint) {
coded += tochar[Number(bigint % 89n)];
bigint /= 89n;
while (chunkInt) {
coded += tochar[Number(chunkInt % 89n)];
chunkInt /= 89n;
}
return coded;
}
}
//bigint = BigInt.asUintN(123, bigint << BigInt(bitOffset));
for (let j = 0; j < 19; j++) {
coded += tochar[Number(bigint % 89n)];
bigint /= 89n;
coded += tochar[Number(chunkInt % 89n)];
chunkInt /= 89n;
}
}
}
@ -82,13 +86,13 @@ function from89(coded: string, bitIndex = 0 /*, byteUp = true*/): Uint8Array {
if (coded.length) {
const chunks = coded.match(/.{1,19}/g) as RegExpMatchArray;
for (const chunk of chunks) {
let bigint = 0n;
let chunkInt = 0n;
for (const letter of chunk.split("").reverse()) {
bigint *= 89n;
bigint += fromchar[letter];
chunkInt *= 89n;
chunkInt += fromchar[letter];
}
for (let i = 123; i; i--) {
let bit = Number(bigint & 1n);
const bit = Number(chunkInt & 1n);
currentByte |= bit << (bitIndex & 7);
if ((bitIndex & 7) === 7) {
data[bitIndex >> 3] = currentByte;
@ -101,7 +105,7 @@ function from89(coded: string, bitIndex = 0 /*, byteUp = true*/): Uint8Array {
}
return data;
}
bigint >>= 1n;
chunkInt >>= 1n;
}
}
}

View File

@ -30,6 +30,7 @@ async function fromUrlData(hash: string): Promise<Song | undefined> {
let compressedCode;
if (v === "6") {
data = from89(hash.substring(2), 12);
// if data[1] is undefined the implicit cast to 0 is fine
data[1] = (data[1] >> 4) | 0x40;
compressedCode = data.subarray(6);
} else {

View File

@ -9,7 +9,9 @@
"strict": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
// as far as i can tell there is no way to define a type that indicates a
// valid index for some value. this makes this pretty much impossible to use
//"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"types": ["jest"]