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 "modules/webaudio/PannerNode.h"
30
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"
38
39namespace blink {
40
41static void fixNANs(double &x)
42{
43    if (std::isnan(x) || std::isinf(x))
44        x = 0.0;
45}
46
47PannerNode::PannerNode(AudioContext* context, float sampleRate)
48    : AudioNode(context, sampleRate)
49    , m_panningModel(Panner::PanningModelHRTF)
50    , m_distanceModel(DistanceEffect::ModelInverse)
51    , m_position(0, 0, 0)
52    , m_orientation(1, 0, 0)
53    , m_velocity(0, 0, 0)
54    , m_isAzimuthElevationDirty(true)
55    , m_isDistanceConeGainDirty(true)
56    , m_isDopplerRateDirty(true)
57    , m_lastGain(-1.0)
58    , m_cachedAzimuth(0)
59    , m_cachedElevation(0)
60    , m_cachedDistanceConeGain(1.0f)
61    , m_cachedDopplerRate(1)
62    , m_connectionCount(0)
63{
64    // Load the HRTF database asynchronously so we don't block the Javascript thread while creating the HRTF database.
65    // The HRTF panner will return zeroes until the database is loaded.
66    listener()->createAndLoadHRTFDatabaseLoader(context->sampleRate());
67
68    addInput();
69    addOutput(AudioNodeOutput::create(this, 2));
70
71    // Node-specific default mixing rules.
72    m_channelCount = 2;
73    m_channelCountMode = ClampedMax;
74    m_channelInterpretation = AudioBus::Speakers;
75
76    setNodeType(NodeTypePanner);
77
78    initialize();
79}
80
81PannerNode::~PannerNode()
82{
83    ASSERT(!isInitialized());
84}
85
86void PannerNode::dispose()
87{
88    uninitialize();
89    AudioNode::dispose();
90}
91
92void PannerNode::pullInputs(size_t framesToProcess)
93{
94    // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made.
95    // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes.
96    if (m_connectionCount != context()->connectionCount()) {
97        m_connectionCount = context()->connectionCount();
98
99        // A map for keeping track if we have visited a node or not. This prevents feedback loops
100        // from recursing infinitely. See crbug.com/331446.
101        HashMap<AudioNode*, bool> visitedNodes;
102
103        // Recursively go through all nodes connected to us
104        notifyAudioSourcesConnectedToNode(this, visitedNodes);
105    }
106
107    AudioNode::pullInputs(framesToProcess);
108}
109
110void PannerNode::process(size_t framesToProcess)
111{
112    AudioBus* destination = output(0)->bus();
113
114    if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) {
115        destination->zero();
116        return;
117    }
118
119    AudioBus* source = input(0)->bus();
120    if (!source) {
121        destination->zero();
122        return;
123    }
124
125    // The audio thread can't block on this lock, so we call tryLock() instead.
126    MutexTryLocker tryLocker(m_processLock);
127    MutexTryLocker tryListenerLocker(listener()->listenerLock());
128
129    if (tryLocker.locked() && tryListenerLocker.locked()) {
130        // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF.
131        if (m_panningModel == Panner::PanningModelHRTF && !listener()->isHRTFDatabaseLoaded()) {
132            if (context()->isOfflineContext()) {
133                listener()->waitForHRTFDatabaseLoaderThreadCompletion();
134            } else {
135                destination->zero();
136                return;
137            }
138        }
139
140        // Apply the panning effect.
141        double azimuth;
142        double elevation;
143        azimuthElevation(&azimuth, &elevation);
144
145        m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
146
147        // Get the distance and cone gain.
148        float totalGain = distanceConeGain();
149
150        // Snap to desired gain at the beginning.
151        if (m_lastGain == -1.0)
152            m_lastGain = totalGain;
153
154        // Apply gain in-place with de-zippering.
155        destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
156    } else {
157        // Too bad - The tryLock() failed.
158        // We must be in the middle of changing the properties of the panner or the listener.
159        destination->zero();
160    }
161}
162
163void PannerNode::initialize()
164{
165    if (isInitialized())
166        return;
167
168    m_panner = Panner::create(m_panningModel, sampleRate(), listener()->hrtfDatabaseLoader());
169    listener()->addPanner(this);
170
171    AudioNode::initialize();
172}
173
174void PannerNode::uninitialize()
175{
176    if (!isInitialized())
177        return;
178
179    m_panner.clear();
180    listener()->removePanner(this);
181
182    AudioNode::uninitialize();
183}
184
185AudioListener* PannerNode::listener()
186{
187    return context()->listener();
188}
189
190String PannerNode::panningModel() const
191{
192    switch (m_panningModel) {
193    case Panner::PanningModelEqualPower:
194        return "equalpower";
195    case Panner::PanningModelHRTF:
196        return "HRTF";
197    default:
198        ASSERT_NOT_REACHED();
199        return "HRTF";
200    }
201}
202
203void PannerNode::setPanningModel(const String& model)
204{
205    if (model == "equalpower")
206        setPanningModel(Panner::PanningModelEqualPower);
207    else if (model == "HRTF")
208        setPanningModel(Panner::PanningModelHRTF);
209}
210
211bool PannerNode::setPanningModel(unsigned model)
212{
213    switch (model) {
214    case Panner::PanningModelEqualPower:
215    case Panner::PanningModelHRTF:
216        if (!m_panner.get() || model != m_panningModel) {
217            // This synchronizes with process().
218            MutexLocker processLocker(m_processLock);
219            m_panner = Panner::create(model, sampleRate(), listener()->hrtfDatabaseLoader());
220            m_panningModel = model;
221        }
222        break;
223    default:
224        ASSERT_NOT_REACHED();
225        return false;
226    }
227
228    return true;
229}
230
231String PannerNode::distanceModel() const
232{
233    switch (const_cast<PannerNode*>(this)->m_distanceEffect.model()) {
234    case DistanceEffect::ModelLinear:
235        return "linear";
236    case DistanceEffect::ModelInverse:
237        return "inverse";
238    case DistanceEffect::ModelExponential:
239        return "exponential";
240    default:
241        ASSERT_NOT_REACHED();
242        return "inverse";
243    }
244}
245
246void PannerNode::setDistanceModel(const String& model)
247{
248    if (model == "linear")
249        setDistanceModel(DistanceEffect::ModelLinear);
250    else if (model == "inverse")
251        setDistanceModel(DistanceEffect::ModelInverse);
252    else if (model == "exponential")
253        setDistanceModel(DistanceEffect::ModelExponential);
254}
255
256bool PannerNode::setDistanceModel(unsigned model)
257{
258    switch (model) {
259    case DistanceEffect::ModelLinear:
260    case DistanceEffect::ModelInverse:
261    case DistanceEffect::ModelExponential:
262        if (model != m_distanceModel) {
263            // This synchronizes with process().
264            MutexLocker processLocker(m_processLock);
265            m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
266            m_distanceModel = model;
267        }
268        break;
269    default:
270        ASSERT_NOT_REACHED();
271        return false;
272    }
273
274    return true;
275}
276
277void PannerNode::setRefDistance(double distance)
278{
279    if (refDistance() == distance)
280        return;
281
282    // This synchronizes with process().
283    MutexLocker processLocker(m_processLock);
284    m_distanceEffect.setRefDistance(distance);
285    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
286}
287
288void PannerNode::setMaxDistance(double distance)
289{
290    if (maxDistance() == distance)
291        return;
292
293    // This synchronizes with process().
294    MutexLocker processLocker(m_processLock);
295    m_distanceEffect.setMaxDistance(distance);
296    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
297}
298
299void PannerNode::setRolloffFactor(double factor)
300{
301    if (rolloffFactor() == factor)
302        return;
303
304    // This synchronizes with process().
305    MutexLocker processLocker(m_processLock);
306    m_distanceEffect.setRolloffFactor(factor);
307    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
308}
309
310void PannerNode::setConeInnerAngle(double angle)
311{
312    if (coneInnerAngle() == angle)
313        return;
314
315    // This synchronizes with process().
316    MutexLocker processLocker(m_processLock);
317    m_coneEffect.setInnerAngle(angle);
318    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
319}
320
321void PannerNode::setConeOuterAngle(double angle)
322{
323    if (coneOuterAngle() == angle)
324        return;
325
326    // This synchronizes with process().
327    MutexLocker processLocker(m_processLock);
328    m_coneEffect.setOuterAngle(angle);
329    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
330}
331
332void PannerNode::setConeOuterGain(double angle)
333{
334    if (coneOuterGain() == angle)
335        return;
336
337    // This synchronizes with process().
338    MutexLocker processLocker(m_processLock);
339    m_coneEffect.setOuterGain(angle);
340    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
341}
342
343void PannerNode::setPosition(float x, float y, float z)
344{
345    FloatPoint3D position = FloatPoint3D(x, y, z);
346
347    if (m_position == position)
348        return;
349
350    // This synchronizes with process().
351    MutexLocker processLocker(m_processLock);
352    m_position = position;
353    markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
354}
355
356void PannerNode::setOrientation(float x, float y, float z)
357{
358    FloatPoint3D orientation = FloatPoint3D(x, y, z);
359
360    if (m_orientation == orientation)
361        return;
362
363    // This synchronizes with process().
364    MutexLocker processLocker(m_processLock);
365    m_orientation = orientation;
366    markPannerAsDirty(PannerNode::DistanceConeGainDirty);
367}
368
369void PannerNode::setVelocity(float x, float y, float z)
370{
371    FloatPoint3D velocity = FloatPoint3D(x, y, z);
372
373    if (m_velocity == velocity)
374        return;
375
376    // This synchronizes with process().
377    MutexLocker processLocker(m_processLock);
378    m_velocity = velocity;
379    markPannerAsDirty(PannerNode::DopplerRateDirty);
380}
381
382void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
383{
384    double azimuth = 0.0;
385
386    // Calculate the source-listener vector
387    FloatPoint3D listenerPosition = listener()->position();
388    FloatPoint3D sourceListener = m_position - listenerPosition;
389
390    // normalize() does nothing if the length of |sourceListener| is zero.
391    sourceListener.normalize();
392
393    // Align axes
394    FloatPoint3D listenerFront = listener()->orientation();
395    FloatPoint3D listenerUp = listener()->upVector();
396    FloatPoint3D listenerRight = listenerFront.cross(listenerUp);
397    listenerRight.normalize();
398
399    FloatPoint3D listenerFrontNorm = listenerFront;
400    listenerFrontNorm.normalize();
401
402    FloatPoint3D up = listenerRight.cross(listenerFrontNorm);
403
404    float upProjection = sourceListener.dot(up);
405
406    FloatPoint3D projectedSource = sourceListener - upProjection * up;
407    projectedSource.normalize();
408
409    azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble;
410    fixNANs(azimuth); // avoid illegal values
411
412    // Source  in front or behind the listener
413    double frontBack = projectedSource.dot(listenerFrontNorm);
414    if (frontBack < 0.0)
415        azimuth = 360.0 - azimuth;
416
417    // Make azimuth relative to "front" and not "right" listener vector
418    if ((azimuth >= 0.0) && (azimuth <= 270.0))
419        azimuth = 90.0 - azimuth;
420    else
421        azimuth = 450.0 - azimuth;
422
423    // Elevation
424    double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble;
425    fixNANs(elevation); // avoid illegal values
426
427    if (elevation > 90.0)
428        elevation = 180.0 - elevation;
429    else if (elevation < -90.0)
430        elevation = -180.0 - elevation;
431
432    if (outAzimuth)
433        *outAzimuth = azimuth;
434    if (outElevation)
435        *outElevation = elevation;
436}
437
438double PannerNode::calculateDopplerRate()
439{
440    double dopplerShift = 1.0;
441    double dopplerFactor = listener()->dopplerFactor();
442
443    if (dopplerFactor > 0.0) {
444        double speedOfSound = listener()->speedOfSound();
445
446        const FloatPoint3D &sourceVelocity = m_velocity;
447        const FloatPoint3D &listenerVelocity = listener()->velocity();
448
449        // Don't bother if both source and listener have no velocity
450        bool sourceHasVelocity = !sourceVelocity.isZero();
451        bool listenerHasVelocity = !listenerVelocity.isZero();
452
453        if (sourceHasVelocity || listenerHasVelocity) {
454            // Calculate the source to listener vector
455            FloatPoint3D listenerPosition = listener()->position();
456            FloatPoint3D sourceToListener = m_position - listenerPosition;
457
458            double sourceListenerMagnitude = sourceToListener.length();
459
460            if (!sourceListenerMagnitude) {
461                // Source and listener are at the same position. Skip the computation of the doppler
462                // shift, and just return the cached value.
463                dopplerShift = m_cachedDopplerRate;
464            } else {
465                double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
466                double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
467
468                listenerProjection = -listenerProjection;
469                sourceProjection = -sourceProjection;
470
471                double scaledSpeedOfSound = speedOfSound / dopplerFactor;
472                listenerProjection = std::min(listenerProjection, scaledSpeedOfSound);
473                sourceProjection = std::min(sourceProjection, scaledSpeedOfSound);
474
475                dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
476                fixNANs(dopplerShift); // avoid illegal values
477
478                // Limit the pitch shifting to 4 octaves up and 3 octaves down.
479                if (dopplerShift > 16.0)
480                    dopplerShift = 16.0;
481                else if (dopplerShift < 0.125)
482                    dopplerShift = 0.125;
483            }
484        }
485    }
486
487    return dopplerShift;
488}
489
490float PannerNode::calculateDistanceConeGain()
491{
492    FloatPoint3D listenerPosition = listener()->position();
493
494    double listenerDistance = m_position.distanceTo(listenerPosition);
495    double distanceGain = m_distanceEffect.gain(listenerDistance);
496    double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
497
498    return float(distanceGain * coneGain);
499}
500
501void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
502{
503    ASSERT(context()->isAudioThread());
504
505    if (isAzimuthElevationDirty()) {
506        calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
507        m_isAzimuthElevationDirty = false;
508    }
509
510    *outAzimuth = m_cachedAzimuth;
511    *outElevation = m_cachedElevation;
512}
513
514double PannerNode::dopplerRate()
515{
516    ASSERT(context()->isAudioThread());
517
518    if (isDopplerRateDirty()) {
519        m_cachedDopplerRate = calculateDopplerRate();
520        m_isDopplerRateDirty = false;
521    }
522
523    return m_cachedDopplerRate;
524}
525
526float PannerNode::distanceConeGain()
527{
528    ASSERT(context()->isAudioThread());
529
530    if (isDistanceConeGainDirty()) {
531        m_cachedDistanceConeGain = calculateDistanceConeGain();
532        m_isDistanceConeGainDirty = false;
533    }
534
535    return m_cachedDistanceConeGain;
536}
537
538void PannerNode::markPannerAsDirty(unsigned dirty)
539{
540    if (dirty & PannerNode::AzimuthElevationDirty)
541        m_isAzimuthElevationDirty = true;
542
543    if (dirty & PannerNode::DistanceConeGainDirty)
544        m_isDistanceConeGainDirty = true;
545
546    if (dirty & PannerNode::DopplerRateDirty)
547        m_isDopplerRateDirty = true;
548}
549
550void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
551{
552    ASSERT(node);
553    if (!node)
554        return;
555
556    // 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.
557    if (node->nodeType() == NodeTypeAudioBufferSource) {
558        AudioBufferSourceNode* bufferSourceNode = static_cast<AudioBufferSourceNode*>(node);
559        bufferSourceNode->setPannerNode(this);
560    } else {
561        // Go through all inputs to this node.
562        for (unsigned i = 0; i < node->numberOfInputs(); ++i) {
563            AudioNodeInput* input = node->input(i);
564
565            // For each input, go through all of its connections, looking for AudioBufferSourceNodes.
566            for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
567                AudioNodeOutput* connectedOutput = input->renderingOutput(j);
568                AudioNode* connectedNode = connectedOutput->node();
569                HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
570
571                // If we've seen this node already, we don't need to process it again. Otherwise,
572                // mark it as visited and recurse through the node looking for sources.
573                if (iterator == visitedNodes.end()) {
574                    visitedNodes.set(connectedNode, true);
575                    notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
576                }
577            }
578        }
579    }
580}
581
582void PannerNode::trace(Visitor* visitor)
583{
584    visitor->trace(m_panner);
585    AudioNode::trace(visitor);
586}
587
588} // namespace blink
589
590#endif // ENABLE(WEB_AUDIO)
591