Bug 957024 - Update PannerNode distance calculations to match the web audio spec; r=karlt draft
authorDan Minor <dminor@mozilla.com>
Tue, 16 Aug 2016 11:07:12 -0400
changeset 426479 5697b44478ac17cd5f7bc2635ef87b09929d8d2d
parent 425967 94b0fddf96b43942bdd851a3275042909ea37e09
child 534169 5f2fdc0dc65089383a3c3972eccfd22e5fad0e31
push id32698
push userdminor@mozilla.com
push dateTue, 18 Oct 2016 12:26:18 +0000
reviewerskarlt
bugs957024
milestone52.0a1
Bug 957024 - Update PannerNode distance calculations to match the web audio spec; r=karlt Distances less than refDistance should be treated as refDistance when calculating the gain. MozReview-Commit-ID: JASGb7jLp5L
dom/media/webaudio/PannerNode.cpp
dom/media/webaudio/test/mochitest.ini
dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html
--- a/dom/media/webaudio/PannerNode.cpp
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -229,24 +229,24 @@ public:
   bool IsActive() const override
   {
     return mLeftOverData != INT_MIN;
   }
 
   void ComputeAzimuthAndElevation(const ThreeDPoint& position, float& aAzimuth, float& aElevation);
   float ComputeConeGain(const ThreeDPoint& position, const ThreeDPoint& orientation);
   // Compute how much the distance contributes to the gain reduction.
-  float ComputeDistanceGain(const ThreeDPoint& position);
+  double ComputeDistanceGain(const ThreeDPoint& position);
 
   void EqualPowerPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput, StreamTime tick);
   void HRTFPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput, StreamTime tick);
 
-  float LinearGainFunction(float aDistance);
-  float InverseGainFunction(float aDistance);
-  float ExponentialGainFunction(float aDistance);
+  float LinearGainFunction(double aDistance);
+  float InverseGainFunction(double aDistance);
+  float ExponentialGainFunction(double aDistance);
 
   ThreeDPoint ConvertAudioParamTimelineTo3DP(AudioParamTimeline& aX, AudioParamTimeline& aY, AudioParamTimeline& aZ, StreamTime& tick);
 
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
     if (mHRTFPanner) {
       amount += mHRTFPanner->sizeOfIncludingThis(aMallocSizeOf);
@@ -262,17 +262,17 @@ public:
 
   AudioNodeStream* mDestination;
   // This member is set on the main thread, but is not accessed on the rendering
   // thread untile mPanningModelFunction has changed, and this happens strictly
   // later, via a MediaStreamGraph ControlMessage.
   nsAutoPtr<HRTFPanner> mHRTFPanner;
   typedef void (PannerNodeEngine::*PanningModelFunction)(const AudioBlock& aInput, AudioBlock* aOutput, StreamTime tick);
   PanningModelFunction mPanningModelFunction;
-  typedef float (PannerNodeEngine::*DistanceModelFunction)(float aDistance);
+  typedef float (PannerNodeEngine::*DistanceModelFunction)(double aDistance);
   DistanceModelFunction mDistanceModelFunction;
   AudioParamTimeline mPositionX;
   AudioParamTimeline mPositionY;
   AudioParamTimeline mPositionZ;
   AudioParamTimeline mOrientationX;
   AudioParamTimeline mOrientationY;
   AudioParamTimeline mOrientationZ;
   ThreeDPoint mVelocity;
@@ -365,31 +365,31 @@ void PannerNode::DestroyMediaStream()
   if (Context()) {
     Context()->UnregisterPannerNode(this);
   }
   AudioNode::DestroyMediaStream();
 }
 
 // Those three functions are described in the spec.
 float
-PannerNodeEngine::LinearGainFunction(float aDistance)
+PannerNodeEngine::LinearGainFunction(double aDistance)
 {
-  return 1 - mRolloffFactor * (aDistance - mRefDistance) / (mMaxDistance - mRefDistance);
+  return 1 - mRolloffFactor * (std::max(std::min(aDistance, mMaxDistance), mRefDistance) - mRefDistance) / (mMaxDistance - mRefDistance);
 }
 
 float
-PannerNodeEngine::InverseGainFunction(float aDistance)
+PannerNodeEngine::InverseGainFunction(double aDistance)
 {
-  return mRefDistance / (mRefDistance + mRolloffFactor * (aDistance - mRefDistance));
+  return mRefDistance / (mRefDistance + mRolloffFactor * (std::max(aDistance, mRefDistance) - mRefDistance));
 }
 
 float
-PannerNodeEngine::ExponentialGainFunction(float aDistance)
+PannerNodeEngine::ExponentialGainFunction(double aDistance)
 {
-  return pow(aDistance / mRefDistance, -mRolloffFactor);
+  return pow(std::max(aDistance, mRefDistance) / mRefDistance, -mRolloffFactor);
 }
 
 void
 PannerNodeEngine::HRTFPanningFunction(const AudioBlock& aInput,
                                       AudioBlock* aOutput,
                                       StreamTime tick)
 {
   // The output of this node is always stereo, no matter what the inputs are.
@@ -678,17 +678,17 @@ PannerNodeEngine::ComputeConeGain(const 
     // inner -> outer, x goes from 0 -> 1
     double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle);
     gain = (1 - x) + mConeOuterGain * x;
   }
 
   return gain;
 }
 
-float
+double
 PannerNodeEngine::ComputeDistanceGain(const ThreeDPoint& position)
 {
   ThreeDPoint distanceVec = position - mListenerPosition;
   float distance = sqrt(distanceVec.DotProduct(distanceVec));
   return std::max(0.0f, (this->*mDistanceModelFunction)(distance));
 }
 
 float
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -168,16 +168,17 @@ skip-if = (toolkit == 'gonk' && !debug) 
 [test_oscillatorNode2.html]
 [test_oscillatorNodeNegativeFrequency.html]
 [test_oscillatorNodePassThrough.html]
 [test_oscillatorNodeStart.html]
 [test_oscillatorTypeChange.html]
 [test_pannerNode.html]
 [test_pannerNode_equalPower.html]
 [test_pannerNodeAbove.html]
+[test_pannerNodeAtZeroDistance.html]
 [test_pannerNodeChannelCount.html]
 [test_pannerNodeHRTFSymmetry.html]
 [test_pannerNodeTail.html]
 [test_pannerNode_maxDistance.html]
 [test_stereoPannerNode.html]
 [test_stereoPannerNodePassThrough.html]
 [test_periodicWave.html]
 [test_periodicWaveDisableNormalization.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test PannerNode produces output even when the even when the distance is from the listener is zero</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var BUF_SIZE = 128;
+
+var types = [
+  "equalpower",
+  "HRTF"
+]
+
+var finished = types.length;
+
+function finish() {
+  if (!--finished) {
+    SimpleTest.finish();
+  }
+}
+
+function test(type) {
+  var ac = new OfflineAudioContext(1, BUF_SIZE, 44100);
+
+  // A sine to be used to fill the buffers
+  function sine(t) {
+    return Math.sin(440 * 2 * Math.PI * t / ac.sampleRate);
+  }
+
+  var monoBuffer = ac.createBuffer(1, BUF_SIZE, ac.sampleRate);
+  for (var i = 0; i < BUF_SIZE; ++i) {
+    monoBuffer.getChannelData(0)[i] = sine(i);
+  }
+
+  var monoSource = ac.createBufferSource();
+  monoSource.buffer = monoBuffer;
+  monoSource.start(0);
+
+  var panner = ac.createPanner();
+  panner.distanceModel = "linear";
+  panner.refDistance = 1;
+  panner.positionX.value = 0;
+  panner.positionY.value = 0;
+  panner.positionZ.value = 0;
+  monoSource.connect(panner);
+
+  var panner2 = ac.createPanner();
+  panner2.distanceModel = "inverse";
+  panner2.refDistance = 1;
+  panner2.positionX.value = 0;
+  panner2.positionY.value = 0;
+  panner2.positionZ.value = 0;
+  panner.connect(panner2);
+
+  var panner3 = ac.createPanner();
+  panner3.distanceModel = "exponential";
+  panner3.refDistance = 1;
+  panner3.positionX.value = 0;
+  panner3.positionY.value = 0;
+  panner3.positionZ.value = 0;
+  panner2.connect(panner3);
+
+  panner3.connect(ac.destination);
+
+  ac.startRendering().then(function(buffer) {
+    var matched = true;
+    var array = buffer.getChannelData(0);
+    for (var i = 0; i < buffer.length; i++) {
+      if (Math.abs(array[i] - monoBuffer[i]) > 1e6) {
+        matched = false;
+        break;
+      }
+    }
+    ok(matched, "The output buffer should match the input.");
+    finish();
+  });
+}
+
+
+addLoadEvent(function() {
+  types.forEach(test);
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+</body>
+</html>