Tonejs / Tone.js

A Web Audio framework for making interactive music in the browser.
https://tonejs.github.io
MIT License
13.52k stars 983 forks source link

`Channel` nodes have gain < 1 even with volume set to 0 dB #941

Closed micahscopes closed 3 years ago

micahscopes commented 3 years ago

Describe the bug Using multiple new Channel() or new Channel(0) nodes in series results in very quiet volume. These nodes don't have unity gain when set to volume of 0 dB.

In my application I'm using Channel.send and Channel.receive all over the place, which is amazing and simplifies my life so much!

However, I started to notice that the more Channel nodes I added, the quieter the sound got...

To Reproduce

Open this JSFiddle and drag the slider to chain multiple channels together, observe the volume meter get quieter the more channels you add: https://jsfiddle.net/micahscopes/qbatvkj1/181/

Expected behavior I should be able to chain together dozens of new Channel(0) nodes and the source volume will be preserved.

What I've tried I tried initializing the Channel objects with new Channel(4) and that seemed to roughly preserve the volume. So the Channel nodes are doing something like -4 dB by default, even when they're set at a volume of 0 dB.

I also looked at the code for Channel and saw that there's some basic unit conversion. Maybe the bug is in the unit conversion code?

micahscopes commented 3 years ago

I'd appreciate any tips on how I might go about fixing this!

micahscopes commented 3 years ago

Looks like it might be a bug in the PanVol node... I tried replacing new Channel() with new PanVol() and the same issue occurred. However, replacing new Channel() with new Gain() or new Volume() preserves the levels as expected...

micahscopes commented 3 years ago

Okay, I figured out the issue: the Panner node has a default channelCount of 1. Passing {channelCount: 2} fixes the issue described here.

c.c. @tambien this behavior seems unintuitive to me. Shouldn't Channel nodes be stereo by default? I'm not sure if I fully understand the nuances of why channelCount is 1 by default.

tambien commented 3 years ago

This seems like the same problem as #609

The issue is that Safari has not implemented the StereoPannerNode natively which means that we can't use that browser's native channel mapping and it has to be passed in explicitly for any node that uses the StereoPannerNode under the hood (like Channel and Panner, PanVol).

Shouldn't Channel nodes be stereo by default?

If we set it to either stereo or mono by default, it is going to be wrong some percentage of the time. The only solution for now is to wait until Safari implements this node type.

micahscopes commented 3 years ago

That makes sense. For now I just made myself a little wrapper:

import { Channel as ToneChannel } from 'tone';

export class Channel extends ToneChannel {
  constructor(...args) {
    const opts = { channelCount: 2 };
    if (args[0]) {
      opts.volume = args[0];
    }
    if (args[1]) {
      opts.pan = args[1];
    }
    if (args[2]) {
      opts.channelCount = args[2];
    }
    super(opts);
  }
}