|  | /* | 
|  | * 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 "modules/webaudio/AudioBufferSourceNode.h" | 
|  | #include <algorithm> | 
|  | #include "bindings/core/v8/ExceptionMessages.h" | 
|  | #include "bindings/core/v8/ExceptionState.h" | 
|  | #include "core/dom/ExceptionCode.h" | 
|  | #include "core/frame/UseCounter.h" | 
|  | #include "modules/webaudio/AudioBufferSourceOptions.h" | 
|  | #include "modules/webaudio/AudioNodeOutput.h" | 
|  | #include "modules/webaudio/BaseAudioContext.h" | 
|  | #include "platform/audio/AudioUtilities.h" | 
|  | #include "platform/wtf/MathExtras.h" | 
|  | #include "platform/wtf/PtrUtil.h" | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | const double kDefaultGrainDuration = 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 kMaxRate = 1024; | 
|  |  | 
|  | // Number of extra frames to use when determining if a source node can be | 
|  | // stopped.  This should be at least one rendering quantum, but we add one more | 
|  | // quantum for good measure.  This doesn't need to be extra precise, just more | 
|  | // than one rendering quantum.  See |handleStoppableSourceNode()|. | 
|  | // FIXME: Expose the rendering quantum somehow instead of hardwiring a value | 
|  | // here. | 
|  | const int kExtraStopFrames = 256; | 
|  |  | 
|  | AudioBufferSourceHandler::AudioBufferSourceHandler( | 
|  | AudioNode& node, | 
|  | float sample_rate, | 
|  | AudioParamHandler& playback_rate, | 
|  | AudioParamHandler& detune) | 
|  | : AudioScheduledSourceHandler(kNodeTypeAudioBufferSource, | 
|  | node, | 
|  | sample_rate), | 
|  | buffer_(nullptr), | 
|  | playback_rate_(&playback_rate), | 
|  | detune_(&detune), | 
|  | is_looping_(false), | 
|  | did_set_looping_(false), | 
|  | loop_start_(0), | 
|  | loop_end_(0), | 
|  | virtual_read_index_(0), | 
|  | is_grain_(false), | 
|  | grain_offset_(0.0), | 
|  | grain_duration_(kDefaultGrainDuration), | 
|  | min_playback_rate_(1.0), | 
|  | buffer_has_been_set_(false) { | 
|  | // Default to mono. A call to setBuffer() will set the number of output | 
|  | // channels to that of the buffer. | 
|  | AddOutput(1); | 
|  |  | 
|  | Initialize(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<AudioBufferSourceHandler> AudioBufferSourceHandler::Create( | 
|  | AudioNode& node, | 
|  | float sample_rate, | 
|  | AudioParamHandler& playback_rate, | 
|  | AudioParamHandler& detune) { | 
|  | return base::AdoptRef( | 
|  | new AudioBufferSourceHandler(node, sample_rate, playback_rate, detune)); | 
|  | } | 
|  |  | 
|  | AudioBufferSourceHandler::~AudioBufferSourceHandler() { | 
|  | Uninitialize(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::Process(size_t frames_to_process) { | 
|  | AudioBus* output_bus = Output(0).Bus(); | 
|  |  | 
|  | if (!IsInitialized()) { | 
|  | output_bus->Zero(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The audio thread can't block on this lock, so we call tryLock() instead. | 
|  | MutexTryLocker try_locker(process_lock_); | 
|  | if (try_locker.Locked()) { | 
|  | if (!Buffer()) { | 
|  | output_bus->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()) { | 
|  | output_bus->Zero(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | size_t quantum_frame_offset; | 
|  | size_t buffer_frames_to_process; | 
|  | double start_time_offset; | 
|  |  | 
|  | UpdateSchedulingInfo(frames_to_process, output_bus, quantum_frame_offset, | 
|  | buffer_frames_to_process, start_time_offset); | 
|  |  | 
|  | if (!buffer_frames_to_process) { | 
|  | output_bus->Zero(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (unsigned i = 0; i < output_bus->NumberOfChannels(); ++i) | 
|  | destination_channels_[i] = output_bus->Channel(i)->MutableData(); | 
|  |  | 
|  | // Render by reading directly from the buffer. | 
|  | if (!RenderFromBuffer(output_bus, quantum_frame_offset, | 
|  | buffer_frames_to_process)) { | 
|  | output_bus->Zero(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | output_bus->ClearSilentFlag(); | 
|  | } else { | 
|  | // Too bad - the tryLock() failed.  We must be in the middle of changing | 
|  | // buffers and were already outputting silence anyway. | 
|  | output_bus->Zero(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Returns true if we're finished. | 
|  | bool AudioBufferSourceHandler::RenderSilenceAndFinishIfNotLooping( | 
|  | AudioBus*, | 
|  | unsigned index, | 
|  | size_t frames_to_process) { | 
|  | if (!Loop()) { | 
|  | // If we're not looping, then stop playing when we get to the end. | 
|  |  | 
|  | if (frames_to_process > 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(destination_channels_[i] + index, 0, | 
|  | sizeof(float) * frames_to_process); | 
|  | } | 
|  |  | 
|  | Finish(); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | bool AudioBufferSourceHandler::RenderFromBuffer( | 
|  | AudioBus* bus, | 
|  | unsigned destination_frame_offset, | 
|  | size_t number_of_frames) { | 
|  | DCHECK(Context()->IsAudioThread()); | 
|  |  | 
|  | // Basic sanity checking | 
|  | DCHECK(bus); | 
|  | DCHECK(Buffer()); | 
|  | if (!bus || !Buffer()) | 
|  | return false; | 
|  |  | 
|  | unsigned number_of_channels = this->NumberOfChannels(); | 
|  | unsigned bus_number_of_channels = bus->NumberOfChannels(); | 
|  |  | 
|  | bool channel_count_good = | 
|  | number_of_channels && number_of_channels == bus_number_of_channels; | 
|  | DCHECK(channel_count_good); | 
|  | if (!channel_count_good) | 
|  | return false; | 
|  |  | 
|  | // Sanity check destinationFrameOffset, numberOfFrames. | 
|  | size_t destination_length = bus->length(); | 
|  |  | 
|  | bool is_length_good = | 
|  | destination_length <= AudioUtilities::kRenderQuantumFrames && | 
|  | number_of_frames <= AudioUtilities::kRenderQuantumFrames; | 
|  | DCHECK(is_length_good); | 
|  | if (!is_length_good) | 
|  | return false; | 
|  |  | 
|  | bool is_offset_good = | 
|  | destination_frame_offset <= destination_length && | 
|  | destination_frame_offset + number_of_frames <= destination_length; | 
|  | DCHECK(is_offset_good); | 
|  | if (!is_offset_good) | 
|  | return false; | 
|  |  | 
|  | // Potentially zero out initial frames leading up to the offset. | 
|  | if (destination_frame_offset) { | 
|  | for (unsigned i = 0; i < number_of_channels; ++i) | 
|  | memset(destination_channels_[i], 0, | 
|  | sizeof(float) * destination_frame_offset); | 
|  | } | 
|  |  | 
|  | // Offset the pointers to the correct offset frame. | 
|  | unsigned write_index = destination_frame_offset; | 
|  |  | 
|  | size_t buffer_length = Buffer()->length(); | 
|  | double buffer_sample_rate = Buffer()->sampleRate(); | 
|  |  | 
|  | // Avoid converting from time to sample-frames twice by computing | 
|  | // the grain end time first before computing the sample frame. | 
|  | unsigned end_frame = | 
|  | is_grain_ ? AudioUtilities::TimeToSampleFrame( | 
|  | grain_offset_ + grain_duration_, buffer_sample_rate) | 
|  | : buffer_length; | 
|  |  | 
|  | // 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 (is_grain_) | 
|  | end_frame += 512; | 
|  |  | 
|  | // Do some sanity checking. | 
|  | if (end_frame > buffer_length) | 
|  | end_frame = buffer_length; | 
|  |  | 
|  | // 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 virtual_end_frame = end_frame; | 
|  | double virtual_delta_frames = end_frame; | 
|  |  | 
|  | if (Loop() && (loop_start_ || loop_end_) && loop_start_ >= 0 && | 
|  | loop_end_ > 0 && loop_start_ < loop_end_) { | 
|  | // Convert from seconds to sample-frames. | 
|  | double loop_start_frame = loop_start_ * Buffer()->sampleRate(); | 
|  | double loop_end_frame = loop_end_ * Buffer()->sampleRate(); | 
|  |  | 
|  | virtual_end_frame = std::min(loop_end_frame, virtual_end_frame); | 
|  | virtual_delta_frames = virtual_end_frame - loop_start_frame; | 
|  | } | 
|  |  | 
|  | // If we're looping and the offset (virtualReadIndex) is past the end of the | 
|  | // loop, wrap back to the beginning of the loop. For other cases, nothing | 
|  | // needs to be done. | 
|  | if (Loop() && virtual_read_index_ >= virtual_end_frame) { | 
|  | virtual_read_index_ = | 
|  | (loop_start_ < 0) ? 0 : (loop_start_ * Buffer()->sampleRate()); | 
|  | virtual_read_index_ = | 
|  | std::min(virtual_read_index_, static_cast<double>(buffer_length - 1)); | 
|  | } | 
|  |  | 
|  | double computed_playback_rate = ComputePlaybackRate(); | 
|  |  | 
|  | // Sanity check that our playback rate isn't larger than the loop size. | 
|  | if (computed_playback_rate > virtual_delta_frames) | 
|  | return false; | 
|  |  | 
|  | // Get local copy. | 
|  | double virtual_read_index = virtual_read_index_; | 
|  |  | 
|  | // Render loop - reading from the source buffer to the destination using | 
|  | // linear interpolation. | 
|  | int frames_to_process = number_of_frames; | 
|  |  | 
|  | const float** source_channels = source_channels_.get(); | 
|  | float** destination_channels = destination_channels_.get(); | 
|  |  | 
|  | DCHECK_GE(virtual_read_index, 0); | 
|  | DCHECK_GE(virtual_delta_frames, 0); | 
|  | DCHECK_GE(virtual_end_frame, 0); | 
|  |  | 
|  | // Optimize for the very common case of playing back with | 
|  | // computedPlaybackRate == 1.  We can avoid the linear interpolation. | 
|  | if (computed_playback_rate == 1 && | 
|  | virtual_read_index == floor(virtual_read_index) && | 
|  | virtual_delta_frames == floor(virtual_delta_frames) && | 
|  | virtual_end_frame == floor(virtual_end_frame)) { | 
|  | unsigned read_index = static_cast<unsigned>(virtual_read_index); | 
|  | unsigned delta_frames = static_cast<unsigned>(virtual_delta_frames); | 
|  | end_frame = static_cast<unsigned>(virtual_end_frame); | 
|  | while (frames_to_process > 0) { | 
|  | int frames_to_end = end_frame - read_index; | 
|  | int frames_this_time = std::min(frames_to_process, frames_to_end); | 
|  | frames_this_time = std::max(0, frames_this_time); | 
|  |  | 
|  | DCHECK_LE(write_index + frames_this_time, destination_length); | 
|  | DCHECK_LE(read_index + frames_this_time, buffer_length); | 
|  |  | 
|  | for (unsigned i = 0; i < number_of_channels; ++i) | 
|  | memcpy(destination_channels[i] + write_index, | 
|  | source_channels[i] + read_index, | 
|  | sizeof(float) * frames_this_time); | 
|  |  | 
|  | write_index += frames_this_time; | 
|  | read_index += frames_this_time; | 
|  | frames_to_process -= frames_this_time; | 
|  |  | 
|  | // It can happen that framesThisTime is 0. DCHECK that we will actually | 
|  | // exit the loop in this case.  framesThisTime is 0 only if | 
|  | // readIndex >= endFrame; | 
|  | DCHECK(frames_this_time ? true : read_index >= end_frame); | 
|  |  | 
|  | // Wrap-around. | 
|  | if (read_index >= end_frame) { | 
|  | read_index -= delta_frames; | 
|  | if (RenderSilenceAndFinishIfNotLooping(bus, write_index, | 
|  | frames_to_process)) | 
|  | break; | 
|  | } | 
|  | } | 
|  | virtual_read_index = read_index; | 
|  | } else { | 
|  | while (frames_to_process--) { | 
|  | unsigned read_index = static_cast<unsigned>(virtual_read_index); | 
|  | double interpolation_factor = virtual_read_index - read_index; | 
|  |  | 
|  | // For linear interpolation we need the next sample-frame too. | 
|  | unsigned read_index2 = read_index + 1; | 
|  | if (read_index2 >= buffer_length) { | 
|  | if (Loop()) { | 
|  | // Make sure to wrap around at the end of the buffer. | 
|  | read_index2 = static_cast<unsigned>(virtual_read_index + 1 - | 
|  | virtual_delta_frames); | 
|  | } else { | 
|  | read_index2 = read_index; | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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 (read_index >= buffer_length || read_index2 >= buffer_length) | 
|  | break; | 
|  |  | 
|  | // Linear interpolation. | 
|  | for (unsigned i = 0; i < number_of_channels; ++i) { | 
|  | float* destination = destination_channels[i]; | 
|  | const float* source = source_channels[i]; | 
|  |  | 
|  | double sample1 = source[read_index]; | 
|  | double sample2 = source[read_index2]; | 
|  | double sample = (1.0 - interpolation_factor) * sample1 + | 
|  | interpolation_factor * sample2; | 
|  |  | 
|  | destination[write_index] = clampTo<float>(sample); | 
|  | } | 
|  | write_index++; | 
|  |  | 
|  | virtual_read_index += computed_playback_rate; | 
|  |  | 
|  | // Wrap-around, retaining sub-sample position since virtualReadIndex is | 
|  | // floating-point. | 
|  | if (virtual_read_index >= virtual_end_frame) { | 
|  | virtual_read_index -= virtual_delta_frames; | 
|  | if (RenderSilenceAndFinishIfNotLooping(bus, write_index, | 
|  | frames_to_process)) | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bus->ClearSilentFlag(); | 
|  |  | 
|  | virtual_read_index_ = virtual_read_index; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::SetBuffer(AudioBuffer* buffer, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | if (buffer && buffer_has_been_set_) { | 
|  | exception_state.ThrowDOMException(kInvalidStateError, | 
|  | "Cannot set buffer to non-null after it " | 
|  | "has been already been set to a non-null " | 
|  | "buffer"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The context must be locked since changing the buffer can re-configure the | 
|  | // number of channels that are output. | 
|  | BaseAudioContext::GraphAutoLocker context_locker(Context()); | 
|  |  | 
|  | // This synchronizes with process(). | 
|  | MutexLocker process_locker(process_lock_); | 
|  |  | 
|  | if (buffer) { | 
|  | buffer_has_been_set_ = true; | 
|  |  | 
|  | // Do any necesssary re-configuration to the buffer's number of channels. | 
|  | unsigned number_of_channels = buffer->numberOfChannels(); | 
|  |  | 
|  | // This should not be possible since AudioBuffers can't be created with too | 
|  | // many channels either. | 
|  | if (number_of_channels > BaseAudioContext::MaxNumberOfChannels()) { | 
|  | exception_state.ThrowDOMException( | 
|  | kNotSupportedError, | 
|  | ExceptionMessages::IndexOutsideRange( | 
|  | "number of input channels", number_of_channels, 1u, | 
|  | ExceptionMessages::kInclusiveBound, | 
|  | BaseAudioContext::MaxNumberOfChannels(), | 
|  | ExceptionMessages::kInclusiveBound)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | Output(0).SetNumberOfChannels(number_of_channels); | 
|  |  | 
|  | source_channels_ = WrapArrayUnique(new const float*[number_of_channels]); | 
|  | destination_channels_ = WrapArrayUnique(new float*[number_of_channels]); | 
|  |  | 
|  | for (unsigned i = 0; i < number_of_channels; ++i) | 
|  | source_channels_[i] = buffer->getChannelData(i).View()->Data(); | 
|  |  | 
|  | // If this is a grain (as set by a previous call to start()), validate the | 
|  | // grain parameters now since it wasn't validated when start was called | 
|  | // (because there was no buffer then). | 
|  | if (is_grain_) | 
|  | ClampGrainParameters(buffer); | 
|  | } | 
|  |  | 
|  | virtual_read_index_ = 0; | 
|  | buffer_ = buffer; | 
|  | } | 
|  |  | 
|  | unsigned AudioBufferSourceHandler::NumberOfChannels() { | 
|  | return Output(0).NumberOfChannels(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::ClampGrainParameters(const AudioBuffer* buffer) { | 
|  | DCHECK(buffer); | 
|  |  | 
|  | // We have a buffer so we can clip the offset and duration to lie within the | 
|  | // buffer. | 
|  | double buffer_duration = buffer->duration(); | 
|  |  | 
|  | grain_offset_ = clampTo(grain_offset_, 0.0, buffer_duration); | 
|  |  | 
|  | // If the duration was not explicitly given, use the buffer duration to set | 
|  | // the grain duration. Otherwise, we want to use the user-specified value, of | 
|  | // course. | 
|  | if (!is_duration_given_) | 
|  | grain_duration_ = buffer_duration - grain_offset_; | 
|  |  | 
|  | if (is_duration_given_ && Loop()) { | 
|  | // We're looping a grain with a grain duration specified. Schedule the loop | 
|  | // to stop after grainDuration seconds after starting, possibly running the | 
|  | // loop multiple times if grainDuration is larger than the buffer duration. | 
|  | // The net effect is as if the user called stop(when + grainDuration). | 
|  | grain_duration_ = | 
|  | clampTo(grain_duration_, 0.0, std::numeric_limits<double>::infinity()); | 
|  | end_time_ = start_time_ + grain_duration_; | 
|  | } else { | 
|  | grain_duration_ = | 
|  | clampTo(grain_duration_, 0.0, buffer_duration - grain_offset_); | 
|  | } | 
|  |  | 
|  | // 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. | 
|  | virtual_read_index_ = | 
|  | AudioUtilities::TimeToSampleFrame(grain_offset_, buffer->sampleRate()); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::Start(double when, | 
|  | ExceptionState& exception_state) { | 
|  | AudioScheduledSourceHandler::Start(when, exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::Start(double when, | 
|  | double grain_offset, | 
|  | ExceptionState& exception_state) { | 
|  | StartSource(when, grain_offset, Buffer() ? Buffer()->duration() : 0, false, | 
|  | exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::Start(double when, | 
|  | double grain_offset, | 
|  | double grain_duration, | 
|  | ExceptionState& exception_state) { | 
|  | StartSource(when, grain_offset, grain_duration, true, exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::StartSource(double when, | 
|  | double grain_offset, | 
|  | double grain_duration, | 
|  | bool is_duration_given, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | Context()->MaybeRecordStartAttempt(); | 
|  |  | 
|  | if (GetPlaybackState() != UNSCHEDULED_STATE) { | 
|  | exception_state.ThrowDOMException(kInvalidStateError, | 
|  | "cannot call start more than once."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (when < 0) { | 
|  | exception_state.ThrowRangeError( | 
|  | ExceptionMessages::IndexExceedsMinimumBound("start time", when, 0.0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (grain_offset < 0) { | 
|  | exception_state.ThrowRangeError(ExceptionMessages::IndexExceedsMinimumBound( | 
|  | "offset", grain_offset, 0.0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (grain_duration < 0) { | 
|  | exception_state.ThrowRangeError(ExceptionMessages::IndexExceedsMinimumBound( | 
|  | "duration", grain_duration, 0.0)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // The node is started. Add a reference to keep us alive so that audio | 
|  | // will eventually get played even if Javascript should drop all references | 
|  | // to this node. The reference will get dropped when the source has finished | 
|  | // playing. | 
|  | Context()->NotifySourceNodeStartedProcessing(GetNode()); | 
|  |  | 
|  | // This synchronizes with process(). updateSchedulingInfo will read some of | 
|  | // the variables being set here. | 
|  | MutexLocker process_locker(process_lock_); | 
|  |  | 
|  | is_duration_given_ = is_duration_given; | 
|  | is_grain_ = true; | 
|  | grain_offset_ = grain_offset; | 
|  | grain_duration_ = grain_duration; | 
|  |  | 
|  | // If |when| < currentTime, the source must start now according to the spec. | 
|  | // So just set startTime to currentTime in this case to start the source now. | 
|  | start_time_ = std::max(when, Context()->currentTime()); | 
|  |  | 
|  | if (Buffer()) | 
|  | ClampGrainParameters(Buffer()); | 
|  |  | 
|  | SetPlaybackState(SCHEDULED_STATE); | 
|  | } | 
|  |  | 
|  | double AudioBufferSourceHandler::ComputePlaybackRate() { | 
|  | // Incorporate buffer's sample-rate versus BaseAudioContext's sample-rate. | 
|  | // Normally it's not an issue because buffers are loaded at the | 
|  | // BaseAudioContext's sample-rate, but we can handle it in any case. | 
|  | double sample_rate_factor = 1.0; | 
|  | if (Buffer()) { | 
|  | // Use doubles to compute this to full accuracy. | 
|  | sample_rate_factor = | 
|  | Buffer()->sampleRate() / static_cast<double>(Context()->sampleRate()); | 
|  | } | 
|  |  | 
|  | // Use finalValue() to incorporate changes of AudioParamTimeline and | 
|  | // AudioSummingJunction from m_playbackRate AudioParam. | 
|  | double base_playback_rate = playback_rate_->FinalValue(); | 
|  |  | 
|  | double final_playback_rate = sample_rate_factor * base_playback_rate; | 
|  |  | 
|  | // Take the detune value into account for the final playback rate. | 
|  | final_playback_rate *= pow(2, detune_->FinalValue() / 1200); | 
|  |  | 
|  | // Sanity check the total rate.  It's very important that the resampler not | 
|  | // get any bad rate values. | 
|  | final_playback_rate = clampTo(final_playback_rate, 0.0, kMaxRate); | 
|  |  | 
|  | bool is_playback_rate_valid = | 
|  | !std::isnan(final_playback_rate) && !std::isinf(final_playback_rate); | 
|  | DCHECK(is_playback_rate_valid); | 
|  |  | 
|  | if (!is_playback_rate_valid) | 
|  | final_playback_rate = 1.0; | 
|  |  | 
|  | // Record the minimum playback rate for use by HandleStoppableSourceNode. | 
|  | if (final_playback_rate < min_playback_rate_) { | 
|  | MutexLocker locker(min_playback_rate_mutex_); | 
|  | min_playback_rate_ = final_playback_rate; | 
|  | } | 
|  |  | 
|  | return final_playback_rate; | 
|  | } | 
|  |  | 
|  | double AudioBufferSourceHandler::GetMinPlaybackRate() { | 
|  | DCHECK(IsMainThread()); | 
|  | MutexLocker locker(min_playback_rate_mutex_); | 
|  | return min_playback_rate_; | 
|  | } | 
|  |  | 
|  | bool AudioBufferSourceHandler::PropagatesSilence() const { | 
|  | return !IsPlayingOrScheduled() || HasFinished() || !buffer_; | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceHandler::HandleStoppableSourceNode() { | 
|  | // If the source node is not looping, and we have a buffer, we can determine | 
|  | // when the source would stop playing.  This is intended to handle the | 
|  | // (uncommon) scenario where start() has been called but is never connected to | 
|  | // the destination (directly or indirectly).  By stopping the node, the node | 
|  | // can be collected.  Otherwise, the node will never get collected, leaking | 
|  | // memory. | 
|  | // | 
|  | // If looping was ever done (m_didSetLooping = true), give up.  We can't | 
|  | // easily determine how long we looped so we don't know the actual duration | 
|  | // thus far, so don't try to do anything fancy. | 
|  | double min_playback_rate = GetMinPlaybackRate(); | 
|  | if (!DidSetLooping() && Buffer() && IsPlayingOrScheduled() && | 
|  | min_playback_rate > 0) { | 
|  | // Adjust the duration to include the playback rate. Only need to account | 
|  | // for rate < 1 which makes the sound last longer.  For rate >= 1, the | 
|  | // source stops sooner, but that's ok. | 
|  | double actual_duration = Buffer()->duration() / min_playback_rate; | 
|  |  | 
|  | double stop_time = start_time_ + actual_duration; | 
|  |  | 
|  | // See crbug.com/478301. If a source node is started via start(), the source | 
|  | // may not start at that time but one quantum (128 frames) later.  But we | 
|  | // compute the stop time based on the start time and the duration, so we end | 
|  | // up stopping one quantum early.  Thus, add a little extra time; we just | 
|  | // need to stop the source sometime after it should have stopped if it | 
|  | // hadn't already.  We don't need to be super precise on when to stop. | 
|  | double extra_stop_time = | 
|  | kExtraStopFrames / static_cast<double>(Context()->sampleRate()); | 
|  |  | 
|  | stop_time += extra_stop_time; | 
|  | if (Context()->currentTime() > stop_time) { | 
|  | // The context time has passed the time when the source nodes should have | 
|  | // stopped playing. Stop the node now and deref it. (But don't run the | 
|  | // onEnded event because the source never actually played.) | 
|  | FinishWithoutOnEnded(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // ---------------------------------------------------------------- | 
|  | AudioBufferSourceNode::AudioBufferSourceNode(BaseAudioContext& context) | 
|  | : AudioScheduledSourceNode(context), | 
|  | playback_rate_(AudioParam::Create(context, | 
|  | kParamTypeAudioBufferSourcePlaybackRate, | 
|  | "AudioBufferSource.playbackRate", | 
|  | 1.0)), | 
|  | detune_(AudioParam::Create(context, | 
|  | kParamTypeAudioBufferSourceDetune, | 
|  | "AudioBufferSource.detune", | 
|  | 0.0)) { | 
|  | SetHandler(AudioBufferSourceHandler::Create(*this, context.sampleRate(), | 
|  | playback_rate_->Handler(), | 
|  | detune_->Handler())); | 
|  | } | 
|  |  | 
|  | AudioBufferSourceNode* AudioBufferSourceNode::Create( | 
|  | BaseAudioContext& context, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | if (context.IsContextClosed()) { | 
|  | context.ThrowExceptionForClosedState(exception_state); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return new AudioBufferSourceNode(context); | 
|  | } | 
|  |  | 
|  | AudioBufferSourceNode* AudioBufferSourceNode::Create( | 
|  | BaseAudioContext* context, | 
|  | AudioBufferSourceOptions& options, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | AudioBufferSourceNode* node = Create(*context, exception_state); | 
|  |  | 
|  | if (!node) | 
|  | return nullptr; | 
|  |  | 
|  | if (options.hasBuffer()) | 
|  | node->setBuffer(options.buffer(), exception_state); | 
|  | node->detune()->setValue(options.detune()); | 
|  | node->setLoop(options.loop()); | 
|  | node->setLoopEnd(options.loopEnd()); | 
|  | node->setLoopStart(options.loopStart()); | 
|  | node->playbackRate()->setValue(options.playbackRate()); | 
|  |  | 
|  | return node; | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::Trace(blink::Visitor* visitor) { | 
|  | visitor->Trace(playback_rate_); | 
|  | visitor->Trace(detune_); | 
|  | AudioScheduledSourceNode::Trace(visitor); | 
|  | } | 
|  |  | 
|  | AudioBufferSourceHandler& AudioBufferSourceNode::GetAudioBufferSourceHandler() | 
|  | const { | 
|  | return static_cast<AudioBufferSourceHandler&>(Handler()); | 
|  | } | 
|  |  | 
|  | AudioBuffer* AudioBufferSourceNode::buffer() const { | 
|  | return GetAudioBufferSourceHandler().Buffer(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::setBuffer(AudioBuffer* new_buffer, | 
|  | ExceptionState& exception_state) { | 
|  | GetAudioBufferSourceHandler().SetBuffer(new_buffer, exception_state); | 
|  | } | 
|  |  | 
|  | AudioParam* AudioBufferSourceNode::playbackRate() const { | 
|  | return playback_rate_; | 
|  | } | 
|  |  | 
|  | AudioParam* AudioBufferSourceNode::detune() const { | 
|  | return detune_; | 
|  | } | 
|  |  | 
|  | bool AudioBufferSourceNode::loop() const { | 
|  | return GetAudioBufferSourceHandler().Loop(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::setLoop(bool loop) { | 
|  | GetAudioBufferSourceHandler().SetLoop(loop); | 
|  | } | 
|  |  | 
|  | double AudioBufferSourceNode::loopStart() const { | 
|  | return GetAudioBufferSourceHandler().LoopStart(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::setLoopStart(double loop_start) { | 
|  | GetAudioBufferSourceHandler().SetLoopStart(loop_start); | 
|  | } | 
|  |  | 
|  | double AudioBufferSourceNode::loopEnd() const { | 
|  | return GetAudioBufferSourceHandler().LoopEnd(); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::setLoopEnd(double loop_end) { | 
|  | GetAudioBufferSourceHandler().SetLoopEnd(loop_end); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::start(ExceptionState& exception_state) { | 
|  | GetAudioBufferSourceHandler().Start(0, exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::start(double when, | 
|  | ExceptionState& exception_state) { | 
|  | GetAudioBufferSourceHandler().Start(when, exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::start(double when, | 
|  | double grain_offset, | 
|  | ExceptionState& exception_state) { | 
|  | GetAudioBufferSourceHandler().Start(when, grain_offset, exception_state); | 
|  | } | 
|  |  | 
|  | void AudioBufferSourceNode::start(double when, | 
|  | double grain_offset, | 
|  | double grain_duration, | 
|  | ExceptionState& exception_state) { | 
|  | GetAudioBufferSourceHandler().Start(when, grain_offset, grain_duration, | 
|  | exception_state); | 
|  | } | 
|  |  | 
|  | }  // namespace blink |