Need help! - In working with Music Sound modify the Web Audio API example: GainNode with PannerNode

Hello!

I ask the MDN community for help in finalizing a specific, working Web Audio API: GainNode with PannerNode.

On the Html 5 web page, we have an Example of playing the sound file: sample = “music.mp3” using the Web Audio API through the GainNode nodes and the PannerNode:

<Script type = "text / javascript">
 Var sample = "music.mp3";

function play( snd, vol, balance ) {
var audioContext = new AudioContext();
audioContext.listener.setPosition( 0, 0, 0 );

var request = new XMLHttpRequest();
request.open( "GET", snd, true );
request.responseType = "arraybuffer";
request.onload = function () {
	var audioData = request.response;
	audioContext.decodeAudioData(
	audioData,
	function ( buffer ) {

	var source = audioContext.createBufferSource();
	source.buffer = buffer;

    // create GainNode
	var gainNode = audioContext.createGain ? audioContext.createGain() : audioContext.createGainNode();
	gainNode.gain.value = vol;

    // create PannerNode
	var panner = audioContext.createPanner();
	panner.setPosition( 0, 0, 1 );
	panner.panningModel = "equalpower";
	panner.setPosition( balance, 0, 1 - Math.abs( balance ) );

    // connect the source and nodes in the chain
	source.connect( gainNode );
	gainNode.connect( panner );
	panner.connect( audioContext.destination );
source.start( 0 );
},
function ( e ) {
alert( "Error with decoding audio data" + e.err );
}
);
};
request.send();
}

</script>

On the Html 5 page there is a button#1 that launches sample = “music.mp3”, which plays with volume = 0.4 (40%) and balance (ie panorama) = (-0,9):
<Input type = "button" value = "Play sample with volume = 0.4 and balance = -0.9" onclick = "play (sample, 0.4, -0.9);" />
Also on the page there is a button#2
<Input type = "button" value = "Play sample with volume = 0.7 and balance = +0.9" onclick = "play (sample, 0.7, +0.9);" />

Problem:
Press the button#1 - sample “music.mp3” starts to play.
We press the button#2 - sample “music.mp3” is launched again and it is layered on itself. It should not be.

Question!
Which specific code needs to be added to the example I cited so that when we click on the button#2 sample “music.mp3” it would not start again, but would continue to play, and its volume and its balance (ie panorama) changed to the new ones Button#2 values?

The little one sometimes gives birth to a lot. It is important only to see this small one.
Please help me in solving my question.

I hope that the question raised by me and its positive decision with the help of the respected community of MDN will somehow help other members of MDN, and first of all web designers, in the creative use of the web audio API.
Responses that are really suitable for solving the problem will be evaluated.

Yours faithfully,
Boris-K.E.

Hi Boris,

I’m a little confused as to what you are trying to do, but I will try to help.

One point first - if you are trying to create a simple stereo panner, you’d be much better off using the following:

This was specifically designed to create stereo panners, and is much simpler than using the original panner node.

Second, you say that you want one button that will play the MP3 at a certain volume, with a certain panning value.

BUT, then you want another button that will play the MP3 with a different volume and panning value? Do you want it to play from the start, or carrying on playing?

In either case, I’d suggest loading the MP3 into an element and then creating a MediaElementAudioSourceNode to put at the start of your audio graph. This way you don’t have to keep loading the MP3 again and again. See here:

You could then change the values just by updating the AudioParams of the nodes as appropriate, e.g.

panner.pan.value = 0.9
gainNode.gain.value = 0.7

Does this help? Let me know if I have misunderstood what you were asking.

Best regards,

Chris

1 Like

Hi Chris!
Firstly, thank you very much for responding.
Thanks to your questions, I formulated my request for the above example much easier by editing the text itself.
The request now sounds like this:

On the Html 5 page there is a button#1 that launches sample = “music.mp3”, which plays with volume = 0.4 (40%) and balance (ie panorama) = (-0,9):
<Input type = "button" value = "Play sample with volume = 0.4 and balance = -0.9" onclick = "play (sample, 0.4, -0.9);" />
Also on the page there is a button#2
<Input type = "button" value = "Play sample with volume = 0.7 and balance = +0.9" onclick = "play (sample, 0.7, +0.9);" />

Problem:
Press the button#1 - sample “music.mp3” starts to play.
We press the button#2 - sample “music.mp3” is launched again and it is layered on itself. It should not be.

Question!
Which specific code needs to be added to the example I cited so that when we click on the button#2 sample “music.mp3” it would not start again, but would continue to play, and its volume and its balance (ie panorama) changed to the new ones Button#2 values?

Chris, if you can, help me, please. Then I will be able to do many interesting and useful things in my project. The project is musical, non-commercial, for kind, not indifferent people. I have been working on it for 9 years, there are hundreds of sounding pages in it. And he actually consists of buttons. Simple, reliable, visual aesthetics …

/ AudioContext.createStereoPanner () does not work for me. In my music project on the page there is music and various other sounds, for each of them its own volume and its own panorama. In addition, there is a general volume.

AudioContext.createMediaElementSource () - interesting advice. The resulted example with AudioContext.createMediaElementSource I disassembled, launched. But, unfortunately, it is not suitable for me, because I do not want to use as source - html5 element. At me so the whole project works on html5 . That’s only for html5 , unfortunately, there is no balance property (panorama). Therefore, in order to enrich the sound picture, I want to add new sounds and music to the already sounding music and sounds on html5 using the capabilities of Web Audio Api without resorting to html5 elements. /

I sincerely thank you for your desire to help.
Yours faithfully,
Boris-KE.

Thanks Boris - this allows me to understand much more clearly what you want to do.

The problem is that both buttons are invoking the play() function, which does everything - creating a new audio context, loading the track, creating new panner and gain nodes, etc. Effectively you are creating two separate audio graphs each with an identical copy of the track inside.

What you want is just one audio graph, which you create with the first button and then manipulate with the second button.

My suggestion would be to make the panner and gainNode global variables, so they are available outside the play() function.

Then inside the play() function initialize those variables with the values rather than declaring and initializing them in the same place.

At this point you should be able to run some code with the second button that alters the pan/vol of the existing audio graph.

Something like

Then include something like this in your JS function:

updateParams(newGain, newPan) {
gainNode.gain.value = newGain
panner.pan.value = newPan
}

1 Like

Thank you, Chris!
To make it easier to understand and check the work, I divided your clues into three parts:

  1. make the global variables panner and gainNode (in order to remove the repeated game sample and its layering into itself by repeatedly pressing button # 1 or by pressing button # 2 after we pressed button # 1);

  2. Initialize the concrete values of the variables panner and gainNode inside the function play (),

  3. add in java:
    UpdateParams (newGain, newPan) {
    GainNode.gain.value = newGain
    Panner.pan.value = newPan
    }

So, 1) I made global variables panner and gainNode, removing var in front of them. But it did not help - the function play () still starts again, the sample sounds again and layers itself.
Then in addition I made a global variable sourse - the result is the same.

I try to go further:

  • initialize the variable gainNode with a specific value:
    GainNode.gain.value = 1.0;
    In this case, the play () function is started, but the sample sound is doubled.

- initialize the panner variable with a specific value:
Panner.pan.value = +0.1;
The play () function has stopped running at all, the sample does not sound!

The code looks like this:

<script type="text/javascript">
window.AudioContext = window.AudioContext || window.webkitAudioContext;
		var sample = "music.mp3";

function play( snd, vol, balance ) {
var audioContext = new AudioContext();
audioContext.listener.setPosition( 0, 0, 0 );


var request = new XMLHttpRequest();
request.open( "GET", snd, true );
request.responseType = "arraybuffer";
request.onload = function () {
	audioData = request.response;
	audioContext.decodeAudioData(
	audioData,
	function ( buffer ) {

 // create source with source global variable
	source = audioContext.createBufferSource();
	source.buffer = buffer;

    // create GainNode with gainNode global variable
	gainNode = audioContext.createGain ? audioContext.createGain() : audioContext.createGainNode();

    // create PannerNode with the panner global variable
	panner = audioContext.createPanner();
	panner.setPosition( 0, 0, 1 );
	panner.panningModel = "equalpower";
	panner.setPosition( balance, 0, 1 - Math.abs( balance ) );

    // connect the source and nodes in the chain
	source.connect( gainNode );
	gainNode.connect( panner );
gainNode.gain.value = 1.0;

	panner.connect( audioContext.destination );
panner.pan.value = +0.1; // Does not allow play ()
//panner.value = +0.1; // Does not interfere the run function play () 
//panner.balance = +0.1; //Does not interfere the run function play ()

source.start( 0 );
},

function UpdateParams (newGain, newPan) {
gainNode.gain.value = newGain
//panner.balance = newPan
panner.pan.value = newPan
}

function ( e ) {
alert( "Error with decoding audio data" + e.err );
}
);
};
request.send();
}

</script>

P.s. Tried the experiment - instead of panner.pan.value = +0.1; Framed - panner.balance = +0.1; // function play () has earned
Also, instead of panner.pan.value = +0.1; Substituted panner.value = +0.1; // function play () has earned
Those. Further passed, but the result did not change.

Your function:
function UpdateParams (newGain, newPan) {
GainNode.gain.value = newGain
Panner.pan.value = newPan
}
I inserted right after source.start (0);

But, the result, for the time being, the same - the main thing is that when the button # 1 is pressed again, the play () function is still re-launched, the sample sounds again and is layered on itself. The same is true when you press # 2.

And in whatever place of code I put your function «function UpdateParams (newGain, newPan)» - nothing changes.

Chris, look, please, what’s wrong at this stage in the code?
Why initialization panner.pan.value = +0.1; Does not let the play () function start?
What will you advise next.

Yours faithfully,
Boris-KE.

Hi Boris,

Still a few problems here, so I decided to write up a quick solution for you:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WAA test</title>
</head>
<body>
  <input type = "button" class="button1" value = "Play sample with volume = 0.4 and balance = -0.9">
  <input type = "button" class="button2" value = "Update volume to 0.7 and balance to +0.9">
<script type="text/javascript">
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
  var sample = "music.mp3";
  var panner, gainNode;
   
  function play( snd, vol, balance ) {
    var audioContext = new AudioContext();
    audioContext.listener.setPosition( 0, 0, 0 );
    
    var request = new XMLHttpRequest();
    request.open( "GET", snd, true );
    request.responseType = "arraybuffer";
    request.onload = function () {
	    audioData = request.response;
	    audioContext.decodeAudioData(
	    audioData,
	    function ( buffer ) {
 
        // create source with source global variable
  	    source = audioContext.createBufferSource();
  	    source.buffer = buffer;
 
        // create GainNode with gainNode global variable
  	    gainNode = audioContext.createGain();
 
        // create StereoPannerNode with the panner global variable
  	    panner = audioContext.createStereoPanner();
 
        // connect the source and nodes in the chain
  	    source.connect( gainNode );
  	    gainNode.connect( panner );
 
        gainNode.gain.value = vol;
        panner.pan.value = balance;
 
  	    panner.connect( audioContext.destination );
        source.start( 0 );
      },
 
      function ( e ) {
        alert( "Error with decoding audio data" + e.err );
      });
    };
    request.send();
  }
 
  function updateParams (newGain, newPan) {
    gainNode.gain.value = newGain;
    panner.pan.value = newPan;
  };
   
  btn1.onclick = function() {
    play(sample, 0.4, -0.9);
  }
   
  btn2.onclick = function() {
    updateParams(0.7, 0.9);
  }
 
</script>
</body>
</html>

Let’s explore what I did.

  • I put the global variable definitions just inside the script tags, so they are available from anywhere in the script. See var panner, gainNode;
  • I put the updateParams function just inside the script tags, not inside play()
  • I’ve changed the onclick handlers from being HTML attributes to JavaScript properties. You can see this first where I grab references to the buttons in variables:
var btn1 = document.querySelector('.button1');
var btn2 = document.querySelector('.button2');

And then I put the onclick handlers in place like this:

btn1.onclick = function() {
  play(sample, 0.4, -0.9);
}

btn2.onclick = function() {
  updateParams(0.7, 0.9);
}

This is a best practice way to do things, as it avoids having JavaScript cluttering up your HTML, and it is also much more flexible — if you want to change your buttons, you only have to alter the JavaScript, not the HTML as well.

  • I’ve changed the second button to run updateParams (0.7, 0.9);
  • I have made sure that all variable, function and element names start with a lower case letter. You should make sure you always follow this rule, as otherwise mistakes can occur. I found a couple of errors that came down simply to a variable name being written wrongly. The only things that should start with upper case letters are constructor function names.
  • I have used a StereoPannerNode like I suggested earlier — it is so much more simple than using a PannerNode. See this part of the code:

panner = audioContext.createStereoPanner();

This just takes a simple pan property to change the stereo panning:

panner.pan.value = balance;

This is much simpler than all that setPosition stuff!

1 Like

Hi, Chris!

Thank you for your consistency in wanting to help. For what you so patiently and carefully explain - what’s what. It’s expensive for me, and I think, not just for me. I hope I will not disappoint you further with my questions, or with my project. (Errors with lowercase letters, which I wrote down in capital letters, I found, and in the previous example I corrected them). Thank you.

You probably forgot to include variables in your example
Var btn1 = document.querySelector (’. Button1’);
Var btn2 = document.querySelector (’. Button2’);
I inserted them after var panner, gainNode;

<Script type = "text / javascript">
Window.AudioContext = window.AudioContext || Window.webkitAudioContext;
Var sample = "music.mp3";
Var btn1 = document.querySelector ('. Button1');
Var btn2 = document.querySelector ('. Button2');

And the sample began to sound!

I.

  1. The first thing I did for a thorough check of work - multiplied the number of buttons and assigned them successively different Volume values with the same Panorama.
    OK! The volume changed, as expected - from 0.0 to 1.0

  2. Then I checked how the panorama works - I assigned the same buttons successively different values of the Panorama at a constant Volume.
    OK! Panorama changed, as expected - from -1.0 to 1.0

  3. Then I checked the operation and the volume and panoramas simultaneously - I assigned the same buttons successively different values of the Volume - from 0.0 to 1.0 and the Panorama from -1.0 to 1.0.
    And then the following was discovered: The Panorama worked as it should, but at the same time the Volume did NOT change at all and stayed at the same level from which we started playing the sample!
    I checked and checked many times - the result is the same: at the same time function updateParams (newGain, newPan) works as it should - only with Panorama! The Volume is not adjusted simultaneously with Panorama!
    What is the reason? Maybe it’s StereoPannerNode?

II.
And suddenly another important question opened. Testing your example showed that as soon as I added sample2,
Then to describe the buttons for separate work function updateParams (newGain, newPan) c updateParams for sample and updateParams for samle2 I did not succeed.
With the usual PannerNode, it was easy: for example (sample, 0.3, +0.8); And (sample2 1.0, -0.9); вut, unfortunately, without adjustment.

And how to be here in your example? After all, I have a lot of sounds on the page, and for each through the function play () should have its own Volume and its Panorama. Without such a universal capability, I simply can not add new sounds using Web Audio Api to my project, which was originally built for me on a simple and flexible work with every sound from the set. Some of which I assign to timers. This is very important for me, and it’s good that in your example I managed to assign play (sample, 0.9, 0.0) and updateParams (0.3, -0.6); Timer. This is a good sign.

I hope that your example with some refinement will work simultaneously with many sounds, probably through updateParams, updateParams2, updateParams3, but what code does it describe in this example??

As a result, at the moment we have:

  1. function updateParams (newGain, newPan) works, but only either by Volume, or by Panorama - simultaneously by Volume and Panorama, for now, - No.
    And maybe, besides, the thing is that the function play () does not have time to process my music.mp3 = 6,5 mb ??
    Tested from a desktop PC (Windows 7 Pro, x64 Ram = 16Gb) in different browsers from the Internet - in Google Chrome and Opera the problem with simultaneous operation of Volume and Panorama in your example is absent! Working! So, it’s not about the size of audio files.
    In Mozilla, the same paradox - as described above - does not work. How so? Miracles and only.

  2. While it is impossible to describe buttons for processing at once several samples: sample, sample2, sample3 …

I sincerely hope you are not tired of my questions and technical problems. The main thing is when, God willing, we with you can specifically solve them in this example - there will be Music!
You will hear it.

Sincerely,
Boris-KE.

Thank you for your consistency in wanting to help. For what you so patiently and carefully explain - what’s what. It’s expensive for me, and I think, not just for me. I hope I will not disappoint you further with my questions, or with my project. (Errors with lowercase letters, which I wrote down in capital letters, I found, and in the previous example I corrected them). Thank you.
 
You probably forgot to include variables in your example
 
Var btn1 = document.querySelector (’. Button1’);
Var btn2 = document.querySelector (’. Button2’);
 
I inserted them after var panner, gainNode;
 
And the sample began to sound!

Ah yes - I forgot to put those in my actual listing, sorry!

Although the classnames are lower case, so again, watch that:

var btn1 = document.querySelector('.button1');
var btn2 = document.querySelector('.button2');

Then I checked the operation and the volume and panoramas simultaneously - I assigned the same buttons successively different values of the Volume - from 0.0 to 1.0 and the Panorama from -1.0 to 1.0.
 
And then the following was discovered: The Panorama worked as it should, but at the same time the Volume did NOT change at all and stayed at the same level from which we started playing the sample!
 
I checked and checked many times - the result is the same: at the same time function updateParams (newGain, newPan) works as it should - only with Panorama! The Volume is not adjusted simultaneously with Panorama!
 
What is the reason? Maybe it’s StereoPannerNode?

I have no idea why this is a problem. The two separate parameters are on two separate audio nodes, and we are updating them independently of each other inside the updateParams() function. Can you send me your updated code listing so I can have a look?

And suddenly another important question opened. Testing your example showed that as soon as I added sample2,
 
Then to describe the buttons for separate work function updateParams (newGain, newPan) c updateParams for sample and updateParams for samle2 I did not succeed.
 
With the usual PannerNode, it was easy: for example (sample, 0.3, +0.8); And (sample2 1.0, -0.9); вut, unfortunately, without adjustment.
 
And how to be here in your example? After all, I have a lot of sounds on the page, and for each through the function play () should have its own Volume and its Panorama. Without such a universal capability, I simply can not add new sounds using Web Audio Api to my project, which was originally built for me on a simple and flexible work with every sound from the set. Some of which I assign to timers. This is very important for me, and it’s good that in your example I managed to assign play (sample, 0.9, 0.0) and updateParams (0.3, -0.6); Timer. This is a good sign.
 
I hope that your example with some refinement will work simultaneously with many sounds, probably through updateParams, updateParams2, updateParams3, but what code does it describe in this example??
 
As a result, at the moment we have:
 
function updateParams (newGain, newPan) works, but only either by Volume, or by Panorama - simultaneously by Volume and Panorama, for now, - No.
 
And maybe, besides, the thing is that the function play () does not have time to process my music.mp3 = 6,5 mb ??
 
Tested from a desktop PC (Windows 7 Pro, x64 Ram = 16Gb) in different browsers from the Internet - in Google Chrome and Opera the problem with simultaneous operation of Volume and Panorama in your example is absent! Working! So, it’s not about the size of audio files.
 
In Mozilla, the same paradox - as described above - does not work. How so? Miracles and only.
 
While it is impossible to describe buttons for processing at once several samples: sample, sample2, sample3.
 
I sincerely hope you are not tired of my questions and technical problems. The main thing is when, God willing, we with you can specifically solve them in this example - there will be Music!
 
You will hear it.

Ok, so jumping to playing multiple samples simultaneously is much more complex, yes. You’ll need to create a separate audio graph for each one, loading each one separately. You won’t need to create multiple play() and updateParams() functions, but you will need to update those functions to tell them which audio sample you want to affect.

So for example the play function could be passed the name of the file to load as a third parameter, and the updateParams() function could be passed the audiocontext object you want to alter the params of.

I think one problem here is that your samples will be different sizes, so will take different times to load, and therefore it will be hard to get them to play accurately and sync up at the right time.

A better solution would probably be to start running code automatically when the page first loads to load all your samples, using some kind of counter variable to keep track of when they are loaded, and then maybe when all samples are loaded, only then show the play buttons.

Then in the play() function, you’d just need to pass in a reference to the sample to be played, connect up the gain and panner nodes, and invoke start()

1 Like

Hello, Chris!
Before I write at the code level - how I tested the work updateParams (newGain, newPan) Volume to the Panorama in your example, let me ask some additional, clarifying questions.

  1. Is it important for the function function updateParams (newGain, newPan) to set the buttons to initialize the variables first, and then run them function updateParams (newGain, newPan)? I.e.:
  <input type = "button" class="button1" value = "Play sample with volume = 0.4 and balance = -0.9">
  <input type = "button" class="button2" value = "Update volume to 0.7 and balance to +0.9">
...
var btn1 = document.querySelector('.button1');
var btn2 = document.querySelector('.button2');

...
function updateParams (newGain, newPan) {
    gainNode.gain.value = newGain;
    panner.pan.value = newPan;
  };
   
  btn1.onclick = function() {
    play(sample, 0.4, -0.9);
  }
   
  btn2.onclick = function() {
    updateParams(0.7, 0.9);
  }

Or it is better to write differently to really reduce the code, simply setting the id to the buttons:

<input type = "button" id="btn1" value = "Play sample with volume = 0.4 and balance = -0.9">
<input type = "button" id="btn2" value = "Update volume to 0.7 and balance to +0.9">

...
function updateParams (newGain, newPan) {
    gainNode.gain.value = newGain;
    panner.pan.value = newPan;
  };
   
  btn1.onclick = function() {
    play(sample, 0.4, -0.9);
  }
   
  btn2.onclick = function() {
    updateParams(0.7, 0.9);
  }

Function updateParams (newGain, newPan) works with both your version and mine. What do you say about this?

  1. For independent work with sample, sample2, sample3, do I understand your words correctly: * You'll need to create a separate audio graph for each one, loading each one separately * - that I first need to create three separate chains:

Source-> GainNode-> StereoPannerNode (gainNode.gain.value = vol; panner.pan.value = balance;)-> audioContext.destination and
Source2-> GainNode2-> StereoPannerNode2 (gainNode2.gain.value = vol; panner2.pan.value = balance;) -> audioContext.destination and
Source3-> GainNode3-> StereoPannerNode3 (gainNode.gain.value = vol; panner.pan.value = balance;) -> audioContext.destination ?

And then for a single function updateParams (newGain, newPan), describe by setting the appropriate buttons on the page, for example the following:

function updateParams (newGain, newPan) {
gainNode.gain.value = newGain;
panner.pan.value = newPan;
gainNode2.gain.value = newGain;
panner2.pan.value = newPan;
gainNode3.gain.value = newGain;
panner3.pan.value = newPan;
};

//for sample
samplebtn1.onclick = function() {
play(sample, 0.4, -0.9);
}

samplebtn2.onclick = function() {
updateParams(0.7, 0.9);
}

//for sample2
sample2btn1.onclick = function() {
play(sample2, 0.5, 0.0);
}

sample2btn2.onclick = function() {
updateParams(0.9, -1.0);
}

//for sample3
sample3btn1.onclick = function() {
play(sample3, 0.3, 0.0);
}

sample3btn2b.onclick = function() {
updateParams(0.7, 1.0);
}

And as a result, the sample, sample2, sample3 will respond to independent processing of Volume and Panorama?

This will be enough or will additionally need to write something else in the code?
If yes, what exactly do your words mean: "you will need to update those functions to tell them which audio sample do you want to affect"?
This is so that I try to write the code in its entirety and show you that you have checked it - whether I wrote everything correctly.

With respect to You,
Boris-KE.

Hello, Chris!
In continuation of my previous letter and in response to your request.

Taking as a basis our example (I just simplified the interaction of the buttons on id with their working in function updateParams (newGain, newPan - seriously reducing the code), I appended the example as a test for checking the reliability of function updateParams (newGain, newPan) with standard Web Audio Api nodes - GainNode and StereoPannerNode.
In addition to the original three buttons - Play sample, Update1 volume and balance and Update2 volume and balance:

< Input type = “button” id = “btn1” value = “Play sample with volume = 0.5 and balance = 0.0”>
< Input type = “button” id = “btn2” value = “Update1 volume to 0.7 and balance to +0.9”>
< Input type = “button” id = “btn3” value = “Update2 volume to 0.9 and balance to -0.9”>

I introduced the example of additional buttons, which I arranged vertically for convenience. All new buttons are divided into three arrays, comprising three Tests:
Test # 1 Test the individual work of the GainNode (Volume) node with the same value of the node StereoPannerNode // --------- VOLUME = from 0.1 to to 1.0 — & — PANORAMA (balance) = constant = 0.0- ----> (10 buttons)
Test # 2 checking the separate operation of the StereoPannerNode node with the GainNode value (constant) constant // --------- PANORAMA (balance) = from -1.0 to 0.0 to +1.0 — & — VOLUME = constant = 1.0 -----> (21 buttons)
Test # 3 checking the simultaneous operation of the GainNode (Volume) node and the StereoPannerNode node (panorama = balance) //! Simultaneously ---- VOLUME = from 0.1 to to 1.0 - & - PANORAMA (balance) = from -1.0 to 0.0 to 1.0 —> (21 buttons)

If we represent new buttons in the form of a graph - each of the new buttons is simultaneously a point on the X-axis (time), the Y-axis reflects the value of this point - its Volume and Panorama (balance).
Testing was done through headphones with listening to the same music stereo-sample.mp3, in which the singer’s voice was clearly defined in the center. Testing was performed on several independent PCs (Windows 7, Windows 8.1) through Mozilla, Google Chrome and Opera browsers, the latest on August 4, 2017 versions.
A thorough testing of the example (the code I’m attaching to you below) yielded the following results:
Test # 1 through Mozilla - OK! Through Google Chrome - OK! Through Opera - OK!

Test # 2 through Mozilla - OK! Through Google Chrome - OK! Through Opera - OK!

Test # 3 through Mozilla - NOT OK! Through Google Chrome - OK! Through Opera - OK!

According to Test # 3, starting from the first to the last point (button) on the X (time) scale, when the Panorama values change step by step from the Y-axis (balance from -1.0 to 0.0 to 1.0-from Left to Right) Linear change in the values of Volume from 0.1 to to 1.0 (from min to max). That’s right, smoothly, without a flaw going through the process through the browsers of Google Chrome and Opera, but not through the Mozilla browser!

What should be through the Mozilla browser on test # 3:

Button1 (Volume = 0.1 (min) and Panorama (balance) = -1.0 (Leftmost)),
Button 11 (Middle Volume = 0.5 and Middle Panorama (balance) = 0.0),
Button21 (Volume = 1.0 (max) and Panorama (balance) = 1.0 (Rightmost))

What do we have in reality through the Mozilla browser on test # 3:

Button1 (Volume = 0.1 (min) - NOT OK! (Not regulated!) And Panorama (balance) = -1.0 (Leftmost) - OK! (but about the problem with the panorama (balance), see below in paragraph 4),

Button 11 (Middle Volume = 0.5 - OK! And Middle Panorama (balance) = 0.0 - OK!),

Button21 (Volume = 1.0 (max) - OK! And Panorama (balance) = 1.0 (Rightmost) - OK!)

Testing showed that through Mozilla in test # 3, with the StereoPannerNode node working poorly in test # 3, the GainNode (Volume) node practically does not work, namely:

  1. The real Volume of all the buttons (points) except kn21, kn20, kn19, kn18 is greatly overestimated! - relative to the corresponding buttons (points) in test # 1.

  2. Volume kn1 (min volume) = kn2 = kn3 = kn4 = kn5 = kn6 = kn7 = kn8 = kn9 = kn10 = constant !!! >> Volume kn11 (Middle Volume) = 0.5 !!! (Balance = 0.0)
    Volume kn17 = kn16 = kn15 = kn14 = kn13 = kn12 = constant !!! Volume kn21 = kn20 = kn19 = kn18 = constant !!!

  3. In reality, through Mozilla in test # 3 of 21 buttons (dots), only two of them work practically normally - kn21 & kn11 !!!, because:
    Test # 3 Volume kn11 (Middle Volume) = 0.5 exactly corresponds to Test # 1 Volume kn5 = 0.5 (balance = 0.0)
    Test # 3 Volume kn21 = 1.0 (balance = 0.0) exactly corresponds to Test # 2 Volume kn21 = 1.0 (balance = 0.0)
    The remaining buttons (19 of 21) in test # 3 through function updateParams (newGain, newPan) with standard Web Audio Api nodes - GainNode and StereoPannerNode in Mozilla browser practically do not work. (Through Google Chrome and Opera browsers - all test # 3 buttons work without flaw)!

  4. when listening attentively through the headphones, it turned out that the Panorama (balance) values in Test # 3 for all buttons (points) except the extreme and center (kn21, kn11, kn1) do not match the Panorama (balance) values for the corresponding buttons ( Points) in Test # 2 !!!
    Hence - a confusing, unpleasant physical audio sensation when listening to test # 3 through Mozilla, regardless of the aesthetic beauty of the music itself presented in the sample. Simply put, the latest version of the Mozilla browser works so test # 3 in our simple example that it really spoils the impression of listening to the music itself, destroys its core - the very sound idea inherent in Music by the composer and her performer.

Conclusions:

  1. since Testing sample.mp3 was conducted from the Internet on several independent from each other PC from under Windows7 and Windows 8.1., And the result is the same - it means it’s not the drivers;
  2. the code of the example itself is written correctly, without errors - because Browsers GoogleChrome and Opera work it out reliably, without any failures.
  3. if the nodes GainNode, StereoPannerNode, and function updateParams (newGain, newPan) were written with some kind of defect - it would inevitably be heard through Google Chrome and Opera, as well as through Mozilla. But they work out the work of GainNode, StereoPannerNode and function updateParams (newGain, newPan) according to the code of our example is almost perfect.
    Most likely it is in the Mozilla browser itself - in its incorrect working out of the interaction of the standard Web Audio nodes Api - GainNode, StereoPannerNode and function updateParams (newGain, newPan).
    Below is what you asked, Chris is our example, adapted for testing (It took some time to again double-check everything and write down). You can check everything by simply copying it and running it through browsers. Note: it is important to test the example on the Internet, on some kind of work site. Better with headphones. And - samle.mp3 should be sure to stereo and the audio center of this stereo-sample should immediately be clearly determined by ear.

The code of our example for Test:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>WAA test ( chrismills b-ke )</title>
</head>

</head>
<body>
  <input type = "button" id="btn1" value = "Play sample with volume = 0.5 and balance = 0.0">
  <input type = "button" id="btn2" value = "Update1 volume to 0.7 and balance to +0.9">
  <input type = "button" id="btn3" value = "Update2 volume to 0.9 and balance to -0.9">

<p></p>
//---------VOLUME = from 0.1 to to 1.0---&---PANORAMA(balance) = constant = 0.0----->
<p></p>
  <input type = "button" id="btnV01P00" value = "Test#1 kn1 Update volume to 0.1 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV02P00" value = "Test#1 kn2 Update volume to 0.2 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV03P00" value = "Test#1 kn3 Update volume to 0.3 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV04P00" value = "Test#1 kn4 Update volume to 0.4 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV05P00" value = "Test#1 kn5 Update volume to 0.5 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV06P00" value = "Test#1 kn6 Update volume to 0.6 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV07P00" value = "Test#1 kn7 Update volume to 0.7 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV08P00" value = "Test#1 kn8 Update volume to 0.8 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV09P00" value = "Test#1 kn9 Update volume to 0.9 and balance = 0.0">
<p></p>
  <input type = "button" id="btnV10P00" value = "Test#1 kn10 Update volume to 1.0 and balance = 0.0">

<p></p>
//---------PANORAMA(balance) = from -1.0 to 0.0 to +1.0---&---VOLUME = constant = 1.0----->
<p></p>
  <input type = "button" id="btnPL10V10" value = "Test#2 kn1 volume = 1.0 and Update balance to -1.0">
<p></p>
  <input type = "button" id="btnPL09V10" value = "Test#2 kn2 volume = 1.0 and Update balance to -0.9">
<p></p>
  <input type = "button" id="btnPL08V10" value = "Test#2 kn3 volume = 1.0 and Update balance to -0.8">
<p></p>
  <input type = "button" id="btnPL07V10" value = "Test#2 kn4 volume = 1.0 and Update balance to -0.7">
<p></p>
  <input type = "button" id="btnPL06V10" value = "Test#2 kn5 volume = 1.0 and Update balance to -0.6">
<p></p>
  <input type = "button" id="btnPL05V10" value = "Test#2 kn6 volume = 1.0 and Update balance to -0.5">
<p></p>
  <input type = "button" id="btnPL04V10" value = "Test#2 kn7 volume = 1.0 and Update balance to -0.4">
<p></p>
  <input type = "button" id="btnPL03V10" value = "Test#2 kn8 volume = 1.0 and Update balance to -0.3">
<p></p>
  <input type = "button" id="btnPL02V10" value = "Test#2 kn9 volume = 1.0 and Update balance to -0.2">
<p></p>
  <input type = "button" id="btnPL01V10" value = "Test#2 kn10 volume = 1.0 and Update balance to -0.1">
<p></p>

  <input type = "button" id="btnPL00V10" value = "Test#2 kn11(Middle Panorama) volume = 1.0 and Update balance to 0.0">
<p></p>

  <input type = "button" id="btnPR01V10" value = "Test#2 kn12 volume = 1.0 and Update balance to 0.1">
<p></p>
  <input type = "button" id="btnPR02V10" value = "Test#2 kn13 volume = 1.0 and Update balance to 0.2">
<p></p>
  <input type = "button" id="btnPR03V10" value = "Test#2 kn14 volume = 1.0 and Update balance to 0.3">
<p></p>
  <input type = "button" id="btnPR04V10" value = "Test#2 kn15 volume = 1.0 and Update balance to 0.4">
<p></p>
  <input type = "button" id="btnPR05V10" value = "Test#2 kn16 volume = 1.0 and Update balance to 0.5">
<p></p>
  <input type = "button" id="btnPR06V10" value = "Test#2 kn17 volume = 1.0 and Update balance to 0.6">
<p></p>
  <input type = "button" id="btnPR07V10" value = "Test#2 kn18 volume = 1.0 and Update balance to 0.7">
<p></p>
  <input type = "button" id="btnPR08V10" value = "Test#2 kn19 volume = 1.0 and Update balance to 0.8">
<p></p>
  <input type = "button" id="btnPR09V10" value = "Test#2 kn20 volume = 1.0 and Update balance to 0.9">
<p></p>
  <input type = "button" id="btnPR10V10" value = "Test#2 kn21 volume = 1.0 and Update balance to 1.0">
<p></p>

<p></p>
//!Simultaneously----VOLUME = from 0.1 to to 1.0--&--PANORAMA(balance) = from -1.0 to 0.0 to 1.0--->
<p></p>
  <input type = "button" id="btnV01PL10" value = "Test#3 kn1 Update volume = 0.1 and balance to -1.0">
<p></p>
  <input type = "button" id="btnV01PL09" value = "Test#3 kn2 Update volume = 0.1 and balance to -0.9">
<p></p>
  <input type = "button" id="btnV02PL08" value = "Test#3 kn3 Update volume = 0.2 and balance to -0.8">
<p></p>
  <input type = "button" id="btnV02PL07" value = "Test#3 kn4 Update volume = 0.2 and balance to -0.7">
<p></p>
  <input type = "button" id="btnV03PL06" value = "Test#3 kn5 Update volume = 0.3 and balance to -0.6">
<p></p>
  <input type = "button" id="btnV03PL05" value = "Test#3 kn6 Update volume = 0.3 and balance to -0.5">
<p></p>
  <input type = "button" id="btnV04PL04" value = "Test#3 kn7 Update volume = 0.4 and balance to -0.4">
<p></p>
  <input type = "button" id="btnV04PL03" value = "Test#3 kn8 Update volume = 0.4 and balance to -0.3">
<p></p>
  <input type = "button" id="btnV04PL02" value = "Test#3 kn9 Update volume = 0.4 and balance to -0.2">
<p></p>
  <input type = "button" id="btnV05PL01" value = "Test#3 kn10 Update volume = 0.5 and balance to -0.1">
<p></p>

  <input type = "button" id="btnV05PL00" value = "Test#3 kn11(Middle Volume & Middle Panorama) Update volume = 0.5 and balance to 0.0">
<p></p>

  <input type = "button" id="btnV06PR01" value = "Test#3 kn12 Update volume = 0.6 and balance to 0.1">
<p></p>
  <input type = "button" id="btnV06PR02" value = "Test#3 kn13 Update volume = 0.6 and balance to 0.2">
<p></p>
  <input type = "button" id="btnV07PR03" value = "Test#3 kn14 Update volume = 0.7 and balance to 0.3">
<p></p>
  <input type = "button" id="btnV07PR04" value = "Test#3 kn15 Update volume = 0.7 and balance to 0.4">
<p></p>
  <input type = "button" id="btnV08PR05" value = "Test#3 kn16 Update volume = 0.8 and balance to 0.5">
<p></p>
  <input type = "button" id="btnV08PR06" value = "Test#3 kn17 Update volume = 0.8 and balance to 0.6">
<p></p>
  <input type = "button" id="btnV09PR07" value = "Test#3 kn18 Update volume = 0.9 and balance to 0.7">
<p></p>
  <input type = "button" id="btnV09PR08" value = "Test#3 kn19 Update volume = 0.9 and balance to 0.8">
<p></p>
  <input type = "button" id="btnV10PR09" value = "Test#3 kn20 Update volume = 1.0 and balance to 0.9">
<p></p>
  <input type = "button" id="btnV10PR10" value = "Test#3 kn21 Update volume = 1.0 and balance to 1.0">
<p></p>


<script type="text/javascript">
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
var sample = "music.mp3";

  var panner, gainNode;
   
  function play( snd, vol, balance ) {
    var audioContext = new AudioContext();
    audioContext.listener.setPosition( 0, 0, 0 );
    
    var request = new XMLHttpRequest();
    request.open( "GET", snd, true );
    request.responseType = "arraybuffer";
    request.onload = function () {
	    audioData = request.response;
	    audioContext.decodeAudioData(
	    audioData,
	    function ( buffer ) {
 
        // create source with source global variable
  	    source = audioContext.createBufferSource();
  	    source.buffer = buffer;
 
        // create GainNode with gainNode global variable
  	    gainNode = audioContext.createGain();
 
        // create StereoPannerNode with the panner global variable
  	    panner = audioContext.createStereoPanner();
 
        // connect the source and nodes in the chain
  	    source.connect( gainNode );
  	    gainNode.connect( panner );
 
        gainNode.gain.value = vol;
        panner.pan.value = balance;
 
  	    panner.connect( audioContext.destination );
        source.start( 0 );
      },
 
      function ( e ) {
        alert( "Error with decoding audio data" + e.err );
      });
    };
    request.send();
  }
 
  function updateParams (newGain, newPan) {
    gainNode.gain.value = newGain;
    panner.pan.value = newPan;
  };
   
  btn1.onclick = function() {
    play(sample, 0.5, 0.0);
  }
  btn2.onclick = function() {
    updateParams(0.7, 0.9);
  }
  btn3.onclick = function() {
    updateParams(0.9, -0.9);
  }

//---------VOLUME = from 0.1 to to 1.0---&---PANORAMA(balance) = constant = 0.0----->
  btnV01P00.onclick = function() {
    updateParams(0.1, 0.0);
  }
  btnV02P00.onclick = function() {
    updateParams(0.2, 0.0);
  }
  btnV03P00.onclick = function() {
    updateParams(0.3, 0.0);
  }
  btnV04P00.onclick = function() {
    updateParams(0.4, 0.0);
  }
  btnV05P00.onclick = function() {
    updateParams(0.5, 0.0);
  }
  btnV06P00.onclick = function() {
    updateParams(0.6, 0.0);
  }
  btnV07P00.onclick = function() {
    updateParams(0.7, 0.0);
  }
  btnV08P00.onclick = function() {
    updateParams(0.8, 0.0);
  }
  btnV09P00.onclick = function() {
    updateParams(0.9, 0.0);
  }
  btnV10P00.onclick = function() {
    updateParams(1.0, 0.0);
  }

//---------PANORAMA(balance) = from -1.0 to 0.0 to +1.0---&---VOLUME = constant = 1.0----->
  btnPL10V10.onclick = function() {
    updateParams(1.0, -1.0);
  }
  btnPL09V10.onclick = function() {
    updateParams(1.0, -0.9);
  }
  btnPL08V10.onclick = function() {
    updateParams(1.0, -0.8);
  }
  btnPL07V10.onclick = function() {
    updateParams(1.0, -0.7);
  }
  btnPL06V10.onclick = function() {
    updateParams(1.0, -0.6);
  }
  btnPL05V10.onclick = function() {
    updateParams(1.0, -0.5);
  }
  btnPL04V10.onclick = function() {
    updateParams(1.0, -0.4);
  }
  btnPL03V10.onclick = function() {
    updateParams(1.0, -0.3);
  }
  btnPL02V10.onclick = function() {
    updateParams(1.0, -0.2);
  }
  btnPL01V10.onclick = function() {
    updateParams(1.0, -0.1);
  }

  btnPL00V10.onclick = function() {
    updateParams(1.0, 0.0);
  }

  btnPR01V10.onclick = function() {
    updateParams(1.0, 0.1);
  }
  btnPR02V10.onclick = function() {
    updateParams(1.0, 0.2);
  }
  btnPR03V10.onclick = function() {
    updateParams(1.0, 0.3);
  }
  btnPR04V10.onclick = function() {
    updateParams(1.0, 0.4);
  }
  btnPR05V10.onclick = function() {
    updateParams(1.0, 0.5);
  }
  btnPR06V10.onclick = function() {
    updateParams(1.0, 0.6);
  }
  btnPR07V10.onclick = function() {
    updateParams(1.0, 0.7);
  }
  btnPR08V10.onclick = function() {
    updateParams(1.0, 0.8);
  }
  btnPR09V10.onclick = function() {
    updateParams(1.0, 0.9);
  }
  btnPR10V10.onclick = function() {
    updateParams(1.0, 1.0);
  }

//!Simultaneously----VOLUME = from 0.1 to to 1.0--&--PANORAMA(balance) = from -1.0 to 0.0 to 1.0--->
  btnV01PL10.onclick = function() {
    updateParams(0.1, -1.0);
  }
  btnV01PL09.onclick = function() {
    updateParams(0.1, -0.9);
  }
  btnV02PL08.onclick = function() {
    updateParams(0.2, -0.8);
  }
  btnV02PL07.onclick = function() {
    updateParams(0.2, -0.7);
  }
  btnV03PL06.onclick = function() {
    updateParams(0.3, -0.6);
  }
  btnV03PL05.onclick = function() {
    updateParams(0.3, -0.5);
  }
  btnV04PL04.onclick = function() {
    updateParams(0.4, -0.4);
  }
  btnV04PL03.onclick = function() {
    updateParams(0.4, -0.3);
  }
  btnV04PL02.onclick = function() {
    updateParams(0.4, -0.2);
  }
  btnV05PL01.onclick = function() {
    updateParams(0.5, -0.1);
  }

  btnV05PL00.onclick = function() {
    updateParams(0.5, 0.0);
  }

  btnV06PR01.onclick = function() {
    updateParams(0.6, 0.1);
  }
  btnV06PR02.onclick = function() {
    updateParams(0.6, 0.2);
  }
  btnV07PR03.onclick = function() {
    updateParams(0.7, 0.3);
  }
  btnV07PR04.onclick = function() {
    updateParams(0.7, 0.4);
  }
  btnV08PR05.onclick = function() {
    updateParams(0.8, 0.5);
  }
  btnV08PR06.onclick = function() {
    updateParams(0.8, 0.6);
  }
  btnV09PR07.onclick = function() {
    updateParams(0.9, 0.7);
  }
  btnV09PR08.onclick = function() {
    updateParams(0.9, 0.8);
  }
  btnV10PR09.onclick = function() {
    updateParams(1.0, 0.9);
  }
  btnV10PR10.onclick = function() {
    updateParams(1.0, 1.0);
  }
 
</script>

</body>
</html>

I have a request to you, Chris - after all of the above you check yourself - if possible, please pass this test example to the developers of the Mozilla browser - let them carefully check everything themselves, as we tested - and making sure that The truth is, they will try to find a failure in the browser, and reliably eliminate it so that in one of the future, new versions of Mozilla, this malfunction no longer exists, so that all who use the Mozilla browser are not tormented by using interesting and promising Web Audio Api development MDN, But sincerely rejoiced. As it seems to me, the technical, technological, software-code relationships between Mozilla and MDN should be an example for developers of other browsers, not for turnover.

Chris, if you do not mind, we’ll talk further about the further development of the code of our example with respect to its work, not with one but with several stereo-samples.mp3, as well as about timers and stereo-samples, and try to look into Web Audio Api even deeper.
So far, I’m not strong at Web Audio Api, but I’m persistent in testing. Thanks to your specific tips and detailed explanations of the work of Web Audio Api, I’m learning in practice, and I think that I’m not the only one. Thank you for this!

Yours faithfully,
Boris-KE.