Scheduled parameter changes not working
martinholters opened this issue · 2 comments
I have an AudioWorkletProcessor
that defines some parameters. If I change those by assigning directly to .value
, everything works as expected. However, if I try to schedule a change by calling linearRampToValueAtTime()
, exponentialRampToValueAtTime()
, or similar, the value is not changed at all. Reading it back using .value
constantly yields the old value, and the processor also only sees the old value. Mainly tested with Firefox 60 (ESR), but from a brief test, Safari and Edge seem to be similarly affected. (The same code in an audioworklet-supporting browser (Chromium) works fine.) Is this an expected limitation of the polyfill? Or am I doing something stupid?
For reference, this a simplified reproducer, where the checkbox should (almost) mute the sine oscillator with fade-in/-out over half a second:
test.html
:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/audioworklet-polyfill/dist/audioworklet-polyfill.js"></script>
<script>
const audioCtx = new window.AudioContext();
const source = audioCtx.createOscillator();
let gain;
source.type = 'sine';
audioCtx.audioWorklet.addModule('testproc.js').then(() => {
gain = new AudioWorkletNode(audioCtx, 'my-gain');
source.connect(gain);
gain.connect(audioCtx.destination);
});
function play() {
source.start();
audioCtx.resume();
}
function setgain() {
gain.parameters.get('gain').exponentialRampToValueAtTime(
1 - 0.9999*document.getElementById('cb').checked,
audioCtx.currentTime + 0.5
);
}
</script>
</head>
<body>
<input type="button" onclick="play();" value="run">
<input type="checkbox" onclick="setgain();" id="cb"><label for="id">mute</label>
</body>
</html>
testproc.js
:
class MyGain extends AudioWorkletProcessor {
static get parameterDescriptors() { return [{ name: 'gain', defaultValue: 1 }]; }
process(inputs, outputs, parameters) {
for (let channel = 0; channel < inputs[0].length; channel++) {
for (let sample = 0; sample < inputs[0][0].length; sample++) {
const g = parameters.gain.length == 1 ? parameters.gain[0] : parameters.gain[sample];
outputs[0][channel][sample] = g * inputs[0][channel][sample];
}
}
return true;
}
}
registerProcessor('my-gain', MyGain);
As a workaround, I have tried this locally:
--- a/src/index.js
+++ b/src/index.js
@@ -31,6 +31,19 @@
const prop = processor.properties[i];
const node = context.createGain().gain;
node.value = prop.defaultValue;
+ node.exponentialRampToValueAtTime = function(v, t) {
+ const t0 = context.currentTime
+ const v0 = node.value;
+ const f = Math.log(v / v0) / (t - t0);
+ window.setTimeout(function expUpdateVal() {
+ if (context.currentTime < t) {
+ node.value = v0 * Math.exp((context.currentTime - t0) * f);
+ window.setTimeout(expUpdateVal, 0.001);
+ } else {
+ node.value = v;
+ }
+ }, 0.001);
+ };
// @TODO there's no good way to construct the proxy AudioParam here
scriptProcessor.parameters.set(prop.name, node);
}
That does work for my case, but is of course not doing exactly what the standard calls for.
EDIT: Before anyone points me to GainNode
, in the real use case, I do more elaborate processing, of course, where I cannot use a pre-defined node.
Hi @martinholters! This is a limitation of the polyfill, and one I'd love to overcome but as of yet haven't found a good solution for. Your timer-based version is novel, but I'm not sure how viable using setTimeout for scheduling would be in a full application. This makes me wonder if it could be modified to use the onaudioprocess
event fired by ScriptProcessorNode?
Yes, doing the value update in onaudioprocess
sounds like a saner approach. It would even allow the parameter to correctly vary within one block. I'll give it a shot and open a PR if I can cook up something I deem generally useful.