b=907986 normalize orientation vectors early and keep existing state if directions are undefined r=padenot a=webaudio
authorKarl Tomlinson <karlt+@karlt.net>
Wed, 04 Sep 2013 21:20:59 +1200
changeset 153960 b9deaf0ea71e7ab077f5aeaf126b0013b698bc3f
parent 153959 8537c5e949529fac00e5d6c675489bef7363ea06
child 153961 ea6aaf786efd10de39fceeae3b731292ecab56f0
push id2859
push userakeybl@mozilla.com
push dateMon, 16 Sep 2013 19:14:59 +0000
treeherdermozilla-beta@87d3c51cd2bf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot, webaudio
bugs907986
milestone25.0a2
b=907986 normalize orientation vectors early and keep existing state if directions are undefined r=padenot a=webaudio Normalizing the AudioListener front orientation vectors before taking their cross product avoids the possibility of overflow. The panning effect and the azimuth and elevation calculation in the Web Audio spec becomes undefined with linearly dependent listener vectors, so keep existing state in these situations. PannerNode orientation is normalized for consistency, but zero is permitted for this vector because the sound cone algorithm in the Web Audio specifies behavior for this case.
content/media/webaudio/AudioListener.cpp
content/media/webaudio/AudioListener.h
content/media/webaudio/PannerNode.cpp
content/media/webaudio/PannerNode.h
content/media/webaudio/ThreeDPoint.cpp
content/media/webaudio/ThreeDPoint.h
--- a/content/media/webaudio/AudioListener.cpp
+++ b/content/media/webaudio/AudioListener.cpp
@@ -16,41 +16,75 @@ namespace dom {
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_1(AudioListener, mContext)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioListener, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioListener, Release)
 
 AudioListener::AudioListener(AudioContext* aContext)
   : mContext(aContext)
   , mPosition()
-  , mOrientation(0., 0., -1.)
-  , mUpVector(0., 1., 0.)
+  , mFrontVector(0., 0., -1.)
+  , mRightVector(1., 0., 0.)
   , mVelocity()
   , mDopplerFactor(1.)
   , mSpeedOfSound(343.3) // meters/second
 {
   MOZ_ASSERT(aContext);
   SetIsDOMBinding();
 }
 
 JSObject*
 AudioListener::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
 {
   return AudioListenerBinding::Wrap(aCx, aScope, this);
 }
 
 void
+AudioListener::SetOrientation(double aX, double aY, double aZ,
+                              double aXUp, double aYUp, double aZUp)
+{
+  ThreeDPoint front(aX, aY, aZ);
+  // The panning effect and the azimuth and elevation calculation in the Web
+  // Audio spec becomes undefined with linearly dependent vectors, so keep
+  // existing state in these situations.
+  if (front.IsZero()) {
+    return;
+  }
+  // Normalize before using CrossProduct() to avoid overflow.
+  front.Normalize();
+  ThreeDPoint up(aXUp, aYUp, aZUp);
+  if (up.IsZero()) {
+    return;
+  }
+  up.Normalize();
+  ThreeDPoint right = front.CrossProduct(up);
+  if (right.IsZero()) {
+    return;
+  }
+  right.Normalize();
+
+  if (!mFrontVector.FuzzyEqual(front)) {
+    mFrontVector = front;
+    SendThreeDPointParameterToStream(PannerNode::LISTENER_FRONT_VECTOR, front);
+  }
+  if (!mRightVector.FuzzyEqual(right)) {
+    mRightVector = right;
+    SendThreeDPointParameterToStream(PannerNode::LISTENER_RIGHT_VECTOR, right);
+  }
+}
+
+void
 AudioListener::RegisterPannerNode(PannerNode* aPannerNode)
 {
   mPanners.AppendElement(aPannerNode->asWeakPtr());
 
   // Let the panner node know about our parameters
   aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_POSITION, mPosition);
-  aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_ORIENTATION, mOrientation);
-  aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_UPVECTOR, mUpVector);
+  aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_FRONT_VECTOR, mFrontVector);
+  aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_RIGHT_VECTOR, mRightVector);
   aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_VELOCITY, mVelocity);
   aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_DOPPLER_FACTOR, mDopplerFactor);
   aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_SPEED_OF_SOUND, mSpeedOfSound);
   UpdatePannersVelocity();
 }
 
 void AudioListener::UnregisterPannerNode(PannerNode* aPannerNode)
 {
--- a/content/media/webaudio/AudioListener.h
+++ b/content/media/webaudio/AudioListener.h
@@ -82,35 +82,17 @@ public:
   }
 
   const ThreeDPoint& Position() const
   {
     return mPosition;
   }
 
   void SetOrientation(double aX, double aY, double aZ,
-                      double aXUp, double aYUp, double aZUp)
-  {
-    if (WebAudioUtils::FuzzyEqual(mOrientation.x, aX) &&
-        WebAudioUtils::FuzzyEqual(mOrientation.y, aY) &&
-        WebAudioUtils::FuzzyEqual(mOrientation.z, aZ) &&
-        WebAudioUtils::FuzzyEqual(mUpVector.x, aX) &&
-        WebAudioUtils::FuzzyEqual(mUpVector.y, aY) &&
-        WebAudioUtils::FuzzyEqual(mUpVector.z, aZ)) {
-      return;
-    }
-    mOrientation.x = aX;
-    mOrientation.y = aY;
-    mOrientation.z = aZ;
-    mUpVector.x = aXUp;
-    mUpVector.y = aYUp;
-    mUpVector.z = aZUp;
-    SendThreeDPointParameterToStream(PannerNode::LISTENER_ORIENTATION, mOrientation);
-    SendThreeDPointParameterToStream(PannerNode::LISTENER_UPVECTOR, mUpVector);
-  }
+                      double aXUp, double aYUp, double aZUp);
 
   const ThreeDPoint& Velocity() const
   {
     return mVelocity;
   }
 
   void SetVelocity(double aX, double aY, double aZ)
   {
@@ -133,18 +115,18 @@ private:
   void SendDoubleParameterToStream(uint32_t aIndex, double aValue);
   void SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue);
   void UpdatePannersVelocity();
 
 private:
   friend class PannerNode;
   nsRefPtr<AudioContext> mContext;
   ThreeDPoint mPosition;
-  ThreeDPoint mOrientation;
-  ThreeDPoint mUpVector;
+  ThreeDPoint mFrontVector;
+  ThreeDPoint mRightVector;
   ThreeDPoint mVelocity;
   double mDopplerFactor;
   double mSpeedOfSound;
   nsTArray<WeakPtr<PannerNode> > mPanners;
 };
 
 }
 }
--- a/content/media/webaudio/PannerNode.cpp
+++ b/content/media/webaudio/PannerNode.cpp
@@ -103,18 +103,18 @@ public:
     default:
       NS_ERROR("Bad PannerNodeEngine Int32Parameter");
     }
   }
   virtual void SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aParam) MOZ_OVERRIDE
   {
     switch (aIndex) {
     case PannerNode::LISTENER_POSITION: mListenerPosition = aParam; break;
-    case PannerNode::LISTENER_ORIENTATION: mListenerOrientation = aParam; break;
-    case PannerNode::LISTENER_UPVECTOR: mListenerUpVector = aParam; break;
+    case PannerNode::LISTENER_FRONT_VECTOR: mListenerFrontVector = aParam; break;
+    case PannerNode::LISTENER_RIGHT_VECTOR: mListenerRightVector = aParam; break;
     case PannerNode::LISTENER_VELOCITY: mListenerVelocity = aParam; break;
     case PannerNode::POSITION: mPosition = aParam; break;
     case PannerNode::ORIENTATION: mOrientation = aParam; break;
     case PannerNode::VELOCITY: mVelocity = aParam; break;
     default:
       NS_ERROR("Bad PannerNodeEngine ThreeDPointParameter");
     }
   }
@@ -172,18 +172,18 @@ public:
   ThreeDPoint mVelocity;
   double mRefDistance;
   double mMaxDistance;
   double mRolloffFactor;
   double mConeInnerAngle;
   double mConeOuterAngle;
   double mConeOuterGain;
   ThreeDPoint mListenerPosition;
-  ThreeDPoint mListenerOrientation;
-  ThreeDPoint mListenerUpVector;
+  ThreeDPoint mListenerFrontVector;
+  ThreeDPoint mListenerRightVector;
   ThreeDPoint mListenerVelocity;
   double mListenerDopplerFactor;
   double mListenerSpeedOfSound;
 };
 
 PannerNode::PannerNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
@@ -370,51 +370,46 @@ void
 PannerNodeEngine::DistanceAndConeGain(AudioChunk* aChunk, float aGain)
 {
   float* samples = static_cast<float*>(const_cast<void*>(*aChunk->mChannelData.Elements()));
   uint32_t channelCount = aChunk->mChannelData.Length();
 
   AudioBufferInPlaceScale(samples, channelCount, aGain);
 }
 
-// This algorithm is specicied in the webaudio spec.
+// This algorithm is specified in the webaudio spec.
 void
 PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
 {
   ThreeDPoint sourceListener = mPosition - mListenerPosition;
 
   if (sourceListener.IsZero()) {
     aAzimuth = 0.0;
     aElevation = 0.0;
     return;
   }
 
   sourceListener.Normalize();
 
   // Project the source-listener vector on the x-z plane.
-  ThreeDPoint& listenerFront = mListenerOrientation;
-  ThreeDPoint listenerRightNorm = listenerFront.CrossProduct(mListenerUpVector);
-  listenerRightNorm.Normalize();
-
-  ThreeDPoint listenerFrontNorm(listenerFront);
-  listenerFrontNorm.Normalize();
-
-  ThreeDPoint up = listenerRightNorm.CrossProduct(listenerFrontNorm);
+  const ThreeDPoint& listenerFront = mListenerFrontVector;
+  const ThreeDPoint& listenerRight = mListenerRightVector;
+  ThreeDPoint up = listenerRight.CrossProduct(listenerFront);
 
   double upProjection = sourceListener.DotProduct(up);
 
   ThreeDPoint projectedSource = sourceListener - up * upProjection;
   projectedSource.Normalize();
 
   // Actually compute the angle, and convert to degrees
-  double projection = projectedSource.DotProduct(listenerRightNorm);
+  double projection = projectedSource.DotProduct(listenerRight);
   aAzimuth = 180 * acos(projection) / M_PI;
 
   // Compute whether the source is in front or behind the listener.
-  double frontBack = projectedSource.DotProduct(listenerFrontNorm);
+  double frontBack = projectedSource.DotProduct(listenerFront);
   if (frontBack < 0) {
     aAzimuth = 360 - aAzimuth;
   }
   // Rotate the azimuth so it is relative to the listener front vector instead
   // of the right vector.
   if ((aAzimuth >= 0) && (aAzimuth <= 270)) {
     aAzimuth = 90 - aAzimuth;
   } else {
@@ -438,21 +433,18 @@ PannerNodeEngine::ComputeConeGain()
   if (mOrientation.IsZero() || ((mConeInnerAngle == 360) && (mConeOuterAngle == 360))) {
     return 1;
   }
 
   // Normalized source-listener vector
   ThreeDPoint sourceToListener = mListenerPosition - mPosition;
   sourceToListener.Normalize();
 
-  ThreeDPoint normalizedSourceOrientation = mOrientation;
-  normalizedSourceOrientation.Normalize();
-
   // Angle between the source orientation vector and the source-listener vector
-  double dotProduct = sourceToListener.DotProduct(normalizedSourceOrientation);
+  double dotProduct = sourceToListener.DotProduct(mOrientation);
   double angle = 180 * acos(dotProduct) / M_PI;
   double absAngle = fabs(angle);
 
   // Divide by 2 here since API is entire angle (not half-angle)
   double absInnerAngle = fabs(mConeInnerAngle) / 2;
   double absOuterAngle = fabs(mConeOuterAngle) / 2;
   double gain = 1;
 
--- a/content/media/webaudio/PannerNode.h
+++ b/content/media/webaudio/PannerNode.h
@@ -118,24 +118,24 @@ public:
     mPosition.x = aX;
     mPosition.y = aY;
     mPosition.z = aZ;
     SendThreeDPointParameterToStream(POSITION, mPosition);
   }
 
   void SetOrientation(double aX, double aY, double aZ)
   {
-    if (WebAudioUtils::FuzzyEqual(mOrientation.x, aX) &&
-        WebAudioUtils::FuzzyEqual(mOrientation.y, aY) &&
-        WebAudioUtils::FuzzyEqual(mOrientation.z, aZ)) {
+    ThreeDPoint orientation(aX, aY, aZ);
+    if (!orientation.IsZero()) {
+      orientation.Normalize();
+    }
+    if (mOrientation.FuzzyEqual(orientation)) {
       return;
     }
-    mOrientation.x = aX;
-    mOrientation.y = aY;
-    mOrientation.z = aZ;
+    mOrientation = orientation;
     SendThreeDPointParameterToStream(ORIENTATION, mOrientation);
   }
 
   void SetVelocity(double aX, double aY, double aZ)
   {
     if (WebAudioUtils::FuzzyEqual(mVelocity.x, aX) &&
         WebAudioUtils::FuzzyEqual(mVelocity.y, aY) &&
         WebAudioUtils::FuzzyEqual(mVelocity.z, aZ)) {
@@ -231,25 +231,25 @@ public:
   void FindConnectedSources();
   void FindConnectedSources(AudioNode* aNode, nsTArray<AudioBufferSourceNode*>& aSources, std::set<AudioNode*>& aSeenNodes);
 
 private:
   friend class AudioListener;
   friend class PannerNodeEngine;
   enum EngineParameters {
     LISTENER_POSITION,
-    LISTENER_ORIENTATION,
-    LISTENER_UPVECTOR,
+    LISTENER_FRONT_VECTOR, // unit length
+    LISTENER_RIGHT_VECTOR, // unit length, orthogonal to LISTENER_FRONT_VECTOR
     LISTENER_VELOCITY,
     LISTENER_DOPPLER_FACTOR,
     LISTENER_SPEED_OF_SOUND,
     PANNING_MODEL,
     DISTANCE_MODEL,
     POSITION,
-    ORIENTATION,
+    ORIENTATION, // unit length or zero
     VELOCITY,
     REF_DISTANCE,
     MAX_DISTANCE,
     ROLLOFF_FACTOR,
     CONE_INNER_ANGLE,
     CONE_OUTER_ANGLE,
     CONE_OUTER_GAIN
   };
--- a/content/media/webaudio/ThreeDPoint.cpp
+++ b/content/media/webaudio/ThreeDPoint.cpp
@@ -4,21 +4,30 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
  * Other similar methods can be added if needed.
  */
 
 #include "ThreeDPoint.h"
+#include "WebAudioUtils.h"
 
 namespace mozilla {
 
 namespace dom {
 
+bool
+ThreeDPoint::FuzzyEqual(const ThreeDPoint& other)
+{
+  return WebAudioUtils::FuzzyEqual(x, other.x) &&
+    WebAudioUtils::FuzzyEqual(y, other.y) &&
+    WebAudioUtils::FuzzyEqual(z, other.z);
+}
+
 ThreeDPoint operator-(const ThreeDPoint& lhs, const ThreeDPoint& rhs)
 {
   return ThreeDPoint(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
 }
 
 ThreeDPoint operator*(const ThreeDPoint& lhs, const ThreeDPoint& rhs)
 {
   return ThreeDPoint(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z);
--- a/content/media/webaudio/ThreeDPoint.h
+++ b/content/media/webaudio/ThreeDPoint.h
@@ -63,16 +63,20 @@ struct ThreeDPoint {
   {
     return sqrt(hypot(rhs.x, x) + hypot(rhs.y, y) + hypot(rhs.z, z));
   }
 
   bool IsZero() const
   {
     return x == 0 && y == 0 && z == 0;
   }
+
+  // For comparing two vectors of close to unit magnitude.
+  bool FuzzyEqual(const ThreeDPoint& other);
+
   double x, y, z;
 
 private:
   double MaxNorm() const
   {
     return std::max(fabs(x), std::max(fabs(y), fabs(z)));
   }
 };