diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/webaudio/AudioPannerNode.cpp | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/webaudio/AudioPannerNode.cpp')
-rw-r--r-- | Source/WebCore/webaudio/AudioPannerNode.cpp | 317 |
1 files changed, 317 insertions, 0 deletions
diff --git a/Source/WebCore/webaudio/AudioPannerNode.cpp b/Source/WebCore/webaudio/AudioPannerNode.cpp new file mode 100644 index 0000000..5c94763 --- /dev/null +++ b/Source/WebCore/webaudio/AudioPannerNode.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#if ENABLE(WEB_AUDIO) + +#include "AudioPannerNode.h" + +#include "AudioBufferSourceNode.h" +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "HRTFPanner.h" +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +static void fixNANs(double &x) +{ + if (isnan(x) || isinf(x)) + x = 0.0; +} + +AudioPannerNode::AudioPannerNode(AudioContext* context, double sampleRate) + : AudioNode(context, sampleRate) + , m_panningModel(Panner::PanningModelHRTF) + , m_lastGain(-1.0) + , m_connectionCount(0) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + m_distanceGain = AudioGain::create("distanceGain", 1.0, 0.0, 1.0); + m_coneGain = AudioGain::create("coneGain", 1.0, 0.0, 1.0); + + m_position = FloatPoint3D(0, 0, 0); + m_orientation = FloatPoint3D(1, 0, 0); + m_velocity = FloatPoint3D(0, 0, 0); + + setType(NodeTypePanner); + + initialize(); +} + +AudioPannerNode::~AudioPannerNode() +{ + uninitialize(); +} + +void AudioPannerNode::pullInputs(size_t framesToProcess) +{ + // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made. + // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes. + if (m_connectionCount != context()->connectionCount()) { + m_connectionCount = context()->connectionCount(); + + // Recursively go through all nodes connected to us. + notifyAudioSourcesConnectedToNode(this); + } + + AudioNode::pullInputs(framesToProcess); +} + +void AudioPannerNode::process(size_t framesToProcess) +{ + AudioBus* destination = output(0)->bus(); + + if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) { + destination->zero(); + return; + } + + AudioBus* source = input(0)->bus(); + + if (!source) { + destination->zero(); + return; + } + + // Apply the panning effect. + double azimuth; + double elevation; + getAzimuthElevation(&azimuth, &elevation); + m_panner->pan(azimuth, elevation, source, destination, framesToProcess); + + // Get the distance and cone gain. + double totalGain = distanceConeGain(); + + // Snap to desired gain at the beginning. + if (m_lastGain == -1.0) + m_lastGain = totalGain; + + // Apply gain in-place with de-zippering. + destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); +} + +void AudioPannerNode::reset() +{ + m_lastGain = -1.0; // force to snap to initial gain + if (m_panner.get()) + m_panner->reset(); +} + +void AudioPannerNode::initialize() +{ + if (isInitialized()) + return; + + m_panner = Panner::create(m_panningModel, sampleRate()); + + AudioNode::initialize(); +} + +void AudioPannerNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_panner.clear(); + AudioNode::uninitialize(); +} + +AudioListener* AudioPannerNode::listener() +{ + return context()->listener(); +} + +void AudioPannerNode::setPanningModel(unsigned short model) +{ + if (!m_panner.get() || model != m_panningModel) { + OwnPtr<Panner> newPanner = Panner::create(model, sampleRate()); + m_panner = newPanner.release(); + } +} + +void AudioPannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation) +{ + // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made. + + double azimuth = 0.0; + + // Calculate the source-listener vector + FloatPoint3D listenerPosition = listener()->position(); + FloatPoint3D sourceListener = m_position - listenerPosition; + + if (sourceListener.isZero()) { + // degenerate case if source and listener are at the same point + *outAzimuth = 0.0; + *outElevation = 0.0; + return; + } + + sourceListener.normalize(); + + // Align axes + FloatPoint3D listenerFront = listener()->orientation(); + FloatPoint3D listenerUp = listener()->upVector(); + FloatPoint3D listenerRight = listenerFront.cross(listenerUp); + listenerRight.normalize(); + + FloatPoint3D listenerFrontNorm = listenerFront; + listenerFrontNorm.normalize(); + + FloatPoint3D up = listenerRight.cross(listenerFrontNorm); + + double upProjection = sourceListener.dot(up); + + FloatPoint3D projectedSource = sourceListener - upProjection * up; + projectedSource.normalize(); + + azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble; + fixNANs(azimuth); // avoid illegal values + + // Source in front or behind the listener + double frontBack = projectedSource.dot(listenerFrontNorm); + if (frontBack < 0.0) + azimuth = 360.0 - azimuth; + + // Make azimuth relative to "front" and not "right" listener vector + if ((azimuth >= 0.0) && (azimuth <= 270.0)) + azimuth = 90.0 - azimuth; + else + azimuth = 450.0 - azimuth; + + // Elevation + double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble; + fixNANs(azimuth); // avoid illegal values + + if (elevation > 90.0) + elevation = 180.0 - elevation; + else if (elevation < -90.0) + elevation = -180.0 - elevation; + + if (outAzimuth) + *outAzimuth = azimuth; + if (outElevation) + *outElevation = elevation; +} + +float AudioPannerNode::dopplerRate() +{ + double dopplerShift = 1.0; + + // FIXME: optimize for case when neither source nor listener has changed... + double dopplerFactor = listener()->dopplerFactor(); + + if (dopplerFactor > 0.0) { + double speedOfSound = listener()->speedOfSound(); + + const FloatPoint3D &sourceVelocity = m_velocity; + const FloatPoint3D &listenerVelocity = listener()->velocity(); + + // Don't bother if both source and listener have no velocity + bool sourceHasVelocity = !sourceVelocity.isZero(); + bool listenerHasVelocity = !listenerVelocity.isZero(); + + if (sourceHasVelocity || listenerHasVelocity) { + // Calculate the source to listener vector + FloatPoint3D listenerPosition = listener()->position(); + FloatPoint3D sourceToListener = m_position - listenerPosition; + + double sourceListenerMagnitude = sourceToListener.length(); + + double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude; + double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude; + + listenerProjection = -listenerProjection; + sourceProjection = -sourceProjection; + + double scaledSpeedOfSound = speedOfSound / dopplerFactor; + listenerProjection = min(listenerProjection, scaledSpeedOfSound); + sourceProjection = min(sourceProjection, scaledSpeedOfSound); + + dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection)); + fixNANs(dopplerShift); // avoid illegal values + + // Limit the pitch shifting to 4 octaves up and 3 octaves down. + if (dopplerShift > 16.0) + dopplerShift = 16.0; + else if (dopplerShift < 0.125) + dopplerShift = 0.125; + } + } + + return static_cast<float>(dopplerShift); +} + +float AudioPannerNode::distanceConeGain() +{ + FloatPoint3D listenerPosition = listener()->position(); + + double listenerDistance = m_position.distanceTo(listenerPosition); + double distanceGain = m_distanceEffect.gain(listenerDistance); + + m_distanceGain->setValue(static_cast<float>(distanceGain)); + + // FIXME: could optimize by caching coneGain + double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition); + + m_coneGain->setValue(static_cast<float>(coneGain)); + + return float(distanceGain * coneGain); +} + +void AudioPannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node) +{ + ASSERT(node); + if (!node) + return; + + // 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. + if (node->type() == NodeTypeAudioBufferSource) { + AudioBufferSourceNode* bufferSourceNode = reinterpret_cast<AudioBufferSourceNode*>(node); + bufferSourceNode->setPannerNode(this); + } else { + // Go through all inputs to this node. + for (unsigned i = 0; i < node->numberOfInputs(); ++i) { + AudioNodeInput* input = node->input(i); + + // For each input, go through all of its connections, looking for AudioBufferSourceNodes. + for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) { + AudioNodeOutput* connectedOutput = input->renderingOutput(j); + AudioNode* connectedNode = connectedOutput->node(); + notifyAudioSourcesConnectedToNode(connectedNode); // recurse + } + } + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) |