summaryrefslogtreecommitdiff
path: root/index.htm
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2025-01-06 23:41:27 +0100
committerMitya Selivanov <automainint@guattari.tech>2025-01-06 23:41:27 +0100
commiteab2433a2daee814492548b95f16892adde0908d (patch)
treee08d2b82fc3525d937bb64475da5543b4628158c /index.htm
parent3359f068dc9e8ac036f0f709aeccf11dfba3cf03 (diff)
downloadreduced_system_layer-eab2433a2daee814492548b95f16892adde0908d.zip
Impl rendering in AudioWorklet
Diffstat (limited to 'index.htm')
-rw-r--r--index.htm470
1 files changed, 263 insertions, 207 deletions
diff --git a/index.htm b/index.htm
index cb1f474..7f82840 100644
--- a/index.htm
+++ b/index.htm
@@ -10,46 +10,149 @@
<canvas style="margin: 0; width: 100%; height: 100%;" id="frame" oncontextmenu="event.preventDefault()"></canvas>
<script type="worklet">
registerProcessor(
- 'Sound_Ring',
+ 'Sound_Node',
class Sound_Ring extends AudioWorkletProcessor {
constructor(options) {
super();
- this.ring = options.processorOptions.ring;
+ this.frame_width = 0;
+ this.frame_height = 0;
+
+ let event_handlers = {
+ wasm : (ev) => {
+ WebAssembly.instantiate(
+ ev.wasm,
+ {
+ wasi_snapshot_preview1 : {
+ clock_time_get : () => { return Date.now(); },
+ args_sizes_get : () => { throw new Error("Unexpected args_sizes_get call"); },
+ args_get : () => { throw new Error("Unexpected args_get call"); },
+ proc_exit : () => { throw new Error("Unexpected proc_exit call"); },
+ fd_close : () => { throw new Error("Unexpected fd_close call"); },
+ fd_write : () => { throw new Error("Unexpected fd_write call"); },
+ fd_seek : () => { throw new Error("Unexpected fd_seek call"); },
+ },
+ env : {
+ p_clipboard_write : (size, text) => {
+ this.port.postMessage({
+ id : "clipboard",
+ text : new TextDecoder("utf8").decode(new Uint8Array(this.memory_buffer, text, size)), // FIXME
+ });
+ },
+ p_time_impl : Date.now,
+
+ floor : Math.floor,
+ ceil : Math.ceil,
+ sqrt : Math.sqrt,
+ pow : Math.pow,
+ log : Math.log,
+ log2 : Math.log2,
+ log10 : Math.log10,
+ exp : Math.exp,
+ sin : Math.sin,
+ cos : Math.cos,
+ tan : Math.tan,
+ asin : Math.asin,
+ acos : Math.acos,
+ atan : Math.atan,
+ atan2 : Math.atan2,
+ },
+ }
+ ).then((program) => {
+ program.exports.js_main(options.processorOptions.href);
+
+ this.memory_buffer = program.exports.memory.buffer;
+ this.sound_buffer_address = program.exports.js_sound_buffer();
+ this.pixels_address = program.exports.js_pixels();
+ this.frames = new Float32Array(this.memory_buffer, this.sound_buffer_address, program.exports.js_max_num_audio_frames());
+
+ let bytes = new Uint8Array(this.memory_buffer);
+ let title_address = program.exports.js_title();
+ let len = 0;
+ while (bytes[title_address + len] != 0)
+ ++len;
+ let title_buffer = new ArrayBuffer(len);
+ new Uint8Array(title_buffer).set(bytes.subarray(title_address, title_address + len));
+
+ this.port.postMessage({
+ id : "init",
+ title_buffer : title_buffer,
+ });
+
+ this.program = program;
+ });
+ },
+
+ resize : (ev) => {
+ this.frame_width = ev.width;
+ this.frame_height = ev.height;
+ },
+
+ mousedown : (ev) => {
+ if (this.program === undefined)
+ return;
+
+ this.program.exports.js_mousedown(ev.buttons);
+ },
- this.position = 0;
- this.max_num_frames = this.ring.byteLength / 4;
+ mouseup : (ev) => {
+ if (this.program === undefined)
+ return;
+
+ this.program.exports.js_mouseup(ev.buttons);
+ },
+
+ mousemove : (ev) => {
+ if (this.program === undefined)
+ return;
+
+ this.program.exports.js_mousemove(ev.x, ev.y);
+ },
+
+ keydown : (ev) => {
+ if (this.program === undefined)
+ return;
+
+ this.program.exports.js_keydown(ev.key, ev.mod);
+ },
+
+ keyup : (ev) => {
+ if (this.program === undefined)
+ return;
+
+ this.program.exports.js_keyup(ev.key, ev.mod);
+ },
+ };
+
+ this.port.onmessage = (ev) => {
+ if (ev.data.id in event_handlers)
+ event_handlers[ev.data.id](ev.data);
+ };
}
process(input, output, parameters) {
+ if (this.program === undefined)
+ return true;
+
let num_channels = output[0].length;
let num_samples = output[0][0].length;
let num_frames = num_samples * num_channels;
- if (num_frames > this.max_num_frames) {
- console.error("Sound buffer overflow");
- return true;
- }
+ this.program.exports.js_frame(this.frame_width, this.frame_height, num_samples);
- let frames = new Float32Array(this.ring);
-
- if (num_frames <= this.max_num_frames - this.position)
- for (let j = 0; j < num_channels; ++j)
- for (let i = 0; i < num_samples; ++i)
- output[0][j][i] = frames[this.position + i * num_channels + j];
- else {
- let part_one = this.max_num_frames - this.position;
- let part_two = num_frames - part_one;
-
- for (let j = 0; j < num_channels; ++j) {
- for (let i = 0; i < part_one; ++i)
- output[0][j][i] = frames[this.position + i * num_channels + j];
- for (let i = 0; i < part_two; ++i)
- output[0][j][part_one + i] = frames[i * num_channels + j];
- }
- }
+ let pixels_size = this.frame_width * this.frame_height * 4;
+ let pixels_buffer = new ArrayBuffer(pixels_size);
+ new Uint8Array(pixels_buffer).set(new Uint8Array(this.memory_buffer, this.pixels_address, pixels_size));
+
+ this.port.postMessage({
+ id : "frame",
+ pixels_buffer : pixels_buffer,
+ });
+
+ for (let j = 0; j < num_channels; ++j)
+ for (let i = 0; i < num_samples; ++i)
+ output[0][j][i] = this.frames[j * num_samples + i];
- this.position = (this.position + num_frames) % this.max_num_frames;
return true;
}
}
@@ -170,44 +273,75 @@
return mod;
};
- function string_from_memory(bytes, offset) {
- let len = 0;
- while (bytes[offset + len] != 0)
- ++len;
- return new TextDecoder("utf8").decode(bytes.subarray(offset, offset + len));
- }
-
async function run(attrs) {
- let wait_for_events = false;
- let sleep_duration = 0;
+ let sound_sample_rate = 44100; // TODO
+ let sound_num_channels = 2; // TODO
+
+ let frame_width = 0;
+ let frame_height = 0;
let canvas;
- let program;
- let memory;
- let pixels_address;
let context;
+ let pixels_buffer;
let animation_frame;
let sound_ready = false;
- let sound_context;
let sound_init;
+ let sound_context;
let sound_node;
- let sound_num_channels;
- let sound_buffer_address;
- let sound_max_num_frames;
-
- let sound_shared_ring;
- let sound_position;
canvas = attrs.canvas;
context = canvas.getContext("2d");
- sound_init = async () => {
- if (sound_ready || sound_context !== undefined)
- return;
+ context.fillStyle = "#000";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ context.fillStyle = "#fff";
+ context.font = "1.7em serif";
+ context.textAlign = "center";
+ context.textBaseline = "middle";
+ context.fillText("Click to run", canvas.width / 2.0, canvas.height / 2.0);
+
+ animation_frame = () => {
+ if (attrs.fit_window) {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ }
+
+ if (frame_width != canvas.width || frame_height != canvas.height) {
+ frame_width = canvas.width;
+ frame_height = canvas.height;
+
+ sound_node.port.postMessage({
+ id : "resize",
+ width : frame_width,
+ height : frame_height,
+ });
+ }
+
+ let num_bytes = 4 * canvas.width * canvas.height;
+
+ if (pixels_buffer !== undefined && pixels_buffer.byteLength >= num_bytes) {
+ let pixels = new Uint8ClampedArray(pixels_buffer, 0, num_bytes);
+
+ let data = new ImageData(
+ pixels,
+ canvas.width,
+ canvas.height
+ );
+
+ context.putImageData(data, 0, 0);
+ } else {
+ context.fillStyle = "#000";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ }
+
+ window.requestAnimationFrame(animation_frame);
+ };
+
+ sound_init = async () => {
sound_context = new AudioContext({
- sampleRate : program.instance.exports.js_sample_rate(),
+ sampleRate : sound_sample_rate,
});
let blob = new Blob(
@@ -217,192 +351,114 @@
await sound_context.audioWorklet.addModule(URL.createObjectURL(blob));
- sound_num_channels = program.instance.exports.js_num_channels();
- sound_max_num_frames = program.instance.exports.js_max_num_audio_frames();
- sound_buffer_address = program.instance.exports.js_sound_buffer();
-
- sound_shared_ring = new SharedArrayBuffer(sound_max_num_frames * 4);
- sound_position = 0;
-
sound_node = new AudioWorkletNode(
sound_context,
- "Sound_Ring",
- { numberOfInputs : 0,
- outputChannelCount : [ sound_num_channels ],
- processorOptions : { ring : sound_shared_ring, }, }
+ "Sound_Node",
+ { numberOfInputs : 0,
+ outputChannelCount : [ sound_num_channels ],
+ processorOptions : { href : document.location.href }, }
);
sound_node.connect(sound_context.destination);
- program.instance.exports.js_sound_sync();
-
- sound_ready = true;
- };
+ let message_handlers = {
+ init : (ev) => {
+ document.title = new TextDecoder("utf8").decode(new Uint8Array(ev.title_buffer));
+ sound_ready = true;
- program = await WebAssembly.instantiateStreaming(
- attrs.wasm,
- {
- "wasi_snapshot_preview1" : {
- clock_time_get : () => { return Date.now(); },
- args_sizes_get : () => { throw new Error("Unexpected args_sizes_get call"); },
- args_get : () => { throw new Error("Unexpected args_get call"); },
- proc_exit : () => { throw new Error("Unexpected proc_exit call"); },
- fd_close : () => { throw new Error("Unexpected fd_close call"); },
- fd_write : () => { throw new Error("Unexpected fd_write call"); },
- fd_seek : () => { throw new Error("Unexpected fd_seek call"); },
+ window.requestAnimationFrame(animation_frame);
},
- "env" : {
- p_init : () => {
- program.instance.exports.js_init();
- memory = new Uint8Array(program.instance.exports.memory.buffer);
- pixels_address = program.instance.exports.js_pixels();
- document.title = string_from_memory(memory, program.instance.exports.js_title());
- },
- p_render_frame_impl : () => {
- let data = new ImageData(
- new Uint8ClampedArray(
- memory.subarray(
- pixels_address,
- pixels_address + 4 * canvas.width * canvas.height
- )
- ),
- canvas.width,
- canvas.height
- );
-
- context.putImageData(data, 0, 0);
- },
- p_clipboard_write : (size, text) => {
- navigator.clipboard.writeText(
- new TextDecoder().decode(memory.subarray(text, text + size))
- ).catch((e) => { console.error(e); });
- },
- p_cleanup : () => {},
- p_wait_events_impl : () => { wait_for_events = true; },
- p_sleep_for_impl : (time) => { sleep_duration += time; },
- p_time_impl : Date.now,
-
- p_handle_audio_impl : (samples_elapsed) => {
- if (!sound_ready)
- return;
-
- let num_frames = samples_elapsed * sound_num_channels;
-
- if (num_frames > sound_max_num_frames) {
- console.error("Sound buffer overflow");
- return;
- }
-
- let dst = new Float32Array(sound_shared_ring);
- let src = new Float32Array(memory.buffer, sound_buffer_address, sound_max_num_frames);
-
- if (num_frames <= sound_max_num_frames - sound_position)
- for (let i = 0; i < num_frames; ++i) {
- dst[sound_position + i] = src[sound_position + i];
- src[sound_position + i] = 0.0;
- }
- else {
- let part_one = sound_max_num_frames - sound_position;
- let part_two = num_frames - part_one;
- for (let i = 0; i < part_one; ++i) {
- dst[sound_position + i] = src[sound_position + i];
- src[sound_position + i] = 0.0;
- }
- for (let i = 0; i < part_two; ++i) {
- dst[i] = src[i];
- src[i] = 0.0;
- }
- }
-
- sound_position = (sound_position + samples_elapsed * sound_num_channels) % sound_max_num_frames;
- },
-
- floor : Math.floor,
- ceil : Math.ceil,
- sqrt : Math.sqrt,
- pow : Math.pow,
- log : Math.log,
- log2 : Math.log2,
- log10 : Math.log10,
- exp : Math.exp,
- sin : Math.sin,
- cos : Math.cos,
- tan : Math.tan,
- asin : Math.asin,
- acos : Math.acos,
- atan : Math.atan,
- atan2 : Math.atan2,
+ clipboard : (ev) => {
+ navigator.clipboard.writeText(ev.text);
},
- }
- );
- program.instance.exports.js_main(document.location.href);
+ frame : (ev) => {
+ pixels_buffer = ev.pixels_buffer;
+ let pixels = new Uint8Array(pixels_buffer);
+ }
+ };
- animation_frame = (time) => {
- if (attrs.fit_window) {
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
- }
+ sound_node.port.onmessage = (ev) => {
+ if (ev.data.id in message_handlers)
+ message_handlers[ev.data.id](ev.data);
+ };
- wait_for_events = false;
- program.instance.exports.js_frame(canvas.width, canvas.height);
-
- if (!wait_for_events) {
- if (sleep_duration > 0) {
- let timeout = sleep_duration;
- sleep_duration = 0;
- setTimeout(() => {
- window.requestAnimationFrame(animation_frame);
- }, timeout);
- } else
- window.requestAnimationFrame(animation_frame);
- }
+ sound_node.port.postMessage({
+ id : "wasm",
+ wasm : await WebAssembly.compileStreaming(attrs.wasm),
+ });
};
- window.addEventListener("resize", (ev) => {
- window.requestAnimationFrame(animation_frame);
- });
-
canvas.addEventListener("mousedown", (ev) => {
- sound_init();
- program.instance.exports.js_mousedown(ev.buttons);
- window.requestAnimationFrame(animation_frame);
+ if (!sound_ready) {
+ sound_init();
+ sound_init = () => {};
+ return;
+ }
+
+ sound_node.port.postMessage({
+ id : "mousedown",
+ buttons : ev.buttons,
+ });
});
canvas.addEventListener("mouseup", (ev) => {
- program.instance.exports.js_mouseup(ev.buttons);
- window.requestAnimationFrame(animation_frame);
+ if (!sound_ready)
+ return;
+
+ sound_node.port.postMessage({
+ id : "mouseup",
+ buttons : ev.buttons,
+ });
});
canvas.addEventListener("mousemove", (ev) => {
- program.instance.exports.js_mousemove(ev.clientX, ev.clientY);
- window.requestAnimationFrame(animation_frame);
+ if (!sound_ready)
+ return;
+
+ sound_node.port.postMessage({
+ id : "mousemove",
+ x : ev.clientX,
+ y : ev.clientY,
+ });
});
- window.addEventListener("keydown", (ev) => {
+ canvas.addEventListener("keydown", (ev) => {
ev.preventDefault();
- if (!ev.repeat) {
- let mod = mod_from_event(ev);
- let key = key_from_code(ev.code);
- if (key != 0)
- program.instance.exports.js_keydown(key, mod);
- window.requestAnimationFrame(animation_frame);
- }
+ if (!sound_ready)
+ return;
+ if (ev.repeat)
+ return;
+ let mod = mod_from_event(ev);
+ let key = key_from_code(ev.code);
+ if (key == 0)
+ return;
+
+ sound_node.port.postMessage({
+ id : "keydown",
+ mod : mod,
+ key : key,
+ });
});
- window.addEventListener("keyup", (ev) => {
+ canvas.addEventListener("keyup", (ev) => {
ev.preventDefault();
- if (!ev.repeat) {
- let mod = mod_from_event(ev);
- let key = key_from_code(ev.code);
- if (key != 0)
- program.instance.exports.js_keyup(key, mod);
- window.requestAnimationFrame(animation_frame);
- }
- });
+ if (!sound_ready)
+ return;
+ if (ev.repeat)
+ return;
+ let mod = mod_from_event(ev);
+ let key = key_from_code(ev.code);
+ if (key == 0)
+ return;
- window.requestAnimationFrame(animation_frame);
+ sound_node.port.postMessage({
+ id : "keyup",
+ mod : mod,
+ key : key,
+ });
+ });
}
run({
@@ -412,4 +468,4 @@
}).catch((e) => console.error(e));
</script>
</body>
-</html>
+</html>