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
--- 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>