hvianna / audioMotion-analyzer

High-resolution real-time graphic audio spectrum analyzer JavaScript module with no dependencies.
https://audioMotion.dev
GNU Affero General Public License v3.0
604 stars 59 forks source link

Gain keeps getting higher every time a AudioMotionAnalyzer is initialized #53

Closed lucienimmink closed 11 months ago

lucienimmink commented 1 year ago

Hi,

First of all: great software! I'm implementing this with Lit with a web component that is only added to the DOM if needed. I have some hacky work-around to bypass having to disconnect on destroy of the component

let audioCtx;
let source;
if (!window._audioCtx) {
          audioCtx = new AudioContext();
          source = audioCtx.createMediaElementSource(window._player);

          // store in memory for reuse
          window._audioCtx = audioCtx;
          window._source = source;
        } else {
          audioCtx = window._audioCtx;
          source = window._source;
        }

Every time I call new AudioMotionAnalyzer() the gain of this chain is increased and it starts clipping. Setting the volume has no effect.

const visualizer = new AudioMotionAnalyzer(canvas, {
          audioCtx,
          source,
});
lucienimmink commented 1 year ago

I guess it has to do with the constructor itself

const analyzer = this._analyzer = [ audioCtx.createAnalyser(), audioCtx.createAnalyser() ];
const splitter = this._splitter = audioCtx.createChannelSplitter(2);
const merger   = this._merger   = audioCtx.createChannelMerger(2);
this._input    = audioCtx.createGain();
this._output   = audioCtx.createGain();

Would be nice to either have a destroy() method that cleans up all webaudio related nodes so that can be called when the component is disconnected or a way to bypass the creation of these webaudio nodes when they are already present, these should be re-used if possible.

hvianna commented 1 year ago

The analyzer is connected to the speakers by default, so multiple calls to the constructor will multiply the output as well. Also, depending on the player you're using, it may already be doing the connection to the AudioContext destination node (speakers).

You can use connectSpeakers: false in the constructor options to skip connecting the analyzer to the speakers, like so:

const visualizer = new AudioMotionAnalyzer(canvas, {
          audioCtx,
          source,
          connectSpeakers: false
});

If you get no sound after that, connect your source node to the destination, like so:

source.connect( audioCtx.destination );

Do you really need to call the constructor multiple times, though? Like, you need multiple visualizations running at the same time? Please note these instances will keep consuming resources, unless you use toggleAnalyzer() to stop the audio processing.

Anyway, a destroy() method is a good suggestion! I'll look into adding this in a future release! 👍

lucienimmink commented 1 year ago

I'll look into your thought, for now just want to come back to the remark if I need more visualisations at the same time: no :) I have a music player with an optional visualiser. If shown it's added to the Dom as a web component and if removed, the component is removed from the dom, so I can call destroy() when the web component is removed, all the while the music keeps on playing.

lucienimmink commented 1 year ago

Just FYI: Using the connectSpeaker: false option is (for me) fixing the issue, although I believe it would be a better/safer option to have the proper destroy() method. Thank you for the tip!

hvianna commented 1 year ago

Cool, thanks for the follow-up!

hvianna commented 11 months ago

destroy() is now available in v4.2.0 🎉