/* * 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 #include using namespace std; namespace WebCore { const double DefaultGrainDuration = 0.020; // 20ms PassRefPtr 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(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(m_grainOffset * bufferSampleRate) : 0; unsigned endFrame = m_isGrain ? static_cast(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(m_grainDuration * m_buffer->sampleRate()); bool isStereo = sourceR && destinationR; int n = framesToProcess; while (n--) { // Apply the grain envelope. float x = static_cast(m_grainFrameCount) / static_cast(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(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)