/* * 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 "modules/webaudio/AudioBufferSourceNode.h" #include "bindings/v8/ExceptionState.h" #include "core/dom/ExceptionCode.h" #include "core/page/PageConsole.h" #include "platform/audio/AudioUtilities.h" #include "modules/webaudio/AudioContext.h" #include "modules/webaudio/AudioNodeOutput.h" #include "platform/FloatConversion.h" #include "wtf/MainThread.h" #include "wtf/MathExtras.h" #include using namespace std; namespace WebCore { const double DefaultGrainDuration = 0.020; // 20ms // Arbitrary upper limit on playback rate. // Higher than expected rates can be useful when playing back oversampled buffers // to minimize linear interpolation aliasing. const double MaxRate = 1024; PassRefPtr AudioBufferSourceNode::create(AudioContext* context, float sampleRate) { return adoptRef(new AudioBufferSourceNode(context, sampleRate)); } AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* context, float sampleRate) : AudioScheduledSourceNode(context, sampleRate) , m_buffer(0) , m_isLooping(false) , m_loopStart(0) , m_loopEnd(0) , m_virtualReadIndex(0) , m_isGrain(false) , m_grainOffset(0.0) , m_grainDuration(DefaultGrainDuration) , m_lastGain(1.0) , m_pannerNode(0) { ScriptWrappable::init(this); setNodeType(NodeTypeAudioBufferSource); m_gain = AudioParam::create(context, "gain", 1.0, 0.0, 1.0); m_playbackRate = AudioParam::create(context, "playbackRate", 1.0, 0.0, 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() { clearPannerNode(); 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. MutexTryLocker tryLocker(m_processLock); if (tryLocker.locked()) { if (!buffer()) { outputBus->zero(); return; } // After calling setBuffer() with a buffer having a different number of channels, there can in rare cases be a slight delay // before the output bus is updated to the new number of channels because of use of tryLocks() in the context's updating system. // In this case, if the the buffer has just been changed and we're not quite ready yet, then just output silence. if (numberOfChannels() != buffer()->numberOfChannels()) { outputBus->zero(); return; } size_t quantumFrameOffset; size_t bufferFramesToProcess; updateSchedulingInfo(framesToProcess, outputBus, quantumFrameOffset, bufferFramesToProcess); if (!bufferFramesToProcess) { outputBus->zero(); return; } for (unsigned i = 0; i < outputBus->numberOfChannels(); ++i) m_destinationChannels[i] = outputBus->channel(i)->mutableData(); // Render by reading directly from the buffer. if (!renderFromBuffer(outputBus, quantumFrameOffset, bufferFramesToProcess)) { outputBus->zero(); return; } // Apply the gain (in-place) to the output bus. float totalGain = gain()->value() * m_buffer->gain(); outputBus->copyWithGainFrom(*outputBus, &m_lastGain, totalGain); outputBus->clearSilentFlag(); } else { // Too bad - the tryLock() failed. We must be in the middle of changing buffers and were already outputting silence anyway. outputBus->zero(); } } // Returns true if we're finished. bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) { if (!loop()) { // If we're not looping, then stop playing when we get to the end. 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. for (unsigned i = 0; i < numberOfChannels(); ++i) memset(m_destinationChannels[i] + index, 0, sizeof(float) * framesToProcess); } finish(); return true; } return false; } bool AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames) { ASSERT(context()->isAudioThread()); // Basic sanity checking ASSERT(bus); ASSERT(buffer()); if (!bus || !buffer()) return false; unsigned numberOfChannels = this->numberOfChannels(); unsigned busNumberOfChannels = bus->numberOfChannels(); bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels; ASSERT(channelCountGood); if (!channelCountGood) return false; // Sanity check destinationFrameOffset, numberOfFrames. size_t destinationLength = bus->length(); bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096; ASSERT(isLengthGood); if (!isLengthGood) return false; bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength; ASSERT(isOffsetGood); if (!isOffsetGood) return false; // Potentially zero out initial frames leading up to the offset. if (destinationFrameOffset) { for (unsigned i = 0; i < numberOfChannels; ++i) memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset); } // Offset the pointers to the correct offset frame. unsigned writeIndex = destinationFrameOffset; size_t bufferLength = buffer()->length(); double bufferSampleRate = buffer()->sampleRate(); // Avoid converting from time to sample-frames twice by computing // the grain end time first before computing the sample frame. unsigned endFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset + 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. // https://bugs.webkit.org/show_bug.cgi?id=77224 if (m_isGrain) endFrame += 512; // Do some sanity checking. if (endFrame > bufferLength) endFrame = bufferLength; if (m_virtualReadIndex >= endFrame) m_virtualReadIndex = 0; // reset to start // If the .loop attribute is true, then values of m_loopStart == 0 && m_loopEnd == 0 implies // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd. double virtualEndFrame = endFrame; double virtualDeltaFrames = endFrame; if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) { // Convert from seconds to sample-frames. double loopStartFrame = m_loopStart * buffer()->sampleRate(); double loopEndFrame = m_loopEnd * buffer()->sampleRate(); virtualEndFrame = min(loopEndFrame, virtualEndFrame); virtualDeltaFrames = virtualEndFrame - loopStartFrame; } double pitchRate = totalPitchRate(); // Sanity check that our playback rate isn't larger than the loop size. if (pitchRate >= virtualDeltaFrames) return false; // Get local copy. double virtualReadIndex = m_virtualReadIndex; // Render loop - reading from the source buffer to the destination using linear interpolation. int framesToProcess = numberOfFrames; const float** sourceChannels = m_sourceChannels.get(); float** destinationChannels = m_destinationChannels.get(); // Optimize for the very common case of playing back with pitchRate == 1. // We can avoid the linear interpolation. if (pitchRate == 1 && virtualReadIndex == floor(virtualReadIndex) && virtualDeltaFrames == floor(virtualDeltaFrames) && virtualEndFrame == floor(virtualEndFrame)) { unsigned readIndex = static_cast(virtualReadIndex); unsigned deltaFrames = static_cast(virtualDeltaFrames); endFrame = static_cast(virtualEndFrame); while (framesToProcess > 0) { int framesToEnd = endFrame - readIndex; int framesThisTime = min(framesToProcess, framesToEnd); framesThisTime = max(0, framesThisTime); for (unsigned i = 0; i < numberOfChannels; ++i) memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime); writeIndex += framesThisTime; readIndex += framesThisTime; framesToProcess -= framesThisTime; // Wrap-around. if (readIndex >= endFrame) { readIndex -= deltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } virtualReadIndex = readIndex; } else { while (framesToProcess--) { unsigned readIndex = static_cast(virtualReadIndex); double interpolationFactor = virtualReadIndex - readIndex; // For linear interpolation we need the next sample-frame too. unsigned readIndex2 = readIndex + 1; if (readIndex2 >= bufferLength) { if (loop()) { // Make sure to wrap around at the end of the buffer. readIndex2 = static_cast(virtualReadIndex + 1 - virtualDeltaFrames); } else readIndex2 = readIndex; } // Final sanity check on buffer access. // FIXME: as an optimization, try to get rid of this inner-loop check and put assertions and guards before the loop. if (readIndex >= bufferLength || readIndex2 >= bufferLength) break; // Linear interpolation. for (unsigned i = 0; i < numberOfChannels; ++i) { float* destination = destinationChannels[i]; const float* source = sourceChannels[i]; double sample1 = source[readIndex]; double sample2 = source[readIndex2]; double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2; destination[writeIndex] = narrowPrecisionToFloat(sample); } writeIndex++; virtualReadIndex += pitchRate; // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point. if (virtualReadIndex >= virtualEndFrame) { virtualReadIndex -= virtualDeltaFrames; if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess)) break; } } } bus->clearSilentFlag(); m_virtualReadIndex = virtualReadIndex; return true; } void AudioBufferSourceNode::reset() { m_virtualReadIndex = 0; m_lastGain = gain()->value(); } void AudioBufferSourceNode::setBuffer(AudioBuffer* buffer, ExceptionState& exceptionState) { ASSERT(isMainThread()); // FIXME: It does not look like we should throw if the buffer is null as // the attribute is nullable in the specification. if (!buffer) { exceptionState.throwTypeError("buffer cannot be null"); return; } // The context must be locked since changing the buffer can re-configure the number of channels that are output. AudioContext::AutoLocker contextLocker(context()); // This synchronizes with process(). MutexLocker processLocker(m_processLock); if (buffer) { // Do any necesssary re-configuration to the buffer's number of channels. unsigned numberOfChannels = buffer->numberOfChannels(); if (numberOfChannels > AudioContext::maxNumberOfChannels()) { exceptionState.throwTypeError("number of input channels (" + String::number(numberOfChannels) + ") exceeds maximum (" + String::number(AudioContext::maxNumberOfChannels()) + ")."); return; } output(0)->setNumberOfChannels(numberOfChannels); m_sourceChannels = adoptArrayPtr(new const float* [numberOfChannels]); m_destinationChannels = adoptArrayPtr(new float* [numberOfChannels]); for (unsigned i = 0; i < numberOfChannels; ++i) m_sourceChannels[i] = buffer->getChannelData(i)->data(); } m_virtualReadIndex = 0; m_buffer = buffer; } unsigned AudioBufferSourceNode::numberOfChannels() { return output(0)->numberOfChannels(); } void AudioBufferSourceNode::start(ExceptionState& exceptionState) { startPlaying(false, 0, 0, buffer() ? buffer()->duration() : 0, exceptionState); } void AudioBufferSourceNode::start(double when, ExceptionState& exceptionState) { startPlaying(false, when, 0, buffer() ? buffer()->duration() : 0, exceptionState); } void AudioBufferSourceNode::start(double when, double grainOffset, ExceptionState& exceptionState) { startPlaying(true, when, grainOffset, buffer() ? buffer()->duration() : 0, exceptionState); } void AudioBufferSourceNode::start(double when, double grainOffset, double grainDuration, ExceptionState& exceptionState) { startPlaying(true, when, grainOffset, grainDuration, exceptionState); } void AudioBufferSourceNode::startPlaying(bool isGrain, double when, double grainOffset, double grainDuration, ExceptionState& exceptionState) { ASSERT(isMainThread()); if (m_playbackState != UNSCHEDULED_STATE) { exceptionState.throwDOMException( InvalidStateError, "cannot call start more than once."); return; } if (!buffer()) return; if (isGrain) { // Do sanity checking of grain parameters versus buffer size. double bufferDuration = buffer()->duration(); grainOffset = max(0.0, grainOffset); grainOffset = min(bufferDuration, grainOffset); m_grainOffset = grainOffset; double maxDuration = bufferDuration - grainOffset; grainDuration = max(0.0, grainDuration); grainDuration = min(maxDuration, grainDuration); m_grainDuration = grainDuration; } m_isGrain = isGrain; m_startTime = when; // We call timeToSampleFrame here since at playbackRate == 1 we don't want to go through linear interpolation // at a sub-sample position since it will degrade the quality. // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer. // Since playbackRate == 1 is very common, it's worth considering quality. m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->sampleRate()); m_playbackState = SCHEDULED_STATE; } void AudioBufferSourceNode::noteGrainOn(double when, double grainOffset, double grainDuration, ExceptionState& exceptionState) { // Handle unspecified duration where 0 means the rest of the buffer. if (!grainDuration && buffer()) grainDuration = buffer()->duration(); startPlaying(true, when, grainOffset, grainDuration, exceptionState); } double AudioBufferSourceNode::totalPitchRate() { double dopplerRate = 1.0; if (m_pannerNode) dopplerRate = m_pannerNode->dopplerRate(); // Incorporate buffer's sample-rate versus AudioContext's sample-rate. // Normally it's not an issue because buffers are loaded at the AudioContext's sample-rate, but we can handle it in any case. double sampleRateFactor = 1.0; if (buffer()) sampleRateFactor = buffer()->sampleRate() / sampleRate(); double basePitchRate = playbackRate()->value(); double totalRate = dopplerRate * sampleRateFactor * basePitchRate; // Sanity check the total rate. It's very important that the resampler not get any bad rate values. totalRate = max(0.0, totalRate); if (!totalRate) totalRate = 1; // zero rate is considered illegal totalRate = min(MaxRate, totalRate); bool isTotalRateValid = !std::isnan(totalRate) && !std::isinf(totalRate); ASSERT(isTotalRateValid); if (!isTotalRateValid) totalRate = 1.0; return totalRate; } bool AudioBufferSourceNode::propagatesSilence() const { return !isPlayingOrScheduled() || hasFinished() || !m_buffer; } void AudioBufferSourceNode::setPannerNode(PannerNode* pannerNode) { if (m_pannerNode != pannerNode && !hasFinished()) { if (pannerNode) pannerNode->ref(AudioNode::RefTypeConnection); if (m_pannerNode) m_pannerNode->deref(AudioNode::RefTypeConnection); m_pannerNode = pannerNode; } } void AudioBufferSourceNode::clearPannerNode() { if (m_pannerNode) { m_pannerNode->deref(AudioNode::RefTypeConnection); m_pannerNode = 0; } } void AudioBufferSourceNode::finish() { clearPannerNode(); ASSERT(!m_pannerNode); AudioScheduledSourceNode::finish(); } } // namespace WebCore #endif // ENABLE(WEB_AUDIO)