AudioParam should directly be given context in create() method
[WebKit-https.git] / Source / WebCore / Modules / webaudio / AudioPannerNode.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 "AudioPannerNode.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 <wtf/MathExtras.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 static void fixNANs(double &x)
45 {
46     if (isnan(x) || isinf(x))
47         x = 0.0;
48 }
49
50 AudioPannerNode::AudioPannerNode(AudioContext* context, float sampleRate)
51     : AudioNode(context, sampleRate)
52     , m_panningModel(Panner::PanningModelHRTF)
53     , m_lastGain(-1.0)
54     , m_connectionCount(0)
55 {
56     addInput(adoptPtr(new AudioNodeInput(this)));
57     addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
58     
59     m_distanceGain = AudioGain::create(context, "distanceGain", 1.0, 0.0, 1.0);
60     m_coneGain = AudioGain::create(context, "coneGain", 1.0, 0.0, 1.0);
61
62     m_position = FloatPoint3D(0, 0, 0);
63     m_orientation = FloatPoint3D(1, 0, 0);
64     m_velocity = FloatPoint3D(0, 0, 0);
65     
66     setNodeType(NodeTypePanner);
67
68     initialize();
69 }
70
71 AudioPannerNode::~AudioPannerNode()
72 {
73     uninitialize();
74 }
75
76 void AudioPannerNode::pullInputs(size_t framesToProcess)
77 {
78     // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
79     // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
80     if (m_connectionCount != context()->connectionCount()) {
81         m_connectionCount = context()->connectionCount();
82
83         // Recursively go through all nodes connected to us.
84         notifyAudioSourcesConnectedToNode(this);
85     }
86     
87     AudioNode::pullInputs(framesToProcess);
88 }
89
90 void AudioPannerNode::process(size_t framesToProcess)
91 {
92     AudioBus* destination = output(0)->bus();
93
94     if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
95         destination->zero();
96         return;
97     }
98
99     AudioBus* source = input(0)->bus();
100
101     if (!source) {
102         destination->zero();
103         return;
104     }
105
106     // Apply the panning effect.
107     double azimuth;
108     double elevation;
109     getAzimuthElevation(&azimuth, &elevation);
110     m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
111
112     // Get the distance and cone gain.
113     double totalGain = distanceConeGain();
114
115     // Snap to desired gain at the beginning.
116     if (m_lastGain == -1.0)
117         m_lastGain = totalGain;
118         
119     // Apply gain in-place with de-zippering.
120     destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
121 }
122
123 void AudioPannerNode::reset()
124 {
125     m_lastGain = -1.0; // force to snap to initial gain
126     if (m_panner.get())
127         m_panner->reset();
128 }
129
130 void AudioPannerNode::initialize()
131 {
132     if (isInitialized())
133         return;
134         
135     m_panner = Panner::create(m_panningModel, sampleRate());
136
137     AudioNode::initialize();
138 }
139
140 void AudioPannerNode::uninitialize()
141 {
142     if (!isInitialized())
143         return;
144         
145     m_panner.clear();
146     AudioNode::uninitialize();
147 }
148
149 AudioListener* AudioPannerNode::listener()
150 {
151     return context()->listener();
152 }
153
154 void AudioPannerNode::setPanningModel(unsigned short model, ExceptionCode& ec)
155 {
156     switch (model) {
157     case EQUALPOWER:
158     case HRTF:
159         if (!m_panner.get() || model != m_panningModel) {
160             OwnPtr<Panner> newPanner = Panner::create(model, sampleRate());
161             m_panner = newPanner.release();
162             m_panningModel = model;
163         }
164         break;
165     case SOUNDFIELD:
166         // FIXME: Implement sound field model. See // https://bugs.webkit.org/show_bug.cgi?id=77367.
167         // For now, fall through to throw an exception.
168     default:
169         ec = NOT_SUPPORTED_ERR;
170         break;
171     }
172 }
173
174 void AudioPannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
175 {
176     // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
177
178     double azimuth = 0.0;
179
180     // Calculate the source-listener vector
181     FloatPoint3D listenerPosition = listener()->position();
182     FloatPoint3D sourceListener = m_position - listenerPosition;
183
184     if (sourceListener.isZero()) {
185         // degenerate case if source and listener are at the same point
186         *outAzimuth = 0.0;
187         *outElevation = 0.0;
188         return;
189     }
190
191     sourceListener.normalize();
192
193     // Align axes
194     FloatPoint3D listenerFront = listener()->orientation();
195     FloatPoint3D listenerUp = listener()->upVector();
196     FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
197     listenerRight.normalize();
198
199     FloatPoint3D listenerFrontNorm = listenerFront;
200     listenerFrontNorm.normalize();
201
202     FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
203
204     float upProjection = sourceListener.dot(up);
205
206     FloatPoint3D projectedSource = sourceListener - upProjection * up;
207     projectedSource.normalize();
208
209     azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
210     fixNANs(azimuth); // avoid illegal values
211
212     // Source  in front or behind the listener
213     double frontBack = projectedSource.dot(listenerFrontNorm);
214     if (frontBack < 0.0)
215         azimuth = 360.0 - azimuth;
216
217     // Make azimuth relative to "front" and not "right" listener vector
218     if ((azimuth >= 0.0) && (azimuth <= 270.0))
219         azimuth = 90.0 - azimuth;
220     else
221         azimuth = 450.0 - azimuth;
222
223     // Elevation
224     double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
225     fixNANs(elevation); // avoid illegal values
226
227     if (elevation > 90.0)
228         elevation = 180.0 - elevation;
229     else if (elevation < -90.0)
230         elevation = -180.0 - elevation;
231
232     if (outAzimuth)
233         *outAzimuth = azimuth;
234     if (outElevation)
235         *outElevation = elevation;
236 }
237
238 float AudioPannerNode::dopplerRate()
239 {
240     double dopplerShift = 1.0;
241
242     // FIXME: optimize for case when neither source nor listener has changed...
243     double dopplerFactor = listener()->dopplerFactor();
244
245     if (dopplerFactor > 0.0) {
246         double speedOfSound = listener()->speedOfSound();
247
248         const FloatPoint3D &sourceVelocity = m_velocity;
249         const FloatPoint3D &listenerVelocity = listener()->velocity();
250
251         // Don't bother if both source and listener have no velocity
252         bool sourceHasVelocity = !sourceVelocity.isZero();
253         bool listenerHasVelocity = !listenerVelocity.isZero();
254
255         if (sourceHasVelocity || listenerHasVelocity) {
256             // Calculate the source to listener vector
257             FloatPoint3D listenerPosition = listener()->position();
258             FloatPoint3D sourceToListener = m_position - listenerPosition;
259
260             double sourceListenerMagnitude = sourceToListener.length();
261
262             double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
263             double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
264
265             listenerProjection = -listenerProjection;
266             sourceProjection = -sourceProjection;
267
268             double scaledSpeedOfSound = speedOfSound / dopplerFactor;
269             listenerProjection = min(listenerProjection, scaledSpeedOfSound);
270             sourceProjection = min(sourceProjection, scaledSpeedOfSound);
271
272             dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
273             fixNANs(dopplerShift); // avoid illegal values
274
275             // Limit the pitch shifting to 4 octaves up and 3 octaves down.
276             if (dopplerShift > 16.0)
277                 dopplerShift = 16.0;
278             else if (dopplerShift < 0.125)
279                 dopplerShift = 0.125;   
280         }
281     }
282
283     return static_cast<float>(dopplerShift);
284 }
285
286 float AudioPannerNode::distanceConeGain()
287 {
288     FloatPoint3D listenerPosition = listener()->position();
289
290     double listenerDistance = m_position.distanceTo(listenerPosition);
291     double distanceGain = m_distanceEffect.gain(listenerDistance);
292     
293     m_distanceGain->setValue(static_cast<float>(distanceGain));
294
295     // FIXME: could optimize by caching coneGain
296     double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
297     
298     m_coneGain->setValue(static_cast<float>(coneGain));
299
300     return float(distanceGain * coneGain);
301 }
302
303 void AudioPannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
304 {
305     ASSERT(node);
306     if (!node)
307         return;
308         
309     // 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.
310     if (node->nodeType() == NodeTypeAudioBufferSource) {
311         AudioBufferSourceNode* bufferSourceNode = reinterpret_cast<AudioBufferSourceNode*>(node);
312         bufferSourceNode->setPannerNode(this);
313     } else {    
314         // Go through all inputs to this node.
315         for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
316             AudioNodeInput* input = node->input(i);
317
318             // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
319             for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
320                 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
321                 AudioNode* connectedNode = connectedOutput->node();
322                 notifyAudioSourcesConnectedToNode(connectedNode); // recurse
323             }
324         }
325     }
326 }
327
328 } // namespace WebCore
329
330 #endif // ENABLE(WEB_AUDIO)