• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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.
23  */
24 
25 #include "config.h"
26 
27 #if ENABLE(WEB_AUDIO)
28 
29 #include "modules/webaudio/PannerNode.h"
30 
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"
38 
39 using namespace std;
40 
41 namespace WebCore {
42 
fixNANs(double & x)43 static void fixNANs(double &x)
44 {
45     if (std::isnan(x) || std::isinf(x))
46         x = 0.0;
47 }
48 
PannerNode(AudioContext * context,float sampleRate)49 PannerNode::PannerNode(AudioContext* context, float sampleRate)
50     : AudioNode(context, sampleRate)
51     , m_panningModel(Panner::PanningModelHRTF)
52     , m_distanceModel(DistanceEffect::ModelInverse)
53     , m_position(0, 0, 0)
54     , m_orientation(1, 0, 0)
55     , m_velocity(0, 0, 0)
56     , m_isAzimuthElevationDirty(true)
57     , m_isDistanceConeGainDirty(true)
58     , m_isDopplerRateDirty(true)
59     , m_lastGain(-1.0)
60     , m_cachedAzimuth(0)
61     , m_cachedElevation(0)
62     , m_cachedDistanceConeGain(1.0f)
63     , m_cachedDopplerRate(1)
64     , m_connectionCount(0)
65 {
66     // Load the HRTF database asynchronously so we don't block the Javascript thread while creating the HRTF database.
67     // The HRTF panner will return zeroes until the database is loaded.
68     m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(context->sampleRate());
69 
70     ScriptWrappable::init(this);
71     addInput(adoptPtr(new AudioNodeInput(this)));
72     addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
73 
74     // Node-specific default mixing rules.
75     m_channelCount = 2;
76     m_channelCountMode = ClampedMax;
77     m_channelInterpretation = AudioBus::Speakers;
78 
79     setNodeType(NodeTypePanner);
80 
81     initialize();
82 }
83 
~PannerNode()84 PannerNode::~PannerNode()
85 {
86     uninitialize();
87 }
88 
pullInputs(size_t framesToProcess)89 void PannerNode::pullInputs(size_t framesToProcess)
90 {
91     // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
92     // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
93     if (m_connectionCount != context()->connectionCount()) {
94         m_connectionCount = context()->connectionCount();
95 
96         // A map for keeping track if we have visited a node or not. This prevents feedback loops
97         // from recursing infinitely. See crbug.com/331446.
98         HashMap<AudioNode*, bool> visitedNodes;
99 
100         // Recursively go through all nodes connected to us
101         notifyAudioSourcesConnectedToNode(this, visitedNodes);
102     }
103 
104     AudioNode::pullInputs(framesToProcess);
105 }
106 
process(size_t framesToProcess)107 void PannerNode::process(size_t framesToProcess)
108 {
109     AudioBus* destination = output(0)->bus();
110 
111     if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
112         destination->zero();
113         return;
114     }
115 
116     AudioBus* source = input(0)->bus();
117     if (!source) {
118         destination->zero();
119         return;
120     }
121 
122     // The audio thread can't block on this lock, so we call tryLock() instead.
123     MutexTryLocker tryLocker(m_processLock);
124     MutexTryLocker tryListenerLocker(listener()->listenerLock());
125 
126     if (tryLocker.locked() && tryListenerLocker.locked()) {
127         // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF.
128         if (m_panningModel == Panner::PanningModelHRTF && !m_hrtfDatabaseLoader->isLoaded()) {
129             if (context()->isOfflineContext()) {
130                 m_hrtfDatabaseLoader->waitForLoaderThreadCompletion();
131             } else {
132                 destination->zero();
133                 return;
134             }
135         }
136 
137         // Apply the panning effect.
138         double azimuth;
139         double elevation;
140         azimuthElevation(&azimuth, &elevation);
141 
142         m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
143 
144         // Get the distance and cone gain.
145         float totalGain = distanceConeGain();
146 
147         // Snap to desired gain at the beginning.
148         if (m_lastGain == -1.0)
149             m_lastGain = totalGain;
150 
151         // Apply gain in-place with de-zippering.
152         destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
153     } else {
154         // Too bad - The tryLock() failed.
155         // We must be in the middle of changing the properties of the panner or the listener.
156         destination->zero();
157     }
158 }
159 
initialize()160 void PannerNode::initialize()
161 {
162     if (isInitialized())
163         return;
164 
165     m_panner = Panner::create(m_panningModel, sampleRate(), m_hrtfDatabaseLoader.get());
166     listener()->addPanner(this);
167 
168     AudioNode::initialize();
169 }
170 
uninitialize()171 void PannerNode::uninitialize()
172 {
173     if (!isInitialized())
174         return;
175 
176     m_panner.clear();
177     listener()->removePanner(this);
178 
179     AudioNode::uninitialize();
180 }
181 
listener()182 AudioListener* PannerNode::listener()
183 {
184     return context()->listener();
185 }
186 
panningModel() const187 String PannerNode::panningModel() const
188 {
189     switch (m_panningModel) {
190     case Panner::PanningModelEqualPower:
191         return "equalpower";
192     case Panner::PanningModelHRTF:
193         return "HRTF";
194     default:
195         ASSERT_NOT_REACHED();
196         return "HRTF";
197     }
198 }
199 
setPanningModel(const String & model)200 void PannerNode::setPanningModel(const String& model)
201 {
202     if (model == "equalpower")
203         setPanningModel(Panner::PanningModelEqualPower);
204     else if (model == "HRTF")
205         setPanningModel(Panner::PanningModelHRTF);
206 }
207 
setPanningModel(unsigned model)208 bool PannerNode::setPanningModel(unsigned model)
209 {
210     switch (model) {
211     case Panner::PanningModelEqualPower:
212     case Panner::PanningModelHRTF:
213         if (!m_panner.get() || model != m_panningModel) {
214             // This synchronizes with process().
215             MutexLocker processLocker(m_processLock);
216             OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), m_hrtfDatabaseLoader.get());
217             m_panner = newPanner.release();
218             m_panningModel = model;
219         }
220         break;
221     default:
222         ASSERT_NOT_REACHED();
223         return false;
224     }
225 
226     return true;
227 }
228 
distanceModel() const229 String PannerNode::distanceModel() const
230 {
231     switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
232     case DistanceEffect::ModelLinear:
233         return "linear";
234     case DistanceEffect::ModelInverse:
235         return "inverse";
236     case DistanceEffect::ModelExponential:
237         return "exponential";
238     default:
239         ASSERT_NOT_REACHED();
240         return "inverse";
241     }
242 }
243 
setDistanceModel(const String & model)244 void PannerNode::setDistanceModel(const String& model)
245 {
246     if (model == "linear")
247         setDistanceModel(DistanceEffect::ModelLinear);
248     else if (model == "inverse")
249         setDistanceModel(DistanceEffect::ModelInverse);
250     else if (model == "exponential")
251         setDistanceModel(DistanceEffect::ModelExponential);
252 }
253 
setDistanceModel(unsigned model)254 bool PannerNode::setDistanceModel(unsigned model)
255 {
256     switch (model) {
257     case DistanceEffect::ModelLinear:
258     case DistanceEffect::ModelInverse:
259     case DistanceEffect::ModelExponential:
260         if (model != m_distanceModel) {
261             // This synchronizes with process().
262             MutexLocker processLocker(m_processLock);
263             m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
264             m_distanceModel = model;
265         }
266         break;
267     default:
268         ASSERT_NOT_REACHED();
269         return false;
270     }
271 
272     return true;
273 }
274 
setRefDistance(double distance)275 void PannerNode::setRefDistance(double distance)
276 {
277     if (refDistance() == distance)
278         return;
279 
280     // This synchronizes with process().
281     MutexLocker processLocker(m_processLock);
282     m_distanceEffect.setRefDistance(distance);
283     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
284 }
285 
setMaxDistance(double distance)286 void PannerNode::setMaxDistance(double distance)
287 {
288     if (maxDistance() == distance)
289         return;
290 
291     // This synchronizes with process().
292     MutexLocker processLocker(m_processLock);
293     m_distanceEffect.setMaxDistance(distance);
294     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
295 }
296 
setRolloffFactor(double factor)297 void PannerNode::setRolloffFactor(double factor)
298 {
299     if (rolloffFactor() == factor)
300         return;
301 
302     // This synchronizes with process().
303     MutexLocker processLocker(m_processLock);
304     m_distanceEffect.setRolloffFactor(factor);
305     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
306 }
307 
setConeInnerAngle(double angle)308 void PannerNode::setConeInnerAngle(double angle)
309 {
310     if (coneInnerAngle() == angle)
311         return;
312 
313     // This synchronizes with process().
314     MutexLocker processLocker(m_processLock);
315     m_coneEffect.setInnerAngle(angle);
316     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
317 }
318 
setConeOuterAngle(double angle)319 void PannerNode::setConeOuterAngle(double angle)
320 {
321     if (coneOuterAngle() == angle)
322         return;
323 
324     // This synchronizes with process().
325     MutexLocker processLocker(m_processLock);
326     m_coneEffect.setOuterAngle(angle);
327     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
328 }
329 
setConeOuterGain(double angle)330 void PannerNode::setConeOuterGain(double angle)
331 {
332     if (coneOuterGain() == angle)
333         return;
334 
335     // This synchronizes with process().
336     MutexLocker processLocker(m_processLock);
337     m_coneEffect.setOuterGain(angle);
338     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
339 }
340 
setPosition(float x,float y,float z)341 void PannerNode::setPosition(float x, float y, float z)
342 {
343     FloatPoint3D position = FloatPoint3D(x, y, z);
344 
345     if (m_position == position)
346         return;
347 
348     // This synchronizes with process().
349     MutexLocker processLocker(m_processLock);
350     m_position = position;
351     markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
352 }
353 
setOrientation(float x,float y,float z)354 void PannerNode::setOrientation(float x, float y, float z)
355 {
356     FloatPoint3D orientation = FloatPoint3D(x, y, z);
357 
358     if (m_orientation == orientation)
359         return;
360 
361     // This synchronizes with process().
362     MutexLocker processLocker(m_processLock);
363     m_orientation = orientation;
364     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
365 }
366 
setVelocity(float x,float y,float z)367 void PannerNode::setVelocity(float x, float y, float z)
368 {
369     FloatPoint3D velocity = FloatPoint3D(x, y, z);
370 
371     if (m_velocity == velocity)
372         return;
373 
374     // This synchronizes with process().
375     MutexLocker processLocker(m_processLock);
376     m_velocity = velocity;
377     markPannerAsDirty(PannerNode::DopplerRateDirty);
378 }
379 
calculateAzimuthElevation(double * outAzimuth,double * outElevation)380 void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
381 {
382     double azimuth = 0.0;
383 
384     // Calculate the source-listener vector
385     FloatPoint3D listenerPosition = listener()->position();
386     FloatPoint3D sourceListener = m_position - listenerPosition;
387 
388     // normalize() does nothing if the length of |sourceListener| is zero.
389     sourceListener.normalize();
390 
391     // Align axes
392     FloatPoint3D listenerFront = listener()->orientation();
393     FloatPoint3D listenerUp = listener()->upVector();
394     FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
395     listenerRight.normalize();
396 
397     FloatPoint3D listenerFrontNorm = listenerFront;
398     listenerFrontNorm.normalize();
399 
400     FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
401 
402     float upProjection = sourceListener.dot(up);
403 
404     FloatPoint3D projectedSource = sourceListener - upProjection * up;
405     projectedSource.normalize();
406 
407     azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
408     fixNANs(azimuth); // avoid illegal values
409 
410     // Source  in front or behind the listener
411     double frontBack = projectedSource.dot(listenerFrontNorm);
412     if (frontBack < 0.0)
413         azimuth = 360.0 - azimuth;
414 
415     // Make azimuth relative to "front" and not "right" listener vector
416     if ((azimuth >= 0.0) && (azimuth <= 270.0))
417         azimuth = 90.0 - azimuth;
418     else
419         azimuth = 450.0 - azimuth;
420 
421     // Elevation
422     double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
423     fixNANs(elevation); // avoid illegal values
424 
425     if (elevation > 90.0)
426         elevation = 180.0 - elevation;
427     else if (elevation < -90.0)
428         elevation = -180.0 - elevation;
429 
430     if (outAzimuth)
431         *outAzimuth = azimuth;
432     if (outElevation)
433         *outElevation = elevation;
434 }
435 
calculateDopplerRate()436 double PannerNode::calculateDopplerRate()
437 {
438     double dopplerShift = 1.0;
439     double dopplerFactor = listener()->dopplerFactor();
440 
441     if (dopplerFactor > 0.0) {
442         double speedOfSound = listener()->speedOfSound();
443 
444         const FloatPoint3D &sourceVelocity = m_velocity;
445         const FloatPoint3D &listenerVelocity = listener()->velocity();
446 
447         // Don't bother if both source and listener have no velocity
448         bool sourceHasVelocity = !sourceVelocity.isZero();
449         bool listenerHasVelocity = !listenerVelocity.isZero();
450 
451         if (sourceHasVelocity || listenerHasVelocity) {
452             // Calculate the source to listener vector
453             FloatPoint3D listenerPosition = listener()->position();
454             FloatPoint3D sourceToListener = m_position - listenerPosition;
455 
456             double sourceListenerMagnitude = sourceToListener.length();
457 
458             if (!sourceListenerMagnitude) {
459                 // Source and listener are at the same position. Skip the computation of the doppler
460                 // shift, and just return the cached value.
461                 dopplerShift = m_cachedDopplerRate;
462             } else {
463                 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
464                 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
465 
466                 listenerProjection = -listenerProjection;
467                 sourceProjection = -sourceProjection;
468 
469                 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
470                 listenerProjection = min(listenerProjection, scaledSpeedOfSound);
471                 sourceProjection = min(sourceProjection, scaledSpeedOfSound);
472 
473                 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
474                 fixNANs(dopplerShift); // avoid illegal values
475 
476                 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
477                 if (dopplerShift > 16.0)
478                     dopplerShift = 16.0;
479                 else if (dopplerShift < 0.125)
480                     dopplerShift = 0.125;
481             }
482         }
483     }
484 
485     return dopplerShift;
486 }
487 
calculateDistanceConeGain()488 float PannerNode::calculateDistanceConeGain()
489 {
490     FloatPoint3D listenerPosition = listener()->position();
491 
492     double listenerDistance = m_position.distanceTo(listenerPosition);
493     double distanceGain = m_distanceEffect.gain(listenerDistance);
494     double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
495 
496     return float(distanceGain * coneGain);
497 }
498 
azimuthElevation(double * outAzimuth,double * outElevation)499 void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
500 {
501     ASSERT(context()->isAudioThread());
502 
503     if (isAzimuthElevationDirty()) {
504         calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
505         m_isAzimuthElevationDirty = false;
506     }
507 
508     *outAzimuth = m_cachedAzimuth;
509     *outElevation = m_cachedElevation;
510 }
511 
dopplerRate()512 double PannerNode::dopplerRate()
513 {
514     ASSERT(context()->isAudioThread());
515 
516     if (isDopplerRateDirty()) {
517         m_cachedDopplerRate = calculateDopplerRate();
518         m_isDopplerRateDirty = false;
519     }
520 
521     return m_cachedDopplerRate;
522 }
523 
distanceConeGain()524 float PannerNode::distanceConeGain()
525 {
526     ASSERT(context()->isAudioThread());
527 
528     if (isDistanceConeGainDirty()) {
529         m_cachedDistanceConeGain = calculateDistanceConeGain();
530         m_isDistanceConeGainDirty = false;
531     }
532 
533     return m_cachedDistanceConeGain;
534 }
535 
markPannerAsDirty(unsigned dirty)536 void PannerNode::markPannerAsDirty(unsigned dirty)
537 {
538     if (dirty & PannerNode::AzimuthElevationDirty)
539         m_isAzimuthElevationDirty = true;
540 
541     if (dirty & PannerNode::DistanceConeGainDirty)
542         m_isDistanceConeGainDirty = true;
543 
544     if (dirty & PannerNode::DopplerRateDirty)
545         m_isDopplerRateDirty = true;
546 }
547 
notifyAudioSourcesConnectedToNode(AudioNode * node,HashMap<AudioNode *,bool> & visitedNodes)548 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
549 {
550     ASSERT(node);
551     if (!node)
552         return;
553 
554     // 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.
555     if (node->nodeType() == NodeTypeAudioBufferSource) {
556         AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
557         bufferSourceNode->setPannerNode(this);
558     } else {
559         // Go through all inputs to this node.
560         for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
561             AudioNodeInput* input = node->input(i);
562 
563             // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
564             for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
565                 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
566                 AudioNode* connectedNode = connectedOutput->node();
567                 HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
568 
569                 // If we've seen this node already, we don't need to process it again. Otherwise,
570                 // mark it as visited and recurse through the node looking for sources.
571                 if (iterator == visitedNodes.end()) {
572                     visitedNodes.set(connectedNode, true);
573                     notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
574                 }
575             }
576         }
577     }
578 }
579 
580 } // namespace WebCore
581 
582 #endif // ENABLE(WEB_AUDIO)
583