diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-01-06 14:44:00 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-01-06 14:44:00 +0100 |
commit | 40736c5763bf61337c8c14e16d8587db021a87d4 (patch) | |
tree | b17a9c00042ad89cb1308e2484491799aa14e9f8 /Source/WebCore/webaudio | |
download | qtwebkit-40736c5763bf61337c8c14e16d8587db021a87d4.tar.gz |
Imported WebKit commit 2ea9d364d0f6efa8fa64acf19f451504c59be0e4 (http://svn.webkit.org/repository/webkit/trunk@104285)
Diffstat (limited to 'Source/WebCore/webaudio')
104 files changed, 10653 insertions, 0 deletions
diff --git a/Source/WebCore/webaudio/AsyncAudioDecoder.cpp b/Source/WebCore/webaudio/AsyncAudioDecoder.cpp new file mode 100644 index 000000000..c26824398 --- /dev/null +++ b/Source/WebCore/webaudio/AsyncAudioDecoder.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011, 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 "AsyncAudioDecoder.h" + +#include "AudioBuffer.h" +#include "AudioBufferCallback.h" +#include <wtf/ArrayBuffer.h> +#include <wtf/MainThread.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> + +namespace WebCore { + +AsyncAudioDecoder::AsyncAudioDecoder() +{ + // Start worker thread. + MutexLocker lock(m_threadCreationMutex); + m_threadID = createThread(AsyncAudioDecoder::threadEntry, this, "Audio Decoder"); +} + +AsyncAudioDecoder::~AsyncAudioDecoder() +{ + m_queue.kill(); + + // Stop thread. + void* exitCode; + waitForThreadCompletion(m_threadID, &exitCode); + m_threadID = 0; +} + +void AsyncAudioDecoder::decodeAsync(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback) +{ + ASSERT(isMainThread()); + ASSERT(audioData); + if (!audioData) + return; + + OwnPtr<DecodingTask> decodingTask = DecodingTask::create(audioData, sampleRate, successCallback, errorCallback); + m_queue.append(decodingTask.release()); // note that ownership of the task is effectively taken by the queue. +} + +// Asynchronously decode in this thread. +void* AsyncAudioDecoder::threadEntry(void* threadData) +{ + ASSERT(threadData); + AsyncAudioDecoder* decoder = reinterpret_cast<AsyncAudioDecoder*>(threadData); + decoder->runLoop(); + return 0; +} + +void AsyncAudioDecoder::runLoop() +{ + ASSERT(!isMainThread()); + + { + // Wait for until we have m_threadID established before starting the run loop. + MutexLocker lock(m_threadCreationMutex); + } + + // Keep running decoding tasks until we're killed. + while (OwnPtr<DecodingTask> decodingTask = m_queue.waitForMessage()) { + // Let the task take care of its own ownership. + // See DecodingTask::notifyComplete() for cleanup. + decodingTask.leakPtr()->decode(); + } +} + +PassOwnPtr<AsyncAudioDecoder::DecodingTask> AsyncAudioDecoder::DecodingTask::create(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback) +{ + return adoptPtr(new DecodingTask(audioData, sampleRate, successCallback, errorCallback)); +} + +AsyncAudioDecoder::DecodingTask::DecodingTask(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback) + : m_audioData(audioData) + , m_sampleRate(sampleRate) + , m_successCallback(successCallback) + , m_errorCallback(errorCallback) +{ +} + +void AsyncAudioDecoder::DecodingTask::decode() +{ + ASSERT(m_audioData.get()); + if (!m_audioData.get()) + return; + + // Do the actual decoding and invoke the callback. + m_audioBuffer = AudioBuffer::createFromAudioFileData(m_audioData->data(), m_audioData->byteLength(), false, sampleRate()); + + // Decoding is finished, but we need to do the callbacks on the main thread. + callOnMainThread(notifyCompleteDispatch, this); +} + +void AsyncAudioDecoder::DecodingTask::notifyCompleteDispatch(void* userData) +{ + AsyncAudioDecoder::DecodingTask* task = reinterpret_cast<AsyncAudioDecoder::DecodingTask*>(userData); + ASSERT(task); + if (!task) + return; + + task->notifyComplete(); +} + +void AsyncAudioDecoder::DecodingTask::notifyComplete() +{ + if (audioBuffer() && successCallback()) + successCallback()->handleEvent(audioBuffer()); + else if (errorCallback()) + errorCallback()->handleEvent(audioBuffer()); + + // Our ownership was given up in AsyncAudioDecoder::runLoop() + // Make sure to clean up here. + delete this; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AsyncAudioDecoder.h b/Source/WebCore/webaudio/AsyncAudioDecoder.h new file mode 100644 index 000000000..e2fc4dcb0 --- /dev/null +++ b/Source/WebCore/webaudio/AsyncAudioDecoder.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef AsyncAudioDecoder_h +#define AsyncAudioDecoder_h + +#include <wtf/Forward.h> +#include <wtf/MessageQueue.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioBuffer; +class AudioBufferCallback; + +// AsyncAudioDecoder asynchronously decodes audio file data from an ArrayBuffer in a worker thread. +// Upon successful decoding, a completion callback will be invoked with the decoded PCM data in an AudioBuffer. + +class AsyncAudioDecoder { + WTF_MAKE_NONCOPYABLE(AsyncAudioDecoder); +public: + AsyncAudioDecoder(); + ~AsyncAudioDecoder(); + + // Must be called on the main thread. + void decodeAsync(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback); + +private: + class DecodingTask { + WTF_MAKE_NONCOPYABLE(DecodingTask); + public: + static PassOwnPtr<DecodingTask> create(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback); + + void decode(); + + private: + DecodingTask(ArrayBuffer* audioData, float sampleRate, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback); + + ArrayBuffer* audioData() { return m_audioData.get(); } + float sampleRate() const { return m_sampleRate; } + AudioBufferCallback* successCallback() { return m_successCallback.get(); } + AudioBufferCallback* errorCallback() { return m_errorCallback.get(); } + AudioBuffer* audioBuffer() { return m_audioBuffer.get(); } + + static void notifyCompleteDispatch(void* userData); + void notifyComplete(); + + RefPtr<ArrayBuffer> m_audioData; + float m_sampleRate; + RefPtr<AudioBufferCallback> m_successCallback; + RefPtr<AudioBufferCallback> m_errorCallback; + RefPtr<AudioBuffer> m_audioBuffer; + }; + + static void* threadEntry(void* threadData); + void runLoop(); + + WTF::ThreadIdentifier m_threadID; + Mutex m_threadCreationMutex; + MessageQueue<DecodingTask> m_queue; +}; + +} // namespace WebCore + +#endif // AsyncAudioDecoder_h diff --git a/Source/WebCore/webaudio/AudioBasicProcessorNode.cpp b/Source/WebCore/webaudio/AudioBasicProcessorNode.cpp new file mode 100644 index 000000000..90142f27b --- /dev/null +++ b/Source/WebCore/webaudio/AudioBasicProcessorNode.cpp @@ -0,0 +1,149 @@ +/* + * 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 "AudioBasicProcessorNode.h" + +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "AudioProcessor.h" + +namespace WebCore { + +AudioBasicProcessorNode::AudioBasicProcessorNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 0))); + + // The subclass must create m_processor. +} + +void AudioBasicProcessorNode::initialize() +{ + if (isInitialized()) + return; + + ASSERT(processor()); + processor()->initialize(); + + AudioNode::initialize(); +} + +void AudioBasicProcessorNode::uninitialize() +{ + if (!isInitialized()) + return; + + ASSERT(processor()); + processor()->uninitialize(); + + AudioNode::uninitialize(); +} + +void AudioBasicProcessorNode::process(size_t framesToProcess) +{ + AudioBus* destinationBus = output(0)->bus(); + + // The realtime thread can't block on this lock, so we call tryLock() instead. + if (m_processLock.tryLock()) { + if (!isInitialized() || !processor()) + destinationBus->zero(); + else { + AudioBus* sourceBus = input(0)->bus(); + + // FIXME: if we take "tail time" into account, then we can avoid calling processor()->process() once the tail dies down. + if (!input(0)->isConnected()) + sourceBus->zero(); + + processor()->process(sourceBus, destinationBus, framesToProcess); + } + + m_processLock.unlock(); + } else { + // Too bad - the tryLock() failed. We must be in the middle of re-connecting and were already outputting silence anyway... + destinationBus->zero(); + } +} + +// Nice optimization in the very common case allowing for "in-place" processing +void AudioBasicProcessorNode::pullInputs(size_t framesToProcess) +{ + // Render input stream - suggest to the input to render directly into output bus for in-place processing in process() if possible. + input(0)->pull(output(0)->bus(), framesToProcess); +} + +void AudioBasicProcessorNode::reset() +{ + if (processor()) + processor()->reset(); +} + +// As soon as we know the channel count of our input, we can lazily initialize. +// Sometimes this may be called more than once with different channel counts, in which case we must safely +// uninitialize and then re-initialize with the new channel count. +void AudioBasicProcessorNode::checkNumberOfChannelsForInput(AudioNodeInput* input) +{ + ASSERT(context()->isAudioThread() && context()->isGraphOwner()); + + ASSERT(input == this->input(0)); + if (input != this->input(0)) + return; + + ASSERT(processor()); + if (!processor()) + return; + + unsigned numberOfChannels = input->numberOfChannels(); + + if (isInitialized() && numberOfChannels != output(0)->numberOfChannels()) { + // We're already initialized but the channel count has changed. + // We need to be careful since we may be actively processing right now, so synchronize with process(). + MutexLocker locker(m_processLock); + uninitialize(); + } + + if (!isInitialized()) { + // This will propagate the channel count to any nodes connected further down the chain... + output(0)->setNumberOfChannels(numberOfChannels); + + // Re-initialize the processor with the new channel count. + processor()->setNumberOfChannels(numberOfChannels); + initialize(); + } +} + +unsigned AudioBasicProcessorNode::numberOfChannels() +{ + return output(0)->numberOfChannels(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioBasicProcessorNode.h b/Source/WebCore/webaudio/AudioBasicProcessorNode.h new file mode 100644 index 000000000..5a555dafb --- /dev/null +++ b/Source/WebCore/webaudio/AudioBasicProcessorNode.h @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#ifndef AudioBasicProcessorNode_h +#define AudioBasicProcessorNode_h + +#include "AudioNode.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioBus; +class AudioNodeInput; +class AudioProcessor; + +// AudioBasicProcessorNode is an AudioNode with one input and one output where the input and output have the same number of channels. +class AudioBasicProcessorNode : public AudioNode { +public: + AudioBasicProcessorNode(AudioContext*, float sampleRate); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void pullInputs(size_t framesToProcess); + virtual void reset(); + virtual void initialize(); + virtual void uninitialize(); + + // Called in the main thread when the number of channels for the input may have changed. + virtual void checkNumberOfChannelsForInput(AudioNodeInput*); + + // Returns the number of channels for both the input and the output. + unsigned numberOfChannels(); + +protected: + AudioProcessor* processor() { return m_processor.get(); } + OwnPtr<AudioProcessor> m_processor; + +private: + // This synchronizes live channel count changes which require an uninitialization / re-initialization. + mutable Mutex m_processLock; +}; + +} // namespace WebCore + +#endif // AudioBasicProcessorNode_h diff --git a/Source/WebCore/webaudio/AudioBuffer.cpp b/Source/WebCore/webaudio/AudioBuffer.cpp new file mode 100644 index 000000000..0515a34a2 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBuffer.cpp @@ -0,0 +1,110 @@ +/* + * 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 "AudioBuffer.h" + +#include "AudioBus.h" +#include "AudioFileReader.h" +#include "ExceptionCode.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +PassRefPtr<AudioBuffer> AudioBuffer::create(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) +{ + if (sampleRate < 22050 || sampleRate > 96000 || numberOfChannels > 10 || !numberOfFrames) + return 0; + + return adoptRef(new AudioBuffer(numberOfChannels, numberOfFrames, sampleRate)); +} + +PassRefPtr<AudioBuffer> AudioBuffer::createFromAudioFileData(const void* data, size_t dataSize, bool mixToMono, float sampleRate) +{ + OwnPtr<AudioBus> bus = createBusFromInMemoryAudioFile(data, dataSize, mixToMono, sampleRate); + if (bus.get()) + return adoptRef(new AudioBuffer(bus.get())); + + return 0; +} + +AudioBuffer::AudioBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate) + : m_gain(1.0) + , m_sampleRate(sampleRate) + , m_length(numberOfFrames) +{ + m_channels.reserveCapacity(numberOfChannels); + + for (unsigned i = 0; i < numberOfChannels; ++i) { + RefPtr<Float32Array> channelDataArray = Float32Array::create(m_length); + m_channels.append(channelDataArray); + } +} + +AudioBuffer::AudioBuffer(AudioBus* bus) + : m_gain(1.0) + , m_sampleRate(bus->sampleRate()) + , m_length(bus->length()) +{ + // Copy audio data from the bus to the Float32Arrays we manage. + unsigned numberOfChannels = bus->numberOfChannels(); + m_channels.reserveCapacity(numberOfChannels); + for (unsigned i = 0; i < numberOfChannels; ++i) { + RefPtr<Float32Array> channelDataArray = Float32Array::create(m_length); + channelDataArray->setRange(bus->channel(i)->data(), m_length, 0); + m_channels.append(channelDataArray); + } +} + +void AudioBuffer::releaseMemory() +{ + m_channels.clear(); +} + +Float32Array* AudioBuffer::getChannelData(unsigned channelIndex) +{ + if (channelIndex >= m_channels.size()) + return 0; + + return m_channels[channelIndex].get(); +} + +void AudioBuffer::zero() +{ + for (unsigned i = 0; i < m_channels.size(); ++i) { + if (getChannelData(i)) + getChannelData(i)->zeroRange(0, length()); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioBuffer.h b/Source/WebCore/webaudio/AudioBuffer.h new file mode 100644 index 000000000..4aeadca57 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBuffer.h @@ -0,0 +1,81 @@ +/* + * 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 AudioBuffer_h +#define AudioBuffer_h + +#include <wtf/Float32Array.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioBus; + +class AudioBuffer : public RefCounted<AudioBuffer> { +public: + static PassRefPtr<AudioBuffer> create(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate); + + // Returns 0 if data is not a valid audio file. + static PassRefPtr<AudioBuffer> createFromAudioFileData(const void* data, size_t dataSize, bool mixToMono, float sampleRate); + + // Format + size_t length() const { return m_length; } + double duration() const { return length() / sampleRate(); } + float sampleRate() const { return m_sampleRate; } + + // Channel data access + unsigned numberOfChannels() const { return m_channels.size(); } + Float32Array* getChannelData(unsigned channelIndex); + void zero(); + + // Scalar gain + double gain() const { return m_gain; } + void setGain(double gain) { m_gain = gain; } + + // Because an AudioBuffer has a JavaScript wrapper, which will be garbage collected, it may take awhile for this object to be deleted. + // releaseMemory() can be called when the AudioContext goes away, so we can release the memory earlier than when the garbage collection happens. + // Careful! Only call this when the page unloads, after the AudioContext is no longer processing. + void releaseMemory(); + +protected: + AudioBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate); + AudioBuffer(AudioBus* bus); + + double m_gain; // scalar gain + float m_sampleRate; + size_t m_length; + + Vector<RefPtr<Float32Array> > m_channels; +}; + +} // namespace WebCore + +#endif // AudioBuffer_h diff --git a/Source/WebCore/webaudio/AudioBuffer.idl b/Source/WebCore/webaudio/AudioBuffer.idl new file mode 100644 index 000000000..8a734e28f --- /dev/null +++ b/Source/WebCore/webaudio/AudioBuffer.idl @@ -0,0 +1,43 @@ +/* + * 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 + ] AudioBuffer { + readonly attribute long length; // in sample-frames + readonly attribute float duration; // in seconds + readonly attribute float sampleRate; // in sample-frames per second + + attribute float gain; // linear gain (default 1.0) + + // Channel access + readonly attribute unsigned long numberOfChannels; + Float32Array getChannelData(in unsigned long channelIndex); + }; +} diff --git a/Source/WebCore/webaudio/AudioBufferCallback.h b/Source/WebCore/webaudio/AudioBufferCallback.h new file mode 100644 index 000000000..feebc469b --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferCallback.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef AudioBufferCallback_h +#define AudioBufferCallback_h + +#if ENABLE(WEB_AUDIO) + +#include <wtf/RefCounted.h> + +namespace WebCore { + +class AudioBuffer; + +class AudioBufferCallback : public RefCounted<AudioBufferCallback> { +public: + virtual ~AudioBufferCallback() { } + virtual bool handleEvent(AudioBuffer*) = 0; +}; + +} // namespace + +#endif // ENABLE(WEB_AUDIO) + +#endif // AudioBufferCallback_h diff --git a/Source/WebCore/webaudio/AudioBufferCallback.idl b/Source/WebCore/webaudio/AudioBufferCallback.idl new file mode 100644 index 000000000..f2d311b92 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferCallback.idl @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS, + Callback + ] AudioBufferCallback { + boolean handleEvent(in AudioBuffer audioBuffer); + }; +} diff --git a/Source/WebCore/webaudio/AudioBufferSourceNode.cpp b/Source/WebCore/webaudio/AudioBufferSourceNode.cpp new file mode 100644 index 000000000..c8a132295 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.cpp @@ -0,0 +1,507 @@ +/* + * 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 "AudioBufferSourceNode.h" + +#include "AudioContext.h" +#include "AudioNodeOutput.h" +#include "Document.h" +#include "FloatConversion.h" +#include "ScriptCallStack.h" +#include <algorithm> +#include <wtf/MainThread.h> +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +const double DefaultGrainDuration = 0.020; // 20ms +const double UnknownTime = -1; + +// Arbitrary upper limit on playback rate. +// Higher than expected rates can be useful when playing back oversampled buffers +// to minimize linear interpolation aliasing. +const double MaxRate = 1024; + +PassRefPtr<AudioBufferSourceNode> AudioBufferSourceNode::create(AudioContext* context, float sampleRate) +{ + return adoptRef(new AudioBufferSourceNode(context, sampleRate)); +} + +AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* context, float sampleRate) + : AudioSourceNode(context, sampleRate) + , m_buffer(0) + , m_isPlaying(false) + , m_isLooping(false) + , m_hasFinished(false) + , m_startTime(0.0) + , m_endTime(UnknownTime) + , m_virtualReadIndex(0) + , m_isGrain(false) + , m_grainOffset(0.0) + , m_grainDuration(DefaultGrainDuration) + , m_lastGain(1.0) + , m_pannerNode(0) +{ + setNodeType(NodeTypeAudioBufferSource); + + m_gain = AudioGain::create("gain", 1.0, 0.0, 1.0); + m_playbackRate = AudioParam::create("playbackRate", 1.0, 0.0, MaxRate); + + m_gain->setContext(context); + m_playbackRate->setContext(context); + + // Default to mono. A call to setBuffer() will set the number of output channels to that of the buffer. + addOutput(adoptPtr(new AudioNodeOutput(this, 1))); + + initialize(); +} + +AudioBufferSourceNode::~AudioBufferSourceNode() +{ + uninitialize(); +} + +void AudioBufferSourceNode::process(size_t framesToProcess) +{ + AudioBus* outputBus = output(0)->bus(); + + if (!isInitialized()) { + outputBus->zero(); + return; + } + + // The audio thread can't block on this lock, so we call tryLock() instead. + // Careful - this is a tryLock() and not an autolocker, so we must unlock() before every return. + if (m_processLock.tryLock()) { + // Check if it's time to start playing. + float sampleRate = this->sampleRate(); + double quantumStartTime = context()->currentTime(); + double quantumEndTime = quantumStartTime + framesToProcess / sampleRate; + + // If we know the end time and it's already passed, then don't bother doing any more rendering this cycle. + if (m_endTime != UnknownTime && m_endTime <= quantumStartTime) { + m_isPlaying = false; + m_virtualReadIndex = 0; + finish(); + } + + if (!m_isPlaying || m_hasFinished || !buffer() || m_startTime >= quantumEndTime) { + // FIXME: can optimize here by propagating silent hint instead of forcing the whole chain to process silence. + outputBus->zero(); + m_processLock.unlock(); + return; + } + + double quantumTimeOffset = m_startTime > quantumStartTime ? m_startTime - quantumStartTime : 0; + size_t quantumFrameOffset = static_cast<unsigned>(quantumTimeOffset * sampleRate); + quantumFrameOffset = min(quantumFrameOffset, framesToProcess); // clamp to valid range + size_t bufferFramesToProcess = framesToProcess - quantumFrameOffset; + + // Render by reading directly from the buffer. + renderFromBuffer(outputBus, quantumFrameOffset, bufferFramesToProcess); + + // Apply the gain (in-place) to the output bus. + double totalGain = gain()->value() * m_buffer->gain(); + outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); + + // If the end time is somewhere in the middle of this time quantum, then simply zero out the + // frames starting at the end time. + if (m_endTime != UnknownTime && m_endTime >= quantumStartTime && m_endTime < quantumEndTime) { + size_t zeroStartFrame = narrowPrecisionToFloat((m_endTime - quantumStartTime) * sampleRate); + size_t framesToZero = framesToProcess - zeroStartFrame; + + bool isSafe = zeroStartFrame < framesToProcess && framesToZero <= framesToProcess && zeroStartFrame + framesToZero <= framesToProcess; + ASSERT(isSafe); + + if (isSafe) { + for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) + memset(outputBus->channel(i)->data() + zeroStartFrame, 0, sizeof(float) * framesToZero); + } + + m_isPlaying = false; + m_virtualReadIndex = 0; + finish(); + } + + m_processLock.unlock(); + } else { + // Too bad - the tryLock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. + outputBus->zero(); + } +} + +// Returns true if we're finished. +bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(float* destinationL, float* destinationR, size_t framesToProcess) +{ + if (!loop()) { + // If we're not looping, then stop playing when we get to the end. + m_isPlaying = false; + + if (framesToProcess > 0) { + // We're not looping and we've reached the end of the sample data, but we still need to provide more output, + // so generate silence for the remaining. + memset(destinationL, 0, sizeof(float) * framesToProcess); + + if (destinationR) + memset(destinationR, 0, sizeof(float) * framesToProcess); + } + + finish(); + return true; + } + return false; +} + +void AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) +{ + ASSERT(context()->isAudioThread()); + + // Basic sanity checking + ASSERT(bus); + ASSERT(buffer()); + if (!bus || !buffer()) + return; + + unsigned numberOfChannels = this->numberOfChannels(); + unsigned busNumberOfChannels = bus->numberOfChannels(); + + // FIXME: we can add support for sources with more than two channels, but this is not a common case. + bool channelCountGood = numberOfChannels == busNumberOfChannels && (numberOfChannels == 1 || numberOfChannels == 2); + ASSERT(channelCountGood); + if (!channelCountGood) + return; + + // Get the destination pointers. + float* destinationL = bus->channel(0)->data(); + ASSERT(destinationL); + if (!destinationL) + return; + float* destinationR = (numberOfChannels < 2) ? 0 : bus->channel(1)->data(); + + bool isStereo = destinationR; + + // Sanity check destinationFrameOffset, numberOfFrames. + size_t destinationLength = bus->length(); + + bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; + ASSERT(isLengthGood); + if (!isLengthGood) + return; + + bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; + ASSERT(isOffsetGood); + if (!isOffsetGood) + return; + + // Potentially zero out initial frames leading up to the offset. + if (destinationFrameOffset) { + memset(destinationL, 0, sizeof(float) * destinationFrameOffset); + if (destinationR) + memset(destinationR, 0, sizeof(float) * destinationFrameOffset); + } + + // Offset the pointers to the correct offset frame. + destinationL += destinationFrameOffset; + if (destinationR) + destinationR += destinationFrameOffset; + + size_t bufferLength = buffer()->length(); + double bufferSampleRate = buffer()->sampleRate(); + + // Calculate the start and end frames in our buffer that we want to play. + // If m_isGrain is true, then we will be playing a portion of the total buffer. + unsigned startFrame = m_isGrain ? static_cast<unsigned>(m_grainOffset * bufferSampleRate) : 0; + unsigned endFrame = m_isGrain ? static_cast<unsigned>(startFrame + m_grainDuration * bufferSampleRate) : bufferLength; + + ASSERT(endFrame >= startFrame); + if (endFrame < startFrame) + return; + + unsigned deltaFrames = endFrame - startFrame; + + // This is a HACK to allow for HRTF tail-time - avoids glitch at end. + // FIXME: implement tailTime for each AudioNode for a more general solution to this problem. + if (m_isGrain) + endFrame += 512; + + // Do some sanity checking. + if (startFrame >= bufferLength) + startFrame = !bufferLength ? 0 : bufferLength - 1; + if (endFrame > bufferLength) + endFrame = bufferLength; + if (m_virtualReadIndex >= endFrame) + m_virtualReadIndex = startFrame; // reset to start + + // Get pointers to the start of the sample buffer. + float* sourceL = m_buffer->getChannelData(0)->data(); + float* sourceR = m_buffer->numberOfChannels() == 2 ? m_buffer->getChannelData(1)->data() : 0; + + double pitchRate = totalPitchRate(); + + // Get local copy. + double virtualReadIndex = m_virtualReadIndex; + + // Render loop - reading from the source buffer to the destination using linear interpolation. + int framesToProcess = numberOfFrames; + + // Optimize for the very common case of playing back with pitchRate == 1. + // We can avoid the linear interpolation. + if (pitchRate == 1 && virtualReadIndex == floor(virtualReadIndex)) { + unsigned readIndex = static_cast<unsigned>(virtualReadIndex); + while (framesToProcess > 0) { + int framesToEnd = endFrame - readIndex; + int framesThisTime = min(framesToProcess, framesToEnd); + framesThisTime = max(0, framesThisTime); + + memcpy(destinationL, sourceL + readIndex, sizeof(*sourceL) * framesThisTime); + destinationL += framesThisTime; + if (isStereo) { + memcpy(destinationR, sourceR + readIndex, sizeof(*sourceR) * framesThisTime); + destinationR += framesThisTime; + } + + readIndex += framesThisTime; + framesToProcess -= framesThisTime; + + // Wrap-around. + if (readIndex >= endFrame) { + readIndex -= deltaFrames; + if (renderSilenceAndFinishIfNotLooping(destinationL, destinationR, framesToProcess)) + break; + } + } + virtualReadIndex = readIndex; + } else { + while (framesToProcess--) { + unsigned readIndex = static_cast<unsigned>(virtualReadIndex); + double interpolationFactor = virtualReadIndex - readIndex; + + // For linear interpolation we need the next sample-frame too. + unsigned readIndex2 = readIndex + 1; + if (readIndex2 >= endFrame) { + if (loop()) { + // Make sure to wrap around at the end of the buffer. + readIndex2 -= deltaFrames; + } else + readIndex2 = readIndex; + } + + // Final sanity check on buffer access. + // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. + if (readIndex >= bufferLength || readIndex2 >= bufferLength) + break; + + // Linear interpolation. + double sampleL1 = sourceL[readIndex]; + double sampleL2 = sourceL[readIndex2]; + double sampleL = (1.0 - interpolationFactor) * sampleL1 + interpolationFactor * sampleL2; + *destinationL++ = narrowPrecisionToFloat(sampleL); + + if (isStereo) { + double sampleR1 = sourceR[readIndex]; + double sampleR2 = sourceR[readIndex2]; + double sampleR = (1.0 - interpolationFactor) * sampleR1 + interpolationFactor * sampleR2; + *destinationR++ = narrowPrecisionToFloat(sampleR); + } + + virtualReadIndex += pitchRate; + + // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. + if (virtualReadIndex >= endFrame) { + virtualReadIndex -= deltaFrames; + + if (renderSilenceAndFinishIfNotLooping(destinationL, destinationR, framesToProcess)) + break; + } + } + } + m_virtualReadIndex = virtualReadIndex; +} + + +void AudioBufferSourceNode::reset() +{ + m_virtualReadIndex = 0; + m_lastGain = gain()->value(); +} + +void AudioBufferSourceNode::finish() +{ + if (!m_hasFinished) { + // Let the context dereference this AudioNode. + context()->notifyNodeFinishedProcessing(this); + m_hasFinished = true; + } +} + +bool AudioBufferSourceNode::setBuffer(AudioBuffer* buffer) +{ + ASSERT(isMainThread()); + + // The context must be locked since changing the buffer can re-configure the number of channels that are output. + AudioContext::AutoLocker contextLocker(context()); + + // This synchronizes with process(). + MutexLocker processLocker(m_processLock); + + if (buffer) { + // Do any necesssary re-configuration to the buffer's number of channels. + unsigned numberOfChannels = buffer->numberOfChannels(); + if (!numberOfChannels || numberOfChannels > 2) { + // FIXME: implement multi-channel greater than stereo. + return false; + } + output(0)->setNumberOfChannels(numberOfChannels); + } + + m_virtualReadIndex = 0; + m_buffer = buffer; + + return true; +} + +unsigned AudioBufferSourceNode::numberOfChannels() +{ + return output(0)->numberOfChannels(); +} + +void AudioBufferSourceNode::noteOn(double when) +{ + ASSERT(isMainThread()); + if (m_isPlaying) + return; + + m_isGrain = false; + m_startTime = when; + m_virtualReadIndex = 0; + m_isPlaying = true; +} + +void AudioBufferSourceNode::noteGrainOn(double when, double grainOffset, double grainDuration) +{ + ASSERT(isMainThread()); + if (m_isPlaying) + return; + + if (!buffer()) + return; + + // Do sanity checking of grain parameters versus buffer size. + double bufferDuration = buffer()->duration(); + + if (grainDuration > bufferDuration) + return; // FIXME: maybe should throw exception - consider in specification. + + double maxGrainOffset = bufferDuration - grainDuration; + maxGrainOffset = max(0.0, maxGrainOffset); + + grainOffset = max(0.0, grainOffset); + grainOffset = min(maxGrainOffset, grainOffset); + m_grainOffset = grainOffset; + + m_grainDuration = grainDuration; + + m_isGrain = true; + m_startTime = when; + + // We call floor() here since at playbackRate == 1 we don't want to go through linear interpolation + // at a sub-sample position since it will degrade the quality. + // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer. + // Since playbackRate == 1 is very common, it's worth considering quality. + m_virtualReadIndex = floor(m_grainOffset * buffer()->sampleRate()); + + m_isPlaying = true; +} + +void AudioBufferSourceNode::noteOff(double when) +{ + ASSERT(isMainThread()); + if (!m_isPlaying) + return; + + when = max(0.0, when); + m_endTime = when; +} + +double AudioBufferSourceNode::totalPitchRate() +{ + double dopplerRate = 1.0; + if (m_pannerNode.get()) + dopplerRate = m_pannerNode->dopplerRate(); + + // Incorporate buffer's sample-rate versus AudioContext's sample-rate. + // Normally it's not an issue because buffers are loaded at the AudioContext's sample-rate, but we can handle it in any case. + double sampleRateFactor = 1.0; + if (buffer()) + sampleRateFactor = buffer()->sampleRate() / sampleRate(); + + double basePitchRate = playbackRate()->value(); + + double totalRate = dopplerRate * sampleRateFactor * basePitchRate; + + // Sanity check the total rate. It's very important that the resampler not get any bad rate values. + totalRate = max(0.0, totalRate); + if (!totalRate) + totalRate = 1; // zero rate is considered illegal + totalRate = min(MaxRate, totalRate); + + bool isTotalRateValid = !isnan(totalRate) && !isinf(totalRate); + ASSERT(isTotalRateValid); + if (!isTotalRateValid) + totalRate = 1.0; + + return totalRate; +} + +bool AudioBufferSourceNode::looping() +{ + static bool firstTime = true; + if (firstTime && context() && context()->document()) { + context()->document()->addConsoleMessage(JSMessageSource, LogMessageType, WarningMessageLevel, "AudioBufferSourceNode 'looping' attribute is deprecated. Use 'loop' instead."); + firstTime = false; + } + + return m_isLooping; +} + +void AudioBufferSourceNode::setLooping(bool looping) +{ + static bool firstTime = true; + if (firstTime && context() && context()->document()) { + context()->document()->addConsoleMessage(JSMessageSource, LogMessageType, WarningMessageLevel, "AudioBufferSourceNode 'looping' attribute is deprecated. Use 'loop' instead."); + firstTime = false; + } + + m_isLooping = looping; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioBufferSourceNode.h b/Source/WebCore/webaudio/AudioBufferSourceNode.h new file mode 100644 index 000000000..777ba89e3 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.h @@ -0,0 +1,146 @@ +/* + * 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. + */ + +#ifndef AudioBufferSourceNode_h +#define AudioBufferSourceNode_h + +#include "AudioBuffer.h" +#include "AudioBus.h" +#include "AudioGain.h" +#include "AudioPannerNode.h" +#include "AudioSourceNode.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioContext; + +// AudioBufferSourceNode is an AudioNode representing an audio source from an in-memory audio asset represented by an AudioBuffer. +// It generally will be used for short sounds which require a high degree of scheduling flexibility (can playback in rhythmically perfect ways). + +class AudioBufferSourceNode : public AudioSourceNode { +public: + static PassRefPtr<AudioBufferSourceNode> create(AudioContext*, float sampleRate); + + virtual ~AudioBufferSourceNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + + // setBuffer() is called on the main thread. This is the buffer we use for playback. + // returns true on success. + bool setBuffer(AudioBuffer*); + AudioBuffer* buffer() { return m_buffer.get(); } + + // numberOfChannels() returns the number of output channels. This value equals the number of channels from the buffer. + // If a new buffer is set with a different number of channels, then this value will dynamically change. + unsigned numberOfChannels(); + + // Play-state + // noteOn(), noteGrainOn(), and noteOff() must all be called from the main thread. + void noteOn(double when); + void noteGrainOn(double when, double grainOffset, double grainDuration); + void noteOff(double when); + + // Note: the attribute was originally exposed as .looping, but to be more consistent in naming with <audio> + // and with how it's described in the specification, the proper attribute name is .loop + // The old attribute is kept for backwards compatibility. + bool loop() const { return m_isLooping; } + void setLoop(bool looping) { m_isLooping = looping; } + + // Deprecated. + bool looping(); + void setLooping(bool); + + AudioGain* gain() { return m_gain.get(); } + AudioParam* playbackRate() { return m_playbackRate.get(); } + + // If a panner node is set, then we can incorporate doppler shift into the playback pitch rate. + void setPannerNode(PassRefPtr<AudioPannerNode> pannerNode) { m_pannerNode = pannerNode; } + +private: + AudioBufferSourceNode(AudioContext*, float sampleRate); + + void renderFromBuffer(AudioBus*, unsigned destinationFrameOffset, size_t numberOfFrames); + + inline bool renderSilenceAndFinishIfNotLooping(float* destinationL, float* destinationR, size_t framesToProcess); + + // m_buffer holds the sample data which this node outputs. + RefPtr<AudioBuffer> m_buffer; + + // Used for the "gain" and "playbackRate" attributes. + RefPtr<AudioGain> m_gain; + RefPtr<AudioParam> m_playbackRate; + + // m_isPlaying is set to true when noteOn() or noteGrainOn() is called. + bool m_isPlaying; + + // If m_isLooping is false, then this node will be done playing and become inactive after it reaches the end of the sample data in the buffer. + // If true, it will wrap around to the start of the buffer each time it reaches the end. + bool m_isLooping; + + // This node is considered finished when it reaches the end of the buffer's sample data after noteOn() has been called. + // This will only be set to true if m_isLooping == false. + bool m_hasFinished; + + // m_startTime is the time to start playing based on the context's timeline (0.0 or a time less than the context's current time means "now"). + double m_startTime; // in seconds + + // m_endTime is the time to stop playing based on the context's timeline (0.0 or a time less than the context's current time means "now"). + // If it hasn't been set explicitly, then the sound will not stop playing (if looping) or will stop when the end of the AudioBuffer + // has been reached. + double m_endTime; // in seconds + + // m_virtualReadIndex is a sample-frame index into our buffer representing the current playback position. + // Since it's floating-point, it has sub-sample accuracy. + double m_virtualReadIndex; + + // Granular playback + bool m_isGrain; + double m_grainOffset; // in seconds + double m_grainDuration; // in seconds + + // totalPitchRate() returns the instantaneous pitch rate (non-time preserving). + // It incorporates the base pitch rate, any sample-rate conversion factor from the buffer, and any doppler shift from an associated panner node. + double totalPitchRate(); + + // m_lastGain provides continuity when we dynamically adjust the gain. + double m_lastGain; + + // We optionally keep track of a panner node which has a doppler shift that is incorporated into the pitch rate. + RefPtr<AudioPannerNode> m_pannerNode; + + // This synchronizes process() with setBuffer() which can cause dynamic channel count changes. + mutable Mutex m_processLock; + + // Handles the time when we reach the end of sample data (non-looping) or the noteOff() time has been reached. + void finish(); +}; + +} // namespace WebCore + +#endif // AudioBufferSourceNode_h diff --git a/Source/WebCore/webaudio/AudioBufferSourceNode.idl b/Source/WebCore/webaudio/AudioBufferSourceNode.idl new file mode 100644 index 000000000..c39ec7524 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.idl @@ -0,0 +1,44 @@ +/* + * 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. + */ + +module audio { + // A cached (non-streamed), memory-resident audio source + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioBufferSourceNode : AudioSourceNode { + attribute [CustomSetter] AudioBuffer buffer + setter raises (DOMException); + + readonly attribute AudioGain gain; + readonly attribute AudioParam playbackRate; + + attribute boolean loop; // This is the proper attribute name from the specification. + attribute boolean looping; // This is an alias for the .loop attribute for backwards compatibility. + + void noteOn(in float when); + void noteGrainOn(in float when, in float grainOffset, in float grainDuration); + void noteOff(in float when); + }; +} diff --git a/Source/WebCore/webaudio/AudioChannelMerger.cpp b/Source/WebCore/webaudio/AudioChannelMerger.cpp new file mode 100644 index 000000000..51e785586 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelMerger.cpp @@ -0,0 +1,112 @@ +/* + * 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 "AudioChannelMerger.h" + +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" + +namespace WebCore { + +// This is considering that 5.1 (6 channels) is the largest we'll ever deal with. +// It can easily be increased to support more if the web audio specification is updated. +const unsigned NumberOfInputs = 6; + +AudioChannelMerger::AudioChannelMerger(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + // Create a fixed number of inputs (able to handle the maximum number of channels we deal with). + for (unsigned i = 0; i < NumberOfInputs; ++i) + addInput(adoptPtr(new AudioNodeInput(this))); + + addOutput(adoptPtr(new AudioNodeOutput(this, 1))); + + setNodeType(NodeTypeChannelMerger); + + initialize(); +} + +void AudioChannelMerger::process(size_t framesToProcess) +{ + AudioNodeOutput* output = this->output(0); + ASSERT(output); + ASSERT_UNUSED(framesToProcess, framesToProcess == output->bus()->length()); + + // Merge all the channels from all the inputs into one output. + unsigned outputChannelIndex = 0; + for (unsigned i = 0; i < numberOfInputs(); ++i) { + AudioNodeInput* input = this->input(i); + if (input->isConnected()) { + unsigned numberOfInputChannels = input->bus()->numberOfChannels(); + + // Merge channels from this particular input. + for (unsigned j = 0; j < numberOfInputChannels; ++j) { + AudioChannel* inputChannel = input->bus()->channel(j); + AudioChannel* outputChannel = output->bus()->channel(outputChannelIndex); + outputChannel->copyFrom(inputChannel); + + ++outputChannelIndex; + } + } + } + + ASSERT(outputChannelIndex == output->numberOfChannels()); +} + +void AudioChannelMerger::reset() +{ +} + +// Any time a connection or disconnection happens on any of our inputs, we potentially need to change the +// number of channels of our output. +void AudioChannelMerger::checkNumberOfChannelsForInput(AudioNodeInput*) +{ + ASSERT(context()->isAudioThread() && context()->isGraphOwner()); + + // Count how many channels we have all together from all of the inputs. + unsigned numberOfOutputChannels = 0; + for (unsigned i = 0; i < numberOfInputs(); ++i) { + AudioNodeInput* input = this->input(i); + if (input->isConnected()) + numberOfOutputChannels += input->bus()->numberOfChannels(); + } + + // Set the correct number of channels on the output + AudioNodeOutput* output = this->output(0); + ASSERT(output); + output->setNumberOfChannels(numberOfOutputChannels); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioChannelMerger.h b/Source/WebCore/webaudio/AudioChannelMerger.h new file mode 100644 index 000000000..e773daeb9 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelMerger.h @@ -0,0 +1,59 @@ +/* + * 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 AudioChannelMerger_h +#define AudioChannelMerger_h + +#include "AudioNode.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioContext; + +class AudioChannelMerger : public AudioNode { +public: + static PassRefPtr<AudioChannelMerger> create(AudioContext* context, float sampleRate) + { + return adoptRef(new AudioChannelMerger(context, sampleRate)); + } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + + // Called in the audio thread (pre-rendering task) when the number of channels for an input may have changed. + virtual void checkNumberOfChannelsForInput(AudioNodeInput*); + +private: + AudioChannelMerger(AudioContext*, float sampleRate); +}; + +} // namespace WebCore + +#endif // AudioChannelMerger_h diff --git a/Source/WebCore/webaudio/AudioChannelMerger.idl b/Source/WebCore/webaudio/AudioChannelMerger.idl new file mode 100644 index 000000000..3862af9d1 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelMerger.idl @@ -0,0 +1,34 @@ +/* + * 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 + ] AudioChannelMerger : AudioNode { + }; +} diff --git a/Source/WebCore/webaudio/AudioChannelSplitter.cpp b/Source/WebCore/webaudio/AudioChannelSplitter.cpp new file mode 100644 index 000000000..e5cc72a86 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelSplitter.cpp @@ -0,0 +1,84 @@ +/* + * 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 "AudioChannelSplitter.h" + +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" + +namespace WebCore { + +// This is considering that 5.1 (6 channels) is the largest we'll ever deal with. +// It can easily be increased to support more if the web audio specification is updated. +const unsigned NumberOfOutputs = 6; + +AudioChannelSplitter::AudioChannelSplitter(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + + // Create a fixed number of outputs (able to handle the maximum number of channels fed to an input). + for (unsigned i = 0; i < NumberOfOutputs; ++i) + addOutput(adoptPtr(new AudioNodeOutput(this, 1))); + + setNodeType(NodeTypeChannelSplitter); + + initialize(); +} + +void AudioChannelSplitter::process(size_t framesToProcess) +{ + AudioBus* source = input(0)->bus(); + ASSERT(source); + ASSERT_UNUSED(framesToProcess, framesToProcess == source->length()); + + unsigned numberOfSourceChannels = source->numberOfChannels(); + + ASSERT(numberOfOutputs() == NumberOfOutputs); + for (unsigned i = 0; i < NumberOfOutputs; ++i) { + AudioBus* destination = output(i)->bus(); + ASSERT(destination); + + if (i < numberOfSourceChannels) { + // Split the channel out if it exists in the source. + // It would be nice to avoid the copy and simply pass along pointers, but this becomes extremely difficult with fanout and fanin. + destination->channel(0)->copyFrom(source->channel(i)); + } else if (output(i)->renderingFanOutCount() > 0) { + // Only bother zeroing out the destination if it's connected to anything + destination->zero(); + } + } +} + +void AudioChannelSplitter::reset() +{ +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioChannelSplitter.h b/Source/WebCore/webaudio/AudioChannelSplitter.h new file mode 100644 index 000000000..71b0ef415 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelSplitter.h @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#ifndef AudioChannelSplitter_h +#define AudioChannelSplitter_h + +#include "AudioNode.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioContext; + +class AudioChannelSplitter : public AudioNode { +public: + static PassRefPtr<AudioChannelSplitter> create(AudioContext* context, float sampleRate) + { + return adoptRef(new AudioChannelSplitter(context, sampleRate)); + } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + +private: + AudioChannelSplitter(AudioContext*, float sampleRate); +}; + +} // namespace WebCore + +#endif // AudioChannelSplitter_h diff --git a/Source/WebCore/webaudio/AudioChannelSplitter.idl b/Source/WebCore/webaudio/AudioChannelSplitter.idl new file mode 100644 index 000000000..076c0510e --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelSplitter.idl @@ -0,0 +1,30 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO + ] AudioChannelSplitter : AudioNode { + }; +} diff --git a/Source/WebCore/webaudio/AudioContext.cpp b/Source/WebCore/webaudio/AudioContext.cpp new file mode 100644 index 000000000..dca96063a --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.cpp @@ -0,0 +1,788 @@ +/* + * 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 "AudioContext.h" + +#include "AsyncAudioDecoder.h" +#include "AudioBuffer.h" +#include "AudioBufferCallback.h" +#include "AudioBufferSourceNode.h" +#include "AudioChannelMerger.h" +#include "AudioChannelSplitter.h" +#include "AudioGainNode.h" +#include "AudioListener.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "AudioPannerNode.h" +#include "BiquadFilterNode.h" +#include "ConvolverNode.h" +#include "DefaultAudioDestinationNode.h" +#include "DelayNode.h" +#include "Document.h" +#include "DynamicsCompressorNode.h" +#include "ExceptionCode.h" +#include "FFTFrame.h" +#include "HRTFDatabaseLoader.h" +#include "HRTFPanner.h" +#include "HighPass2FilterNode.h" +#include "JavaScriptAudioNode.h" +#include "LowPass2FilterNode.h" +#include "OfflineAudioCompletionEvent.h" +#include "OfflineAudioDestinationNode.h" +#include "PlatformString.h" +#include "RealtimeAnalyserNode.h" +#include "WaveShaperNode.h" +#include "ScriptCallStack.h" + +#if ENABLE(VIDEO) +#include "HTMLMediaElement.h" +#include "MediaElementAudioSourceNode.h" +#endif + +#if DEBUG_AUDIONODE_REFERENCES +#include <stdio.h> +#endif + +#include <wtf/ArrayBuffer.h> +#include <wtf/MainThread.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; + +const unsigned MaxNodesToDeletePerQuantum = 10; + +namespace WebCore { + +namespace { + +bool 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) +{ + ASSERT(document); + ASSERT(isMainThread()); + if (s_hardwareContextCount >= MaxHardwareContexts) + return 0; + + return adoptRef(new AudioContext(document)); +} + +PassRefPtr<AudioContext> AudioContext::createOfflineContext(Document* document, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionCode& ec) +{ + ASSERT(document); + + // FIXME: offline contexts have limitations on supported sample-rates. + // Currently all AudioContexts must have the same sample-rate. + HRTFDatabaseLoader* loader = HRTFDatabaseLoader::loader(); + if (numberOfChannels > 10 || !isSampleRateRangeGood(sampleRate) || (loader && loader->databaseSampleRate() != sampleRate)) { + ec = SYNTAX_ERR; + return 0; + } + + return adoptRef(new AudioContext(document, numberOfChannels, numberOfFrames, sampleRate)); +} + +// Constructor for rendering to the audio hardware. +AudioContext::AudioContext(Document* document) + : ActiveDOMObject(document, this) + , m_isInitialized(false) + , m_isAudioThreadFinished(false) + , m_document(document) + , m_destinationNode(0) + , m_isDeletionScheduled(false) + , m_connectionCount(0) + , m_audioThread(0) + , m_graphOwnerThread(UndefinedThreadIdentifier) + , m_isOfflineContext(false) +{ + 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, this) + , m_isInitialized(false) + , m_isAudioThreadFinished(false) + , m_document(document) + , m_destinationNode(0) + , m_connectionCount(0) + , m_audioThread(0) + , m_graphOwnerThread(UndefinedThreadIdentifier) + , m_isOfflineContext(true) +{ + 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); + m_destinationNode = OfflineAudioDestinationNode::create(this, m_renderTarget.get()); +} + +void AudioContext::constructCommon() +{ + FFTFrame::initialize(); + + m_listener = AudioListener::create(); + m_temporaryMonoBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); + m_temporaryStereoBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); +} + +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()) { + 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::uninitialize() +{ + ASSERT(isMainThread()); + + if (m_isInitialized) { + // Protect this object from being deleted before we finish uninitializing. + RefPtr<AudioContext> protect(this); + + // 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(); + + if (!isOfflineContext()) { + ASSERT(s_hardwareContextCount); + --s_hardwareContextCount; + } + + // Get rid of the sources which may still be playing. + derefUnfinishedSourceNodes(); + + deleteMarkedNodes(); + + // 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 m_hrtfDatabaseLoader->isLoaded(); +} + +void AudioContext::uninitializeDispatch(void* userData) +{ + AudioContext* context = reinterpret_cast<AudioContext*>(userData); + ASSERT(context); + if (!context) + return; + + context->uninitialize(); +} + +void AudioContext::stop() +{ + m_document = 0; // document is going away + + // Don't call uninitialize() immediately here because the ScriptExecutionContext 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(uninitializeDispatch, this); +} + +Document* AudioContext::document() const +{ + 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, float sampleRate, ExceptionCode& ec) +{ + RefPtr<AudioBuffer> audioBuffer = AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); + if (!audioBuffer.get()) { + ec = SYNTAX_ERR; + return 0; + } + + return audioBuffer; +} + +PassRefPtr<AudioBuffer> AudioContext::createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono, ExceptionCode& ec) +{ + ASSERT(arrayBuffer); + if (!arrayBuffer) { + ec = SYNTAX_ERR; + return 0; + } + + RefPtr<AudioBuffer> audioBuffer = AudioBuffer::createFromAudioFileData(arrayBuffer->data(), arrayBuffer->byteLength(), mixToMono, sampleRate()); + if (!audioBuffer.get()) { + ec = SYNTAX_ERR; + return 0; + } + + return audioBuffer; +} + +void AudioContext::decodeAudioData(ArrayBuffer* audioData, PassRefPtr<AudioBufferCallback> successCallback, PassRefPtr<AudioBufferCallback> errorCallback, ExceptionCode& ec) +{ + if (!audioData) { + ec = SYNTAX_ERR; + return; + } + m_audioDecoder.decodeAsync(audioData, sampleRate(), successCallback, errorCallback); +} + +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; +} + +#if ENABLE(VIDEO) +PassRefPtr<MediaElementAudioSourceNode> AudioContext::createMediaElementSource(HTMLMediaElement* mediaElement, ExceptionCode& ec) +{ + ASSERT(mediaElement); + if (!mediaElement) { + ec = INVALID_STATE_ERR; + return 0; + } + + ASSERT(isMainThread()); + lazyInitialize(); + + // First check if this media element already has a source node. + if (mediaElement->audioSourceNode()) { + ec = INVALID_STATE_ERR; + 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; +} +#endif + +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<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<LowPass2FilterNode> AudioContext::createLowPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + if (document()) + document()->addConsoleMessage(JSMessageSource, LogMessageType, WarningMessageLevel, "createLowPass2Filter() is deprecated. Use createBiquadFilter() instead."); + + return LowPass2FilterNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<HighPass2FilterNode> AudioContext::createHighPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + if (document()) + document()->addConsoleMessage(JSMessageSource, LogMessageType, WarningMessageLevel, "createHighPass2Filter() is deprecated. Use createBiquadFilter() instead."); + + 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<DynamicsCompressorNode> AudioContext::createDynamicsCompressor() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return DynamicsCompressorNode::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(); + 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, AudioNode::RefType refType) +{ + ASSERT(isAudioThread()); + m_deferredFinishDerefList.append(AudioContext::RefInfo(node, refType)); +} + +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 AudioNodeInputs and AudioNodeOutputs. + handleDirtyAudioNodeInputs(); + handleDirtyAudioNodeOutputs(); + + 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 AudioNodeInputs and AudioNodeOutputs. + handleDirtyAudioNodeInputs(); + handleDirtyAudioNodeOutputs(); + + if (mustReleaseLock) + unlock(); + } +} + +void AudioContext::handleDeferredFinishDerefs() +{ + ASSERT(isAudioThread() && isGraphOwner()); + 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::scheduleNodeDeletion() +{ + bool isGood = m_isInitialized && isGraphOwner(); + ASSERT(isGood); + if (!isGood) + return; + + // Make sure to call deleteMarkedNodes() on main thread. + if (m_nodesToDelete.size() && !m_isDeletionScheduled) { + 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()); + + AutoLocker locker(this); + + // 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(); + + // Before deleting the node, clear out any AudioNodeInputs from m_dirtyAudioNodeInputs. + unsigned numberOfInputs = node->numberOfInputs(); + for (unsigned i = 0; i < numberOfInputs; ++i) + m_dirtyAudioNodeInputs.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::markAudioNodeInputDirty(AudioNodeInput* input) +{ + ASSERT(isGraphOwner()); + m_dirtyAudioNodeInputs.add(input); +} + +void AudioContext::markAudioNodeOutputDirty(AudioNodeOutput* output) +{ + ASSERT(isGraphOwner()); + m_dirtyAudioNodeOutputs.add(output); +} + +void AudioContext::handleDirtyAudioNodeInputs() +{ + ASSERT(isGraphOwner()); + + for (HashSet<AudioNodeInput*>::iterator i = m_dirtyAudioNodeInputs.begin(); i != m_dirtyAudioNodeInputs.end(); ++i) + (*i)->updateRenderingState(); + + m_dirtyAudioNodeInputs.clear(); +} + +void AudioContext::handleDirtyAudioNodeOutputs() +{ + ASSERT(isGraphOwner()); + + for (HashSet<AudioNodeOutput*>::iterator i = m_dirtyAudioNodeOutputs.begin(); i != m_dirtyAudioNodeOutputs.end(); ++i) + (*i)->updateRenderingState(); + + m_dirtyAudioNodeOutputs.clear(); +} + +const AtomicString& AudioContext::interfaceName() const +{ + return eventNames().interfaceForAudioContext; +} + +ScriptExecutionContext* AudioContext::scriptExecutionContext() const +{ + return document(); +} + +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 (hasDocument()) { + // Call the offline rendering completion event listener. + dispatchEvent(OfflineAudioCompletionEvent::create(renderedBuffer)); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioContext.h b/Source/WebCore/webaudio/AudioContext.h new file mode 100644 index 000000000..c4eec08c0 --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.h @@ -0,0 +1,321 @@ +/* + * 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. + */ + +#ifndef AudioContext_h +#define AudioContext_h + +#include "ActiveDOMObject.h" +#include "AsyncAudioDecoder.h" +#include "AudioBus.h" +#include "AudioDestinationNode.h" +#include "EventListener.h" +#include "EventTarget.h" +#include "HRTFDatabaseLoader.h" +#include <wtf/HashSet.h> +#include <wtf/MainThread.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/ThreadSafeRefCounted.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> +#include <wtf/text/AtomicStringHash.h> + +namespace WebCore { + +class AudioBuffer; +class AudioBufferCallback; +class AudioBufferSourceNode; +class MediaElementAudioSourceNode; +class HTMLMediaElement; +class AudioChannelMerger; +class AudioChannelSplitter; +class AudioGainNode; +class AudioPannerNode; +class AudioListener; +class BiquadFilterNode; +class DelayNode; +class Document; +class LowPass2FilterNode; +class HighPass2FilterNode; +class ConvolverNode; +class DynamicsCompressorNode; +class RealtimeAnalyserNode; +class WaveShaperNode; +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 ThreadSafeRefCounted<AudioContext>, public EventTarget { +public: + // Create an AudioContext for rendering to the audio hardware. + static PassRefPtr<AudioContext> create(Document*); + + // Create an AudioContext for offline (non-realtime) rendering. + static PassRefPtr<AudioContext> createOfflineContext(Document*, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionCode&); + + virtual ~AudioContext(); + + bool isInitialized() const; + + bool isOfflineContext() { return m_isOfflineContext; } + + // Returns true when initialize() was called AND all asynchronous initialization has completed. + bool isRunnable() const; + + // Document notification + virtual void stop(); + + Document* document() const; // ASSERTs if document no longer exists. + bool hasDocument(); + + AudioDestinationNode* destination() { return m_destinationNode.get(); } + double currentTime() { return m_destinationNode->currentTime(); } + float sampleRate() { return m_destinationNode->sampleRate(); } + + PassRefPtr<AudioBuffer> createBuffer(unsigned numberOfChannels, size_t numberOfFrames, float sampleRate, ExceptionCode&); + PassRefPtr<AudioBuffer> createBuffer(ArrayBuffer*, bool mixToMono, ExceptionCode&); + + // Asynchronous audio file data decoding. + void decodeAudioData(ArrayBuffer*, PassRefPtr<AudioBufferCallback>, PassRefPtr<AudioBufferCallback>, ExceptionCode& ec); + + // 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(); +#if ENABLE(VIDEO) + PassRefPtr<MediaElementAudioSourceNode> createMediaElementSource(HTMLMediaElement*, ExceptionCode&); +#endif + PassRefPtr<AudioGainNode> createGainNode(); + PassRefPtr<BiquadFilterNode> createBiquadFilter(); + PassRefPtr<WaveShaperNode> createWaveShaper(); + PassRefPtr<DelayNode> createDelayNode(); + PassRefPtr<LowPass2FilterNode> createLowPass2Filter(); + PassRefPtr<HighPass2FilterNode> createHighPass2Filter(); + PassRefPtr<AudioPannerNode> createPanner(); + PassRefPtr<ConvolverNode> createConvolver(); + PassRefPtr<DynamicsCompressorNode> createDynamicsCompressor(); + 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 start of each render quantum. + void handlePreRenderTasks(); + + // 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 schedule deletion of all marked nodes at the end of each realtime render quantum. + 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() const; + + // 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() const; + + 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(); + + // Only accessed when the graph lock is held. + void markAudioNodeInputDirty(AudioNodeInput*); + void markAudioNodeOutputDirty(AudioNodeOutput*); + + // EventTarget + virtual const AtomicString& interfaceName() const; + virtual ScriptExecutionContext* scriptExecutionContext() const; + virtual EventTargetData* eventTargetData() { return &m_eventTargetData; } + virtual EventTargetData* ensureEventTargetData() { return &m_eventTargetData; } + + DEFINE_ATTRIBUTE_EVENT_LISTENER(complete); + + // Reconcile ref/deref which are defined both in ThreadSafeRefCounted and EventTarget. + using ThreadSafeRefCounted<AudioContext>::ref; + using ThreadSafeRefCounted<AudioContext>::deref; + + void startRendering(); + void fireCompletionEvent(); + + static unsigned s_hardwareContextCount; + +private: + AudioContext(Document*); + AudioContext(Document*, unsigned numberOfChannels, size_t numberOfFrames, float sampleRate); + void constructCommon(); + + void lazyInitialize(); + void uninitialize(); + static void uninitializeDispatch(void* userData); + + void scheduleNodeDeletion(); + static void deleteMarkedNodesDispatch(void* userData); + + 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 here. + // They will be scheduled for deletion (on the main thread) at the end of a render cycle (in realtime thread). + Vector<AudioNode*> m_nodesToDelete; + bool m_isDeletionScheduled; + + // Only accessed when the graph lock is held. + HashSet<AudioNodeInput*> m_dirtyAudioNodeInputs; + HashSet<AudioNodeOutput*> m_dirtyAudioNodeOutputs; + void handleDirtyAudioNodeInputs(); + void handleDirtyAudioNodeOutputs(); + + 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; + + // HRTF Database loader + RefPtr<HRTFDatabaseLoader> m_hrtfDatabaseLoader; + + // EventTarget + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + EventTargetData m_eventTargetData; + + RefPtr<AudioBuffer> m_renderTarget; + + bool m_isOfflineContext; + + AsyncAudioDecoder m_audioDecoder; +}; + +} // WebCore + +#endif // AudioContext_h diff --git a/Source/WebCore/webaudio/AudioContext.idl b/Source/WebCore/webaudio/AudioContext.idl new file mode 100644 index 000000000..10f9c9062 --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.idl @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2010, Google Inc. All rights reserved. + * Copyright (C) 2011 Apple 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. + */ + +module webaudio { + interface [ + Conditional=WEB_AUDIO, + ActiveDOMObject, + CustomConstructor, + CustomMarkFunction, +#if defined(V8_BINDING) && V8_BINDING + EventTarget +#endif + ] 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) + raises(DOMException); + AudioBuffer createBuffer(in ArrayBuffer buffer, in boolean mixToMono) + raises(DOMException); + + // Asynchronous audio file data decoding. + void decodeAudioData(in ArrayBuffer audioData, in [Callback] AudioBufferCallback successCallback, in [Optional, Callback] AudioBufferCallback errorCallback) + raises(DOMException); + + // Sources + AudioBufferSourceNode createBufferSource(); +#if defined(ENABLE_VIDEO) && ENABLE_VIDEO + MediaElementAudioSourceNode createMediaElementSource(in HTMLMediaElement mediaElement) + raises(DOMException); +#endif + // Processing nodes + AudioGainNode createGainNode(); + DelayNode createDelayNode(); + BiquadFilterNode createBiquadFilter(); + WaveShaperNode createWaveShaper(); + LowPass2FilterNode createLowPass2Filter(); + HighPass2FilterNode createHighPass2Filter(); + AudioPannerNode createPanner(); + ConvolverNode createConvolver(); + DynamicsCompressorNode createDynamicsCompressor(); + RealtimeAnalyserNode createAnalyser(); + JavaScriptAudioNode createJavaScriptNode(in unsigned long bufferSize); + + // Channel splitting and merging + AudioChannelSplitter createChannelSplitter(); + AudioChannelMerger createChannelMerger(); + + // Offline rendering + // void prepareOfflineBufferRendering(in unsigned long numberOfChannels, in unsigned long numberOfFrames, in float sampleRate); + attribute EventListener oncomplete; + void startRendering(); + }; +} diff --git a/Source/WebCore/webaudio/AudioDestinationNode.cpp b/Source/WebCore/webaudio/AudioDestinationNode.cpp new file mode 100644 index 000000000..ff9ebbd32 --- /dev/null +++ b/Source/WebCore/webaudio/AudioDestinationNode.cpp @@ -0,0 +1,91 @@ +/* + * 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 "AudioDestinationNode.h" + +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "DenormalDisabler.h" + +namespace WebCore { + +AudioDestinationNode::AudioDestinationNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) + , m_currentTime(0.0) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + + setNodeType(NodeTypeDestination); +} + +AudioDestinationNode::~AudioDestinationNode() +{ + uninitialize(); +} + +// The audio hardware calls us back here to gets its input stream. +void AudioDestinationNode::provideInput(AudioBus* destinationBus, size_t numberOfFrames) +{ + // We don't want denormals slowing down any of the audio processing + // since they can very seriously hurt performance. + // This will take care of all AudioNodes because they all process within this scope. + DenormalDisabler denormalDisabler; + + context()->setAudioThread(currentThread()); + + if (!context()->isRunnable()) { + destinationBus->zero(); + return; + } + + // Let the context take care of any business at the start of each render quantum. + context()->handlePreRenderTasks(); + + // 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/Source/WebCore/webaudio/AudioDestinationNode.h b/Source/WebCore/webaudio/AudioDestinationNode.h new file mode 100644 index 000000000..d7bc7bc12 --- /dev/null +++ b/Source/WebCore/webaudio/AudioDestinationNode.h @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#ifndef AudioDestinationNode_h +#define AudioDestinationNode_h + +#include "AudioBuffer.h" +#include "AudioNode.h" +#include "AudioSourceProvider.h" + +namespace WebCore { + +class AudioBus; +class AudioContext; + +class AudioDestinationNode : public AudioNode, public AudioSourceProvider { +public: + AudioDestinationNode(AudioContext*, float sampleRate); + 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; }; + + // The audio hardware calls here periodically to gets its input stream. + virtual void provideInput(AudioBus*, size_t numberOfFrames); + + double currentTime() { return m_currentTime; } + + virtual float sampleRate() const = 0; + + virtual unsigned numberOfChannels() const { return 2; } // FIXME: update when multi-channel (more than stereo) is supported + + virtual void startRendering() = 0; + +protected: + double m_currentTime; +}; + +} // namespace WebCore + +#endif // AudioDestinationNode_h diff --git a/Source/WebCore/webaudio/AudioDestinationNode.idl b/Source/WebCore/webaudio/AudioDestinationNode.idl new file mode 100644 index 000000000..d7bf09f83 --- /dev/null +++ b/Source/WebCore/webaudio/AudioDestinationNode.idl @@ -0,0 +1,32 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioDestinationNode : AudioNode { + readonly attribute long numberOfChannels; + }; +} diff --git a/Source/WebCore/webaudio/AudioGain.h b/Source/WebCore/webaudio/AudioGain.h new file mode 100644 index 000000000..eb3c52d87 --- /dev/null +++ b/Source/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/Source/WebCore/webaudio/AudioGain.idl b/Source/WebCore/webaudio/AudioGain.idl new file mode 100644 index 000000000..ead7c9ab5 --- /dev/null +++ b/Source/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/Source/WebCore/webaudio/AudioGainNode.cpp b/Source/WebCore/webaudio/AudioGainNode.cpp new file mode 100644 index 000000000..2129c85d6 --- /dev/null +++ b/Source/WebCore/webaudio/AudioGainNode.cpp @@ -0,0 +1,125 @@ +/* + * 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 "AudioGainNode.h" + +#include "AudioBus.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" + +namespace WebCore { + +AudioGainNode::AudioGainNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) + , m_lastGain(1.0) + , m_sampleAccurateGainValues(AudioNode::ProcessingSizeInFrames) // FIXME: can probably share temp buffer in context +{ + m_gain = AudioGain::create("gain", 1.0, 0.0, 1.0); + m_gain->setContext(context); + + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 1))); + + setNodeType(NodeTypeGain); + + initialize(); +} + +void AudioGainNode::process(size_t framesToProcess) +{ + // FIXME: for some cases there is a nice optimization to avoid processing here, and let the gain change + // happen in the summing junction input of the AudioNode we're connected to. + // Then we can avoid all of the following: + + AudioBus* outputBus = output(0)->bus(); + ASSERT(outputBus); + + // The realtime thread can't block on this lock, so we call tryLock() instead. + if (m_processLock.tryLock()) { + if (!isInitialized() || !input(0)->isConnected()) + outputBus->zero(); + else { + AudioBus* inputBus = input(0)->bus(); + + if (gain()->hasTimelineValues()) { + // Apply sample-accurate gain scaling for precise envelopes, grain windows, etc. + ASSERT(framesToProcess <= m_sampleAccurateGainValues.size()); + if (framesToProcess <= m_sampleAccurateGainValues.size()) { + float* gainValues = m_sampleAccurateGainValues.data(); + gain()->calculateSampleAccurateValues(gainValues, framesToProcess); + outputBus->copyWithSampleAccurateGainValuesFrom(*inputBus, gainValues, framesToProcess); + } + } else { + // Apply the gain with de-zippering into the output bus. + outputBus->copyWithGainFrom(*inputBus, &m_lastGain, gain()->value()); + } + } + + m_processLock.unlock(); + } else { + // Too bad - the tryLock() failed. We must be in the middle of re-connecting and were already outputting silence anyway... + outputBus->zero(); + } +} + +void AudioGainNode::reset() +{ + // Snap directly to desired gain. + m_lastGain = gain()->value(); +} + +// FIXME: this can go away when we do mixing with gain directly in summing junction of AudioNodeInput +// +// As soon as we know the channel count of our input, we can lazily initialize. +// Sometimes this may be called more than once with different channel counts, in which case we must safely +// uninitialize and then re-initialize with the new channel count. +void AudioGainNode::checkNumberOfChannelsForInput(AudioNodeInput* input) +{ + ASSERT(input && input == this->input(0)); + if (input != this->input(0)) + return; + + unsigned numberOfChannels = input->numberOfChannels(); + + if (isInitialized() && numberOfChannels != output(0)->numberOfChannels()) { + // We're already initialized but the channel count has changed. + // We need to be careful since we may be actively processing right now, so synchronize with process(). + MutexLocker locker(m_processLock); + uninitialize(); + } + + if (!isInitialized()) { + // This will propagate the channel count to any nodes connected further downstream in the graph. + output(0)->setNumberOfChannels(numberOfChannels); + initialize(); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioGainNode.h b/Source/WebCore/webaudio/AudioGainNode.h new file mode 100644 index 000000000..e6342128c --- /dev/null +++ b/Source/WebCore/webaudio/AudioGainNode.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#ifndef AudioGainNode_h +#define AudioGainNode_h + +#include "AudioGain.h" +#include "AudioNode.h" +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioContext; + +// AudioGainNode is an AudioNode with one input and one output which applies a gain (volume) change to the audio signal. +// De-zippering (smoothing) is applied when the gain value is changed dynamically. + +class AudioGainNode : public AudioNode { +public: + static PassRefPtr<AudioGainNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new AudioGainNode(context, sampleRate)); + } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + + // Called in the main thread when the number of channels for the input may have changed. + virtual void checkNumberOfChannelsForInput(AudioNodeInput*); + + // JavaScript interface + AudioGain* gain() { return m_gain.get(); } + +private: + AudioGainNode(AudioContext*, float sampleRate); + + double m_lastGain; // for de-zippering + RefPtr<AudioGain> m_gain; + + AudioFloatArray m_sampleAccurateGainValues; + + // This synchronizes live channel count changes which require an uninitialization / re-initialization. + // FIXME: this can go away when we implement optimization for mixing with gain directly in summing junction of AudioNodeInput. + mutable Mutex m_processLock; +}; + +} // namespace WebCore + +#endif // AudioGainNode_h diff --git a/Source/WebCore/webaudio/AudioGainNode.idl b/Source/WebCore/webaudio/AudioGainNode.idl new file mode 100644 index 000000000..3d4f40faa --- /dev/null +++ b/Source/WebCore/webaudio/AudioGainNode.idl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioGainNode : AudioNode { + // FIXME: eventually it will be interesting to remove the readonly restriction, but need to properly deal with thread safety here. + readonly attribute AudioGain gain; + }; +} diff --git a/Source/WebCore/webaudio/AudioListener.cpp b/Source/WebCore/webaudio/AudioListener.cpp new file mode 100644 index 000000000..2f1ef764d --- /dev/null +++ b/Source/WebCore/webaudio/AudioListener.cpp @@ -0,0 +1,51 @@ +/* + * 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 "AudioListener.h" + +#include "AudioBus.h" + +namespace WebCore { + +AudioListener::AudioListener() + : m_position(0, 0, 0) + , m_orientation(0, 0, -1) + , m_upVector(0, 1, 0) + , m_velocity(0, 0, 0) + , m_dopplerFactor(1) + , m_speedOfSound(343.3) +{ +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioListener.h b/Source/WebCore/webaudio/AudioListener.h new file mode 100644 index 000000000..8b5d8ad1a --- /dev/null +++ b/Source/WebCore/webaudio/AudioListener.h @@ -0,0 +1,94 @@ +/* + * 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 AudioListener_h +#define AudioListener_h + +#include "FloatPoint3D.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +// AudioListener maintains the state of the listener in the audio scene as defined in the OpenAL specification. + +class AudioListener : public RefCounted<AudioListener> { +public: + static PassRefPtr<AudioListener> create() + { + return adoptRef(new AudioListener()); + } + + // Position + void setPosition(float x, float y, float z) { setPosition(FloatPoint3D(x, y, z)); } + void setPosition(const FloatPoint3D &position) { m_position = position; } + const FloatPoint3D& position() const { return m_position; } + + // Orientation + void setOrientation(float x, float y, float z, float upX, float upY, float upZ) + { + setOrientation(FloatPoint3D(x, y, z)); + setUpVector(FloatPoint3D(upX, upY, upZ)); + } + void setOrientation(const FloatPoint3D &orientation) { m_orientation = orientation; } + const FloatPoint3D& orientation() const { return m_orientation; } + + // Up-vector + void setUpVector(const FloatPoint3D &upVector) { m_upVector = upVector; } + const FloatPoint3D& upVector() const { return m_upVector; } + + // Velocity + void setVelocity(float x, float y, float z) { setVelocity(FloatPoint3D(x, y, z)); } + void setVelocity(const FloatPoint3D &velocity) { m_velocity = velocity; } + const FloatPoint3D& velocity() const { return m_velocity; } + + // Doppler factor + void setDopplerFactor(double dopplerFactor) { m_dopplerFactor = dopplerFactor; } + double dopplerFactor() const { return m_dopplerFactor; } + + // Speed of sound + void setSpeedOfSound(double speedOfSound) { m_speedOfSound = speedOfSound; } + double speedOfSound() const { return m_speedOfSound; } + +private: + AudioListener(); + + // Position / Orientation + FloatPoint3D m_position; + FloatPoint3D m_orientation; + FloatPoint3D m_upVector; + + FloatPoint3D m_velocity; + + double m_dopplerFactor; + double m_speedOfSound; +}; + +} // WebCore + +#endif // AudioListener_h diff --git a/Source/WebCore/webaudio/AudioListener.idl b/Source/WebCore/webaudio/AudioListener.idl new file mode 100644 index 000000000..cf6d8cfce --- /dev/null +++ b/Source/WebCore/webaudio/AudioListener.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 + ] AudioListener { + attribute float dopplerFactor; // same as OpenAL (default 1.0) + attribute float speedOfSound; // in meters / second (default 343.3) + + void setPosition(in float x, in float y, in float z); + void setOrientation(in float x, in float y, in float z, in float xUp, in float yUp, in float zUp); + void setVelocity(in float x, in float y, in float z); + }; +} diff --git a/Source/WebCore/webaudio/AudioNode.cpp b/Source/WebCore/webaudio/AudioNode.cpp new file mode 100644 index 000000000..e2a3b783f --- /dev/null +++ b/Source/WebCore/webaudio/AudioNode.cpp @@ -0,0 +1,342 @@ +/* + * 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 "AudioNode.h" + +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "ExceptionCode.h" +#include <wtf/Atomics.h> +#include <wtf/MainThread.h> + +namespace WebCore { + +AudioNode::AudioNode(AudioContext* context, float sampleRate) + : m_isInitialized(false) + , m_nodeType(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[nodeType()]; + printf("%p: %d: AudioNode::~AudioNode() %d %d %d\n", this, nodeType(), m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif +} + +void AudioNode::initialize() +{ + m_isInitialized = true; +} + +void AudioNode::uninitialize() +{ + m_isInitialized = false; +} + +void AudioNode::setNodeType(NodeType type) +{ + m_nodeType = 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) +{ + if (i < m_inputs.size()) + return m_inputs[i].get(); + return 0; +} + +AudioNodeOutput* AudioNode::output(unsigned i) +{ + if (i < m_outputs.size()) + return m_outputs[i].get(); + return 0; +} + +void AudioNode::connect(AudioNode* destination, unsigned outputIndex, unsigned inputIndex, ExceptionCode& ec) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + if (!destination) { + ec = SYNTAX_ERR; + return; + } + + // Sanity check input and output indices. + if (outputIndex >= numberOfOutputs()) { + ec = INDEX_SIZE_ERR; + return; + } + + if (destination && inputIndex >= destination->numberOfInputs()) { + ec = INDEX_SIZE_ERR; + return; + } + + if (context() != destination->context()) { + ec = SYNTAX_ERR; + return; + } + + AudioNodeInput* input = destination->input(inputIndex); + AudioNodeOutput* output = this->output(outputIndex); + input->connect(output); + + // Let context know that a connection has been made. + context()->incrementConnectionCount(); +} + +void AudioNode::disconnect(unsigned outputIndex, ExceptionCode& ec) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + // Sanity check input and output indices. + if (outputIndex >= numberOfOutputs()) { + ec = INDEX_SIZE_ERR; + return; + } + + AudioNodeOutput* output = this->output(outputIndex); + output->disconnectAllInputs(); +} + +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, nodeType(), refType, m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif + + // See the disabling code in finishDeref() below. This handles the case where a node + // is being re-connected after being used at least once and disconnected. + // In this case, we need to re-enable. + if (m_isDisabled && m_connectionRefCount > 0 && refType == RefTypeConnection) { + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + m_isDisabled = false; + for (unsigned i = 0; i < m_outputs.size(); ++i) + output(i)->enable(); + } +} + +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, nodeType(), 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 (nodeType() != NodeTypeConvolver && nodeType() != 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/Source/WebCore/webaudio/AudioNode.h b/Source/WebCore/webaudio/AudioNode.h new file mode 100644 index 000000000..d1325e1db --- /dev/null +++ b/Source/WebCore/webaudio/AudioNode.h @@ -0,0 +1,176 @@ +/* + * 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. + */ + +#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; + +typedef int ExceptionCode; + +// 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*, float sampleRate); + virtual ~AudioNode(); + + AudioContext* context() { return m_context.get(); } + + enum NodeType { + NodeTypeUnknown, + NodeTypeDestination, + NodeTypeAudioBufferSource, + NodeTypeMediaElementAudioSource, + NodeTypeJavaScript, + NodeTypeBiquadFilter, + NodeTypeLowPass2Filter, + NodeTypeHighPass2Filter, + NodeTypePanner, + NodeTypeConvolver, + NodeTypeDelay, + NodeTypeGain, + NodeTypeChannelSplitter, + NodeTypeChannelMerger, + NodeTypeAnalyser, + NodeTypeDynamicsCompressor, + NodeTypeWaveShaper, + NodeTypeEnd + }; + + NodeType nodeType() const { return m_nodeType; } + void setNodeType(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(); + virtual void uninitialize(); + + 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); + + // Called from main thread by corresponding JavaScript methods. + void connect(AudioNode*, unsigned outputIndex, unsigned inputIndex, ExceptionCode&); + void disconnect(unsigned outputIndex, ExceptionCode&); + + float 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 + + bool isMarkedForDeletion() const { return m_isMarkedForDeletion; } + +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); + +private: + volatile bool m_isInitialized; + NodeType m_nodeType; + RefPtr<AudioContext> m_context; + float 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/Source/WebCore/webaudio/AudioNode.idl b/Source/WebCore/webaudio/AudioNode.idl new file mode 100644 index 000000000..06f873e3a --- /dev/null +++ b/Source/WebCore/webaudio/AudioNode.idl @@ -0,0 +1,39 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO + ] AudioNode { + readonly attribute AudioContext context; + readonly attribute unsigned long numberOfInputs; + readonly attribute unsigned long numberOfOutputs; + + void connect(in AudioNode destination, in [Optional=CallWithDefaultValue] unsigned long output, in [Optional=CallWithDefaultValue] unsigned long input) + raises(DOMException); + + void disconnect(in [Optional=CallWithDefaultValue] unsigned long output) + raises(DOMException); + }; +} diff --git a/Source/WebCore/webaudio/AudioNodeInput.cpp b/Source/WebCore/webaudio/AudioNodeInput.cpp new file mode 100644 index 000000000..9fd1852e0 --- /dev/null +++ b/Source/WebCore/webaudio/AudioNodeInput.cpp @@ -0,0 +1,270 @@ +/* + * 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 "AudioNodeInput.h" + +#include "AudioContext.h" +#include "AudioNode.h" +#include "AudioNodeOutput.h" +#include <algorithm> + +using namespace std; + +namespace WebCore { + +AudioNodeInput::AudioNodeInput(AudioNode* node) + : m_node(node) + , m_renderingStateNeedUpdating(false) +{ + m_monoSummingBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); + m_stereoSummingBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); +} + +void AudioNodeInput::connect(AudioNodeOutput* output) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(output && node()); + if (!output || !node()) + return; + + // Check if we're already connected to this output. + if (m_outputs.contains(output)) + return; + + output->addInput(this); + m_outputs.add(output); + changedOutputs(); + + // Sombody has just connected to us, so count it as a reference. + node()->ref(AudioNode::RefTypeConnection); +} + +void AudioNodeInput::disconnect(AudioNodeOutput* output) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(output && node()); + if (!output || !node()) + return; + + // First try to disconnect from "active" connections. + if (m_outputs.contains(output)) { + m_outputs.remove(output); + changedOutputs(); + output->removeInput(this); + node()->deref(AudioNode::RefTypeConnection); // Note: it's important to return immediately after all deref() calls since the node may be deleted. + return; + } + + // Otherwise, try to disconnect from disabled connections. + if (m_disabledOutputs.contains(output)) { + m_disabledOutputs.remove(output); + output->removeInput(this); + node()->deref(AudioNode::RefTypeDisabled); // Note: it's important to return immediately after all deref() calls since the node may be deleted. + return; + } + + ASSERT_NOT_REACHED(); +} + +void AudioNodeInput::disable(AudioNodeOutput* output) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(output && node()); + if (!output || !node()) + return; + + ASSERT(m_outputs.contains(output)); + + m_disabledOutputs.add(output); + m_outputs.remove(output); + changedOutputs(); + + node()->ref(AudioNode::RefTypeDisabled); + node()->deref(AudioNode::RefTypeConnection); // Note: it's important to return immediately after all deref() calls since the node may be deleted. +} + +void AudioNodeInput::enable(AudioNodeOutput* output) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(output && node()); + if (!output || !node()) + return; + + ASSERT(m_disabledOutputs.contains(output)); + + // Move output from disabled list to active list. + m_outputs.add(output); + m_disabledOutputs.remove(output); + changedOutputs(); + + node()->ref(AudioNode::RefTypeConnection); + node()->deref(AudioNode::RefTypeDisabled); // Note: it's important to return immediately after all deref() calls since the node may be deleted. +} + +void AudioNodeInput::changedOutputs() +{ + ASSERT(context()->isGraphOwner()); + if (!m_renderingStateNeedUpdating && !node()->isMarkedForDeletion()) { + context()->markAudioNodeInputDirty(this); + m_renderingStateNeedUpdating = true; + } +} + +void AudioNodeInput::updateRenderingState() +{ + ASSERT(context()->isAudioThread() && context()->isGraphOwner()); + + if (m_renderingStateNeedUpdating && !node()->isMarkedForDeletion()) { + // Copy from m_outputs to m_renderingOutputs. + m_renderingOutputs.resize(m_outputs.size()); + unsigned j = 0; + for (HashSet<AudioNodeOutput*>::iterator i = m_outputs.begin(); i != m_outputs.end(); ++i, ++j) { + AudioNodeOutput* output = *i; + m_renderingOutputs[j] = output; + output->updateRenderingState(); + } + + node()->checkNumberOfChannelsForInput(this); + + m_renderingStateNeedUpdating = false; + } +} + +unsigned AudioNodeInput::numberOfChannels() const +{ + // Find the number of channels of the connection with the largest number of channels. + unsigned maxChannels = 1; // one channel is the minimum allowed + + for (HashSet<AudioNodeOutput*>::iterator i = m_outputs.begin(); i != m_outputs.end(); ++i) { + AudioNodeOutput* output = *i; + maxChannels = max(maxChannels, output->bus()->numberOfChannels()); + } + + return maxChannels; +} + +unsigned AudioNodeInput::numberOfRenderingChannels() +{ + ASSERT(context()->isAudioThread()); + + // Find the number of channels of the rendering connection with the largest number of channels. + unsigned maxChannels = 1; // one channel is the minimum allowed + + for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) + maxChannels = max(maxChannels, renderingOutput(i)->bus()->numberOfChannels()); + + return maxChannels; +} + +AudioBus* AudioNodeInput::bus() +{ + ASSERT(context()->isAudioThread()); + + // Handle single connection specially to allow for in-place processing. + if (numberOfRenderingConnections() == 1) + return renderingOutput(0)->bus(); + + // Multiple connections case (or no connections). + return internalSummingBus(); +} + +AudioBus* AudioNodeInput::internalSummingBus() +{ + ASSERT(context()->isAudioThread()); + + // We must pick a summing bus which is the right size to handle the largest connection. + switch (numberOfRenderingChannels()) { + case 1: + return m_monoSummingBus.get(); + case 2: + return m_stereoSummingBus.get(); + // FIXME: could implement more than just mono and stereo mixing in the future + } + + ASSERT_NOT_REACHED(); + return 0; +} + +void AudioNodeInput::sumAllConnections(AudioBus* summingBus, size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + // We shouldn't be calling this method if there's only one connection, since it's less efficient. + ASSERT(numberOfRenderingConnections() > 1); + + ASSERT(summingBus); + if (!summingBus) + return; + + summingBus->zero(); + + for (unsigned i = 0; i < numberOfRenderingConnections(); ++i) { + AudioNodeOutput* output = renderingOutput(i); + ASSERT(output); + + // Render audio from this output. + AudioBus* connectionBus = output->pull(0, framesToProcess); + + // Sum, with unity-gain. + summingBus->sumFrom(*connectionBus); + } +} + +AudioBus* AudioNodeInput::pull(AudioBus* inPlaceBus, size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + // Handle single connection case. + if (numberOfRenderingConnections() == 1) { + // The output will optimize processing using inPlaceBus if it's able. + AudioNodeOutput* output = this->renderingOutput(0); + return output->pull(inPlaceBus, framesToProcess); + } + + AudioBus* internalSummingBus = this->internalSummingBus(); + + if (!numberOfRenderingConnections()) { + // At least, generate silence if we're not connected to anything. + // FIXME: if we wanted to get fancy, we could propagate a 'silent hint' here to optimize the downstream graph processing. + internalSummingBus->zero(); + return internalSummingBus; + } + + // Handle multiple connections case. + sumAllConnections(internalSummingBus, framesToProcess); + + return internalSummingBus; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioNodeInput.h b/Source/WebCore/webaudio/AudioNodeInput.h new file mode 100644 index 000000000..1d90986f9 --- /dev/null +++ b/Source/WebCore/webaudio/AudioNodeInput.h @@ -0,0 +1,125 @@ +/* + * 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. + */ + +#ifndef AudioNodeInput_h +#define AudioNodeInput_h + +#include "AudioBus.h" +#include "AudioNode.h" +#include <wtf/HashSet.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioNode; +class AudioNodeOutput; + +// An AudioNodeInput represents an input to an AudioNode and can be connected from one or more AudioNodeOutputs. +// In the case of multiple connections, the input will act as a unity-gain summing junction, mixing all the outputs. +// The number of channels of the input's bus is the maximum of the number of channels of all its connections. + +class AudioNodeInput { +public: + AudioNodeInput(AudioNode*); + + // Can be called from any thread. + AudioNode* node() const { return m_node; } + AudioContext* context() { return m_node->context(); } + + // Must be called with the context's graph lock. + void connect(AudioNodeOutput*); + void disconnect(AudioNodeOutput*); + + // disable() will take the output out of the active connections list and set aside in a disabled list. + // enable() will put the output back into the active connections list. + // Must be called with the context's graph lock. + void enable(AudioNodeOutput*); + void disable(AudioNodeOutput*); + + // pull() processes all of the AudioNodes connected to us. + // In the case of multiple connections it sums the result into an internal summing bus. + // In the single connection case, it allows in-place processing where possible using inPlaceBus. + // It returns the bus which it rendered into, returning inPlaceBus if in-place processing was performed. + // Called from context's audio thread. + AudioBus* pull(AudioBus* inPlaceBus, size_t framesToProcess); + + // bus() contains the rendered audio after pull() has been called for each time quantum. + // Called from context's audio thread. + AudioBus* bus(); + + // This copies m_outputs to m_renderingOutputs. Please see comments for these lists below. + // This must be called when we own the context's graph lock in the audio thread at the very start or end of the render quantum. + void updateRenderingState(); + + // Rendering code accesses its version of the current connections here. + unsigned numberOfRenderingConnections() const { return m_renderingOutputs.size(); } + AudioNodeOutput* renderingOutput(unsigned i) { return m_renderingOutputs[i]; } + const AudioNodeOutput* renderingOutput(unsigned i) const { return m_renderingOutputs[i]; } + bool isConnected() const { return numberOfRenderingConnections() > 0; } + + // The number of channels of the connection with the largest number of channels. + unsigned numberOfChannels() const; + +private: + AudioNode* m_node; + + // m_outputs contains the AudioNodeOutputs representing current connections which are not disabled. + // The rendering code should never use this directly, but instead uses m_renderingOutputs. + HashSet<AudioNodeOutput*> m_outputs; + + // numberOfConnections() should never be called from the audio rendering thread. + // Instead numberOfRenderingConnections() and renderingOutput() should be used. + unsigned numberOfConnections() const { return m_outputs.size(); } + + // This must be called whenever we modify m_outputs. + void changedOutputs(); + + // m_renderingOutputs is a copy of m_outputs which will never be modified during the graph rendering on the audio thread. + // This is the list which is used by the rendering code. + // Whenever m_outputs is modified, the context is told so it can later update m_renderingOutputs from m_outputs at a safe time. + // Most of the time, m_renderingOutputs is identical to m_outputs. + Vector<AudioNodeOutput*> m_renderingOutputs; + + // m_renderingStateNeedUpdating keeps track if m_outputs is modified. + bool m_renderingStateNeedUpdating; + + // The number of channels of the rendering connection with the largest number of channels. + unsigned numberOfRenderingChannels(); + + // m_disabledOutputs contains the AudioNodeOutputs which are disabled (will not be processed) by the audio graph rendering. + // But, from JavaScript's perspective, these outputs are still connected to us. + // Generally, these represent disabled connections from "notes" which have finished playing but are not yet garbage collected. + HashSet<AudioNodeOutput*> m_disabledOutputs; + + // Called from context's audio thread. + AudioBus* internalSummingBus(); + void sumAllConnections(AudioBus* summingBus, size_t framesToProcess); + + OwnPtr<AudioBus> m_monoSummingBus; + OwnPtr<AudioBus> m_stereoSummingBus; +}; + +} // namespace WebCore + +#endif // AudioNodeInput_h diff --git a/Source/WebCore/webaudio/AudioNodeOutput.cpp b/Source/WebCore/webaudio/AudioNodeOutput.cpp new file mode 100644 index 000000000..4c777e6bd --- /dev/null +++ b/Source/WebCore/webaudio/AudioNodeOutput.cpp @@ -0,0 +1,216 @@ +/* + * 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 "AudioNodeOutput.h" + +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include <wtf/Threading.h> + +namespace WebCore { + +AudioNodeOutput::AudioNodeOutput(AudioNode* node, unsigned numberOfChannels) + : m_node(node) + , m_numberOfChannels(numberOfChannels) + , m_desiredNumberOfChannels(numberOfChannels) + , m_internalOutputBus(0) + , m_actualDestinationBus(0) + , m_isEnabled(true) + , m_renderingFanOutCount(0) +{ + m_monoInternalBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); + m_stereoInternalBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); + setInternalBus(); +} + +void AudioNodeOutput::setNumberOfChannels(unsigned numberOfChannels) +{ + ASSERT(context()->isGraphOwner()); + + m_desiredNumberOfChannels = numberOfChannels; + + if (context()->isAudioThread()) { + // If we're in the audio thread then we can take care of it right away (we should be at the very start or end of a rendering quantum). + updateNumberOfChannels(); + } else { + // Let the context take care of it in the audio thread in the pre and post render tasks. + context()->markAudioNodeOutputDirty(this); + } +} + +void AudioNodeOutput::setInternalBus() +{ + switch (m_numberOfChannels) { + case 0: + case 1: + m_internalOutputBus = m_monoInternalBus.get(); + break; + case 2: + m_internalOutputBus = m_stereoInternalBus.get(); + break; + default: + // FIXME: later we can fully implement more than stereo, 5.1, etc. + ASSERT_NOT_REACHED(); + } + + // This may later be changed in pull() to point to an in-place bus with the same number of channels. + m_actualDestinationBus = m_internalOutputBus; +} + +void AudioNodeOutput::updateRenderingState() +{ + updateNumberOfChannels(); + m_renderingFanOutCount = fanOutCount(); +} + +void AudioNodeOutput::updateNumberOfChannels() +{ + ASSERT(context()->isAudioThread() && context()->isGraphOwner()); + + if (m_numberOfChannels != m_desiredNumberOfChannels) { + m_numberOfChannels = m_desiredNumberOfChannels; + setInternalBus(); + propagateChannelCount(); + } +} + +void AudioNodeOutput::propagateChannelCount() +{ + ASSERT(context()->isAudioThread() && context()->isGraphOwner()); + + if (isChannelCountKnown()) { + // Announce to any nodes we're connected to that we changed our channel count for its input. + for (InputsIterator i = m_inputs.begin(); i != m_inputs.end(); ++i) { + AudioNodeInput* input = *i; + AudioNode* connectionNode = input->node(); + connectionNode->checkNumberOfChannelsForInput(input); + } + } +} + +AudioBus* AudioNodeOutput::pull(AudioBus* inPlaceBus, size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + ASSERT(m_renderingFanOutCount > 0); + + // Causes our AudioNode to process if it hasn't already for this render quantum. + // We try to do in-place processing (using inPlaceBus) if at all possible, + // but we can't process in-place if we're connected to more than one input (fan-out > 1). + // In this case pull() is called multiple times per rendering quantum, and the processIfNecessary() call below will + // cause our node to process() only the first time, caching the output in m_internalOutputBus for subsequent calls. + + bool isInPlace = inPlaceBus && inPlaceBus->numberOfChannels() == numberOfChannels() && m_renderingFanOutCount == 1; + + // Setup the actual destination bus for processing when our node's process() method gets called in processIfNecessary() below. + m_actualDestinationBus = isInPlace ? inPlaceBus : m_internalOutputBus; + + node()->processIfNecessary(framesToProcess); + return m_actualDestinationBus; +} + +AudioBus* AudioNodeOutput::bus() const +{ + ASSERT(const_cast<AudioNodeOutput*>(this)->context()->isAudioThread()); + ASSERT(m_actualDestinationBus); + return m_actualDestinationBus; +} + +unsigned AudioNodeOutput::renderingFanOutCount() const +{ + return m_renderingFanOutCount; +} + +unsigned AudioNodeOutput::fanOutCount() +{ + ASSERT(context()->isGraphOwner()); + return m_inputs.size(); +} + +void AudioNodeOutput::addInput(AudioNodeInput* input) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(input); + if (!input) + return; + + m_inputs.add(input); +} + +void AudioNodeOutput::removeInput(AudioNodeInput* input) +{ + ASSERT(context()->isGraphOwner()); + + ASSERT(input); + if (!input) + return; + + m_inputs.remove(input); +} + +void AudioNodeOutput::disconnectAllInputs() +{ + ASSERT(context()->isGraphOwner()); + + // AudioNodeInput::disconnect() changes m_inputs by calling removeInput(). + while (!m_inputs.isEmpty()) { + AudioNodeInput* input = *m_inputs.begin(); + input->disconnect(this); + } +} + +void AudioNodeOutput::disable() +{ + ASSERT(context()->isGraphOwner()); + + if (m_isEnabled) { + for (InputsIterator i = m_inputs.begin(); i != m_inputs.end(); ++i) { + AudioNodeInput* input = *i; + input->disable(this); + } + m_isEnabled = false; + } +} + +void AudioNodeOutput::enable() +{ + ASSERT(context()->isGraphOwner()); + + if (!m_isEnabled) { + for (InputsIterator i = m_inputs.begin(); i != m_inputs.end(); ++i) { + AudioNodeInput* input = *i; + input->enable(this); + } + m_isEnabled = true; + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioNodeOutput.h b/Source/WebCore/webaudio/AudioNodeOutput.h new file mode 100644 index 000000000..7114b389d --- /dev/null +++ b/Source/WebCore/webaudio/AudioNodeOutput.h @@ -0,0 +1,134 @@ +/* + * 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. + */ + +#ifndef AudioNodeOutput_h +#define AudioNodeOutput_h + +#include "AudioBus.h" +#include "AudioNode.h" +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioContext; +class AudioNodeInput; + +// AudioNodeOutput represents a single output for an AudioNode. +// It may be connected to one or more AudioNodeInputs. + +class AudioNodeOutput { +public: + // It's OK to pass 0 for numberOfChannels in which case setNumberOfChannels() must be called later on. + AudioNodeOutput(AudioNode*, unsigned numberOfChannels); + + // Can be called from any thread. + AudioNode* node() const { return m_node; } + AudioContext* context() { return m_node->context(); } + + // Causes our AudioNode to process if it hasn't already for this render quantum. + // It returns the bus containing the processed audio for this output, returning inPlaceBus if in-place processing was possible. + // Called from context's audio thread. + AudioBus* pull(AudioBus* inPlaceBus, size_t framesToProcess); + + // bus() will contain the rendered audio after pull() is called for each rendering time quantum. + // Called from context's audio thread. + AudioBus* bus() const; + + // fanOutCount() is the number of AudioNodeInputs that we're connected to. + // This function should not be called in audio thread rendering code, instead renderingFanOutCount() should be used. + // It must be called with the context's graph lock. + unsigned fanOutCount(); + + // renderingFanOutCount() is the number of AudioNodeInputs that we're connected to during rendering. + // Unlike fanOutCount() it will not change during the course of a render quantum. + unsigned renderingFanOutCount() const; + + // It must be called with the context's graph lock. + void disconnectAllInputs(); + + void setNumberOfChannels(unsigned); + unsigned numberOfChannels() const { return m_numberOfChannels; } + bool isChannelCountKnown() const { return numberOfChannels() > 0; } + + // Disable/Enable happens when there are still JavaScript references to a node, but it has otherwise "finished" its work. + // For example, when a note has finished playing. It is kept around, because it may be played again at a later time. + // They must be called with the context's graph lock. + void disable(); + void enable(); + + // updateRenderingState() is called in the audio thread at the start or end of the render quantum to handle any recent changes to the graph state. + // It must be called with the context's graph lock. + void updateRenderingState(); + +private: + AudioNode* m_node; + + friend class AudioNodeInput; + + // These are called from AudioNodeInput. + // They must be called with the context's graph lock. + void addInput(AudioNodeInput*); + void removeInput(AudioNodeInput*); + + // setInternalBus() sets m_internalOutputBus appropriately for the number of channels. + // It is called in the constructor or in the audio thread with the context's graph lock. + void setInternalBus(); + + // Announce to any nodes we're connected to that we changed our channel count for its input. + // It must be called in the audio thread with the context's graph lock. + void propagateChannelCount(); + + // updateNumberOfChannels() is called in the audio thread at the start or end of the render quantum to pick up channel changes. + // It must be called with the context's graph lock. + void updateNumberOfChannels(); + + // m_numberOfChannels will only be changed in the audio thread. + // The main thread sets m_desiredNumberOfChannels which will later get picked up in the audio thread in updateNumberOfChannels(). + unsigned m_numberOfChannels; + unsigned m_desiredNumberOfChannels; + + // m_internalOutputBus will point to either m_monoInternalBus or m_stereoInternalBus. + // It must only be changed in the audio thread (or constructor). + AudioBus* m_internalOutputBus; + OwnPtr<AudioBus> m_monoInternalBus; + OwnPtr<AudioBus> m_stereoInternalBus; + + // m_actualDestinationBus is set in pull() and will either point to one of our internal busses or to the in-place bus. + // It must only be changed in the audio thread (or constructor). + AudioBus* m_actualDestinationBus; + + HashSet<AudioNodeInput*> m_inputs; + typedef HashSet<AudioNodeInput*>::iterator InputsIterator; + bool m_isEnabled; + + // For the purposes of rendering, keeps track of the number of inputs we're connected to. + // This value should only be changed at the very start or end of the rendering quantum. + unsigned m_renderingFanOutCount; +}; + +} // namespace WebCore + +#endif // AudioNodeOutput_h diff --git a/Source/WebCore/webaudio/AudioPannerNode.cpp b/Source/WebCore/webaudio/AudioPannerNode.cpp new file mode 100644 index 000000000..faf7f159e --- /dev/null +++ b/Source/WebCore/webaudio/AudioPannerNode.cpp @@ -0,0 +1,317 @@ +/* + * 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 "AudioPannerNode.h" + +#include "AudioBufferSourceNode.h" +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "HRTFPanner.h" +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +static void fixNANs(double &x) +{ + if (isnan(x) || isinf(x)) + x = 0.0; +} + +AudioPannerNode::AudioPannerNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) + , m_panningModel(Panner::PanningModelHRTF) + , m_lastGain(-1.0) + , m_connectionCount(0) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + m_distanceGain = AudioGain::create("distanceGain", 1.0, 0.0, 1.0); + m_coneGain = AudioGain::create("coneGain", 1.0, 0.0, 1.0); + + m_position = FloatPoint3D(0, 0, 0); + m_orientation = FloatPoint3D(1, 0, 0); + m_velocity = FloatPoint3D(0, 0, 0); + + setNodeType(NodeTypePanner); + + initialize(); +} + +AudioPannerNode::~AudioPannerNode() +{ + uninitialize(); +} + +void AudioPannerNode::pullInputs(size_t framesToProcess) +{ + // We override pullInputs(), so we can detect new AudioSourceNodes which have connected to us when new connections are made. + // These AudioSourceNodes need to be made aware of our existence in order to handle doppler shift pitch changes. + if (m_connectionCount != context()->connectionCount()) { + m_connectionCount = context()->connectionCount(); + + // Recursively go through all nodes connected to us. + notifyAudioSourcesConnectedToNode(this); + } + + AudioNode::pullInputs(framesToProcess); +} + +void AudioPannerNode::process(size_t framesToProcess) +{ + AudioBus* destination = output(0)->bus(); + + if (!isInitialized() || !input(0)->isConnected() || !m_panner.get()) { + destination->zero(); + return; + } + + AudioBus* source = input(0)->bus(); + + if (!source) { + destination->zero(); + return; + } + + // Apply the panning effect. + double azimuth; + double elevation; + getAzimuthElevation(&azimuth, &elevation); + m_panner->pan(azimuth, elevation, source, destination, framesToProcess); + + // Get the distance and cone gain. + double totalGain = distanceConeGain(); + + // Snap to desired gain at the beginning. + if (m_lastGain == -1.0) + m_lastGain = totalGain; + + // Apply gain in-place with de-zippering. + destination->copyWithGainFrom(*destination, &m_lastGain, totalGain); +} + +void AudioPannerNode::reset() +{ + m_lastGain = -1.0; // force to snap to initial gain + if (m_panner.get()) + m_panner->reset(); +} + +void AudioPannerNode::initialize() +{ + if (isInitialized()) + return; + + m_panner = Panner::create(m_panningModel, sampleRate()); + + AudioNode::initialize(); +} + +void AudioPannerNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_panner.clear(); + AudioNode::uninitialize(); +} + +AudioListener* AudioPannerNode::listener() +{ + return context()->listener(); +} + +void AudioPannerNode::setPanningModel(unsigned short model) +{ + if (!m_panner.get() || model != m_panningModel) { + OwnPtr<Panner> newPanner = Panner::create(model, sampleRate()); + m_panner = newPanner.release(); + } +} + +void AudioPannerNode::getAzimuthElevation(double* outAzimuth, double* outElevation) +{ + // FIXME: we should cache azimuth and elevation (if possible), so we only re-calculate if a change has been made. + + double azimuth = 0.0; + + // Calculate the source-listener vector + FloatPoint3D listenerPosition = listener()->position(); + FloatPoint3D sourceListener = m_position - listenerPosition; + + if (sourceListener.isZero()) { + // degenerate case if source and listener are at the same point + *outAzimuth = 0.0; + *outElevation = 0.0; + return; + } + + sourceListener.normalize(); + + // Align axes + FloatPoint3D listenerFront = listener()->orientation(); + FloatPoint3D listenerUp = listener()->upVector(); + FloatPoint3D listenerRight = listenerFront.cross(listenerUp); + listenerRight.normalize(); + + FloatPoint3D listenerFrontNorm = listenerFront; + listenerFrontNorm.normalize(); + + FloatPoint3D up = listenerRight.cross(listenerFrontNorm); + + float upProjection = sourceListener.dot(up); + + FloatPoint3D projectedSource = sourceListener - upProjection * up; + projectedSource.normalize(); + + azimuth = 180.0 * acos(projectedSource.dot(listenerRight)) / piDouble; + fixNANs(azimuth); // avoid illegal values + + // Source in front or behind the listener + double frontBack = projectedSource.dot(listenerFrontNorm); + if (frontBack < 0.0) + azimuth = 360.0 - azimuth; + + // Make azimuth relative to "front" and not "right" listener vector + if ((azimuth >= 0.0) && (azimuth <= 270.0)) + azimuth = 90.0 - azimuth; + else + azimuth = 450.0 - azimuth; + + // Elevation + double elevation = 90.0 - 180.0 * acos(sourceListener.dot(up)) / piDouble; + fixNANs(azimuth); // avoid illegal values + + if (elevation > 90.0) + elevation = 180.0 - elevation; + else if (elevation < -90.0) + elevation = -180.0 - elevation; + + if (outAzimuth) + *outAzimuth = azimuth; + if (outElevation) + *outElevation = elevation; +} + +float AudioPannerNode::dopplerRate() +{ + double dopplerShift = 1.0; + + // FIXME: optimize for case when neither source nor listener has changed... + double dopplerFactor = listener()->dopplerFactor(); + + if (dopplerFactor > 0.0) { + double speedOfSound = listener()->speedOfSound(); + + const FloatPoint3D &sourceVelocity = m_velocity; + const FloatPoint3D &listenerVelocity = listener()->velocity(); + + // Don't bother if both source and listener have no velocity + bool sourceHasVelocity = !sourceVelocity.isZero(); + bool listenerHasVelocity = !listenerVelocity.isZero(); + + if (sourceHasVelocity || listenerHasVelocity) { + // Calculate the source to listener vector + FloatPoint3D listenerPosition = listener()->position(); + FloatPoint3D sourceToListener = m_position - listenerPosition; + + double sourceListenerMagnitude = sourceToListener.length(); + + double listenerProjection = sourceToListener.dot(listenerVelocity) / sourceListenerMagnitude; + double sourceProjection = sourceToListener.dot(sourceVelocity) / sourceListenerMagnitude; + + listenerProjection = -listenerProjection; + sourceProjection = -sourceProjection; + + double scaledSpeedOfSound = speedOfSound / dopplerFactor; + listenerProjection = min(listenerProjection, scaledSpeedOfSound); + sourceProjection = min(sourceProjection, scaledSpeedOfSound); + + dopplerShift = ((speedOfSound - dopplerFactor * listenerProjection) / (speedOfSound - dopplerFactor * sourceProjection)); + fixNANs(dopplerShift); // avoid illegal values + + // Limit the pitch shifting to 4 octaves up and 3 octaves down. + if (dopplerShift > 16.0) + dopplerShift = 16.0; + else if (dopplerShift < 0.125) + dopplerShift = 0.125; + } + } + + return static_cast<float>(dopplerShift); +} + +float AudioPannerNode::distanceConeGain() +{ + FloatPoint3D listenerPosition = listener()->position(); + + double listenerDistance = m_position.distanceTo(listenerPosition); + double distanceGain = m_distanceEffect.gain(listenerDistance); + + m_distanceGain->setValue(static_cast<float>(distanceGain)); + + // FIXME: could optimize by caching coneGain + double coneGain = m_coneEffect.gain(m_position, m_orientation, listenerPosition); + + m_coneGain->setValue(static_cast<float>(coneGain)); + + return float(distanceGain * coneGain); +} + +void AudioPannerNode::notifyAudioSourcesConnectedToNode(AudioNode* node) +{ + ASSERT(node); + if (!node) + return; + + // First check if this node is an AudioBufferSourceNode. If so, let it know about us so that doppler shift pitch can be taken into account. + if (node->nodeType() == NodeTypeAudioBufferSource) { + AudioBufferSourceNode* bufferSourceNode = reinterpret_cast<AudioBufferSourceNode*>(node); + bufferSourceNode->setPannerNode(this); + } else { + // Go through all inputs to this node. + for (unsigned i = 0; i < node->numberOfInputs(); ++i) { + AudioNodeInput* input = node->input(i); + + // For each input, go through all of its connections, looking for AudioBufferSourceNodes. + for (unsigned j = 0; j < input->numberOfRenderingConnections(); ++j) { + AudioNodeOutput* connectedOutput = input->renderingOutput(j); + AudioNode* connectedNode = connectedOutput->node(); + notifyAudioSourcesConnectedToNode(connectedNode); // recurse + } + } + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioPannerNode.h b/Source/WebCore/webaudio/AudioPannerNode.h new file mode 100644 index 000000000..6ecebeaae --- /dev/null +++ b/Source/WebCore/webaudio/AudioPannerNode.h @@ -0,0 +1,148 @@ +/* + * 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. + */ + +#ifndef AudioPannerNode_h +#define AudioPannerNode_h + +#include "AudioBus.h" +#include "AudioGain.h" +#include "AudioListener.h" +#include "AudioNode.h" +#include "Cone.h" +#include "Distance.h" +#include "FloatPoint3D.h" +#include "Panner.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +// AudioPannerNode is an AudioNode with one input and one output. +// It positions a sound in 3D space, with the exact effect dependent on the panning model. +// It has a position and an orientation in 3D space which is relative to the position and orientation of the context's AudioListener. +// A distance effect will attenuate the gain as the position moves away from the listener. +// A cone effect will attenuate the gain as the orientation moves away from the listener. +// All of these effects follow the OpenAL specification very closely. + +class AudioPannerNode : public AudioNode { +public: + // These must be defined as in the .idl file and must match those in the Panner class. + enum { + EQUALPOWER = 0, + HRTF = 1, + SOUNDFIELD = 2, + }; + + static PassRefPtr<AudioPannerNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new AudioPannerNode(context, sampleRate)); + } + + virtual ~AudioPannerNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void pullInputs(size_t framesToProcess); + virtual void reset(); + virtual void initialize(); + virtual void uninitialize(); + + // Listener + AudioListener* listener(); + + // Panning model + unsigned short panningModel() const { return m_panningModel; } + void setPanningModel(unsigned short); + + // Position + FloatPoint3D position() const { return m_position; } + void setPosition(float x, float y, float z) { m_position = FloatPoint3D(x, y, z); } + + // Orientation + FloatPoint3D orientation() const { return m_position; } + void setOrientation(float x, float y, float z) { m_orientation = FloatPoint3D(x, y, z); } + + // Velocity + FloatPoint3D velocity() const { return m_velocity; } + void setVelocity(float x, float y, float z) { m_velocity = FloatPoint3D(x, y, z); } + + // Distance parameters + unsigned short distanceModel() { return m_distanceEffect.model(); } + void setDistanceModel(unsigned short model) { m_distanceEffect.setModel(static_cast<DistanceEffect::ModelType>(model), true); } + + float refDistance() { return static_cast<float>(m_distanceEffect.refDistance()); } + void setRefDistance(float refDistance) { m_distanceEffect.setRefDistance(refDistance); } + + float maxDistance() { return static_cast<float>(m_distanceEffect.maxDistance()); } + void setMaxDistance(float maxDistance) { m_distanceEffect.setMaxDistance(maxDistance); } + + float rolloffFactor() { return static_cast<float>(m_distanceEffect.rolloffFactor()); } + void setRolloffFactor(float rolloffFactor) { m_distanceEffect.setRolloffFactor(rolloffFactor); } + + // Sound cones - angles in degrees + float coneInnerAngle() const { return static_cast<float>(m_coneEffect.innerAngle()); } + void setConeInnerAngle(float angle) { m_coneEffect.setInnerAngle(angle); } + + float coneOuterAngle() const { return static_cast<float>(m_coneEffect.outerAngle()); } + void setConeOuterAngle(float angle) { m_coneEffect.setOuterAngle(angle); } + + float coneOuterGain() const { return static_cast<float>(m_coneEffect.outerGain()); } + void setConeOuterGain(float angle) { m_coneEffect.setOuterGain(angle); } + + void getAzimuthElevation(double* outAzimuth, double* outElevation); + float dopplerRate(); + + // Accessors for dynamically calculated gain values. + AudioGain* distanceGain() { return m_distanceGain.get(); } + AudioGain* coneGain() { return m_coneGain.get(); } + +private: + AudioPannerNode(AudioContext*, float sampleRate); + + // Returns the combined distance and cone gain attenuation. + float distanceConeGain(); + + // Notifies any AudioBufferSourceNodes connected to us either directly or indirectly about our existence. + // This is in order to handle the pitch change necessary for the doppler shift. + void notifyAudioSourcesConnectedToNode(AudioNode*); + + OwnPtr<Panner> m_panner; + unsigned m_panningModel; + + FloatPoint3D m_position; + FloatPoint3D m_orientation; + FloatPoint3D m_velocity; + + // Gain + RefPtr<AudioGain> m_distanceGain; + RefPtr<AudioGain> m_coneGain; + DistanceEffect m_distanceEffect; + ConeEffect m_coneEffect; + double m_lastGain; + + unsigned m_connectionCount; +}; + +} // namespace WebCore + +#endif // AudioPannerNode_h diff --git a/Source/WebCore/webaudio/AudioPannerNode.idl b/Source/WebCore/webaudio/AudioPannerNode.idl new file mode 100644 index 000000000..2db093d7b --- /dev/null +++ b/Source/WebCore/webaudio/AudioPannerNode.idl @@ -0,0 +1,59 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateConstructor, + GenerateToJS + ] AudioPannerNode : AudioNode { + // Panning model + const unsigned short EQUALPOWER = 0; + const unsigned short HRTF = 1; + const unsigned short SOUNDFIELD = 2; + + // Default model for stereo is HRTF + attribute unsigned long panningModel; // FIXME: use unsigned short when glue generation supports it + + // Uses a 3D cartesian coordinate system + void setPosition(in float x, in float y, in float z); + void setOrientation(in float x, in float y, in float z); + void setVelocity(in float x, in float y, in float z); + + // Distance model + attribute unsigned long distanceModel; // FIXME: use unsigned short when glue generation supports it + attribute float refDistance; + attribute float maxDistance; + attribute float rolloffFactor; + + // Directional sound cone + attribute float coneInnerAngle; + attribute float coneOuterAngle; + attribute float coneOuterGain; + + // Dynamically calculated gain values + readonly attribute AudioGain coneGain; + readonly attribute AudioGain distanceGain; + }; +} diff --git a/Source/WebCore/webaudio/AudioParam.cpp b/Source/WebCore/webaudio/AudioParam.cpp new file mode 100644 index 000000000..be9279fdf --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.cpp @@ -0,0 +1,116 @@ +/* + * 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 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 "AudioParam.h" + +#include "AudioNode.h" +#include "AudioUtilities.h" +#include "FloatConversion.h" +#include <wtf/MathExtras.h> + +namespace WebCore { + +const double AudioParam::DefaultSmoothingConstant = 0.05; +const double AudioParam::SnapThreshold = 0.001; + +float AudioParam::value() +{ + // Update value for timeline. + if (context() && context()->isAudioThread()) { + bool hasValue; + float timelineValue = m_timeline.valueForContextTime(context(), narrowPrecisionToFloat(m_value), hasValue); + + if (hasValue) + m_value = timelineValue; + } + + return narrowPrecisionToFloat(m_value); +} + +void AudioParam::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; +} + +float AudioParam::smoothedValue() +{ + return narrowPrecisionToFloat(m_smoothedValue); +} + +bool AudioParam::smooth() +{ + // If values have been explicitly scheduled on the timeline, then use the exact value. + // Smoothing effectively is performed by the timeline. + bool useTimelineValue = false; + if (context()) + m_value = m_timeline.valueForContextTime(context(), narrowPrecisionToFloat(m_value), useTimelineValue); + + if (m_smoothedValue == m_value) { + // Smoothed value has already approached and snapped to value. + return true; + } + + if (useTimelineValue) + m_smoothedValue = m_value; + else { + // Dezipper - exponential approach. + m_smoothedValue += (m_value - m_smoothedValue) * m_smoothingConstant; + + // If we get close enough then snap to actual value. + if (fabs(m_smoothedValue - m_value) < SnapThreshold) // FIXME: the threshold needs to be adjustable depending on range - but this is OK general purpose value. + m_smoothedValue = m_value; + } + + return false; +} + +void AudioParam::calculateSampleAccurateValues(float* values, unsigned numberOfValues) +{ + bool isSafe = context() && context()->isAudioThread() && values; + ASSERT(isSafe); + if (!isSafe) + return; + + // Calculate values for this render quantum. + // Normally numberOfValues will equal AudioNode::ProcessingSizeInFrames (the render quantum size). + float sampleRate = context()->sampleRate(); + float startTime = narrowPrecisionToFloat(context()->currentTime()); + float endTime = startTime + numberOfValues / sampleRate; + + // Note we're running control rate at the sample-rate. + // Pass in the current value as default value. + m_value = m_timeline.valuesForTimeRange(startTime, endTime, narrowPrecisionToFloat(m_value), values, numberOfValues, sampleRate, sampleRate); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioParam.h b/Source/WebCore/webaudio/AudioParam.h new file mode 100644 index 000000000..2758487da --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.h @@ -0,0 +1,123 @@ +/* + * 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 AudioParam_h +#define AudioParam_h + +#include "AudioContext.h" +#include "AudioParamTimeline.h" +#include "PlatformString.h" +#include <sys/types.h> +#include <wtf/Float32Array.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +namespace WebCore { + +class AudioParam : public RefCounted<AudioParam> { +public: + static const double DefaultSmoothingConstant; + static const double SnapThreshold; + + static PassRefPtr<AudioParam> create(const String& name, double defaultValue, double minValue, double maxValue, unsigned units = 0) + { + return adoptRef(new AudioParam(name, defaultValue, minValue, maxValue, units)); + } + + AudioParam(const String& name, double defaultValue, double minValue, double maxValue, unsigned units = 0) + : m_name(name) + , m_value(defaultValue) + , m_defaultValue(defaultValue) + , m_minValue(minValue) + , m_maxValue(maxValue) + , m_units(units) + , m_smoothedValue(defaultValue) + , m_smoothingConstant(DefaultSmoothingConstant) + { + } + + void setContext(AudioContext* context) { m_context = context; } + AudioContext* context() { return m_context.get(); } + + float value(); + + void setValue(float); + + String name() const { return m_name; } + + float minValue() const { return static_cast<float>(m_minValue); } + float maxValue() const { return static_cast<float>(m_maxValue); } + float defaultValue() const { return static_cast<float>(m_defaultValue); } + unsigned units() const { return m_units; } + + // Value smoothing: + + // When a new value is set with setValue(), in our internal use of the parameter we don't immediately jump to it. + // Instead we smoothly approach this value to avoid glitching. + float smoothedValue(); + + // Smoothly exponentially approaches to (de-zippers) the desired value. + // Returns true if smoothed value has already snapped exactly to value. + bool smooth(); + + void resetSmoothedValue() { m_smoothedValue = m_value; } + void setSmoothingConstant(double k) { m_smoothingConstant = k; } + + // Parameter automation. + void setValueAtTime(float value, float time) { m_timeline.setValueAtTime(value, time); } + void linearRampToValueAtTime(float value, float time) { m_timeline.linearRampToValueAtTime(value, time); } + void exponentialRampToValueAtTime(float value, float time) { m_timeline.exponentialRampToValueAtTime(value, time); } + void setTargetValueAtTime(float targetValue, float time, float timeConstant) { m_timeline.setTargetValueAtTime(targetValue, time, timeConstant); } + void setValueCurveAtTime(Float32Array* curve, float time, float duration) { m_timeline.setValueCurveAtTime(curve, time, duration); } + void cancelScheduledValues(float startTime) { m_timeline.cancelScheduledValues(startTime); } + + bool hasTimelineValues() { return m_timeline.hasValues(); } + + // Calculates numberOfValues parameter values starting at the context's current time. + // Must be called in the context's render thread. + void calculateSampleAccurateValues(float* values, unsigned numberOfValues); + +private: + RefPtr<AudioContext> m_context; + String m_name; + double m_value; + double m_defaultValue; + double m_minValue; + double m_maxValue; + unsigned m_units; + + // Smoothing (de-zippering) + double m_smoothedValue; + double m_smoothingConstant; + + AudioParamTimeline m_timeline; +}; + +} // namespace WebCore + +#endif // AudioParam_h diff --git a/Source/WebCore/webaudio/AudioParam.idl b/Source/WebCore/webaudio/AudioParam.idl new file mode 100644 index 000000000..d9946f839 --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.idl @@ -0,0 +1,58 @@ +/* + * 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 + ] AudioParam { + attribute float value; + readonly attribute float minValue; + readonly attribute float maxValue; + readonly attribute float defaultValue; + + readonly attribute DOMString name; + + // FIXME: Could define units constants here (seconds, decibels, cents, etc.)... + readonly attribute unsigned short units; + + // Parameter automation. + void setValueAtTime(in float value, in float time); + void linearRampToValueAtTime(in float value, in float time); + void exponentialRampToValueAtTime(in float value, in float time); + + // Exponentially approach the target value with a rate having the given time constant. + void setTargetValueAtTime(in float targetValue, in float time, in float timeConstant); + + // Sets an array of arbitrary parameter values starting at time for the given duration. + // The number of values will be scaled to fit into the desired duration. + void setValueCurveAtTime(in Float32Array values, in float time, in float duration); + + // Cancels all scheduled parameter changes with times greater than or equal to startTime. + void cancelScheduledValues(in float startTime); + }; +} diff --git a/Source/WebCore/webaudio/AudioParamTimeline.cpp b/Source/WebCore/webaudio/AudioParamTimeline.cpp new file mode 100644 index 000000000..30d6192c8 --- /dev/null +++ b/Source/WebCore/webaudio/AudioParamTimeline.cpp @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2011 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 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 "AudioParamTimeline.h" + +#include "AudioUtilities.h" +#include "FloatConversion.h" +#include <algorithm> +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +void AudioParamTimeline::setValueAtTime(float value, float time) +{ + insertEvent(ParamEvent(ParamEvent::SetValue, value, time, 0, 0, 0)); +} + +void AudioParamTimeline::linearRampToValueAtTime(float value, float time) +{ + insertEvent(ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, 0, 0)); +} + +void AudioParamTimeline::exponentialRampToValueAtTime(float value, float time) +{ + insertEvent(ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, 0, 0)); +} + +void AudioParamTimeline::setTargetValueAtTime(float targetValue, float time, float timeConstant) +{ + insertEvent(ParamEvent(ParamEvent::SetTargetValue, targetValue, time, timeConstant, 0, 0)); +} + +void AudioParamTimeline::setValueCurveAtTime(Float32Array* curve, float time, float duration) +{ + insertEvent(ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, curve)); +} + +static bool isValidNumber(float x) +{ + return !isnan(x) && !isinf(x); +} + +void AudioParamTimeline::insertEvent(const ParamEvent& event) +{ + // Sanity check the event. Be super careful we're not getting infected with NaN or Inf. + bool isValid = event.type() < ParamEvent::LastType + && isValidNumber(event.value()) + && isValidNumber(event.time()) + && isValidNumber(event.timeConstant()) + && isValidNumber(event.duration()) + && event.duration() >= 0; + + ASSERT(isValid); + if (!isValid) + return; + + MutexLocker locker(m_eventsLock); + + unsigned i = 0; + float insertTime = event.time(); + for (i = 0; i < m_events.size(); ++i) { + // Overwrite same event type and time. + if (m_events[i].time() == insertTime && m_events[i].type() == event.type()) { + m_events[i] = event; + return; + } + + if (m_events[i].time() > insertTime) + break; + } + + m_events.insert(i, event); +} + +void AudioParamTimeline::cancelScheduledValues(float startTime) +{ + MutexLocker locker(m_eventsLock); + + // Remove all events starting at startTime. + for (unsigned i = 0; i < m_events.size(); ++i) { + if (m_events[i].time() >= startTime) { + m_events.remove(i, m_events.size() - i); + break; + } + } +} + +float AudioParamTimeline::valueForContextTime(AudioContext* context, float defaultValue, bool& hasValue) +{ + ASSERT(context); + + if (!context || !m_events.size() || context->currentTime() < m_events[0].time()) { + hasValue = false; + return defaultValue; + } + + // Ask for just a single value. + float value; + float sampleRate = context->sampleRate(); + float startTime = narrowPrecisionToFloat(context->currentTime()); + float endTime = startTime + 1.1f / sampleRate; // time just beyond one sample-frame + float controlRate = sampleRate / AudioNode::ProcessingSizeInFrames; // one parameter change per render quantum + value = valuesForTimeRange(startTime, endTime, defaultValue, &value, 1, sampleRate, controlRate); + + hasValue = true; + return value; +} + +float AudioParamTimeline::valuesForTimeRange(float startTime, + float endTime, + float defaultValue, + float* values, + unsigned numberOfValues, + float sampleRate, + float controlRate) +{ + // We can't contend the lock in the realtime audio thread. + if (!m_eventsLock.tryLock()) { + if (values) { + for (unsigned i = 0; i < numberOfValues; ++i) + values[i] = defaultValue; + } + return defaultValue; + } + + float value = valuesForTimeRangeImpl(startTime, endTime, defaultValue, values, numberOfValues, sampleRate, controlRate); + m_eventsLock.unlock(); + + return value; +} + +// Returns the rounded down integer sample-frame for the time and sample-rate. +static unsigned timeToSampleFrame(double time, float sampleRate) +{ + double k = 0.5 / sampleRate; + return static_cast<unsigned>((time + k) * sampleRate); +} + +float AudioParamTimeline::valuesForTimeRangeImpl(float startTime, + float endTime, + float defaultValue, + float* values, + unsigned numberOfValues, + float sampleRate, + float controlRate) +{ + ASSERT(values); + if (!values) + return defaultValue; + + // Return default value if there are no events matching the desired time range. + ASSERT(m_events.size()); + if (!m_events.size() || endTime <= m_events[0].time()) { + for (unsigned i = 0; i < numberOfValues; ++i) + values[i] = defaultValue; + return defaultValue; + } + + // Maintain a running time and index for writing the values buffer. + float currentTime = startTime; + unsigned writeIndex = 0; + + // If first event is after startTime then fill initial part of values buffer with defaultValue + // until we reach the first event time. + float firstEventTime = m_events[0].time(); + if (firstEventTime > startTime) { + float fillToTime = min(endTime, firstEventTime); + unsigned fillToFrame = timeToSampleFrame(fillToTime - startTime, sampleRate); + fillToFrame = min(fillToFrame, numberOfValues); + for (; writeIndex < fillToFrame; ++writeIndex) + values[writeIndex] = defaultValue; + + currentTime = fillToTime; + } + + float value = defaultValue; + + // Go through each event and render the value buffer where the times overlap, + // stopping when we've rendered all the requested values. + // FIXME: could try to optimize by avoiding having to iterate starting from the very first event + // and keeping track of a "current" event index. + int n = m_events.size(); + for (int i = 0; i < n && writeIndex < numberOfValues; ++i) { + ParamEvent& event = m_events[i]; + ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : 0; + + // Wait until we get a more recent event. + if (nextEvent && nextEvent->time() < currentTime) + continue; + + float value1 = event.value(); + float time1 = event.time(); + float value2 = nextEvent ? nextEvent->value() : value1; + float time2 = nextEvent ? nextEvent->time() : endTime + 1; + + float deltaTime = time2 - time1; + float k = deltaTime > 0 ? 1 / deltaTime : 0; + float sampleFrameTimeIncr = 1 / sampleRate; + + float fillToTime = min(endTime, time2); + unsigned fillToFrame = timeToSampleFrame(fillToTime - startTime, sampleRate); + fillToFrame = min(fillToFrame, numberOfValues); + + ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */; + + // First handle linear and exponential ramps which require looking ahead to the next event. + if (nextEventType == ParamEvent::LinearRampToValue) { + for (; writeIndex < fillToFrame; ++writeIndex) { + float x = (currentTime - time1) * k; + value = (1 - x) * value1 + x * value2; + values[writeIndex] = value; + currentTime += sampleFrameTimeIncr; + } + } else if (nextEventType == ParamEvent::ExponentialRampToValue) { + if (value1 <= 0 || value2 <= 0) { + // Handle negative values error case by propagating previous value. + for (; writeIndex < fillToFrame; ++writeIndex) + values[writeIndex] = value; + } else { + float numSampleFrames = deltaTime * sampleRate; + // The value goes exponentially from value1 to value2 in a duration of deltaTime seconds (corresponding to numSampleFrames). + // Compute the per-sample multiplier. + float multiplier = powf(value2 / value1, 1 / numSampleFrames); + for (; writeIndex < fillToFrame; ++writeIndex) { + values[writeIndex] = value; + value *= multiplier; + currentTime += sampleFrameTimeIncr; + } + } + } else { + // Handle event types not requiring looking ahead to the next event. + switch (event.type()) { + case ParamEvent::SetValue: + case ParamEvent::LinearRampToValue: + case ParamEvent::ExponentialRampToValue: + { + currentTime = fillToTime; + + // Simply stay at a constant value. + value = event.value(); + for (; writeIndex < fillToFrame; ++writeIndex) + values[writeIndex] = value; + + break; + } + + case ParamEvent::SetTargetValue: + { + currentTime = fillToTime; + + // Exponential approach to target value with given time constant. + float targetValue = event.value(); + float timeConstant = event.timeConstant(); + float discreteTimeConstant = AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate); + + for (; writeIndex < fillToFrame; ++writeIndex) { + values[writeIndex] = value; + value += (targetValue - value) * discreteTimeConstant; + } + + break; + } + + case ParamEvent::SetValueCurve: + { + Float32Array* curve = event.curve(); + float* curveData = curve ? curve->data() : 0; + unsigned numberOfCurvePoints = curve ? curve->length() : 0; + + // Curve events have duration, so don't just use next event time. + float duration = event.duration(); + float durationFrames = duration * sampleRate; + float curvePointsPerFrame = static_cast<float>(numberOfCurvePoints) / durationFrames; + + if (!curve || !curveData || !numberOfCurvePoints || duration <= 0 || sampleRate <= 0) { + // Error condition - simply propagate previous value. + currentTime = fillToTime; + for (; writeIndex < fillToFrame; ++writeIndex) + values[writeIndex] = value; + break; + } + + // Save old values and recalculate information based on the curve's duration + // instead of the next event time. + unsigned nextEventFillToFrame = fillToFrame; + float nextEventFillToTime = fillToTime; + fillToTime = min(endTime, time1 + duration); + fillToFrame = timeToSampleFrame(fillToTime - startTime, sampleRate); + fillToFrame = min(fillToFrame, numberOfValues); + + // Index into the curve data using a floating-point value. + // We're scaling the number of curve points by the duration (see curvePointsPerFrame). + float curveVirtualIndex = 0; + if (time1 < currentTime) { + // Index somewhere in the middle of the curve data. + // Don't use timeToSampleFrame() since we want the exact floating-point frame. + float frameOffset = (currentTime - time1) * sampleRate; + curveVirtualIndex = curvePointsPerFrame * frameOffset; + } + + // Render the stretched curve data using nearest neighbor sampling. + // Oversampled curve data can be provided if smoothness is desired. + for (; writeIndex < fillToFrame; ++writeIndex) { + unsigned curveIndex = static_cast<unsigned>(curveVirtualIndex); + curveVirtualIndex += curvePointsPerFrame; + + // Bounds check. + if (curveIndex < numberOfCurvePoints) + value = curveData[curveIndex]; + + values[writeIndex] = value; + } + + // If there's any time left after the duration of this event and the start + // of the next, then just propagate the last value. + for (; writeIndex < nextEventFillToFrame; ++writeIndex) + values[writeIndex] = value; + + // Re-adjust current time + currentTime = nextEventFillToTime; + + break; + } + } + } + } + + // If there's any time left after processing the last event then just propagate the last value + // to the end of the values buffer. + for (; writeIndex < numberOfValues; ++writeIndex) + values[writeIndex] = value; + + return value; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioParamTimeline.h b/Source/WebCore/webaudio/AudioParamTimeline.h new file mode 100644 index 000000000..22ea89081 --- /dev/null +++ b/Source/WebCore/webaudio/AudioParamTimeline.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2011 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 AudioParamTimeline_h +#define AudioParamTimeline_h + +#include "AudioContext.h" +#include <wtf/Float32Array.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioParamTimeline { +public: + AudioParamTimeline() + : m_currentEventIndex(0) + , m_value(0) + { + } + + void setValueAtTime(float value, float time); + void linearRampToValueAtTime(float value, float time); + void exponentialRampToValueAtTime(float value, float time); + void setTargetValueAtTime(float targetValue, float time, float timeConstant); + void setValueCurveAtTime(Float32Array* curve, float time, float duration); + void cancelScheduledValues(float startTime); + + // hasValue is set to true if a valid timeline value is returned. + // otherwise defaultValue is returned. + float valueForContextTime(AudioContext*, float defaultValue, bool& hasValue); + + // Given the time range, calculates parameter values into the values buffer + // and returns the last parameter value calculated for "values" or the defaultValue if none were calculated. + // controlRate is the rate (number per second) at which parameter values will be calculated. + // It should equal sampleRate for sample-accurate parameter changes, and otherwise will usually match + // the render quantum size such that the parameter value changes once per render quantum. + float valuesForTimeRange(float startTime, float endTime, float defaultValue, float* values, unsigned numberOfValues, float sampleRate, float controlRate); + + bool hasValues() { return m_events.size(); } + +private: + class ParamEvent { + public: + enum Type { + SetValue, + LinearRampToValue, + ExponentialRampToValue, + SetTargetValue, + SetValueCurve, + LastType + }; + + ParamEvent(Type type, float value, float time, float timeConstant, float duration, PassRefPtr<Float32Array> curve) + : m_type(type) + , m_value(value) + , m_time(time) + , m_timeConstant(timeConstant) + , m_duration(duration) + , m_curve(curve) + { + } + + unsigned type() const { return m_type; } + float value() const { return m_value; } + float time() const { return m_time; } + float timeConstant() const { return m_timeConstant; } + float duration() const { return m_duration; } + Float32Array* curve() { return m_curve.get(); } + + private: + unsigned m_type; + float m_value; + float m_time; + float m_timeConstant; + float m_duration; + RefPtr<Float32Array> m_curve; + }; + + void insertEvent(const ParamEvent&); + float valuesForTimeRangeImpl(float startTime, float endTime, float defaultValue, float* values, unsigned numberOfValues, float sampleRate, float controlRate); + + Vector<ParamEvent> m_events; + unsigned m_currentEventIndex; + float m_value; + + Mutex m_eventsLock; +}; + +} // namespace WebCore + +#endif // AudioParamTimeline_h diff --git a/Source/WebCore/webaudio/AudioProcessingEvent.cpp b/Source/WebCore/webaudio/AudioProcessingEvent.cpp new file mode 100644 index 000000000..fd5431ce4 --- /dev/null +++ b/Source/WebCore/webaudio/AudioProcessingEvent.cpp @@ -0,0 +1,68 @@ +/* + * 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 "AudioProcessingEvent.h" + +#include "AudioBuffer.h" +#include "EventNames.h" + +namespace WebCore { + +PassRefPtr<AudioProcessingEvent> AudioProcessingEvent::create() +{ + return adoptRef(new AudioProcessingEvent); +} + +PassRefPtr<AudioProcessingEvent> AudioProcessingEvent::create(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer) +{ + return adoptRef(new AudioProcessingEvent(inputBuffer, outputBuffer)); +} + +AudioProcessingEvent::AudioProcessingEvent() +{ +} + +AudioProcessingEvent::AudioProcessingEvent(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer) + : Event(eventNames().audioprocessEvent, true, false) + , m_inputBuffer(inputBuffer) + , m_outputBuffer(outputBuffer) +{ +} + +AudioProcessingEvent::~AudioProcessingEvent() +{ +} + +const AtomicString& AudioProcessingEvent::interfaceName() const +{ + return eventNames().interfaceForAudioProcessingEvent; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioProcessingEvent.h b/Source/WebCore/webaudio/AudioProcessingEvent.h new file mode 100644 index 000000000..3a4a1446c --- /dev/null +++ b/Source/WebCore/webaudio/AudioProcessingEvent.h @@ -0,0 +1,59 @@ +/* + * 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. + */ + +#ifndef AudioProcessingEvent_h +#define AudioProcessingEvent_h + +#include "AudioBuffer.h" +#include "Event.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class AudioBuffer; + +class AudioProcessingEvent : public Event { +public: + static PassRefPtr<AudioProcessingEvent> create(); + static PassRefPtr<AudioProcessingEvent> create(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer); + + virtual ~AudioProcessingEvent(); + + AudioBuffer* inputBuffer() { return m_inputBuffer.get(); } + AudioBuffer* outputBuffer() { return m_outputBuffer.get(); } + + virtual const AtomicString& interfaceName() const; + +private: + AudioProcessingEvent(); + AudioProcessingEvent(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer); + + RefPtr<AudioBuffer> m_inputBuffer; + RefPtr<AudioBuffer> m_outputBuffer; +}; + +} // namespace WebCore + +#endif // AudioProcessingEvent_h diff --git a/Source/WebCore/webaudio/AudioProcessingEvent.idl b/Source/WebCore/webaudio/AudioProcessingEvent.idl new file mode 100644 index 000000000..c2f8a8331 --- /dev/null +++ b/Source/WebCore/webaudio/AudioProcessingEvent.idl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] AudioProcessingEvent : Event { + readonly attribute AudioBuffer inputBuffer; + readonly attribute AudioBuffer outputBuffer; + }; +} diff --git a/Source/WebCore/webaudio/AudioSourceNode.h b/Source/WebCore/webaudio/AudioSourceNode.h new file mode 100644 index 000000000..a6bdd427b --- /dev/null +++ b/Source/WebCore/webaudio/AudioSourceNode.h @@ -0,0 +1,46 @@ +/* + * 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 AudioSourceNode_h +#define AudioSourceNode_h + +#include "AudioNode.h" + +namespace WebCore { + +class AudioSourceNode : public AudioNode { +public: + AudioSourceNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) + { + } +}; + +} // namespace WebCore + +#endif // AudioSourceNode_h diff --git a/Source/WebCore/webaudio/AudioSourceNode.idl b/Source/WebCore/webaudio/AudioSourceNode.idl new file mode 100644 index 000000000..ec3c35681 --- /dev/null +++ b/Source/WebCore/webaudio/AudioSourceNode.idl @@ -0,0 +1,34 @@ +/* + * 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 + ] AudioSourceNode : AudioNode { + }; +} diff --git a/Source/WebCore/webaudio/BiquadDSPKernel.cpp b/Source/WebCore/webaudio/BiquadDSPKernel.cpp new file mode 100644 index 000000000..9faac6598 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadDSPKernel.cpp @@ -0,0 +1,139 @@ +/* + * 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 "BiquadDSPKernel.h" + +#include "BiquadProcessor.h" +#include "FloatConversion.h" +#include <wtf/Vector.h> + +namespace WebCore { + +void BiquadDSPKernel::updateCoefficientsIfNecessary(bool useSmoothing, bool forceUpdate) +{ + if (forceUpdate || biquadProcessor()->filterCoefficientsDirty()) { + double value1; + double value2; + double gain; + + if (useSmoothing) { + value1 = biquadProcessor()->parameter1()->smoothedValue(); + value2 = biquadProcessor()->parameter2()->smoothedValue(); + gain = biquadProcessor()->parameter3()->smoothedValue(); + } else { + value1 = biquadProcessor()->parameter1()->value(); + value2 = biquadProcessor()->parameter2()->value(); + gain = biquadProcessor()->parameter3()->value(); + } + + // Convert from Hertz to normalized frequency 0 -> 1. + double nyquist = this->nyquist(); + double normalizedFrequency = value1 / nyquist; + + // Configure the biquad with the new filter parameters for the appropriate type of filter. + switch (biquadProcessor()->type()) { + case BiquadProcessor::LowPass: + m_biquad.setLowpassParams(normalizedFrequency, value2); + break; + + case BiquadProcessor::HighPass: + m_biquad.setHighpassParams(normalizedFrequency, value2); + break; + + case BiquadProcessor::BandPass: + m_biquad.setBandpassParams(normalizedFrequency, value2); + break; + + case BiquadProcessor::LowShelf: + m_biquad.setLowShelfParams(normalizedFrequency, gain); + break; + + case BiquadProcessor::HighShelf: + m_biquad.setHighShelfParams(normalizedFrequency, gain); + break; + + case BiquadProcessor::Peaking: + m_biquad.setPeakingParams(normalizedFrequency, value2, gain); + break; + + case BiquadProcessor::Notch: + m_biquad.setNotchParams(normalizedFrequency, value2); + break; + + case BiquadProcessor::Allpass: + m_biquad.setAllpassParams(normalizedFrequency, value2); + break; + } + } +} + +void BiquadDSPKernel::process(const float* source, float* destination, size_t framesToProcess) +{ + ASSERT(source && destination && biquadProcessor()); + + // Recompute filter coefficients if any of the parameters have changed. + // FIXME: as an optimization, implement a way that a Biquad object can simply copy its internal filter coefficients from another Biquad object. + // Then re-factor this code to only run for the first BiquadDSPKernel of each BiquadProcessor. + + updateCoefficientsIfNecessary(true, false); + + m_biquad.process(source, destination, framesToProcess); +} + +void BiquadDSPKernel::getFrequencyResponse(int nFrequencies, + const float* frequencyHz, + float* magResponse, + float* phaseResponse) +{ + bool isGood = nFrequencies > 0 && frequencyHz && magResponse && phaseResponse; + ASSERT(isGood); + if (!isGood) + return; + + Vector<float> frequency(nFrequencies); + + double nyquist = this->nyquist(); + + // Convert from frequency in Hz to normalized frequency (0 -> 1), + // with 1 equal to the Nyquist frequency. + for (int k = 0; k < nFrequencies; ++k) + frequency[k] = narrowPrecisionToFloat(frequencyHz[k] / nyquist); + + // We want to get the final values of the coefficients and compute + // the response from that instead of some intermediate smoothed + // set. Forcefully update the coefficients even if they are not + // dirty. + + updateCoefficientsIfNecessary(false, true); + + m_biquad.getFrequencyResponse(nFrequencies, frequency.data(), magResponse, phaseResponse); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/BiquadDSPKernel.h b/Source/WebCore/webaudio/BiquadDSPKernel.h new file mode 100644 index 000000000..a21e24c0d --- /dev/null +++ b/Source/WebCore/webaudio/BiquadDSPKernel.h @@ -0,0 +1,71 @@ +/* + * 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. + */ + +#ifndef BiquadDSPKernel_h +#define BiquadDSPKernel_h + +#include "AudioDSPKernel.h" +#include "Biquad.h" +#include "BiquadProcessor.h" + +namespace WebCore { + +class BiquadProcessor; + +// BiquadDSPKernel is an AudioDSPKernel and is responsible for filtering one channel of a BiquadProcessor using a Biquad object. + +class BiquadDSPKernel : public AudioDSPKernel { +public: + BiquadDSPKernel(BiquadProcessor* processor) + : AudioDSPKernel(processor) + { + } + + // AudioDSPKernel + virtual void process(const float* source, float* dest, size_t framesToProcess); + virtual void reset() { m_biquad.reset(); } + + // Get the magnitude and phase response of the filter at the given + // set of frequencies (in Hz). The phase response is in radians. + void getFrequencyResponse(int nFrequencies, + const float* frequencyHz, + float* magResponse, + float* phaseResponse); +protected: + Biquad m_biquad; + BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); } + + // To prevent audio glitches when parameters are changed, + // dezippering is used to slowly change the parameters. + // |useSmoothing| implies that we want to update using the + // smoothed values. Otherwise the final target values are + // used. If |forceUpdate| is true, we update the coefficients even + // if they are not dirty. (Used when computing the frequency + // response.) + void updateCoefficientsIfNecessary(bool useSmoothing, bool forceUpdate); +}; + +} // namespace WebCore + +#endif // BiquadDSPKernel_h diff --git a/Source/WebCore/webaudio/BiquadFilterNode.cpp b/Source/WebCore/webaudio/BiquadFilterNode.cpp new file mode 100644 index 000000000..80c3f938c --- /dev/null +++ b/Source/WebCore/webaudio/BiquadFilterNode.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011, 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 "BiquadFilterNode.h" + +#include "ExceptionCode.h" + +namespace WebCore { + +BiquadFilterNode::BiquadFilterNode(AudioContext* context, float sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + // Initially setup as lowpass filter. + m_processor = adoptPtr(new BiquadProcessor(BiquadProcessor::LowPass, sampleRate, 1, false)); + biquadProcessor()->parameter1()->setContext(context); + biquadProcessor()->parameter2()->setContext(context); + biquadProcessor()->parameter3()->setContext(context); + setNodeType(NodeTypeBiquadFilter); +} + +void BiquadFilterNode::setType(unsigned short type, ExceptionCode& ec) +{ + if (type > BiquadProcessor::Allpass) { + ec = NOT_SUPPORTED_ERR; + return; + } + + biquadProcessor()->setType(static_cast<BiquadProcessor::FilterType>(type)); +} + + +void BiquadFilterNode::getFrequencyResponse(const Float32Array* frequencyHz, + Float32Array* magResponse, + Float32Array* phaseResponse) +{ + if (!frequencyHz || !magResponse || !phaseResponse) + return; + + int n = std::min(frequencyHz->length(), + std::min(magResponse->length(), phaseResponse->length())); + + if (n) { + biquadProcessor()->getFrequencyResponse(n, + frequencyHz->data(), + magResponse->data(), + phaseResponse->data()); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/BiquadFilterNode.h b/Source/WebCore/webaudio/BiquadFilterNode.h new file mode 100644 index 000000000..545b060be --- /dev/null +++ b/Source/WebCore/webaudio/BiquadFilterNode.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef BiquadFilterNode_h +#define BiquadFilterNode_h + +#include "AudioBasicProcessorNode.h" +#include "BiquadProcessor.h" + +namespace WebCore { + +class AudioParam; + +class BiquadFilterNode : public AudioBasicProcessorNode { +public: + // These must be defined as in the .idl file and must match those in the BiquadProcessor class. + enum { + LOWPASS = 0, + HIGHPASS = 1, + BANDPASS = 2, + LOWSHELF = 3, + HIGHSHELF = 4, + PEAKING = 5, + NOTCH = 6, + ALLPASS = 7 + }; + + static PassRefPtr<BiquadFilterNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new BiquadFilterNode(context, sampleRate)); + } + + unsigned short type() { return biquadProcessor()->type(); } + void setType(unsigned short type, ExceptionCode&); + + AudioParam* frequency() { return biquadProcessor()->parameter1(); } + AudioParam* q() { return biquadProcessor()->parameter2(); } + AudioParam* gain() { return biquadProcessor()->parameter3(); } + + // Get the magnitude and phase response of the filter at the given + // set of frequencies (in Hz). The phase response is in radians. + void getFrequencyResponse(const Float32Array* frequencyHz, + Float32Array* magResponse, + Float32Array* phaseResponse); +private: + BiquadFilterNode(AudioContext*, float sampleRate); + + BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // BiquadFilterNode_h diff --git a/Source/WebCore/webaudio/BiquadFilterNode.idl b/Source/WebCore/webaudio/BiquadFilterNode.idl new file mode 100644 index 000000000..84e015aa3 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadFilterNode.idl @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] BiquadFilterNode : AudioNode { + // Filter type. + const unsigned short LOWPASS = 0; + const unsigned short HIGHPASS = 1; + const unsigned short BANDPASS = 2; + const unsigned short LOWSHELF = 3; + const unsigned short HIGHSHELF = 4; + const unsigned short PEAKING = 5; + const unsigned short NOTCH = 6; + const unsigned short ALLPASS = 7; + + attribute unsigned short type + setter raises(DOMException); + + readonly attribute AudioParam frequency; // in Hertz + readonly attribute AudioParam Q; // Quality factor + readonly attribute AudioParam gain; // in Decibels + + void getFrequencyResponse(in Float32Array frequencyHz, + in Float32Array magResponse, + in Float32Array phaseResponse); + }; +} diff --git a/Source/WebCore/webaudio/BiquadProcessor.cpp b/Source/WebCore/webaudio/BiquadProcessor.cpp new file mode 100644 index 000000000..12243c0c9 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadProcessor.cpp @@ -0,0 +1,155 @@ +/* + * 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 "BiquadProcessor.h" + +#include "BiquadDSPKernel.h" + +namespace WebCore { + +BiquadProcessor::BiquadProcessor(float sampleRate, size_t numberOfChannels, bool autoInitialize) + : AudioDSPKernelProcessor(sampleRate, numberOfChannels) + , m_type(LowPass) + , m_parameter1(0) + , m_parameter2(0) + , m_parameter3(0) + , m_filterCoefficientsDirty(true) +{ + double nyquist = 0.5 * this->sampleRate(); + + // Create parameters for BiquadFilterNode. + m_parameter1 = AudioParam::create("frequency", 350.0, 10.0, nyquist); + m_parameter2 = AudioParam::create("Q", 1, 0.0001, 1000.0); + m_parameter3 = AudioParam::create("gain", 0.0, -40, 40); + + if (autoInitialize) + initialize(); +} + +BiquadProcessor::BiquadProcessor(FilterType type, float sampleRate, size_t numberOfChannels, bool autoInitialize) + : AudioDSPKernelProcessor(sampleRate, numberOfChannels) + , m_type(type) + , m_parameter1(0) + , m_parameter2(0) + , m_parameter3(0) + , m_filterCoefficientsDirty(true) +{ + double nyquist = 0.5 * this->sampleRate(); + + // Handle the deprecated LowPass2FilterNode and HighPass2FilterNode. + switch (type) { + // Highpass and lowpass share the same parameters and only differ in filter type. + case LowPass: + case HighPass: + m_parameter1 = AudioParam::create("frequency", 350.0, 20.0, nyquist); + m_parameter2 = AudioParam::create("resonance", 0.0, -20.0, 20.0); + m_parameter3 = AudioParam::create("unused", 0.0, 0.0, 1.0); + break; + + default: + ASSERT_NOT_REACHED(); + } + + if (autoInitialize) + initialize(); +} + +BiquadProcessor::~BiquadProcessor() +{ + if (isInitialized()) + uninitialize(); +} + +PassOwnPtr<AudioDSPKernel> BiquadProcessor::createKernel() +{ + return adoptPtr(new BiquadDSPKernel(this)); +} + +void BiquadProcessor::checkForDirtyCoefficients() +{ + // Deal with smoothing / de-zippering. Start out assuming filter parameters are not changing. + + // The BiquadDSPKernel objects rely on this value to see if they need to re-compute their internal filter coefficients. + m_filterCoefficientsDirty = false; + + if (m_hasJustReset) { + // Snap to exact values first time after reset, then smooth for subsequent changes. + m_parameter1->resetSmoothedValue(); + m_parameter2->resetSmoothedValue(); + m_parameter3->resetSmoothedValue(); + m_filterCoefficientsDirty = true; + m_hasJustReset = false; + } else { + // Smooth all of the filter parameters. If they haven't yet converged to their target value then mark coefficients as dirty. + bool isStable1 = m_parameter1->smooth(); + bool isStable2 = m_parameter2->smooth(); + bool isStable3 = m_parameter3->smooth(); + if (!(isStable1 && isStable2 && isStable3)) + m_filterCoefficientsDirty = true; + } +} + +void BiquadProcessor::process(AudioBus* source, AudioBus* destination, size_t framesToProcess) +{ + if (!isInitialized()) { + destination->zero(); + return; + } + + checkForDirtyCoefficients(); + + // For each channel of our input, process using the corresponding BiquadDSPKernel into the output channel. + for (unsigned i = 0; i < m_kernels.size(); ++i) + m_kernels[i]->process(source->channel(i)->data(), destination->channel(i)->data(), framesToProcess); +} + +void BiquadProcessor::setType(FilterType type) +{ + if (type != m_type) { + m_type = type; + reset(); // The filter state must be reset only if the type has changed. + } +} + +void BiquadProcessor::getFrequencyResponse(int nFrequencies, + const float* frequencyHz, + float* magResponse, + float* phaseResponse) +{ + // Compute the frequency response on a separate temporary kernel + // to avoid interfering with the processing running in the audio + // thread on the main kernels. + + OwnPtr<BiquadDSPKernel> responseKernel = adoptPtr(new BiquadDSPKernel(this)); + + responseKernel->getFrequencyResponse(nFrequencies, frequencyHz, magResponse, phaseResponse); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/BiquadProcessor.h b/Source/WebCore/webaudio/BiquadProcessor.h new file mode 100644 index 000000000..c7f7d3068 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadProcessor.h @@ -0,0 +1,94 @@ +/* + * 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. + */ + +#ifndef BiquadProcessor_h +#define BiquadProcessor_h + +#include "AudioDSPKernel.h" +#include "AudioDSPKernelProcessor.h" +#include "AudioNode.h" +#include "AudioParam.h" +#include "Biquad.h" +#include <wtf/RefPtr.h> + +namespace WebCore { + +// BiquadProcessor is an AudioDSPKernelProcessor which uses Biquad objects to implement several common filters. + +class BiquadProcessor : public AudioDSPKernelProcessor { +public: + enum FilterType { + LowPass = 0, + HighPass = 1, + BandPass = 2, + LowShelf = 3, + HighShelf = 4, + Peaking = 5, + Notch = 6, + Allpass = 7 + }; + + BiquadProcessor(float sampleRate, size_t numberOfChannels, bool autoInitialize); + + // Old constructor used by deprecated LowPass2FilterNode and HighPass2FilterNode + BiquadProcessor(FilterType, float sampleRate, size_t numberOfChannels, bool autoInitialize = true); + + virtual ~BiquadProcessor(); + + virtual PassOwnPtr<AudioDSPKernel> createKernel(); + + virtual void process(AudioBus* source, AudioBus* destination, size_t framesToProcess); + + // Get the magnitude and phase response of the filter at the given + // set of frequencies (in Hz). The phase response is in radians. + void getFrequencyResponse(int nFrequencies, + const float* frequencyHz, + float* magResponse, + float* phaseResponse); + + void checkForDirtyCoefficients(); + + bool filterCoefficientsDirty() const { return m_filterCoefficientsDirty; } + + AudioParam* parameter1() { return m_parameter1.get(); } + AudioParam* parameter2() { return m_parameter2.get(); } + AudioParam* parameter3() { return m_parameter3.get(); } + + FilterType type() const { return m_type; } + void setType(FilterType); + +private: + FilterType m_type; + + RefPtr<AudioParam> m_parameter1; + RefPtr<AudioParam> m_parameter2; + RefPtr<AudioParam> m_parameter3; + + // so DSP kernels know when to re-compute coefficients + bool m_filterCoefficientsDirty; +}; + +} // namespace WebCore + +#endif // BiquadProcessor_h diff --git a/Source/WebCore/webaudio/ConvolverNode.cpp b/Source/WebCore/webaudio/ConvolverNode.cpp new file mode 100644 index 000000000..315835619 --- /dev/null +++ b/Source/WebCore/webaudio/ConvolverNode.cpp @@ -0,0 +1,155 @@ +/* + * 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 "ConvolverNode.h" + +#include "AudioBuffer.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "Reverb.h" +#include <wtf/MainThread.h> + +// Note about empirical tuning: +// The maximum FFT size affects reverb performance and accuracy. +// If the reverb is single-threaded and processes entirely in the real-time audio thread, +// it's important not to make this too high. In this case 8192 is a good value. +// But, the Reverb object is multi-threaded, so we want this as high as possible without losing too much accuracy. +// Very large FFTs will have worse phase errors. Given these constraints 32768 is a good compromise. +const size_t MaxFFTSize = 32768; + +namespace WebCore { + +ConvolverNode::ConvolverNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setNodeType(NodeTypeConvolver); + + initialize(); +} + +ConvolverNode::~ConvolverNode() +{ + uninitialize(); +} + +void ConvolverNode::process(size_t framesToProcess) +{ + AudioBus* outputBus = output(0)->bus(); + ASSERT(outputBus); + + // Synchronize with possible dynamic changes to the impulse response. + if (m_processLock.tryLock()) { + if (!isInitialized() || !m_reverb.get()) + outputBus->zero(); + else { + // Process using the convolution engine. + // Note that we can handle the case where nothing is connected to the input, in which case we'll just feed silence into the convolver. + // FIXME: If we wanted to get fancy we could try to factor in the 'tail time' and stop processing once the tail dies down if + // we keep getting fed silence. + m_reverb->process(input(0)->bus(), outputBus, framesToProcess); + } + + m_processLock.unlock(); + } else { + // Too bad - the tryLock() failed. We must be in the middle of setting a new impulse response. + outputBus->zero(); + } +} + +void ConvolverNode::reset() +{ + MutexLocker locker(m_processLock); + if (m_reverb.get()) + m_reverb->reset(); +} + +void ConvolverNode::initialize() +{ + if (isInitialized()) + return; + + AudioNode::initialize(); +} + +void ConvolverNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_reverb.clear(); + AudioNode::uninitialize(); +} + +void ConvolverNode::setBuffer(AudioBuffer* buffer) +{ + ASSERT(isMainThread()); + + ASSERT(buffer); + if (!buffer) + return; + + unsigned numberOfChannels = buffer->numberOfChannels(); + size_t bufferLength = buffer->length(); + + // The current implementation supports up to four channel impulse responses, which are interpreted as true-stereo (see Reverb class). + bool isBufferGood = numberOfChannels > 0 && numberOfChannels <= 4 && bufferLength; + ASSERT(isBufferGood); + if (!isBufferGood) + return; + + // Wrap the AudioBuffer by an AudioBus. It's an efficient pointer set and not a memcpy(). + // This memory is simply used in the Reverb constructor and no reference to it is kept for later use in that class. + AudioBus bufferBus(numberOfChannels, bufferLength, false); + for (unsigned i = 0; i < numberOfChannels; ++i) + bufferBus.setChannelMemory(i, buffer->getChannelData(i)->data(), bufferLength); + + // Create the reverb with the given impulse response. + bool useBackgroundThreads = !context()->isOfflineContext(); + OwnPtr<Reverb> reverb = adoptPtr(new Reverb(&bufferBus, AudioNode::ProcessingSizeInFrames, MaxFFTSize, 2, useBackgroundThreads)); + + { + // Synchronize with process(). + MutexLocker locker(m_processLock); + m_reverb = reverb.release(); + m_buffer = buffer; + } +} + +AudioBuffer* ConvolverNode::buffer() +{ + ASSERT(isMainThread()); + return m_buffer.get(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/ConvolverNode.h b/Source/WebCore/webaudio/ConvolverNode.h new file mode 100644 index 000000000..fb29d5c5d --- /dev/null +++ b/Source/WebCore/webaudio/ConvolverNode.h @@ -0,0 +1,69 @@ +/* + * 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. + */ + +#ifndef ConvolverNode_h +#define ConvolverNode_h + +#include "AudioNode.h" +#include <wtf/OwnPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioBuffer; +class Reverb; + +class ConvolverNode : public AudioNode { +public: + static PassRefPtr<ConvolverNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new ConvolverNode(context, sampleRate)); + } + + virtual ~ConvolverNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + virtual void initialize(); + virtual void uninitialize(); + + // Impulse responses + void setBuffer(AudioBuffer*); + AudioBuffer* buffer(); + +private: + ConvolverNode(AudioContext*, float sampleRate); + + OwnPtr<Reverb> m_reverb; + RefPtr<AudioBuffer> m_buffer; + + // This synchronizes dynamic changes to the convolution impulse response with process(). + mutable Mutex m_processLock; +}; + +} // namespace WebCore + +#endif // ConvolverNode_h diff --git a/Source/WebCore/webaudio/ConvolverNode.idl b/Source/WebCore/webaudio/ConvolverNode.idl new file mode 100644 index 000000000..d3eb475f0 --- /dev/null +++ b/Source/WebCore/webaudio/ConvolverNode.idl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +module audio { + // A linear convolution effect + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] ConvolverNode : AudioNode { + attribute [JSCCustomSetter] AudioBuffer buffer; + }; +} diff --git a/Source/WebCore/webaudio/DOMWindowWebAudio.idl b/Source/WebCore/webaudio/DOMWindowWebAudio.idl new file mode 100644 index 000000000..dd108efb7 --- /dev/null +++ b/Source/WebCore/webaudio/DOMWindowWebAudio.idl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 Google Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +module window { + + interface [ + Conditional=WEB_AUDIO, + Supplemental=DOMWindow + ] DOMWindowWebAudio { + attribute [JSCCustomGetter, EnabledAtRuntime] AudioContextConstructor webkitAudioContext; + attribute AudioPannerNodeConstructor webkitAudioPannerNode; + attribute AudioProcessingEventConstructor AudioProcessingEvent; + attribute OfflineAudioCompletionEventConstructor OfflineAudioCompletionEvent; + }; + +}
\ No newline at end of file diff --git a/Source/WebCore/webaudio/DefaultAudioDestinationNode.cpp b/Source/WebCore/webaudio/DefaultAudioDestinationNode.cpp new file mode 100644 index 000000000..f7db34e05 --- /dev/null +++ b/Source/WebCore/webaudio/DefaultAudioDestinationNode.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011, 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 "DefaultAudioDestinationNode.h" + +#ifndef NDEBUG +#include <stdio.h> +#endif + +namespace WebCore { + +DefaultAudioDestinationNode::DefaultAudioDestinationNode(AudioContext* context) + : AudioDestinationNode(context, AudioDestination::hardwareSampleRate()) +{ + initialize(); +} + +DefaultAudioDestinationNode::~DefaultAudioDestinationNode() +{ + uninitialize(); +} + +void DefaultAudioDestinationNode::initialize() +{ + if (isInitialized()) + return; + + float hardwareSampleRate = AudioDestination::hardwareSampleRate(); +#ifndef NDEBUG + fprintf(stderr, ">>>> hardwareSampleRate = %f\n", hardwareSampleRate); +#endif + + m_destination = AudioDestination::create(*this, hardwareSampleRate); + + AudioNode::initialize(); +} + +void DefaultAudioDestinationNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_destination->stop(); + + AudioNode::uninitialize(); +} + +void DefaultAudioDestinationNode::startRendering() +{ + ASSERT(isInitialized()); + if (isInitialized()) + m_destination->start(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/DefaultAudioDestinationNode.h b/Source/WebCore/webaudio/DefaultAudioDestinationNode.h new file mode 100644 index 000000000..630bdc335 --- /dev/null +++ b/Source/WebCore/webaudio/DefaultAudioDestinationNode.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef DefaultAudioDestinationNode_h +#define DefaultAudioDestinationNode_h + +#include "AudioDestination.h" +#include "AudioDestinationNode.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class AudioContext; + +class DefaultAudioDestinationNode : public AudioDestinationNode { +public: + static PassRefPtr<DefaultAudioDestinationNode> create(AudioContext* context) + { + return adoptRef(new DefaultAudioDestinationNode(context)); + } + + virtual ~DefaultAudioDestinationNode(); + + // AudioNode + virtual void initialize(); + virtual void uninitialize(); + + float sampleRate() const { return m_destination->sampleRate(); } + + virtual void startRendering(); + +private: + DefaultAudioDestinationNode(AudioContext*); + + OwnPtr<AudioDestination> m_destination; +}; + +} // namespace WebCore + +#endif // DefaultAudioDestinationNode_h diff --git a/Source/WebCore/webaudio/DelayDSPKernel.cpp b/Source/WebCore/webaudio/DelayDSPKernel.cpp new file mode 100644 index 000000000..aeda189c5 --- /dev/null +++ b/Source/WebCore/webaudio/DelayDSPKernel.cpp @@ -0,0 +1,140 @@ +/* + * 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 "DelayDSPKernel.h" + +#include "AudioUtilities.h" +#include <algorithm> + +using namespace std; + +const float DefaultMaxDelayTime = 1; +const float SmoothingTimeConstant = 0.020f; // 20ms + +namespace WebCore { + +DelayDSPKernel::DelayDSPKernel(DelayProcessor* processor) + : AudioDSPKernel(processor) + , m_maxDelayTime(DefaultMaxDelayTime) + , m_writeIndex(0) + , m_firstTime(true) +{ + ASSERT(processor && processor->sampleRate() > 0); + if (!processor) + return; + + m_buffer.allocate(static_cast<size_t>(processor->sampleRate() * DefaultMaxDelayTime)); + m_buffer.zero(); + + m_smoothingRate = AudioUtilities::discreteTimeConstantForSampleRate(SmoothingTimeConstant, processor->sampleRate()); +} + +DelayDSPKernel::DelayDSPKernel(double maxDelayTime, float sampleRate) + : AudioDSPKernel(sampleRate) + , m_maxDelayTime(maxDelayTime) + , m_writeIndex(0) + , m_firstTime(true) +{ + ASSERT(maxDelayTime > 0.0); + if (maxDelayTime <= 0.0) + return; + + size_t bufferLength = static_cast<size_t>(sampleRate * maxDelayTime); + ASSERT(bufferLength); + if (!bufferLength) + return; + + m_buffer.allocate(bufferLength); + m_buffer.zero(); + + m_smoothingRate = AudioUtilities::discreteTimeConstantForSampleRate(SmoothingTimeConstant, sampleRate); +} + +void DelayDSPKernel::process(const float* source, float* destination, size_t framesToProcess) +{ + size_t bufferLength = m_buffer.size(); + float* buffer = m_buffer.data(); + + ASSERT(bufferLength); + if (!bufferLength) + return; + + ASSERT(source && destination); + if (!source || !destination) + return; + + float sampleRate = this->sampleRate(); + double delayTime = delayProcessor() ? delayProcessor()->delayTime()->value() : m_desiredDelayFrames / sampleRate; + + // Make sure the delay time is in a valid range. + delayTime = min(maxDelayTime(), delayTime); + delayTime = max(0.0, delayTime); + + if (m_firstTime) { + m_currentDelayTime = delayTime; + m_firstTime = false; + } + + int n = framesToProcess; + while (n--) { + // Approach desired delay time. + m_currentDelayTime += (delayTime - m_currentDelayTime) * m_smoothingRate; + + double desiredDelayFrames = m_currentDelayTime * sampleRate; + + double readPosition = m_writeIndex + bufferLength - desiredDelayFrames; + if (readPosition >= bufferLength) + readPosition -= bufferLength; + + // Linearly interpolate in-between delay times. + int readIndex1 = static_cast<int>(readPosition); + int readIndex2 = (readIndex1 + 1) % bufferLength; + double interpolationFactor = readPosition - readIndex1; + + double input = static_cast<float>(*source++); + buffer[m_writeIndex] = static_cast<float>(input); + m_writeIndex = (m_writeIndex + 1) % bufferLength; + + double sample1 = buffer[readIndex1]; + double sample2 = buffer[readIndex2]; + + double output = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; + + *destination++ = static_cast<float>(output); + } +} + +void DelayDSPKernel::reset() +{ + m_firstTime = true; + m_buffer.zero(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/DelayDSPKernel.h b/Source/WebCore/webaudio/DelayDSPKernel.h new file mode 100644 index 000000000..79a39568b --- /dev/null +++ b/Source/WebCore/webaudio/DelayDSPKernel.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#ifndef DelayDSPKernel_h +#define DelayDSPKernel_h + +#include "AudioArray.h" +#include "AudioDSPKernel.h" +#include "DelayProcessor.h" + +namespace WebCore { + +class DelayProcessor; + +class DelayDSPKernel : public AudioDSPKernel { +public: + DelayDSPKernel(DelayProcessor*); + DelayDSPKernel(double maxDelayTime, float sampleRate); + + virtual void process(const float* source, float* destination, size_t framesToProcess); + virtual void reset(); + + double maxDelayTime() const { return m_maxDelayTime; } + + void setDelayFrames(double numberOfFrames) { m_desiredDelayFrames = numberOfFrames; } + +private: + AudioFloatArray m_buffer; + double m_maxDelayTime; + int m_writeIndex; + double m_currentDelayTime; + double m_smoothingRate; + bool m_firstTime; + double m_desiredDelayFrames; + + DelayProcessor* delayProcessor() { return static_cast<DelayProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // DelayDSPKernel_h diff --git a/Source/WebCore/webaudio/DelayNode.cpp b/Source/WebCore/webaudio/DelayNode.cpp new file mode 100644 index 000000000..2a525dd1a --- /dev/null +++ b/Source/WebCore/webaudio/DelayNode.cpp @@ -0,0 +1,48 @@ +/* + * 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 "DelayNode.h" + +namespace WebCore { + +DelayNode::DelayNode(AudioContext* context, float sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new DelayProcessor(sampleRate, 1)); + delayTime()->setContext(context); + setNodeType(NodeTypeDelay); +} + +AudioParam* DelayNode::delayTime() +{ + return delayProcessor()->delayTime(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/DelayNode.h b/Source/WebCore/webaudio/DelayNode.h new file mode 100644 index 000000000..3d58a8913 --- /dev/null +++ b/Source/WebCore/webaudio/DelayNode.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. + * + * 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. + */ + +#ifndef DelayNode_h +#define DelayNode_h + +#include "AudioBasicProcessorNode.h" +#include "DelayProcessor.h" +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioParam; + +class DelayNode : public AudioBasicProcessorNode { +public: + static PassRefPtr<DelayNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new DelayNode(context, sampleRate)); + } + + AudioParam* delayTime(); + +private: + DelayNode(AudioContext*, float sampleRate); + + DelayProcessor* delayProcessor() { return static_cast<DelayProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // DelayNode_h diff --git a/Source/WebCore/webaudio/DelayNode.idl b/Source/WebCore/webaudio/DelayNode.idl new file mode 100644 index 000000000..77566270c --- /dev/null +++ b/Source/WebCore/webaudio/DelayNode.idl @@ -0,0 +1,32 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] DelayNode : AudioNode { + readonly attribute AudioParam delayTime; + }; +} diff --git a/Source/WebCore/webaudio/DelayProcessor.cpp b/Source/WebCore/webaudio/DelayProcessor.cpp new file mode 100644 index 000000000..8ed3e4337 --- /dev/null +++ b/Source/WebCore/webaudio/DelayProcessor.cpp @@ -0,0 +1,54 @@ +/* + * 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 "DelayProcessor.h" + +#include "DelayDSPKernel.h" + +namespace WebCore { + +DelayProcessor::DelayProcessor(float sampleRate, unsigned numberOfChannels) + : AudioDSPKernelProcessor(sampleRate, numberOfChannels) +{ + m_delayTime = AudioParam::create("delayTime", 0.0, 0.0, 1.0); +} + +DelayProcessor::~DelayProcessor() +{ + if (isInitialized()) + uninitialize(); +} + +PassOwnPtr<AudioDSPKernel> DelayProcessor::createKernel() +{ + return adoptPtr(new DelayDSPKernel(this)); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/DelayProcessor.h b/Source/WebCore/webaudio/DelayProcessor.h new file mode 100644 index 000000000..15428ce75 --- /dev/null +++ b/Source/WebCore/webaudio/DelayProcessor.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. + * + * 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. + */ + +#ifndef DelayProcessor_h +#define DelayProcessor_h + +#include "AudioDSPKernelProcessor.h" +#include "AudioParam.h" + +#include <wtf/PassOwnPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class AudioDSPKernel; + +class DelayProcessor : public AudioDSPKernelProcessor { +public: + DelayProcessor(float sampleRate, unsigned numberOfChannels); + virtual ~DelayProcessor(); + + virtual PassOwnPtr<AudioDSPKernel> createKernel(); + + AudioParam* delayTime() const { return m_delayTime.get(); } + +private: + RefPtr<AudioParam> m_delayTime; +}; + +} // namespace WebCore + +#endif // DelayProcessor_h diff --git a/Source/WebCore/webaudio/DynamicsCompressorNode.cpp b/Source/WebCore/webaudio/DynamicsCompressorNode.cpp new file mode 100644 index 000000000..ea8b58922 --- /dev/null +++ b/Source/WebCore/webaudio/DynamicsCompressorNode.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011, 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 "DynamicsCompressorNode.h" + +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "DynamicsCompressor.h" + +namespace WebCore { + +DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setNodeType(NodeTypeDynamicsCompressor); + + initialize(); +} + +DynamicsCompressorNode::~DynamicsCompressorNode() +{ + uninitialize(); +} + +void DynamicsCompressorNode::process(size_t framesToProcess) +{ + AudioBus* outputBus = output(0)->bus(); + ASSERT(outputBus); + + m_dynamicsCompressor->process(input(0)->bus(), outputBus, framesToProcess); +} + +void DynamicsCompressorNode::reset() +{ + m_dynamicsCompressor->reset(); +} + +void DynamicsCompressorNode::initialize() +{ + if (isInitialized()) + return; + + AudioNode::initialize(); + m_dynamicsCompressor = adoptPtr(new DynamicsCompressor(true, sampleRate())); +} + +void DynamicsCompressorNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_dynamicsCompressor.clear(); + AudioNode::uninitialize(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/DynamicsCompressorNode.h b/Source/WebCore/webaudio/DynamicsCompressorNode.h new file mode 100644 index 000000000..d718ab341 --- /dev/null +++ b/Source/WebCore/webaudio/DynamicsCompressorNode.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef DynamicsCompressorNode_h +#define DynamicsCompressorNode_h + +#include "AudioNode.h" +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class DynamicsCompressor; + +class DynamicsCompressorNode : public AudioNode { +public: + static PassRefPtr<DynamicsCompressorNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new DynamicsCompressorNode(context, sampleRate)); + } + + virtual ~DynamicsCompressorNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + virtual void initialize(); + virtual void uninitialize(); + +private: + DynamicsCompressorNode(AudioContext*, float sampleRate); + + OwnPtr<DynamicsCompressor> m_dynamicsCompressor; +}; + +} // namespace WebCore + +#endif // DynamicsCompressorNode_h diff --git a/Source/WebCore/webaudio/DynamicsCompressorNode.idl b/Source/WebCore/webaudio/DynamicsCompressorNode.idl new file mode 100644 index 000000000..74997eae5 --- /dev/null +++ b/Source/WebCore/webaudio/DynamicsCompressorNode.idl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] DynamicsCompressorNode : AudioNode { + }; +} diff --git a/Source/WebCore/webaudio/HighPass2FilterNode.cpp b/Source/WebCore/webaudio/HighPass2FilterNode.cpp new file mode 100644 index 000000000..520785f80 --- /dev/null +++ b/Source/WebCore/webaudio/HighPass2FilterNode.cpp @@ -0,0 +1,45 @@ +/* + * 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 "HighPass2FilterNode.h" + +namespace WebCore { + +HighPass2FilterNode::HighPass2FilterNode(AudioContext* context, float sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new BiquadProcessor(BiquadProcessor::HighPass, sampleRate, 1, false)); + biquadProcessor()->parameter1()->setContext(context); + biquadProcessor()->parameter2()->setContext(context); + biquadProcessor()->parameter3()->setContext(context); + setNodeType(NodeTypeHighPass2Filter); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/HighPass2FilterNode.h b/Source/WebCore/webaudio/HighPass2FilterNode.h new file mode 100644 index 000000000..bfd76a0d5 --- /dev/null +++ b/Source/WebCore/webaudio/HighPass2FilterNode.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. + * + * 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. + */ + +#ifndef HighPass2FilterNode_h +#define HighPass2FilterNode_h + +#include "AudioBasicProcessorNode.h" +#include "BiquadProcessor.h" + +namespace WebCore { + +class AudioParam; + +class HighPass2FilterNode : public AudioBasicProcessorNode { +public: + static PassRefPtr<HighPass2FilterNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new HighPass2FilterNode(context, sampleRate)); + } + + AudioParam* cutoff() { return biquadProcessor()->parameter1(); } + AudioParam* resonance() { return biquadProcessor()->parameter2(); } + +private: + HighPass2FilterNode(AudioContext*, float sampleRate); + + BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // HighPass2FilterNode_h diff --git a/Source/WebCore/webaudio/HighPass2FilterNode.idl b/Source/WebCore/webaudio/HighPass2FilterNode.idl new file mode 100644 index 000000000..399f9b500 --- /dev/null +++ b/Source/WebCore/webaudio/HighPass2FilterNode.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. + * + * 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. + */ + +module audio { + // Two-pole highpass filter + // FIXME: design BiquadNode and use instead of this + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] HighPass2FilterNode : AudioNode { + readonly attribute AudioParam cutoff; + readonly attribute AudioParam resonance; + }; +} diff --git a/Source/WebCore/webaudio/JavaScriptAudioNode.cpp b/Source/WebCore/webaudio/JavaScriptAudioNode.cpp new file mode 100644 index 000000000..4221509cf --- /dev/null +++ b/Source/WebCore/webaudio/JavaScriptAudioNode.cpp @@ -0,0 +1,272 @@ +/* + * 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 "JavaScriptAudioNode.h" + +#include "AudioBuffer.h" +#include "AudioBus.h" +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "AudioProcessingEvent.h" +#include "Document.h" +#include <wtf/Float32Array.h> +#include <wtf/MainThread.h> + +namespace WebCore { + +const size_t DefaultBufferSize = 4096; + +PassRefPtr<JavaScriptAudioNode> JavaScriptAudioNode::create(AudioContext* context, float sampleRate, size_t bufferSize, unsigned numberOfInputs, unsigned numberOfOutputs) +{ + return adoptRef(new JavaScriptAudioNode(context, sampleRate, bufferSize, numberOfInputs, numberOfOutputs)); +} + +JavaScriptAudioNode::JavaScriptAudioNode(AudioContext* context, float sampleRate, size_t bufferSize, unsigned numberOfInputs, unsigned numberOfOutputs) + : AudioNode(context, sampleRate) + , m_doubleBufferIndex(0) + , m_doubleBufferIndexForEvent(0) + , m_bufferSize(bufferSize) + , m_bufferReadWriteIndex(0) + , m_isRequestOutstanding(false) +{ + // Check for valid buffer size. + switch (bufferSize) { + case 256: + case 512: + case 1024: + case 2048: + case 4096: + case 8192: + case 16384: + m_bufferSize = bufferSize; + break; + default: + m_bufferSize = DefaultBufferSize; + } + + // Regardless of the allowed buffer sizes above, we still need to process at the granularity of the AudioNode. + if (m_bufferSize < AudioNode::ProcessingSizeInFrames) + m_bufferSize = AudioNode::ProcessingSizeInFrames; + + // FIXME: Right now we're hardcoded to single input and single output. + // Although the specification says this is OK for a simple implementation, multiple inputs and outputs would be good. + ASSERT_UNUSED(numberOfInputs, numberOfInputs == 1); + ASSERT_UNUSED(numberOfOutputs, numberOfOutputs == 1); + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setNodeType(NodeTypeJavaScript); + + initialize(); +} + +JavaScriptAudioNode::~JavaScriptAudioNode() +{ + uninitialize(); +} + +void JavaScriptAudioNode::initialize() +{ + if (isInitialized()) + return; + + float sampleRate = context()->sampleRate(); + + // Create double buffers on both the input and output sides. + // These AudioBuffers will be directly accessed in the main thread by JavaScript. + for (unsigned i = 0; i < 2; ++i) { + m_inputBuffers.append(AudioBuffer::create(2, bufferSize(), sampleRate)); + m_outputBuffers.append(AudioBuffer::create(2, bufferSize(), sampleRate)); + } + + AudioNode::initialize(); +} + +void JavaScriptAudioNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_inputBuffers.clear(); + m_outputBuffers.clear(); + + AudioNode::uninitialize(); +} + +void JavaScriptAudioNode::process(size_t framesToProcess) +{ + // Discussion about inputs and outputs: + // As in other AudioNodes, JavaScriptAudioNode uses an AudioBus for its input and output (see inputBus and outputBus below). + // Additionally, there is a double-buffering for input and output which is exposed directly to JavaScript (see inputBuffer and outputBuffer below). + // This node is the producer for inputBuffer and the consumer for outputBuffer. + // The JavaScript code is the consumer of inputBuffer and the producer for outputBuffer. + + // Get input and output busses. + AudioBus* inputBus = this->input(0)->bus(); + AudioBus* outputBus = this->output(0)->bus(); + + // Get input and output buffers. We double-buffer both the input and output sides. + unsigned doubleBufferIndex = this->doubleBufferIndex(); + bool isDoubleBufferIndexGood = doubleBufferIndex < 2 && doubleBufferIndex < m_inputBuffers.size() && doubleBufferIndex < m_outputBuffers.size(); + ASSERT(isDoubleBufferIndexGood); + if (!isDoubleBufferIndexGood) + return; + + AudioBuffer* inputBuffer = m_inputBuffers[doubleBufferIndex].get(); + AudioBuffer* outputBuffer = m_outputBuffers[doubleBufferIndex].get(); + + // Check the consistency of input and output buffers. + bool buffersAreGood = inputBuffer && outputBuffer && bufferSize() == inputBuffer->length() && bufferSize() == outputBuffer->length() + && m_bufferReadWriteIndex + framesToProcess <= bufferSize(); + ASSERT(buffersAreGood); + if (!buffersAreGood) + return; + + // We assume that bufferSize() is evenly divisible by framesToProcess - should always be true, but we should still check. + bool isFramesToProcessGood = framesToProcess && bufferSize() >= framesToProcess && !(bufferSize() % framesToProcess); + ASSERT(isFramesToProcessGood); + if (!isFramesToProcessGood) + return; + + unsigned numberOfInputChannels = inputBus->numberOfChannels(); + + bool channelsAreGood = (numberOfInputChannels == 1 || numberOfInputChannels == 2) && outputBus->numberOfChannels() == 2; + ASSERT(channelsAreGood); + if (!channelsAreGood) + return; + + float* sourceL = inputBus->channel(0)->data(); + float* sourceR = numberOfInputChannels > 1 ? inputBus->channel(1)->data() : 0; + float* destinationL = outputBus->channel(0)->data(); + float* destinationR = outputBus->channel(1)->data(); + + // Copy from the input to the input buffer. See "buffersAreGood" check above for safety. + size_t bytesToCopy = sizeof(float) * framesToProcess; + memcpy(inputBuffer->getChannelData(0)->data() + m_bufferReadWriteIndex, sourceL, bytesToCopy); + + if (numberOfInputChannels == 2) + memcpy(inputBuffer->getChannelData(1)->data() + m_bufferReadWriteIndex, sourceR, bytesToCopy); + else if (numberOfInputChannels == 1) { + // If the input is mono, then also copy the mono input to the right channel of the AudioBuffer which the AudioProcessingEvent uses. + // FIXME: it is likely the audio API will evolve to present an AudioBuffer with the same number of channels as our input. + memcpy(inputBuffer->getChannelData(1)->data() + m_bufferReadWriteIndex, sourceL, bytesToCopy); + } + + // Copy from the output buffer to the output. See "buffersAreGood" check above for safety. + memcpy(destinationL, outputBuffer->getChannelData(0)->data() + m_bufferReadWriteIndex, bytesToCopy); + memcpy(destinationR, outputBuffer->getChannelData(1)->data() + m_bufferReadWriteIndex, bytesToCopy); + + // Update the buffering index. + m_bufferReadWriteIndex = (m_bufferReadWriteIndex + framesToProcess) % bufferSize(); + + // m_bufferReadWriteIndex will wrap back around to 0 when the current input and output buffers are full. + // When this happens, fire an event and swap buffers. + if (!m_bufferReadWriteIndex) { + // Avoid building up requests on the main thread to fire process events when they're not being handled. + // This could be a problem if the main thread is very busy doing other things and is being held up handling previous requests. + if (m_isRequestOutstanding) { + // We're late in handling the previous request. The main thread must be very busy. + // The best we can do is clear out the buffer ourself here. + outputBuffer->zero(); + } else { + // Reference ourself so we don't accidentally get deleted before fireProcessEvent() gets called. + ref(); + + // Fire the event on the main thread, not this one (which is the realtime audio thread). + m_doubleBufferIndexForEvent = m_doubleBufferIndex; + m_isRequestOutstanding = true; + callOnMainThread(fireProcessEventDispatch, this); + } + + swapBuffers(); + } +} + +void JavaScriptAudioNode::fireProcessEventDispatch(void* userData) +{ + JavaScriptAudioNode* jsAudioNode = static_cast<JavaScriptAudioNode*>(userData); + ASSERT(jsAudioNode); + if (!jsAudioNode) + return; + + jsAudioNode->fireProcessEvent(); + + // De-reference to match the ref() call in process(). + jsAudioNode->deref(); +} + +void JavaScriptAudioNode::fireProcessEvent() +{ + ASSERT(isMainThread() && m_isRequestOutstanding); + + bool isIndexGood = m_doubleBufferIndexForEvent < 2; + ASSERT(isIndexGood); + if (!isIndexGood) + return; + + AudioBuffer* inputBuffer = m_inputBuffers[m_doubleBufferIndexForEvent].get(); + AudioBuffer* outputBuffer = m_outputBuffers[m_doubleBufferIndexForEvent].get(); + ASSERT(inputBuffer && outputBuffer); + if (!inputBuffer || !outputBuffer) + return; + + // Avoid firing the event if the document has already gone away. + if (context()->hasDocument()) { + // Let the audio thread know we've gotten to the point where it's OK for it to make another request. + m_isRequestOutstanding = false; + + // Call the JavaScript event handler which will do the audio processing. + dispatchEvent(AudioProcessingEvent::create(inputBuffer, outputBuffer)); + } +} + +void JavaScriptAudioNode::reset() +{ + m_bufferReadWriteIndex = 0; + m_doubleBufferIndex = 0; + + for (unsigned i = 0; i < 2; ++i) { + m_inputBuffers[i]->zero(); + m_outputBuffers[i]->zero(); + } +} + +const AtomicString& JavaScriptAudioNode::interfaceName() const +{ + return eventNames().interfaceForJavaScriptAudioNode; +} + +ScriptExecutionContext* JavaScriptAudioNode::scriptExecutionContext() const +{ + return const_cast<JavaScriptAudioNode*>(this)->context()->document(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/JavaScriptAudioNode.h b/Source/WebCore/webaudio/JavaScriptAudioNode.h new file mode 100644 index 000000000..57435885d --- /dev/null +++ b/Source/WebCore/webaudio/JavaScriptAudioNode.h @@ -0,0 +1,105 @@ +/* + * 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. + */ + +#ifndef JavaScriptAudioNode_h +#define JavaScriptAudioNode_h + +#include "ActiveDOMObject.h" +#include "AudioNode.h" +#include "EventListener.h" +#include "EventTarget.h" +#include <wtf/Forward.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioBuffer; +class AudioContext; +class AudioProcessingEvent; + +// JavaScriptAudioNode is an AudioNode which allows for arbitrary synthesis or processing directly using JavaScript. +// The API allows for a variable number of inputs and outputs, although it must have at least one input or output. +// This basic implementation supports no more than one input and output. +// The "onaudioprocess" attribute is an event listener which will get called periodically with an AudioProcessingEvent which has +// AudioBuffers for each input and output. + +// FIXME: EventTarget should be introduced at the base of the inheritance hierarchy (i.e., as a base class for AudioNode). +class JavaScriptAudioNode : public AudioNode, public EventTarget { +public: + // bufferSize must be one of the following values: 256, 512, 1024, 2048, 4096, 8192, 16384. + // This value controls how frequently the onaudioprocess event handler is called and how many sample-frames need to be processed each call. + // Lower numbers for bufferSize will result in a lower (better) latency. Higher numbers will be necessary to avoid audio breakup and glitches. + // The value chosen must carefully balance between latency and audio quality. + static PassRefPtr<JavaScriptAudioNode> create(AudioContext*, float sampleRate, size_t bufferSize, unsigned numberOfInputs = 1, unsigned numberOfOutputs = 1); + + virtual ~JavaScriptAudioNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + virtual void initialize(); + virtual void uninitialize(); + + // EventTarget + virtual const AtomicString& interfaceName() const; + virtual ScriptExecutionContext* scriptExecutionContext() const; + virtual EventTargetData* eventTargetData() { return &m_eventTargetData; } + virtual EventTargetData* ensureEventTargetData() { return &m_eventTargetData; } + + size_t bufferSize() const { return m_bufferSize; } + + DEFINE_ATTRIBUTE_EVENT_LISTENER(audioprocess); + + // Reconcile ref/deref which are defined both in AudioNode and EventTarget. + using AudioNode::ref; + using AudioNode::deref; + +private: + JavaScriptAudioNode(AudioContext*, float sampleRate, size_t bufferSize, unsigned numberOfInputs, unsigned numberOfOutputs); + + static void fireProcessEventDispatch(void* userData); + void fireProcessEvent(); + + // Double buffering + unsigned doubleBufferIndex() const { return m_doubleBufferIndex; } + void swapBuffers() { m_doubleBufferIndex = 1 - m_doubleBufferIndex; } + unsigned m_doubleBufferIndex; + unsigned m_doubleBufferIndexForEvent; + Vector<RefPtr<AudioBuffer> > m_inputBuffers; + Vector<RefPtr<AudioBuffer> > m_outputBuffers; + + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + EventTargetData m_eventTargetData; + + size_t m_bufferSize; + unsigned m_bufferReadWriteIndex; + volatile bool m_isRequestOutstanding; +}; + +} // namespace WebCore + +#endif // JavaScriptAudioNode_h diff --git a/Source/WebCore/webaudio/JavaScriptAudioNode.idl b/Source/WebCore/webaudio/JavaScriptAudioNode.idl new file mode 100644 index 000000000..ef5359b97 --- /dev/null +++ b/Source/WebCore/webaudio/JavaScriptAudioNode.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. + * + * 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. + */ + +module audio { + // For real-time audio stream synthesis/processing in JavaScript + interface [ + Conditional=WEB_AUDIO, + GenerateToJS, + CustomMarkFunction, +#if defined(V8_BINDING) && V8_BINDING + EventTarget +#endif + ] JavaScriptAudioNode : AudioNode { + // Rendering callback + attribute EventListener onaudioprocess; + + readonly attribute long bufferSize; + }; +} diff --git a/Source/WebCore/webaudio/LowPass2FilterNode.cpp b/Source/WebCore/webaudio/LowPass2FilterNode.cpp new file mode 100644 index 000000000..e2d669acd --- /dev/null +++ b/Source/WebCore/webaudio/LowPass2FilterNode.cpp @@ -0,0 +1,45 @@ +/* + * 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 "LowPass2FilterNode.h" + +namespace WebCore { + +LowPass2FilterNode::LowPass2FilterNode(AudioContext* context, float sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new BiquadProcessor(BiquadProcessor::LowPass, sampleRate, 1, false)); + biquadProcessor()->parameter1()->setContext(context); + biquadProcessor()->parameter2()->setContext(context); + biquadProcessor()->parameter3()->setContext(context); + setNodeType(NodeTypeLowPass2Filter); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/LowPass2FilterNode.h b/Source/WebCore/webaudio/LowPass2FilterNode.h new file mode 100644 index 000000000..3342c6f96 --- /dev/null +++ b/Source/WebCore/webaudio/LowPass2FilterNode.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. + * + * 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. + */ + +#ifndef LowPass2FilterNode_h +#define LowPass2FilterNode_h + +#include "AudioBasicProcessorNode.h" +#include "BiquadProcessor.h" + +namespace WebCore { + +class AudioParam; + +class LowPass2FilterNode : public AudioBasicProcessorNode { +public: + static PassRefPtr<LowPass2FilterNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new LowPass2FilterNode(context, sampleRate)); + } + + AudioParam* cutoff() { return biquadProcessor()->parameter1(); } + AudioParam* resonance() { return biquadProcessor()->parameter2(); } + +private: + LowPass2FilterNode(AudioContext*, float sampleRate); + + BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // LowPass2FilterNode_h diff --git a/Source/WebCore/webaudio/LowPass2FilterNode.idl b/Source/WebCore/webaudio/LowPass2FilterNode.idl new file mode 100644 index 000000000..310c21e47 --- /dev/null +++ b/Source/WebCore/webaudio/LowPass2FilterNode.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. + * + * 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. + */ + +module audio { + // Two-pole lowpass filter + // FIXME: design BiquadNode and use instead of this + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] LowPass2FilterNode : AudioNode { + readonly attribute AudioParam cutoff; + readonly attribute AudioParam resonance; + }; +} diff --git a/Source/WebCore/webaudio/MediaElementAudioSourceNode.cpp b/Source/WebCore/webaudio/MediaElementAudioSourceNode.cpp new file mode 100644 index 000000000..360ec1b8b --- /dev/null +++ b/Source/WebCore/webaudio/MediaElementAudioSourceNode.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011, 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) && ENABLE(VIDEO) + +#include "MediaElementAudioSourceNode.h" + +#include "AudioContext.h" +#include "AudioNodeOutput.h" +#include "Logging.h" +#include "MediaPlayer.h" +#include <wtf/Locker.h> + +// These are somewhat arbitrary limits, but we need to do some kind of sanity-checking. +const unsigned minSampleRate = 8000; +const unsigned maxSampleRate = 192000; + +namespace WebCore { + +PassRefPtr<MediaElementAudioSourceNode> MediaElementAudioSourceNode::create(AudioContext* context, HTMLMediaElement* mediaElement) +{ + return adoptRef(new MediaElementAudioSourceNode(context, mediaElement)); +} + +MediaElementAudioSourceNode::MediaElementAudioSourceNode(AudioContext* context, HTMLMediaElement* mediaElement) + : AudioSourceNode(context, context->sampleRate()) + , m_mediaElement(mediaElement) + , m_sourceNumberOfChannels(0) + , m_sourceSampleRate(0) +{ + // Default to stereo. This could change depending on what the media element .src is set to. + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setNodeType(NodeTypeMediaElementAudioSource); + + initialize(); +} + +MediaElementAudioSourceNode::~MediaElementAudioSourceNode() +{ + m_mediaElement->setAudioSourceNode(0); + uninitialize(); +} + +void MediaElementAudioSourceNode::setFormat(size_t numberOfChannels, float sourceSampleRate) +{ + if (numberOfChannels != m_sourceNumberOfChannels || sourceSampleRate != m_sourceSampleRate) { + // FIXME: implement multi-channel greater than stereo. + // https://bugs.webkit.org/show_bug.cgi?id=75119 + if (!numberOfChannels || numberOfChannels > 2 || sourceSampleRate < minSampleRate || sourceSampleRate > maxSampleRate) { + // process() will generate silence for these uninitialized values. + LOG(Media, "MediaElementAudioSourceNode::setFormat(%u, %f) - unhandled format change", static_cast<unsigned>(numberOfChannels), sourceSampleRate); + m_sourceNumberOfChannels = 0; + m_sourceSampleRate = 0; + return; + } + + m_sourceNumberOfChannels = numberOfChannels; + m_sourceSampleRate = sourceSampleRate; + + // Synchronize with process(). + Locker<MediaElementAudioSourceNode> locker(*this); + + if (sourceSampleRate != sampleRate()) { + double scaleFactor = sourceSampleRate / sampleRate(); + m_multiChannelResampler = adoptPtr(new MultiChannelResampler(scaleFactor, numberOfChannels)); + } else { + // Bypass resampling. + m_multiChannelResampler.clear(); + } + + { + // The context must be locked when changing the number of output channels. + AudioContext::AutoLocker contextLocker(context()); + + // Do any necesssary re-configuration to the output's number of channels. + output(0)->setNumberOfChannels(numberOfChannels); + } + } +} + +void MediaElementAudioSourceNode::process(size_t numberOfFrames) +{ + AudioBus* outputBus = output(0)->bus(); + + if (!mediaElement() || !m_sourceNumberOfChannels || !m_sourceSampleRate) { + outputBus->zero(); + return; + } + + // Use a tryLock() to avoid contention in the real-time audio thread. + // If we fail to acquire the lock then the HTMLMediaElement must be in the middle of + // reconfiguring its playback engine, so we output silence in this case. + if (m_processLock.tryLock()) { + if (AudioSourceProvider* provider = mediaElement()->audioSourceProvider()) { + if (m_multiChannelResampler.get()) { + ASSERT(m_sourceSampleRate != sampleRate()); + m_multiChannelResampler->process(provider, outputBus, numberOfFrames); + } else { + // Bypass the resampler completely if the source is at the context's sample-rate. + ASSERT(m_sourceSampleRate == sampleRate()); + provider->provideInput(outputBus, numberOfFrames); + } + } else { + // Either this port doesn't yet support HTMLMediaElement audio stream access, + // or the stream is not yet available. + outputBus->zero(); + } + m_processLock.unlock(); + } else { + // We failed to acquire the lock. + outputBus->zero(); + } +} + +void MediaElementAudioSourceNode::reset() +{ +} + +void MediaElementAudioSourceNode::lock() +{ + ref(); + m_processLock.lock(); +} + +void MediaElementAudioSourceNode::unlock() +{ + m_processLock.unlock(); + deref(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/MediaElementAudioSourceNode.h b/Source/WebCore/webaudio/MediaElementAudioSourceNode.h new file mode 100644 index 000000000..06ec7e16b --- /dev/null +++ b/Source/WebCore/webaudio/MediaElementAudioSourceNode.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef MediaElementAudioSourceNode_h +#define MediaElementAudioSourceNode_h + +#if ENABLE(VIDEO) + +#include "AudioSourceNode.h" +#include "AudioSourceProviderClient.h" +#include "HTMLMediaElement.h" +#include "MultiChannelResampler.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioContext; + +class MediaElementAudioSourceNode : public AudioSourceNode, public AudioSourceProviderClient { +public: + static PassRefPtr<MediaElementAudioSourceNode> create(AudioContext*, HTMLMediaElement*); + + virtual ~MediaElementAudioSourceNode(); + + HTMLMediaElement* mediaElement() { return m_mediaElement.get(); } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + + // AudioSourceProviderClient + virtual void setFormat(size_t numberOfChannels, float sampleRate); + + void lock(); + void unlock(); + +private: + MediaElementAudioSourceNode(AudioContext*, HTMLMediaElement*); + + RefPtr<HTMLMediaElement> m_mediaElement; + Mutex m_processLock; + + unsigned m_sourceNumberOfChannels; + double m_sourceSampleRate; + + OwnPtr<MultiChannelResampler> m_multiChannelResampler; +}; + +} // namespace WebCore + +#endif // ENABLE(VIDEO) + +#endif // MediaElementAudioSourceNode_h diff --git a/Source/WebCore/webaudio/MediaElementAudioSourceNode.idl b/Source/WebCore/webaudio/MediaElementAudioSourceNode.idl new file mode 100644 index 000000000..27c67d3a3 --- /dev/null +++ b/Source/WebCore/webaudio/MediaElementAudioSourceNode.idl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO&VIDEO, + GenerateToJS + ] MediaElementAudioSourceNode : AudioSourceNode { + readonly attribute HTMLMediaElement mediaElement; + }; +} diff --git a/Source/WebCore/webaudio/OfflineAudioCompletionEvent.cpp b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.cpp new file mode 100644 index 000000000..7f8243f40 --- /dev/null +++ b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2011, 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 "OfflineAudioCompletionEvent.h" + +#include "AudioBuffer.h" +#include "EventNames.h" + +namespace WebCore { + +PassRefPtr<OfflineAudioCompletionEvent> OfflineAudioCompletionEvent::create() +{ + return adoptRef(new OfflineAudioCompletionEvent); +} + +PassRefPtr<OfflineAudioCompletionEvent> OfflineAudioCompletionEvent::create(PassRefPtr<AudioBuffer> renderedBuffer) +{ + return adoptRef(new OfflineAudioCompletionEvent(renderedBuffer)); +} + +OfflineAudioCompletionEvent::OfflineAudioCompletionEvent() +{ +} + + +OfflineAudioCompletionEvent::OfflineAudioCompletionEvent(PassRefPtr<AudioBuffer> renderedBuffer) + : Event(eventNames().completeEvent, true, false) + , m_renderedBuffer(renderedBuffer) +{ +} + +OfflineAudioCompletionEvent::~OfflineAudioCompletionEvent() +{ +} + +const AtomicString& OfflineAudioCompletionEvent::interfaceName() const +{ + return eventNames().interfaceForOfflineAudioCompletionEvent; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/OfflineAudioCompletionEvent.h b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.h new file mode 100644 index 000000000..409aeaf67 --- /dev/null +++ b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef OfflineAudioCompletionEvent_h +#define OfflineAudioCompletionEvent_h + +#include "AudioBuffer.h" +#include "Event.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> + +namespace WebCore { + +class AudioBuffer; + +class OfflineAudioCompletionEvent : public Event { +public: + static PassRefPtr<OfflineAudioCompletionEvent> create(); + static PassRefPtr<OfflineAudioCompletionEvent> create(PassRefPtr<AudioBuffer> renderedBuffer); + + virtual ~OfflineAudioCompletionEvent(); + + AudioBuffer* renderedBuffer() { return m_renderedBuffer.get(); } + + virtual const AtomicString& interfaceName() const; + +private: + OfflineAudioCompletionEvent(); + OfflineAudioCompletionEvent(PassRefPtr<AudioBuffer> renderedBuffer); + + RefPtr<AudioBuffer> m_renderedBuffer; +}; + +} // namespace WebCore + +#endif // OfflineAudioCompletionEvent_h diff --git a/Source/WebCore/webaudio/OfflineAudioCompletionEvent.idl b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.idl new file mode 100644 index 000000000..cd5ccb091 --- /dev/null +++ b/Source/WebCore/webaudio/OfflineAudioCompletionEvent.idl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] OfflineAudioCompletionEvent : Event { + readonly attribute AudioBuffer renderedBuffer; + }; +} diff --git a/Source/WebCore/webaudio/OfflineAudioDestinationNode.cpp b/Source/WebCore/webaudio/OfflineAudioDestinationNode.cpp new file mode 100644 index 000000000..ae1835b9a --- /dev/null +++ b/Source/WebCore/webaudio/OfflineAudioDestinationNode.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2011, 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 "OfflineAudioDestinationNode.h" + +#include "AudioBus.h" +#include "AudioContext.h" +#include "HRTFDatabaseLoader.h" +#include <algorithm> +#include <wtf/MainThread.h> + +using namespace std; + +namespace WebCore { + +const size_t renderQuantumSize = 128; + +OfflineAudioDestinationNode::OfflineAudioDestinationNode(AudioContext* context, AudioBuffer* renderTarget) + : AudioDestinationNode(context, renderTarget->sampleRate()) + , m_renderTarget(renderTarget) + , m_renderThread(0) + , m_startedRendering(false) +{ + m_renderBus = adoptPtr(new AudioBus(renderTarget->numberOfChannels(), renderQuantumSize)); + + initialize(); +} + +OfflineAudioDestinationNode::~OfflineAudioDestinationNode() +{ + uninitialize(); +} + +void OfflineAudioDestinationNode::initialize() +{ + if (isInitialized()) + return; + + AudioNode::initialize(); +} + +void OfflineAudioDestinationNode::uninitialize() +{ + if (!isInitialized()) + return; + + if (m_renderThread) { + waitForThreadCompletion(m_renderThread, 0); + m_renderThread = 0; + } + + AudioNode::uninitialize(); +} + +void OfflineAudioDestinationNode::startRendering() +{ + ASSERT(isMainThread()); + ASSERT(m_renderTarget.get()); + if (!m_renderTarget.get()) + return; + + if (!m_startedRendering) { + m_startedRendering = true; + ref(); // See corresponding deref() call in notifyCompleteDispatch(). + m_renderThread = createThread(OfflineAudioDestinationNode::renderEntry, this, "offline renderer"); + } +} + +// Do offline rendering in this thread. +void* OfflineAudioDestinationNode::renderEntry(void* threadData) +{ + OfflineAudioDestinationNode* destinationNode = reinterpret_cast<OfflineAudioDestinationNode*>(threadData); + ASSERT(destinationNode); + destinationNode->render(); + + return 0; +} + +void OfflineAudioDestinationNode::render() +{ + ASSERT(!isMainThread()); + ASSERT(m_renderBus.get()); + if (!m_renderBus.get()) + return; + + bool channelsMatch = m_renderBus->numberOfChannels() == m_renderTarget->numberOfChannels(); + ASSERT(channelsMatch); + if (!channelsMatch) + return; + + bool isRenderBusAllocated = m_renderBus->length() >= renderQuantumSize; + ASSERT(isRenderBusAllocated); + if (!isRenderBusAllocated) + return; + + // Synchronize with HRTFDatabaseLoader. + // The database must be loaded before we can proceed. + HRTFDatabaseLoader* loader = HRTFDatabaseLoader::loader(); + ASSERT(loader); + if (!loader) + return; + + loader->waitForLoaderThreadCompletion(); + + // Break up the render target into smaller "render quantize" sized pieces. + // Render until we're finished. + size_t framesToProcess = m_renderTarget->length(); + unsigned numberOfChannels = m_renderTarget->numberOfChannels(); + + unsigned n = 0; + while (framesToProcess > 0) { + // Render one render quantum. + provideInput(m_renderBus.get(), renderQuantumSize); + + size_t framesAvailableToCopy = min(framesToProcess, renderQuantumSize); + + for (unsigned channelIndex = 0; channelIndex < numberOfChannels; ++channelIndex) { + float* source = m_renderBus->channel(channelIndex)->data(); + float* destination = m_renderTarget->getChannelData(channelIndex)->data(); + memcpy(destination + n, source, sizeof(float) * framesAvailableToCopy); + } + + n += framesAvailableToCopy; + framesToProcess -= framesAvailableToCopy; + } + + // Our work is done. Let the AudioContext know. + callOnMainThread(notifyCompleteDispatch, this); +} + +void OfflineAudioDestinationNode::notifyCompleteDispatch(void* userData) +{ + OfflineAudioDestinationNode* destinationNode = static_cast<OfflineAudioDestinationNode*>(userData); + ASSERT(destinationNode); + if (!destinationNode) + return; + + destinationNode->notifyComplete(); + destinationNode->deref(); +} + +void OfflineAudioDestinationNode::notifyComplete() +{ + context()->fireCompletionEvent(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/OfflineAudioDestinationNode.h b/Source/WebCore/webaudio/OfflineAudioDestinationNode.h new file mode 100644 index 000000000..c4d567a5d --- /dev/null +++ b/Source/WebCore/webaudio/OfflineAudioDestinationNode.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef OfflineAudioDestinationNode_h +#define OfflineAudioDestinationNode_h + +#include "AudioBuffer.h" +#include "AudioDestinationNode.h" +#include <wtf/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class AudioBus; +class AudioContext; + +class OfflineAudioDestinationNode : public AudioDestinationNode { +public: + static PassRefPtr<OfflineAudioDestinationNode> create(AudioContext* context, AudioBuffer* renderTarget) + { + return adoptRef(new OfflineAudioDestinationNode(context, renderTarget)); + } + + virtual ~OfflineAudioDestinationNode(); + + // AudioNode + virtual void initialize(); + virtual void uninitialize(); + + float sampleRate() const { return m_renderTarget->sampleRate(); } + + void startRendering(); + +private: + OfflineAudioDestinationNode(AudioContext*, AudioBuffer* renderTarget); + + // This AudioNode renders into this AudioBuffer. + RefPtr<AudioBuffer> m_renderTarget; + + // Temporary AudioBus for each render quantum. + OwnPtr<AudioBus> m_renderBus; + + // Rendering thread. + volatile ThreadIdentifier m_renderThread; + bool m_startedRendering; + static void* renderEntry(void* threadData); + void render(); + + // For completion callback on main thread. + static void notifyCompleteDispatch(void* userData); + void notifyComplete(); +}; + +} // namespace WebCore + +#endif // OfflineAudioDestinationNode_h diff --git a/Source/WebCore/webaudio/RealtimeAnalyser.cpp b/Source/WebCore/webaudio/RealtimeAnalyser.cpp new file mode 100644 index 000000000..ccd865522 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyser.cpp @@ -0,0 +1,299 @@ +/* + * 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 "RealtimeAnalyser.h" + +#include "AudioBus.h" +#include "AudioUtilities.h" +#include "FFTFrame.h" + +#include <algorithm> +#include <limits.h> +#include <wtf/Complex.h> +#include <wtf/Float32Array.h> +#include <wtf/MainThread.h> +#include <wtf/MathExtras.h> +#include <wtf/Uint8Array.h> + +using namespace std; + +namespace WebCore { + +const double RealtimeAnalyser::DefaultSmoothingTimeConstant = 0.8; +const double RealtimeAnalyser::DefaultMinDecibels = -100.0; +const double RealtimeAnalyser::DefaultMaxDecibels = -30.0; + +const unsigned RealtimeAnalyser::DefaultFFTSize = 2048; +// All FFT implementations are expected to handle power-of-two sizes MinFFTSize <= size <= MaxFFTSize. +const unsigned RealtimeAnalyser::MinFFTSize = 128; +const unsigned RealtimeAnalyser::MaxFFTSize = 2048; +const unsigned RealtimeAnalyser::InputBufferSize = RealtimeAnalyser::MaxFFTSize * 2; + +RealtimeAnalyser::RealtimeAnalyser() + : m_inputBuffer(InputBufferSize) + , m_writeIndex(0) + , m_fftSize(DefaultFFTSize) + , m_magnitudeBuffer(DefaultFFTSize / 2) + , m_smoothingTimeConstant(DefaultSmoothingTimeConstant) + , m_minDecibels(DefaultMinDecibels) + , m_maxDecibels(DefaultMaxDecibels) +{ + m_analysisFrame = adoptPtr(new FFTFrame(DefaultFFTSize)); +} + +RealtimeAnalyser::~RealtimeAnalyser() +{ +} + +void RealtimeAnalyser::reset() +{ + m_writeIndex = 0; + m_inputBuffer.zero(); + m_magnitudeBuffer.zero(); +} + +void RealtimeAnalyser::setFftSize(size_t size) +{ + ASSERT(isMainThread()); + + // Only allow powers of two. + unsigned log2size = static_cast<unsigned>(log2(size)); + bool isPOT(1UL << log2size == size); + + if (!isPOT || size > MaxFFTSize || size < MinFFTSize) { + // FIXME: It would be good to also set an exception. + return; + } + + if (m_fftSize != size) { + m_analysisFrame = adoptPtr(new FFTFrame(size)); + // m_magnitudeBuffer has size = fftSize / 2 because it contains floats reduced from complex values in m_analysisFrame. + m_magnitudeBuffer.allocate(size / 2); + m_fftSize = size; + } +} + +void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess) +{ + bool isBusGood = bus && bus->numberOfChannels() > 0 && bus->channel(0)->length() >= framesToProcess; + ASSERT(isBusGood); + if (!isBusGood) + return; + + // FIXME : allow to work with non-FFTSize divisible chunking + bool isDestinationGood = m_writeIndex < m_inputBuffer.size() && m_writeIndex + framesToProcess <= m_inputBuffer.size(); + ASSERT(isDestinationGood); + if (!isDestinationGood) + return; + + // Perform real-time analysis + // FIXME : for now just use left channel (must mix if stereo source) + float* source = bus->channel(0)->data(); + + // The source has already been sanity checked with isBusGood above. + + memcpy(m_inputBuffer.data() + m_writeIndex, source, sizeof(float) * framesToProcess); + + m_writeIndex += framesToProcess; + if (m_writeIndex >= InputBufferSize) + m_writeIndex = 0; +} + +namespace { + +void applyWindow(float* p, size_t n) +{ + ASSERT(isMainThread()); + + // Blackman window + double alpha = 0.16; + double a0 = 0.5 * (1.0 - alpha); + double a1 = 0.5; + double a2 = 0.5 * alpha; + + for (unsigned i = 0; i < n; ++i) { + double x = static_cast<double>(i) / static_cast<double>(n); + double window = a0 - a1 * cos(2.0 * piDouble * x) + a2 * cos(4.0 * piDouble * x); + p[i] *= float(window); + } +} + +} // namespace + +void RealtimeAnalyser::doFFTAnalysis() +{ + ASSERT(isMainThread()); + + // Unroll the input buffer into a temporary buffer, where we'll apply an analysis window followed by an FFT. + size_t fftSize = this->fftSize(); + + AudioFloatArray temporaryBuffer(fftSize); + float* inputBuffer = m_inputBuffer.data(); + float* tempP = temporaryBuffer.data(); + + // Take the previous fftSize values from the input buffer and copy into the temporary buffer. + unsigned writeIndex = m_writeIndex; + if (writeIndex < fftSize) { + memcpy(tempP, inputBuffer + writeIndex - fftSize + InputBufferSize, sizeof(*tempP) * (fftSize - writeIndex)); + memcpy(tempP + fftSize - writeIndex, inputBuffer, sizeof(*tempP) * writeIndex); + } else + memcpy(tempP, inputBuffer + writeIndex - fftSize, sizeof(*tempP) * fftSize); + + + // Window the input samples. + applyWindow(tempP, fftSize); + + // Do the analysis. + m_analysisFrame->doFFT(tempP); + + float* realP = m_analysisFrame->realData(); + float* imagP = m_analysisFrame->imagData(); + + // Blow away the packed nyquist component. + imagP[0] = 0.0f; + + // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor). + const double MagnitudeScale = 1.0 / DefaultFFTSize; + + // A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes. + double k = m_smoothingTimeConstant; + k = max(0.0, k); + k = min(1.0, k); + + // Convert the analysis data from complex to magnitude and average with the previous result. + float* destination = magnitudeBuffer().data(); + size_t n = magnitudeBuffer().size(); + for (size_t i = 0; i < n; ++i) { + Complex c(realP[i], imagP[i]); + double scalarMagnitude = abs(c) * MagnitudeScale; + destination[i] = float(k * destination[i] + (1.0 - k) * scalarMagnitude); + } +} + +void RealtimeAnalyser::getFloatFrequencyData(Float32Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + doFFTAnalysis(); + + // Convert from linear magnitude to floating-point decibels. + const double MinDecibels = m_minDecibels; + unsigned sourceLength = magnitudeBuffer().size(); + size_t len = min(sourceLength, destinationArray->length()); + if (len > 0) { + const float* source = magnitudeBuffer().data(); + float* destination = destinationArray->data(); + + for (unsigned i = 0; i < len; ++i) { + float linearValue = source[i]; + double dbMag = !linearValue ? MinDecibels : AudioUtilities::linearToDecibels(linearValue); + destination[i] = float(dbMag); + } + } +} + +void RealtimeAnalyser::getByteFrequencyData(Uint8Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + doFFTAnalysis(); + + // Convert from linear magnitude to unsigned-byte decibels. + unsigned sourceLength = magnitudeBuffer().size(); + size_t len = min(sourceLength, destinationArray->length()); + if (len > 0) { + const double RangeScaleFactor = m_maxDecibels == m_minDecibels ? 1.0 : 1.0 / (m_maxDecibels - m_minDecibels); + + const float* source = magnitudeBuffer().data(); + unsigned char* destination = destinationArray->data(); + + for (unsigned i = 0; i < len; ++i) { + float linearValue = source[i]; + double dbMag = !linearValue ? m_minDecibels : AudioUtilities::linearToDecibels(linearValue); + + // The range m_minDecibels to m_maxDecibels will be scaled to byte values from 0 to UCHAR_MAX. + double scaledValue = UCHAR_MAX * (dbMag - m_minDecibels) * RangeScaleFactor; + + // Clip to valid range. + if (scaledValue < 0.0) + scaledValue = 0.0; + if (scaledValue > UCHAR_MAX) + scaledValue = UCHAR_MAX; + + destination[i] = static_cast<unsigned char>(scaledValue); + } + } +} + +void RealtimeAnalyser::getByteTimeDomainData(Uint8Array* destinationArray) +{ + ASSERT(isMainThread()); + + if (!destinationArray) + return; + + unsigned fftSize = this->fftSize(); + size_t len = min(fftSize, destinationArray->length()); + if (len > 0) { + bool isInputBufferGood = m_inputBuffer.size() == InputBufferSize && m_inputBuffer.size() > fftSize; + ASSERT(isInputBufferGood); + if (!isInputBufferGood) + return; + + float* inputBuffer = m_inputBuffer.data(); + unsigned char* destination = destinationArray->data(); + + unsigned writeIndex = m_writeIndex; + + for (unsigned i = 0; i < len; ++i) { + // Buffer access is protected due to modulo operation. + float value = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize]; + + // Scale from nominal -1.0 -> +1.0 to unsigned byte. + double scaledValue = 128.0 * (value + 1.0); + + // Clip to valid range. + if (scaledValue < 0.0) + scaledValue = 0.0; + if (scaledValue > UCHAR_MAX) + scaledValue = UCHAR_MAX; + + destination[i] = static_cast<unsigned char>(scaledValue); + } + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/RealtimeAnalyser.h b/Source/WebCore/webaudio/RealtimeAnalyser.h new file mode 100644 index 000000000..242eacaf0 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyser.h @@ -0,0 +1,99 @@ +/* + * 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. + */ + +#ifndef RealtimeAnalyser_h +#define RealtimeAnalyser_h + +#include "AudioArray.h" +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class AudioBus; +class FFTFrame; + +class RealtimeAnalyser { + WTF_MAKE_NONCOPYABLE(RealtimeAnalyser); +public: + RealtimeAnalyser(); + virtual ~RealtimeAnalyser(); + + void reset(); + + size_t fftSize() const { return m_fftSize; } + void setFftSize(size_t size); + + unsigned frequencyBinCount() const { return m_fftSize / 2; } + + void setMinDecibels(float k) { m_minDecibels = k; } + float minDecibels() const { return static_cast<float>(m_minDecibels); } + + void setMaxDecibels(float k) { m_maxDecibels = k; } + float maxDecibels() const { return static_cast<float>(m_maxDecibels); } + + void setSmoothingTimeConstant(float k) { m_smoothingTimeConstant = k; } + float smoothingTimeConstant() const { return static_cast<float>(m_smoothingTimeConstant); } + + void getFloatFrequencyData(Float32Array*); + void getByteFrequencyData(Uint8Array*); + void getByteTimeDomainData(Uint8Array*); + + // The audio thread writes input data here. + void writeInput(AudioBus*, size_t framesToProcess); + + static const double DefaultSmoothingTimeConstant; + static const double DefaultMinDecibels; + static const double DefaultMaxDecibels; + + static const unsigned DefaultFFTSize; + static const unsigned MinFFTSize; + static const unsigned MaxFFTSize; + static const unsigned InputBufferSize; + +private: + // The audio thread writes the input audio here. + AudioFloatArray m_inputBuffer; + unsigned m_writeIndex; + + size_t m_fftSize; + OwnPtr<FFTFrame> m_analysisFrame; + void doFFTAnalysis(); + + // doFFTAnalysis() stores the floating-point magnitude analysis data here. + AudioFloatArray m_magnitudeBuffer; + AudioFloatArray& magnitudeBuffer() { return m_magnitudeBuffer; } + + // A value between 0 and 1 which averages the previous version of m_magnitudeBuffer with the current analysis magnitude data. + double m_smoothingTimeConstant; + + // The range used when converting when using getByteFrequencyData(). + double m_minDecibels; + double m_maxDecibels; +}; + +} // namespace WebCore + +#endif // RealtimeAnalyser_h diff --git a/Source/WebCore/webaudio/RealtimeAnalyserNode.cpp b/Source/WebCore/webaudio/RealtimeAnalyserNode.cpp new file mode 100644 index 000000000..8d74a67a8 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyserNode.cpp @@ -0,0 +1,88 @@ +/* + * 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 "RealtimeAnalyserNode.h" + +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" + +namespace WebCore { + +RealtimeAnalyserNode::RealtimeAnalyserNode(AudioContext* context, float sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setNodeType(NodeTypeAnalyser); + + initialize(); +} + +RealtimeAnalyserNode::~RealtimeAnalyserNode() +{ + uninitialize(); +} + +void RealtimeAnalyserNode::process(size_t framesToProcess) +{ + AudioBus* outputBus = output(0)->bus(); + + if (!isInitialized() || !input(0)->isConnected()) { + outputBus->zero(); + return; + } + + AudioBus* inputBus = input(0)->bus(); + + // Give the analyser the audio which is passing through this AudioNode. + m_analyser.writeInput(inputBus, framesToProcess); + + // For in-place processing, our override of pullInputs() will just pass the audio data through unchanged if the channel count matches from input to output + // (resulting in inputBus == outputBus). Otherwise, do an up-mix to stereo. + if (inputBus != outputBus) + outputBus->copyFrom(*inputBus); +} + +// We override pullInputs() as an optimization allowing this node to take advantage of in-place processing, +// where the input is simply passed through unprocessed to the output. +// Note: this only applies if the input and output channel counts match. +void RealtimeAnalyserNode::pullInputs(size_t framesToProcess) +{ + // Render input stream - try to render directly into output bus for pass-through processing where process() doesn't need to do anything... + input(0)->pull(output(0)->bus(), framesToProcess); +} + +void RealtimeAnalyserNode::reset() +{ + m_analyser.reset(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/RealtimeAnalyserNode.h b/Source/WebCore/webaudio/RealtimeAnalyserNode.h new file mode 100644 index 000000000..c37c6c1b5 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyserNode.h @@ -0,0 +1,75 @@ +/* + * 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. + */ + +#ifndef RealtimeAnalyserNode_h +#define RealtimeAnalyserNode_h + +#include "AudioNode.h" +#include "RealtimeAnalyser.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class RealtimeAnalyserNode : public AudioNode { +public: + static PassRefPtr<RealtimeAnalyserNode> create(AudioContext* context, float sampleRate) + { + return adoptRef(new RealtimeAnalyserNode(context, sampleRate)); + } + + virtual ~RealtimeAnalyserNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void pullInputs(size_t framesToProcess); + virtual void reset(); + + // Javascript bindings + unsigned int fftSize() const { return m_analyser.fftSize(); } + void setFftSize(unsigned int size) { m_analyser.setFftSize(size); } + + unsigned frequencyBinCount() const { return m_analyser.frequencyBinCount(); } + + void setMinDecibels(float k) { m_analyser.setMinDecibels(k); } + float minDecibels() const { return m_analyser.minDecibels(); } + + void setMaxDecibels(float k) { m_analyser.setMaxDecibels(k); } + float maxDecibels() const { return m_analyser.maxDecibels(); } + + void setSmoothingTimeConstant(float k) { m_analyser.setSmoothingTimeConstant(k); } + float smoothingTimeConstant() const { return m_analyser.smoothingTimeConstant(); } + + void getFloatFrequencyData(Float32Array* array) { m_analyser.getFloatFrequencyData(array); } + void getByteFrequencyData(Uint8Array* array) { m_analyser.getByteFrequencyData(array); } + void getByteTimeDomainData(Uint8Array* array) { m_analyser.getByteTimeDomainData(array); } + +private: + RealtimeAnalyserNode(AudioContext*, float sampleRate); + + RealtimeAnalyser m_analyser; +}; + +} // namespace WebCore + +#endif // RealtimeAnalyserNode_h diff --git a/Source/WebCore/webaudio/RealtimeAnalyserNode.idl b/Source/WebCore/webaudio/RealtimeAnalyserNode.idl new file mode 100644 index 000000000..1f7bf7a7d --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyserNode.idl @@ -0,0 +1,48 @@ +/* + * 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] RealtimeAnalyserNode : AudioNode { + attribute unsigned long fftSize; + readonly attribute unsigned long frequencyBinCount; + + // minDecibels / maxDecibels represent the range to scale the FFT analysis data for conversion to unsigned byte values. + attribute float minDecibels; + attribute float maxDecibels; + + // A value from 0.0 -> 1.0 where 0.0 represents no time averaging with the last analysis frame. + attribute float smoothingTimeConstant; + + // Copies the current frequency data into the passed array. + // If the array has fewer elements than the frequencyBinCount, the excess elements will be dropped. + void getFloatFrequencyData(in Float32Array array); + void getByteFrequencyData(in Uint8Array array); + + // Real-time waveform data + void getByteTimeDomainData(in Uint8Array array); + }; +} diff --git a/Source/WebCore/webaudio/WaveShaperDSPKernel.cpp b/Source/WebCore/webaudio/WaveShaperDSPKernel.cpp new file mode 100644 index 000000000..5f3cf3476 --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperDSPKernel.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011, 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 "WaveShaperDSPKernel.h" + +#include "WaveShaperProcessor.h" +#include <algorithm> + +using namespace std; + +namespace WebCore { + +void WaveShaperDSPKernel::process(const float* source, float* destination, size_t framesToProcess) +{ + ASSERT(source && destination && waveShaperProcessor()); + + Float32Array* curve = waveShaperProcessor()->curve(); + if (!curve) { + // Act as "straight wire" pass-through if no curve is set. + memcpy(destination, source, sizeof(float) * framesToProcess); + return; + } + + float* curveData = curve->data(); + int curveLength = curve->length(); + + ASSERT(curveData); + + if (!curveData || !curveLength) { + memcpy(destination, source, sizeof(float) * framesToProcess); + return; + } + + // Apply waveshaping curve. + for (unsigned i = 0; i < framesToProcess; ++i) { + const float input = source[i]; + + // Calculate an index based on input -1 -> +1 with 0 being at the center of the curve data. + int index = (curveLength * (input + 1)) / 2; + + // Clip index to the input range of the curve. + // This takes care of input outside of nominal range -1 -> +1 + index = max(index, 0); + index = min(index, curveLength - 1); + destination[i] = curveData[index]; + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/WaveShaperDSPKernel.h b/Source/WebCore/webaudio/WaveShaperDSPKernel.h new file mode 100644 index 000000000..c725f4dfe --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperDSPKernel.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef WaveShaperDSPKernel_h +#define WaveShaperDSPKernel_h + +#include "AudioDSPKernel.h" +#include "WaveShaperProcessor.h" + +namespace WebCore { + +class WaveShaperProcessor; + +// WaveShaperDSPKernel is an AudioDSPKernel and is responsible for non-linear distortion on one channel. + +class WaveShaperDSPKernel : public AudioDSPKernel { +public: + WaveShaperDSPKernel(WaveShaperProcessor* processor) + : AudioDSPKernel(processor) + { + } + + // AudioDSPKernel + virtual void process(const float* source, float* dest, size_t framesToProcess); + virtual void reset() { } + +protected: + WaveShaperProcessor* waveShaperProcessor() { return static_cast<WaveShaperProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // WaveShaperDSPKernel_h diff --git a/Source/WebCore/webaudio/WaveShaperNode.cpp b/Source/WebCore/webaudio/WaveShaperNode.cpp new file mode 100644 index 000000000..03504e777 --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperNode.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011, 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 "WaveShaperNode.h" +#include <wtf/MainThread.h> + +namespace WebCore { + +WaveShaperNode::WaveShaperNode(AudioContext* context) + : AudioBasicProcessorNode(context, context->sampleRate()) +{ + m_processor = adoptPtr(new WaveShaperProcessor(context->sampleRate(), 1)); + setNodeType(NodeTypeWaveShaper); +} + +void WaveShaperNode::setCurve(Float32Array* curve) +{ + ASSERT(isMainThread()); + waveShaperProcessor()->setCurve(curve); +} + +Float32Array* WaveShaperNode::curve() +{ + return waveShaperProcessor()->curve(); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/WaveShaperNode.h b/Source/WebCore/webaudio/WaveShaperNode.h new file mode 100644 index 000000000..cb1ccf603 --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperNode.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef WaveShaperNode_h +#define WaveShaperNode_h + +#include "AudioBasicProcessorNode.h" +#include "BiquadProcessor.h" +#include "WaveShaperProcessor.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class WaveShaperNode : public AudioBasicProcessorNode { +public: + static PassRefPtr<WaveShaperNode> create(AudioContext* context) + { + return adoptRef(new WaveShaperNode(context)); + } + + // setCurve() is called on the main thread. + void setCurve(Float32Array*); + Float32Array* curve(); + +private: + WaveShaperNode(AudioContext*); + + WaveShaperProcessor* waveShaperProcessor() { return static_cast<WaveShaperProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // WaveShaperNode_h diff --git a/Source/WebCore/webaudio/WaveShaperNode.idl b/Source/WebCore/webaudio/WaveShaperNode.idl new file mode 100644 index 000000000..19c61a6d0 --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperNode.idl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011, 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. + */ + +module audio { + interface [ + Conditional=WEB_AUDIO, + GenerateToJS + ] WaveShaperNode : AudioNode { + attribute [JSCCustomSetter] Float32Array curve; + }; +} diff --git a/Source/WebCore/webaudio/WaveShaperProcessor.cpp b/Source/WebCore/webaudio/WaveShaperProcessor.cpp new file mode 100644 index 000000000..f7571debb --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperProcessor.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011, 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 "WaveShaperProcessor.h" + +#include "WaveShaperDSPKernel.h" + +namespace WebCore { + +WaveShaperProcessor::WaveShaperProcessor(float sampleRate, size_t numberOfChannels) + : AudioDSPKernelProcessor(sampleRate, numberOfChannels) +{ +} + +WaveShaperProcessor::~WaveShaperProcessor() +{ + if (isInitialized()) + uninitialize(); +} + +PassOwnPtr<AudioDSPKernel> WaveShaperProcessor::createKernel() +{ + return adoptPtr(new WaveShaperDSPKernel(this)); +} + +void WaveShaperProcessor::setCurve(Float32Array* curve) +{ + // This synchronizes with process(). + MutexLocker processLocker(m_processLock); + + m_curve = curve; +} + +void WaveShaperProcessor::process(AudioBus* source, AudioBus* destination, size_t framesToProcess) +{ + if (!isInitialized()) { + destination->zero(); + return; + } + + // The audio thread can't block on this lock, so we call tryLock() instead. + // Careful - this is a tryLock() and not an autolocker, so we must unlock() before every return. + if (m_processLock.tryLock()) { + // For each channel of our input, process using the corresponding WaveShaperDSPKernel into the output channel. + for (unsigned i = 0; i < m_kernels.size(); ++i) + m_kernels[i]->process(source->channel(i)->data(), destination->channel(i)->data(), framesToProcess); + + m_processLock.unlock(); + } else { + // Too bad - the tryLock() failed. We must be in the middle of a setCurve() call. + destination->zero(); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/WaveShaperProcessor.h b/Source/WebCore/webaudio/WaveShaperProcessor.h new file mode 100644 index 000000000..4016e8bde --- /dev/null +++ b/Source/WebCore/webaudio/WaveShaperProcessor.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011, 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. + */ + +#ifndef WaveShaperProcessor_h +#define WaveShaperProcessor_h + +#include "AudioDSPKernel.h" +#include "AudioDSPKernelProcessor.h" +#include "AudioNode.h" +#include <wtf/Float32Array.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +// WaveShaperProcessor is an AudioDSPKernelProcessor which uses WaveShaperDSPKernel objects to implement non-linear distortion effects. + +class WaveShaperProcessor : public AudioDSPKernelProcessor { +public: + WaveShaperProcessor(float sampleRate, size_t numberOfChannels); + + virtual ~WaveShaperProcessor(); + + virtual PassOwnPtr<AudioDSPKernel> createKernel(); + + virtual void process(AudioBus* source, AudioBus* destination, size_t framesToProcess); + + void setCurve(Float32Array*); + Float32Array* curve() { return m_curve.get(); } + +private: + // m_curve represents the non-linear shaping curve. + RefPtr<Float32Array> m_curve; + + // This synchronizes process() with setCurve(). + mutable Mutex m_processLock; +}; + +} // namespace WebCore + +#endif // WaveShaperProcessor_h |