WebAudio/web-audio-api

Add a BaseAudioContext.audioWorklet.removeModule() method

JSmithOner opened this issue · 9 comments

Describe the feature

I would like to be able to remove a module from audioWorklet.

Is there a prototype?

audioContext.audioWorklet.removeModule('my-module');

or

audioContext.audioWorklet.removeModule( url );

Describe the feature in more detail

As I'm not aware how memory treats the added modules when not used, I can't really know if this feature is kind of built-in (garbage collector).

The asked feature speaks for itself so I'll describe the problem I'm facing.
If I edit the code of AudioWorkletProcessor and update my AudioWorkletNode, I imagine addModule() saves the previous versions in memory which can cause a lot of memory usage. The memory should be freed.
Also the registered module name cannot be the same when adding it a second time, this cancels the possibility to update the worklet, also one solution would to overwrite the previous one with the current one.

Thanks in advance

This looks like a request to unload included source code, which I think isn't supported for Worklets or JavaScript in general.

Are you trying to make a web application that dynamically rewrites the source code of an AudioWorkletProcessor?

@mjwilson-google Yes exactly I'm making an AudioWorkletProcessor editor.Any workarounds?Thanks in advance

@mjwilson-google I was thinking of something. Would garbage collector work in the case I create a new AudioContext then add the updated processor module after each change or would this cause huge memory leaks? Thanks in advance.

We don't specify garbage collector behavior in the Web Audio specification, so this isn't the best place to ask that question. You might have to check the behavior of each browser.

I probably should close this, but would reloading the page or frame when the processor source code has changed be a viable workaround for you?

Would this be possibe to create a local iframe and load audicontext and module inside of it? then delete it and create it back when code is updated? if that possible I guess this would do the trick?Thanks in advance

This is often how this type of software dispose of their resources indeed. You can also try creating a new AudioContext and dispose of the previous one you were using.

@padenot Thank you paul so what your saying is I don't need to create an iframe and just dispose the previous AudioContext is that right?

Yes, this is something you can do indeed, see this example code that should hopefully be clear:

<script type="worklet">
  registerProcessor('test-param', class param extends AudioWorkletProcessor {
    constructor() {
      super();
      console.log("in ctor, before setting expando: " + globalThis.someExpando);
      globalThis.someExpando = "hi!";
      console.log("in ctor, after setting expando: " + globalThis.someExpando);
    }
    process(input, output, parameters) {
    }
  });
</script>
<script>
  var e = document.querySelector("script[type=worklet]")
  var text = e.innerText;
  const blob = new Blob([text], {type: "application/javascript"});
  var url = URL.createObjectURL(blob);

  var ac = new AudioContext;
  ac.audioWorklet.addModule(url).then(() => {
      nodea = new AudioWorkletNode(ac, 'test-param', {});
      nodea.connect(ac.destination);
  });

// In a real app, close the previous AudioContext
// ac.close();

  var ac2 = new AudioContext;
  ac2.audioWorklet.addModule(url).then(() => {
      nodea = new AudioWorkletNode(ac2, 'test-param', {});
      nodea.connect(ac2.destination);
  });
</script>

This creates two AudioContexts and loads a module in each one (here they are the same, for the sake of the example, but they could be different, e.g. different version of something a user would write), and then logs a piece of global state, then sets it to a value, then logs it again. It prints:

in ctor, before setting expando: undefined
in ctor, after setting expando: hi!
in ctor, before setting expando: undefined
in ctor, after setting expando: hi!

to the JavaScript console because each AudioContext has a distinct AudioWorkletGlobalScope: no state is shared between the two.

If I were to use this technique, I would be careful to call close() on each AudioContext I wanted to dispose of, and then carefully release all reference to this AudioContext objects (be it AudioNode or the AudioContext itself), to free resources as soon as possible and to avoid having multiple real-time audio threads running at the same time (they are expensive, and it would be wasteful).

The JavaScript language doesn't have a way to "remove" an added module in a scope, so this is an approach that gives you a fresh scope each time.

@padenot Thank you you're example spkeaks for itself.
I guess we can close this even though a removeModule() method would be great but apparently not possible.