2 * Copyright (C) 2010, Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "AudioPannerNode.h"
31 #include "AudioBufferSourceNode.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>
44 static void fixNANs(double &x)
46 if (isnan(x) || isinf(x))
50 AudioPannerNode::AudioPannerNode(AudioContext* context, float sampleRate)
51 : AudioNode(context, sampleRate)
52 , m_panningModel(Panner::PanningModelHRTF)
54 , m_connectionCount(0)
56 addInput(adoptPtr(new AudioNodeInput(this)));
57 addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
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);
62 m_position = FloatPoint3D(0, 0, 0);
63 m_orientation = FloatPoint3D(1, 0, 0);
64 m_velocity = FloatPoint3D(0, 0, 0);
66 setNodeType(NodeTypePanner);
71 AudioPannerNode::~AudioPannerNode()
76 void AudioPannerNode::pullInputs(size_t framesToProcess)
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();
83 // Recursively go through all nodes connected to us.
84 notifyAudioSourcesConnectedToNode(this);
87 AudioNode::pullInputs(framesToProcess);
90 void AudioPannerNode::process(size_t framesToProcess)
92 AudioBus* destination = output(0)->bus();
94 if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
99 AudioBus* source = input(0)->bus();
106 // Apply the panning effect.
109 getAzimuthElevation(&azimuth, &elevation);
110 m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
112 // Get the distance and cone gain.
113 double totalGain = distanceConeGain();
115 // Snap to desired gain at the beginning.
116 if (m_lastGain == -1.0)
117 m_lastGain = totalGain;
119 // Apply gain in-place with de-zippering.
120 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
123 void AudioPannerNode::reset()
125 m_lastGain = -1.0; // force to snap to initial gain
130 void AudioPannerNode::initialize()
135 m_panner = Panner::create(m_panningModel, sampleRate());
137 AudioNode::initialize();
140 void AudioPannerNode::uninitialize()
142 if (!isInitialized())
146 AudioNode::uninitialize();
149 AudioListener* AudioPannerNode::listener()
151 return context()->listener();
154 void AudioPannerNode::setPanningModel(unsigned short model, ExceptionCode& ec)
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;
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.
169 ec = NOT_SUPPORTED_ERR;
174 void AudioPannerNode::setDistanceModel(unsigned short model, ExceptionCode& ec)
177 case DistanceEffect::ModelLinear:
178 case DistanceEffect::ModelInverse:
179 case DistanceEffect::ModelExponential:
180 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
183 ec = NOT_SUPPORTED_ERR;
188 void AudioPannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
190 // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
192 double azimuth = 0.0;
194 // Calculate the source-listener vector
195 FloatPoint3D listenerPosition = listener()->position();
196 FloatPoint3D sourceListener = m_position - listenerPosition;
198 if (sourceListener.isZero()) {
199 // degenerate case if source and listener are at the same point
205 sourceListener.normalize();
208 FloatPoint3D listenerFront = listener()->orientation();
209 FloatPoint3D listenerUp = listener()->upVector();
210 FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
211 listenerRight.normalize();
213 FloatPoint3D listenerFrontNorm = listenerFront;
214 listenerFrontNorm.normalize();
216 FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
218 float upProjection = sourceListener.dot(up);
220 FloatPoint3D projectedSource = sourceListener - upProjection * up;
221 projectedSource.normalize();
223 azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
224 fixNANs(azimuth); // avoid illegal values
226 // Source in front or behind the listener
227 double frontBack = projectedSource.dot(listenerFrontNorm);
229 azimuth = 360.0 - azimuth;
231 // Make azimuth relative to "front" and not "right" listener vector
232 if ((azimuth >= 0.0) && (azimuth <= 270.0))
233 azimuth = 90.0 - azimuth;
235 azimuth = 450.0 - azimuth;
238 double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
239 fixNANs(elevation); // avoid illegal values
241 if (elevation > 90.0)
242 elevation = 180.0 - elevation;
243 else if (elevation < -90.0)
244 elevation = -180.0 - elevation;
247 *outAzimuth = azimuth;
249 *outElevation = elevation;
252 float AudioPannerNode::dopplerRate()
254 double dopplerShift = 1.0;
256 // FIXME: optimize for case when neither source nor listener has changed...
257 double dopplerFactor = listener()->dopplerFactor();
259 if (dopplerFactor > 0.0) {
260 double speedOfSound = listener()->speedOfSound();
262 const FloatPoint3D &sourceVelocity = m_velocity;
263 const FloatPoint3D &listenerVelocity = listener()->velocity();
265 // Don't bother if both source and listener have no velocity
266 bool sourceHasVelocity = !sourceVelocity.isZero();
267 bool listenerHasVelocity = !listenerVelocity.isZero();
269 if (sourceHasVelocity || listenerHasVelocity) {
270 // Calculate the source to listener vector
271 FloatPoint3D listenerPosition = listener()->position();
272 FloatPoint3D sourceToListener = m_position - listenerPosition;
274 double sourceListenerMagnitude = sourceToListener.length();
276 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
277 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
279 listenerProjection = -listenerProjection;
280 sourceProjection = -sourceProjection;
282 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
283 listenerProjection = min(listenerProjection, scaledSpeedOfSound);
284 sourceProjection = min(sourceProjection, scaledSpeedOfSound);
286 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
287 fixNANs(dopplerShift); // avoid illegal values
289 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
290 if (dopplerShift > 16.0)
292 else if (dopplerShift < 0.125)
293 dopplerShift = 0.125;
297 return static_cast<float>(dopplerShift);
300 float AudioPannerNode::distanceConeGain()
302 FloatPoint3D listenerPosition = listener()->position();
304 double listenerDistance = m_position.distanceTo(listenerPosition);
305 double distanceGain = m_distanceEffect.gain(listenerDistance);
307 m_distanceGain->setValue(static_cast<float>(distanceGain));
309 // FIXME: could optimize by caching coneGain
310 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
312 m_coneGain->setValue(static_cast<float>(coneGain));
314 return float(distanceGain * coneGain);
317 void AudioPannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
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);
328 // Go through all inputs to this node.
329 for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
330 AudioNodeInput* input = node->input(i);
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
342 } // namespace WebCore
344 #endif // ENABLE(WEB_AUDIO)