I have been struggling with trying to get two different audio worklets to be able to process samples. I’ve tried several different approaches and all resulted in a bunch of various errors, but this was what I came up with that does not throw errors, but the result is crackling / distorted audio for ONE of my sources. If I comment out one of the emscripten_start_wasm_audio_worklet_thread_async
calls in the constructor function (only create a single worklet node using either contextA OR contextB), then the audio no longer has distortion and sounds fine…
Is there something I am missing that I need to be doing in order to have multiple worklets generating samples?
I know I could structure this differently and just use one context, one worklet, and manually sum the samples from each of my two processors, and put that into the buffer and divide by two… But I am trying to understand WHY this is not working, and I cannot seem to find any good information about mixing multiple audio sources together.
One other note is, on the client-side, I setup visual analyzers of the two audio nodes / sources / worklets, and it seems that when I am hearing the distortion, even though I am only playing ONE of the sources, I see the visualizer for the OTHER source has very low activity… So it’s almost like the distortion I am hearing is somehow signal bleeding from one source to the other…..
#include "web_audio_worklet.h"
#include "sample_streamable.h"
#include "speech_synthesizer.h"
#include "nsf_player.h"
#include <stdint.h>
uint8_t audioThreadStack[4096];
struct AudioPackage {
SampleStreamable *sampleStreamable;
int audioContextSyncCallback;
bool isSpeech;
EMSCRIPTEN_WEBAUDIO_T audioContext;
};
EM_BOOL AudioCallback(int numInputs, const AudioSampleFrame *inputs,
int numOutputs, AudioSampleFrame *outputs,
int numParams, const AudioParamFrame *params,
void *userData) {
AudioPackage *audioPackage = static_cast<AudioPackage *>(userData);
int frames = 128;
for (int i = 0; i < numOutputs; i++) {
audioPackage->sampleStreamable->process(outputs[i].data, frames*outputs[i].numberOfChannels);
}
return EM_TRUE;
}
EM_BOOL OnClick(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) {
AudioPackage *audioPackage = static_cast<AudioPackage *>(userData);
EMSCRIPTEN_WEBAUDIO_T audioContext = audioPackage->audioContext;
if (emscripten_audio_context_state(audioContext) != AUDIO_CONTEXT_STATE_RUNNING) {
emscripten_resume_audio_context_sync(audioContext);
void (*callback)() = reinterpret_cast<void (*)()>(audioPackage->audioContextSyncCallback);
callback();
}
return EM_FALSE;
}
void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) {
if (!success) return;
int outputChannelCounts[1] = { 1 };
EmscriptenAudioWorkletNodeCreateOptions options = {
.numberOfInputs = 0,
.numberOfOutputs = 1,
.outputChannelCounts = outputChannelCounts
};
AudioPackage *audioPackage = static_cast<AudioPackage *>(userData);
EMSCRIPTEN_AUDIO_WORKLET_NODE_T wasmAudioWorklet = emscripten_create_wasm_audio_worklet_node(audioContext, audioPackage->sampleStreamable->processorName(), &options, &AudioCallback, userData);
// Connect it to audio context destination
EM_ASM({
const worklet = emscriptenGetAudioObject($0);
const context = emscriptenGetAudioObject($1);
worklet.connect(context.destination);
if (Module.onAudioWorkletNodeCreated) {
Module.onAudioWorkletNodeCreated(worklet, $2);
}
}, wasmAudioWorklet, audioContext, audioPackage->isSpeech);
emscripten_set_click_callback("body", (void *)userData, 0, OnClick);
}
void AudioThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext, EM_BOOL success, void *userData) {
if (!success) return;
AudioPackage *audioPackage = static_cast<AudioPackage *>(userData);
audioPackage->audioContext = audioContext;
WebAudioWorkletProcessorCreateOptions opts = {
.name = audioPackage->sampleStreamable->processorName(),
};
emscripten_create_wasm_audio_worklet_processor_async(audioContext, &opts, &AudioWorkletProcessorCreated, userData);
}
WebAudioWorklet::WebAudioWorklet(SpeechSynthesizer *speechSynthesizer, NSFPlayer *nsfPlayer, int speechInitCallback, int nsfInitCallback) {
EmscriptenWebAudioCreateAttributes attrs = {};
attrs.sampleRate = 48000;
attrs.latencyHint = "playback";
EMSCRIPTEN_WEBAUDIO_T contextA = emscripten_create_audio_context(&attrs);
AudioPackage *speechPackage = new AudioPackage();
speechPackage->sampleStreamable = static_cast<SampleStreamable *>(speechSynthesizer);
speechPackage->isSpeech = true;
speechPackage->audioContextSyncCallback = speechInitCallback;
emscripten_start_wasm_audio_worklet_thread_async(contextA, audioThreadStack, sizeof(audioThreadStack), &AudioThreadInitialized, speechPackage);
EMSCRIPTEN_WEBAUDIO_T contextB = emscripten_create_audio_context(&attrs);
AudioPackage *nsfPackage = new AudioPackage();
nsfPackage->sampleStreamable = static_cast<SampleStreamable *>(nsfPlayer);
nsfPackage->isSpeech = false;
nsfPackage->audioContextSyncCallback = nsfInitCallback;
emscripten_start_wasm_audio_worklet_thread_async(contextB, audioThreadStack, sizeof(audioThreadStack), &AudioThreadInitialized, nsfPackage);
}