AudioPannerNode should raise exception when distanceModel is set incorrectly
[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::setDistanceModel(unsigned short model, ExceptionCode& ec)
175 {
176     switch (model) {
177     case DistanceEffect::ModelLinear:
178     case DistanceEffect::ModelInverse:
179     case DistanceEffect::ModelExponential:
180         m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
181         break;
182     default:
183         ec = NOT_SUPPORTED_ERR;
184         break;
185     }
186 }
187
188 void AudioPannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
189 {
190     // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
191
192     double azimuth = 0.0;
193
194     // Calculate the source-listener vector
195     FloatPoint3D listenerPosition = listener()->position();
196     FloatPoint3D sourceListener = m_position - listenerPosition;
197
198     if (sourceListener.isZero()) {
199         // degenerate case if source and listener are at the same point
200         *outAzimuth = 0.0;
201         *outElevation = 0.0;
202         return;
203     }
204
205     sourceListener.normalize();
206
207     // Align axes
208     FloatPoint3D listenerFront = listener()->orientation();
209     FloatPoint3D listenerUp = listener()->upVector();
210     FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
211     listenerRight.normalize();
212
213     FloatPoint3D listenerFrontNorm = listenerFront;
214     listenerFrontNorm.normalize();
215
216     FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
217
218     float upProjection = sourceListener.dot(up);
219
220     FloatPoint3D projectedSource = sourceListener - upProjection * up;
221     projectedSource.normalize();
222
223     azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
224     fixNANs(azimuth); // avoid illegal values
225
226     // Source  in front or behind the listener
227     double frontBack = projectedSource.dot(listenerFrontNorm);
228     if (frontBack < 0.0)
229         azimuth = 360.0 - azimuth;
230
231     // Make azimuth relative to "front" and not "right" listener vector
232     if ((azimuth >= 0.0) && (azimuth <= 270.0))
233         azimuth = 90.0 - azimuth;
234     else
235         azimuth = 450.0 - azimuth;
236
237     // Elevation
238     double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
239     fixNANs(elevation); // avoid illegal values
240
241     if (elevation > 90.0)
242         elevation = 180.0 - elevation;
243     else if (elevation < -90.0)
244         elevation = -180.0 - elevation;
245
246     if (outAzimuth)
247         *outAzimuth = azimuth;
248     if (outElevation)
249         *outElevation = elevation;
250 }
251
252 float AudioPannerNode::dopplerRate()
253 {
254     double dopplerShift = 1.0;
255
256     // FIXME: optimize for case when neither source nor listener has changed...
257     double dopplerFactor = listener()->dopplerFactor();
258
259     if (dopplerFactor > 0.0) {
260         double speedOfSound = listener()->speedOfSound();
261
262         const FloatPoint3D &sourceVelocity = m_velocity;
263         const FloatPoint3D &listenerVelocity = listener()->velocity();
264
265         // Don't bother if both source and listener have no velocity
266         bool sourceHasVelocity = !sourceVelocity.isZero();
267         bool listenerHasVelocity = !listenerVelocity.isZero();
268
269         if (sourceHasVelocity || listenerHasVelocity) {
270             // Calculate the source to listener vector
271             FloatPoint3D listenerPosition = listener()->position();
272             FloatPoint3D sourceToListener = m_position - listenerPosition;
273
274             double sourceListenerMagnitude = sourceToListener.length();
275
276             double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
277             double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
278
279             listenerProjection = -listenerProjection;
280             sourceProjection = -sourceProjection;
281
282             double scaledSpeedOfSound = speedOfSound / dopplerFactor;
283             listenerProjection = min(listenerProjection, scaledSpeedOfSound);
284             sourceProjection = min(sourceProjection, scaledSpeedOfSound);
285
286             dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
287             fixNANs(dopplerShift); // avoid illegal values
288
289             // Limit the pitch shifting to 4 octaves up and 3 octaves down.
290             if (dopplerShift > 16.0)
291                 dopplerShift = 16.0;
292             else if (dopplerShift < 0.125)
293                 dopplerShift = 0.125;   
294         }
295     }
296
297     return static_cast<float>(dopplerShift);
298 }
299
300 float AudioPannerNode::distanceConeGain()
301 {
302     FloatPoint3D listenerPosition = listener()->position();
303
304     double listenerDistance = m_position.distanceTo(listenerPosition);
305     double distanceGain = m_distanceEffect.gain(listenerDistance);
306     
307     m_distanceGain->setValue(static_cast<float>(distanceGain));
308
309     // FIXME: could optimize by caching coneGain
310     double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
311     
312     m_coneGain->setValue(static_cast<float>(coneGain));
313
314     return float(distanceGain * coneGain);
315 }
316
317 void AudioPannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
318 {
319     ASSERT(node);
320     if (!node)
321         return;
322         
323     // 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.
324     if (node->nodeType() == NodeTypeAudioBufferSource) {
325         AudioBufferSourceNode* bufferSourceNode = reinterpret_cast<AudioBufferSourceNode*>(node);
326         bufferSourceNode->setPannerNode(this);
327     } else {    
328         // Go through all inputs to this node.
329         for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
330             AudioNodeInput* input = node->input(i);
331
332             // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
333             for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
334                 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
335                 AudioNode* connectedNode = connectedOutput->node();
336                 notifyAudioSourcesConnectedToNode(connectedNode); // recurse
337             }
338         }
339     }
340 }
341
342 } // namespace WebCore
343
344 #endif // ENABLE(WEB_AUDIO)