summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp')
-rw-r--r--chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp332
1 files changed, 249 insertions, 83 deletions
diff --git a/chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp b/chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
index fcebe1273dc..029c73cf16c 100644
--- a/chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
+++ b/chromium/third_party/WebKit/Source/modules/webaudio/PannerNode.cpp
@@ -49,9 +49,24 @@ static void fixNANs(double &x)
PannerNode::PannerNode(AudioContext* context, float sampleRate)
: AudioNode(context, sampleRate)
, m_panningModel(Panner::PanningModelHRTF)
+ , m_distanceModel(DistanceEffect::ModelInverse)
+ , m_position(0, 0, 0)
+ , m_orientation(1, 0, 0)
+ , m_velocity(0, 0, 0)
+ , m_isAzimuthElevationDirty(true)
+ , m_isDistanceConeGainDirty(true)
+ , m_isDopplerRateDirty(true)
, m_lastGain(-1.0)
+ , m_cachedAzimuth(0)
+ , m_cachedElevation(0)
+ , m_cachedDistanceConeGain(1.0f)
+ , m_cachedDopplerRate(1)
, m_connectionCount(0)
{
+ // Load the HRTF database asynchronously so we don't block the Javascript thread while creating the HRTF database.
+ // The HRTF panner will return zeroes until the database is loaded.
+ m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(context->sampleRate());
+
ScriptWrappable::init(this);
addInput(adoptPtr(new AudioNodeInput(this)));
addOutput(adoptPtr(new AudioNodeOutput(this, 2)));
@@ -61,13 +76,6 @@ PannerNode::PannerNode(AudioContext* context, float sampleRate)
m_channelCountMode = ClampedMax;
m_channelInterpretation = AudioBus::Speakers;
- m_distanceGain = AudioParam::create(context, "distanceGain", 1.0, 0.0, 1.0);
- m_coneGain = AudioParam::create(context, "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);
-
setNodeType(NodeTypePanner);
initialize();
@@ -85,8 +93,12 @@ void PannerNode::pullInputs(size_t framesToProcess)
if (m_connectionCount != context()->connectionCount()) {
m_connectionCount = context()->connectionCount();
- // Recursively go through all nodes connected to us.
- notifyAudioSourcesConnectedToNode(this);
+ // A map for keeping track if we have visited a node or not. This prevents feedback loops
+ // from recursing infinitely. See crbug.com/331446.
+ HashMap<AudioNode*, bool> visitedNodes;
+
+ // Recursively go through all nodes connected to us
+ notifyAudioSourcesConnectedToNode(this, visitedNodes);
}
AudioNode::pullInputs(framesToProcess);
@@ -102,23 +114,35 @@ void PannerNode::process(size_t framesToProcess)
}
AudioBus* source = input(0)->bus();
-
if (!source) {
destination->zero();
return;
}
// The audio thread can't block on this lock, so we call tryLock() instead.
- MutexTryLocker tryLocker(m_pannerLock);
- if (tryLocker.locked()) {
+ MutexTryLocker tryLocker(m_processLock);
+ MutexTryLocker tryListenerLocker(listener()->listenerLock());
+
+ if (tryLocker.locked() && tryListenerLocker.locked()) {
+ // HRTFDatabase should be loaded before proceeding for offline audio context when the panning model is HRTF.
+ if (m_panningModel == Panner::PanningModelHRTF && !m_hrtfDatabaseLoader->isLoaded()) {
+ if (context()->isOfflineContext()) {
+ m_hrtfDatabaseLoader->waitForLoaderThreadCompletion();
+ } else {
+ destination->zero();
+ return;
+ }
+ }
+
// Apply the panning effect.
double azimuth;
double elevation;
- getAzimuthElevation(&azimuth, &elevation);
+ azimuthElevation(&azimuth, &elevation);
+
m_panner->pan(azimuth, elevation, source, destination, framesToProcess);
// Get the distance and cone gain.
- double totalGain = distanceConeGain();
+ float totalGain = distanceConeGain();
// Snap to desired gain at the beginning.
if (m_lastGain == -1.0)
@@ -127,24 +151,19 @@ void PannerNode::process(size_t framesToProcess)
// Apply gain in-place with de-zippering.
destination->copyWithGainFrom(*destination, &m_lastGain, totalGain);
} else {
- // Too bad - The tryLock() failed. We must be in the middle of changing the panner.
+ // Too bad - The tryLock() failed.
+ // We must be in the middle of changing the properties of the panner or the listener.
destination->zero();
}
}
-void PannerNode::reset()
-{
- m_lastGain = -1.0; // force to snap to initial gain
- if (m_panner.get())
- m_panner->reset();
-}
-
void PannerNode::initialize()
{
if (isInitialized())
return;
- m_panner = Panner::create(m_panningModel, sampleRate(), context()->hrtfDatabaseLoader());
+ m_panner = Panner::create(m_panningModel, sampleRate(), m_hrtfDatabaseLoader.get());
+ listener()->addPanner(this);
AudioNode::initialize();
}
@@ -155,6 +174,8 @@ void PannerNode::uninitialize()
return;
m_panner.clear();
+ listener()->removePanner(this);
+
AudioNode::uninitialize();
}
@@ -166,12 +187,10 @@ AudioListener* PannerNode::listener()
String PannerNode::panningModel() const
{
switch (m_panningModel) {
- case EQUALPOWER:
+ case Panner::PanningModelEqualPower:
return "equalpower";
- case HRTF:
+ case Panner::PanningModelHRTF:
return "HRTF";
- case SOUNDFIELD:
- return "soundfield";
default:
ASSERT_NOT_REACHED();
return "HRTF";
@@ -181,34 +200,26 @@ String PannerNode::panningModel() const
void PannerNode::setPanningModel(const String& model)
{
if (model == "equalpower")
- setPanningModel(EQUALPOWER);
+ setPanningModel(Panner::PanningModelEqualPower);
else if (model == "HRTF")
- setPanningModel(HRTF);
- else if (model == "soundfield")
- setPanningModel(SOUNDFIELD);
- else
- ASSERT_NOT_REACHED();
+ setPanningModel(Panner::PanningModelHRTF);
}
bool PannerNode::setPanningModel(unsigned model)
{
switch (model) {
- case EQUALPOWER:
- case HRTF:
+ case Panner::PanningModelEqualPower:
+ case Panner::PanningModelHRTF:
if (!m_panner.get() || model != m_panningModel) {
// This synchronizes with process().
- MutexLocker processLocker(m_pannerLock);
-
- OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), context()->hrtfDatabaseLoader());
+ MutexLocker processLocker(m_processLock);
+ OwnPtr<Panner> newPanner = Panner::create(model, sampleRate(), m_hrtfDatabaseLoader.get());
m_panner = newPanner.release();
m_panningModel = model;
}
break;
- case SOUNDFIELD:
- // FIXME: Implement sound field model. See // https://bugs.webkit.org/show_bug.cgi?id=77367.
- context()->executionContext()->addConsoleMessage(JSMessageSource, WarningMessageLevel, "'soundfield' panning model not implemented.");
- break;
default:
+ ASSERT_NOT_REACHED();
return false;
}
@@ -238,8 +249,6 @@ void PannerNode::setDistanceModel(const String& model)
setDistanceModel(DistanceEffect::ModelInverse);
else if (model == "exponential")
setDistanceModel(DistanceEffect::ModelExponential);
- else
- ASSERT_NOT_REACHED();
}
bool PannerNode::setDistanceModel(unsigned model)
@@ -248,32 +257,135 @@ bool PannerNode::setDistanceModel(unsigned model)
case DistanceEffect::ModelLinear:
case DistanceEffect::ModelInverse:
case DistanceEffect::ModelExponential:
- m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
+ if (model != m_distanceModel) {
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true);
+ m_distanceModel = model;
+ }
break;
default:
+ ASSERT_NOT_REACHED();
return false;
}
return true;
}
-void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
+void PannerNode::setRefDistance(double distance)
+{
+ if (refDistance() == distance)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_distanceEffect.setRefDistance(distance);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setMaxDistance(double distance)
+{
+ if (maxDistance() == distance)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_distanceEffect.setMaxDistance(distance);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setRolloffFactor(double factor)
+{
+ if (rolloffFactor() == factor)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_distanceEffect.setRolloffFactor(factor);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setConeInnerAngle(double angle)
+{
+ if (coneInnerAngle() == angle)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_coneEffect.setInnerAngle(angle);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setConeOuterAngle(double angle)
{
- // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made.
+ if (coneOuterAngle() == angle)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_coneEffect.setOuterAngle(angle);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+void PannerNode::setConeOuterGain(double angle)
+{
+ if (coneOuterGain() == angle)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_coneEffect.setOuterGain(angle);
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setPosition(float x, float y, float z)
+{
+ FloatPoint3D position = FloatPoint3D(x, y, z);
+
+ if (m_position == position)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_position = position;
+ markPannerAsDirty(PannerNode::AzimuthElevationDirty | PannerNode::DistanceConeGainDirty | PannerNode::DopplerRateDirty);
+}
+
+void PannerNode::setOrientation(float x, float y, float z)
+{
+ FloatPoint3D orientation = FloatPoint3D(x, y, z);
+
+ if (m_orientation == orientation)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_orientation = orientation;
+ markPannerAsDirty(PannerNode::DistanceConeGainDirty);
+}
+
+void PannerNode::setVelocity(float x, float y, float z)
+{
+ FloatPoint3D velocity = FloatPoint3D(x, y, z);
+
+ if (m_velocity == velocity)
+ return;
+
+ // This synchronizes with process().
+ MutexLocker processLocker(m_processLock);
+ m_velocity = velocity;
+ markPannerAsDirty(PannerNode::DopplerRateDirty);
+}
+
+void PannerNode::calculateAzimuthElevation(double* outAzimuth, double* outElevation)
+{
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;
- }
-
+ // normalize() does nothing if the length of |sourceListener| is zero.
sourceListener.normalize();
// Align axes
@@ -321,11 +433,9 @@ void PannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation)
*outElevation = elevation;
}
-float PannerNode::dopplerRate()
+double PannerNode::calculateDopplerRate()
{
double dopplerShift = 1.0;
-
- // FIXME: optimize for case when neither source nor listener has changed...
double dopplerFactor = listener()->dopplerFactor();
if (dopplerFactor > 0.0) {
@@ -345,48 +455,97 @@ float PannerNode::dopplerRate()
double sourceListenerMagnitude = sourceToListener.length();
- double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude;
- double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude;
+ if (!sourceListenerMagnitude) {
+ // Source and listener are at the same position. Skip the computation of the doppler
+ // shift, and just return the cached value.
+ dopplerShift = m_cachedDopplerRate;
+ } else {
+ 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;
+ }
+ }
+ }
- listenerProjection = -listenerProjection;
- sourceProjection = -sourceProjection;
+ return dopplerShift;
+}
- double scaledSpeedOfSound = speedOfSound / dopplerFactor;
- listenerProjection = min(listenerProjection, scaledSpeedOfSound);
- sourceProjection = min(sourceProjection, scaledSpeedOfSound);
+float PannerNode::calculateDistanceConeGain()
+{
+ FloatPoint3D listenerPosition = listener()->position();
- dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection));
- fixNANs(dopplerShift); // avoid illegal values
+ double listenerDistance = m_position.distanceTo(listenerPosition);
+ double distanceGain = m_distanceEffect.gain(listenerDistance);
+ double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
- // 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 float(distanceGain * coneGain);
+}
+
+void PannerNode::azimuthElevation(double* outAzimuth, double* outElevation)
+{
+ ASSERT(context()->isAudioThread());
+
+ if (isAzimuthElevationDirty()) {
+ calculateAzimuthElevation(&m_cachedAzimuth, &m_cachedElevation);
+ m_isAzimuthElevationDirty = false;
}
- return static_cast<float>(dopplerShift);
+ *outAzimuth = m_cachedAzimuth;
+ *outElevation = m_cachedElevation;
+}
+
+double PannerNode::dopplerRate()
+{
+ ASSERT(context()->isAudioThread());
+
+ if (isDopplerRateDirty()) {
+ m_cachedDopplerRate = calculateDopplerRate();
+ m_isDopplerRateDirty = false;
+ }
+
+ return m_cachedDopplerRate;
}
float PannerNode::distanceConeGain()
{
- FloatPoint3D listenerPosition = listener()->position();
+ ASSERT(context()->isAudioThread());
- double listenerDistance = m_position.distanceTo(listenerPosition);
- double distanceGain = m_distanceEffect.gain(listenerDistance);
+ if (isDistanceConeGainDirty()) {
+ m_cachedDistanceConeGain = calculateDistanceConeGain();
+ m_isDistanceConeGainDirty = false;
+ }
- m_distanceGain->setValue(static_cast<float>(distanceGain));
+ return m_cachedDistanceConeGain;
+}
- // FIXME: could optimize by caching coneGain
- double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition);
+void PannerNode::markPannerAsDirty(unsigned dirty)
+{
+ if (dirty & PannerNode::AzimuthElevationDirty)
+ m_isAzimuthElevationDirty = true;
- m_coneGain->setValue(static_cast<float>(coneGain));
+ if (dirty & PannerNode::DistanceConeGainDirty)
+ m_isDistanceConeGainDirty = true;
- return float(distanceGain * coneGain);
+ if (dirty & PannerNode::DopplerRateDirty)
+ m_isDopplerRateDirty = true;
}
-void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
+void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node, HashMap<AudioNode*, bool>& visitedNodes)
{
ASSERT(node);
if (!node)
@@ -405,7 +564,14 @@ void PannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node)
for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) {
AudioNodeOutput* connectedOutput = input->renderingOutput(j);
AudioNode* connectedNode = connectedOutput->node();
- notifyAudioSourcesConnectedToNode(connectedNode); // recurse
+ HashMap<AudioNode*, bool>::iterator iterator = visitedNodes.find(connectedNode);
+
+ // If we've seen this node already, we don't need to process it again. Otherwise,
+ // mark it as visited and recurse through the node looking for sources.
+ if (iterator == visitedNodes.end()) {
+ visitedNodes.set(connectedNode, true);
+ notifyAudioSourcesConnectedToNode(connectedNode, visitedNodes); // recurse
+ }
}
}
}