Home | History | Annotate | Download | only in webaudio
      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 
     43 static void fixNANs(double &x)
     44 {
     45     if (isnan(x) || isinf(x))
     46         x = 0.0;
     47 }
     48 
     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 
     70 AudioPannerNode::~AudioPannerNode()
     71 {
     72     uninitialize();
     73 }
     74 
     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 
     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 
    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 
    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 
    139 void AudioPannerNode::uninitialize()
    140 {
    141     if (!isInitialized())
    142         return;
    143 
    144     m_panner.clear();
    145     AudioNode::uninitialize();
    146 }
    147 
    148 AudioListener* AudioPannerNode::listener()
    149 {
    150     return context()->listener();
    151 }
    152 
    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 
    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 
    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 
    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 
    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