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.