b=907986 normalize orientation vectors early and keep existing state if directions are undefined r=padenot

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.

--HG--
extra : transplant_source : %DA%C7e%E7%90%14%AF%EA%08%94x%C1%A2g%F1%9A%EE%16%EB%29
This commit is contained in:
Karl Tomlinson 2013-09-04 21:20:59 +12:00
parent b88e8922d4
commit 2a2ab79432
6 changed files with 74 additions and 53 deletions

View File

@ -19,8 +19,8 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioListener, Release)
AudioListener::AudioListener(AudioContext* aContext) AudioListener::AudioListener(AudioContext* aContext)
: mContext(aContext) : mContext(aContext)
, mPosition() , mPosition()
, mOrientation(0., 0., -1.) , mFrontVector(0., 0., -1.)
, mUpVector(0., 1., 0.) , mRightVector(1., 0., 0.)
, mVelocity() , mVelocity()
, mDopplerFactor(1.) , mDopplerFactor(1.)
, mSpeedOfSound(343.3) // meters/second , mSpeedOfSound(343.3) // meters/second
@ -35,6 +35,40 @@ AudioListener::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
return AudioListenerBinding::Wrap(aCx, aScope, this); 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 void
AudioListener::RegisterPannerNode(PannerNode* aPannerNode) AudioListener::RegisterPannerNode(PannerNode* aPannerNode)
{ {
@ -42,8 +76,8 @@ AudioListener::RegisterPannerNode(PannerNode* aPannerNode)
// Let the panner node know about our parameters // Let the panner node know about our parameters
aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_POSITION, mPosition); aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_POSITION, mPosition);
aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_ORIENTATION, mOrientation); aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_FRONT_VECTOR, mFrontVector);
aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_UPVECTOR, mUpVector); aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_RIGHT_VECTOR, mRightVector);
aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_VELOCITY, mVelocity); aPannerNode->SendThreeDPointParameterToStream(PannerNode::LISTENER_VELOCITY, mVelocity);
aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_DOPPLER_FACTOR, mDopplerFactor); aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_DOPPLER_FACTOR, mDopplerFactor);
aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_SPEED_OF_SOUND, mSpeedOfSound); aPannerNode->SendDoubleParameterToStream(PannerNode::LISTENER_SPEED_OF_SOUND, mSpeedOfSound);

View File

@ -84,25 +84,7 @@ public:
} }
void SetOrientation(double aX, double aY, double aZ, void SetOrientation(double aX, double aY, double aZ,
double aXUp, double aYUp, double aZUp) 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);
}
const ThreeDPoint& Velocity() const const ThreeDPoint& Velocity() const
{ {
@ -135,8 +117,8 @@ private:
friend class PannerNode; friend class PannerNode;
nsRefPtr<AudioContext> mContext; nsRefPtr<AudioContext> mContext;
ThreeDPoint mPosition; ThreeDPoint mPosition;
ThreeDPoint mOrientation; ThreeDPoint mFrontVector;
ThreeDPoint mUpVector; ThreeDPoint mRightVector;
ThreeDPoint mVelocity; ThreeDPoint mVelocity;
double mDopplerFactor; double mDopplerFactor;
double mSpeedOfSound; double mSpeedOfSound;

View File

@ -109,8 +109,8 @@ public:
{ {
switch (aIndex) { switch (aIndex) {
case PannerNode::LISTENER_POSITION: mListenerPosition = aParam; break; case PannerNode::LISTENER_POSITION: mListenerPosition = aParam; break;
case PannerNode::LISTENER_ORIENTATION: mListenerOrientation = aParam; break; case PannerNode::LISTENER_FRONT_VECTOR: mListenerFrontVector = aParam; break;
case PannerNode::LISTENER_UPVECTOR: mListenerUpVector = aParam; break; case PannerNode::LISTENER_RIGHT_VECTOR: mListenerRightVector = aParam; break;
case PannerNode::LISTENER_VELOCITY: mListenerVelocity = aParam; break; case PannerNode::LISTENER_VELOCITY: mListenerVelocity = aParam; break;
case PannerNode::POSITION: mPosition = aParam; break; case PannerNode::POSITION: mPosition = aParam; break;
case PannerNode::ORIENTATION: mOrientation = aParam; break; case PannerNode::ORIENTATION: mOrientation = aParam; break;
@ -178,8 +178,8 @@ public:
double mConeOuterAngle; double mConeOuterAngle;
double mConeOuterGain; double mConeOuterGain;
ThreeDPoint mListenerPosition; ThreeDPoint mListenerPosition;
ThreeDPoint mListenerOrientation; ThreeDPoint mListenerFrontVector;
ThreeDPoint mListenerUpVector; ThreeDPoint mListenerRightVector;
ThreeDPoint mListenerVelocity; ThreeDPoint mListenerVelocity;
double mListenerDopplerFactor; double mListenerDopplerFactor;
double mListenerSpeedOfSound; double mListenerSpeedOfSound;
@ -376,7 +376,7 @@ PannerNodeEngine::DistanceAndConeGain(AudioChunk* aChunk, float aGain)
AudioBufferInPlaceScale(samples, channelCount, aGain); AudioBufferInPlaceScale(samples, channelCount, aGain);
} }
// This algorithm is specicied in the webaudio spec. // This algorithm is specified in the webaudio spec.
void void
PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation) PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
{ {
@ -391,14 +391,9 @@ PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
sourceListener.Normalize(); sourceListener.Normalize();
// Project the source-listener vector on the x-z plane. // Project the source-listener vector on the x-z plane.
ThreeDPoint& listenerFront = mListenerOrientation; const ThreeDPoint& listenerFront = mListenerFrontVector;
ThreeDPoint listenerRightNorm = listenerFront.CrossProduct(mListenerUpVector); const ThreeDPoint& listenerRight = mListenerRightVector;
listenerRightNorm.Normalize(); ThreeDPoint up = listenerRight.CrossProduct(listenerFront);
ThreeDPoint listenerFrontNorm(listenerFront);
listenerFrontNorm.Normalize();
ThreeDPoint up = listenerRightNorm.CrossProduct(listenerFrontNorm);
double upProjection = sourceListener.DotProduct(up); double upProjection = sourceListener.DotProduct(up);
@ -406,11 +401,11 @@ PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
projectedSource.Normalize(); projectedSource.Normalize();
// Actually compute the angle, and convert to degrees // Actually compute the angle, and convert to degrees
double projection = projectedSource.DotProduct(listenerRightNorm); double projection = projectedSource.DotProduct(listenerRight);
aAzimuth = 180 * acos(projection) / M_PI; aAzimuth = 180 * acos(projection) / M_PI;
// Compute whether the source is in front or behind the listener. // Compute whether the source is in front or behind the listener.
double frontBack = projectedSource.DotProduct(listenerFrontNorm); double frontBack = projectedSource.DotProduct(listenerFront);
if (frontBack < 0) { if (frontBack < 0) {
aAzimuth = 360 - aAzimuth; aAzimuth = 360 - aAzimuth;
} }
@ -444,11 +439,8 @@ PannerNodeEngine::ComputeConeGain()
ThreeDPoint sourceToListener = mListenerPosition - mPosition; ThreeDPoint sourceToListener = mListenerPosition - mPosition;
sourceToListener.Normalize(); sourceToListener.Normalize();
ThreeDPoint normalizedSourceOrientation = mOrientation;
normalizedSourceOrientation.Normalize();
// Angle between the source orientation vector and the source-listener vector // 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 angle = 180 * acos(dotProduct) / M_PI;
double absAngle = fabs(angle); double absAngle = fabs(angle);

View File

@ -120,14 +120,14 @@ public:
void SetOrientation(double aX, double aY, double aZ) void SetOrientation(double aX, double aY, double aZ)
{ {
if (WebAudioUtils::FuzzyEqual(mOrientation.x, aX) && ThreeDPoint orientation(aX, aY, aZ);
WebAudioUtils::FuzzyEqual(mOrientation.y, aY) && if (!orientation.IsZero()) {
WebAudioUtils::FuzzyEqual(mOrientation.z, aZ)) { orientation.Normalize();
}
if (mOrientation.FuzzyEqual(orientation)) {
return; return;
} }
mOrientation.x = aX; mOrientation = orientation;
mOrientation.y = aY;
mOrientation.z = aZ;
SendThreeDPointParameterToStream(ORIENTATION, mOrientation); SendThreeDPointParameterToStream(ORIENTATION, mOrientation);
} }
@ -233,15 +233,15 @@ private:
friend class PannerNodeEngine; friend class PannerNodeEngine;
enum EngineParameters { enum EngineParameters {
LISTENER_POSITION, LISTENER_POSITION,
LISTENER_ORIENTATION, LISTENER_FRONT_VECTOR, // unit length
LISTENER_UPVECTOR, LISTENER_RIGHT_VECTOR, // unit length, orthogonal to LISTENER_FRONT_VECTOR
LISTENER_VELOCITY, LISTENER_VELOCITY,
LISTENER_DOPPLER_FACTOR, LISTENER_DOPPLER_FACTOR,
LISTENER_SPEED_OF_SOUND, LISTENER_SPEED_OF_SOUND,
PANNING_MODEL, PANNING_MODEL,
DISTANCE_MODEL, DISTANCE_MODEL,
POSITION, POSITION,
ORIENTATION, ORIENTATION, // unit length or zero
VELOCITY, VELOCITY,
REF_DISTANCE, REF_DISTANCE,
MAX_DISTANCE, MAX_DISTANCE,

View File

@ -9,11 +9,20 @@
*/ */
#include "ThreeDPoint.h" #include "ThreeDPoint.h"
#include "WebAudioUtils.h"
namespace mozilla { namespace mozilla {
namespace dom { 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) ThreeDPoint operator-(const ThreeDPoint& lhs, const ThreeDPoint& rhs)
{ {
return ThreeDPoint(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); return ThreeDPoint(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);

View File

@ -68,6 +68,10 @@ struct ThreeDPoint {
{ {
return x == 0 && y == 0 && z == 0; 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; double x, y, z;
private: private: