Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / modules / webaudio / PannerNode.cpp
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 namespace blink {
40
41 static void fixNANs(double &x)
42 {
43     if (std::isnan(x) || std::isinf(x))
44         x = 0.0;
45 }
46
47 PannerNode::PannerNode(AudioContext* context, float sampleRate)
48     : AudioNode(context, sampleRate)
49     , m_panningModel(Panner::PanningModelHRTF)
50     , m_distanceModel(DistanceEffect::ModelInverse)
51     , m_position(0, 0, 0)
52     , m_orientation(1, 0, 0)
53     , m_velocity(0, 0, 0)
54     , m_isAzimuthElevationDirty(true)
55     , m_isDistanceConeGainDirty(true)
56     , m_isDopplerRateDirty(true)
57     , m_lastGain(-1.0)
58     , m_cachedAzimuth(0)
59     , m_cachedElevation(0)
60     , m_cachedDistanceConeGain(1.0f)
61     , m_cachedDopplerRate(1)
62     , m_connectionCount(0)
63 {
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());
67
68     ScriptWrappable::init(this);
69     addInput();
70     addOutput(AudioNodeOutput::create(this, 2));
71
72     // Node-specific default mixing rules.
73     m_channelCount = 2;
74     m_channelCountMode = ClampedMax;
75     m_channelInterpretation = AudioBus::Speakers;
76
77     setNodeType(NodeTypePanner);
78
79     initialize();
80 }
81
82 PannerNode::~PannerNode()
83 {
84     ASSERT(!isInitialized());
85 }
86
87 void PannerNode::dispose()
88 {
89     uninitialize();
90     AudioNode::dispose();
91 }
92
93 void PannerNode::pullInputs(size_t framesToProcess)
94 {
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();
99
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;
103
104         // Recursively go through all nodes connected to us
105         notifyAudioSourcesConnectedToNode(this, visitedNodes);
106     }
107
108     AudioNode::pullInputs(framesToProcess);
109 }
110
111 void PannerNode::process(size_t framesToProcess)
112 {
113     AudioBus* destination = output(0)->bus();
114
115     if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
116         destination->zero();
117         return;
118     }
119
120     AudioBus* source = input(0)->bus();
121     if (!source) {
122         destination->zero();
123         return;
124     }
125
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());
129
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();
135             } else {
136                 destination->zero();
137                 return;
138             }
139         }
140
141         // Apply the panning effect.
142         double azimuth;
143         double elevation;
144         azimuthElevation(&azimuth, &elevation);
145
146         m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
147
148         // Get the distance and cone gain.
149         float totalGain = distanceConeGain();
150
151         // Snap to desired gain at the beginning.
152         if (m_lastGain == -1.0)
153             m_lastGain = totalGain;
154
155         // Apply gain in-place with de-zippering.
156         destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
157     } else {
158         // Too bad - The tryLock() failed.
159         // We must be in the middle of changing the properties of the panner or the listener.
160         destination->zero();
161     }
162 }
163
164 void PannerNode::initialize()
165 {
166     if (isInitialized())
167         return;
168
169     m_panner = Panner::create(m_panningModel, sampleRate(), listener()->hrtfDatabaseLoader());
170     listener()->addPanner(this);
171
172     AudioNode::initialize();
173 }
174
175 void PannerNode::uninitialize()
176 {
177     if (!isInitialized())
178         return;
179
180     m_panner.clear();
181     listener()->removePanner(this);
182
183     AudioNode::uninitialize();
184 }
185
186 AudioListener* PannerNode::listener()
187 {
188     return context()->listener();
189 }
190
191 String PannerNode::panningModel() const
192 {
193     switch (m_panningModel) {
194     case Panner::PanningModelEqualPower:
195         return "equalpower";
196     case Panner::PanningModelHRTF:
197         return "HRTF";
198     default:
199         ASSERT_NOT_REACHED();
200         return "HRTF";
201     }
202 }
203
204 void PannerNode::setPanningModel(const String& model)
205 {
206     if (model == "equalpower")
207         setPanningModel(Panner::PanningModelEqualPower);
208     else if (model == "HRTF")
209         setPanningModel(Panner::PanningModelHRTF);
210 }
211
212 bool PannerNode::setPanningModel(unsigned model)
213 {
214     switch (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;
223         }
224         break;
225     default:
226         ASSERT_NOT_REACHED();
227         return false;
228     }
229
230     return true;
231 }
232
233 String PannerNode::distanceModel() const
234 {
235     switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
236     case DistanceEffect::ModelLinear:
237         return "linear";
238     case DistanceEffect::ModelInverse:
239         return "inverse";
240     case DistanceEffect::ModelExponential:
241         return "exponential";
242     default:
243         ASSERT_NOT_REACHED();
244         return "inverse";
245     }
246 }
247
248 void PannerNode::setDistanceModel(const String& model)
249 {
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);
256 }
257
258 bool PannerNode::setDistanceModel(unsigned model)
259 {
260     switch (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;
269         }
270         break;
271     default:
272         ASSERT_NOT_REACHED();
273         return false;
274     }
275
276     return true;
277 }
278
279 void PannerNode::setRefDistance(double distance)
280 {
281     if (refDistance() == distance)
282         return;
283
284     // This synchronizes with process().
285     MutexLocker processLocker(m_processLock);
286     m_distanceEffect.setRefDistance(distance);
287     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
288 }
289
290 void PannerNode::setMaxDistance(double distance)
291 {
292     if (maxDistance() == distance)
293         return;
294
295     // This synchronizes with process().
296     MutexLocker processLocker(m_processLock);
297     m_distanceEffect.setMaxDistance(distance);
298     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
299 }
300
301 void PannerNode::setRolloffFactor(double factor)
302 {
303     if (rolloffFactor() == factor)
304         return;
305
306     // This synchronizes with process().
307     MutexLocker processLocker(m_processLock);
308     m_distanceEffect.setRolloffFactor(factor);
309     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
310 }
311
312 void PannerNode::setConeInnerAngle(double angle)
313 {
314     if (coneInnerAngle() == angle)
315         return;
316
317     // This synchronizes with process().
318     MutexLocker processLocker(m_processLock);
319     m_coneEffect.setInnerAngle(angle);
320     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
321 }
322
323 void PannerNode::setConeOuterAngle(double angle)
324 {
325     if (coneOuterAngle() == angle)
326         return;
327
328     // This synchronizes with process().
329     MutexLocker processLocker(m_processLock);
330     m_coneEffect.setOuterAngle(angle);
331     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
332 }
333
334 void PannerNode::setConeOuterGain(double angle)
335 {
336     if (coneOuterGain() == angle)
337         return;
338
339     // This synchronizes with process().
340     MutexLocker processLocker(m_processLock);
341     m_coneEffect.setOuterGain(angle);
342     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
343 }
344
345 void PannerNode::setPosition(float x, float y, float z)
346 {
347     FloatPoint3D position = FloatPoint3D(x, y, z);
348
349     if (m_position == position)
350         return;
351
352     // This synchronizes with process().
353     MutexLocker processLocker(m_processLock);
354     m_position = position;
355     markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
356 }
357
358 void PannerNode::setOrientation(float x, float y, float z)
359 {
360     FloatPoint3D orientation = FloatPoint3D(x, y, z);
361
362     if (m_orientation == orientation)
363         return;
364
365     // This synchronizes with process().
366     MutexLocker processLocker(m_processLock);
367     m_orientation = orientation;
368     markPannerAsDirty(PannerNode::DistanceConeGainDirty);
369 }
370
371 void PannerNode::setVelocity(float x, float y, float z)
372 {
373     FloatPoint3D velocity = FloatPoint3D(x, y, z);
374
375     if (m_velocity == velocity)
376         return;
377
378     // This synchronizes with process().
379     MutexLocker processLocker(m_processLock);
380     m_velocity = velocity;
381     markPannerAsDirty(PannerNode::DopplerRateDirty);
382 }
383
384 void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
385 {
386     double azimuth = 0.0;
387
388     // Calculate the source-listener vector
389     FloatPoint3D listenerPosition = listener()->position();
390     FloatPoint3D sourceListener = m_position - listenerPosition;
391
392     // normalize() does nothing if the length of |sourceListener| is zero.
393     sourceListener.normalize();
394
395     // Align axes
396     FloatPoint3D listenerFront = listener()->orientation();
397     FloatPoint3D listenerUp = listener()->upVector();
398     FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
399     listenerRight.normalize();
400
401     FloatPoint3D listenerFrontNorm = listenerFront;
402     listenerFrontNorm.normalize();
403
404     FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
405
406     float upProjection = sourceListener.dot(up);
407
408     FloatPoint3D projectedSource = sourceListener - upProjection * up;
409     projectedSource.normalize();
410
411     azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
412     fixNANs(azimuth); // avoid illegal values
413
414     // Source  in front or behind the listener
415     double frontBack = projectedSource.dot(listenerFrontNorm);
416     if (frontBack < 0.0)
417         azimuth = 360.0 - azimuth;
418
419     // Make azimuth relative to "front" and not "right" listener vector
420     if ((azimuth >= 0.0) && (azimuth <= 270.0))
421         azimuth = 90.0 - azimuth;
422     else
423         azimuth = 450.0 - azimuth;
424
425     // Elevation
426     double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
427     fixNANs(elevation); // avoid illegal values
428
429     if (elevation > 90.0)
430         elevation = 180.0 - elevation;
431     else if (elevation < -90.0)
432         elevation = -180.0 - elevation;
433
434     if (outAzimuth)
435         *outAzimuth = azimuth;
436     if (outElevation)
437         *outElevation = elevation;
438 }
439
440 double PannerNode::calculateDopplerRate()
441 {
442     double dopplerShift = 1.0;
443     double dopplerFactor = listener()->dopplerFactor();
444
445     if (dopplerFactor > 0.0) {
446         double speedOfSound = listener()->speedOfSound();
447
448         const FloatPoint3D &sourceVelocity = m_velocity;
449         const FloatPoint3D &listenerVelocity = listener()->velocity();
450
451         // Don't bother if both source and listener have no velocity
452         bool sourceHasVelocity = !sourceVelocity.isZero();
453         bool listenerHasVelocity = !listenerVelocity.isZero();
454
455         if (sourceHasVelocity || listenerHasVelocity) {
456             // Calculate the source to listener vector
457             FloatPoint3D listenerPosition = listener()->position();
458             FloatPoint3D sourceToListener = m_position - listenerPosition;
459
460             double sourceListenerMagnitude = sourceToListener.length();
461
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;
466             } else {
467                 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
468                 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
469
470                 listenerProjection = -listenerProjection;
471                 sourceProjection = -sourceProjection;
472
473                 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
474                 listenerProjection = std::min(listenerProjection, scaledSpeedOfSound);
475                 sourceProjection = std::min(sourceProjection, scaledSpeedOfSound);
476
477                 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
478                 fixNANs(dopplerShift); // avoid illegal values
479
480                 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
481                 if (dopplerShift > 16.0)
482                     dopplerShift = 16.0;
483                 else if (dopplerShift < 0.125)
484                     dopplerShift = 0.125;
485             }
486         }
487     }
488
489     return dopplerShift;
490 }
491
492 float PannerNode::calculateDistanceConeGain()
493 {
494     FloatPoint3D listenerPosition = listener()->position();
495
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);
499
500     return float(distanceGain * coneGain);
501 }
502
503 void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
504 {
505     ASSERT(context()->isAudioThread());
506
507     if (isAzimuthElevationDirty()) {
508         calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
509         m_isAzimuthElevationDirty = false;
510     }
511
512     *outAzimuth = m_cachedAzimuth;
513     *outElevation = m_cachedElevation;
514 }
515
516 double PannerNode::dopplerRate()
517 {
518     ASSERT(context()->isAudioThread());
519
520     if (isDopplerRateDirty()) {
521         m_cachedDopplerRate = calculateDopplerRate();
522         m_isDopplerRateDirty = false;
523     }
524
525     return m_cachedDopplerRate;
526 }
527
528 float PannerNode::distanceConeGain()
529 {
530     ASSERT(context()->isAudioThread());
531
532     if (isDistanceConeGainDirty()) {
533         m_cachedDistanceConeGain = calculateDistanceConeGain();
534         m_isDistanceConeGainDirty = false;
535     }
536
537     return m_cachedDistanceConeGain;
538 }
539
540 void PannerNode::markPannerAsDirty(unsigned dirty)
541 {
542     if (dirty & PannerNode::AzimuthElevationDirty)
543         m_isAzimuthElevationDirty = true;
544
545     if (dirty & PannerNode::DistanceConeGainDirty)
546         m_isDistanceConeGainDirty = true;
547
548     if (dirty & PannerNode::DopplerRateDirty)
549         m_isDopplerRateDirty = true;
550 }
551
552 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
553 {
554     ASSERT(node);
555     if (!node)
556         return;
557
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);
562     } else {
563         // Go through all inputs to this node.
564         for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
565             AudioNodeInput* input = node->input(i);
566
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);
572
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
578                 }
579             }
580         }
581     }
582 }
583
584 } // namespace blink
585
586 #endif // ENABLE(WEB_AUDIO)