summaryrefslogtreecommitdiffstats
path: root/WebCore/webaudio/AudioBufferSourceNode.cpp
diff options
context:
space:
mode:
authorTeng-Hui Zhu <ztenghui@google.com>2010-11-10 15:31:59 -0800
committerTeng-Hui Zhu <ztenghui@google.com>2010-11-17 13:35:59 -0800
commit28040489d744e0c5d475a88663056c9040ed5320 (patch)
treec463676791e4a63e452a95f0a12b2a8519730693 /WebCore/webaudio/AudioBufferSourceNode.cpp
parenteff9be92c41913c92fb1d3b7983c071f3e718678 (diff)
downloadexternal_webkit-28040489d744e0c5d475a88663056c9040ed5320.zip
external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.gz
external_webkit-28040489d744e0c5d475a88663056c9040ed5320.tar.bz2
Merge WebKit at r71558: Initial merge by git.
Change-Id: Ib345578fa29df7e4bc72b4f00e4a6fddcb754c4c
Diffstat (limited to 'WebCore/webaudio/AudioBufferSourceNode.cpp')
-rw-r--r--WebCore/webaudio/AudioBufferSourceNode.cpp454
1 files changed, 454 insertions, 0 deletions
diff --git a/WebCore/webaudio/AudioBufferSourceNode.cpp b/WebCore/webaudio/AudioBufferSourceNode.cpp
new file mode 100644
index 0000000..7aaeb04
--- /dev/null
+++ b/WebCore/webaudio/AudioBufferSourceNode.cpp
@@ -0,0 +1,454 @@
+/*
+ * 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>
+
+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(&currentDestinationBus, 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(M_PI * 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)