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 39using namespace std; 40 41namespace WebCore { 42 43static void fixNANs(double &x) 44{ 45 if (isnan(x) || isinf(x)) 46 x = 0.0; 47} 48 49AudioPannerNode::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 70AudioPannerNode::~AudioPannerNode() 71{ 72 uninitialize(); 73} 74 75void 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 89void 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 122void 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 129void AudioPannerNode::initialize() 130{ 131 if (isInitialized()) 132 return; 133 134 m_panner = Panner::create(m_panningModel, sampleRate()); 135 136 AudioNode::initialize(); 137} 138 139void AudioPannerNode::uninitialize() 140{ 141 if (!isInitialized()) 142 return; 143 144 m_panner.clear(); 145 AudioNode::uninitialize(); 146} 147 148AudioListener* AudioPannerNode::listener() 149{ 150 return context()->listener(); 151} 152 153void 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 161void 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 225float 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 273float 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 290void 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