Skip to content

Commit d8f6a99

Browse files
trummerschlunkfalkTX
authored andcommitted
bbba -> la suite meet
1 parent 69c6e58 commit d8f6a99

8 files changed

Lines changed: 484 additions & 13 deletions

File tree

src/frontend/public/wasm/BBBA-mapi.js

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
3.51 MB
Binary file not shown.

src/frontend/public/wasm/BBBA-nosimd-mapi.js

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
3.48 MB
Binary file not shown.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
// Copyright 2025-2026 Filipe Coelho <falktx@falktx.com>
2+
// SPDX-License-Identifier: ISC
3+
4+
// known constants
5+
const maxSymbolLength = 255;
6+
const nominalBufferSize = 128;
7+
8+
// function to setup wasm + emscripten module options for offline fetch
9+
const createWasmOpts = (wasmBlob, postRunCallback, errorCallback) => {
10+
return {
11+
// override to use previously retrieved blob data, as `fetch` is not allowed in worklets
12+
instantiateWasm: (imports, successCallback) => {
13+
WebAssembly.instantiate(wasmBlob, imports).then(output => {
14+
successCallback(output.instance, output.module);
15+
}).catch(error => {
16+
errorCallback(error);
17+
});
18+
19+
return {};
20+
},
21+
postRun: postRunCallback,
22+
};
23+
};
24+
25+
// class that holds a mono audio plugin instance
26+
// see https://github.com/DISTRHO/MAPI for the API used here
27+
class MapiProcessorInstance {
28+
constructor(port, module) {
29+
this.port = port;
30+
this.module = module;
31+
this.handle = module._mapi_create(sampleRate, nominalBufferSize);
32+
this.enabled = true;
33+
this.monitor_param = null;
34+
this.monitor_value = null;
35+
36+
this.audioData = module._malloc(module.HEAPF32.BYTES_PER_ELEMENT * nominalBufferSize);
37+
this.audioPtrs = module._malloc(module.HEAPU32.BYTES_PER_ELEMENT);
38+
module.HEAPU32[this.audioPtrs + (0 << 2) >> 2] = this.audioData;
39+
40+
this.csymbolData = module._malloc(maxSymbolLength);
41+
}
42+
43+
csymbol(symbol) {
44+
const len = Math.min(maxSymbolLength, this.module.lengthBytesUTF8(symbol) + 1);
45+
this.module.stringToUTF8(symbol, this.csymbolData, len);
46+
return this.csymbolData;
47+
}
48+
49+
param(symbol, value) {
50+
this.module._mapi_set_parameter(this.handle, this.csymbol(symbol), value);
51+
}
52+
53+
monitor(symbol) {
54+
this.monitor_param = symbol;
55+
this.monitor_value = this.module._mapi_get_parameter(this.handle, this.csymbol(symbol));
56+
}
57+
58+
process(buffer, bufferSize, bufferOffset) {
59+
if (! this.enabled)
60+
return;
61+
62+
for (let i = 0; i < bufferSize; ++i)
63+
this.module.HEAPF32[this.audioData + (i << 2) >> 2] = buffer[bufferOffset + i];
64+
65+
this.module._mapi_process(this.handle, this.audioPtrs, this.audioPtrs, bufferSize);
66+
67+
for (let i = 0; i < bufferSize; ++i)
68+
buffer[bufferOffset + i] = this.module.HEAPF32[this.audioData + (i << 2) >> 2];
69+
70+
if (this.monitor_param != null) {
71+
const value = this.module._mapi_get_parameter(this.handle, this.csymbol(this.monitor_param));
72+
if (this.monitor_value != value) {
73+
this.monitor_value = value;
74+
this.port.postMessage({ type: 'monitor', value: value });
75+
}
76+
}
77+
}
78+
};
79+
80+
// worklet processor implementation
81+
class MapiWorkletProcessor extends AudioWorkletProcessor {
82+
constructor(options) {
83+
super(options);
84+
85+
// validity checks
86+
if (options.numberOfInputs != options.numberOfOutputs)
87+
throw Error('Mis-matching IO, number of inputs must match outputs');
88+
if (options.numberOfInputs != 1)
89+
throw Error('Invalid IO, must be mono');
90+
91+
// workaround for Chromium-based browsers, return true in `process` until disconnected
92+
this.disconnected = false;
93+
94+
// MAPI processor instance
95+
this.bbba = null;
96+
97+
// bi-directional port communication
98+
this.port.onmessage = event => {
99+
switch (event.data.type)
100+
{
101+
case 'init':
102+
this.init(event.data);
103+
break;
104+
case 'enable':
105+
this.enable(event.data);
106+
break;
107+
case 'monitor':
108+
this.monitor(event.data);
109+
break;
110+
case 'param':
111+
this.param(event.data);
112+
break;
113+
case 'destroy':
114+
this.destroy();
115+
break;
116+
}
117+
};
118+
}
119+
120+
init(data) {
121+
// execute JS to expose the emscripten load module function
122+
const jsfn_bbba = new Function(data.js + 'return mapi_bbba;');
123+
const create_module_bbba = jsfn_bbba.call();
124+
125+
// create wasm opts for offline loading
126+
const opts = createWasmOpts(data.wasm,
127+
(module) => {
128+
this.bbba = new MapiProcessorInstance(this.port, module);
129+
this.port.postMessage({ type: 'loaded' });
130+
},
131+
(error) => {
132+
this.port.postMessage({ type: 'error', error: error });
133+
},
134+
);
135+
136+
// create the wasm module and instance
137+
create_module_bbba(opts);
138+
}
139+
140+
enable(data) {
141+
if (!this.bbba) {
142+
console.error('BBBA wasm is not loaded yet!');
143+
return;
144+
}
145+
146+
this.bbba.enabled = !!data.enable;
147+
}
148+
149+
monitor(data) {
150+
if (!this.bbba) {
151+
console.error('BBBA wasm is not loaded yet!');
152+
return;
153+
}
154+
155+
this.bbba.monitor(data.symbol);
156+
}
157+
158+
param(data) {
159+
if (!this.bbba) {
160+
console.error('BBBA wasm is not loaded yet!');
161+
return;
162+
}
163+
164+
this.bbba.param(data.symbol, data.value);
165+
}
166+
167+
destroy() {
168+
this.disconnected = true;
169+
this.bbba = null;
170+
}
171+
172+
process(inputs, outputs, parameters) {
173+
if (this.disconnected)
174+
return false;
175+
if (!this.bbba)
176+
return true;
177+
178+
const input = inputs[0];
179+
const output = outputs[0];
180+
181+
// IO check, can be zero if stream is not connected yet
182+
if (input.length == 0 || output.length == 0)
183+
return true;
184+
185+
// use in-place processing
186+
const buffer = output[0];
187+
buffer.set(input[0]);
188+
189+
for (let offset = 0; offset < buffer.length; offset += nominalBufferSize)
190+
this.bbba.process(buffer, Math.min(nominalBufferSize, buffer.length - offset), offset);
191+
192+
// reuse mono buffer if stereo
193+
if (output.length == 2)
194+
output[1].set(buffer);
195+
196+
return true;
197+
}
198+
};
199+
200+
registerProcessor("mapi-proc", MapiWorkletProcessor);

src/frontend/src/features/rooms/components/Join.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,16 @@ export const Join = ({
217217
try {
218218
const track = await createLocalAudioTrack({
219219
deviceId: { exact: audioDeviceId },
220+
noiseSuppression: false,
221+
echoCancellation: true,
222+
autoGainControl: true,
223+
voiceIsolation: false,
224+
225+
226+
// Audio quality optimized for voice
227+
sampleRate: 48000, // High quality sample rate
228+
channelCount: 1, // Mono for voice calls (saves bandwidth)
229+
sampleSize: 16 // 16-bit audio
220230
})
221231
setDynamicAudioTrack(track)
222232
} catch (error) {

0 commit comments

Comments
 (0)