From b4b9131f3976388b096343725fce1bdf35f4e3df Mon Sep 17 00:00:00 2001 From: Guy Perfect Date: Sat, 2 Nov 2024 11:14:24 -0500 Subject: [PATCH] Web: add panning, move mixing to wasm --- core/vsu.c | 2 +- web/Constants.js | 4 -- web/Core.js | 37 +++++----- web/VB.js | 34 ++++++++-- web/wasm.c | 171 +++++++++++++++++++++++++++++++++++------------ 5 files changed, 175 insertions(+), 73 deletions(-) diff --git a/core/vsu.c b/core/vsu.c index cf4c866..0621f5c 100644 --- a/core/vsu.c +++ b/core/vsu.c @@ -369,7 +369,7 @@ static void vsuEmulate(VB *sim, uint32_t clocks) { sim->vsu.out.offset >> 1 < sim->vsu.out.capacity ) { - /* Processing by data type*/ + /* Processing by data type */ switch (sim->vsu.out.type) { case VB_S16: ((int16_t *) sim->vsu.out.samples) diff --git a/web/Constants.js b/web/Constants.js index 09e57f2..59244cb 100644 --- a/web/Constants.js +++ b/web/Constants.js @@ -75,10 +75,6 @@ let Constants = { BREAK_FRAME: 1, BREAK_POINT: 2, - // Extra properties - EXT_PIXELS : 0, - EXT_SAMPLES: 1, - // Anaglyph colors STEREO_CYAN : 0x00C6F0, STEREO_GREEN : 0x00B400, diff --git a/web/Core.js b/web/Core.js index db28494..5829e54 100644 --- a/web/Core.js +++ b/web/Core.js @@ -82,23 +82,20 @@ new class Core { let sim = { canvas : null, keys : Constants.VB.SGN, - pointer : sims[x] = this.CreateSim(), - volume : 1 + pointer : sims[x] = this.CreateSim() }; this.sims.set(sim.pointer, sim); // Video - this.SetAnaglyph(sim.pointer, - Constants.web.STEREO_RED, Constants.web.STEREO_CYAN); sim.pixels = this.#noalloc( - this.GetExt(sim.pointer, Constants.web.EXT_PIXELS), + this.GetExtPixels(sim.pointer), 384*224*4, sim, "pixels", Uint8ClampedArray ); sim.image = new ImageData(sim.pixels, 384, 224); // Audio sim.samples = this.#noalloc( - this.GetExt(sim.pointer, Constants.web.EXT_SAMPLES), + this.GetExtSamples(sim.pointer), 41700 / 50 * 2, sim, "samples", Float32Array ); @@ -296,6 +293,12 @@ new class Core { this.dom.postMessage({ promised: true }); } + // Specify audio panning + setPanning(message) { + this.SetPanning(message.sim, message.panning); + this.dom.postMessage({ promised: true }); + } + // Specify a new communication peer setPeer(message) { let orphaned = []; @@ -318,7 +321,7 @@ new class Core { // Specify audio volume setVolume(message) { - this.sims.get(message.sim).volume = message.volume; + this.SetVolume(message.sim, message.volume); this.dom.postMessage({ promised: true }); } @@ -408,19 +411,13 @@ new class Core { // Mix and output audio samples let buffer = this.audio.buffers.shift(); - if (this.automatic.sims.length > 1) { - for (let x = 0; x < buffer.length; x++) - buffer[x] = 0; - for (let sim of this.automatic.sims) - for (let x = 0; x < buffer.length; x++) - buffer[x] += sim.samples[x] * sim.volume; - for (let x = 0; x < buffer.length; x++) - buffer[x] = Math.min(Math.max(-1, buffer[x]), +1); - } else { - let sim = this.automatic.sims[0]; - for (let x = 0; x < buffer.length; x++) - buffer[x] = sim.samples[x] * sim.volume; - } + this.Mix( + this.audio.samples.pointer, + this.automatic.pointers.pointer, + this.automatic.sims.length + ); + for (let x = 0; x < buffer.length; x++) + buffer[x] = this.audio.samples[x]; this.audio.postMessage(buffer.buffer, [ buffer.buffer ]); // Output staged images if there's one audio buffer to go diff --git a/web/VB.js b/web/VB.js index 2d6d3ae..7b4fb84 100644 --- a/web/VB.js +++ b/web/VB.js @@ -285,6 +285,7 @@ class Sim extends HTMLElement { #core; // Core proxy #emulating; // Current emulation status #keys; // Controller state + #panning; // Audio stereo balance #peer; // Communication peer #pointer; // Pointer in core memory #volume; // Audio output volume @@ -313,9 +314,10 @@ class Sim extends HTMLElement { this.#core = core; this.#emulating = false; this.#keys = Constants.VB.SGN; + this.#panning = 0.0; this.#peer = null; this.#pointer = pointer; - this.#volume = 1; + this.#volume = 1.0; delete this.proxy; // Create a for the video image @@ -358,10 +360,11 @@ class Sim extends HTMLElement { /////////////////////////// Property Accessors //////////////////////////// get anaglyph() { return this.#anaglyph.slice(); } - get core () { return this.#core.core; } - get keys () { return this.#keys; } - get peer () { return this.#peer; } - get volume () { return this.#volume; } + get core () { return this.#core.core ; } + get keys () { return this.#keys ; } + get panning () { return this.#panning ; } + get peer () { return this.#peer ; } + get volume () { return this.#volume ; } @@ -479,6 +482,27 @@ class Sim extends HTMLElement { }); } + // Specify audio panning + async setPanning(panning) { + + // Error checking + if (!Number.isFinite(panning) ||panning < -1 || panning > +1) { + throw new RangeError( + "Panning must be a number from -1 to +1."); + } + + // Configure instance fields + this.#panning = panning; + + // Send the panning to the core + await this.#core.toCore({ + command : "setPanning", + promised: true, + sim : this.#pointer, + panning : panning + }); + } + // Specify a new communication peer async setPeer(peer = null) { diff --git a/web/wasm.c b/web/wasm.c index 14a07ca..15cd95a 100644 --- a/web/wasm.c +++ b/web/wasm.c @@ -12,9 +12,14 @@ #define BREAK_FRAME 1 #define BREAK_POINT 2 -// Extra properties -#define EXT_PIXELS 0 -#define EXT_SAMPLES 1 +// Anaglyph colors +#define STEREO_CYAN 0x00C6F0 +#define STEREO_GREEN 0x00B400 +#define STEREO_MAGENTA 0xC800FF +#define STEREO_RED 0xFF0000 + +// Element counts +#define NUM_SAMPLES (41700 / 50 * 2) @@ -24,9 +29,11 @@ typedef struct { int32_t breaks; uint32_t left[256]; + float panning; uint8_t pixels[384 * 224 * 4]; uint32_t right[256]; - float samples[41700 * 2]; + float samples[NUM_SAMPLES]; + float volume; } Ext; @@ -43,6 +50,36 @@ int wasmOnFrame(VB *sim) { /////////////////////////////// Module Exports //////////////////////////////// +// Specify anaglyph colors +EMSCRIPTEN_KEEPALIVE void SetAnaglyph(VB *sim, uint32_t left, uint32_t right) { + Ext *ext = (Ext *) vbGetUserData(sim); + + // Erase all RGB values + for (int x = 0; x < 256; x++) + ext->left[x] = ext->right[x] = 0xFF000000; + + // Process all RGB channels + for (int c = 0, shift = 16; c < 3; c++, shift -= 8) { + double max; // Magnitude of channel value + uint32_t *dest; // Lookup data + + // Select the magnitude and lookup channel + dest = ext->left; + max = (left >> shift & 0xFF) / 255.0; + if (max == 0) { + dest = ext->right; + max = (right >> shift & 0xFF) / 255.0; + if (max == 0) + continue; + } + + // Compute the resulting RGB values + for (int x = 0; x < 256; x++) + *dest++ |= (uint32_t) (x * max + 0.5) << (16 - shift); + } + +} + // Instantiate a simulation EMSCRIPTEN_KEEPALIVE void* CreateSim() { size_t sizeOfSim = vbSizeOf(); @@ -55,8 +92,11 @@ EMSCRIPTEN_KEEPALIVE void* CreateSim() { // Configure extra Ext *ext = (Ext *) (pointer + sizeOfSim); - ext->breaks = 0; + ext->breaks = 0; + ext->panning = 0.0f; + ext->volume = 1.0f; vbSetUserData(sim, ext); + SetAnaglyph(sim, STEREO_RED, STEREO_CYAN); // Initialize pixels with opaque black for (unsigned x = 0; x < 384 * 224; x++) @@ -99,6 +139,16 @@ EMSCRIPTEN_KEEPALIVE int Emulate(VB **sims, unsigned count, uint32_t *clocks) { return vbEmulateEx(sims, count, clocks); } +// Retrieve a sim's pixel pointer +EMSCRIPTEN_KEEPALIVE void* GetExtPixels(VB *sim) { + return ((Ext *) vbGetUserData(sim))->pixels; +} + +// Retrieve a sim's sample pointer +EMSCRIPTEN_KEEPALIVE void* GetExtSamples(VB *sim) { + return ((Ext *) vbGetUserData(sim))->samples; +} + // Retrieve the break condition flags for a sim EMSCRIPTEN_KEEPALIVE int32_t GetBreaks(VB *sim) { return ((Ext *) vbGetUserData(sim))->breaks; @@ -127,16 +177,6 @@ EMSCRIPTEN_KEEPALIVE void GetDasm(uint32_t *buffer, void *dasm, int count) { } } -// Retrieve an extra property -EMSCRIPTEN_KEEPALIVE void* GetExt(VB *sim, int id) { - Ext *ext = (Ext *) vbGetUserData(sim); - switch (id) { - case EXT_PIXELS : return ext->pixels; - case EXT_SAMPLES: return ext->samples; - } - return NULL; -} - // Retrieve anaglyph-tinted pixels from a sim EMSCRIPTEN_KEEPALIVE void GetPixels(VB *sim) { Ext *ext = (Ext *) vbGetUserData(sim); @@ -146,6 +186,71 @@ EMSCRIPTEN_KEEPALIVE void GetPixels(VB *sim) { *(uint32_t *) pixels = ext->left[pixels[0]] | ext->right[pixels[1]]; } +// Mix audio samples for output +EMSCRIPTEN_KEEPALIVE void Mix(float *buffer, VB **sims, int count) { + + // Process all sims + for (int x = 0; x < count; x++) { + Ext *ext = (Ext *) vbGetUserData(sims[x]); + float *samples = ext->samples; + float v = ext->volume; + + // First sim initializes buffer + if (x == 0) { + if (ext->panning < 0) { + float l = -ext->panning * v; + float r = (1.0f + ext->panning) * v; + for (unsigned y = 0; y < NUM_SAMPLES; y += 2) { + buffer[y ] = samples[y] * v + samples[y + 1] * l; + buffer[y + 1] = samples[y + 1] * r; + } + } else if (ext->panning > 0) { + float r = ext->panning * v; + float l = (1.0f - ext->panning) * v; + for (unsigned y = 0; y < NUM_SAMPLES; y += 2) { + buffer[y ] = samples[y] * l; + buffer[y + 1] = samples[y + 1] * v + samples[y] * r; + } + } else { + for (unsigned y = 0; y < NUM_SAMPLES; y++) + buffer[y] = samples[y] * v; + } + } + + // Subsequent sims add to buffer + else { + if (ext->panning < 0) { + float l = -ext->panning * v; + float r = (1.0f + ext->panning) * v; + for (unsigned y = 0; y < NUM_SAMPLES; y += 2) { + buffer[y ] += samples[y] * v + samples[y + 1] * l; + buffer[y + 1] += samples[y + 1] * r; + } + } else if (ext->panning > 0) { + float r = ext->panning * v; + float l = (1.0f - ext->panning) * v; + for (unsigned y = 0; y < NUM_SAMPLES; y += 2) { + buffer[y ] += samples[y] * l; + buffer[y + 1] += samples[y + 1] * v + samples[y] * r; + } + } else { + for (unsigned y = 0; y < NUM_SAMPLES; y++) + buffer[y] += samples[y] * v; + } + } + + } + + // Clipping + for (unsigned y = 0; y < NUM_SAMPLES; y++) { + if (buffer[y] < -1.0f) + buffer[y] = -1.0f; + else if (buffer[y] > +1.0f) + buffer[y] = +1.0f; + } + +} + // Determine the size in bytes of a pointer EMSCRIPTEN_KEEPALIVE int PointerSize() { return sizeof (void *); @@ -156,32 +261,12 @@ EMSCRIPTEN_KEEPALIVE void* Realloc(void *data, size_t size) { return realloc(data, size); } -// Specify anaglyph colors -EMSCRIPTEN_KEEPALIVE void SetAnaglyph(VB *sim, uint32_t left, uint32_t right) { - Ext *ext = (Ext *) vbGetUserData(sim); - - // Erase all RGB values - for (int x = 0; x < 256; x++) - ext->left[x] = ext->right[x] = 0xFF000000; - - // Process all RGB channels - for (int c = 0, shift = 16; c < 3; c++, shift -= 8) { - double max; // Magnitude of channel value - uint32_t *dest; // Lookup data - - // Select the magnitude and lookup channel - dest = ext->left; - max = (left >> shift & 0xFF) / 255.0; - if (max == 0) { - dest = ext->right; - max = (right >> shift & 0xFF) / 255.0; - if (max == 0) - continue; - } - - // Compute the resulting RGB values - for (int x = 0; x < 256; x++) - *dest++ |= (uint32_t) (x * max + 0.5) << (16 - shift); - } - +// Specify audio panning +EMSCRIPTEN_KEEPALIVE void SetPanning(VB *sim, float panning) { + ((Ext *) vbGetUserData(sim))->panning = panning; +} + +// Specify audio volume +EMSCRIPTEN_KEEPALIVE void SetVolume(VB *sim, float volume) { + ((Ext *) vbGetUserData(sim))->volume = volume; }