blob: 05abed88eb6cadb15758e6fdbc7a933ace3e72c5 [file] [log] [blame]
/*
* Copyright (C) 2010, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#if ENABLE(WEB_AUDIO)
#include "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(&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(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)