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