Bug 849918 - Initial support for PannerNode's 3D positional audio (equalpower panning...
[gecko.git] / content / media / webaudio / PannerNode.cpp
blob469b10d2b5648313e33b6d0beee4d79bca02fa68
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PannerNode.h"
8 #include "AudioNodeEngine.h"
9 #include "AudioNodeStream.h"
10 #include "AudioListener.h"
12 namespace mozilla {
13 namespace dom {
15 using namespace std;
17 class PannerNodeEngine : public AudioNodeEngine
19 public:
20 PannerNodeEngine()
21 // Please keep these default values consistent with PannerNode::PannerNode below.
22 : mPanningModel(PanningModelTypeValues::HRTF)
23 , mPanningModelFunction(&PannerNodeEngine::HRTFPanningFunction)
24 , mDistanceModel(DistanceModelTypeValues::Inverse)
25 , mDistanceModelFunction(&PannerNodeEngine::InverseGainFunction)
26 , mPosition()
27 , mOrientation(1., 0., 0.)
28 , mVelocity()
29 , mRefDistance(1.)
30 , mMaxDistance(10000.)
31 , mRolloffFactor(1.)
32 , mConeInnerAngle(360.)
33 , mConeOuterAngle(360.)
34 , mConeOuterGain(0.)
35 // These will be initialized when a PannerNode is created, so just initialize them
36 // to some dummy values here.
37 , mListenerDopplerFactor(0.)
38 , mListenerSpeedOfSound(0.)
42 virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE
44 switch (aIndex) {
45 case PannerNode::PANNING_MODEL:
46 mPanningModel = PanningModelType(aParam);
47 switch (mPanningModel) {
48 case PanningModelTypeValues::Equalpower:
49 mPanningModelFunction = &PannerNodeEngine::EqualPowerPanningFunction;
50 break;
51 case PanningModelTypeValues::HRTF:
52 mPanningModelFunction = &PannerNodeEngine::HRTFPanningFunction;
53 break;
54 case PanningModelTypeValues::Soundfield:
55 mPanningModelFunction = &PannerNodeEngine::SoundfieldPanningFunction;
56 break;
58 break;
59 case PannerNode::DISTANCE_MODEL:
60 mDistanceModel = DistanceModelType(aParam);
61 switch (mDistanceModel) {
62 case DistanceModelTypeValues::Inverse:
63 mDistanceModelFunction = &PannerNodeEngine::InverseGainFunction;
64 break;
65 case DistanceModelTypeValues::Linear:
66 mDistanceModelFunction = &PannerNodeEngine::LinearGainFunction;
67 break;
68 case DistanceModelTypeValues::Exponential:
69 mDistanceModelFunction = &PannerNodeEngine::ExponentialGainFunction;
70 break;
72 break;
73 default:
74 NS_ERROR("Bad PannerNodeEngine Int32Parameter");
77 virtual void SetThreeDPointParameter(uint32_t aIndex, const ThreeDPoint& aParam) MOZ_OVERRIDE
79 switch (aIndex) {
80 case PannerNode::LISTENER_POSITION: mListenerPosition = aParam; break;
81 case PannerNode::LISTENER_ORIENTATION: mListenerOrientation = aParam; break;
82 case PannerNode::LISTENER_UPVECTOR: mListenerUpVector = aParam; break;
83 case PannerNode::LISTENER_VELOCITY: mListenerVelocity = aParam; break;
84 case PannerNode::POSITION: mPosition = aParam; break;
85 case PannerNode::ORIENTATION: mOrientation = aParam; break;
86 case PannerNode::VELOCITY: mVelocity = aParam; break;
87 default:
88 NS_ERROR("Bad PannerNodeEngine ThreeDPointParameter");
91 virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE
93 switch (aIndex) {
94 case PannerNode::LISTENER_DOPPLER_FACTOR: mListenerDopplerFactor = aParam; break;
95 case PannerNode::LISTENER_SPEED_OF_SOUND: mListenerSpeedOfSound = aParam; break;
96 case PannerNode::REF_DISTANCE: mRefDistance = aParam; break;
97 case PannerNode::MAX_DISTANCE: mMaxDistance = aParam; break;
98 case PannerNode::ROLLOFF_FACTOR: mRolloffFactor = aParam; break;
99 case PannerNode::CONE_INNER_ANGLE: mConeInnerAngle = aParam; break;
100 case PannerNode::CONE_OUTER_ANGLE: mConeOuterAngle = aParam; break;
101 case PannerNode::CONE_OUTER_GAIN: mConeOuterGain = aParam; break;
102 default:
103 NS_ERROR("Bad PannerNodeEngine DoubleParameter");
107 virtual void ProduceAudioBlock(AudioNodeStream* aStream,
108 const AudioChunk& aInput,
109 AudioChunk* aOutput,
110 bool *aFinished) MOZ_OVERRIDE
112 if (aInput.IsNull()) {
113 *aOutput = aInput;
114 return;
116 (this->*mPanningModelFunction)(aInput, aOutput);
119 void ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation);
120 void DistanceGain(AudioChunk* aChunk, float aGain);
122 void GainMonoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
123 float aGainL, float aGainR);
124 void GainStereoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
125 float aGainL, float aGainR, double aAzimuth);
127 void EqualPowerPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
128 void HRTFPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
129 void SoundfieldPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
131 float LinearGainFunction(float aDistance);
132 float InverseGainFunction(float aDistance);
133 float ExponentialGainFunction(float aDistance);
135 PanningModelType mPanningModel;
136 typedef void (PannerNodeEngine::*PanningModelFunction)(const AudioChunk& aInput, AudioChunk* aOutput);
137 PanningModelFunction mPanningModelFunction;
138 DistanceModelType mDistanceModel;
139 typedef float (PannerNodeEngine::*DistanceModelFunction)(float aDistance);
140 DistanceModelFunction mDistanceModelFunction;
141 ThreeDPoint mPosition;
142 ThreeDPoint mOrientation;
143 ThreeDPoint mVelocity;
144 double mRefDistance;
145 double mMaxDistance;
146 double mRolloffFactor;
147 double mConeInnerAngle;
148 double mConeOuterAngle;
149 double mConeOuterGain;
150 ThreeDPoint mListenerPosition;
151 ThreeDPoint mListenerOrientation;
152 ThreeDPoint mListenerUpVector;
153 ThreeDPoint mListenerVelocity;
154 double mListenerDopplerFactor;
155 double mListenerSpeedOfSound;
158 PannerNode::PannerNode(AudioContext* aContext)
159 : AudioNode(aContext)
160 // Please keep these default values consistent with PannerNodeEngine::PannerNodeEngine above.
161 , mPanningModel(PanningModelTypeValues::HRTF)
162 , mDistanceModel(DistanceModelTypeValues::Inverse)
163 , mPosition()
164 , mOrientation(1., 0., 0.)
165 , mVelocity()
166 , mRefDistance(1.)
167 , mMaxDistance(10000.)
168 , mRolloffFactor(1.)
169 , mConeInnerAngle(360.)
170 , mConeOuterAngle(360.)
171 , mConeOuterGain(0.)
173 mStream = aContext->Graph()->CreateAudioNodeStream(new PannerNodeEngine(),
174 MediaStreamGraph::INTERNAL_STREAM);
175 // We should register once we have set up our stream and engine.
176 Context()->Listener()->RegisterPannerNode(this);
179 PannerNode::~PannerNode()
181 DestroyMediaStream();
184 JSObject*
185 PannerNode::WrapObject(JSContext* aCx, JSObject* aScope)
187 return PannerNodeBinding::Wrap(aCx, aScope, this);
190 // Those three functions are described in the spec.
191 float
192 PannerNodeEngine::LinearGainFunction(float aDistance)
194 return 1 - mRolloffFactor * (aDistance - mRefDistance) / (mMaxDistance - mRefDistance);
197 float
198 PannerNodeEngine::InverseGainFunction(float aDistance)
200 return mRefDistance / (mRefDistance + mRolloffFactor * (aDistance - mRefDistance));
203 float
204 PannerNodeEngine::ExponentialGainFunction(float aDistance)
206 return pow(aDistance / mRefDistance, -mRolloffFactor);
209 void
210 PannerNodeEngine::SoundfieldPanningFunction(const AudioChunk& aInput,
211 AudioChunk* aOutput)
213 // not implemented: noop
214 *aOutput = aInput;
217 void
218 PannerNodeEngine::HRTFPanningFunction(const AudioChunk& aInput,
219 AudioChunk* aOutput)
221 // not implemented: noop
222 *aOutput = aInput;
225 void
226 PannerNodeEngine::EqualPowerPanningFunction(const AudioChunk& aInput,
227 AudioChunk* aOutput)
229 float azimuth, elevation, gainL, gainR, normalizedAzimuth, distance, distanceGain;
230 int inputChannels = aInput.mChannelData.Length();
231 ThreeDPoint distanceVec;
233 // If both the listener are in the same spot, and no cone gain is specified,
234 // this node is noop.
235 if (mListenerPosition == mPosition &&
236 mConeInnerAngle == 360 &&
237 mConeOuterAngle == 360) {
238 *aOutput = aInput;
241 // The output of this node is always stereo, no matter what the inputs are.
242 AllocateAudioBlock(2, aOutput);
244 ComputeAzimuthAndElevation(azimuth, elevation);
246 // The following algorithm is described in the spec.
247 // Clamp azimuth in the [-90, 90] range.
248 azimuth = min(180.f, max(-180.f, azimuth));
250 // Wrap around
251 if (azimuth < -90.f) {
252 azimuth = -180.f - azimuth;
253 } else if (azimuth > 90) {
254 azimuth = 180.f - azimuth;
257 // Normalize the value in the [0, 1] range.
258 if (inputChannels == 1) {
259 normalizedAzimuth = (azimuth + 90.f) / 180.f;
260 } else {
261 if (azimuth <= 0) {
262 normalizedAzimuth = (azimuth + 90.f) / 90.f;
263 } else {
264 normalizedAzimuth = azimuth / 90.f;
268 // Compute how much the distance contributes to the gain reduction.
269 distanceVec = mPosition - mListenerPosition;
270 distance = sqrt(distanceVec.DotProduct(distanceVec));
271 distanceGain = (this->*mDistanceModelFunction)(distance);
273 // Actually compute the left and right gain.
274 gainL = cos(0.5 * M_PI * normalizedAzimuth);
275 gainR = sin(0.5 * M_PI * normalizedAzimuth);
277 // Compute the output.
278 if (inputChannels == 1) {
279 GainMonoToStereo(aInput, aOutput, gainL, gainR);
280 } else {
281 GainStereoToStereo(aInput, aOutput, gainL, gainR, azimuth);
284 DistanceGain(aOutput, distanceGain);
287 void
288 PannerNodeEngine::GainMonoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
289 float aGainL, float aGainR)
291 float* outputL = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
292 float* outputR = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[1]));
293 const float* input = static_cast<float*>(const_cast<void*>(aInput.mChannelData[0]));
295 AudioBlockPanMonoToStereo(input, aGainL, aGainR, outputL, outputR);
298 void
299 PannerNodeEngine::GainStereoToStereo(const AudioChunk& aInput, AudioChunk* aOutput,
300 float aGainL, float aGainR, double aAzimuth)
302 float* outputL = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[0]));
303 float* outputR = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[1]));
304 const float* inputL = static_cast<float*>(const_cast<void*>(aInput.mChannelData[0]));
305 const float* inputR = static_cast<float*>(const_cast<void*>(aInput.mChannelData[1]));
307 AudioBlockPanStereoToStereo(inputL, inputR, aGainL, aGainR, aAzimuth <= 0, outputL, outputR);
310 void
311 PannerNodeEngine::DistanceGain(AudioChunk* aChunk, float aGain)
313 float* samples = static_cast<float*>(const_cast<void*>(*aChunk->mChannelData.Elements()));
314 uint32_t channelCount = aChunk->mChannelData.Length();
316 AudioBlockInPlaceScale(samples, channelCount, aGain);
319 // This algorithm is specicied in the webaudio spec.
320 void
321 PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth, float& aElevation)
323 ThreeDPoint sourceListener = mPosition - mListenerPosition;
325 if (sourceListener.IsZero()) {
326 aAzimuth = 0.0;
327 aElevation = 0.0;
328 return;
331 sourceListener.Normalize();
333 // Project the source-listener vector on the x-z plane.
334 ThreeDPoint& listenerFront = mListenerOrientation;
335 ThreeDPoint listenerRightNorm = listenerFront.CrossProduct(mListenerUpVector);
336 listenerRightNorm.Normalize();
338 ThreeDPoint listenerFrontNorm(listenerFront);
339 listenerFrontNorm.Normalize();
341 ThreeDPoint up = listenerRightNorm.CrossProduct(listenerFrontNorm);
343 double upProjection = sourceListener.DotProduct(up);
345 ThreeDPoint projectedSource = sourceListener - up * upProjection;
346 projectedSource.Normalize();
348 // Actually compute the angle, and convert to degrees
349 double projection = projectedSource.DotProduct(listenerRightNorm);
350 aAzimuth = 180 * acos(projection) / M_PI;
352 // Compute whether the source is in front or behind the listener.
353 double frontBack = projectedSource.DotProduct(listenerFrontNorm);
354 if (frontBack < 0) {
355 aAzimuth = 360 - aAzimuth;
357 // Rotate the azimuth so it is relative to the listener front vector instead
358 // of the right vector.
359 if ((aAzimuth >= 0) && (aAzimuth <= 270)) {
360 aAzimuth = 90 - aAzimuth;
361 } else {
362 aAzimuth = 450 - aAzimuth;
365 aElevation = 90 - 180 * acos(sourceListener.DotProduct(up)) / M_PI;
367 if (aElevation > 90) {
368 aElevation = 180 - aElevation;
369 } else if (aElevation < -90) {
370 aElevation = -180 - aElevation;