From 46c6e38b6c197f56436885892f1db687053f4fe6 Mon Sep 17 00:00:00 2001 From: Andreas 'phix' Jonsson Date: Wed, 31 Jan 2024 16:56:28 +0100 Subject: [PATCH] Improved audio in web frontend. --- front/web/main.c | 18 ++++++++-- front/web/sampler.c | 82 +++++++++++++++++++++++++++++++++++++++++++++ front/web/script.js | 42 +++++++++-------------- 3 files changed, 113 insertions(+), 29 deletions(-) create mode 100644 front/web/sampler.c diff --git a/front/web/main.c b/front/web/main.c index e0e9f43d..e048b4bc 100644 --- a/front/web/main.c +++ b/front/web/main.c @@ -50,12 +50,16 @@ int cga_width = -1; int cga_height = -1; vxt_dword cga_border = 0; +float *sampler_get_buffer(struct vxt_pirepheral *p); +struct vxt_pirepheral *sampler_create(vxt_allocator *alloc, int frequency, int num_samples, float (*generate)(int freq)); + struct frontend_video_adapter video_adapter = {0}; struct frontend_mouse_adapter mouse_adapter = {0}; vxt_system *sys = NULL; struct vxt_pirepheral *disk = NULL; struct vxt_pirepheral *ppi = NULL; +struct vxt_pirepheral *sampler = NULL; static int log_wrapper(const char *fmt, ...) { va_list args; @@ -168,6 +172,10 @@ static int render_callback(int width, int height, const vxt_byte *rgba, void *us return 0; } +static float generate_sample(int freq) { + return (float)vxtu_ppi_generate_sample(ppi, freq) / 32767.0f; +} + int wasm_video_width(void) { return cga_width; } @@ -215,11 +223,13 @@ int wasm_step_emulation(int cycles) { return s.cycles; } -double wasm_generate_sample(int freq) { - return (double)vxtu_ppi_generate_sample(ppi, freq) / 32767.0; +void *wasm_audio_sampler_memory_pointer(void) { + if (!sampler) + return NULL; + return (void*)sampler_get_buffer(sampler); } -void wasm_initialize_emulator(int v20, int freq) { +void wasm_initialize_emulator(int v20, int freq, int afreq, int bsize) { vxt_set_logger(&log_wrapper); struct vxtu_disk_interface intrf = { @@ -229,6 +239,7 @@ void wasm_initialize_emulator(int v20, int freq) { vxtu_disk_set_activity_callback(disk, &js_disk_activity, NULL); ppi = vxtu_ppi_create(&ALLOCATOR); + sampler = sampler_create(ALLOCATOR, afreq, bsize, &generate_sample); APPEND_DEVICE(vxtu_memory_create(&ALLOCATOR, 0x0, 0x100000, false)); APPEND_DEVICE(load_bios(glabios_bin, (int)glabios_bin_len, 0xFE000)); @@ -238,6 +249,7 @@ void wasm_initialize_emulator(int v20, int freq) { APPEND_DEVICE(vxtu_dma_create(&ALLOCATOR)); APPEND_DEVICE(vxtu_pit_create(&ALLOCATOR)); APPEND_DEVICE(ppi); + APPEND_DEVICE(sampler); APPEND_DEVICE(disk); #ifndef VXTU_STATIC_MODULES diff --git a/front/web/sampler.c b/front/web/sampler.c new file mode 100644 index 00000000..fb80c822 --- /dev/null +++ b/front/web/sampler.c @@ -0,0 +1,82 @@ +// Copyright (c) 2019-2024 Andreas T Jonsson +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in +// a product, an acknowledgment (see the following) in the product +// documentation is required. +// +// This product make use of the VirtualXT software emulator. +// Visit https://virtualxt.org for more information. +// +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source distribution. + +#include + +struct sampler { + int frequency; + float (*generate)(int freq); + + int buffer_size; + int buffer_len; + float *buffer; +}; + +static vxt_error timer(struct sampler *a, vxt_timer_id id, int cycles) { + (void)id; (void)cycles; + + float sample = a->generate(a->frequency); + if (a->buffer_len >= a->buffer_size) + return VXT_NO_ERROR; + + a->buffer[a->buffer_len++] = sample; + return VXT_NO_ERROR; +} + +static vxt_error install(struct sampler *a, vxt_system *s) { + vxt_system_install_timer(s, VXT_GET_PIREPHERAL(a), 1000000 / a->frequency); + return VXT_NO_ERROR; +} + +static vxt_error destroy(struct sampler *a) { + vxt_allocator *alloc = vxt_system_allocator(VXT_GET_SYSTEM(a)); + alloc(a->buffer, 0); + alloc(VXT_GET_PIREPHERAL(a), 0); + return VXT_NO_ERROR; +} + +static const char *name(struct sampler *a) { + (void)a; return "Audio Sampler"; +} + +float *sampler_get_buffer(struct vxt_pirepheral *p) { + struct sampler *a = VXT_GET_DEVICE(sampler, p); + while (a->buffer_len < a->buffer_size) + a->buffer[a->buffer_len++] = a->generate(a->frequency); + a->buffer_len = 0; + return a->buffer; +} + +struct vxt_pirepheral *sampler_create(vxt_allocator *alloc, int frequency, int num_samples, float (*generate)(int freq)) VXT_PIREPHERAL_CREATE(alloc, sampler, { + DEVICE->frequency = frequency; + DEVICE->generate = generate; + + DEVICE->buffer_len = 0; + DEVICE->buffer_size = num_samples; + DEVICE->buffer = (float*)alloc(NULL, num_samples * sizeof(float)); + + PIREPHERAL->install = &install; + PIREPHERAL->destroy = &destroy; + PIREPHERAL->name = &name; + PIREPHERAL->timer = &timer; +}) diff --git a/front/web/script.js b/front/web/script.js index 86139344..63c409ae 100644 --- a/front/web/script.js +++ b/front/web/script.js @@ -451,25 +451,14 @@ function getCanvas() { return document.getElementById(urlParams.get("canvas") || defaultCanvas); } -function genAudioSamples(samples, delta) { - const num = delta * samples.freq; - const max = Math.min(samples.len + num, samples.data.length); - for (var i = samples.len; i < max; i++) - samples.data[samples.len++] = samples.func(samples.freq); -} - -function nextAudioBuffer(ctx, buffer, samples) { - for (var i = samples.len; i < samples.data.length; i++) - samples.data[i] = samples.func(samples.freq); - - buffer.copyToChannel(samples.data, 0); - samples.len = 0; +function nextAudioBuffer(ctx, buffer, func) { + buffer.copyToChannel(func(), 0); const source = ctx.createBufferSource(); source.buffer = buffer; source.connect(ctx.destination); source.onended = () => { - nextAudioBuffer(ctx, buffer, samples); + nextAudioBuffer(ctx, buffer, func); } source.start(); } @@ -533,7 +522,10 @@ function startEmulator(binary) { }; const initialize = () => { - C.wasm_initialize_emulator((urlParams.get("v20") == 1) ? 1 : 0, targetFreq * 1000000); + const audioCtx = new (window.AudioContext || window.webkitAudioContext)({latencyHint: "interactive"}); + const audioBufferSize = audioCtx.sampleRate * defaultAudioLatency; + + C.wasm_initialize_emulator((urlParams.get("v20") == 1) ? 1 : 0, targetFreq * 1000000, audioCtx.sampleRate, audioBufferSize); if (urlParams.has("storage") && (urlParams.get("storage") != "0")) { initLocalStorage(); @@ -584,20 +576,20 @@ function startEmulator(binary) { window.requestAnimationFrame(renderFrame); - const audioCtx = new (window.AudioContext || window.webkitAudioContext)({latencyHint: "interactive"}); - const audioSamples = { - data: new Float32Array(audioCtx.sampleRate * defaultAudioLatency), - len: 0, - freq: audioCtx.sampleRate, - func: C.wasm_generate_sample - }; const audioBuffer = audioCtx.createBuffer( 1, - audioSamples.data.length, + audioBufferSize, audioCtx.sampleRate ); - nextAudioBuffer(audioCtx, audioBuffer, audioSamples); + nextAudioBuffer(audioCtx, audioBuffer, () => { + const bufferOffset = C.wasm_audio_sampler_memory_pointer(); + const sampleDataArray = wasmMemoryArray.slice( + bufferOffset, + bufferOffset + audioBufferSize * 4 + ); + return new Float32Array(sampleDataArray.buffer); + }); const cycleCap = targetFreq * 15000; var stepper = { t: performance.now(), c: 0 }; @@ -611,8 +603,6 @@ function startEmulator(binary) { cycles = cycleCap; } - genAudioSamples(audioSamples, delta / 1000); - stepper.c += C.wasm_step_emulation(cycles); stepper.t = t; }, 1);