2 * Copyright (C) 2010, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "modules/webaudio/PannerNode.h"
31 #include "core/dom/ExecutionContext.h"
32 #include "platform/audio/HRTFPanner.h"
33 #include "modules/webaudio/AudioBufferSourceNode.h"
34 #include "modules/webaudio/AudioContext.h"
35 #include "modules/webaudio/AudioNodeInput.h"
36 #include "modules/webaudio/AudioNodeOutput.h"
37 #include "wtf/MathExtras.h"
41 static void fixNANs(double &x)
43 if (std::isnan(x) || std::isinf(x))
47 PannerNode::PannerNode(AudioContext* context, float sampleRate)
48 : AudioNode(context, sampleRate)
49 , m_panningModel(Panner::PanningModelHRTF)
50 , m_distanceModel(DistanceEffect::ModelInverse)
52 , m_orientation(1, 0, 0)
54 , m_isAzimuthElevationDirty(true)
55 , m_isDistanceConeGainDirty(true)
56 , m_isDopplerRateDirty(true)
59 , m_cachedElevation(0)
60 , m_cachedDistanceConeGain(1.0f)
61 , m_cachedDopplerRate(1)
62 , m_connectionCount(0)
64 // Load the HRTF database asynchronously so we don't block the Javascript thread while creating the HRTF database.
65 // The HRTF panner will return zeroes until the database is loaded.
66 listener()->createAndLoadHRTFDatabaseLoader(context->sampleRate());
68 ScriptWrappable::init(this);
70 addOutput(AudioNodeOutput::create(this, 2));
72 // Node-specific default mixing rules.
74 m_channelCountMode = ClampedMax;
75 m_channelInterpretation = AudioBus::Speakers;
77 setNodeType(NodeTypePanner);
82 PannerNode::~PannerNode()
84 ASSERT(!isInitialized());
87 void PannerNode::dispose()
93 void PannerNode::pullInputs(size_t framesToProcess)
95 // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
96 // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
97 if (m_connectionCount != context()->connectionCount()) {
98 m_connectionCount = context()->connectionCount();
100 // A map for keeping track if we have visited a node or not. This prevents feedback loops
101 // from recursing infinitely. See crbug.com/331446.
102 HashMap<AudioNode*, bool> visitedNodes;
104 // Recursively go through all nodes connected to us
105 notifyAudioSourcesConnectedToNode(this, visitedNodes);
108 AudioNode::pullInputs(framesToProcess);
111 void PannerNode::process(size_t framesToProcess)
113 AudioBus* destination = output(0)->bus();
115 if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
120 AudioBus* source = input(0)->bus();
126 // The audio thread can't block on this lock, so we call tryLock() instead.
127 MutexTryLocker tryLocker(m_processLock);
128 MutexTryLocker tryListenerLocker(listener()->listenerLock());
130 if (tryLocker.locked() && tryListenerLocker.locked()) {
131 // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF.
132 if (m_panningModel == Panner::PanningModelHRTF && !listener()->isHRTFDatabaseLoaded()) {
133 if (context()->isOfflineContext()) {
134 listener()->waitForHRTFDatabaseLoaderThreadCompletion();
141 // Apply the panning effect.
144 azimuthElevation(&azimuth, &elevation);
146 m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
148 // Get the distance and cone gain.
149 float totalGain = distanceConeGain();
151 // Snap to desired gain at the beginning.
152 if (m_lastGain == -1.0)
153 m_lastGain = totalGain;
155 // Apply gain in-place with de-zippering.
156 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
158 // Too bad - The tryLock() failed.
159 // We must be in the middle of changing the properties of the panner or the listener.
164 void PannerNode::initialize()
169 m_panner = Panner::create(m_panningModel, sampleRate(), listener()->hrtfDatabaseLoader());
170 listener()->addPanner(this);
172 AudioNode::initialize();
175 void PannerNode::uninitialize()
177 if (!isInitialized())
181 listener()->removePanner(this);
183 AudioNode::uninitialize();
186 AudioListener* PannerNode::listener()
188 return context()->listener();
191 String PannerNode::panningModel() const
193 switch (m_panningModel) {
194 case Panner::PanningModelEqualPower:
196 case Panner::PanningModelHRTF:
199 ASSERT_NOT_REACHED();
204 void PannerNode::setPanningModel(const String& model)
206 if (model == "equalpower")
207 setPanningModel(Panner::PanningModelEqualPower);
208 else if (model == "HRTF")
209 setPanningModel(Panner::PanningModelHRTF);
212 bool PannerNode::setPanningModel(unsigned model)
215 case Panner::PanningModelEqualPower:
216 case Panner::PanningModelHRTF:
217 if (!m_panner.get() || model != m_panningModel) {
218 // This synchronizes with process().
219 MutexLocker processLocker(m_processLock);
220 OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), listener()->hrtfDatabaseLoader());
221 m_panner = newPanner.release();
222 m_panningModel = model;
226 ASSERT_NOT_REACHED();
233 String PannerNode::distanceModel() const
235 switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
236 case DistanceEffect::ModelLinear:
238 case DistanceEffect::ModelInverse:
240 case DistanceEffect::ModelExponential:
241 return "exponential";
243 ASSERT_NOT_REACHED();
248 void PannerNode::setDistanceModel(const String& model)
250 if (model == "linear")
251 setDistanceModel(DistanceEffect::ModelLinear);
252 else if (model == "inverse")
253 setDistanceModel(DistanceEffect::ModelInverse);
254 else if (model == "exponential")
255 setDistanceModel(DistanceEffect::ModelExponential);
258 bool PannerNode::setDistanceModel(unsigned model)
261 case DistanceEffect::ModelLinear:
262 case DistanceEffect::ModelInverse:
263 case DistanceEffect::ModelExponential:
264 if (model != m_distanceModel) {
265 // This synchronizes with process().
266 MutexLocker processLocker(m_processLock);
267 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
268 m_distanceModel = model;
272 ASSERT_NOT_REACHED();
279 void PannerNode::setRefDistance(double distance)
281 if (refDistance() == distance)
284 // This synchronizes with process().
285 MutexLocker processLocker(m_processLock);
286 m_distanceEffect.setRefDistance(distance);
287 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
290 void PannerNode::setMaxDistance(double distance)
292 if (maxDistance() == distance)
295 // This synchronizes with process().
296 MutexLocker processLocker(m_processLock);
297 m_distanceEffect.setMaxDistance(distance);
298 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
301 void PannerNode::setRolloffFactor(double factor)
303 if (rolloffFactor() == factor)
306 // This synchronizes with process().
307 MutexLocker processLocker(m_processLock);
308 m_distanceEffect.setRolloffFactor(factor);
309 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
312 void PannerNode::setConeInnerAngle(double angle)
314 if (coneInnerAngle() == angle)
317 // This synchronizes with process().
318 MutexLocker processLocker(m_processLock);
319 m_coneEffect.setInnerAngle(angle);
320 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
323 void PannerNode::setConeOuterAngle(double angle)
325 if (coneOuterAngle() == angle)
328 // This synchronizes with process().
329 MutexLocker processLocker(m_processLock);
330 m_coneEffect.setOuterAngle(angle);
331 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
334 void PannerNode::setConeOuterGain(double angle)
336 if (coneOuterGain() == angle)
339 // This synchronizes with process().
340 MutexLocker processLocker(m_processLock);
341 m_coneEffect.setOuterGain(angle);
342 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
345 void PannerNode::setPosition(float x, float y, float z)
347 FloatPoint3D position = FloatPoint3D(x, y, z);
349 if (m_position == position)
352 // This synchronizes with process().
353 MutexLocker processLocker(m_processLock);
354 m_position = position;
355 markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
358 void PannerNode::setOrientation(float x, float y, float z)
360 FloatPoint3D orientation = FloatPoint3D(x, y, z);
362 if (m_orientation == orientation)
365 // This synchronizes with process().
366 MutexLocker processLocker(m_processLock);
367 m_orientation = orientation;
368 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
371 void PannerNode::setVelocity(float x, float y, float z)
373 FloatPoint3D velocity = FloatPoint3D(x, y, z);
375 if (m_velocity == velocity)
378 // This synchronizes with process().
379 MutexLocker processLocker(m_processLock);
380 m_velocity = velocity;
381 markPannerAsDirty(PannerNode::DopplerRateDirty);
384 void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
386 double azimuth = 0.0;
388 // Calculate the source-listener vector
389 FloatPoint3D listenerPosition = listener()->position();
390 FloatPoint3D sourceListener = m_position - listenerPosition;
392 // normalize() does nothing if the length of |sourceListener| is zero.
393 sourceListener.normalize();
396 FloatPoint3D listenerFront = listener()->orientation();
397 FloatPoint3D listenerUp = listener()->upVector();
398 FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
399 listenerRight.normalize();
401 FloatPoint3D listenerFrontNorm = listenerFront;
402 listenerFrontNorm.normalize();
404 FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
406 float upProjection = sourceListener.dot(up);
408 FloatPoint3D projectedSource = sourceListener - upProjection * up;
409 projectedSource.normalize();
411 azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
412 fixNANs(azimuth); // avoid illegal values
414 // Source in front or behind the listener
415 double frontBack = projectedSource.dot(listenerFrontNorm);
417 azimuth = 360.0 - azimuth;
419 // Make azimuth relative to "front" and not "right" listener vector
420 if ((azimuth >= 0.0) && (azimuth <= 270.0))
421 azimuth = 90.0 - azimuth;
423 azimuth = 450.0 - azimuth;
426 double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
427 fixNANs(elevation); // avoid illegal values
429 if (elevation > 90.0)
430 elevation = 180.0 - elevation;
431 else if (elevation < -90.0)
432 elevation = -180.0 - elevation;
435 *outAzimuth = azimuth;
437 *outElevation = elevation;
440 double PannerNode::calculateDopplerRate()
442 double dopplerShift = 1.0;
443 double dopplerFactor = listener()->dopplerFactor();
445 if (dopplerFactor > 0.0) {
446 double speedOfSound = listener()->speedOfSound();
448 const FloatPoint3D &sourceVelocity = m_velocity;
449 const FloatPoint3D &listenerVelocity = listener()->velocity();
451 // Don't bother if both source and listener have no velocity
452 bool sourceHasVelocity = !sourceVelocity.isZero();
453 bool listenerHasVelocity = !listenerVelocity.isZero();
455 if (sourceHasVelocity || listenerHasVelocity) {
456 // Calculate the source to listener vector
457 FloatPoint3D listenerPosition = listener()->position();
458 FloatPoint3D sourceToListener = m_position - listenerPosition;
460 double sourceListenerMagnitude = sourceToListener.length();
462 if (!sourceListenerMagnitude) {
463 // Source and listener are at the same position. Skip the computation of the doppler
464 // shift, and just return the cached value.
465 dopplerShift = m_cachedDopplerRate;
467 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
468 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
470 listenerProjection = -listenerProjection;
471 sourceProjection = -sourceProjection;
473 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
474 listenerProjection = std::min(listenerProjection, scaledSpeedOfSound);
475 sourceProjection = std::min(sourceProjection, scaledSpeedOfSound);
477 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
478 fixNANs(dopplerShift); // avoid illegal values
480 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
481 if (dopplerShift > 16.0)
483 else if (dopplerShift < 0.125)
484 dopplerShift = 0.125;
492 float PannerNode::calculateDistanceConeGain()
494 FloatPoint3D listenerPosition = listener()->position();
496 double listenerDistance = m_position.distanceTo(listenerPosition);
497 double distanceGain = m_distanceEffect.gain(listenerDistance);
498 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
500 return float(distanceGain * coneGain);
503 void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
505 ASSERT(context()->isAudioThread());
507 if (isAzimuthElevationDirty()) {
508 calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
509 m_isAzimuthElevationDirty = false;
512 *outAzimuth = m_cachedAzimuth;
513 *outElevation = m_cachedElevation;
516 double PannerNode::dopplerRate()
518 ASSERT(context()->isAudioThread());
520 if (isDopplerRateDirty()) {
521 m_cachedDopplerRate = calculateDopplerRate();
522 m_isDopplerRateDirty = false;
525 return m_cachedDopplerRate;
528 float PannerNode::distanceConeGain()
530 ASSERT(context()->isAudioThread());
532 if (isDistanceConeGainDirty()) {
533 m_cachedDistanceConeGain = calculateDistanceConeGain();
534 m_isDistanceConeGainDirty = false;
537 return m_cachedDistanceConeGain;
540 void PannerNode::markPannerAsDirty(unsigned dirty)
542 if (dirty & PannerNode::AzimuthElevationDirty)
543 m_isAzimuthElevationDirty = true;
545 if (dirty & PannerNode::DistanceConeGainDirty)
546 m_isDistanceConeGainDirty = true;
548 if (dirty & PannerNode::DopplerRateDirty)
549 m_isDopplerRateDirty = true;
552 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
558 // First check if this node is an AudioBufferSourceNode. If so, let it know about us so that doppler shift pitch can be taken into account.
559 if (node->nodeType() == NodeTypeAudioBufferSource) {
560 AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
561 bufferSourceNode->setPannerNode(this);
563 // Go through all inputs to this node.
564 for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
565 AudioNodeInput* input = node->input(i);
567 // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
568 for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
569 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
570 AudioNode* connectedNode = connectedOutput->node();
571 HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
573 // If we've seen this node already, we don't need to process it again. Otherwise,
574 // mark it as visited and recurse through the node looking for sources.
575 if (iterator == visitedNodes.end()) {
576 visitedNodes.set(connectedNode, true);
577 notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
586 #endif // ENABLE(WEB_AUDIO)