Skip to content

Scheduled parameter changes not working #15

@martinholters

Description

@martinholters

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions