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"
17 class PannerNodeEngine
: public AudioNodeEngine
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
)
27 , mOrientation(1., 0., 0.)
30 , mMaxDistance(10000.)
32 , mConeInnerAngle(360.)
33 , mConeOuterAngle(360.)
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
45 case PannerNode::PANNING_MODEL
:
46 mPanningModel
= PanningModelType(aParam
);
47 switch (mPanningModel
) {
48 case PanningModelTypeValues::Equalpower
:
49 mPanningModelFunction
= &PannerNodeEngine::EqualPowerPanningFunction
;
51 case PanningModelTypeValues::HRTF
:
52 mPanningModelFunction
= &PannerNodeEngine::HRTFPanningFunction
;
54 case PanningModelTypeValues::Soundfield
:
55 mPanningModelFunction
= &PannerNodeEngine::SoundfieldPanningFunction
;
59 case PannerNode::DISTANCE_MODEL
:
60 mDistanceModel
= DistanceModelType(aParam
);
61 switch (mDistanceModel
) {
62 case DistanceModelTypeValues::Inverse
:
63 mDistanceModelFunction
= &PannerNodeEngine::InverseGainFunction
;
65 case DistanceModelTypeValues::Linear
:
66 mDistanceModelFunction
= &PannerNodeEngine::LinearGainFunction
;
68 case DistanceModelTypeValues::Exponential
:
69 mDistanceModelFunction
= &PannerNodeEngine::ExponentialGainFunction
;
74 NS_ERROR("Bad PannerNodeEngine Int32Parameter");
77 virtual void SetThreeDPointParameter(uint32_t aIndex
, const ThreeDPoint
& aParam
) MOZ_OVERRIDE
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;
88 NS_ERROR("Bad PannerNodeEngine ThreeDPointParameter");
91 virtual void SetDoubleParameter(uint32_t aIndex
, double aParam
) MOZ_OVERRIDE
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;
103 NS_ERROR("Bad PannerNodeEngine DoubleParameter");
107 virtual void ProduceAudioBlock(AudioNodeStream
* aStream
,
108 const AudioChunk
& aInput
,
110 bool *aFinished
) MOZ_OVERRIDE
112 if (aInput
.IsNull()) {
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
;
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
)
164 , mOrientation(1., 0., 0.)
167 , mMaxDistance(10000.)
169 , mConeInnerAngle(360.)
170 , mConeOuterAngle(360.)
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();
185 PannerNode::WrapObject(JSContext
* aCx
, JSObject
* aScope
)
187 return PannerNodeBinding::Wrap(aCx
, aScope
, this);
190 // Those three functions are described in the spec.
192 PannerNodeEngine::LinearGainFunction(float aDistance
)
194 return 1 - mRolloffFactor
* (aDistance
- mRefDistance
) / (mMaxDistance
- mRefDistance
);
198 PannerNodeEngine::InverseGainFunction(float aDistance
)
200 return mRefDistance
/ (mRefDistance
+ mRolloffFactor
* (aDistance
- mRefDistance
));
204 PannerNodeEngine::ExponentialGainFunction(float aDistance
)
206 return pow(aDistance
/ mRefDistance
, -mRolloffFactor
);
210 PannerNodeEngine::SoundfieldPanningFunction(const AudioChunk
& aInput
,
213 // not implemented: noop
218 PannerNodeEngine::HRTFPanningFunction(const AudioChunk
& aInput
,
221 // not implemented: noop
226 PannerNodeEngine::EqualPowerPanningFunction(const AudioChunk
& aInput
,
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) {
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
));
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
;
262 normalizedAzimuth
= (azimuth
+ 90.f
) / 90.f
;
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
);
281 GainStereoToStereo(aInput
, aOutput
, gainL
, gainR
, azimuth
);
284 DistanceGain(aOutput
, distanceGain
);
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
);
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
);
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.
321 PannerNodeEngine::ComputeAzimuthAndElevation(float& aAzimuth
, float& aElevation
)
323 ThreeDPoint sourceListener
= mPosition
- mListenerPosition
;
325 if (sourceListener
.IsZero()) {
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
);
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
;
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
;