diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/webaudio | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/webaudio')
74 files changed, 7568 insertions, 0 deletions
diff --git a/Source/WebCore/webaudio/AudioBasicProcessorNode.cpp b/Source/WebCore/webaudio/AudioBasicProcessorNode.cpp new file mode 100644 index 0000000..828062e --- /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, double 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 0000000..38bfd3b --- /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*, double 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 0000000..f46d153 --- /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) & ENABLE(3D_CANVAS) + +#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, double sampleRate) +{ + return adoptRef(new AudioBuffer(numberOfChannels, numberOfFrames, sampleRate)); +} + +PassRefPtr<AudioBuffer> AudioBuffer::createFromAudioFileData(const void* data, size_t dataSize, bool mixToMono, double 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, double 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); + ExceptionCode ec; + channelDataArray->setRange(bus->channel(i)->data(), m_length, 0, ec); + 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)) { + ExceptionCode ec; + getChannelData(i)->zeroRange(0, length(), ec); + } + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) & ENABLE(3D_CANVAS) diff --git a/Source/WebCore/webaudio/AudioBuffer.h b/Source/WebCore/webaudio/AudioBuffer.h new file mode 100644 index 0000000..b11a20e --- /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 "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, double sampleRate); + + // Returns 0 if data is not a valid audio file. + static PassRefPtr<AudioBuffer> createFromAudioFileData(const void* data, size_t dataSize, bool mixToMono, double sampleRate); + + // Format + size_t length() const { return m_length; } + double duration() const { return length() / sampleRate(); } + double 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, double sampleRate); + AudioBuffer(AudioBus* bus); + + double m_gain; // scalar gain + double 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 0000000..e7353bf --- /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 & 3D_CANVAS + ] 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/AudioBufferSourceNode.cpp b/Source/WebCore/webaudio/AudioBufferSourceNode.cpp new file mode 100644 index 0000000..05abed8 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.cpp @@ -0,0 +1,455 @@ +/* + * 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 <algorithm> +#include <wtf/MathExtras.h> + +using namespace std; + +namespace WebCore { + +const double DefaultGrainDuration = 0.020; // 20ms + +PassRefPtr<AudioBufferSourceNode> AudioBufferSourceNode::create(AudioContext* context, double sampleRate) +{ + return adoptRef(new AudioBufferSourceNode(context, sampleRate)); +} + +AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* context, double sampleRate) + : AudioSourceNode(context, sampleRate) + , m_buffer(0) + , m_isPlaying(false) + , m_isLooping(false) + , m_hasFinished(false) + , m_startTime(0.0) + , m_schedulingFrameDelay(0) + , m_readIndex(0) + , m_isGrain(false) + , m_grainOffset(0.0) + , m_grainDuration(DefaultGrainDuration) + , m_grainFrameCount(0) + , m_lastGain(1.0) + , m_pannerNode(0) +{ + setType(NodeTypeAudioBufferSource); + + m_gain = AudioGain::create("gain", 1.0, 0.0, 1.0); + m_playbackRate = AudioParam::create("playbackRate", 1.0, 0.0, AudioResampler::MaxRate); + + // 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. + double sampleRate = this->sampleRate(); + double pitchRate = totalPitchRate(); + double quantumStartTime = context()->currentTime(); + double quantumEndTime = quantumStartTime + framesToProcess / sampleRate; + + 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; + } + + // Handle sample-accurate scheduling so that buffer playback will happen at a very precise time. + m_schedulingFrameDelay = 0; + if (m_startTime >= quantumStartTime) { + // m_schedulingFrameDelay is set here only the very first render quantum (because of above check: m_startTime >= quantumEndTime) + // So: quantumStartTime <= m_startTime < quantumEndTime + ASSERT(m_startTime < quantumEndTime); + + double startTimeInQuantum = m_startTime - quantumStartTime; + double startFrameInQuantum = startTimeInQuantum * sampleRate; + + // m_schedulingFrameDelay is used in provideInput(), so factor in the current playback pitch rate. + m_schedulingFrameDelay = static_cast<int>(pitchRate * startFrameInQuantum); + } + + // FIXME: optimization opportunity: + // With a bit of work, it should be possible to avoid going through the resampler completely when the pitchRate == 1, + // especially if the pitchRate has never deviated from 1 in the past. + + // Read the samples through the pitch resampler. Our provideInput() method will be called by the resampler. + m_resampler.setRate(pitchRate); + m_resampler.process(this, outputBus, framesToProcess); + + // Apply the gain (in-place) to the output bus. + double totalGain = gain()->value() * m_buffer->gain(); + outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); + + 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(); + } +} + +// The resampler calls us back here to get the input samples from our buffer. +void AudioBufferSourceNode::provideInput(AudioBus* bus, 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(); + + 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; + + // 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_readIndex >= endFrame) + m_readIndex = startFrame; // reset to start + + int framesToProcess = numberOfFrames; + + // Handle sample-accurate scheduling so that we play the buffer at a very precise time. + // m_schedulingFrameDelay will only be non-zero the very first time that provideInput() is called, which corresponds + // with the very start of the buffer playback. + if (m_schedulingFrameDelay > 0) { + ASSERT(m_schedulingFrameDelay <= framesToProcess); + if (m_schedulingFrameDelay <= framesToProcess) { + // Generate silence for the initial portion of the destination. + memset(destinationL, 0, sizeof(float) * m_schedulingFrameDelay); + destinationL += m_schedulingFrameDelay; + if (destinationR) { + memset(destinationR, 0, sizeof(float) * m_schedulingFrameDelay); + destinationR += m_schedulingFrameDelay; + } + + // Since we just generated silence for the initial portion, we have fewer frames to provide. + framesToProcess -= m_schedulingFrameDelay; + } + } + + // We have to generate a certain number of output sample-frames, but we need to handle the case where we wrap around + // from the end of the buffer to the start if playing back with looping and also the case where we simply reach the + // end of the sample data, but haven't yet rendered numberOfFrames worth of output. + while (framesToProcess > 0) { + ASSERT(m_readIndex <= endFrame); + if (m_readIndex > endFrame) + return; + + // Figure out how many frames we can process this time. + int framesAvailable = endFrame - m_readIndex; + int framesThisTime = min(framesToProcess, framesAvailable); + + // Create the destination bus for the part of the destination we're processing this time. + AudioBus currentDestinationBus(busNumberOfChannels, framesThisTime, false); + currentDestinationBus.setChannelMemory(0, destinationL, framesThisTime); + if (busNumberOfChannels > 1) + currentDestinationBus.setChannelMemory(1, destinationR, framesThisTime); + + // Generate output from the buffer. + readFromBuffer(¤tDestinationBus, framesThisTime); + + // Update the destination pointers. + destinationL += framesThisTime; + if (busNumberOfChannels > 1) + destinationR += framesThisTime; + + framesToProcess -= framesThisTime; + + // Handle the case where we reach the end of the part of the sample data we're supposed to play for the buffer. + if (m_readIndex >= endFrame) { + m_readIndex = startFrame; + m_grainFrameCount = 0; + + if (!looping()) { + // 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); + } + + if (!m_hasFinished) { + // Let the context dereference this AudioNode. + context()->notifyNodeFinishedProcessing(this); + m_hasFinished = true; + } + return; + } + } + } +} + +void AudioBufferSourceNode::readFromBuffer(AudioBus* destinationBus, size_t framesToProcess) +{ + bool isBusGood = destinationBus && destinationBus->length() == framesToProcess && destinationBus->numberOfChannels() == numberOfChannels(); + ASSERT(isBusGood); + if (!isBusGood) + return; + + unsigned numberOfChannels = this->numberOfChannels(); + // FIXME: we can add support for sources with more than two channels, but this is not a common case. + bool channelCountGood = numberOfChannels == 1 || numberOfChannels == 2; + ASSERT(channelCountGood); + if (!channelCountGood) + return; + + // 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; + + // Sanity check buffer access. + bool isSourceGood = sourceL && (numberOfChannels == 1 || sourceR) && m_readIndex + framesToProcess <= m_buffer->length(); + ASSERT(isSourceGood); + if (!isSourceGood) + return; + + // Offset the pointers to the current read position in the sample buffer. + sourceL += m_readIndex; + sourceR += m_readIndex; + + // Get pointers to the destination. + float* destinationL = destinationBus->channel(0)->data(); + float* destinationR = numberOfChannels == 2 ? destinationBus->channel(1)->data() : 0; + bool isDestinationGood = destinationL && (numberOfChannels == 1 || destinationR); + ASSERT(isDestinationGood); + if (!isDestinationGood) + return; + + if (m_isGrain) + readFromBufferWithGrainEnvelope(sourceL, sourceR, destinationL, destinationR, framesToProcess); + else { + // Simply copy the data from the source buffer to the destination. + memcpy(destinationL, sourceL, sizeof(float) * framesToProcess); + if (numberOfChannels == 2) + memcpy(destinationR, sourceR, sizeof(float) * framesToProcess); + } + + // Advance the buffer's read index. + m_readIndex += framesToProcess; +} + +void AudioBufferSourceNode::readFromBufferWithGrainEnvelope(float* sourceL, float* sourceR, float* destinationL, float* destinationR, size_t framesToProcess) +{ + ASSERT(sourceL && destinationL); + if (!sourceL || !destinationL) + return; + + int grainFrameLength = static_cast<int>(m_grainDuration * m_buffer->sampleRate()); + bool isStereo = sourceR && destinationR; + + int n = framesToProcess; + while (n--) { + // Apply the grain envelope. + float x = static_cast<float>(m_grainFrameCount) / static_cast<float>(grainFrameLength); + m_grainFrameCount++; + + x = min(1.0f, x); + float grainEnvelope = sinf(piFloat * x); + + *destinationL++ = grainEnvelope * *sourceL++; + + if (isStereo) + *destinationR++ = grainEnvelope * *sourceR++; + } +} + +void AudioBufferSourceNode::reset() +{ + m_resampler.reset(); + m_readIndex = 0; + m_grainFrameCount = 0; + m_lastGain = gain()->value(); +} + +void 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(); + m_resampler.configureChannels(numberOfChannels); + output(0)->setNumberOfChannels(numberOfChannels); + } + + m_readIndex = 0; + m_buffer = buffer; +} + +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_readIndex = 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_grainFrameCount = 0; + + m_isGrain = true; + m_startTime = when; + m_readIndex = static_cast<int>(m_grainOffset * buffer()->sampleRate()); + m_isPlaying = true; +} + +void AudioBufferSourceNode::noteOff(double) +{ + ASSERT(isMainThread()); + if (!m_isPlaying) + return; + + // FIXME: the "when" argument to this method is ignored. + m_isPlaying = false; + m_readIndex = 0; +} + +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); + totalRate = min(AudioResampler::MaxRate, totalRate); + + bool isTotalRateValid = !isnan(totalRate) && !isinf(totalRate); + ASSERT(isTotalRateValid); + if (!isTotalRateValid) + totalRate = 1.0; + + return totalRate; +} + +} // 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 0000000..40b8555 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.h @@ -0,0 +1,147 @@ +/* + * 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 "AudioResampler.h" +#include "AudioSourceNode.h" +#include "AudioSourceProvider.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 AudioSourceProvider { +public: + static PassRefPtr<AudioBufferSourceNode> create(AudioContext*, double sampleRate); + + virtual ~AudioBufferSourceNode(); + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + + // AudioSourceProvider + // When process() is called, the resampler calls provideInput (in the audio thread) to gets its input stream. + virtual void provideInput(AudioBus*, size_t numberOfFrames); + + // setBuffer() is called on the main thread. This is the buffer we use for playback. + void 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); + + bool looping() const { return m_isLooping; } + void setLooping(bool looping) { m_isLooping = looping; } + + 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*, double sampleRate); + + // 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_schedulingFrameDelay is the sample-accurate scheduling offset. + // It's used so that we start rendering audio samples at a very precise point in time. + // It will only be a non-zero value the very first render quantum that we render from the buffer. + int m_schedulingFrameDelay; + + // m_readIndex is a sample-frame index into our buffer representing the current playback position. + unsigned m_readIndex; + + // Granular playback + bool m_isGrain; + double m_grainOffset; // in seconds + double m_grainDuration; // in seconds + int m_grainFrameCount; // keeps track of which frame in the grain we're currently rendering + + // 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_resampler performs the pitch rate changes to the buffer playback. + AudioResampler m_resampler; + + // 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; + + // Reads the next framesToProcess sample-frames from the AudioBuffer into destinationBus. + // A grain envelope will be applied if m_isGrain is set to true. + void readFromBuffer(AudioBus* destinationBus, size_t framesToProcess); + + // readFromBufferWithGrainEnvelope() is a low-level blitter which reads from the AudioBuffer and applies a grain envelope. + void readFromBufferWithGrainEnvelope(float* sourceL, float* sourceR, float* destinationL, float* destinationR, size_t framesToProcess); +}; + +} // namespace WebCore + +#endif // AudioBufferSourceNode_h diff --git a/Source/WebCore/webaudio/AudioBufferSourceNode.idl b/Source/WebCore/webaudio/AudioBufferSourceNode.idl new file mode 100644 index 0000000..dec7461 --- /dev/null +++ b/Source/WebCore/webaudio/AudioBufferSourceNode.idl @@ -0,0 +1,41 @@ +/* + * 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 [JSCCustomSetter] AudioBuffer buffer; + + readonly attribute AudioGain gain; + readonly attribute AudioParam playbackRate; + attribute boolean looping; // FIXME: change name to 'loop' once samples are updated + + 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 0000000..c418a61 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelMerger.cpp @@ -0,0 +1,102 @@ +/* + * 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 "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, double 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))); + + setType(NodeTypeChannelMerger); + + initialize(); +} + +void AudioChannelMerger::process(size_t framesToProcess) +{ + AudioNodeOutput* output = this->output(0); + ASSERT(output); + ASSERT_UNUSED(framesToProcess, framesToProcess == output->bus()->length()); + + // 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 + output->setNumberOfChannels(numberOfOutputChannels); + + // Now merge the channels back 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 == numberOfOutputChannels); +} + +void AudioChannelMerger::reset() +{ +} + +} // 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 0000000..20a9628 --- /dev/null +++ b/Source/WebCore/webaudio/AudioChannelMerger.h @@ -0,0 +1,56 @@ +/* + * 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, double sampleRate) + { + return adoptRef(new AudioChannelMerger(context, sampleRate)); + } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + +private: + AudioChannelMerger(AudioContext*, double 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 0000000..3862af9 --- /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 0000000..f4fa041 --- /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, double 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))); + + setType(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 0000000..7dadac5 --- /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, double sampleRate) + { + return adoptRef(new AudioChannelSplitter(context, sampleRate)); + } + + // AudioNode + virtual void process(size_t framesToProcess); + virtual void reset(); + +private: + AudioChannelSplitter(AudioContext*, double 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 0000000..076c051 --- /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 0000000..a452775 --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.cpp @@ -0,0 +1,529 @@ +/* + * 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 "ArrayBuffer.h" +#include "AudioBuffer.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 "ConvolverNode.h" +#include "DelayNode.h" +#include "Document.h" +#include "HRTFDatabaseLoader.h" +#include "HRTFPanner.h" +#include "HighPass2FilterNode.h" +#include "JavaScriptAudioNode.h" +#include "LowPass2FilterNode.h" +#include "PlatformString.h" +#include "RealtimeAnalyserNode.h" + +#include <wtf/OwnPtr.h> +#include <wtf/PassOwnPtr.h> +#include <wtf/RefCounted.h> + +// FIXME: check the proper way to reference an undefined thread ID +const int UndefinedThreadIdentifier = 0xffffffff; + +const unsigned MaxNodesToDeletePerQuantum = 10; + +namespace WebCore { + +PassRefPtr<AudioContext> AudioContext::create(Document* document) +{ + return adoptRef(new AudioContext(document)); +} + +AudioContext::AudioContext(Document* document) + : ActiveDOMObject(document, this) + , m_isInitialized(false) + , m_isAudioThreadFinished(false) + , m_document(document) + , m_destinationNode(0) + , m_connectionCount(0) + , m_audioThread(0) + , m_graphOwnerThread(UndefinedThreadIdentifier) +{ + // Note: because adoptRef() won't be called until we leave this constructor, but code in this constructor needs to reference this context, + // relax the check. + relaxAdoptionRequirement(); + + m_destinationNode = AudioDestinationNode::create(this); + m_listener = AudioListener::create(); + m_temporaryMonoBus = adoptPtr(new AudioBus(1, AudioNode::ProcessingSizeInFrames)); + m_temporaryStereoBus = adoptPtr(new AudioBus(2, AudioNode::ProcessingSizeInFrames)); + + // This sets in motion an asynchronous loading mechanism on another thread. + // We can check 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()); +} + +AudioContext::~AudioContext() +{ +#if DEBUG_AUDIONODE_REFERENCES + printf("%p: AudioContext::~AudioContext()\n", this); +#endif + // AudioNodes keep a reference to their context, so there should be no way to be in the destructor if there are still AudioNodes around. + ASSERT(!m_nodesToDelete.size()); + ASSERT(!m_referencedNodes.size()); + ASSERT(!m_finishedNodes.size()); +} + +void AudioContext::lazyInitialize() +{ + if (!m_isInitialized) { + // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. + ASSERT(!m_isAudioThreadFinished); + if (!m_isAudioThreadFinished) { + if (m_destinationNode.get()) { + // This starts the audio thread. The destination node's provideInput() method will now be called repeatedly to render audio. + // Each time provideInput() is called, a portion of the audio stream is rendered. Let's call this time period a "render quantum". + m_destinationNode->initialize(); + } + m_isInitialized = true; + } + } +} + +void AudioContext::uninitialize() +{ + if (m_isInitialized) { + // This stops the audio thread and all audio rendering. + m_destinationNode->uninitialize(); + + // Don't allow the context to initialize a second time after it's already been explicitly uninitialized. + m_isAudioThreadFinished = true; + + // We have to release our reference to the destination node before the context will ever be deleted since the destination node holds a reference to the context. + m_destinationNode.clear(); + + // Get rid of the sources which may still be playing. + derefUnfinishedSourceNodes(); + + // Because the AudioBuffers are garbage collected, we can't delete them here. + // Instead, at least release the potentially large amount of allocated memory for the audio data. + // Note that we do this *after* the context is uninitialized and stops processing audio. + for (unsigned i = 0; i < m_allocatedBuffers.size(); ++i) + m_allocatedBuffers[i]->releaseMemory(); + m_allocatedBuffers.clear(); + + m_isInitialized = false; + } +} + +bool AudioContext::isInitialized() const +{ + return m_isInitialized; +} + +bool AudioContext::isRunnable() const +{ + if (!isInitialized()) + return false; + + // Check with the HRTF spatialization system to see if it's finished loading. + return m_hrtfDatabaseLoader->isLoaded(); +} + +void AudioContext::stop() +{ + m_document = 0; // document is going away + uninitialize(); +} + +Document* AudioContext::document() +{ + ASSERT(m_document); + return m_document; +} + +bool AudioContext::hasDocument() +{ + return m_document; +} + +void AudioContext::refBuffer(PassRefPtr<AudioBuffer> buffer) +{ + m_allocatedBuffers.append(buffer); +} + +PassRefPtr<AudioBuffer> AudioContext::createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate) +{ + return AudioBuffer::create(numberOfChannels, numberOfFrames, sampleRate); +} + +PassRefPtr<AudioBuffer> AudioContext::createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono) +{ + ASSERT(arrayBuffer); + if (!arrayBuffer) + return 0; + + return AudioBuffer::createFromAudioFileData(arrayBuffer->data(), arrayBuffer->byteLength(), mixToMono, sampleRate()); +} + +PassRefPtr<AudioBufferSourceNode> AudioContext::createBufferSource() +{ + ASSERT(isMainThread()); + lazyInitialize(); + RefPtr<AudioBufferSourceNode> node = AudioBufferSourceNode::create(this, m_destinationNode->sampleRate()); + + refNode(node.get()); // context keeps reference until source has finished playing + return node; +} + +PassRefPtr<JavaScriptAudioNode> AudioContext::createJavaScriptNode(size_t bufferSize) +{ + ASSERT(isMainThread()); + lazyInitialize(); + RefPtr<JavaScriptAudioNode> node = JavaScriptAudioNode::create(this, m_destinationNode->sampleRate(), bufferSize); + + refNode(node.get()); // context keeps reference until we stop making javascript rendering callbacks + return node; +} + +PassRefPtr<LowPass2FilterNode> AudioContext::createLowPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return LowPass2FilterNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<HighPass2FilterNode> AudioContext::createHighPass2Filter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return HighPass2FilterNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioPannerNode> AudioContext::createPanner() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioPannerNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<ConvolverNode> AudioContext::createConvolver() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return ConvolverNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<RealtimeAnalyserNode> AudioContext::createAnalyser() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return RealtimeAnalyserNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioGainNode> AudioContext::createGainNode() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioGainNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<DelayNode> AudioContext::createDelayNode() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return DelayNode::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioChannelSplitter> AudioContext::createChannelSplitter() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioChannelSplitter::create(this, m_destinationNode->sampleRate()); +} + +PassRefPtr<AudioChannelMerger> AudioContext::createChannelMerger() +{ + ASSERT(isMainThread()); + lazyInitialize(); + return AudioChannelMerger::create(this, m_destinationNode->sampleRate()); +} + +void AudioContext::notifyNodeFinishedProcessing(AudioNode* node) +{ + ASSERT(isAudioThread()); + m_finishedNodes.append(node); +} + +void AudioContext::derefFinishedSourceNodes() +{ + ASSERT(isGraphOwner()); + ASSERT(isAudioThread() || isAudioThreadFinished()); + for (unsigned i = 0; i < m_finishedNodes.size(); i++) + derefNode(m_finishedNodes[i]); + + m_finishedNodes.clear(); +} + +void AudioContext::refNode(AudioNode* node) +{ + ASSERT(isMainThread()); + AutoLocker locker(this); + + node->ref(AudioNode::RefTypeConnection); + m_referencedNodes.append(node); +} + +void AudioContext::derefNode(AudioNode* node) +{ + ASSERT(isGraphOwner()); + + node->deref(AudioNode::RefTypeConnection); + + for (unsigned i = 0; i < m_referencedNodes.size(); ++i) { + if (node == m_referencedNodes[i]) { + m_referencedNodes.remove(i); + break; + } + } +} + +void AudioContext::derefUnfinishedSourceNodes() +{ + ASSERT(isMainThread() && isAudioThreadFinished()); + for (unsigned i = 0; i < m_referencedNodes.size(); ++i) + m_referencedNodes[i]->deref(AudioNode::RefTypeConnection); + + m_referencedNodes.clear(); +} + +void AudioContext::lock(bool& mustReleaseLock) +{ + // Don't allow regular lock in real-time audio thread. + ASSERT(isMainThread()); + + ThreadIdentifier thisThread = currentThread(); + + if (thisThread == m_graphOwnerThread) { + // We already have the lock. + mustReleaseLock = false; + } else { + // Acquire the lock. + m_contextGraphMutex.lock(); + 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(); + + // Finally actually delete. + deleteMarkedNodes(); + + // 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::deleteMarkedNodes() +{ + ASSERT(isGraphOwner() || isAudioThreadFinished()); + + // Note: deleting an AudioNode can cause m_nodesToDelete to grow. + size_t nodesDeleted = 0; + 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; + + // Don't delete too many nodes per render quantum since we don't want to do too much work in the realtime audio thread. + if (++nodesDeleted > MaxNodesToDeletePerQuantum) + break; + } +} + +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(); +} + + +} // 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 0000000..ddd474c --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.h @@ -0,0 +1,259 @@ +/* + * 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 "AudioBus.h" +#include "AudioDestinationNode.h" +#include "HRTFDatabaseLoader.h" +#include <wtf/HashSet.h> +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> +#include <wtf/text/AtomicStringHash.h> + +namespace WebCore { + +class ArrayBuffer; +class AudioBuffer; +class AudioBufferSourceNode; +class AudioChannelMerger; +class AudioChannelSplitter; +class AudioGainNode; +class AudioPannerNode; +class AudioListener; +class DelayNode; +class Document; +class LowPass2FilterNode; +class HighPass2FilterNode; +class ConvolverNode; +class RealtimeAnalyserNode; +class JavaScriptAudioNode; + +// AudioContext is the cornerstone of the web audio API and all AudioNodes are created from it. +// For thread safety between the audio thread and the main thread, it has a rendering graph locking mechanism. + +class AudioContext : public ActiveDOMObject, public RefCounted<AudioContext> { +public: + static PassRefPtr<AudioContext> create(Document*); + + virtual ~AudioContext(); + + bool isInitialized() const; + + // Returns true when initialize() was called AND all asynchronous initialization has completed. + bool isRunnable() const; + + // Document notification + virtual void stop(); + + Document* document(); // ASSERTs if document no longer exists. + bool hasDocument(); + + AudioDestinationNode* destination() { return m_destinationNode.get(); } + double currentTime() { return m_destinationNode->currentTime(); } + double sampleRate() { return m_destinationNode->sampleRate(); } + + PassRefPtr<AudioBuffer> createBuffer(unsigned numberOfChannels, size_t numberOfFrames, double sampleRate); + PassRefPtr<AudioBuffer> createBuffer(ArrayBuffer* arrayBuffer, bool mixToMono); + + // Keep track of this buffer so we can release memory after the context is shut down... + void refBuffer(PassRefPtr<AudioBuffer> buffer); + + AudioListener* listener() { return m_listener.get(); } + + // The AudioNode create methods are called on the main thread (from JavaScript). + PassRefPtr<AudioBufferSourceNode> createBufferSource(); + PassRefPtr<AudioGainNode> createGainNode(); + PassRefPtr<DelayNode> createDelayNode(); + PassRefPtr<LowPass2FilterNode> createLowPass2Filter(); + PassRefPtr<HighPass2FilterNode> createHighPass2Filter(); + PassRefPtr<AudioPannerNode> createPanner(); + PassRefPtr<ConvolverNode> createConvolver(); + PassRefPtr<RealtimeAnalyserNode> createAnalyser(); + PassRefPtr<JavaScriptAudioNode> createJavaScriptNode(size_t bufferSize); + PassRefPtr<AudioChannelSplitter> createChannelSplitter(); + PassRefPtr<AudioChannelMerger> createChannelMerger(); + + AudioBus* temporaryMonoBus() { return m_temporaryMonoBus.get(); } + AudioBus* temporaryStereoBus() { return m_temporaryStereoBus.get(); } + + // When a source node has no more processing to do (has finished playing), then it tells the context to dereference it. + void notifyNodeFinishedProcessing(AudioNode*); + + // Called at the 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 reap all marked nodes at the end of each realtime render quantum in deleteMarkedNodes(). + void markForDeletion(AudioNode*); + void deleteMarkedNodes(); + + // Keeps track of the number of connections made. + void incrementConnectionCount() + { + ASSERT(isMainThread()); + m_connectionCount++; + } + + unsigned connectionCount() const { return m_connectionCount; } + + // + // Thread Safety and Graph Locking: + // + + void setAudioThread(ThreadIdentifier thread) { m_audioThread = thread; } // FIXME: check either not initialized or the same + ThreadIdentifier audioThread() const { return m_audioThread; } + bool isAudioThread() 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*); + +private: + AudioContext(Document*); + void lazyInitialize(); + void uninitialize(); + + bool m_isInitialized; + bool m_isAudioThreadFinished; + bool m_isAudioThreadShutdown; + + Document* m_document; + + // The context itself keeps a reference to all source nodes. The source nodes, then reference all nodes they're connected to. + // In turn, these nodes reference all nodes they're connected to. All nodes are ultimately connected to the AudioDestinationNode. + // When the context dereferences a source node, it will be deactivated from the rendering graph along with all other nodes it is + // uniquely connected to. See the AudioNode::ref() and AudioNode::deref() methods for more details. + void refNode(AudioNode*); + void derefNode(AudioNode*); + + // When the context goes away, there might still be some sources which haven't finished playing. + // Make sure to dereference them here. + void derefUnfinishedSourceNodes(); + + RefPtr<AudioDestinationNode> m_destinationNode; + RefPtr<AudioListener> m_listener; + + // Only accessed in the main thread. + Vector<RefPtr<AudioBuffer> > m_allocatedBuffers; + + // Only accessed in the audio thread. + Vector<AudioNode*> m_finishedNodes; + + // We don't use RefPtr<AudioNode> here because AudioNode has a more complex ref() / deref() implementation + // with an optional argument for refType. We need to use the special refType: RefTypeConnection + // Either accessed when the graph lock is held, or on the main thread when the audio thread has finished. + Vector<AudioNode*> m_referencedNodes; + + // Accumulate nodes which need to be deleted at the end of a render cycle (in realtime thread) here. + Vector<AudioNode*> m_nodesToDelete; + + // 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; +}; + +} // WebCore + +#endif // AudioContext_h diff --git a/Source/WebCore/webaudio/AudioContext.idl b/Source/WebCore/webaudio/AudioContext.idl new file mode 100644 index 0000000..9f0f49c --- /dev/null +++ b/Source/WebCore/webaudio/AudioContext.idl @@ -0,0 +1,64 @@ +/* + * 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 webaudio { + interface [ + Conditional=WEB_AUDIO, + CanBeConstructed, + CustomConstructFunction, + V8CustomConstructor + ] AudioContext { + // All rendered audio ultimately connects to destination, which represents the audio hardware. + readonly attribute AudioDestinationNode destination; + + // All scheduled times are relative to this time in seconds. + readonly attribute float currentTime; + + // All AudioNodes in the context run at this sample-rate (in sample-frames per second). + readonly attribute float sampleRate; + + // All panning is relative to this listener. + readonly attribute AudioListener listener; + + AudioBuffer createBuffer(in unsigned long numberOfChannels, in unsigned long numberOfFrames, in float sampleRate); + AudioBuffer createBuffer(in ArrayBuffer buffer, in boolean mixToMono); + + // Source + AudioBufferSourceNode createBufferSource(); + + // Processing nodes + AudioGainNode createGainNode(); + DelayNode createDelayNode(); + LowPass2FilterNode createLowPass2Filter(); + HighPass2FilterNode createHighPass2Filter(); + AudioPannerNode createPanner(); + ConvolverNode createConvolver(); + RealtimeAnalyserNode createAnalyser(); + JavaScriptAudioNode createJavaScriptNode(in unsigned long bufferSize); + + // Channel splitting and merging + AudioChannelSplitter createChannelSplitter(); + AudioChannelMerger createChannelMerger(); + }; +} diff --git a/Source/WebCore/webaudio/AudioDestinationNode.cpp b/Source/WebCore/webaudio/AudioDestinationNode.cpp new file mode 100644 index 0000000..d2f4928 --- /dev/null +++ b/Source/WebCore/webaudio/AudioDestinationNode.cpp @@ -0,0 +1,114 @@ +/* + * 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 <wtf/Threading.h> + +namespace WebCore { + +AudioDestinationNode::AudioDestinationNode(AudioContext* context) + : AudioNode(context, AudioDestination::hardwareSampleRate()) + , m_currentTime(0.0) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + + setType(NodeTypeDestination); + + initialize(); +} + +AudioDestinationNode::~AudioDestinationNode() +{ + uninitialize(); +} + +void AudioDestinationNode::initialize() +{ + if (isInitialized()) + return; + + double hardwareSampleRate = AudioDestination::hardwareSampleRate(); +#ifndef NDEBUG + fprintf(stderr, ">>>> hardwareSampleRate = %f\n", hardwareSampleRate); +#endif + + m_destination = AudioDestination::create(*this, hardwareSampleRate); + m_destination->start(); + + AudioNode::initialize(); +} + +void AudioDestinationNode::uninitialize() +{ + if (!isInitialized()) + return; + + m_destination->stop(); + + AudioNode::uninitialize(); +} + +// The audio hardware calls us back here to gets its input stream. +void AudioDestinationNode::provideInput(AudioBus* destinationBus, size_t numberOfFrames) +{ + context()->setAudioThread(currentThread()); + + if (!context()->isRunnable()) { + destinationBus->zero(); + return; + } + + // 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 0000000..4c21bb8 --- /dev/null +++ b/Source/WebCore/webaudio/AudioDestinationNode.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 AudioDestinationNode_h +#define AudioDestinationNode_h + +#include "AudioDestination.h" +#include "AudioNode.h" +#include "AudioSourceProvider.h" +#include <wtf/OwnPtr.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class AudioBus; +class AudioContext; + +class AudioDestinationNode : public AudioNode, public AudioSourceProvider { +public: + static PassRefPtr<AudioDestinationNode> create(AudioContext* context) + { + return adoptRef(new AudioDestinationNode(context)); + } + + virtual ~AudioDestinationNode(); + + // AudioNode + virtual void process(size_t) { }; // we're pulled by hardware so this is never called + virtual void reset() { m_currentTime = 0.0; }; + virtual void initialize(); + virtual void uninitialize(); + + // The audio hardware calls here periodically to gets its input stream. + virtual void provideInput(AudioBus*, size_t numberOfFrames); + + double currentTime() { return m_currentTime; } + + double sampleRate() const { return m_destination->sampleRate(); } + + unsigned numberOfChannels() const { return 2; } // FIXME: update when multi-channel (more than stereo) is supported + +private: + AudioDestinationNode(AudioContext*); + + OwnPtr<AudioDestination> m_destination; + double m_currentTime; +}; + +} // namespace WebCore + +#endif // AudioDestinationNode_h diff --git a/Source/WebCore/webaudio/AudioDestinationNode.idl b/Source/WebCore/webaudio/AudioDestinationNode.idl new file mode 100644 index 0000000..d7bf09f --- /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 0000000..eb3c52d --- /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 0000000..ead7c9a --- /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 0000000..5b9af07 --- /dev/null +++ b/Source/WebCore/webaudio/AudioGainNode.cpp @@ -0,0 +1,113 @@ +/* + * 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, double sampleRate) + : AudioNode(context, sampleRate) + , m_lastGain(1.0) +{ + m_gain = AudioGain::create("gain", 1.0, 0.0, 1.0); + + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 1))); + + setType(NodeTypeGain); + + initialize(); +} + +void AudioGainNode::process(size_t /*framesToProcess*/) +{ + // FIXME: 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(); + + // 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 0000000..3710472 --- /dev/null +++ b/Source/WebCore/webaudio/AudioGainNode.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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, double 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*, double sampleRate); + + double m_lastGain; // for de-zippering + RefPtr<AudioGain> m_gain; + + // 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 0000000..3d4f40f --- /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 0000000..44fb02c --- /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, 0.0, -1.0) + , m_upVector(0.0, 1.0, 0.0) + , m_velocity(0, 0, 0) + , m_dopplerFactor(1.0) + , 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 0000000..5281a89 --- /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(double x, double y, double z) { setPosition(FloatPoint3D(x, y, z)); } + void setPosition(const FloatPoint3D &position) { m_position = position; } + const FloatPoint3D& position() const { return m_position; } + + // Orientation + void setOrientation(double x, double y, double z, double upX, double upY, double 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(double x, double y, double 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 0000000..cf6d8cf --- /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 0000000..18ddd3b --- /dev/null +++ b/Source/WebCore/webaudio/AudioNode.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 "AudioNode.h" + +#include "AudioContext.h" +#include "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include <wtf/Atomics.h> + +namespace WebCore { + +AudioNode::AudioNode(AudioContext* context, double sampleRate) + : m_isInitialized(false) + , m_type(NodeTypeUnknown) + , m_context(context) + , m_sampleRate(sampleRate) + , m_lastProcessingTime(-1.0) + , m_normalRefCount(1) // start out with normal refCount == 1 (like WTF::RefCounted class) + , m_connectionRefCount(0) + , m_disabledRefCount(0) + , m_isMarkedForDeletion(false) + , m_isDisabled(false) +{ +#if DEBUG_AUDIONODE_REFERENCES + if (!s_isNodeCountInitialized) { + s_isNodeCountInitialized = true; + atexit(AudioNode::printNodeCounts); + } +#endif +} + +AudioNode::~AudioNode() +{ +#if DEBUG_AUDIONODE_REFERENCES + --s_nodeCount[type()]; + printf("%p: %d: AudioNode::~AudioNode() %d %d %d\n", this, type(), m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif +} + +void AudioNode::initialize() +{ + m_isInitialized = true; +} + +void AudioNode::uninitialize() +{ + m_isInitialized = false; +} + +void AudioNode::setType(NodeType type) +{ + m_type = type; + +#if DEBUG_AUDIONODE_REFERENCES + ++s_nodeCount[type]; +#endif +} + +void AudioNode::lazyInitialize() +{ + if (!isInitialized()) + initialize(); +} + +void AudioNode::addInput(PassOwnPtr<AudioNodeInput> input) +{ + m_inputs.append(input); +} + +void AudioNode::addOutput(PassOwnPtr<AudioNodeOutput> output) +{ + m_outputs.append(output); +} + +AudioNodeInput* AudioNode::input(unsigned i) +{ + return m_inputs[i].get(); +} + +AudioNodeOutput* AudioNode::output(unsigned i) +{ + return m_outputs[i].get(); +} + +bool AudioNode::connect(AudioNode* destination, unsigned outputIndex, unsigned inputIndex) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + // Sanity check input and output indices. + if (outputIndex >= numberOfOutputs()) + return false; + if (destination && inputIndex >= destination->numberOfInputs()) + return false; + + AudioNodeOutput* output = this->output(outputIndex); + if (!destination) { + // Disconnect output from any inputs it may be currently connected to. + output->disconnectAllInputs(); + return true; + } + + AudioNodeInput* input = destination->input(inputIndex); + input->connect(output); + + // Let context know that a connection has been made. + context()->incrementConnectionCount(); + + return true; +} + +bool AudioNode::disconnect(unsigned outputIndex) +{ + ASSERT(isMainThread()); + AudioContext::AutoLocker locker(context()); + + return connect(0, outputIndex); +} + +void AudioNode::processIfNecessary(size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + if (!isInitialized()) + return; + + // Ensure that we only process once per rendering quantum. + // This handles the "fanout" problem where an output is connected to multiple inputs. + // The first time we're called during this time slice we process, but after that we don't want to re-process, + // instead our output(s) will already have the results cached in their bus; + double currentTime = context()->currentTime(); + if (m_lastProcessingTime != currentTime) { + m_lastProcessingTime = currentTime; // important to first update this time because of feedback loops in the rendering graph + pullInputs(framesToProcess); + process(framesToProcess); + } +} + +void AudioNode::pullInputs(size_t framesToProcess) +{ + ASSERT(context()->isAudioThread()); + + // Process all of the AudioNodes connected to our inputs. + for (unsigned i = 0; i < m_inputs.size(); ++i) + input(i)->pull(0, framesToProcess); +} + +void AudioNode::ref(RefType refType) +{ + switch (refType) { + case RefTypeNormal: + atomicIncrement(&m_normalRefCount); + break; + case RefTypeConnection: + atomicIncrement(&m_connectionRefCount); + break; + case RefTypeDisabled: + atomicIncrement(&m_disabledRefCount); + break; + default: + ASSERT_NOT_REACHED(); + } + +#if DEBUG_AUDIONODE_REFERENCES + printf("%p: %d: AudioNode::ref(%d) %d %d %d\n", this, type(), refType, m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif + + if (m_connectionRefCount == 1 && refType == RefTypeConnection) { + // FIXME: implement wake-up - this is an advanced feature and is not necessary in a simple implementation. + // We should not be "actively" connected to anything, but now we're "waking up" + // For example, a note which has finished playing, but is now being played again. + // Note that if this is considered a worthwhile feature to add, then an evaluation of the locking considerations must be made. + } +} + +void AudioNode::deref(RefType refType) +{ + // The actually work for deref happens completely within the audio context's graph lock. + // In the case of the audio thread, we must use a tryLock to avoid glitches. + bool hasLock = false; + bool mustReleaseLock = false; + + if (context()->isAudioThread()) { + // Real-time audio thread must not contend lock (to avoid glitches). + hasLock = context()->tryLock(mustReleaseLock); + } else { + context()->lock(mustReleaseLock); + hasLock = true; + } + + if (hasLock) { + // This is where the real deref work happens. + finishDeref(refType); + + if (mustReleaseLock) + context()->unlock(); + } else { + // We were unable to get the lock, so put this in a list to finish up later. + ASSERT(context()->isAudioThread()); + context()->addDeferredFinishDeref(this, refType); + } + + // Once AudioContext::uninitialize() is called there's no more chances for deleteMarkedNodes() to get called, so we call here. + // We can't call in AudioContext::~AudioContext() since it will never be called as long as any AudioNode is alive + // because AudioNodes keep a reference to the context. + if (context()->isAudioThreadFinished()) + context()->deleteMarkedNodes(); +} + +void AudioNode::finishDeref(RefType refType) +{ + ASSERT(context()->isGraphOwner()); + + switch (refType) { + case RefTypeNormal: + ASSERT(m_normalRefCount > 0); + atomicDecrement(&m_normalRefCount); + break; + case RefTypeConnection: + ASSERT(m_connectionRefCount > 0); + atomicDecrement(&m_connectionRefCount); + break; + case RefTypeDisabled: + ASSERT(m_disabledRefCount > 0); + atomicDecrement(&m_disabledRefCount); + break; + default: + ASSERT_NOT_REACHED(); + } + +#if DEBUG_AUDIONODE_REFERENCES + printf("%p: %d: AudioNode::deref(%d) %d %d %d\n", this, type(), refType, m_normalRefCount, m_connectionRefCount, m_disabledRefCount); +#endif + + if (!m_connectionRefCount) { + if (!m_normalRefCount && !m_disabledRefCount) { + if (!m_isMarkedForDeletion) { + // All references are gone - we need to go away. + for (unsigned i = 0; i < m_outputs.size(); ++i) + output(i)->disconnectAllInputs(); // this will deref() nodes we're connected to... + + // Mark for deletion at end of each render quantum or when context shuts down. + context()->markForDeletion(this); + m_isMarkedForDeletion = true; + } + } else if (refType == RefTypeConnection) { + if (!m_isDisabled) { + // Still may have JavaScript references, but no more "active" connection references, so put all of our outputs in a "dormant" disabled state. + // Garbage collection may take a very long time after this time, so the "dormant" disabled nodes should not bog down the rendering... + + // As far as JavaScript is concerned, our outputs must still appear to be connected. + // But internally our outputs should be disabled from the inputs they're connected to. + // disable() can recursively deref connections (and call disable()) down a whole chain of connected nodes. + + // FIXME: we special case the convolver and delay since they have a significant tail-time and shouldn't be disconnected simply + // because they no longer have any input connections. This needs to be handled more generally where AudioNodes have + // a tailTime attribute. Then the AudioNode only needs to remain "active" for tailTime seconds after there are no + // longer any active connections. + if (type() != NodeTypeConvolver && type() != NodeTypeDelay) { + m_isDisabled = true; + for (unsigned i = 0; i < m_outputs.size(); ++i) + output(i)->disable(); + } + } + } + } +} + +#if DEBUG_AUDIONODE_REFERENCES + +bool AudioNode::s_isNodeCountInitialized = false; +int AudioNode::s_nodeCount[NodeTypeEnd]; + +void AudioNode::printNodeCounts() +{ + printf("\n\n"); + printf("===========================\n"); + printf("AudioNode: reference counts\n"); + printf("===========================\n"); + + for (unsigned i = 0; i < NodeTypeEnd; ++i) + printf("%d: %d\n", i, s_nodeCount[i]); + + printf("===========================\n\n\n"); +} + +#endif // DEBUG_AUDIONODE_REFERENCES + +} // namespace WebCore + +#endif // ENABLE(WEB_AUDIO) diff --git a/Source/WebCore/webaudio/AudioNode.h b/Source/WebCore/webaudio/AudioNode.h new file mode 100644 index 0000000..069407d --- /dev/null +++ b/Source/WebCore/webaudio/AudioNode.h @@ -0,0 +1,171 @@ +/* + * 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; + +// An AudioNode is the basic building block for handling audio within an AudioContext. +// It may be an audio source, an intermediate processing module, or an audio destination. +// Each AudioNode can have inputs and/or outputs. An AudioSourceNode has no inputs and a single output. +// An AudioDestinationNode has one input and no outputs and represents the final destination to the audio hardware. +// Most processing nodes such as filters will have one input and one output, although multiple inputs and outputs are possible. + +class AudioNode { +public: + enum { ProcessingSizeInFrames = 128 }; + + AudioNode(AudioContext*, double sampleRate); + virtual ~AudioNode(); + + AudioContext* context() { return m_context.get(); } + + enum NodeType { + NodeTypeUnknown, + NodeTypeDestination, + NodeTypeAudioBufferSource, + NodeTypeJavaScript, + NodeTypeLowPass2Filter, + NodeTypeHighPass2Filter, + NodeTypePanner, + NodeTypeConvolver, + NodeTypeDelay, + NodeTypeGain, + NodeTypeChannelSplitter, + NodeTypeChannelMerger, + NodeTypeAnalyser, + NodeTypeEnd + }; + + NodeType type() const { return m_type; } + void setType(NodeType); + + // We handle our own ref-counting because of the threading issues and subtle nature of + // how AudioNodes can continue processing (playing one-shot sound) after there are no more + // JavaScript references to the object. + enum RefType { RefTypeNormal, RefTypeConnection, RefTypeDisabled }; + + // Can be called from main thread or context's audio thread. + void ref(RefType refType = RefTypeNormal); + void deref(RefType refType = RefTypeNormal); + + // Can be called from main thread or context's audio thread. It must be called while the context's graph lock is held. + void finishDeref(RefType refType); + + // The AudioNodeInput(s) (if any) will already have their input data available when process() is called. + // Subclasses will take this input data and put the results in the AudioBus(s) of its AudioNodeOutput(s) (if any). + // Called from context's audio thread. + virtual void process(size_t framesToProcess) = 0; + + // Resets DSP processing state (clears delay lines, filter memory, etc.) + // Called from context's audio thread. + virtual void reset() = 0; + + // No significant resources should be allocated until initialize() is called. + // Processing may not occur until a node is initialized. + virtual void initialize(); + 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); + + // connect() / disconnect() return true on success. + // Called from main thread by corresponding JavaScript methods. + bool connect(AudioNode* destination, unsigned outputIndex = 0, unsigned inputIndex = 0); + bool disconnect(unsigned outputIndex = 0); + + double sampleRate() const { return m_sampleRate; } + + // processIfNecessary() is called by our output(s) when the rendering graph needs this AudioNode to process. + // This method ensures that the AudioNode will only process once per rendering time quantum even if it's called repeatedly. + // This handles the case of "fanout" where an output is connected to multiple AudioNode inputs. + // Called from context's audio thread. + void processIfNecessary(size_t framesToProcess); + + // Called when a new connection has been made to one of our inputs or the connection number of channels has changed. + // This potentially gives us enough information to perform a lazy initialization or, if necessary, a re-initialization. + // Called from main thread. + virtual void checkNumberOfChannelsForInput(AudioNodeInput*) { } + +#if DEBUG_AUDIONODE_REFERENCES + static void printNodeCounts(); +#endif + + 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_type; + RefPtr<AudioContext> m_context; + double m_sampleRate; + Vector<OwnPtr<AudioNodeInput> > m_inputs; + Vector<OwnPtr<AudioNodeOutput> > m_outputs; + + double m_lastProcessingTime; + + // Ref-counting + volatile int m_normalRefCount; + volatile int m_connectionRefCount; + volatile int m_disabledRefCount; + + bool m_isMarkedForDeletion; + bool m_isDisabled; + +#if DEBUG_AUDIONODE_REFERENCES + static bool s_isNodeCountInitialized; + static int s_nodeCount[NodeTypeEnd]; +#endif +}; + +} // namespace WebCore + +#endif // AudioNode_h diff --git a/Source/WebCore/webaudio/AudioNode.idl b/Source/WebCore/webaudio/AudioNode.idl new file mode 100644 index 0000000..8d903e2 --- /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; + + [Custom] void connect(in AudioNode destination, in unsigned long output, in unsigned long input) + raises(DOMException); + + [Custom] void disconnect(in unsigned long output) + raises(DOMException); + }; +} diff --git a/Source/WebCore/webaudio/AudioNodeInput.cpp b/Source/WebCore/webaudio/AudioNodeInput.cpp new file mode 100644 index 0000000..9fd1852 --- /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 0000000..1d90986 --- /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 0000000..4c777e6 --- /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 0000000..7114b38 --- /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 0000000..5c94763 --- /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, double 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); + + setType(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); + + double 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->type() == 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 0000000..61e34a9 --- /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, double 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*, double 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 0000000..2db093d --- /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 0000000..dcf918f --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.cpp @@ -0,0 +1,66 @@ +/* + * 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 <wtf/MathExtras.h> + +namespace WebCore { + +const double AudioParam::DefaultSmoothingConstant = 0.05; +const double AudioParam::SnapThreshold = 0.001; + +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; +} + +bool AudioParam::smooth() +{ + if (m_smoothedValue == m_value) { + // Smoothed value has already approached and snapped to value. + return true; + } + + // 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; +} + +} // 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 0000000..88b7615 --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.h @@ -0,0 +1,100 @@ +/* + * 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 "PlatformString.h" +#include <sys/types.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) + { + } + + float value() const { return static_cast<float>(m_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() const { return static_cast<float>(m_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; } + +private: + 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; +}; + +} // namespace WebCore + +#endif // AudioParam_h diff --git a/Source/WebCore/webaudio/AudioParam.idl b/Source/WebCore/webaudio/AudioParam.idl new file mode 100644 index 0000000..ff2598e --- /dev/null +++ b/Source/WebCore/webaudio/AudioParam.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 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; + }; +} diff --git a/Source/WebCore/webaudio/AudioProcessingEvent.cpp b/Source/WebCore/webaudio/AudioProcessingEvent.cpp new file mode 100644 index 0000000..54ce521 --- /dev/null +++ b/Source/WebCore/webaudio/AudioProcessingEvent.cpp @@ -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. + */ + +#include "config.h" + +#if ENABLE(WEB_AUDIO) + +#include "AudioProcessingEvent.h" + +#include "AudioBuffer.h" +#include "EventNames.h" + +namespace WebCore { + +PassRefPtr<AudioProcessingEvent> AudioProcessingEvent::create(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer) +{ + return adoptRef(new AudioProcessingEvent(inputBuffer, outputBuffer)); +} + +AudioProcessingEvent::AudioProcessingEvent(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer) + : Event(eventNames().audioprocessEvent, true, false) + , m_inputBuffer(inputBuffer) + , m_outputBuffer(outputBuffer) +{ +} + +AudioProcessingEvent::~AudioProcessingEvent() +{ +} + +bool AudioProcessingEvent::isAudioProcessingEvent() const +{ + return true; +} + +} // 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 0000000..a88669c --- /dev/null +++ b/Source/WebCore/webaudio/AudioProcessingEvent.h @@ -0,0 +1,57 @@ +/* + * 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(PassRefPtr<AudioBuffer> inputBuffer, PassRefPtr<AudioBuffer> outputBuffer); + + virtual ~AudioProcessingEvent(); + + virtual bool isAudioProcessingEvent() const; + + AudioBuffer* inputBuffer() { return m_inputBuffer.get(); } + AudioBuffer* outputBuffer() { return m_outputBuffer.get(); } + +private: + 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 0000000..c2f8a83 --- /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 0000000..6091371 --- /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, double 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 0000000..ec3c356 --- /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 0000000..a4b28be --- /dev/null +++ b/Source/WebCore/webaudio/BiquadDSPKernel.cpp @@ -0,0 +1,77 @@ +/* + * 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" + +namespace WebCore { + +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. + if (biquadProcessor()->filterCoefficientsDirty()) { + double value1 = biquadProcessor()->parameter1()->smoothedValue(); + double value2 = biquadProcessor()->parameter2()->smoothedValue(); + + // Convert from Hertz to normalized frequency 0 -> 1. + double nyquist = this->nyquist(); + double normalizedValue1 = value1 / nyquist; + + // Configure the biquad with the new filter parameters for the appropriate type of filter. + switch (biquadProcessor()->type()) { + case BiquadProcessor::LowPass2: + m_biquad.setLowpassParams(normalizedValue1, value2); + break; + + case BiquadProcessor::HighPass2: + m_biquad.setHighpassParams(normalizedValue1, value2); + break; + + case BiquadProcessor::LowShelf: + m_biquad.setLowShelfParams(normalizedValue1, value2); + break; + + // FIXME: add other biquad filter types... + case BiquadProcessor::Peaking: + case BiquadProcessor::Allpass: + case BiquadProcessor::HighShelf: + break; + } + } + + m_biquad.process(source, destination, framesToProcess); +} + +} // 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 0000000..47d0f34 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadDSPKernel.h @@ -0,0 +1,56 @@ +/* + * 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(); } + +protected: + Biquad m_biquad; + BiquadProcessor* biquadProcessor() { return static_cast<BiquadProcessor*>(processor()); } +}; + +} // namespace WebCore + +#endif // BiquadDSPKernel_h diff --git a/Source/WebCore/webaudio/BiquadProcessor.cpp b/Source/WebCore/webaudio/BiquadProcessor.cpp new file mode 100644 index 0000000..97a480e --- /dev/null +++ b/Source/WebCore/webaudio/BiquadProcessor.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 "BiquadProcessor.h" + +#include "BiquadDSPKernel.h" + +namespace WebCore { + +BiquadProcessor::BiquadProcessor(FilterType type, double 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(); + + switch (type) { + // Highpass and lowpass share the same parameters and only differ in filter type. + case LowPass2: + case HighPass2: + 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; + + case Peaking: + m_parameter1 = AudioParam::create("frequency", 2500.0, 20.0, nyquist); + m_parameter2 = AudioParam::create("gain", 0.0, -20.0, 20.0); + m_parameter3 = AudioParam::create("Q", 0.5, 0.0, 1000.0); + break; + case Allpass: + m_parameter1 = AudioParam::create("frequency", 2500.0, 20.0, nyquist); + m_parameter2 = AudioParam::create("Q", 0.5, 0.0, 1000.0); + m_parameter3 = AudioParam::create("unused", 0.0, 0.0, 1.0); + break; + case LowShelf: + m_parameter1 = AudioParam::create("frequency", 80.0, 20.0, nyquist); + m_parameter2 = AudioParam::create("gain", 0.0, 0.0, 1.0); + m_parameter3 = AudioParam::create("unused", 0.0, 0.0, 1.0); + break; + case HighShelf: + m_parameter1 = AudioParam::create("frequency", 10000.0, 20.0, nyquist); + m_parameter2 = AudioParam::create("gain", 0.0, 0.0, 1.0); + m_parameter3 = AudioParam::create("unused", 0.0, 0.0, 1.0); + break; + } + + if (autoInitialize) + initialize(); +} + +BiquadProcessor::~BiquadProcessor() +{ + if (isInitialized()) + uninitialize(); +} + +PassOwnPtr<AudioDSPKernel> BiquadProcessor::createKernel() +{ + return adoptPtr(new BiquadDSPKernel(this)); +} + +void BiquadProcessor::process(AudioBus* source, AudioBus* destination, size_t framesToProcess) +{ + if (!isInitialized()) { + destination->zero(); + return; + } + + // 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; + } + + // 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); +} + +} // 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 0000000..55dca33 --- /dev/null +++ b/Source/WebCore/webaudio/BiquadProcessor.h @@ -0,0 +1,78 @@ +/* + * 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 { + LowPass2, + HighPass2, + Peaking, + Allpass, + LowShelf, + HighShelf + }; + + BiquadProcessor(FilterType, double sampleRate, size_t numberOfChannels, bool autoInitialize = true); + virtual ~BiquadProcessor(); + + virtual PassOwnPtr<AudioDSPKernel> createKernel(); + + virtual void process(AudioBus* source, AudioBus* destination, size_t framesToProcess); + + 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; } + +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 0000000..c778a41 --- /dev/null +++ b/Source/WebCore/webaudio/ConvolverNode.cpp @@ -0,0 +1,152 @@ +/* + * 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 "AudioNodeInput.h" +#include "AudioNodeOutput.h" +#include "Reverb.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 16384 is a good compromise. +const size_t MaxFFTSize = 16384; + +namespace WebCore { + +ConvolverNode::ConvolverNode(AudioContext* context, double sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setType(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. + OwnPtr<Reverb> reverb = adoptPtr(new Reverb(&bufferBus, AudioNode::ProcessingSizeInFrames, MaxFFTSize, 2, true)); + + { + // 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 0000000..7b71ba9 --- /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, double 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*, double 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 0000000..d3eb475 --- /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/DelayDSPKernel.cpp b/Source/WebCore/webaudio/DelayDSPKernel.cpp new file mode 100644 index 0000000..9cb0450 --- /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 double DefaultMaxDelayTime = 1.0; +const double SmoothingTimeConstant = 0.020; // 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.resize(static_cast<size_t>(processor->sampleRate() * DefaultMaxDelayTime)); + m_buffer.zero(); + + m_smoothingRate = AudioUtilities::discreteTimeConstantForSampleRate(SmoothingTimeConstant, processor->sampleRate()); +} + +DelayDSPKernel::DelayDSPKernel(double maxDelayTime, double 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.resize(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; + + double 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 0000000..2ae36cb --- /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, double 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 0000000..29fceae --- /dev/null +++ b/Source/WebCore/webaudio/DelayNode.cpp @@ -0,0 +1,47 @@ +/* + * 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, double sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new DelayProcessor(sampleRate, 1)); + setType(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 0000000..93ad227 --- /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, double sampleRate) + { + return adoptRef(new DelayNode(context, sampleRate)); + } + + AudioParam* delayTime(); + +private: + DelayNode(AudioContext*, double 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 0000000..7756627 --- /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 0000000..5fdc8df --- /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(double 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 0000000..4844c4b --- /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(double 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/HighPass2FilterNode.cpp b/Source/WebCore/webaudio/HighPass2FilterNode.cpp new file mode 100644 index 0000000..ca33784 --- /dev/null +++ b/Source/WebCore/webaudio/HighPass2FilterNode.cpp @@ -0,0 +1,42 @@ +/* + * 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, double sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new BiquadProcessor(BiquadProcessor::HighPass2, sampleRate, 1, false)); + setType(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 0000000..be0beb6 --- /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, double sampleRate) + { + return adoptRef(new HighPass2FilterNode(context, sampleRate)); + } + + AudioParam* cutoff() { return biquadProcessor()->parameter1(); } + AudioParam* resonance() { return biquadProcessor()->parameter2(); } + +private: + HighPass2FilterNode(AudioContext*, double 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 0000000..399f9b5 --- /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 0000000..15a8cf7 --- /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 "Float32Array.h" +#include <wtf/MainThread.h> + +namespace WebCore { + +const size_t DefaultBufferSize = 4096; + +PassRefPtr<JavaScriptAudioNode> JavaScriptAudioNode::create(AudioContext* context, double sampleRate, size_t bufferSize, unsigned numberOfInputs, unsigned numberOfOutputs) +{ + return adoptRef(new JavaScriptAudioNode(context, sampleRate, bufferSize, numberOfInputs, numberOfOutputs)); +} + +JavaScriptAudioNode::JavaScriptAudioNode(AudioContext* context, double 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))); + + setType(NodeTypeJavaScript); + + initialize(); +} + +JavaScriptAudioNode::~JavaScriptAudioNode() +{ + uninitialize(); +} + +void JavaScriptAudioNode::initialize() +{ + if (isInitialized()) + return; + + double 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(); +} + +JavaScriptAudioNode* JavaScriptAudioNode::toJavaScriptAudioNode() +{ + return this; +} + +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; + callOnMainThread(fireProcessEventDispatch, this); + m_isRequestOutstanding = true; + } + + 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(); + } +} + +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 0000000..e99a25d --- /dev/null +++ b/Source/WebCore/webaudio/JavaScriptAudioNode.h @@ -0,0 +1,104 @@ +/* + * 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/PassRefPtr.h> +#include <wtf/RefPtr.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class AudioBuffer; +class AudioContext; +class AudioProcessingEvent; +class Float32Array; + +// 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. + +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*, double 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 ScriptExecutionContext* scriptExecutionContext() const; + virtual JavaScriptAudioNode* toJavaScriptAudioNode(); + 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*, double 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 0000000..ef5359b --- /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 0000000..691f4ed --- /dev/null +++ b/Source/WebCore/webaudio/LowPass2FilterNode.cpp @@ -0,0 +1,42 @@ +/* + * 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, double sampleRate) + : AudioBasicProcessorNode(context, sampleRate) +{ + m_processor = adoptPtr(new BiquadProcessor(BiquadProcessor::LowPass2, sampleRate, 1, false)); + setType(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 0000000..43d7051 --- /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, double sampleRate) + { + return adoptRef(new LowPass2FilterNode(context, sampleRate)); + } + + AudioParam* cutoff() { return biquadProcessor()->parameter1(); } + AudioParam* resonance() { return biquadProcessor()->parameter2(); } + +private: + LowPass2FilterNode(AudioContext*, double 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 0000000..310c21e --- /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/RealtimeAnalyser.cpp b/Source/WebCore/webaudio/RealtimeAnalyser.cpp new file mode 100644 index 0000000..30a7de1 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyser.cpp @@ -0,0 +1,301 @@ +/* + * 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" + +#if ENABLE(3D_CANVAS) +#include "Float32Array.h" +#include "Uint8Array.h" +#endif + +#include <algorithm> +#include <limits.h> +#include <wtf/Complex.h> +#include <wtf/MathExtras.h> +#include <wtf/Threading.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; +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) { + // FIXME: It would be good to also set an exception. + return; + } + + if (m_fftSize != size) { + m_analysisFrame = adoptPtr(new FFTFrame(m_fftSize)); + m_magnitudeBuffer.resize(size); + 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. + // FIXME : optimize with memcpy(). + unsigned writeIndex = m_writeIndex; + for (unsigned i = 0; i < fftSize; ++i) + tempP[i] = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize]; + + // Window the input samples. + applyWindow(tempP, fftSize); + + // Do the analysis. + m_analysisFrame->doFFT(tempP); + + size_t n = DefaultFFTSize / 2; + + 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(); + for (unsigned 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); + } +} + +#if ENABLE(3D_CANVAS) + +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); + } + } +} + +#endif // 3D_CANVAS + +} // 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 0000000..686c17c --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyser.h @@ -0,0 +1,103 @@ +/* + * 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/NonCopyable.h> +#include <wtf/OwnPtr.h> + +namespace WebCore { + +class AudioBus; +class FFTFrame; + +#if ENABLE(3D_CANVAS) +class Float32Array; +class Uint8Array; +#endif + +class RealtimeAnalyser : public Noncopyable { +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); } + +#if ENABLE(3D_CANVAS) + void getFloatFrequencyData(Float32Array*); + void getByteFrequencyData(Uint8Array*); + void getByteTimeDomainData(Uint8Array*); +#endif + + // 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 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 0000000..2ba751a --- /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, double sampleRate) + : AudioNode(context, sampleRate) +{ + addInput(adoptPtr(new AudioNodeInput(this))); + addOutput(adoptPtr(new AudioNodeOutput(this, 2))); + + setType(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 0000000..9f62464 --- /dev/null +++ b/Source/WebCore/webaudio/RealtimeAnalyserNode.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 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" + +namespace WebCore { + +class RealtimeAnalyserNode : public AudioNode { +public: + static PassRefPtr<RealtimeAnalyserNode> create(AudioContext* context, double 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(); } + +#if ENABLE(3D_CANVAS) + void getFloatFrequencyData(Float32Array* array) { m_analyser.getFloatFrequencyData(array); } + void getByteFrequencyData(Uint8Array* array) { m_analyser.getByteFrequencyData(array); } + void getByteTimeDomainData(Uint8Array* array) { m_analyser.getByteTimeDomainData(array); } +#endif + +private: + RealtimeAnalyserNode(AudioContext*, double 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 0000000..5b2b223 --- /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. + [Conditional=3D_CANVAS] void getFloatFrequencyData(in Float32Array array); + [Conditional=3D_CANVAS] void getByteFrequencyData(in Uint8Array array); + + // Real-time waveform data + [Conditional=3D_CANVAS] void getByteTimeDomainData(in Uint8Array array); + }; +} |