summaryrefslogtreecommitdiff
path: root/dom/media/webaudio/PannerNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/PannerNode.cpp')
-rw-r--r--dom/media/webaudio/PannerNode.cpp786
1 files changed, 786 insertions, 0 deletions
diff --git a/dom/media/webaudio/PannerNode.cpp b/dom/media/webaudio/PannerNode.cpp
new file mode 100644
index 0000000000..7696e984ef
--- /dev/null
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -0,0 +1,786 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+#include "PannerNode.h"
+#include "AlignmentUtils.h"
+#include "AudioDestinationNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "AudioListener.h"
+#include "PanningUtils.h"
+#include "AudioBufferSourceNode.h"
+#include "PlayingRefChangeHandler.h"
+#include "blink/HRTFPanner.h"
+#include "blink/HRTFDatabaseLoader.h"
+#include "nsAutoPtr.h"
+
+using WebCore::HRTFDatabaseLoader;
+using WebCore::HRTFPanner;
+
+namespace mozilla {
+namespace dom {
+
+using namespace std;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PannerNode)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PannerNode, AudioNode)
+ if (tmp->Context()) {
+ tmp->Context()->UnregisterPannerNode(tmp);
+ }
+NS_IMPL_CYCLE_COLLECTION_UNLINK(mPositionX, mPositionY, mPositionZ, mOrientationX, mOrientationY, mOrientationZ)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PannerNode, AudioNode)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositionX, mPositionY, mPositionZ, mOrientationX, mOrientationY, mOrientationZ)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PannerNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(PannerNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(PannerNode, AudioNode)
+
+class PannerNodeEngine final : public AudioNodeEngine
+{
+public:
+ explicit PannerNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode)
+ , mDestination(aDestination->Stream())
+ // Please keep these default values consistent with PannerNode::PannerNode below.
+ , mPanningModelFunction(&PannerNodeEngine::EqualPowerPanningFunction)
+ , mDistanceModelFunction(&PannerNodeEngine::InverseGainFunction)
+ , mPositionX(0.)
+ , mPositionY(0.)
+ , mPositionZ(0.)
+ , mOrientationX(1.)
+ , mOrientationY(0.)
+ , mOrientationZ(0.)
+ , mVelocity()
+ , mRefDistance(1.)
+ , mMaxDistance(10000.)
+ , mRolloffFactor(1.)
+ , mConeInnerAngle(360.)
+ , mConeOuterAngle(360.)
+ , mConeOuterGain(0.)
+ // These will be initialized when a PannerNode is created, so just initialize them
+ // to some dummy values here.
+ , mListenerDopplerFactor(0.)
+ , mListenerSpeedOfSound(0.)
+ , mLeftOverData(INT_MIN)
+ {
+ }
+
+ void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override
+ {
+ MOZ_ASSERT(mDestination);
+ WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
+ mDestination);
+ switch (aIndex) {
+ case PannerNode::POSITIONX:
+ mPositionX.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::POSITIONY:
+ mPositionY.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::POSITIONZ:
+ mPositionZ.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONX:
+ mOrientationX.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONY:
+ mOrientationY.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONZ:
+ mOrientationZ.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad PannerNode TimelineParameter");
+ }
+ }
+
+ void CreateHRTFPanner()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mHRTFPanner) {
+ return;
+ }
+ // HRTFDatabaseLoader needs to be fetched on the main thread.
+ already_AddRefed<HRTFDatabaseLoader> loader =
+ HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(NodeMainThread()->Context()->SampleRate());
+ mHRTFPanner = new HRTFPanner(NodeMainThread()->Context()->SampleRate(), Move(loader));
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override
+ {
+ switch (aIndex) {
+ case PannerNode::PANNING_MODEL:
+ switch (PanningModelType(aParam)) {
+ case PanningModelType::Equalpower:
+ mPanningModelFunction = &PannerNodeEngine::EqualPowerPanningFunction;
+ break;
+ case PanningModelType::HRTF:
+ mPanningModelFunction = &PannerNodeEngine::HRTFPanningFunction;
+ break;
+ default:
+ NS_NOTREACHED("We should never see the alternate names here");
+ break;
+ }
+ break;
+ case PannerNode::DISTANCE_MODEL:
+ switch (DistanceModelType(aParam)) {
+ case DistanceModelType::Inverse:
+ mDistanceModelFunction = &PannerNodeEngine::InverseGainFunction;
+ break;
+ case DistanceModelType::Linear:
+ mDistanceModelFunction = &PannerNodeEngine::LinearGainFunction;
+ break;
+ case DistanceModelType::Exponential:
+ mDistanceModelFunction = &PannerNodeEngine::ExponentialGainFunction;
+ break;
+ default:
+ NS_NOTREACHED("We should never see the alternate names here");
+ break;
+ }
+ break;
+ default:
+ NS_ERROR("Bad PannerNodeEngine Int32Parameter");
+ }
+ }
+ void SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aParam) override
+ {
+ switch (aIndex) {
+ case PannerNode::LISTENER_POSITION: mListenerPosition = 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:
+ mPositionX.SetValue(aParam.x);
+ mPositionY.SetValue(aParam.y);
+ mPositionZ.SetValue(aParam.z);
+ break;
+ case PannerNode::ORIENTATION:
+ mOrientationX.SetValue(aParam.x);
+ mOrientationY.SetValue(aParam.y);
+ mOrientationZ.SetValue(aParam.z);
+ break;
+ case PannerNode::VELOCITY: mVelocity = aParam; break;
+ default:
+ NS_ERROR("Bad PannerNodeEngine ThreeDPointParameter");
+ }
+ }
+ void SetDoubleParameter(uint32_t aIndex, double aParam) override
+ {
+ switch (aIndex) {
+ case PannerNode::LISTENER_DOPPLER_FACTOR: mListenerDopplerFactor = aParam; break;
+ case PannerNode::LISTENER_SPEED_OF_SOUND: mListenerSpeedOfSound = aParam; break;
+ case PannerNode::REF_DISTANCE: mRefDistance = aParam; break;
+ case PannerNode::MAX_DISTANCE: mMaxDistance = aParam; break;
+ case PannerNode::ROLLOFF_FACTOR: mRolloffFactor = aParam; break;
+ case PannerNode::CONE_INNER_ANGLE: mConeInnerAngle = aParam; break;
+ case PannerNode::CONE_OUTER_ANGLE: mConeOuterAngle = aParam; break;
+ case PannerNode::CONE_OUTER_GAIN: mConeOuterGain = aParam; break;
+ default:
+ NS_ERROR("Bad PannerNodeEngine DoubleParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeStream* aStream,
+ GraphTime aFrom,
+ const AudioBlock& aInput,
+ AudioBlock* aOutput,
+ bool *aFinished) override
+ {
+ if (aInput.IsNull()) {
+ // mLeftOverData != INT_MIN means that the panning model was HRTF and a
+ // tail-time reference was added. Even if the model is now equalpower,
+ // the reference will need to be removed.
+ if (mLeftOverData > 0 &&
+ mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
+ mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
+ } else {
+ if (mLeftOverData != INT_MIN) {
+ mLeftOverData = INT_MIN;
+ aStream->ScheduleCheckForInactive();
+ mHRTFPanner->reset();
+
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::RELEASE);
+ aStream->Graph()->
+ DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+ }
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+ } else if (mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
+ if (mLeftOverData == INT_MIN) {
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aStream, PlayingRefChangeHandler::ADDREF);
+ aStream->Graph()->
+ DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget());
+ }
+ mLeftOverData = mHRTFPanner->maxTailFrames();
+ }
+
+ StreamTime tick = mDestination->GraphTimeToStreamTime(aFrom);
+ (this->*mPanningModelFunction)(aInput, aOutput, tick);
+ }
+
+ 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.
+ 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(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);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ 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)(double aDistance);
+ DistanceModelFunction mDistanceModelFunction;
+ AudioParamTimeline mPositionX;
+ AudioParamTimeline mPositionY;
+ AudioParamTimeline mPositionZ;
+ AudioParamTimeline mOrientationX;
+ AudioParamTimeline mOrientationY;
+ AudioParamTimeline mOrientationZ;
+ ThreeDPoint mVelocity;
+ double mRefDistance;
+ double mMaxDistance;
+ double mRolloffFactor;
+ double mConeInnerAngle;
+ double mConeOuterAngle;
+ double mConeOuterGain;
+ ThreeDPoint mListenerPosition;
+ ThreeDPoint mListenerFrontVector;
+ ThreeDPoint mListenerRightVector;
+ ThreeDPoint mListenerVelocity;
+ double mListenerDopplerFactor;
+ double mListenerSpeedOfSound;
+ int mLeftOverData;
+};
+
+PannerNode::PannerNode(AudioContext* aContext)
+ : AudioNode(aContext,
+ 2,
+ ChannelCountMode::Clamped_max,
+ ChannelInterpretation::Speakers)
+ // Please keep these default values consistent with PannerNodeEngine::PannerNodeEngine above.
+ , mPanningModel(PanningModelType::Equalpower)
+ , mDistanceModel(DistanceModelType::Inverse)
+ , mPositionX(new AudioParam(this, PannerNode::POSITIONX, 0., this->NodeType()))
+ , mPositionY(new AudioParam(this, PannerNode::POSITIONY, 0., this->NodeType()))
+ , mPositionZ(new AudioParam(this, PannerNode::POSITIONZ, 0., this->NodeType()))
+ , mOrientationX(new AudioParam(this, PannerNode::ORIENTATIONX, 1., this->NodeType()))
+ , mOrientationY(new AudioParam(this, PannerNode::ORIENTATIONY, 0., this->NodeType()))
+ , mOrientationZ(new AudioParam(this, PannerNode::ORIENTATIONZ, 0., this->NodeType()))
+ , mVelocity()
+ , mRefDistance(1.)
+ , mMaxDistance(10000.)
+ , mRolloffFactor(1.)
+ , mConeInnerAngle(360.)
+ , mConeOuterAngle(360.)
+ , mConeOuterGain(0.)
+{
+ mStream = AudioNodeStream::Create(aContext,
+ new PannerNodeEngine(this, aContext->Destination()),
+ AudioNodeStream::NO_STREAM_FLAGS,
+ aContext->Graph());
+ // We should register once we have set up our stream and engine.
+ Context()->Listener()->RegisterPannerNode(this);
+}
+
+PannerNode::~PannerNode()
+{
+ if (Context()) {
+ Context()->UnregisterPannerNode(this);
+ }
+}
+
+void PannerNode::SetPanningModel(PanningModelType aPanningModel)
+{
+ mPanningModel = aPanningModel;
+ if (mPanningModel == PanningModelType::HRTF) {
+ // We can set the engine's `mHRTFPanner` member here from the main thread,
+ // because the engine will not touch it from the MediaStreamGraph
+ // thread until the PANNING_MODEL message sent below is received.
+ static_cast<PannerNodeEngine*>(mStream->Engine())->CreateHRTFPanner();
+ }
+ SendInt32ParameterToStream(PANNING_MODEL, int32_t(mPanningModel));
+}
+
+size_t
+PannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mSources.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t
+PannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject*
+PannerNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return PannerNodeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void PannerNode::DestroyMediaStream()
+{
+ if (Context()) {
+ Context()->UnregisterPannerNode(this);
+ }
+ AudioNode::DestroyMediaStream();
+}
+
+// Those three functions are described in the spec.
+float
+PannerNodeEngine::LinearGainFunction(double aDistance)
+{
+ return 1 - mRolloffFactor * (std::max(std::min(aDistance, mMaxDistance), mRefDistance) - mRefDistance) / (mMaxDistance - mRefDistance);
+}
+
+float
+PannerNodeEngine::InverseGainFunction(double aDistance)
+{
+ return mRefDistance / (mRefDistance + mRolloffFactor * (std::max(aDistance, mRefDistance) - mRefDistance));
+}
+
+float
+PannerNodeEngine::ExponentialGainFunction(double aDistance)
+{
+ 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.
+ aOutput->AllocateChannels(2);
+
+ float azimuth, elevation;
+
+ ThreeDPoint position = ConvertAudioParamTimelineTo3DP(mPositionX, mPositionY, mPositionZ, tick);
+ ThreeDPoint orientation = ConvertAudioParamTimelineTo3DP(mOrientationX, mOrientationY, mOrientationZ, tick);
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+
+ AudioBlock input = aInput;
+ // Gain is applied before the delay and convolution of the HRTF.
+ input.mVolume *= ComputeConeGain(position, orientation) * ComputeDistanceGain(position);
+
+ mHRTFPanner->pan(azimuth, elevation, &input, aOutput);
+}
+
+ThreeDPoint
+PannerNodeEngine::ConvertAudioParamTimelineTo3DP(AudioParamTimeline& aX, AudioParamTimeline& aY, AudioParamTimeline& aZ, StreamTime &tick)
+{
+ return ThreeDPoint(aX.GetValueAtTime(tick),
+ aY.GetValueAtTime(tick),
+ aZ.GetValueAtTime(tick));
+}
+
+void
+PannerNodeEngine::EqualPowerPanningFunction(const AudioBlock& aInput,
+ AudioBlock* aOutput,
+ StreamTime tick)
+{
+ float azimuth, elevation, gainL, gainR, normalizedAzimuth, distanceGain, coneGain;
+ int inputChannels = aInput.ChannelCount();
+
+ // Optimize the case where the position and orientation is constant for this
+ // processing block: we can just apply a constant gain on the left and right
+ // channel
+ if (mPositionX.HasSimpleValue() &&
+ mPositionY.HasSimpleValue() &&
+ mPositionZ.HasSimpleValue() &&
+ mOrientationX.HasSimpleValue() &&
+ mOrientationY.HasSimpleValue() &&
+ mOrientationZ.HasSimpleValue()) {
+
+ ThreeDPoint position = ConvertAudioParamTimelineTo3DP(mPositionX, mPositionY, mPositionZ, tick);
+ ThreeDPoint orientation = ConvertAudioParamTimelineTo3DP(mOrientationX, mOrientationY, mOrientationZ, tick);
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+
+ // If both the listener are in the same spot, and no cone gain is specified,
+ // this node is noop.
+ if (mListenerPosition == position &&
+ mConeInnerAngle == 360 &&
+ mConeOuterAngle == 360) {
+ *aOutput = aInput;
+ return;
+ }
+
+ // The output of this node is always stereo, no matter what the inputs are.
+ aOutput->AllocateChannels(2);
+
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+ coneGain = ComputeConeGain(position, orientation);
+
+ // The following algorithm is described in the spec.
+ // Clamp azimuth in the [-90, 90] range.
+ azimuth = min(180.f, max(-180.f, azimuth));
+
+ // Wrap around
+ if (azimuth < -90.f) {
+ azimuth = -180.f - azimuth;
+ } else if (azimuth > 90) {
+ azimuth = 180.f - azimuth;
+ }
+
+ // Normalize the value in the [0, 1] range.
+ if (inputChannels == 1) {
+ normalizedAzimuth = (azimuth + 90.f) / 180.f;
+ } else {
+ if (azimuth <= 0) {
+ normalizedAzimuth = (azimuth + 90.f) / 90.f;
+ } else {
+ normalizedAzimuth = azimuth / 90.f;
+ }
+ }
+
+ distanceGain = ComputeDistanceGain(position);
+
+ // Actually compute the left and right gain.
+ gainL = cos(0.5 * M_PI * normalizedAzimuth);
+ gainR = sin(0.5 * M_PI * normalizedAzimuth);
+
+ // Compute the output.
+ ApplyStereoPanning(aInput, aOutput, gainL, gainR, azimuth <= 0);
+
+ aOutput->mVolume = aInput.mVolume * distanceGain * coneGain;
+ } else {
+ float positionX[WEBAUDIO_BLOCK_SIZE];
+ float positionY[WEBAUDIO_BLOCK_SIZE];
+ float positionZ[WEBAUDIO_BLOCK_SIZE];
+ float orientationX[WEBAUDIO_BLOCK_SIZE];
+ float orientationY[WEBAUDIO_BLOCK_SIZE];
+ float orientationZ[WEBAUDIO_BLOCK_SIZE];
+
+ // The output of this node is always stereo, no matter what the inputs are.
+ aOutput->AllocateChannels(2);
+
+ if (!mPositionX.HasSimpleValue()) {
+ mPositionX.GetValuesAtTime(tick, positionX, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionX[0] = mPositionX.GetValueAtTime(tick);
+ }
+ if (!mPositionY.HasSimpleValue()) {
+ mPositionY.GetValuesAtTime(tick, positionY, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionY[0] = mPositionY.GetValueAtTime(tick);
+ }
+ if (!mPositionZ.HasSimpleValue()) {
+ mPositionZ.GetValuesAtTime(tick, positionZ, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionZ[0] = mPositionZ.GetValueAtTime(tick);
+ }
+ if (!mOrientationX.HasSimpleValue()) {
+ mOrientationX.GetValuesAtTime(tick, orientationX, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationX[0] = mOrientationX.GetValueAtTime(tick);
+ }
+ if (!mOrientationY.HasSimpleValue()) {
+ mOrientationY.GetValuesAtTime(tick, orientationY, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationY[0] = mOrientationY.GetValueAtTime(tick);
+ }
+ if (!mOrientationZ.HasSimpleValue()) {
+ mOrientationZ.GetValuesAtTime(tick, orientationZ, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationZ[0] = mOrientationZ.GetValueAtTime(tick);
+ }
+
+ float computedGain[2*WEBAUDIO_BLOCK_SIZE + 4];
+ bool onLeft[WEBAUDIO_BLOCK_SIZE];
+
+ float* alignedComputedGain = ALIGNED16(computedGain);
+ ASSERT_ALIGNED16(alignedComputedGain);
+ for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
+ ThreeDPoint position(mPositionX.HasSimpleValue() ? positionX[0] : positionX[counter],
+ mPositionY.HasSimpleValue() ? positionY[0] : positionY[counter],
+ mPositionZ.HasSimpleValue() ? positionZ[0] : positionZ[counter]);
+ ThreeDPoint orientation(mOrientationX.HasSimpleValue() ? orientationX[0] : orientationX[counter],
+ mOrientationY.HasSimpleValue() ? orientationY[0] : orientationY[counter],
+ mOrientationZ.HasSimpleValue() ? orientationZ[0] : orientationZ[counter]);
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+ coneGain = ComputeConeGain(position, orientation);
+
+ // The following algorithm is described in the spec.
+ // Clamp azimuth in the [-90, 90] range.
+ azimuth = min(180.f, max(-180.f, azimuth));
+
+ // Wrap around
+ if (azimuth < -90.f) {
+ azimuth = -180.f - azimuth;
+ } else if (azimuth > 90) {
+ azimuth = 180.f - azimuth;
+ }
+
+ // Normalize the value in the [0, 1] range.
+ if (inputChannels == 1) {
+ normalizedAzimuth = (azimuth + 90.f) / 180.f;
+ } else {
+ if (azimuth <= 0) {
+ normalizedAzimuth = (azimuth + 90.f) / 90.f;
+ } else {
+ normalizedAzimuth = azimuth / 90.f;
+ }
+ }
+
+ distanceGain = ComputeDistanceGain(position);
+
+ // Actually compute the left and right gain.
+ float gainL = cos(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain;
+ float gainR = sin(0.5 * M_PI * normalizedAzimuth) * aInput.mVolume * distanceGain * coneGain;
+
+ alignedComputedGain[counter] = gainL;
+ alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = gainR;
+ onLeft[counter] = azimuth <= 0;
+ }
+
+ // Apply the gain to the output buffer
+ ApplyStereoPanning(aInput, aOutput, alignedComputedGain, &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft);
+
+ }
+}
+
+// This algorithm is specified in the webaudio spec.
+void
+PannerNodeEngine::ComputeAzimuthAndElevation(const ThreeDPoint& position, float& aAzimuth, float& aElevation)
+{
+ ThreeDPoint sourceListener = position - mListenerPosition;
+ if (sourceListener.IsZero()) {
+ aAzimuth = 0.0;
+ aElevation = 0.0;
+ return;
+ }
+
+ sourceListener.Normalize();
+
+ // Project the source-listener vector on the x-z plane.
+ const ThreeDPoint& listenerFront = mListenerFrontVector;
+ const ThreeDPoint& listenerRight = mListenerRightVector;
+ ThreeDPoint up = listenerRight.CrossProduct(listenerFront);
+
+ double upProjection = sourceListener.DotProduct(up);
+ aElevation = 90 - 180 * acos(upProjection) / M_PI;
+
+ if (aElevation > 90) {
+ aElevation = 180 - aElevation;
+ } else if (aElevation < -90) {
+ aElevation = -180 - aElevation;
+ }
+
+ ThreeDPoint projectedSource = sourceListener - up * upProjection;
+ if (projectedSource.IsZero()) {
+ // source - listener direction is up or down.
+ aAzimuth = 0.0;
+ return;
+ }
+ projectedSource.Normalize();
+
+ // Actually compute the angle, and convert to degrees
+ 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(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 {
+ aAzimuth = 450 - aAzimuth;
+ }
+}
+
+// This algorithm is described in the WebAudio spec.
+float
+PannerNodeEngine::ComputeConeGain(const ThreeDPoint& position,
+ const ThreeDPoint& orientation)
+{
+ // Omnidirectional source
+ if (orientation.IsZero() || ((mConeInnerAngle == 360) && (mConeOuterAngle == 360))) {
+ return 1;
+ }
+
+ // Normalized source-listener vector
+ ThreeDPoint sourceToListener = mListenerPosition - position;
+ sourceToListener.Normalize();
+
+ // Angle between the source orientation vector and the source-listener vector
+ double dotProduct = sourceToListener.DotProduct(orientation);
+ 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;
+
+ if (absAngle <= absInnerAngle) {
+ // No attenuation
+ gain = 1;
+ } else if (absAngle >= absOuterAngle) {
+ // Max attenuation
+ gain = mConeOuterGain;
+ } else {
+ // Between inner and outer cones
+ // inner -> outer, x goes from 0 -> 1
+ double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle);
+ gain = (1 - x) + mConeOuterGain * x;
+ }
+
+ return gain;
+}
+
+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
+PannerNode::ComputeDopplerShift()
+{
+ double dopplerShift = 1.0; // Initialize to default value
+
+ AudioListener* listener = Context()->Listener();
+
+ if (listener->DopplerFactor() > 0) {
+ // Don't bother if both source and listener have no velocity.
+ if (!mVelocity.IsZero() || !listener->Velocity().IsZero()) {
+ // Calculate the source to listener vector.
+ ThreeDPoint sourceToListener = ConvertAudioParamTo3DP(mPositionX, mPositionY, mPositionZ) - listener->Velocity();
+
+ double sourceListenerMagnitude = sourceToListener.Magnitude();
+
+ double listenerProjection = sourceToListener.DotProduct(listener->Velocity()) / sourceListenerMagnitude;
+ double sourceProjection = sourceToListener.DotProduct(mVelocity) / sourceListenerMagnitude;
+
+ listenerProjection = -listenerProjection;
+ sourceProjection = -sourceProjection;
+
+ double scaledSpeedOfSound = listener->SpeedOfSound() / listener->DopplerFactor();
+ listenerProjection = min(listenerProjection, scaledSpeedOfSound);
+ sourceProjection = min(sourceProjection, scaledSpeedOfSound);
+
+ dopplerShift = ((listener->SpeedOfSound() - listener->DopplerFactor() * listenerProjection) / (listener->SpeedOfSound() - listener->DopplerFactor() * sourceProjection));
+
+ WebAudioUtils::FixNaN(dopplerShift); // Avoid illegal values
+
+ // Limit the pitch shifting to 4 octaves up and 3 octaves down.
+ dopplerShift = min(dopplerShift, 16.);
+ dopplerShift = max(dopplerShift, 0.125);
+ }
+ }
+
+ return dopplerShift;
+}
+
+void
+PannerNode::FindConnectedSources()
+{
+ mSources.Clear();
+ std::set<AudioNode*> cycleSet;
+ FindConnectedSources(this, mSources, cycleSet);
+}
+
+void
+PannerNode::FindConnectedSources(AudioNode* aNode,
+ nsTArray<AudioBufferSourceNode*>& aSources,
+ std::set<AudioNode*>& aNodesSeen)
+{
+ if (!aNode) {
+ return;
+ }
+
+ const nsTArray<InputNode>& inputNodes = aNode->InputNodes();
+
+ for(unsigned i = 0; i < inputNodes.Length(); i++) {
+ // Return if we find a node that we have seen already.
+ if (aNodesSeen.find(inputNodes[i].mInputNode) != aNodesSeen.end()) {
+ return;
+ }
+ aNodesSeen.insert(inputNodes[i].mInputNode);
+ // Recurse
+ FindConnectedSources(inputNodes[i].mInputNode, aSources, aNodesSeen);
+
+ // Check if this node is an AudioBufferSourceNode that still have a stream,
+ // which means it has not finished playing.
+ AudioBufferSourceNode* node = inputNodes[i].mInputNode->AsAudioBufferSourceNode();
+ if (node && node->GetStream()) {
+ aSources.AppendElement(node);
+ }
+ }
+}
+
+void
+PannerNode::SendDopplerToSourcesIfNeeded()
+{
+ // Don't bother sending the doppler shift if both the source and the listener
+ // are not moving, because the doppler shift is going to be 1.0.
+ if (!(Context()->Listener()->Velocity().IsZero() && mVelocity.IsZero())) {
+ for(uint32_t i = 0; i < mSources.Length(); i++) {
+ mSources[i]->SendDopplerShiftToStream(ComputeDopplerShift());
+ }
+ }
+}
+
+
+} // namespace dom
+} // namespace mozilla
+