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());
69 addOutput(AudioNodeOutput::create(this, 2));
71 // Node-specific default mixing rules.
73 m_channelCountMode = ClampedMax;
74 m_channelInterpretation = AudioBus::Speakers;
76 setNodeType(NodeTypePanner);
81 PannerNode::~PannerNode()
83 ASSERT(!isInitialized());
86 void PannerNode::dispose()
92 void PannerNode::pullInputs(size_t framesToProcess)
94 // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
95 // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
96 if (m_connectionCount != context()->connectionCount()) {
97 m_connectionCount = context()->connectionCount();
99 // A map for keeping track if we have visited a node or not. This prevents feedback loops
100 // from recursing infinitely. See crbug.com/331446.
101 HashMap<AudioNode*, bool> visitedNodes;
103 // Recursively go through all nodes connected to us
104 notifyAudioSourcesConnectedToNode(this, visitedNodes);
107 AudioNode::pullInputs(framesToProcess);
110 void PannerNode::process(size_t framesToProcess)
112 AudioBus* destination = output(0)->bus();
114 if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
119 AudioBus* source = input(0)->bus();
125 // The audio thread can't block on this lock, so we call tryLock() instead.
126 MutexTryLocker tryLocker(m_processLock);
127 MutexTryLocker tryListenerLocker(listener()->listenerLock());
129 if (tryLocker.locked() && tryListenerLocker.locked()) {
130 // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF.
131 if (m_panningModel == Panner::PanningModelHRTF && !listener()->isHRTFDatabaseLoaded()) {
132 if (context()->isOfflineContext()) {
133 listener()->waitForHRTFDatabaseLoaderThreadCompletion();
140 // Apply the panning effect.
143 azimuthElevation(&azimuth, &elevation);
145 m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
147 // Get the distance and cone gain.
148 float totalGain = distanceConeGain();
150 // Snap to desired gain at the beginning.
151 if (m_lastGain == -1.0)
152 m_lastGain = totalGain;
154 // Apply gain in-place with de-zippering.
155 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
157 // Too bad - The tryLock() failed.
158 // We must be in the middle of changing the properties of the panner or the listener.
163 void PannerNode::initialize()
168 m_panner = Panner::create(m_panningModel, sampleRate(), listener()->hrtfDatabaseLoader());
169 listener()->addPanner(this);
171 AudioNode::initialize();
174 void PannerNode::uninitialize()
176 if (!isInitialized())
180 listener()->removePanner(this);
182 AudioNode::uninitialize();
185 AudioListener* PannerNode::listener()
187 return context()->listener();
190 String PannerNode::panningModel() const
192 switch (m_panningModel) {
193 case Panner::PanningModelEqualPower:
195 case Panner::PanningModelHRTF:
198 ASSERT_NOT_REACHED();
203 void PannerNode::setPanningModel(const String& model)
205 if (model == "equalpower")
206 setPanningModel(Panner::PanningModelEqualPower);
207 else if (model == "HRTF")
208 setPanningModel(Panner::PanningModelHRTF);
211 bool PannerNode::setPanningModel(unsigned model)
214 case Panner::PanningModelEqualPower:
215 case Panner::PanningModelHRTF:
216 if (!m_panner.get() || model != m_panningModel) {
217 // This synchronizes with process().
218 MutexLocker processLocker(m_processLock);
219 m_panner = Panner::create(model, sampleRate(), listener()->hrtfDatabaseLoader());
220 m_panningModel = model;
224 ASSERT_NOT_REACHED();
231 String PannerNode::distanceModel() const
233 switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
234 case DistanceEffect::ModelLinear:
236 case DistanceEffect::ModelInverse:
238 case DistanceEffect::ModelExponential:
239 return "exponential";
241 ASSERT_NOT_REACHED();
246 void PannerNode::setDistanceModel(const String& model)
248 if (model == "linear")
249 setDistanceModel(DistanceEffect::ModelLinear);
250 else if (model == "inverse")
251 setDistanceModel(DistanceEffect::ModelInverse);
252 else if (model == "exponential")
253 setDistanceModel(DistanceEffect::ModelExponential);
256 bool PannerNode::setDistanceModel(unsigned model)
259 case DistanceEffect::ModelLinear:
260 case DistanceEffect::ModelInverse:
261 case DistanceEffect::ModelExponential:
262 if (model != m_distanceModel) {
263 // This synchronizes with process().
264 MutexLocker processLocker(m_processLock);
265 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
266 m_distanceModel = model;
270 ASSERT_NOT_REACHED();
277 void PannerNode::setRefDistance(double distance)
279 if (refDistance() == distance)
282 // This synchronizes with process().
283 MutexLocker processLocker(m_processLock);
284 m_distanceEffect.setRefDistance(distance);
285 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
288 void PannerNode::setMaxDistance(double distance)
290 if (maxDistance() == distance)
293 // This synchronizes with process().
294 MutexLocker processLocker(m_processLock);
295 m_distanceEffect.setMaxDistance(distance);
296 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
299 void PannerNode::setRolloffFactor(double factor)
301 if (rolloffFactor() == factor)
304 // This synchronizes with process().
305 MutexLocker processLocker(m_processLock);
306 m_distanceEffect.setRolloffFactor(factor);
307 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
310 void PannerNode::setConeInnerAngle(double angle)
312 if (coneInnerAngle() == angle)
315 // This synchronizes with process().
316 MutexLocker processLocker(m_processLock);
317 m_coneEffect.setInnerAngle(angle);
318 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
321 void PannerNode::setConeOuterAngle(double angle)
323 if (coneOuterAngle() == angle)
326 // This synchronizes with process().
327 MutexLocker processLocker(m_processLock);
328 m_coneEffect.setOuterAngle(angle);
329 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
332 void PannerNode::setConeOuterGain(double angle)
334 if (coneOuterGain() == angle)
337 // This synchronizes with process().
338 MutexLocker processLocker(m_processLock);
339 m_coneEffect.setOuterGain(angle);
340 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
343 void PannerNode::setPosition(float x, float y, float z)
345 FloatPoint3D position = FloatPoint3D(x, y, z);
347 if (m_position == position)
350 // This synchronizes with process().
351 MutexLocker processLocker(m_processLock);
352 m_position = position;
353 markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
356 void PannerNode::setOrientation(float x, float y, float z)
358 FloatPoint3D orientation = FloatPoint3D(x, y, z);
360 if (m_orientation == orientation)
363 // This synchronizes with process().
364 MutexLocker processLocker(m_processLock);
365 m_orientation = orientation;
366 markPannerAsDirty(PannerNode::DistanceConeGainDirty);
369 void PannerNode::setVelocity(float x, float y, float z)
371 FloatPoint3D velocity = FloatPoint3D(x, y, z);
373 if (m_velocity == velocity)
376 // This synchronizes with process().
377 MutexLocker processLocker(m_processLock);
378 m_velocity = velocity;
379 markPannerAsDirty(PannerNode::DopplerRateDirty);
382 void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
384 double azimuth = 0.0;
386 // Calculate the source-listener vector
387 FloatPoint3D listenerPosition = listener()->position();
388 FloatPoint3D sourceListener = m_position - listenerPosition;
390 // normalize() does nothing if the length of |sourceListener| is zero.
391 sourceListener.normalize();
394 FloatPoint3D listenerFront = listener()->orientation();
395 FloatPoint3D listenerUp = listener()->upVector();
396 FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
397 listenerRight.normalize();
399 FloatPoint3D listenerFrontNorm = listenerFront;
400 listenerFrontNorm.normalize();
402 FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
404 float upProjection = sourceListener.dot(up);
406 FloatPoint3D projectedSource = sourceListener - upProjection * up;
407 projectedSource.normalize();
409 azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
410 fixNANs(azimuth); // avoid illegal values
412 // Source in front or behind the listener
413 double frontBack = projectedSource.dot(listenerFrontNorm);
415 azimuth = 360.0 - azimuth;
417 // Make azimuth relative to "front" and not "right" listener vector
418 if ((azimuth >= 0.0) && (azimuth <= 270.0))
419 azimuth = 90.0 - azimuth;
421 azimuth = 450.0 - azimuth;
424 double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
425 fixNANs(elevation); // avoid illegal values
427 if (elevation > 90.0)
428 elevation = 180.0 - elevation;
429 else if (elevation < -90.0)
430 elevation = -180.0 - elevation;
433 *outAzimuth = azimuth;
435 *outElevation = elevation;
438 double PannerNode::calculateDopplerRate()
440 double dopplerShift = 1.0;
441 double dopplerFactor = listener()->dopplerFactor();
443 if (dopplerFactor > 0.0) {
444 double speedOfSound = listener()->speedOfSound();
446 const FloatPoint3D &sourceVelocity = m_velocity;
447 const FloatPoint3D &listenerVelocity = listener()->velocity();
449 // Don't bother if both source and listener have no velocity
450 bool sourceHasVelocity = !sourceVelocity.isZero();
451 bool listenerHasVelocity = !listenerVelocity.isZero();
453 if (sourceHasVelocity || listenerHasVelocity) {
454 // Calculate the source to listener vector
455 FloatPoint3D listenerPosition = listener()->position();
456 FloatPoint3D sourceToListener = m_position - listenerPosition;
458 double sourceListenerMagnitude = sourceToListener.length();
460 if (!sourceListenerMagnitude) {
461 // Source and listener are at the same position. Skip the computation of the doppler
462 // shift, and just return the cached value.
463 dopplerShift = m_cachedDopplerRate;
465 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
466 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
468 listenerProjection = -listenerProjection;
469 sourceProjection = -sourceProjection;
471 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
472 listenerProjection = std::min(listenerProjection, scaledSpeedOfSound);
473 sourceProjection = std::min(sourceProjection, scaledSpeedOfSound);
475 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
476 fixNANs(dopplerShift); // avoid illegal values
478 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
479 if (dopplerShift > 16.0)
481 else if (dopplerShift < 0.125)
482 dopplerShift = 0.125;
490 float PannerNode::calculateDistanceConeGain()
492 FloatPoint3D listenerPosition = listener()->position();
494 double listenerDistance = m_position.distanceTo(listenerPosition);
495 double distanceGain = m_distanceEffect.gain(listenerDistance);
496 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
498 return float(distanceGain * coneGain);
501 void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
503 ASSERT(context()->isAudioThread());
505 if (isAzimuthElevationDirty()) {
506 calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
507 m_isAzimuthElevationDirty = false;
510 *outAzimuth = m_cachedAzimuth;
511 *outElevation = m_cachedElevation;
514 double PannerNode::dopplerRate()
516 ASSERT(context()->isAudioThread());
518 if (isDopplerRateDirty()) {
519 m_cachedDopplerRate = calculateDopplerRate();
520 m_isDopplerRateDirty = false;
523 return m_cachedDopplerRate;
526 float PannerNode::distanceConeGain()
528 ASSERT(context()->isAudioThread());
530 if (isDistanceConeGainDirty()) {
531 m_cachedDistanceConeGain = calculateDistanceConeGain();
532 m_isDistanceConeGainDirty = false;
535 return m_cachedDistanceConeGain;
538 void PannerNode::markPannerAsDirty(unsigned dirty)
540 if (dirty & PannerNode::AzimuthElevationDirty)
541 m_isAzimuthElevationDirty = true;
543 if (dirty & PannerNode::DistanceConeGainDirty)
544 m_isDistanceConeGainDirty = true;
546 if (dirty & PannerNode::DopplerRateDirty)
547 m_isDopplerRateDirty = true;
550 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
556 // 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.
557 if (node->nodeType() == NodeTypeAudioBufferSource) {
558 AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
559 bufferSourceNode->setPannerNode(this);
561 // Go through all inputs to this node.
562 for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
563 AudioNodeInput* input = node->input(i);
565 // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
566 for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
567 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
568 AudioNode* connectedNode = connectedOutput->node();
569 HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
571 // If we've seen this node already, we don't need to process it again. Otherwise,
572 // mark it as visited and recurse through the node looking for sources.
573 if (iterator == visitedNodes.end()) {
574 visitedNodes.set(connectedNode, true);
575 notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
582 void PannerNode::trace(Visitor* visitor)
584 visitor->trace(m_panner);
585 AudioNode::trace(visitor);
590 #endif // ENABLE(WEB_AUDIO)