blob: a7c6ce2df26d1bdd157bf1f31f3c372a2288d6f8 [file] [log] [blame]
/*
* 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 "modules/webaudio/AudioContext.h"
#include "bindings/v8/ExceptionMessages.h"
#include "bindings/v8/ExceptionState.h"
#include "core/dom/Document.h"
#include "core/dom/ExceptionCode.h"
#include "core/html/HTMLMediaElement.h"
#include "core/inspector/ScriptCallStack.h"
#include "platform/audio/FFTFrame.h"
#include "platform/audio/HRTFDatabaseLoader.h"
#include "platform/audio/HRTFPanner.h"
#include "modules/mediastream/MediaStream.h"
#include "modules/webaudio/AnalyserNode.h"
#include "modules/webaudio/AsyncAudioDecoder.h"
#include "modules/webaudio/AudioBuffer.h"
#include "modules/webaudio/AudioBufferCallback.h"
#include "modules/webaudio/AudioBufferSourceNode.h"
#include "modules/webaudio/AudioListener.h"
#include "modules/webaudio/AudioNodeInput.h"
#include "modules/webaudio/AudioNodeOutput.h"
#include "modules/webaudio/BiquadFilterNode.h"
#include "modules/webaudio/ChannelMergerNode.h"
#include "modules/webaudio/ChannelSplitterNode.h"
#include "modules/webaudio/ConvolverNode.h"
#include "modules/webaudio/DefaultAudioDestinationNode.h"
#include "modules/webaudio/DelayNode.h"
#include "modules/webaudio/DynamicsCompressorNode.h"
#include "modules/webaudio/GainNode.h"
#include "modules/webaudio/MediaElementAudioSourceNode.h"
#include "modules/webaudio/MediaStreamAudioDestinationNode.h"
#include "modules/webaudio/MediaStreamAudioSourceNode.h"
#include "modules/webaudio/OfflineAudioCompletionEvent.h"
#include "modules/webaudio/OfflineAudioContext.h"
#include "modules/webaudio/OfflineAudioDestinationNode.h"
#include "modules/webaudio/OscillatorNode.h"
#include "modules/webaudio/PannerNode.h"
#include "modules/webaudio/PeriodicWave.h"
#include "modules/webaudio/ScriptProcessorNode.h"
#include "modules/webaudio/WaveShaperNode.h"
#if DEBUG_AUDIONODE_REFERENCES
#include <stdio.h>
#endif
#include "wtf/ArrayBuffer.h"
#include "wtf/Atomics.h"
#include "wtf/MainThread.h"
#include "wtf/OwnPtr.h"
#include "wtf/PassOwnPtr.h"
#include "wtf/RefCounted.h"
#include "wtf/text/WTFString.h"
// FIXME: check the proper way to reference an undefined thread ID
const int UndefinedThreadIdentifier = 0xffffffff;
const unsigned MaxNodesToDeletePerQuantum = 10;
namespace WebCore {
bool AudioContext::isSampleRateRangeGood(float sampleRate)
{
// FIXME: It would be nice if the minimum sample-rate could be less than 44.1KHz,
// but that will require some fixes in HRTFPanner::fftSizeForSampleRate(), and some testing there.
return sampleRate >= 44100 && sampleRate <= 96000;
}
// Don't allow more than this number of simultaneous AudioContexts talking to hardware.
const unsigned MaxHardwareContexts = 4;
unsigned AudioContext::s_hardwareContextCount = 0;
PassRefPtr<AudioContext> AudioContext::create(Document& document, ExceptionState& es)
{
ASSERT(isMainThread());
if (s_hardwareContextCount >= MaxHardwareContexts) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToConstruct(
"AudioContext",
"number of hardware contexts reached maximum (" + String::number(MaxHardwareContexts) + ")."));
return 0;
}
RefPtr<AudioContext> audioContext(adoptRef(new AudioContext(&document)));
audioContext->suspendIfNeeded();
return audioContext.release();
}
PassRefPtr<AudioContext> AudioContext::create(Document& document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& es)
{
document.addConsoleMessage(JSMessageSource, WarningMessageLevel, "Deprecated AudioContext constructor: use OfflineAudioContext instead");
return OfflineAudioContext::create(&document, numberOfChannels, numberOfFrames, sampleRate, es);
}
// Constructor for rendering to the audio hardware.
AudioContext::AudioContext(Document* document)
: ActiveDOMObject(document)
, m_isStopScheduled(false)
, m_isInitialized(false)
, m_isAudioThreadFinished(false)
, m_destinationNode(0)
, m_isDeletionScheduled(false)
, m_automaticPullNodesNeedUpdating(false)
, m_connectionCount(0)
, m_audioThread(0)
, m_graphOwnerThread(UndefinedThreadIdentifier)
, m_isOfflineContext(false)
, m_activeSourceCount(0)
{
constructCommon();
m_destinationNode = DefaultAudioDestinationNode::create(this);
// This sets in motion an asynchronous loading mechanism on another thread.
// We can check m_hrtfDatabaseLoader->isLoaded() to find out whether or not it has been fully loaded.
// It's not that useful to have a callback function for this since the audio thread automatically starts rendering on the graph
// when this has finished (see AudioDestinationNode).
m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(sampleRate());
}
// Constructor for offline (non-realtime) rendering.
AudioContext::AudioContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate)
: ActiveDOMObject(document)
, m_isStopScheduled(false)
, m_isInitialized(false)
, m_isAudioThreadFinished(false)
, m_destinationNode(0)
, m_automaticPullNodesNeedUpdating(false)
, m_connectionCount(0)
, m_audioThread(0)
, m_graphOwnerThread(UndefinedThreadIdentifier)
, m_isOfflineContext(true)
, m_activeSourceCount(0)
{
constructCommon();
// FIXME: the passed in sampleRate MUST match the hardware sample-rate since HRTFDatabaseLoader is a singleton.
m_hrtfDatabaseLoader = HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(sampleRate);
// Create a new destination for offline rendering.
m_renderTarget = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate);
ASSERT(m_renderTarget);
m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTarget.get());
ASSERT(m_destinationNode);
}
void AudioContext::constructCommon()
{
ScriptWrappable::init(this);
// According to spec AudioContext must die only after page navigate.
// Lets mark it as ActiveDOMObject with pending activity and unmark it in clear method.
setPendingActivity(this);
FFTFrame::initialize();
m_listener = AudioListener::create();
}
AudioContext::~AudioContext()
{
#if DEBUG_AUDIONODE_REFERENCES
fprintf(stderr, "%p: AudioContext::~AudioContext()\n", this);
#endif
// AudioNodes keep a reference to their context, so there should be no way to be in the destructor if there are still AudioNodes around.
ASSERT(!m_isInitialized);
ASSERT(m_isStopScheduled);
ASSERT(!m_nodesToDelete.size());
ASSERT(!m_referencedNodes.size());
ASSERT(!m_finishedNodes.size());
ASSERT(!m_automaticPullNodes.size());
if (m_automaticPullNodesNeedUpdating)
m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size());
ASSERT(!m_renderingAutomaticPullNodes.size());
}
void AudioContext::lazyInitialize()
{
if (!m_isInitialized) {
// Don't allow the context to initialize a second time after it's already been explicitly uninitialized.
ASSERT(!m_isAudioThreadFinished);
if (!m_isAudioThreadFinished) {
if (m_destinationNode.get()) {
m_destinationNode->initialize();
if (!isOfflineContext()) {
// This starts the audio thread. The destination node's provideInput() method will now be called repeatedly to render audio.
// Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum".
// NOTE: for now default AudioContext does not need an explicit startRendering() call from JavaScript.
// We may want to consider requiring it for symmetry with OfflineAudioContext.
m_destinationNode->startRendering();
++s_hardwareContextCount;
}
}
m_isInitialized = true;
}
}
}
void AudioContext::clear()
{
// We have to release our reference to the destination node before the context will ever be deleted since the destination node holds a reference to the context.
if (m_destinationNode)
m_destinationNode.clear();
// Audio thread is dead. Nobody will schedule node deletion action. Let's do it ourselves.
do {
deleteMarkedNodes();
m_nodesToDelete.append(m_nodesMarkedForDeletion);
m_nodesMarkedForDeletion.clear();
} while (m_nodesToDelete.size());
// It was set in constructCommon.
unsetPendingActivity(this);
}
void AudioContext::uninitialize()
{
ASSERT(isMainThread());
if (!m_isInitialized)
return;
// This stops the audio thread and all audio rendering.
m_destinationNode->uninitialize();
// Don't allow the context to initialize a second time after it's already been explicitly uninitialized.
m_isAudioThreadFinished = true;
if (!isOfflineContext()) {
ASSERT(s_hardwareContextCount);
--s_hardwareContextCount;
}
// Get rid of the sources which may still be playing.
derefUnfinishedSourceNodes();
m_isInitialized = false;
}
bool AudioContext::isInitialized() const
{
return m_isInitialized;
}
bool AudioContext::isRunnable() const
{
if (!isInitialized())
return false;
// Check with the HRTF spatialization system to see if it's finished loading.
return m_hrtfDatabaseLoader->isLoaded();
}
void AudioContext::stopDispatch(void* userData)
{
AudioContext* context = reinterpret_cast<AudioContext*>(userData);
ASSERT(context);
if (!context)
return;
context->uninitialize();
context->clear();
}
void AudioContext::stop()
{
// Usually ExecutionContext calls stop twice.
if (m_isStopScheduled)
return;
m_isStopScheduled = true;
// Don't call uninitialize() immediately here because the ExecutionContext is in the middle
// of dealing with all of its ActiveDOMObjects at this point. uninitialize() can de-reference other
// ActiveDOMObjects so let's schedule uninitialize() to be called later.
// FIXME: see if there's a more direct way to handle this issue.
callOnMainThread(stopDispatch, this);
}
PassRefPtr<AudioBuffer> AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionState& es)
{
RefPtr<AudioBuffer> audioBuffer = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate);
if (!audioBuffer.get()) {
if (numberOfChannels > AudioContext::maxNumberOfChannels()) {
es.throwDOMException(
NotSupportedError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"requested number of channels (" + String::number(numberOfChannels) + ") exceeds maximum (" + String::number(AudioContext::maxNumberOfChannels()) + ")"));
} else if (sampleRate < AudioBuffer::minAllowedSampleRate() || sampleRate > AudioBuffer::maxAllowedSampleRate()) {
es.throwDOMException(
NotSupportedError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"requested sample rate (" + String::number(sampleRate)
+ ") does not lie in the allowed range of "
+ String::number(AudioBuffer::minAllowedSampleRate())
+ "-" + String::number(AudioBuffer::maxAllowedSampleRate()) + " Hz"));
} else if (!numberOfFrames) {
es.throwDOMException(
NotSupportedError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"number of frames must be greater than 0."));
} else {
es.throwDOMException(
NotSupportedError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"unable to create buffer of " + String::number(numberOfChannels)
+ " channel(s) of " + String::number(numberOfFrames)
+ " frames each."));
}
return 0;
}
return audioBuffer;
}
PassRefPtr<AudioBuffer> AudioContext::createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono, ExceptionState& es)
{
ASSERT(arrayBuffer);
if (!arrayBuffer) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"invalid ArrayBuffer."));
return 0;
}
RefPtr<AudioBuffer> audioBuffer = AudioBuffer::createFromAudioFileData(arrayBuffer->data(), arrayBuffer->byteLength(), mixToMono, sampleRate());
if (!audioBuffer.get()) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToConstruct(
"AudioBuffer",
"invalid audio data in ArrayBuffer."));
return 0;
}
return audioBuffer;
}
void AudioContext::decodeAudioData(ArrayBuffer* audioData, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback, ExceptionState& es)
{
if (!audioData) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToExecute(
"decodeAudioData",
"AudioContext",
"invalid ArrayBuffer for audioData."));
return;
}
m_audioDecoder.decodeAsync(audioData, sampleRate(), successCallback, errorCallback);
}
PassRefPtr<AudioBufferSourceNode> AudioContext::createBufferSource()
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<AudioBufferSourceNode> node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate());
// Because this is an AudioScheduledSourceNode, the context keeps a reference until it has finished playing.
// When this happens, AudioScheduledSourceNode::finish() calls AudioContext::notifyNodeFinishedProcessing().
refNode(node.get());
return node;
}
PassRefPtr<MediaElementAudioSourceNode> AudioContext::createMediaElementSource(HTMLMediaElement* mediaElement, ExceptionState& es)
{
if (!mediaElement) {
es.throwDOMException(
InvalidStateError,
ExceptionMessages::failedToConstruct(
"MediaElementAudioSourceNode",
"invalid HTMLMedialElement."));
return 0;
}
ASSERT(isMainThread());
lazyInitialize();
// First check if this media element already has a source node.
if (mediaElement->audioSourceNode()) {
es.throwDOMException(
InvalidStateError,
ExceptionMessages::failedToConstruct(
"MediaElementAudioSourceNode",
"invalid HTMLMediaElement."));
return 0;
}
RefPtr<MediaElementAudioSourceNode> node = MediaElementAudioSourceNode::create(this, mediaElement);
mediaElement->setAudioSourceNode(node.get());
refNode(node.get()); // context keeps reference until node is disconnected
return node;
}
PassRefPtr<MediaStreamAudioSourceNode> AudioContext::createMediaStreamSource(MediaStream* mediaStream, ExceptionState& es)
{
if (!mediaStream) {
es.throwDOMException(
InvalidStateError,
ExceptionMessages::failedToConstruct(
"MediaStreamAudioSourceNode",
"invalid MediaStream source"));
return 0;
}
ASSERT(isMainThread());
lazyInitialize();
AudioSourceProvider* provider = 0;
MediaStreamTrackVector audioTracks = mediaStream->getAudioTracks();
// FIXME: get a provider for non-local MediaStreams (like from a remote peer).
for (size_t i = 0; i < audioTracks.size(); ++i) {
RefPtr<MediaStreamTrack> localAudio = audioTracks[i];
if (localAudio->component()->audioSourceProvider()) {
provider = localAudio->component()->audioSourceProvider();
break;
}
}
RefPtr<MediaStreamAudioSourceNode> node = MediaStreamAudioSourceNode::create(this, mediaStream, provider);
// FIXME: Only stereo streams are supported right now. We should be able to accept multi-channel streams.
node->setFormat(2, sampleRate());
refNode(node.get()); // context keeps reference until node is disconnected
return node;
}
PassRefPtr<MediaStreamAudioDestinationNode> AudioContext::createMediaStreamDestination()
{
// FIXME: Add support for an optional argument which specifies the number of channels.
// FIXME: The default should probably be stereo instead of mono.
return MediaStreamAudioDestinationNode::create(this, 1);
}
PassRefPtr<ScriptProcessorNode> AudioContext::createScriptProcessor(ExceptionState& es)
{
// Set number of input/output channels to stereo by default.
return createScriptProcessor(0, 2, 2, es);
}
PassRefPtr<ScriptProcessorNode> AudioContext::createScriptProcessor(size_t bufferSize, ExceptionState& es)
{
// Set number of input/output channels to stereo by default.
return createScriptProcessor(bufferSize, 2, 2, es);
}
PassRefPtr<ScriptProcessorNode> AudioContext::createScriptProcessor(size_t bufferSize, size_t numberOfInputChannels, ExceptionState& es)
{
// Set number of output channels to stereo by default.
return createScriptProcessor(bufferSize, numberOfInputChannels, 2, es);
}
PassRefPtr<ScriptProcessorNode> AudioContext::createScriptProcessor(size_t bufferSize, size_t numberOfInputChannels, size_t numberOfOutputChannels, ExceptionState& es)
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<ScriptProcessorNode> node = ScriptProcessorNode::create(this, m_destinationNode->sampleRate(), bufferSize, numberOfInputChannels, numberOfOutputChannels);
if (!node.get()) {
if (!numberOfInputChannels && !numberOfOutputChannels) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ScriptProcessorNode",
"number of input channels and output channels cannot both be zero."));
} else if (numberOfInputChannels > AudioContext::maxNumberOfChannels()) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ScriptProcessorNode",
"number of input channels (" + String::number(numberOfInputChannels)
+ ") exceeds maximum ("
+ String::number(AudioContext::maxNumberOfChannels()) + ")."));
} else if (numberOfOutputChannels > AudioContext::maxNumberOfChannels()) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ScriptProcessorNode",
"number of output channels (" + String::number(numberOfInputChannels)
+ ") exceeds maximum ("
+ String::number(AudioContext::maxNumberOfChannels()) + ")."));
} else {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ScriptProcessorNode",
"buffer size (" + String::number(bufferSize)
+ ") must be a power of two between 256 and 16384."));
}
return 0;
}
refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks
return node;
}
PassRefPtr<BiquadFilterNode> AudioContext::createBiquadFilter()
{
ASSERT(isMainThread());
lazyInitialize();
return BiquadFilterNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<WaveShaperNode> AudioContext::createWaveShaper()
{
ASSERT(isMainThread());
lazyInitialize();
return WaveShaperNode::create(this);
}
PassRefPtr<PannerNode> AudioContext::createPanner()
{
ASSERT(isMainThread());
lazyInitialize();
return PannerNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<ConvolverNode> AudioContext::createConvolver()
{
ASSERT(isMainThread());
lazyInitialize();
return ConvolverNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<DynamicsCompressorNode> AudioContext::createDynamicsCompressor()
{
ASSERT(isMainThread());
lazyInitialize();
return DynamicsCompressorNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<AnalyserNode> AudioContext::createAnalyser()
{
ASSERT(isMainThread());
lazyInitialize();
return AnalyserNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<GainNode> AudioContext::createGain()
{
ASSERT(isMainThread());
lazyInitialize();
return GainNode::create(this, m_destinationNode->sampleRate());
}
PassRefPtr<DelayNode> AudioContext::createDelay(ExceptionState& es)
{
const double defaultMaxDelayTime = 1;
return createDelay(defaultMaxDelayTime, es);
}
PassRefPtr<DelayNode> AudioContext::createDelay(double maxDelayTime, ExceptionState& es)
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<DelayNode> node = DelayNode::create(this, m_destinationNode->sampleRate(), maxDelayTime, es);
if (es.hadException())
return 0;
return node;
}
PassRefPtr<ChannelSplitterNode> AudioContext::createChannelSplitter(ExceptionState& es)
{
const unsigned ChannelSplitterDefaultNumberOfOutputs = 6;
return createChannelSplitter(ChannelSplitterDefaultNumberOfOutputs, es);
}
PassRefPtr<ChannelSplitterNode> AudioContext::createChannelSplitter(size_t numberOfOutputs, ExceptionState& es)
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<ChannelSplitterNode> node = ChannelSplitterNode::create(this, m_destinationNode->sampleRate(), numberOfOutputs);
if (!node.get()) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ChannelSplitterNode",
"number of outputs (" + String::number(numberOfOutputs)
+ ") must be between 1 and "
+ String::number(AudioContext::maxNumberOfChannels()) + "."));
return 0;
}
return node;
}
PassRefPtr<ChannelMergerNode> AudioContext::createChannelMerger(ExceptionState& es)
{
const unsigned ChannelMergerDefaultNumberOfInputs = 6;
return createChannelMerger(ChannelMergerDefaultNumberOfInputs, es);
}
PassRefPtr<ChannelMergerNode> AudioContext::createChannelMerger(size_t numberOfInputs, ExceptionState& es)
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<ChannelMergerNode> node = ChannelMergerNode::create(this, m_destinationNode->sampleRate(), numberOfInputs);
if (!node.get()) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"ChannelMergerNode",
"number of inputs (" + String::number(numberOfInputs)
+ ") must be between 1 and "
+ String::number(AudioContext::maxNumberOfChannels()) + "."));
return 0;
}
return node;
}
PassRefPtr<OscillatorNode> AudioContext::createOscillator()
{
ASSERT(isMainThread());
lazyInitialize();
RefPtr<OscillatorNode> node = OscillatorNode::create(this, m_destinationNode->sampleRate());
// Because this is an AudioScheduledSourceNode, the context keeps a reference until it has finished playing.
// When this happens, AudioScheduledSourceNode::finish() calls AudioContext::notifyNodeFinishedProcessing().
refNode(node.get());
return node;
}
PassRefPtr<PeriodicWave> AudioContext::createPeriodicWave(Float32Array* real, Float32Array* imag, ExceptionState& es)
{
ASSERT(isMainThread());
if (!real) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToConstruct(
"PeriodicWave",
"invalid real array"));
return 0;
}
if (!imag) {
es.throwDOMException(
SyntaxError,
ExceptionMessages::failedToConstruct(
"PeriodicWave",
"invalid imaginary array"));
return 0;
}
if (real->length() != imag->length()) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"PeriodicWave",
"length of real array (" + String::number(real->length())
+ ") and length of imaginary array (" + String::number(imag->length())
+ ") must match."));
return 0;
}
if (real->length() > 4096) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"PeriodicWave",
"length of real array (" + String::number(real->length())
+ ") exceeds allowed maximum of 4096"));
return 0;
}
if (imag->length() > 4096) {
es.throwDOMException(
IndexSizeError,
ExceptionMessages::failedToConstruct(
"PeriodicWave",
"length of imaginary array (" + String::number(imag->length())
+ ") exceeds allowed maximum of 4096"));
return 0;
}
lazyInitialize();
return PeriodicWave::create(sampleRate(), real, imag);
}
void AudioContext::notifyNodeFinishedProcessing(AudioNode* node)
{
ASSERT(isAudioThread());
m_finishedNodes.append(node);
}
void AudioContext::derefFinishedSourceNodes()
{
ASSERT(isGraphOwner());
ASSERT(isAudioThread() || isAudioThreadFinished());
for (unsigned i = 0; i < m_finishedNodes.size(); i++)
derefNode(m_finishedNodes[i]);
m_finishedNodes.clear();
}
void AudioContext::refNode(AudioNode* node)
{
ASSERT(isMainThread());
AutoLocker locker(this);
node->ref(AudioNode::RefTypeConnection);
m_referencedNodes.append(node);
}
void AudioContext::derefNode(AudioNode* node)
{
ASSERT(isGraphOwner());
node->deref(AudioNode::RefTypeConnection);
for (unsigned i = 0; i < m_referencedNodes.size(); ++i) {
if (node == m_referencedNodes[i]) {
m_referencedNodes.remove(i);
break;
}
}
}
void AudioContext::derefUnfinishedSourceNodes()
{
ASSERT(isMainThread() && isAudioThreadFinished());
for (unsigned i = 0; i < m_referencedNodes.size(); ++i)
m_referencedNodes[i]->deref(AudioNode::RefTypeConnection);
m_referencedNodes.clear();
}
void AudioContext::lock(bool& mustReleaseLock)
{
// Don't allow regular lock in real-time audio thread.
ASSERT(isMainThread());
ThreadIdentifier thisThread = currentThread();
if (thisThread == m_graphOwnerThread) {
// We already have the lock.
mustReleaseLock = false;
} else {
// Acquire the lock.
m_contextGraphMutex.lock();
m_graphOwnerThread = thisThread;
mustReleaseLock = true;
}
}
bool AudioContext::tryLock(bool& mustReleaseLock)
{
ThreadIdentifier thisThread = currentThread();
bool isAudioThread = thisThread == audioThread();
// Try to catch cases of using try lock on main thread - it should use regular lock.
ASSERT(isAudioThread || isAudioThreadFinished());
if (!isAudioThread) {
// In release build treat tryLock() as lock() (since above ASSERT(isAudioThread) never fires) - this is the best we can do.
lock(mustReleaseLock);
return true;
}
bool hasLock;
if (thisThread == m_graphOwnerThread) {
// Thread already has the lock.
hasLock = true;
mustReleaseLock = false;
} else {
// Don't already have the lock - try to acquire it.
hasLock = m_contextGraphMutex.tryLock();
if (hasLock)
m_graphOwnerThread = thisThread;
mustReleaseLock = hasLock;
}
return hasLock;
}
void AudioContext::unlock()
{
ASSERT(currentThread() == m_graphOwnerThread);
m_graphOwnerThread = UndefinedThreadIdentifier;
m_contextGraphMutex.unlock();
}
bool AudioContext::isAudioThread() const
{
return currentThread() == m_audioThread;
}
bool AudioContext::isGraphOwner() const
{
return currentThread() == m_graphOwnerThread;
}
void AudioContext::addDeferredFinishDeref(AudioNode* node)
{
ASSERT(isAudioThread());
m_deferredFinishDerefList.append(node);
}
void AudioContext::handlePreRenderTasks()
{
ASSERT(isAudioThread());
// At the beginning of every render quantum, try to update the internal rendering graph state (from main thread changes).
// It's OK if the tryLock() fails, we'll just take slightly longer to pick up the changes.
bool mustReleaseLock;
if (tryLock(mustReleaseLock)) {
// Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutputs.
handleDirtyAudioSummingJunctions();
handleDirtyAudioNodeOutputs();
updateAutomaticPullNodes();
if (mustReleaseLock)
unlock();
}
}
void AudioContext::handlePostRenderTasks()
{
ASSERT(isAudioThread());
// Must use a tryLock() here too. Don't worry, the lock will very rarely be contended and this method is called frequently.
// The worst that can happen is that there will be some nodes which will take slightly longer than usual to be deleted or removed
// from the render graph (in which case they'll render silence).
bool mustReleaseLock;
if (tryLock(mustReleaseLock)) {
// Take care of finishing any derefs where the tryLock() failed previously.
handleDeferredFinishDerefs();
// Dynamically clean up nodes which are no longer needed.
derefFinishedSourceNodes();
// Don't delete in the real-time thread. Let the main thread do it.
// Ref-counted objects held by certain AudioNodes may not be thread-safe.
scheduleNodeDeletion();
// Fixup the state of any dirty AudioSummingJunctions and AudioNodeOutputs.
handleDirtyAudioSummingJunctions();
handleDirtyAudioNodeOutputs();
updateAutomaticPullNodes();
if (mustReleaseLock)
unlock();
}
}
void AudioContext::handleDeferredFinishDerefs()
{
ASSERT(isAudioThread() && isGraphOwner());
for (unsigned i = 0; i < m_deferredFinishDerefList.size(); ++i) {
AudioNode* node = m_deferredFinishDerefList[i];
node->finishDeref(AudioNode::RefTypeConnection);
}
m_deferredFinishDerefList.clear();
}
void AudioContext::markForDeletion(AudioNode* node)
{
ASSERT(isGraphOwner());
if (isAudioThreadFinished())
m_nodesToDelete.append(node);
else
m_nodesMarkedForDeletion.append(node);
// This is probably the best time for us to remove the node from automatic pull list,
// since all connections are gone and we hold the graph lock. Then when handlePostRenderTasks()
// gets a chance to schedule the deletion work, updateAutomaticPullNodes() also gets a chance to
// modify m_renderingAutomaticPullNodes.
removeAutomaticPullNode(node);
}
void AudioContext::scheduleNodeDeletion()
{
bool isGood = m_isInitialized && isGraphOwner();
ASSERT(isGood);
if (!isGood)
return;
// Make sure to call deleteMarkedNodes() on main thread.
if (m_nodesMarkedForDeletion.size() && !m_isDeletionScheduled) {
m_nodesToDelete.append(m_nodesMarkedForDeletion);
m_nodesMarkedForDeletion.clear();
m_isDeletionScheduled = true;
// Don't let ourself get deleted before the callback.
// See matching deref() in deleteMarkedNodesDispatch().
ref();
callOnMainThread(deleteMarkedNodesDispatch, this);
}
}
void AudioContext::deleteMarkedNodesDispatch(void* userData)
{
AudioContext* context = reinterpret_cast<AudioContext*>(userData);
ASSERT(context);
if (!context)
return;
context->deleteMarkedNodes();
context->deref();
}
void AudioContext::deleteMarkedNodes()
{
ASSERT(isMainThread());
// Protect this object from being deleted before we release the mutex locked by AutoLocker.
RefPtr<AudioContext> protect(this);
{
AutoLocker locker(this);
while (size_t n = m_nodesToDelete.size()) {
AudioNode* node = m_nodesToDelete[n - 1];
m_nodesToDelete.removeLast();
// Before deleting the node, clear out any AudioNodeInputs from m_dirtySummingJunctions.
unsigned numberOfInputs = node->numberOfInputs();
for (unsigned i = 0; i < numberOfInputs; ++i)
m_dirtySummingJunctions.remove(node->input(i));
// Before deleting the node, clear out any AudioNodeOutputs from m_dirtyAudioNodeOutputs.
unsigned numberOfOutputs = node->numberOfOutputs();
for (unsigned i = 0; i < numberOfOutputs; ++i)
m_dirtyAudioNodeOutputs.remove(node->output(i));
// Finally, delete it.
delete node;
}
m_isDeletionScheduled = false;
}
}
void AudioContext::markSummingJunctionDirty(AudioSummingJunction* summingJunction)
{
ASSERT(isGraphOwner());
m_dirtySummingJunctions.add(summingJunction);
}
void AudioContext::removeMarkedSummingJunction(AudioSummingJunction* summingJunction)
{
ASSERT(isMainThread());
AutoLocker locker(this);
m_dirtySummingJunctions.remove(summingJunction);
}
void AudioContext::markAudioNodeOutputDirty(AudioNodeOutput* output)
{
ASSERT(isGraphOwner());
m_dirtyAudioNodeOutputs.add(output);
}
void AudioContext::handleDirtyAudioSummingJunctions()
{
ASSERT(isGraphOwner());
for (HashSet<AudioSummingJunction*>::iterator i = m_dirtySummingJunctions.begin(); i != m_dirtySummingJunctions.end(); ++i)
(*i)->updateRenderingState();
m_dirtySummingJunctions.clear();
}
void AudioContext::handleDirtyAudioNodeOutputs()
{
ASSERT(isGraphOwner());
for (HashSet<AudioNodeOutput*>::iterator i = m_dirtyAudioNodeOutputs.begin(); i != m_dirtyAudioNodeOutputs.end(); ++i)
(*i)->updateRenderingState();
m_dirtyAudioNodeOutputs.clear();
}
void AudioContext::addAutomaticPullNode(AudioNode* node)
{
ASSERT(isGraphOwner());
if (!m_automaticPullNodes.contains(node)) {
m_automaticPullNodes.add(node);
m_automaticPullNodesNeedUpdating = true;
}
}
void AudioContext::removeAutomaticPullNode(AudioNode* node)
{
ASSERT(isGraphOwner());
if (m_automaticPullNodes.contains(node)) {
m_automaticPullNodes.remove(node);
m_automaticPullNodesNeedUpdating = true;
}
}
void AudioContext::updateAutomaticPullNodes()
{
ASSERT(isGraphOwner());
if (m_automaticPullNodesNeedUpdating) {
// Copy from m_automaticPullNodes to m_renderingAutomaticPullNodes.
m_renderingAutomaticPullNodes.resize(m_automaticPullNodes.size());
unsigned j = 0;
for (HashSet<AudioNode*>::iterator i = m_automaticPullNodes.begin(); i != m_automaticPullNodes.end(); ++i, ++j) {
AudioNode* output = *i;
m_renderingAutomaticPullNodes[j] = output;
}
m_automaticPullNodesNeedUpdating = false;
}
}
void AudioContext::processAutomaticPullNodes(size_t framesToProcess)
{
ASSERT(isAudioThread());
for (unsigned i = 0; i < m_renderingAutomaticPullNodes.size(); ++i)
m_renderingAutomaticPullNodes[i]->processIfNecessary(framesToProcess);
}
const AtomicString& AudioContext::interfaceName() const
{
return EventTargetNames::AudioContext;
}
ExecutionContext* AudioContext::executionContext() const
{
return m_isStopScheduled ? 0 : ActiveDOMObject::executionContext();
}
void AudioContext::startRendering()
{
destination()->startRendering();
}
void AudioContext::fireCompletionEvent()
{
ASSERT(isMainThread());
if (!isMainThread())
return;
AudioBuffer* renderedBuffer = m_renderTarget.get();
ASSERT(renderedBuffer);
if (!renderedBuffer)
return;
// Avoid firing the event if the document has already gone away.
if (executionContext()) {
// Call the offline rendering completion event listener.
dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer));
}
}
void AudioContext::incrementActiveSourceCount()
{
atomicIncrement(&m_activeSourceCount);
}
void AudioContext::decrementActiveSourceCount()
{
atomicDecrement(&m_activeSourceCount);
}
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)