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 "modules/webaudio/PannerNode.h"
31 #include "core/dom/ExecutionContext.h"
32 #include "platform/audio/HRTFPanner.h"
33 #include "modules/webaudio/AudioBufferSourceNode.h"
34 #include "modules/webaudio/AudioContext.h"
35 #include "modules/webaudio/AudioNodeInput.h"
36 #include "modules/webaudio/AudioNodeOutput.h"
37 #include "wtf/MathExtras.h"
43 static void fixNANs(double &x)
45 if (std::isnan(x) || std::isinf(x))
49 PannerNode::PannerNode(AudioContext* context, float sampleRate)
50 : AudioNode(context, sampleRate)
51 , m_panningModel(Panner::PanningModelHRTF)
53 , m_connectionCount(0)
55 ScriptWrappable::init(this);
56 addInput(adoptPtr(new AudioNodeInput(this)));
57 addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
59 // Node-specific default mixing rules.
61 m_channelCountMode = ClampedMax;
62 m_channelInterpretation = AudioBus::Speakers;
64 m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0);
65 m_coneGain = AudioParam::create(context, "coneGain", 1.0, 0.0, 1.0);
67 m_position = FloatPoint3D(0, 0, 0);
68 m_orientation = FloatPoint3D(1, 0, 0);
69 m_velocity = FloatPoint3D(0, 0, 0);
71 setNodeType(NodeTypePanner);
76 PannerNode::~PannerNode()
81 void PannerNode::pullInputs(size_t framesToProcess)
83 // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
84 // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
85 if (m_connectionCount != context()->connectionCount()) {
86 m_connectionCount = context()->connectionCount();
88 // A map for keeping track if we have visited a node or not. This prevents feedback loops
89 // from recursing infinitely. See crbug.com/331446.
90 HashMap<AudioNode*, bool> visitedNodes;
92 // Recursively go through all nodes connected to us
93 notifyAudioSourcesConnectedToNode(this, visitedNodes);
96 AudioNode::pullInputs(framesToProcess);
99 void PannerNode::process(size_t framesToProcess)
101 AudioBus* destination = output(0)->bus();
103 if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
108 AudioBus* source = input(0)->bus();
115 // The audio thread can't block on this lock, so we call tryLock() instead.
116 MutexTryLocker tryLocker(m_pannerLock);
117 if (tryLocker.locked()) {
118 // Apply the panning effect.
121 getAzimuthElevation(&azimuth, &elevation);
122 m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
124 // Get the distance and cone gain.
125 double totalGain = distanceConeGain();
127 // Snap to desired gain at the beginning.
128 if (m_lastGain == -1.0)
129 m_lastGain = totalGain;
131 // Apply gain in-place with de-zippering.
132 destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
134 // Too bad - The tryLock() failed. We must be in the middle of changing the panner.
139 void PannerNode::initialize()
144 m_panner = Panner::create(m_panningModel, sampleRate(), context()->hrtfDatabaseLoader());
146 AudioNode::initialize();
149 void PannerNode::uninitialize()
151 if (!isInitialized())
155 AudioNode::uninitialize();
158 AudioListener* PannerNode::listener()
160 return context()->listener();
163 String PannerNode::panningModel() const
165 switch (m_panningModel) {
173 ASSERT_NOT_REACHED();
178 void PannerNode::setPanningModel(const String& model)
180 if (model == "equalpower")
181 setPanningModel(EQUALPOWER);
182 else if (model == "HRTF")
183 setPanningModel(HRTF);
184 else if (model == "soundfield")
185 setPanningModel(SOUNDFIELD);
187 ASSERT_NOT_REACHED();
190 bool PannerNode::setPanningModel(unsigned model)
195 if (!m_panner.get() || model != m_panningModel) {
196 // This synchronizes with process().
197 MutexLocker processLocker(m_pannerLock);
199 OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), context()->hrtfDatabaseLoader());
200 m_panner = newPanner.release();
201 m_panningModel = model;
205 // FIXME: Implement sound field model. See // https://bugs.webkit.org/show_bug.cgi?id=77367.
206 context()->executionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'soundfield' panning model not implemented.");
215 String PannerNode::distanceModel() const
217 switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
218 case DistanceEffect::ModelLinear:
220 case DistanceEffect::ModelInverse:
222 case DistanceEffect::ModelExponential:
223 return "exponential";
225 ASSERT_NOT_REACHED();
230 void PannerNode::setDistanceModel(const String& model)
232 if (model == "linear")
233 setDistanceModel(DistanceEffect::ModelLinear);
234 else if (model == "inverse")
235 setDistanceModel(DistanceEffect::ModelInverse);
236 else if (model == "exponential")
237 setDistanceModel(DistanceEffect::ModelExponential);
239 ASSERT_NOT_REACHED();
242 bool PannerNode::setDistanceModel(unsigned model)
245 case DistanceEffect::ModelLinear:
246 case DistanceEffect::ModelInverse:
247 case DistanceEffect::ModelExponential:
248 m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
257 void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
259 // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
261 double azimuth = 0.0;
263 // Calculate the source-listener vector
264 FloatPoint3D listenerPosition = listener()->position();
265 FloatPoint3D sourceListener = m_position - listenerPosition;
267 if (sourceListener.isZero()) {
268 // degenerate case if source and listener are at the same point
274 sourceListener.normalize();
277 FloatPoint3D listenerFront = listener()->orientation();
278 FloatPoint3D listenerUp = listener()->upVector();
279 FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
280 listenerRight.normalize();
282 FloatPoint3D listenerFrontNorm = listenerFront;
283 listenerFrontNorm.normalize();
285 FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
287 float upProjection = sourceListener.dot(up);
289 FloatPoint3D projectedSource = sourceListener - upProjection * up;
290 projectedSource.normalize();
292 azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
293 fixNANs(azimuth); // avoid illegal values
295 // Source in front or behind the listener
296 double frontBack = projectedSource.dot(listenerFrontNorm);
298 azimuth = 360.0 - azimuth;
300 // Make azimuth relative to "front" and not "right" listener vector
301 if ((azimuth >= 0.0) && (azimuth <= 270.0))
302 azimuth = 90.0 - azimuth;
304 azimuth = 450.0 - azimuth;
307 double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
308 fixNANs(elevation); // avoid illegal values
310 if (elevation > 90.0)
311 elevation = 180.0 - elevation;
312 else if (elevation < -90.0)
313 elevation = -180.0 - elevation;
316 *outAzimuth = azimuth;
318 *outElevation = elevation;
321 float PannerNode::dopplerRate()
323 double dopplerShift = 1.0;
325 // FIXME: optimize for case when neither source nor listener has changed...
326 double dopplerFactor = listener()->dopplerFactor();
328 if (dopplerFactor > 0.0) {
329 double speedOfSound = listener()->speedOfSound();
331 const FloatPoint3D &sourceVelocity = m_velocity;
332 const FloatPoint3D &listenerVelocity = listener()->velocity();
334 // Don't bother if both source and listener have no velocity
335 bool sourceHasVelocity = !sourceVelocity.isZero();
336 bool listenerHasVelocity = !listenerVelocity.isZero();
338 if (sourceHasVelocity || listenerHasVelocity) {
339 // Calculate the source to listener vector
340 FloatPoint3D listenerPosition = listener()->position();
341 FloatPoint3D sourceToListener = m_position - listenerPosition;
343 double sourceListenerMagnitude = sourceToListener.length();
345 double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
346 double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
348 listenerProjection = -listenerProjection;
349 sourceProjection = -sourceProjection;
351 double scaledSpeedOfSound = speedOfSound / dopplerFactor;
352 listenerProjection = min(listenerProjection, scaledSpeedOfSound);
353 sourceProjection = min(sourceProjection, scaledSpeedOfSound);
355 dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
356 fixNANs(dopplerShift); // avoid illegal values
358 // Limit the pitch shifting to 4 octaves up and 3 octaves down.
359 if (dopplerShift > 16.0)
361 else if (dopplerShift < 0.125)
362 dopplerShift = 0.125;
366 return static_cast<float>(dopplerShift);
369 float PannerNode::distanceConeGain()
371 FloatPoint3D listenerPosition = listener()->position();
373 double listenerDistance = m_position.distanceTo(listenerPosition);
374 double distanceGain = m_distanceEffect.gain(listenerDistance);
376 m_distanceGain->setValue(static_cast<float>(distanceGain));
378 // FIXME: could optimize by caching coneGain
379 double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
381 m_coneGain->setValue(static_cast<float>(coneGain));
383 return float(distanceGain * coneGain);
386 void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
392 // 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.
393 if (node->nodeType() == NodeTypeAudioBufferSource) {
394 AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
395 bufferSourceNode->setPannerNode(this);
397 // Go through all inputs to this node.
398 for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
399 AudioNodeInput* input = node->input(i);
401 // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
402 for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
403 AudioNodeOutput* connectedOutput = input->renderingOutput(j);
404 AudioNode* connectedNode = connectedOutput->node();
405 HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
407 // If we've seen this node already, we don't need to process it again. Otherwise,
408 // mark it as visited and recurse through the node looking for sources.
409 if (iterator == visitedNodes.end()) {
410 visitedNodes.set(connectedNode, true);
411 notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
418 } // namespace WebCore
420 #endif // ENABLE(WEB_AUDIO)