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