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