From 38c1c44728a197fa28ff8e7dfa7009b385c3db30 Mon Sep 17 00:00:00 2001 From: Alexandre Lunelli da Silva Date: Thu, 28 May 2026 00:05:16 -0300 Subject: [PATCH 1/2] Refatora preview realtime para AudioWorkletNode --- targets/wasm/js/demo.js | 94 +++++++++-------------------------------- 1 file changed, 20 insertions(+), 74 deletions(-) diff --git a/targets/wasm/js/demo.js b/targets/wasm/js/demo.js index 24f298b..d718c6b 100644 --- a/targets/wasm/js/demo.js +++ b/targets/wasm/js/demo.js @@ -225,36 +225,9 @@ function updateBypassUi() { const realtimeState = { - isOfflineProcessing: false, - ptrInL: 0, - ptrInR: 0, - ptrOutL: 0, - ptrOutR: 0, - blockSize: 0 + isOfflineProcessing: false }; -function ensureRealtimeBuffers(blockSize) { - if (!module) { - return; - } - - if (realtimeState.blockSize >= blockSize && realtimeState.ptrInL && realtimeState.ptrInR && realtimeState.ptrOutL && realtimeState.ptrOutR) { - return; - } - - if (realtimeState.ptrInL) module._free(realtimeState.ptrInL); - if (realtimeState.ptrInR) module._free(realtimeState.ptrInR); - if (realtimeState.ptrOutL) module._free(realtimeState.ptrOutL); - if (realtimeState.ptrOutR) module._free(realtimeState.ptrOutR); - - const bytes = blockSize * 4; // Float32Array.BYTES_PER_ELEMENT - realtimeState.ptrInL = module._malloc(bytes); - realtimeState.ptrInR = module._malloc(bytes); - realtimeState.ptrOutL = module._malloc(bytes); - realtimeState.ptrOutR = module._malloc(bytes); - realtimeState.blockSize = blockSize; -} - async function ensurePreviewGraph() { if (previewGraph.audioCtx) return; const audioCtx = new AudioContext({ sampleRate: DEFAULT_UI_SAMPLE_RATE }); @@ -267,63 +240,27 @@ async function ensurePreviewGraph() { dryGain.gain.value = 0; wetGain.gain.value = 1; - const scriptNode = audioCtx.createScriptProcessor(BLOCK_SIZE, 2, 2); - - ensureRealtimeBuffers(BLOCK_SIZE); + await audioCtx.audioWorklet.addModule('./worklet-processor.js'); + const orbitNode = new AudioWorkletNode(audioCtx, 'orbit-delay-processor'); + orbitNode.port.onmessage = (event) => { + if (event.data?.type === 'orbit_diag') { + console.debug('[orbit_diag]', event.data); + } + }; if (api) { api.reset(uiSampleRate); applyParams(); } - scriptNode.onaudioprocess = (e) => { - const inputBuffer = e.inputBuffer; - const outputBuffer = e.outputBuffer; - - if (realtimeState.isOfflineProcessing || !api) { - for (let channel = 0; channel < outputBuffer.numberOfChannels; channel++) { - const inputData = inputBuffer.getChannelData(channel); - const outputData = outputBuffer.getChannelData(channel); - outputData.set(inputData); - } - return; - } - - const numSamples = inputBuffer.length; - if (numSamples <= 0) { - return; - } - ensureRealtimeBuffers(numSamples); - if (!realtimeState.ptrInL || !realtimeState.ptrInR || !realtimeState.ptrOutL || !realtimeState.ptrOutR) { - return; - } - - const processLen = numSamples; - - const inL = inputBuffer.getChannelData(0); - const inR = inputBuffer.numberOfChannels > 1 ? inputBuffer.getChannelData(1) : inL; - const outL = outputBuffer.getChannelData(0); - const outR = outputBuffer.numberOfChannels > 1 ? outputBuffer.getChannelData(1) : outL; - - module.HEAPF32.set(inL.subarray(0, processLen), realtimeState.ptrInL >> 2); - module.HEAPF32.set(inR.subarray(0, processLen), realtimeState.ptrInR >> 2); - - api.process(realtimeState.ptrInL, realtimeState.ptrInR, realtimeState.ptrOutL, realtimeState.ptrOutR, processLen); - - outL.set(module.HEAPF32.subarray(realtimeState.ptrOutL >> 2, (realtimeState.ptrOutL >> 2) + processLen)); - if (outputBuffer.numberOfChannels > 1) { - outR.set(module.HEAPF32.subarray(realtimeState.ptrOutR >> 2, (realtimeState.ptrOutR >> 2) + processLen)); - } }; - drySource.connect(dryGain).connect(audioCtx.destination); - drySource.connect(scriptNode).connect(wetGain).connect(audioCtx.destination); + drySource.connect(orbitNode).connect(wetGain).connect(audioCtx.destination); previewGraph.audioCtx = audioCtx; previewGraph.dryElement = els.preview; - previewGraph.dryGain = dryGain; previewGraph.wetGain = wetGain; - previewGraph.scriptNode = scriptNode; + previewGraph.orbitNode = orbitNode; } @@ -912,12 +849,21 @@ async function decodeToStereoFloat(file) { } function setParam(key) { - if (!api) return; const el = els[key]; const converter = paramConverters[key]; if (!el || !converter) return; const value = converter(el.value); + + if (previewGraph.orbitNode && previewGraph.audioCtx && !realtimeState.isOfflineProcessing) { + const param = previewGraph.orbitNode.parameters.get(key); + if (param) { + param.setValueAtTime(value, previewGraph.audioCtx.currentTime); + } + return; + } + + if (!api) return; switch (key) { case 'orbit': api.setOrbit(value); break; case 'offsetSamples': api.setOffsetSamples(value); break; From 73b6f6334197e090ba4753090d6f509edae5f9a2 Mon Sep 17 00:00:00 2001 From: Alexandre Lunelli da Silva Date: Thu, 28 May 2026 02:07:12 -0300 Subject: [PATCH 2/2] =?UTF-8?q?Corrige=20init=20do=20worklet=20e=20sincron?= =?UTF-8?q?iza=20par=C3=A2metros=20realtime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- targets/wasm/js/demo.js | 45 +++++++++++++++++++++++----- targets/wasm/js/worklet-processor.js | 11 ++++++- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/targets/wasm/js/demo.js b/targets/wasm/js/demo.js index d718c6b..3b9c218 100644 --- a/targets/wasm/js/demo.js +++ b/targets/wasm/js/demo.js @@ -240,18 +240,42 @@ async function ensurePreviewGraph() { dryGain.gain.value = 0; wetGain.gain.value = 1; - await audioCtx.audioWorklet.addModule('./worklet-processor.js'); - const orbitNode = new AudioWorkletNode(audioCtx, 'orbit-delay-processor'); + try { + await audioCtx.audioWorklet.addModule('./worklet-processor.js'); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error('Falha ao carregar módulo AudioWorklet.', err); + els.status.textContent = `Erro no módulo AudioWorklet: ${message}`; + throw err; + } + + let orbitNode; + try { + orbitNode = new AudioWorkletNode(audioCtx, 'orbit-delay-processor'); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + console.error('Falha ao criar AudioWorkletNode orbit-delay-processor.', err); + els.status.textContent = `Erro ao criar AudioWorkletNode: ${message}`; + throw err; + } + orbitNode.port.onmessage = (event) => { if (event.data?.type === 'orbit_diag') { console.debug('[orbit_diag]', event.data); + return; } - }; - if (api) { - api.reset(uiSampleRate); - applyParams(); - } + if (event.data?.type === 'ready') { + console.debug('[orbit-worklet] WASM pronto.'); + return; + } + + if (event.data?.type === 'error') { + const message = event.data?.message || 'Erro desconhecido no worklet.'; + console.error('[orbit-worklet:error]', event.data); + els.status.textContent = `Erro no worklet: ${message}`; + } + }; drySource.connect(dryGain).connect(audioCtx.destination); drySource.connect(orbitNode).connect(wetGain).connect(audioCtx.destination); @@ -261,6 +285,11 @@ async function ensurePreviewGraph() { previewGraph.dryGain = dryGain; previewGraph.wetGain = wetGain; previewGraph.orbitNode = orbitNode; + + if (api) { + api.reset(uiSampleRate); + applyParams(); + } } @@ -859,6 +888,8 @@ function setParam(key) { const param = previewGraph.orbitNode.parameters.get(key); if (param) { param.setValueAtTime(value, previewGraph.audioCtx.currentTime); + } else { + console.warn(`Parâmetro "${key}" não encontrado no AudioWorkletNode.`); } return; } diff --git a/targets/wasm/js/worklet-processor.js b/targets/wasm/js/worklet-processor.js index 4a587e1..7e62f08 100644 --- a/targets/wasm/js/worklet-processor.js +++ b/targets/wasm/js/worklet-processor.js @@ -71,7 +71,10 @@ class OrbitDelayProcessor extends AudioWorkletProcessor { { name: 'toneHz', defaultValue: 6500.0, minValue: 20.0, maxValue: 20000.0 }, { name: 'smearAmount', defaultValue: 0.25, minValue: 0.0, maxValue: 1.0 }, { name: 'diffuserStages', defaultValue: 4.0, minValue: 0.0, maxValue: 8.0 }, - { name: 'dcBlockEnabled', defaultValue: 1.0, minValue: 0.0, maxValue: 1.0 } + { name: 'dcBlockEnabled', defaultValue: 1.0, minValue: 0.0, maxValue: 1.0 }, + { name: 'shimmerMode', defaultValue: 0.0, minValue: 0.0, maxValue: 1.0 }, + { name: 'feedbackTapMixerEnabled', defaultValue: 0.0, minValue: 0.0, maxValue: 1.0 }, + { name: 'feedbackTapMixerAmount', defaultValue: 0.0, minValue: 0.0, maxValue: 1.0 } ]; } @@ -135,6 +138,9 @@ class OrbitDelayProcessor extends AudioWorkletProcessor { set_diffuser_stages: this.module.cwrap('orbit_wasm_set_diffuser_stages', 'number', ['number']), set_dc_block_enabled: this.module.cwrap('orbit_wasm_set_dc_block_enabled', 'number', ['number']), set_read_mode: this.module.cwrap('orbit_wasm_set_read_mode', 'number', ['number']), + set_shimmer_mode: this.module.cwrap('orbit_wasm_set_shimmer_mode', 'number', ['number']), + set_feedback_tap_mixer_enabled: this.module.cwrap('orbit_wasm_set_feedback_tap_mixer_enabled', 'number', ['number']), + set_feedback_tap_mixer_amount: this.module.cwrap('orbit_wasm_set_feedback_tap_mixer_amount', 'number', ['number']), get_diag: this.module.cwrap('orbit_wasm_get_last_block_diagnostics', 'number', ['number','number','number','number','number','number','number','number','number','number']) }; @@ -182,6 +188,9 @@ class OrbitDelayProcessor extends AudioWorkletProcessor { this.api.set_diffuser_stages(Math.round(at('diffuserStages'))); this.api.set_dc_block_enabled(at('dcBlockEnabled') >= 0.5 ? 1 : 0); this.api.set_read_mode(Math.round(at('readMode'))); + this.api.set_shimmer_mode(Math.round(at('shimmerMode'))); + this.api.set_feedback_tap_mixer_enabled(at('feedbackTapMixerEnabled') >= 0.5 ? 1 : 0); + this.api.set_feedback_tap_mixer_amount(at('feedbackTapMixerAmount')); } process(inputs, outputs, parameters) {