Upstream version 5.34.92.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 using namespace std;
40
41 namespace WebCore {
42
43 static void fixNANs(double &x)
44 {
45     if (std::isnan(x) || std::isinf(x))
46         x = 0.0;
47 }
48
49 PannerNode::PannerNode(AudioContext* context, float sampleRate)
50     : AudioNode(context, sampleRate)
51     , m_panningModel(Panner::PanningModelHRTF)
52     , m_lastGain(-1.0)
53     , m_connectionCount(0)
54 {
55     ScriptWrappable::init(this);
56     addInput(adoptPtr(new AudioNodeInput(this)));
57     addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
58
59     // Node-specific default mixing rules.
60     m_channelCount = 2;
61     m_channelCountMode = ClampedMax;
62     m_channelInterpretation = AudioBus::Speakers;
63
64     m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0);
65     m_coneGain = AudioParam::create(context, "coneGain", 1.0, 0.0, 1.0);
66
67     m_position = FloatPoint3D(0, 0, 0);
68     m_orientation = FloatPoint3D(1, 0, 0);
69     m_velocity = FloatPoint3D(0, 0, 0);
70
71     setNodeType(NodeTypePanner);
72
73     initialize();
74 }
75
76 PannerNode::~PannerNode()
77 {
78     uninitialize();
79 }
80
81 void PannerNode::pullInputs(size_t framesToProcess)
82 {
83     // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
84     // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
85     if (m_connectionCount != context()->connectionCount()) {
86         m_connectionCount = context()->connectionCount();
87
88         // A map for keeping track if we have visited a node or not. This prevents feedback loops
89         // from recursing infinitely. See crbug.com/331446.
90         HashMap<AudioNode*, bool> visitedNodes;
91
92         // Recursively go through all nodes connected to us
93         notifyAudioSourcesConnectedToNode(this, visitedNodes);
94     }
95
96     AudioNode::pullInputs(framesToProcess);
97 }
98
99 void PannerNode::process(size_t framesToProcess)
100 {
101     AudioBus* destination = output(0)->bus();
102
103     if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
104         destination->zero();
105         return;
106     }
107
108     AudioBus* source = input(0)->bus();
109
110     if (!source) {
111         destination->zero();
112         return;
113     }
114
115     // The audio thread can't block on this lock, so we call tryLock() instead.
116     MutexTryLocker tryLocker(m_pannerLock);
117     if (tryLocker.locked()) {
118         // Apply the panning effect.
119         double azimuth;
120         double elevation;
121         getAzimuthElevation(&azimuth, &elevation);
122         m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
123
124         // Get the distance and cone gain.
125         double totalGain = distanceConeGain();
126
127         // Snap to desired gain at the beginning.
128         if (m_lastGain == -1.0)
129             m_lastGain = totalGain;
130
131         // Apply gain in-place with de-zippering.
132         destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
133     } else {
134         // Too bad - The tryLock() failed. We must be in the middle of changing the panner.
135         destination->zero();
136     }
137 }
138
139 void PannerNode::initialize()
140 {
141     if (isInitialized())
142         return;
143
144     m_panner = Panner::create(m_panningModel, sampleRate(), context()->hrtfDatabaseLoader());
145
146     AudioNode::initialize();
147 }
148
149 void PannerNode::uninitialize()
150 {
151     if (!isInitialized())
152         return;
153
154     m_panner.clear();
155     AudioNode::uninitialize();
156 }
157
158 AudioListener* PannerNode::listener()
159 {
160     return context()->listener();
161 }
162
163 String PannerNode::panningModel() const
164 {
165     switch (m_panningModel) {
166     case EQUALPOWER:
167         return "equalpower";
168     case HRTF:
169         return "HRTF";
170     case SOUNDFIELD:
171         return "soundfield";
172     default:
173         ASSERT_NOT_REACHED();
174         return "HRTF";
175     }
176 }
177
178 void PannerNode::setPanningModel(const String& model)
179 {
180     if (model == "equalpower")
181         setPanningModel(EQUALPOWER);
182     else if (model == "HRTF")
183         setPanningModel(HRTF);
184     else if (model == "soundfield")
185         setPanningModel(SOUNDFIELD);
186     else
187         ASSERT_NOT_REACHED();
188 }
189
190 bool PannerNode::setPanningModel(unsigned model)
191 {
192     switch (model) {
193     case EQUALPOWER:
194     case HRTF:
195         if (!m_panner.get() || model != m_panningModel) {
196             // This synchronizes with process().
197             MutexLocker processLocker(m_pannerLock);
198
199             OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), context()->hrtfDatabaseLoader());
200             m_panner = newPanner.release();
201             m_panningModel = model;
202         }
203         break;
204     case SOUNDFIELD:
205         // FIXME: Implement sound field model. See // https://bugs.webkit.org/show_bug.cgi?id=77367.
206         context()->executionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'soundfield' panning model not implemented.");
207         break;
208     default:
209         return false;
210     }
211
212     return true;
213 }
214
215 String PannerNode::distanceModel() const
216 {
217     switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
218     case DistanceEffect::ModelLinear:
219         return "linear";
220     case DistanceEffect::ModelInverse:
221         return "inverse";
222     case DistanceEffect::ModelExponential:
223         return "exponential";
224     default:
225         ASSERT_NOT_REACHED();
226         return "inverse";
227     }
228 }
229
230 void PannerNode::setDistanceModel(const String& model)
231 {
232     if (model == "linear")
233         setDistanceModel(DistanceEffect::ModelLinear);
234     else if (model == "inverse")
235         setDistanceModel(DistanceEffect::ModelInverse);
236     else if (model == "exponential")
237         setDistanceModel(DistanceEffect::ModelExponential);
238     else
239         ASSERT_NOT_REACHED();
240 }
241
242 bool PannerNode::setDistanceModel(unsigned model)
243 {
244     switch (model) {
245     case DistanceEffect::ModelLinear:
246     case DistanceEffect::ModelInverse:
247     case DistanceEffect::ModelExponential:
248         m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
249         break;
250     default:
251         return false;
252     }
253
254     return true;
255 }
256
257 void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
258 {
259     // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
260
261     double azimuth = 0.0;
262
263     // Calculate the source-listener vector
264     FloatPoint3D listenerPosition = listener()->position();
265     FloatPoint3D sourceListener = m_position - listenerPosition;
266
267     if (sourceListener.isZero()) {
268         // degenerate case if source and listener are at the same point
269         *outAzimuth = 0.0;
270         *outElevation = 0.0;
271         return;
272     }
273
274     sourceListener.normalize();
275
276     // Align axes
277     FloatPoint3D listenerFront = listener()->orientation();
278     FloatPoint3D listenerUp = listener()->upVector();
279     FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
280     listenerRight.normalize();
281
282     FloatPoint3D listenerFrontNorm = listenerFront;
283     listenerFrontNorm.normalize();
284
285     FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
286
287     float upProjection = sourceListener.dot(up);
288
289     FloatPoint3D projectedSource = sourceListener - upProjection * up;
290     projectedSource.normalize();
291
292     azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
293     fixNANs(azimuth); // avoid illegal values
294
295     // Source  in front or behind the listener
296     double frontBack = projectedSource.dot(listenerFrontNorm);
297     if (frontBack < 0.0)
298         azimuth = 360.0 - azimuth;
299
300     // Make azimuth relative to "front" and not "right" listener vector
301     if ((azimuth >= 0.0) && (azimuth <= 270.0))
302         azimuth = 90.0 - azimuth;
303     else
304         azimuth = 450.0 - azimuth;
305
306     // Elevation
307     double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
308     fixNANs(elevation); // avoid illegal values
309
310     if (elevation > 90.0)
311         elevation = 180.0 - elevation;
312     else if (elevation < -90.0)
313         elevation = -180.0 - elevation;
314
315     if (outAzimuth)
316         *outAzimuth = azimuth;
317     if (outElevation)
318         *outElevation = elevation;
319 }
320
321 float PannerNode::dopplerRate()
322 {
323     double dopplerShift = 1.0;
324
325     // FIXME: optimize for case when neither source nor listener has changed...
326     double dopplerFactor = listener()->dopplerFactor();
327
328     if (dopplerFactor > 0.0) {
329         double speedOfSound = listener()->speedOfSound();
330
331         const FloatPoint3D &sourceVelocity = m_velocity;
332         const FloatPoint3D &listenerVelocity = listener()->velocity();
333
334         // Don't bother if both source and listener have no velocity
335         bool sourceHasVelocity = !sourceVelocity.isZero();
336         bool listenerHasVelocity = !listenerVelocity.isZero();
337
338         if (sourceHasVelocity || listenerHasVelocity) {
339             // Calculate the source to listener vector
340             FloatPoint3D listenerPosition = listener()->position();
341             FloatPoint3D sourceToListener = m_position - listenerPosition;
342
343             double sourceListenerMagnitude = sourceToListener.length();
344
345             double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
346             double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
347
348             listenerProjection = -listenerProjection;
349             sourceProjection = -sourceProjection;
350
351             double scaledSpeedOfSound = speedOfSound / dopplerFactor;
352             listenerProjection = min(listenerProjection, scaledSpeedOfSound);
353             sourceProjection = min(sourceProjection, scaledSpeedOfSound);
354
355             dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
356             fixNANs(dopplerShift); // avoid illegal values
357
358             // Limit the pitch shifting to 4 octaves up and 3 octaves down.
359             if (dopplerShift > 16.0)
360                 dopplerShift = 16.0;
361             else if (dopplerShift < 0.125)
362                 dopplerShift = 0.125;
363         }
364     }
365
366     return static_cast<float>(dopplerShift);
367 }
368
369 float PannerNode::distanceConeGain()
370 {
371     FloatPoint3D listenerPosition = listener()->position();
372
373     double listenerDistance = m_position.distanceTo(listenerPosition);
374     double distanceGain = m_distanceEffect.gain(listenerDistance);
375
376     m_distanceGain->setValue(static_cast<float>(distanceGain));
377
378     // FIXME: could optimize by caching coneGain
379     double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
380
381     m_coneGain->setValue(static_cast<float>(coneGain));
382
383     return float(distanceGain * coneGain);
384 }
385
386 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
387 {
388     ASSERT(node);
389     if (!node)
390         return;
391
392     // 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.
393     if (node->nodeType() == NodeTypeAudioBufferSource) {
394         AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
395         bufferSourceNode->setPannerNode(this);
396     } else {
397         // Go through all inputs to this node.
398         for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
399             AudioNodeInput* input = node->input(i);
400
401             // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
402             for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
403                 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
404                 AudioNode* connectedNode = connectedOutput->node();
405                 HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
406
407                 // If we've seen this node already, we don't need to process it again. Otherwise,
408                 // mark it as visited and recurse through the node looking for sources.
409                 if (iterator == visitedNodes.end()) {
410                     visitedNodes.set(connectedNode, true);
411                     notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
412                 }
413             }
414         }
415     }
416 }
417
418 } // namespace WebCore
419
420 #endif // ENABLE(WEB_AUDIO)