diff options
Diffstat (limited to 'WebCore/webaudio')
-rw-r--r-- | WebCore/webaudio/AudioContext.cpp | 467 | ||||
-rw-r--r-- | WebCore/webaudio/AudioContext.h | 248 | ||||
-rw-r--r-- | WebCore/webaudio/AudioContext.idl | 70 | ||||
-rw-r--r-- | WebCore/webaudio/AudioDestinationNode.cpp | 115 | ||||
-rw-r--r-- | WebCore/webaudio/AudioDestinationNode.h | 76 | ||||
-rw-r--r-- | WebCore/webaudio/AudioDestinationNode.idl | 36 | ||||
-rw-r--r-- | WebCore/webaudio/AudioGain.h | 53 | ||||
-rw-r--r-- | WebCore/webaudio/AudioGain.idl | 35 | ||||
-rw-r--r-- | WebCore/webaudio/AudioNode.cpp | 311 | ||||
-rw-r--r-- | WebCore/webaudio/AudioNode.h | 174 | ||||
-rw-r--r-- | WebCore/webaudio/AudioNode.idl | 40 | ||||
-rw-r--r-- | WebCore/webaudio/AudioParam.h | 9 |
12 files changed, 1633 insertions, 1 deletions
diff --git a/WebCore/webaudio/AudioContext.cpp b/WebCore/webaudio/AudioContext.cpp new file mode 100644 index 0000000..6ca8ee1 --- /dev/null +++ b/WebCore/webaudio/AudioContext.cpp @@ -0,0 +1,467 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AudioContext.h" + +#include "AudioBuffer.h" +#include "AudioBufferSourceNode.h" +#include "AudioChannelMerger.h" +#include "AudioChannelSplitter.h" +#include "AudioGainNode.h" +#include "AudioListener.h" +#include "AudioPannerNode.h" +#include "CachedAudio.h" +#include "ConvolverNode.h" +#include "DelayNode.h" +#include "Document.h" +#include "HRTFDatabaseLoader.h" +#include "HRTFPanner.h" +#include "HTMLNames.h" +#include "HighPass2FilterNode.h" +#include "JavaScriptAudioNode.h" +#include "LowPass2FilterNode.h" +#include "PlatformString.h" +#include "RealtimeAnalyserNode.h" + +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefCounted.h> + +// FIXME: check the proper way to reference an undefined thread ID +const int UndefinedThreadIdentifier = 0xffffffff; + +namespace WebCore { + +PassRefPtr<CachedAudio> AudioContext::createAudioRequest(const String &url, bool mixToMono) +{ + lazyInitialize(); + + // Convert relative URL to absolute + KURL completedURL = document()->completeURL(url); + String completedURLString = completedURL.string(); + + RefPtr<CachedAudio> cachedAudio = CachedAudio::create(completedURLString, this, document(), sampleRate(), mixToMono); + CachedAudio* c = cachedAudio.get(); + + m_cachedAudioReferences.append(c); + + return cachedAudio; +} + +PassRefPtr<AudioContext> AudioContext::create(Document* document) +{ + return adoptRef(new AudioContext(document)); +} + +AudioContext::AudioContext(Document* document) + : ActiveDOMObject(document, this) + , m_isInitialized(false) + , m_isAudioThreadFinished(false) + , m_document(document) + , m_destinationNode(0) + , m_connectionCount(0) + , m_audioThread(0) + , m_graphOwnerThread(UndefinedThreadIdentifier) +{ + // Note: because adoptRef() won't be called until we leave this constructor, but code in this constructor needs to reference this context, + // relax the check. + relaxAdoptionRequirement(); + + m_destinationNode = AudioDestinationNode::create(this); + m_listener = AudioListener::create(); + m_temporaryMonoBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); + m_temporaryStereoBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); + + // This sets in motion an asynchronous loading mechanism on another thread. + // We can check 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). + hrtfDatabaseLoader()->loadAsynchronously(sampleRate()); +} + +AudioContext::~AudioContext() +{ +#if DEBUG_AUDIONODE_REFERENCES + printf("%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_nodesToDelete.size()); + ASSERT(!m_referencedNodes.size()); + ASSERT(!m_finishedNodes.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()) { + // 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". + m_destinationNode->initialize(); + } + m_isInitialized = true; + } + } +} + +void AudioContext::uninitialize() +{ + if (m_isInitialized) { + // 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; + + // 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. + m_destinationNode.clear(); + + // Get rid of the sources which may still be playing. + derefUnfinishedSourceNodes(); + + // Because the AudioBuffers are garbage collected, we can't delete them here. + // Instead, at least release the potentially large amount of allocated memory for the audio data. + // Note that we do this *after* the context is uninitialized and stops processing audio. + for (unsigned i = 0; i < m_allocatedBuffers.size(); ++i) + m_allocatedBuffers[i]->releaseMemory(); + m_allocatedBuffers.clear(); + + 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 hrtfDatabaseLoader()->isLoaded(); +} + +void AudioContext::stop() +{ + m_document = 0; // document is going away + uninitialize(); +} + +Document* AudioContext::document() +{ + ASSERT(m_document); + return m_document; +} + +bool AudioContext::hasDocument() +{ + return m_document; +} + +void AudioContext::refBuffer(PassRefPtr<AudioBuffer> buffer) +{ + m_allocatedBuffers.append(buffer); +} + +PassRefPtr<AudioBuffer> AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate) +{ + return AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); +} + +PassRefPtr<AudioBufferSourceNode> AudioContext::createBufferSource() +{ + ASSERT(isMainThread()); + lazyInitialize(); + RefPtr<AudioBufferSourceNode> node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate()); + + refNode(node.get()); // context keeps reference until source has finished playing + return node; +} + +PassRefPtr<JavaScriptAudioNode> AudioContext::createJavaScriptNode(size_t bufferSize) +{ + ASSERT(isMainThread()); + lazyInitialize(); + RefPtr<JavaScriptAudioNode> node = JavaScriptAudioNode::create(this, m_destinationNode->sampleRate(), bufferSize); + + refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks + return node; +} + +PassRefPtr<LowPass2FilterNode> AudioContext::createLowPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return LowPass2FilterNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<HighPass2FilterNode> AudioContext::createHighPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return HighPass2FilterNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioPannerNode> AudioContext::createPanner() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioPannerNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<ConvolverNode> AudioContext::createConvolver() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return ConvolverNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<RealtimeAnalyserNode> AudioContext::createAnalyser() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return RealtimeAnalyserNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioGainNode> AudioContext::createGainNode() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioGainNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<DelayNode> AudioContext::createDelayNode() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return DelayNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioChannelSplitter> AudioContext::createChannelSplitter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioChannelSplitter::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioChannelMerger> AudioContext::createChannelMerger() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioChannelMerger::create(this, m_destinationNode->sampleRate()); +} + +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(); + mustReleaseLock = true; + } + + m_graphOwnerThread = thisThread; +} + +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() +{ + return currentThread() == m_audioThread; +} + +bool AudioContext::isGraphOwner() +{ + return currentThread() == m_graphOwnerThread; +} + +void AudioContext::addDeferredFinishDeref(AudioNode* node, AudioNode::RefType refType) +{ + ASSERT(isAudioThread()); + m_deferredFinishDerefList.append(AudioContext::RefInfo(node, refType)); +} + +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(); + + // Finally actually delete. + deleteMarkedNodes(); + + if (mustReleaseLock) + unlock(); + } +} + +void AudioContext::handleDeferredFinishDerefs() +{ + ASSERT(isAudioThread()); + for (unsigned i = 0; i < m_deferredFinishDerefList.size(); ++i) { + AudioNode* node = m_deferredFinishDerefList[i].m_node; + AudioNode::RefType refType = m_deferredFinishDerefList[i].m_refType; + node->finishDeref(refType); + } + + m_deferredFinishDerefList.clear(); +} + +void AudioContext::markForDeletion(AudioNode* node) +{ + ASSERT(isGraphOwner()); + m_nodesToDelete.append(node); +} + +void AudioContext::deleteMarkedNodes() +{ + ASSERT(isGraphOwner() || isAudioThreadFinished()); + + // Note: deleting an AudioNode can cause m_nodesToDelete to grow. + while (size_t n = m_nodesToDelete.size()) { + AudioNode* node = m_nodesToDelete[n - 1]; + m_nodesToDelete.removeLast(); + delete node; + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/webaudio/AudioContext.h b/WebCore/webaudio/AudioContext.h new file mode 100644 index 0000000..f175bfe --- /dev/null +++ b/WebCore/webaudio/AudioContext.h @@ -0,0 +1,248 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef AudioContext_h +#define AudioContext_h + +#include "ActiveDOMObject.h" +#include "AudioBus.h" +#include "AudioDestinationNode.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> +#include <wtf/text/AtomicStringHash.h> + +namespace WebCore { + +class AudioBuffer; +class AudioBufferSourceNode; +class AudioChannelMerger; +class AudioChannelSplitter; +class AudioGainNode; +class AudioPannerNode; +class AudioListener; +class CachedAudio; +class DelayNode; +class Document; +class LowPass2FilterNode; +class HighPass2FilterNode; +class ConvolverNode; +class RealtimeAnalyserNode; +class JavaScriptAudioNode; + +// AudioContext is the cornerstone of the web audio API and all AudioNodes are created from it. +// For thread safety between the audio thread and the main thread, it has a rendering graph locking mechanism. + +class AudioContext : public ActiveDOMObject, public RefCounted<AudioContext> { +public: + static PassRefPtr<AudioContext> create(Document*); + + virtual ~AudioContext(); + + bool isInitialized() const; + + // Returns true when initialize() was called AND all asynchronous initialization has completed. + bool isRunnable() const; + + // Document notification + virtual void stop(); + + Document* document(); // ASSERTs if document no longer exists. + bool hasDocument(); + + AudioDestinationNode* destination() { return m_destinationNode.get(); } + double currentTime() { return m_destinationNode->currentTime(); } + double sampleRate() { return m_destinationNode->sampleRate(); } + + PassRefPtr<AudioBuffer> createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate); + + PassRefPtr<CachedAudio> createAudioRequest(const String &url, bool mixToMono); + + // Keep track of this buffer so we can release memory after the context is shut down... + void refBuffer(PassRefPtr<AudioBuffer> buffer); + + AudioListener* listener() { return m_listener.get(); } + + // The AudioNode create methods are called on the main thread (from JavaScript). + PassRefPtr<AudioBufferSourceNode> createBufferSource(); + PassRefPtr<AudioGainNode> createGainNode(); + PassRefPtr<DelayNode> createDelayNode(); + PassRefPtr<LowPass2FilterNode> createLowPass2Filter(); + PassRefPtr<HighPass2FilterNode> createHighPass2Filter(); + PassRefPtr<AudioPannerNode> createPanner(); + PassRefPtr<ConvolverNode> createConvolver(); + PassRefPtr<RealtimeAnalyserNode> createAnalyser(); + PassRefPtr<JavaScriptAudioNode> createJavaScriptNode(size_t bufferSize); + PassRefPtr<AudioChannelSplitter> createChannelSplitter(); + PassRefPtr<AudioChannelMerger> createChannelMerger(); + + AudioBus* temporaryMonoBus() { return m_temporaryMonoBus.get(); } + AudioBus* temporaryStereoBus() { return m_temporaryStereoBus.get(); } + + // When a source node has no more processing to do (has finished playing), then it tells the context to dereference it. + void notifyNodeFinishedProcessing(AudioNode*); + + // Called at the end of each render quantum. + void handlePostRenderTasks(); + + // Called periodically at the end of each render quantum to dereference finished source nodes. + void derefFinishedSourceNodes(); + + // We reap all marked nodes at the end of each realtime render quantum in deleteMarkedNodes(). + void markForDeletion(AudioNode*); + void deleteMarkedNodes(); + + // Keeps track of the number of connections made. + void incrementConnectionCount() + { + ASSERT(isMainThread()); + m_connectionCount++; + } + + unsigned connectionCount() const { return m_connectionCount; } + + // + // Thread Safety and Graph Locking: + // + + void setAudioThread(ThreadIdentifier thread) { m_audioThread = thread; } // FIXME: check either not initialized or the same + ThreadIdentifier audioThread() const { return m_audioThread; } + bool isAudioThread(); + + // Returns true only after the audio thread has been started and then shutdown. + bool isAudioThreadFinished() { return m_isAudioThreadFinished; } + + // mustReleaseLock is set to true if we acquired the lock in this method call and caller must unlock(), false if it was previously acquired. + void lock(bool& mustReleaseLock); + + // Returns true if we own the lock. + // mustReleaseLock is set to true if we acquired the lock in this method call and caller must unlock(), false if it was previously acquired. + bool tryLock(bool& mustReleaseLock); + + void unlock(); + + // Returns true if this thread owns the context's lock. + bool isGraphOwner(); + + class AutoLocker { + public: + AutoLocker(AudioContext* context) + : m_context(context) + { + ASSERT(context); + context->lock(m_mustReleaseLock); + } + + ~AutoLocker() + { + if (m_mustReleaseLock) + m_context->unlock(); + } + private: + AudioContext* m_context; + bool m_mustReleaseLock; + }; + + // In AudioNode::deref() a tryLock() is used for calling finishDeref(), but if it fails keep track here. + void addDeferredFinishDeref(AudioNode*, AudioNode::RefType); + + // In the audio thread at the start of each render cycle, we'll call handleDeferredFinishDerefs(). + void handleDeferredFinishDerefs(); + +private: + AudioContext(Document*); + void lazyInitialize(); + void uninitialize(); + + bool m_isInitialized; + bool m_isAudioThreadFinished; + bool m_isAudioThreadShutdown; + + Document* m_document; + + // The context itself keeps a reference to all source nodes. The source nodes, then reference all nodes they're connected to. + // In turn, these nodes reference all nodes they're connected to. All nodes are ultimately connected to the AudioDestinationNode. + // When the context dereferences a source node, it will be deactivated from the rendering graph along with all other nodes it is + // uniquely connected to. See the AudioNode::ref() and AudioNode::deref() methods for more details. + void refNode(AudioNode*); + void derefNode(AudioNode*); + + // When the context goes away, there might still be some sources which haven't finished playing. + // Make sure to dereference them here. + void derefUnfinishedSourceNodes(); + + RefPtr<AudioDestinationNode> m_destinationNode; + RefPtr<AudioListener> m_listener; + + // Only accessed in the main thread. + Vector<RefPtr<AudioBuffer> > m_allocatedBuffers; + + // Only accessed in the audio thread. + Vector<AudioNode*> m_finishedNodes; + + // We don't use RefPtr<AudioNode> here because AudioNode has a more complex ref() / deref() implementation + // with an optional argument for refType. We need to use the special refType: RefTypeConnection + // Either accessed when the graph lock is held, or on the main thread when the audio thread has finished. + Vector<AudioNode*> m_referencedNodes; + + // Accumulate nodes which need to be deleted at the end of a render cycle (in realtime thread) here. + Vector<AudioNode*> m_nodesToDelete; + + Vector<RefPtr<CachedAudio> > m_cachedAudioReferences; + + OwnPtr<AudioBus> m_temporaryMonoBus; + OwnPtr<AudioBus> m_temporaryStereoBus; + + unsigned m_connectionCount; + + // Graph locking. + Mutex m_contextGraphMutex; + volatile ThreadIdentifier m_audioThread; + volatile ThreadIdentifier m_graphOwnerThread; // if the lock is held then this is the thread which owns it, otherwise == UndefinedThreadIdentifier + + // Deferred de-referencing. + struct RefInfo { + RefInfo(AudioNode* node, AudioNode::RefType refType) + : m_node(node) + , m_refType(refType) + { + } + AudioNode* m_node; + AudioNode::RefType m_refType; + }; + + // Only accessed in the audio thread. + Vector<RefInfo> m_deferredFinishDerefList; +}; + +} // WebCore + +#endif // AudioContext_h diff --git a/WebCore/webaudio/AudioContext.idl b/WebCore/webaudio/AudioContext.idl new file mode 100644 index 0000000..8951121 --- /dev/null +++ b/WebCore/webaudio/AudioContext.idl @@ -0,0 +1,70 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +module webaudio { + interface [ + Conditional=WEB_AUDIO, + CanBeConstructed, + CustomConstructFunction, + V8CustomConstructor + ] AudioContext { + // All rendered audio ultimately connects to destination, which represents the audio hardware. + readonly attribute AudioDestinationNode destination; + + // All scheduled times are relative to this time in seconds. + readonly attribute float currentTime; + + // All AudioNodes in the context run at this sample-rate (in sample-frames per second). + readonly attribute float sampleRate; + + // All panning is relative to this listener. + readonly attribute AudioListener listener; + + AudioBuffer createBuffer(in unsigned long numberOfChannels, in unsigned long numberOfFrames, in float sampleRate); + + // Source + AudioBufferSourceNode createBufferSource(); + + // Processing nodes + AudioGainNode createGainNode(); + DelayNode createDelayNode(); + LowPass2FilterNode createLowPass2Filter(); + HighPass2FilterNode createHighPass2Filter(); + AudioPannerNode createPanner(); + ConvolverNode createConvolver(); + RealtimeAnalyserNode createAnalyser(); + JavaScriptAudioNode createJavaScriptNode(in unsigned long bufferSize); + + // Channel splitting and merging + AudioChannelSplitter createChannelSplitter(); + AudioChannelMerger createChannelMerger(); + + // FIXME: Temporary - to be replaced with XHR. + CachedAudio createAudioRequest(in DOMString url, in boolean mixToMono); + }; +} diff --git a/WebCore/webaudio/AudioDestinationNode.cpp b/WebCore/webaudio/AudioDestinationNode.cpp new file mode 100644 index 0000000..82f5145 --- /dev/null +++ b/WebCore/webaudio/AudioDestinationNode.cpp @@ -0,0 +1,115 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AudioDestinationNode.h" + +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include <wtf/Threading.h> + +namespace WebCore { + +AudioDestinationNode::AudioDestinationNode(AudioContext* context) + : AudioNode(context, AudioDestination::hardwareSampleRate()) + , m_currentTime(0.0) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + + setType(NodeTypeDestination); + + initialize(); +} + +AudioDestinationNode::~AudioDestinationNode() +{ + uninitialize(); +} + +void AudioDestinationNode::initialize() +{ + if (isInitialized()) + return; + + double hardwareSampleRate = AudioDestination::hardwareSampleRate(); +#ifndef NDEBUG + fprintf(stderr, ">>>> hardwareSampleRate = %f\n", hardwareSampleRate); +#endif + + m_destination = AudioDestination::create(*this, hardwareSampleRate); + m_destination->start(); + + m_isInitialized = true; +} + +void AudioDestinationNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_destination->stop(); + + m_isInitialized = false; +} + +// The audio hardware calls us back here to gets its input stream. +void AudioDestinationNode::provideInput(AudioBus* destinationBus, size_t numberOfFrames) +{ + context()->setAudioThread(currentThread()); + + if (!context()->isRunnable()) { + destinationBus->zero(); + return; + } + + // This will cause the node(s) connected to us to process, which in turn will pull on their input(s), + // all the way backwards through the rendering graph. + AudioBus* renderedBus = input(0)->pull(destinationBus, numberOfFrames); + + if (!renderedBus) + destinationBus->zero(); + else if (renderedBus != destinationBus) { + // in-place processing was not possible - so copy + destinationBus->copyFrom(*renderedBus); + } + + // Let the context take care of any business at the end of each render quantum. + context()->handlePostRenderTasks(); + + // Advance current time. + m_currentTime += numberOfFrames / sampleRate(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/webaudio/AudioDestinationNode.h b/WebCore/webaudio/AudioDestinationNode.h new file mode 100644 index 0000000..b130518 --- /dev/null +++ b/WebCore/webaudio/AudioDestinationNode.h @@ -0,0 +1,76 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef AudioDestinationNode_h +#define AudioDestinationNode_h + +#include "AudioDestination.h" +#include "AudioNode.h" +#include "AudioSourceProvider.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioBus; +class AudioContext; + +class AudioDestinationNode : public AudioNode, public AudioSourceProvider { +public: + static PassRefPtr<AudioDestinationNode> create(AudioContext* context) + { + return adoptRef(new AudioDestinationNode(context)); + } + + virtual ~AudioDestinationNode(); + + // AudioNode + virtual void process(size_t) { }; // we're pulled by hardware so this is never called + virtual void reset() { m_currentTime = 0.0; }; + virtual void initialize(); + virtual void uninitialize(); + + // The audio hardware calls here periodically to gets its input stream. + virtual void provideInput(AudioBus*, size_t numberOfFrames); + + double currentTime() { return m_currentTime; } + + double sampleRate() const { return m_destination->sampleRate(); } + + unsigned numberOfChannels() const { return 2; } // FIXME: update when multi-channel (more than stereo) is supported + +private: + AudioDestinationNode(AudioContext*); + + OwnPtr<AudioDestination> m_destination; + double m_currentTime; +}; + +} // namespace WebCore + +#endif // AudioDestinationNode_h diff --git a/WebCore/webaudio/AudioDestinationNode.idl b/WebCore/webaudio/AudioDestinationNode.idl new file mode 100644 index 0000000..1d2a235 --- /dev/null +++ b/WebCore/webaudio/AudioDestinationNode.idl @@ -0,0 +1,36 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioDestinationNode : AudioNode { + readonly attribute long numberOfChannels; + }; +} diff --git a/WebCore/webaudio/AudioGain.h b/WebCore/webaudio/AudioGain.h new file mode 100644 index 0000000..eb3c52d --- /dev/null +++ b/WebCore/webaudio/AudioGain.h @@ -0,0 +1,53 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef AudioGain_h +#define AudioGain_h + +#include "AudioParam.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioGain : public AudioParam { +public: + static PassRefPtr<AudioGain> create(const char* name, double defaultValue, double minValue, double maxValue) + { + return adoptRef(new AudioGain(name, defaultValue, minValue, maxValue)); + } + +private: + AudioGain(const char* name, double defaultValue, double minValue, double maxValue) + : AudioParam(name, defaultValue, minValue, maxValue) + { + } +}; + +} // namespace WebCore + +#endif // AudioParam_h diff --git a/WebCore/webaudio/AudioGain.idl b/WebCore/webaudio/AudioGain.idl new file mode 100644 index 0000000..ead7c9a --- /dev/null +++ b/WebCore/webaudio/AudioGain.idl @@ -0,0 +1,35 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioGain : AudioParam { + }; +} diff --git a/WebCore/webaudio/AudioNode.cpp b/WebCore/webaudio/AudioNode.cpp new file mode 100644 index 0000000..497ac95 --- /dev/null +++ b/WebCore/webaudio/AudioNode.cpp @@ -0,0 +1,311 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "AudioNode.h" + +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include <wtf/Atomics.h> + +namespace WebCore { + +AudioNode::AudioNode(AudioContext* context, double sampleRate) + : m_isInitialized(false) + , m_type(NodeTypeUnknown) + , m_context(context) + , m_sampleRate(sampleRate) + , m_lastProcessingTime(-1.0) + , m_normalRefCount(1) // start out with normal refCount == 1 (like WTF::RefCounted class) + , m_connectionRefCount(0) + , m_disabledRefCount(0) + , m_isMarkedForDeletion(false) + , m_isDisabled(false) +{ +#if DEBUG_AUDIONODE_REFERENCES + if (!s_isNodeCountInitialized) { + s_isNodeCountInitialized = true; + atexit(AudioNode::printNodeCounts); + } +#endif +} + +AudioNode::~AudioNode() +{ +#if DEBUG_AUDIONODE_REFERENCES + --s_nodeCount[type()]; + printf("%p: %d: AudioNode::~AudioNode() %d %d %d\n", this, type(), m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif +} + +void AudioNode::setType(NodeType type) +{ + m_type = type; + +#if DEBUG_AUDIONODE_REFERENCES + ++s_nodeCount[type]; +#endif +} + +void AudioNode::lazyInitialize() +{ + if (!isInitialized()) + initialize(); +} + +void AudioNode::addInput(PassOwnPtr<AudioNodeInput> input) +{ + m_inputs.append(input); +} + +void AudioNode::addOutput(PassOwnPtr<AudioNodeOutput> output) +{ + m_outputs.append(output); +} + +AudioNodeInput* AudioNode::input(unsigned i) +{ + return m_inputs[i].get(); +} + +AudioNodeOutput* AudioNode::output(unsigned i) +{ + return m_outputs[i].get(); +} + +bool AudioNode::connect(AudioNode* destination, unsigned outputIndex, unsigned inputIndex) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + // Sanity check input and output indices. + if (outputIndex >= numberOfOutputs()) + return false; + if (destination && inputIndex >= destination->numberOfInputs()) + return false; + + AudioNodeOutput* output = this->output(outputIndex); + if (!destination) { + // Disconnect output from any inputs it may be currently connected to. + output->disconnectAllInputs(); + return true; + } + + AudioNodeInput* input = destination->input(inputIndex); + input->connect(output); + + // Let context know that a connection has been made. + context()->incrementConnectionCount(); + + return true; +} + +bool AudioNode::disconnect(unsigned outputIndex) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + return connect(0, outputIndex); +} + +void AudioNode::processIfNecessary(size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + if (!isInitialized()) + return; + + // Ensure that we only process once per rendering quantum. + // This handles the "fanout" problem where an output is connected to multiple inputs. + // The first time we're called during this time slice we process, but after that we don't want to re-process, + // instead our output(s) will already have the results cached in their bus; + double currentTime = context()->currentTime(); + if (m_lastProcessingTime != currentTime) { + m_lastProcessingTime = currentTime; // important to first update this time because of feedback loops in the rendering graph + pullInputs(framesToProcess); + process(framesToProcess); + } +} + +void AudioNode::pullInputs(size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + // Process all of the AudioNodes connected to our inputs. + for (unsigned i = 0; i < m_inputs.size(); ++i) + input(i)->pull(0, framesToProcess); +} + +void AudioNode::ref(RefType refType) +{ + switch (refType) { + case RefTypeNormal: + atomicIncrement(&m_normalRefCount); + break; + case RefTypeConnection: + atomicIncrement(&m_connectionRefCount); + break; + case RefTypeDisabled: + atomicIncrement(&m_disabledRefCount); + break; + default: + ASSERT_NOT_REACHED(); + } + +#if DEBUG_AUDIONODE_REFERENCES + printf("%p: %d: AudioNode::ref(%d) %d %d %d\n", this, type(), refType, m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif + + if (m_connectionRefCount == 1 && refType == RefTypeConnection) { + // FIXME: implement wake-up - this is an advanced feature and is not necessary in a simple implementation. + // We should not be "actively" connected to anything, but now we're "waking up" + // For example, a note which has finished playing, but is now being played again. + // Note that if this is considered a worthwhile feature to add, then an evaluation of the locking considerations must be made. + } +} + +void AudioNode::deref(RefType refType) +{ + // The actually work for deref happens completely within the audio context's graph lock. + // In the case of the audio thread, we must use a tryLock to avoid glitches. + bool hasLock = false; + bool mustReleaseLock = false; + + if (context()->isAudioThread()) { + // Real-time audio thread must not contend lock (to avoid glitches). + hasLock = context()->tryLock(mustReleaseLock); + } else { + context()->lock(mustReleaseLock); + hasLock = true; + } + + if (hasLock) { + // This is where the real deref work happens. + finishDeref(refType); + + if (mustReleaseLock) + context()->unlock(); + } else { + // We were unable to get the lock, so put this in a list to finish up later. + ASSERT(context()->isAudioThread()); + context()->addDeferredFinishDeref(this, refType); + } + + // Once AudioContext::uninitialize() is called there's no more chances for deleteMarkedNodes() to get called, so we call here. + // We can't call in AudioContext::~AudioContext() since it will never be called as long as any AudioNode is alive + // because AudioNodes keep a reference to the context. + if (context()->isAudioThreadFinished()) + context()->deleteMarkedNodes(); +} + +void AudioNode::finishDeref(RefType refType) +{ + ASSERT(context()->isGraphOwner()); + + switch (refType) { + case RefTypeNormal: + ASSERT(m_normalRefCount > 0); + atomicDecrement(&m_normalRefCount); + break; + case RefTypeConnection: + ASSERT(m_connectionRefCount > 0); + atomicDecrement(&m_connectionRefCount); + break; + case RefTypeDisabled: + ASSERT(m_disabledRefCount > 0); + atomicDecrement(&m_disabledRefCount); + break; + default: + ASSERT_NOT_REACHED(); + } + +#if DEBUG_AUDIONODE_REFERENCES + printf("%p: %d: AudioNode::deref(%d) %d %d %d\n", this, type(), refType, m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif + + if (!m_connectionRefCount) { + if (!m_normalRefCount && !m_disabledRefCount) { + if (!m_isMarkedForDeletion) { + // All references are gone - we need to go away. + for (unsigned i = 0; i < m_outputs.size(); ++i) + output(i)->disconnectAllInputs(); // this will deref() nodes we're connected to... + + // Mark for deletion at end of each render quantum or when context shuts down. + context()->markForDeletion(this); + m_isMarkedForDeletion = true; + } + } else if (refType == RefTypeConnection) { + if (!m_isDisabled) { + // Still may have JavaScript references, but no more "active" connection references, so put all of our outputs in a "dormant" disabled state. + // Garbage collection may take a very long time after this time, so the "dormant" disabled nodes should not bog down the rendering... + + // As far as JavaScript is concerned, our outputs must still appear to be connected. + // But internally our outputs should be disabled from the inputs they're connected to. + // disable() can recursively deref connections (and call disable()) down a whole chain of connected nodes. + + // FIXME: we special case the convolver and delay since they have a significant tail-time and shouldn't be disconnected simply + // because they no longer have any input connections. This needs to be handled more generally where AudioNodes have + // a tailTime attribute. Then the AudioNode only needs to remain "active" for tailTime seconds after there are no + // longer any active connections. + if (type() != NodeTypeConvolver && type() != NodeTypeDelay) { + m_isDisabled = true; + for (unsigned i = 0; i < m_outputs.size(); ++i) + output(i)->disable(); + } + } + } + } +} + +#if DEBUG_AUDIONODE_REFERENCES + +bool AudioNode::s_isNodeCountInitialized = false; +int AudioNode::s_nodeCount[NodeTypeEnd]; + +void AudioNode::printNodeCounts() +{ + printf("\n\n"); + printf("===========================\n"); + printf("AudioNode: reference counts\n"); + printf("===========================\n"); + + for (unsigned i = 0; i < NodeTypeEnd; ++i) + printf("%d: %d\n", i, s_nodeCount[i]); + + printf("===========================\n\n\n"); +} + +#endif // DEBUG_AUDIONODE_REFERENCES + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/WebCore/webaudio/AudioNode.h b/WebCore/webaudio/AudioNode.h new file mode 100644 index 0000000..b697457 --- /dev/null +++ b/WebCore/webaudio/AudioNode.h @@ -0,0 +1,174 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +#ifndef AudioNode_h +#define AudioNode_h + +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +#define DEBUG_AUDIONODE_REFERENCES 0 + +namespace WebCore { + +class AudioContext; +class AudioNodeInput; +class AudioNodeOutput; + +// An AudioNode is the basic building block for handling audio within an AudioContext. +// It may be an audio source, an intermediate processing module, or an audio destination. +// Each AudioNode can have inputs and/or outputs. An AudioSourceNode has no inputs and a single output. +// An AudioDestinationNode has one input and no outputs and represents the final destination to the audio hardware. +// Most processing nodes such as filters will have one input and one output, although multiple inputs and outputs are possible. + +class AudioNode { +public: + enum { ProcessingSizeInFrames = 128 }; + + AudioNode(AudioContext*, double sampleRate); + virtual ~AudioNode(); + + AudioContext* context() { return m_context.get(); } + + enum NodeType { + NodeTypeUnknown, + NodeTypeDestination, + NodeTypeAudioBufferSource, + NodeTypeJavaScript, + NodeTypeLowPass2Filter, + NodeTypeHighPass2Filter, + NodeTypePanner, + NodeTypeConvolver, + NodeTypeDelay, + NodeTypeGain, + NodeTypeChannelSplitter, + NodeTypeChannelMerger, + NodeTypeAnalyser, + NodeTypeEnd + }; + + NodeType type() const { return m_type; } + void setType(NodeType); + + // We handle our own ref-counting because of the threading issues and subtle nature of + // how AudioNodes can continue processing (playing one-shot sound) after there are no more + // JavaScript references to the object. + enum RefType { RefTypeNormal, RefTypeConnection, RefTypeDisabled }; + + // Can be called from main thread or context's audio thread. + void ref(RefType refType = RefTypeNormal); + void deref(RefType refType = RefTypeNormal); + + // Can be called from main thread or context's audio thread. It must be called while the context's graph lock is held. + void finishDeref(RefType refType); + + // The AudioNodeInput(s) (if any) will already have their input data available when process() is called. + // Subclasses will take this input data and put the results in the AudioBus(s) of its AudioNodeOutput(s) (if any). + // Called from context's audio thread. + virtual void process(size_t framesToProcess) = 0; + + // Resets DSP processing state (clears delay lines, filter memory, etc.) + // Called from context's audio thread. + virtual void reset() = 0; + + // No significant resources should be allocated until initialize() is called. + // Processing may not occur until a node is initialized. + virtual void initialize() = 0; + virtual void uninitialize() = 0; + + bool isInitialized() const { return m_isInitialized; } + void lazyInitialize(); + + unsigned numberOfInputs() const { return m_inputs.size(); } + unsigned numberOfOutputs() const { return m_outputs.size(); } + + AudioNodeInput* input(unsigned); + AudioNodeOutput* output(unsigned); + + // connect() / disconnect() return true on success. + // Called from main thread by corresponding JavaScript methods. + bool connect(AudioNode* destination, unsigned outputIndex = 0, unsigned inputIndex = 0); + bool disconnect(unsigned outputIndex = 0); + + double sampleRate() const { return m_sampleRate; } + + // processIfNecessary() is called by our output(s) when the rendering graph needs this AudioNode to process. + // This method ensures that the AudioNode will only process once per rendering time quantum even if it's called repeatedly. + // This handles the case of "fanout" where an output is connected to multiple AudioNode inputs. + // Called from context's audio thread. + void processIfNecessary(size_t framesToProcess); + + // Called when a new connection has been made to one of our inputs or the connection number of channels has changed. + // This potentially gives us enough information to perform a lazy initialization or, if necessary, a re-initialization. + // Called from main thread. + virtual void checkNumberOfChannelsForInput(AudioNodeInput*) { } + +#if DEBUG_AUDIONODE_REFERENCES + static void printNodeCounts(); +#endif + +protected: + // Inputs and outputs must be created before the AudioNode is initialized. + void addInput(PassOwnPtr<AudioNodeInput>); + void addOutput(PassOwnPtr<AudioNodeOutput>); + + // Called by processIfNecessary() to cause all parts of the rendering graph connected to us to process. + // Each rendering quantum, the audio data for each of the AudioNode's inputs will be available after this method is called. + // Called from context's audio thread. + virtual void pullInputs(size_t framesToProcess); + + bool m_isInitialized; + +private: + NodeType m_type; + RefPtr<AudioContext> m_context; + double m_sampleRate; + Vector<OwnPtr<AudioNodeInput> > m_inputs; + Vector<OwnPtr<AudioNodeOutput> > m_outputs; + + double m_lastProcessingTime; + + // Ref-counting + volatile int m_normalRefCount; + volatile int m_connectionRefCount; + volatile int m_disabledRefCount; + + bool m_isMarkedForDeletion; + bool m_isDisabled; + +#if DEBUG_AUDIONODE_REFERENCES + static bool s_isNodeCountInitialized; + static int s_nodeCount[NodeTypeEnd]; +#endif +}; + +} // namespace WebCore + +#endif // AudioNode_h diff --git a/WebCore/webaudio/AudioNode.idl b/WebCore/webaudio/AudioNode.idl new file mode 100644 index 0000000..5ed47cb --- /dev/null +++ b/WebCore/webaudio/AudioNode.idl @@ -0,0 +1,40 @@ +/* + * 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. + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY APPLE 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 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO + ] AudioNode { + readonly attribute AudioContext context; + readonly attribute unsigned long numberOfInputs; + readonly attribute unsigned long numberOfOutputs; + + [Custom] void connect(in AudioNode destination, in unsigned long output, in unsigned long input); + [Custom] void disconnect(in unsigned long output); + }; +} diff --git a/WebCore/webaudio/AudioParam.h b/WebCore/webaudio/AudioParam.h index 2fbd805..7643cf3 100644 --- a/WebCore/webaudio/AudioParam.h +++ b/WebCore/webaudio/AudioParam.h @@ -60,7 +60,14 @@ public: } float value() const { return static_cast<float>(m_value); } - void setValue(float value) { m_value = value; } + + void setValue(float value) + { + // Check against JavaScript giving us bogus floating-point values. + // Don't ASSERT, since this can happen if somebody writes bad JS. + if (!isnan(value) && !isinf(value)) + m_value = value; + } String name() const { return m_name; } |