Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduled parameter changes not working #15

Open
martinholters opened this issue Nov 5, 2018 · 2 comments · May be fixed by #16
Open

Scheduled parameter changes not working #15

martinholters opened this issue Nov 5, 2018 · 2 comments · May be fixed by #16

Comments

@martinholters
Copy link

martinholters commented Nov 5, 2018

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.

@developit
Copy link
Collaborator

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?

@martinholters
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants