Typescript, Web Audio.
I have a tool that plays audio and some users have reported that the "panning" effect doesn't work for them. Basically there is a slider which the user can use to control the position of their sound from the far-left to far-right ear. This is analagous to the "Room of Metal" PannerNode example: https://mdn.github.io/webaudio-examples/panner-node/
The implementation I will share below is known to work in most contexts, but I will outline the specific places it has issues.
Most commonly the issue description from users is that panning effect doesn't work and equally affects both speakers, such that when the pan slider is far to the right the audio will be at full volume, and when it is far left the audio will be at zero.
I asked the users having issues to explain where they saw the issue and they fairly unilaterally report issues on various versions and browsers for iOS. I'm aware of the issue with some models of iPhone only having a right speaker due to a mindblowingly bad design, but I've asked for clarification that users were indeed using headphones and still seeing the issue. I even had someone in person demonstrate to me on their phone with headphones on an iPhone 7. I also had a user test the "Room of Metal" implementation and they saw similar results - panning wasn't working in the same way I've described.
...
Checking the type of node to use:
if (this.audioCtx.createStereoPanner) {
this.pannerLeft = this.audioCtx.createStereoPanner();
this.pannerRight = this.audioCtx.createStereoPanner();
}
else if (this.audioCtx.createPanner) {
this.pannerLeft = this.audioCtx.createPanner();
this.pannerRight = this.audioCtx.createPanner();
}
else {
this.forceMono = true;
}
...
Setting up the nodes:
if (this.audioCtx.createStereoPanner) {
(this.pannerLeft as StereoPannerNode).pan.setValueAtTime(-1.0, this.audioCtx.currentTime); // Hard left
(this.pannerRight as StereoPannerNode).pan.setValueAtTime(1.0, this.audioCtx.currentTime); // Hard right
}
else {
(this.pannerLeft as PannerNode).setPosition(-5, 0, 1); // Hard left
(this.pannerRight as PannerNode).setPosition(5, 0, 1); // Hard right
}
this.scriptNode = this.audioCtx.createScriptProcessor ?
this.audioCtx.createScriptProcessor(2048, 0, 2) :
this.audioCtx.createJavaScriptNode(2048, 0, 2); // 2048, 0 input channels, 2 outputs (L + R)
this.scriptNode.onaudioprocess = this.audioProcessCallback;
this.scriptNode.channelCountMode = 'explicit';
this.scriptNode.channelInterpretation = 'speakers';
this.splitter = this.audioCtx.createChannelSplitter(2);
this.merger = this.audioCtx.createChannelMerger(2);
this.scriptProcessorNode.connect(this.splitter);
this.splitter.connect(this.pannerLeft, 0);
this.splitter.connect(this.pannerRight, 1);
this.pannerLeft.connect(this.merger, 0, 0);
this.pannerRight.connect(this.merger, 0, 1);
this.merger.connect(this.audioCtx.destination);
...
Audio process callback snippet:
// Check if panning should be used - otherwise just use mono and both
// channels will be the same power.
if ( !this.forceMono ) {
// "Constant Power" panning - left audio is cos(angle), right audio is sin(angle)
// "angle" in this context is in radians from 0 (left) to pi/2 (right)
dataLeft[bufferIndex] += sample * Math.cos(((pan) / 100.0) * Math.PI / 2);
dataRight[bufferIndex] += sample * Math.sin(((pan) / 100.0) * Math.PI / 2);
} else {
dataLeft[bufferIndex] += sample;
dataRight[bufferIndex] += dataLeft[bufferIndex];
}
In the code snippet above, I have some checks to use a StereoPannerNode first (not supported on mobile so it is often skipped), and then it checks if PannerNodes are supported. And here's the catch - on my mobile debugger on iOS Safari, PannerNode is supported, but the issue is present even though it initializes fine. This means that I can't get the "forceMono" piece of code to run in contexts where this panning issue would manifest.
Note that I left out some bits where the audio processing chain is set up differently when forceMono is used, since the problem is that we aren't getting to the forceMono part anyway!
So my questions are as follows:
- Is this incompatibility widely known?
- How do I detect when the user has this issue if PannerNode seems to initialize and work fine but in reality works completely improperly?
- Is anything wrong in my setup, specifically the PannerNode positioning? It is made to mimic the Room of Metal example, but I suppose that could be wrong too.
Aucun commentaire:
Enregistrer un commentaire