Skip to content

Commit

Permalink
reuse Float32Array between process call for parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
b-ma committed May 11, 2024
1 parent 7f1bdc0 commit 7264803
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 11 deletions.
14 changes: 12 additions & 2 deletions js/AudioWorkletGlobalScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const kHiddenOptions = Symbol('node-web-audio-api:worklet-hidden-options');
const kWorkletInputs = Symbol.for('node-web-audio-api:worklet-inputs');
const kWorkletOutputs = Symbol.for('node-web-audio-api:worklet-outputs');
const kWorkletParams = Symbol.for('node-web-audio-api:worklet-params');
const kWorkletParamsCache = Symbol.for('node-web-audio-api:worklet-params-cache');
// const kWorkletOrderedParamNames = Symbol.for('node-web-audio-api:worklet-ordered-param-names');

const nameProcessorCtorMap = new Map();
Expand Down Expand Up @@ -75,13 +76,22 @@ globalThis.AudioWorkletProcessor = class AudioWorkletProcessor {

this.#port = port;

// @todo - reuse Float32Arrays between calls + freeze arrays
this[kWorkletInputs] = new Array(numberOfInputs).fill([]);
// @todo - use `outputChannelCount`
this[kWorkletOutputs] = new Array(numberOfOutputs).fill([]);

// Object to be reused as `process` parameters argument
this[kWorkletParams] = {};
// prepare kWorkletParams object with parameter descriptors names
// Cache of 2 Float32Array (of length 128 and 1) for each param, to be reused on
// each process call according to the size the param for the current render quantum
this[kWorkletParamsCache] = {};

parameterDescriptors.forEach(desc => {
this[kWorkletParams][desc.name] = null;
this[kWorkletParamsCache][desc.name] = [
new Float32Array(128), // should be globalThis.renderQuantumSize
new Float32Array(1),
]
});
}

Expand Down
2 changes: 1 addition & 1 deletion js/AudioWorkletNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ module.exports = (jsExport, nativeBinding) => {

// if we delegate this check to Rust, this can poison a Mutex
// (probably the `audio_param_descriptor_channel` one)
if (parsedOptions.channelCount <= 0 || parsedOptions.channelCount > 32) {
if (parsedOptions.channelCount <= 0 || parsedOptions.channelCount > IMPLEMENTATION_MAX_NUMBER_OF_CHANNELS) {
throw new DOMException(`Failed to construct 'AudioWorkletNode': Invalid 'channelCount' property: Number of channels: ${parsedOptions.channelCount} is outside range [1, 32]`, 'NotSupportedError')
}
}
Expand Down
28 changes: 20 additions & 8 deletions src/audio_worklet_node.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::{float_buffer_to_js, get_symbol_for};
use crate::utils::{float_buffer_to_js, get_symbol_for, to_byte_slice};
use crate::{NapiAudioContext, NapiAudioParam, NapiOfflineAudioContext};

use crossbeam_channel::{self, Receiver, Sender};
Expand Down Expand Up @@ -160,7 +160,8 @@ fn process_audio_worklet(env: &Env, args: ProcessorArguments) -> Result<()> {

let k_worklet_inputs = get_symbol_for(env, "node-web-audio-api:worklet-inputs");
let k_worklet_outputs = get_symbol_for(env, "node-web-audio-api:worklet-outputs");
let k_worklet_params = get_symbol_for(env, "node-web-audio-api:worklet-outputs");
let k_worklet_params = get_symbol_for(env, "node-web-audio-api:worklet-params");
let k_worklet_params_cache = get_symbol_for(env, "node-web-audio-api:worklet-params-cache");

let js_inputs = processor.get_property::<JsSymbol, JsObject>(k_worklet_inputs)?;

Expand Down Expand Up @@ -195,12 +196,23 @@ fn process_audio_worklet(env: &Env, args: ProcessorArguments) -> Result<()> {
}

let mut js_params = processor.get_property::<JsSymbol, JsObject>(k_worklet_params)?;
// @note - could maybe rely on the fact that ParameterDescriptors
// are ordered to avoid sending param names in `param_values`
param_values.iter().for_each(|(name, data)| {
let val = float_buffer_to_js(env, data.as_ptr() as *mut _, data.len());
js_params.set_named_property(name, val).unwrap()
});
let js_params_cache = processor.get_property::<JsSymbol, JsObject>(k_worklet_params_cache)?;

// @perf - We could rely on the fact that ParameterDescriptors
// are ordered maps to avoid sending param names in `param_values`
for (name, data) in param_values.iter() {
let float32_arr_cache = js_params_cache.get_named_property::<JsObject>(name)?;
// retrieve right Float32Array according to actual param size, i.e. 128 or 1
let cache_index = if data.len() == 1 { 1 } else { 0 };
let float32_arr = float32_arr_cache.get_element::<JsTypedArray>(cache_index)?;
// copy data into undeerlying ArrayBuffer
let mut array_buffer_value = float32_arr.into_value()?.arraybuffer.into_value()?;
let u8_slice = to_byte_slice(data);
array_buffer_value.copy_from_slice(u8_slice);
// get new owned value, as `float32_arr` as been consumed by `into_value` call
let float32_arr = float32_arr_cache.get_element::<JsTypedArray>(cache_index)?;
js_params.set_named_property(name, float32_arr).unwrap();
}

let process_method = processor.get_named_property::<JsFunction>("process")?;
let js_ret: JsUnknown = process_method.apply3(processor, js_inputs, js_outputs, js_params)?;
Expand Down

0 comments on commit 7264803

Please sign in to comment.