content/media/webaudio/PannerNode.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "PannerNode.h"
michael@0 8 #include "AudioNodeEngine.h"
michael@0 9 #include "AudioNodeStream.h"
michael@0 10 #include "AudioListener.h"
michael@0 11 #include "AudioBufferSourceNode.h"
michael@0 12 #include "PlayingRefChangeHandler.h"
michael@0 13 #include "blink/HRTFPanner.h"
michael@0 14 #include "blink/HRTFDatabaseLoader.h"
michael@0 15
michael@0 16 using WebCore::HRTFDatabaseLoader;
michael@0 17 using WebCore::HRTFPanner;
michael@0 18
michael@0 19 namespace mozilla {
michael@0 20 namespace dom {
michael@0 21
michael@0 22 using namespace std;
michael@0 23
michael@0 24 NS_IMPL_CYCLE_COLLECTION_CLASS(PannerNode)
michael@0 25
michael@0 26 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PannerNode)
michael@0 27 if (tmp->Context()) {
michael@0 28 tmp->Context()->UnregisterPannerNode(tmp);
michael@0 29 }
michael@0 30 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
michael@0 31
michael@0 32 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PannerNode, AudioNode)
michael@0 33 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
michael@0 34
michael@0 35 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PannerNode)
michael@0 36 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
michael@0 37
michael@0 38 NS_IMPL_ADDREF_INHERITED(PannerNode, AudioNode)
michael@0 39 NS_IMPL_RELEASE_INHERITED(PannerNode, AudioNode)
michael@0 40
michael@0 41 class PannerNodeEngine : public AudioNodeEngine
michael@0 42 {
michael@0 43 public:
michael@0 44 explicit PannerNodeEngine(AudioNode* aNode)
michael@0 45 : AudioNodeEngine(aNode)
michael@0 46 // Please keep these default values consistent with PannerNode::PannerNode below.
michael@0 47 , mPanningModelFunction(&PannerNodeEngine::HRTFPanningFunction)
michael@0 48 , mDistanceModelFunction(&PannerNodeEngine::InverseGainFunction)
michael@0 49 , mPosition()
michael@0 50 , mOrientation(1., 0., 0.)
michael@0 51 , mVelocity()
michael@0 52 , mRefDistance(1.)
michael@0 53 , mMaxDistance(10000.)
michael@0 54 , mRolloffFactor(1.)
michael@0 55 , mConeInnerAngle(360.)
michael@0 56 , mConeOuterAngle(360.)
michael@0 57 , mConeOuterGain(0.)
michael@0 58 // These will be initialized when a PannerNode is created, so just initialize them
michael@0 59 // to some dummy values here.
michael@0 60 , mListenerDopplerFactor(0.)
michael@0 61 , mListenerSpeedOfSound(0.)
michael@0 62 , mLeftOverData(INT_MIN)
michael@0 63 {
michael@0 64 // HRTFDatabaseLoader needs to be fetched on the main thread.
michael@0 65 TemporaryRef<HRTFDatabaseLoader> loader =
michael@0 66 HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(aNode->Context()->SampleRate());
michael@0 67 mHRTFPanner = new HRTFPanner(aNode->Context()->SampleRate(), loader);
michael@0 68 }
michael@0 69
michael@0 70 virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE
michael@0 71 {
michael@0 72 switch (aIndex) {
michael@0 73 case PannerNode::PANNING_MODEL:
michael@0 74 switch (PanningModelType(aParam)) {
michael@0 75 case PanningModelType::Equalpower:
michael@0 76 mPanningModelFunction = &PannerNodeEngine::EqualPowerPanningFunction;
michael@0 77 break;
michael@0 78 case PanningModelType::HRTF:
michael@0 79 mPanningModelFunction = &PannerNodeEngine::HRTFPanningFunction;
michael@0 80 break;
michael@0 81 default:
michael@0 82 NS_NOTREACHED("We should never see the alternate names here");
michael@0 83 break;
michael@0 84 }
michael@0 85 break;
michael@0 86 case PannerNode::DISTANCE_MODEL:
michael@0 87 switch (DistanceModelType(aParam)) {
michael@0 88 case DistanceModelType::Inverse:
michael@0 89 mDistanceModelFunction = &PannerNodeEngine::InverseGainFunction;
michael@0 90 break;
michael@0 91 case DistanceModelType::Linear:
michael@0 92 mDistanceModelFunction = &PannerNodeEngine::LinearGainFunction;
michael@0 93 break;
michael@0 94 case DistanceModelType::Exponential:
michael@0 95 mDistanceModelFunction = &PannerNodeEngine::ExponentialGainFunction;
michael@0 96 break;
michael@0 97 default:
michael@0 98 NS_NOTREACHED("We should never see the alternate names here");
michael@0 99 break;
michael@0 100 }
michael@0 101 break;
michael@0 102 default:
michael@0 103 NS_ERROR("Bad PannerNodeEngine Int32Parameter");
michael@0 104 }
michael@0 105 }
michael@0 106 virtual void SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aParam) MOZ_OVERRIDE
michael@0 107 {
michael@0 108 switch (aIndex) {
michael@0 109 case PannerNode::LISTENER_POSITION: mListenerPosition = aParam; break;
michael@0 110 case PannerNode::LISTENER_FRONT_VECTOR: mListenerFrontVector = aParam; break;
michael@0 111 case PannerNode::LISTENER_RIGHT_VECTOR: mListenerRightVector = aParam; break;
michael@0 112 case PannerNode::LISTENER_VELOCITY: mListenerVelocity = aParam; break;
michael@0 113 case PannerNode::POSITION: mPosition = aParam; break;
michael@0 114 case PannerNode::ORIENTATION: mOrientation = aParam; break;
michael@0 115 case PannerNode::VELOCITY: mVelocity = aParam; break;
michael@0 116 default:
michael@0 117 NS_ERROR("Bad PannerNodeEngine ThreeDPointParameter");
michael@0 118 }
michael@0 119 }
michael@0 120 virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE
michael@0 121 {
michael@0 122 switch (aIndex) {
michael@0 123 case PannerNode::LISTENER_DOPPLER_FACTOR: mListenerDopplerFactor = aParam; break;
michael@0 124 case PannerNode::LISTENER_SPEED_OF_SOUND: mListenerSpeedOfSound = aParam; break;
michael@0 125 case PannerNode::REF_DISTANCE: mRefDistance = aParam; break;
michael@0 126 case PannerNode::MAX_DISTANCE: mMaxDistance = aParam; break;
michael@0 127 case PannerNode::ROLLOFF_FACTOR: mRolloffFactor = aParam; break;
michael@0 128 case PannerNode::CONE_INNER_ANGLE: mConeInnerAngle = aParam; break;
michael@0 129 case PannerNode::CONE_OUTER_ANGLE: mConeOuterAngle = aParam; break;
michael@0 130 case PannerNode::CONE_OUTER_GAIN: mConeOuterGain = aParam; break;
michael@0 131 default:
michael@0 132 NS_ERROR("Bad PannerNodeEngine DoubleParameter");
michael@0 133 }
michael@0 134 }
michael@0 135
michael@0 136 virtual void ProcessBlock(AudioNodeStream* aStream,
michael@0 137 const AudioChunk& aInput,
michael@0 138 AudioChunk* aOutput,
michael@0 139 bool *aFinished) MOZ_OVERRIDE
michael@0 140 {
michael@0 141 if (aInput.IsNull()) {
michael@0 142 // mLeftOverData != INT_MIN means that the panning model was HRTF and a
michael@0 143 // tail-time reference was added. Even if the model is now equalpower,
michael@0 144 // the reference will need to be removed.
michael@0 145 if (mLeftOverData > 0 &&
michael@0 146 mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
michael@0 147 mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
michael@0 148 } else {
michael@0 149 if (mLeftOverData != INT_MIN) {
michael@0 150 mLeftOverData = INT_MIN;
michael@0 151 mHRTFPanner->reset();
michael@0 152
michael@0 153 nsRefPtr<PlayingRefChangeHandler> refchanged =
michael@0 154 new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
michael@0 155 aStream->Graph()->
michael@0 156 DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
michael@0 157 }
michael@0 158 *aOutput = aInput;
michael@0 159 return;
michael@0 160 }
michael@0 161 } else if (mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
michael@0 162 if (mLeftOverData == INT_MIN) {
michael@0 163 nsRefPtr<PlayingRefChangeHandler> refchanged =
michael@0 164 new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
michael@0 165 aStream->Graph()->
michael@0 166 DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
michael@0 167 }
michael@0 168 mLeftOverData = mHRTFPanner->maxTailFrames();
michael@0 169 }
michael@0 170
michael@0 171 (this->*mPanningModelFunction)(aInput, aOutput);
michael@0 172 }
michael@0 173
michael@0 174 void ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation);
michael@0 175 float ComputeConeGain();
michael@0 176 // Compute how much the distance contributes to the gain reduction.
michael@0 177 float ComputeDistanceGain();
michael@0 178
michael@0 179 void GainMonoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
michael@0 180 float aGainL, float aGainR);
michael@0 181 void GainStereoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
michael@0 182 float aGainL, float aGainR, double aAzimuth);
michael@0 183
michael@0 184 void EqualPowerPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
michael@0 185 void HRTFPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
michael@0 186
michael@0 187 float LinearGainFunction(float aDistance);
michael@0 188 float InverseGainFunction(float aDistance);
michael@0 189 float ExponentialGainFunction(float aDistance);
michael@0 190
michael@0 191 virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
michael@0 192 {
michael@0 193 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
michael@0 194 if (mHRTFPanner) {
michael@0 195 amount += mHRTFPanner->sizeOfIncludingThis(aMallocSizeOf);
michael@0 196 }
michael@0 197
michael@0 198 return amount;
michael@0 199 }
michael@0 200
michael@0 201 virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
michael@0 202 {
michael@0 203 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
michael@0 204 }
michael@0 205
michael@0 206 nsAutoPtr<HRTFPanner> mHRTFPanner;
michael@0 207 typedef void (PannerNodeEngine::*PanningModelFunction)(const AudioChunk& aInput, AudioChunk* aOutput);
michael@0 208 PanningModelFunction mPanningModelFunction;
michael@0 209 typedef float (PannerNodeEngine::*DistanceModelFunction)(float aDistance);
michael@0 210 DistanceModelFunction mDistanceModelFunction;
michael@0 211 ThreeDPoint mPosition;
michael@0 212 ThreeDPoint mOrientation;
michael@0 213 ThreeDPoint mVelocity;
michael@0 214 double mRefDistance;
michael@0 215 double mMaxDistance;
michael@0 216 double mRolloffFactor;
michael@0 217 double mConeInnerAngle;
michael@0 218 double mConeOuterAngle;
michael@0 219 double mConeOuterGain;
michael@0 220 ThreeDPoint mListenerPosition;
michael@0 221 ThreeDPoint mListenerFrontVector;
michael@0 222 ThreeDPoint mListenerRightVector;
michael@0 223 ThreeDPoint mListenerVelocity;
michael@0 224 double mListenerDopplerFactor;
michael@0 225 double mListenerSpeedOfSound;
michael@0 226 int mLeftOverData;
michael@0 227 };
michael@0 228
michael@0 229 PannerNode::PannerNode(AudioContext* aContext)
michael@0 230 : AudioNode(aContext,
michael@0 231 2,
michael@0 232 ChannelCountMode::Clamped_max,
michael@0 233 ChannelInterpretation::Speakers)
michael@0 234 // Please keep these default values consistent with PannerNodeEngine::PannerNodeEngine above.
michael@0 235 , mPanningModel(PanningModelType::HRTF)
michael@0 236 , mDistanceModel(DistanceModelType::Inverse)
michael@0 237 , mPosition()
michael@0 238 , mOrientation(1., 0., 0.)
michael@0 239 , mVelocity()
michael@0 240 , mRefDistance(1.)
michael@0 241 , mMaxDistance(10000.)
michael@0 242 , mRolloffFactor(1.)
michael@0 243 , mConeInnerAngle(360.)
michael@0 244 , mConeOuterAngle(360.)
michael@0 245 , mConeOuterGain(0.)
michael@0 246 {
michael@0 247 mStream = aContext->Graph()->CreateAudioNodeStream(new PannerNodeEngine(this),
michael@0 248 MediaStreamGraph::INTERNAL_STREAM);
michael@0 249 // We should register once we have set up our stream and engine.
michael@0 250 Context()->Listener()->RegisterPannerNode(this);
michael@0 251 }
michael@0 252
michael@0 253 PannerNode::~PannerNode()
michael@0 254 {
michael@0 255 if (Context()) {
michael@0 256 Context()->UnregisterPannerNode(this);
michael@0 257 }
michael@0 258 }
michael@0 259
michael@0 260 size_t
michael@0 261 PannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 262 {
michael@0 263 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
michael@0 264 amount += mSources.SizeOfExcludingThis(aMallocSizeOf);
michael@0 265 return amount;
michael@0 266 }
michael@0 267
michael@0 268 size_t
michael@0 269 PannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
michael@0 270 {
michael@0 271 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
michael@0 272 }
michael@0 273
michael@0 274 JSObject*
michael@0 275 PannerNode::WrapObject(JSContext* aCx)
michael@0 276 {
michael@0 277 return PannerNodeBinding::Wrap(aCx, this);
michael@0 278 }
michael@0 279
michael@0 280 void PannerNode::DestroyMediaStream()
michael@0 281 {
michael@0 282 if (Context()) {
michael@0 283 Context()->UnregisterPannerNode(this);
michael@0 284 }
michael@0 285 AudioNode::DestroyMediaStream();
michael@0 286 }
michael@0 287
michael@0 288 // Those three functions are described in the spec.
michael@0 289 float
michael@0 290 PannerNodeEngine::LinearGainFunction(float aDistance)
michael@0 291 {
michael@0 292 return 1 - mRolloffFactor * (aDistance - mRefDistance) / (mMaxDistance - mRefDistance);
michael@0 293 }
michael@0 294
michael@0 295 float
michael@0 296 PannerNodeEngine::InverseGainFunction(float aDistance)
michael@0 297 {
michael@0 298 return mRefDistance / (mRefDistance + mRolloffFactor * (aDistance - mRefDistance));
michael@0 299 }
michael@0 300
michael@0 301 float
michael@0 302 PannerNodeEngine::ExponentialGainFunction(float aDistance)
michael@0 303 {
michael@0 304 return pow(aDistance / mRefDistance, -mRolloffFactor);
michael@0 305 }
michael@0 306
michael@0 307 void
michael@0 308 PannerNodeEngine::HRTFPanningFunction(const AudioChunk& aInput,
michael@0 309 AudioChunk* aOutput)
michael@0 310 {
michael@0 311 // The output of this node is always stereo, no matter what the inputs are.
michael@0 312 AllocateAudioBlock(2, aOutput);
michael@0 313
michael@0 314 float azimuth, elevation;
michael@0 315 ComputeAzimuthAndElevation(azimuth, elevation);
michael@0 316
michael@0 317 AudioChunk input = aInput;
michael@0 318 // Gain is applied before the delay and convolution of the HRTF
michael@0 319 input.mVolume *= ComputeConeGain() * ComputeDistanceGain();
michael@0 320
michael@0 321 mHRTFPanner->pan(azimuth, elevation, &input, aOutput);
michael@0 322 }
michael@0 323
michael@0 324 void
michael@0 325 PannerNodeEngine::EqualPowerPanningFunction(const AudioChunk& aInput,
michael@0 326 AudioChunk* aOutput)
michael@0 327 {
michael@0 328 float azimuth, elevation, gainL, gainR, normalizedAzimuth, distanceGain, coneGain;
michael@0 329 int inputChannels = aInput.mChannelData.Length();
michael@0 330
michael@0 331 // If both the listener are in the same spot, and no cone gain is specified,
michael@0 332 // this node is noop.
michael@0 333 if (mListenerPosition == mPosition &&
michael@0 334 mConeInnerAngle == 360 &&
michael@0 335 mConeOuterAngle == 360) {
michael@0 336 *aOutput = aInput;
michael@0 337 return;
michael@0 338 }
michael@0 339
michael@0 340 // The output of this node is always stereo, no matter what the inputs are.
michael@0 341 AllocateAudioBlock(2, aOutput);
michael@0 342
michael@0 343 ComputeAzimuthAndElevation(azimuth, elevation);
michael@0 344 coneGain = ComputeConeGain();
michael@0 345
michael@0 346 // The following algorithm is described in the spec.
michael@0 347 // Clamp azimuth in the [-90, 90] range.
michael@0 348 azimuth = min(180.f, max(-180.f, azimuth));
michael@0 349
michael@0 350 // Wrap around
michael@0 351 if (azimuth < -90.f) {
michael@0 352 azimuth = -180.f - azimuth;
michael@0 353 } else if (azimuth > 90) {
michael@0 354 azimuth = 180.f - azimuth;
michael@0 355 }
michael@0 356
michael@0 357 // Normalize the value in the [0, 1] range.
michael@0 358 if (inputChannels == 1) {
michael@0 359 normalizedAzimuth = (azimuth + 90.f) / 180.f;
michael@0 360 } else {
michael@0 361 if (azimuth <= 0) {
michael@0 362 normalizedAzimuth = (azimuth + 90.f) / 90.f;
michael@0 363 } else {
michael@0 364 normalizedAzimuth = azimuth / 90.f;
michael@0 365 }
michael@0 366 }
michael@0 367
michael@0 368 distanceGain = ComputeDistanceGain();
michael@0 369
michael@0 370 // Actually compute the left and right gain.
michael@0 371 gainL = cos(0.5 * M_PI * normalizedAzimuth);
michael@0 372 gainR = sin(0.5 * M_PI * normalizedAzimuth);
michael@0 373
michael@0 374 // Compute the output.
michael@0 375 if (inputChannels == 1) {
michael@0 376 GainMonoToStereo(aInput, aOutput, gainL, gainR);
michael@0 377 } else {
michael@0 378 GainStereoToStereo(aInput, aOutput, gainL, gainR, azimuth);
michael@0 379 }
michael@0 380
michael@0 381 aOutput->mVolume = aInput.mVolume * distanceGain * coneGain;
michael@0 382 }
michael@0 383
michael@0 384 void
michael@0 385 PannerNodeEngine::GainMonoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
michael@0 386 float aGainL, float aGainR)
michael@0 387 {
michael@0 388 float* outputL = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
michael@0 389 float* outputR = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[1]));
michael@0 390 const float* input = static_cast<float*>(const_cast<void*>(aInput.mChannelData[0]));
michael@0 391
michael@0 392 AudioBlockPanMonoToStereo(input, aGainL, aGainR, outputL, outputR);
michael@0 393 }
michael@0 394
michael@0 395 void
michael@0 396 PannerNodeEngine::GainStereoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
michael@0 397 float aGainL, float aGainR, double aAzimuth)
michael@0 398 {
michael@0 399 float* outputL = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
michael@0 400 float* outputR = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[1]));
michael@0 401 const float* inputL = static_cast<float*>(const_cast<void*>(aInput.mChannelData[0]));
michael@0 402 const float* inputR = static_cast<float*>(const_cast<void*>(aInput.mChannelData[1]));
michael@0 403
michael@0 404 AudioBlockPanStereoToStereo(inputL, inputR, aGainL, aGainR, aAzimuth <= 0, outputL, outputR);
michael@0 405 }
michael@0 406
michael@0 407 // This algorithm is specified in the webaudio spec.
michael@0 408 void
michael@0 409 PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
michael@0 410 {
michael@0 411 ThreeDPoint sourceListener = mPosition - mListenerPosition;
michael@0 412
michael@0 413 if (sourceListener.IsZero()) {
michael@0 414 aAzimuth = 0.0;
michael@0 415 aElevation = 0.0;
michael@0 416 return;
michael@0 417 }
michael@0 418
michael@0 419 sourceListener.Normalize();
michael@0 420
michael@0 421 // Project the source-listener vector on the x-z plane.
michael@0 422 const ThreeDPoint& listenerFront = mListenerFrontVector;
michael@0 423 const ThreeDPoint& listenerRight = mListenerRightVector;
michael@0 424 ThreeDPoint up = listenerRight.CrossProduct(listenerFront);
michael@0 425
michael@0 426 double upProjection = sourceListener.DotProduct(up);
michael@0 427 aElevation = 90 - 180 * acos(upProjection) / M_PI;
michael@0 428
michael@0 429 if (aElevation > 90) {
michael@0 430 aElevation = 180 - aElevation;
michael@0 431 } else if (aElevation < -90) {
michael@0 432 aElevation = -180 - aElevation;
michael@0 433 }
michael@0 434
michael@0 435 ThreeDPoint projectedSource = sourceListener - up * upProjection;
michael@0 436 if (projectedSource.IsZero()) {
michael@0 437 // source - listener direction is up or down.
michael@0 438 aAzimuth = 0.0;
michael@0 439 return;
michael@0 440 }
michael@0 441 projectedSource.Normalize();
michael@0 442
michael@0 443 // Actually compute the angle, and convert to degrees
michael@0 444 double projection = projectedSource.DotProduct(listenerRight);
michael@0 445 aAzimuth = 180 * acos(projection) / M_PI;
michael@0 446
michael@0 447 // Compute whether the source is in front or behind the listener.
michael@0 448 double frontBack = projectedSource.DotProduct(listenerFront);
michael@0 449 if (frontBack < 0) {
michael@0 450 aAzimuth = 360 - aAzimuth;
michael@0 451 }
michael@0 452 // Rotate the azimuth so it is relative to the listener front vector instead
michael@0 453 // of the right vector.
michael@0 454 if ((aAzimuth >= 0) && (aAzimuth <= 270)) {
michael@0 455 aAzimuth = 90 - aAzimuth;
michael@0 456 } else {
michael@0 457 aAzimuth = 450 - aAzimuth;
michael@0 458 }
michael@0 459 }
michael@0 460
michael@0 461 // This algorithm is described in the WebAudio spec.
michael@0 462 float
michael@0 463 PannerNodeEngine::ComputeConeGain()
michael@0 464 {
michael@0 465 // Omnidirectional source
michael@0 466 if (mOrientation.IsZero() || ((mConeInnerAngle == 360) && (mConeOuterAngle == 360))) {
michael@0 467 return 1;
michael@0 468 }
michael@0 469
michael@0 470 // Normalized source-listener vector
michael@0 471 ThreeDPoint sourceToListener = mListenerPosition - mPosition;
michael@0 472 sourceToListener.Normalize();
michael@0 473
michael@0 474 // Angle between the source orientation vector and the source-listener vector
michael@0 475 double dotProduct = sourceToListener.DotProduct(mOrientation);
michael@0 476 double angle = 180 * acos(dotProduct) / M_PI;
michael@0 477 double absAngle = fabs(angle);
michael@0 478
michael@0 479 // Divide by 2 here since API is entire angle (not half-angle)
michael@0 480 double absInnerAngle = fabs(mConeInnerAngle) / 2;
michael@0 481 double absOuterAngle = fabs(mConeOuterAngle) / 2;
michael@0 482 double gain = 1;
michael@0 483
michael@0 484 if (absAngle <= absInnerAngle) {
michael@0 485 // No attenuation
michael@0 486 gain = 1;
michael@0 487 } else if (absAngle >= absOuterAngle) {
michael@0 488 // Max attenuation
michael@0 489 gain = mConeOuterGain;
michael@0 490 } else {
michael@0 491 // Between inner and outer cones
michael@0 492 // inner -> outer, x goes from 0 -> 1
michael@0 493 double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle);
michael@0 494 gain = (1 - x) + mConeOuterGain * x;
michael@0 495 }
michael@0 496
michael@0 497 return gain;
michael@0 498 }
michael@0 499
michael@0 500 float
michael@0 501 PannerNodeEngine::ComputeDistanceGain()
michael@0 502 {
michael@0 503 ThreeDPoint distanceVec = mPosition - mListenerPosition;
michael@0 504 float distance = sqrt(distanceVec.DotProduct(distanceVec));
michael@0 505 return (this->*mDistanceModelFunction)(distance);
michael@0 506 }
michael@0 507
michael@0 508 float
michael@0 509 PannerNode::ComputeDopplerShift()
michael@0 510 {
michael@0 511 double dopplerShift = 1.0; // Initialize to default value
michael@0 512
michael@0 513 AudioListener* listener = Context()->Listener();
michael@0 514
michael@0 515 if (listener->DopplerFactor() > 0) {
michael@0 516 // Don't bother if both source and listener have no velocity.
michael@0 517 if (!mVelocity.IsZero() || !listener->Velocity().IsZero()) {
michael@0 518 // Calculate the source to listener vector.
michael@0 519 ThreeDPoint sourceToListener = mPosition - listener->Velocity();
michael@0 520
michael@0 521 double sourceListenerMagnitude = sourceToListener.Magnitude();
michael@0 522
michael@0 523 double listenerProjection = sourceToListener.DotProduct(listener->Velocity()) / sourceListenerMagnitude;
michael@0 524 double sourceProjection = sourceToListener.DotProduct(mVelocity) / sourceListenerMagnitude;
michael@0 525
michael@0 526 listenerProjection = -listenerProjection;
michael@0 527 sourceProjection = -sourceProjection;
michael@0 528
michael@0 529 double scaledSpeedOfSound = listener->DopplerFactor() / listener->DopplerFactor();
michael@0 530 listenerProjection = min(listenerProjection, scaledSpeedOfSound);
michael@0 531 sourceProjection = min(sourceProjection, scaledSpeedOfSound);
michael@0 532
michael@0 533 dopplerShift = ((listener->SpeedOfSound() - listener->DopplerFactor() * listenerProjection) / (listener->SpeedOfSound() - listener->DopplerFactor() * sourceProjection));
michael@0 534
michael@0 535 WebAudioUtils::FixNaN(dopplerShift); // Avoid illegal values
michael@0 536
michael@0 537 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
michael@0 538 dopplerShift = min(dopplerShift, 16.);
michael@0 539 dopplerShift = max(dopplerShift, 0.125);
michael@0 540 }
michael@0 541 }
michael@0 542
michael@0 543 return dopplerShift;
michael@0 544 }
michael@0 545
michael@0 546 void
michael@0 547 PannerNode::FindConnectedSources()
michael@0 548 {
michael@0 549 mSources.Clear();
michael@0 550 std::set<AudioNode*> cycleSet;
michael@0 551 FindConnectedSources(this, mSources, cycleSet);
michael@0 552 }
michael@0 553
michael@0 554 void
michael@0 555 PannerNode::FindConnectedSources(AudioNode* aNode,
michael@0 556 nsTArray<AudioBufferSourceNode*>& aSources,
michael@0 557 std::set<AudioNode*>& aNodesSeen)
michael@0 558 {
michael@0 559 if (!aNode) {
michael@0 560 return;
michael@0 561 }
michael@0 562
michael@0 563 const nsTArray<InputNode>& inputNodes = aNode->InputNodes();
michael@0 564
michael@0 565 for(unsigned i = 0; i < inputNodes.Length(); i++) {
michael@0 566 // Return if we find a node that we have seen already.
michael@0 567 if (aNodesSeen.find(inputNodes[i].mInputNode) != aNodesSeen.end()) {
michael@0 568 return;
michael@0 569 }
michael@0 570 aNodesSeen.insert(inputNodes[i].mInputNode);
michael@0 571 // Recurse
michael@0 572 FindConnectedSources(inputNodes[i].mInputNode, aSources, aNodesSeen);
michael@0 573
michael@0 574 // Check if this node is an AudioBufferSourceNode
michael@0 575 AudioBufferSourceNode* node = inputNodes[i].mInputNode->AsAudioBufferSourceNode();
michael@0 576 if (node) {
michael@0 577 aSources.AppendElement(node);
michael@0 578 }
michael@0 579 }
michael@0 580 }
michael@0 581
michael@0 582 void
michael@0 583 PannerNode::SendDopplerToSourcesIfNeeded()
michael@0 584 {
michael@0 585 // Don't bother sending the doppler shift if both the source and the listener
michael@0 586 // are not moving, because the doppler shift is going to be 1.0.
michael@0 587 if (!(Context()->Listener()->Velocity().IsZero() && mVelocity.IsZero())) {
michael@0 588 for(uint32_t i = 0; i < mSources.Length(); i++) {
michael@0 589 mSources[i]->SendDopplerShiftToStream(ComputeDopplerShift());
michael@0 590 }
michael@0 591 }
michael@0 592 }
michael@0 593
michael@0 594
michael@0 595 }
michael@0 596 }
michael@0 597

mercurial