|  | /* | 
|  | * 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/ConvolverNode.h" | 
|  | #include <memory> | 
|  | #include "bindings/core/v8/ExceptionState.h" | 
|  | #include "core/dom/ExceptionCode.h" | 
|  | #include "modules/webaudio/AudioBuffer.h" | 
|  | #include "modules/webaudio/AudioNodeInput.h" | 
|  | #include "modules/webaudio/AudioNodeOutput.h" | 
|  | #include "modules/webaudio/ConvolverOptions.h" | 
|  | #include "platform/audio/Reverb.h" | 
|  |  | 
|  | // Note about empirical tuning: | 
|  | // The maximum FFT size affects reverb performance and accuracy. | 
|  | // If the reverb is single-threaded and processes entirely in the real-time | 
|  | // audio thread, it's important not to make this too high.  In this case 8192 is | 
|  | // a good value.  But, the Reverb object is multi-threaded, so we want this as | 
|  | // high as possible without losing too much accuracy.  Very large FFTs will have | 
|  | // worse phase errors. Given these constraints 32768 is a good compromise. | 
|  | const size_t MaxFFTSize = 32768; | 
|  |  | 
|  | namespace blink { | 
|  |  | 
|  | ConvolverHandler::ConvolverHandler(AudioNode& node, float sample_rate) | 
|  | : AudioHandler(kNodeTypeConvolver, node, sample_rate), normalize_(true) { | 
|  | AddInput(); | 
|  | AddOutput(1); | 
|  |  | 
|  | // Node-specific default mixing rules. | 
|  | channel_count_ = 2; | 
|  | SetInternalChannelCountMode(kClampedMax); | 
|  | SetInternalChannelInterpretation(AudioBus::kSpeakers); | 
|  |  | 
|  | Initialize(); | 
|  | } | 
|  |  | 
|  | scoped_refptr<ConvolverHandler> ConvolverHandler::Create(AudioNode& node, | 
|  | float sample_rate) { | 
|  | return base::AdoptRef(new ConvolverHandler(node, sample_rate)); | 
|  | } | 
|  |  | 
|  | ConvolverHandler::~ConvolverHandler() { | 
|  | Uninitialize(); | 
|  | } | 
|  |  | 
|  | void ConvolverHandler::Process(size_t frames_to_process) { | 
|  | AudioBus* output_bus = Output(0).Bus(); | 
|  | DCHECK(output_bus); | 
|  |  | 
|  | // Synchronize with possible dynamic changes to the impulse response. | 
|  | MutexTryLocker try_locker(process_lock_); | 
|  | if (try_locker.Locked()) { | 
|  | if (!IsInitialized() || !reverb_) { | 
|  | output_bus->Zero(); | 
|  | } else { | 
|  | // Process using the convolution engine. | 
|  | // Note that we can handle the case where nothing is connected to the | 
|  | // input, in which case we'll just feed silence into the convolver. | 
|  | // FIXME:  If we wanted to get fancy we could try to factor in the 'tail | 
|  | // time' and stop processing once the tail dies down if | 
|  | // we keep getting fed silence. | 
|  | reverb_->Process(Input(0).Bus(), output_bus, frames_to_process); | 
|  | } | 
|  | } else { | 
|  | // Too bad - the tryLock() failed.  We must be in the middle of setting a | 
|  | // new impulse response. | 
|  | output_bus->Zero(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConvolverHandler::SetBuffer(AudioBuffer* buffer, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | if (!buffer) | 
|  | return; | 
|  |  | 
|  | if (buffer->sampleRate() != Context()->sampleRate()) { | 
|  | exception_state.ThrowDOMException( | 
|  | kNotSupportedError, | 
|  | "The buffer sample rate of " + String::Number(buffer->sampleRate()) + | 
|  | " does not match the context rate of " + | 
|  | String::Number(Context()->sampleRate()) + " Hz."); | 
|  | return; | 
|  | } | 
|  |  | 
|  | unsigned number_of_channels = buffer->numberOfChannels(); | 
|  | size_t buffer_length = buffer->length(); | 
|  |  | 
|  | // The current implementation supports only 1-, 2-, or 4-channel impulse | 
|  | // responses, with the 4-channel response being interpreted as true-stereo | 
|  | // (see Reverb class). | 
|  | bool is_channel_count_good = number_of_channels == 1 || | 
|  | number_of_channels == 2 || | 
|  | number_of_channels == 4; | 
|  |  | 
|  | if (!is_channel_count_good) { | 
|  | exception_state.ThrowDOMException( | 
|  | kNotSupportedError, "The buffer must have 1, 2, or 4 channels, not " + | 
|  | String::Number(number_of_channels)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Wrap the AudioBuffer by an AudioBus. It's an efficient pointer set and not | 
|  | // a memcpy().  This memory is simply used in the Reverb constructor and no | 
|  | // reference to it is kept for later use in that class. | 
|  | scoped_refptr<AudioBus> buffer_bus = | 
|  | AudioBus::Create(number_of_channels, buffer_length, false); | 
|  | for (unsigned i = 0; i < number_of_channels; ++i) | 
|  | buffer_bus->SetChannelMemory(i, buffer->getChannelData(i).View()->Data(), | 
|  | buffer_length); | 
|  |  | 
|  | buffer_bus->SetSampleRate(buffer->sampleRate()); | 
|  |  | 
|  | // Create the reverb with the given impulse response. | 
|  | std::unique_ptr<Reverb> reverb = std::make_unique<Reverb>( | 
|  | buffer_bus.get(), AudioUtilities::kRenderQuantumFrames, MaxFFTSize, | 
|  | Context() && Context()->HasRealtimeConstraint(), normalize_); | 
|  |  | 
|  | { | 
|  | // The context must be locked since changing the buffer can | 
|  | // re-configure the number of channels that are output. | 
|  | BaseAudioContext::GraphAutoLocker context_locker(Context()); | 
|  |  | 
|  | // Synchronize with process(). | 
|  | MutexLocker locker(process_lock_); | 
|  | reverb_ = std::move(reverb); | 
|  | buffer_ = buffer; | 
|  | if (buffer) { | 
|  | // This will propagate the channel count to any nodes connected further | 
|  | // downstream in the graph. | 
|  | Output(0).SetNumberOfChannels(ComputeNumberOfOutputChannels( | 
|  | Input(0).NumberOfChannels(), buffer_->numberOfChannels())); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | AudioBuffer* ConvolverHandler::Buffer() { | 
|  | DCHECK(IsMainThread()); | 
|  | return buffer_.Get(); | 
|  | } | 
|  |  | 
|  | double ConvolverHandler::TailTime() const { | 
|  | MutexTryLocker try_locker(process_lock_); | 
|  | if (try_locker.Locked()) | 
|  | return reverb_ ? reverb_->ImpulseResponseLength() / | 
|  | static_cast<double>(Context()->sampleRate()) | 
|  | : 0; | 
|  | // Since we don't want to block the Audio Device thread, we return a large | 
|  | // value instead of trying to acquire the lock. | 
|  | return std::numeric_limits<double>::infinity(); | 
|  | } | 
|  |  | 
|  | double ConvolverHandler::LatencyTime() const { | 
|  | MutexTryLocker try_locker(process_lock_); | 
|  | if (try_locker.Locked()) | 
|  | return reverb_ ? reverb_->LatencyFrames() / | 
|  | static_cast<double>(Context()->sampleRate()) | 
|  | : 0; | 
|  | // Since we don't want to block the Audio Device thread, we return a large | 
|  | // value instead of trying to acquire the lock. | 
|  | return std::numeric_limits<double>::infinity(); | 
|  | } | 
|  |  | 
|  | unsigned ConvolverHandler::ComputeNumberOfOutputChannels( | 
|  | unsigned input_channels, | 
|  | unsigned response_channels) const { | 
|  | // The number of output channels for a Convolver must be one or two. | 
|  | // And can only be one if there's a mono source and a mono response | 
|  | // buffer. | 
|  | return clampTo(std::max(input_channels, response_channels), 1, 2); | 
|  | } | 
|  |  | 
|  | void ConvolverHandler::SetChannelCount(unsigned long channel_count, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  | BaseAudioContext::GraphAutoLocker locker(Context()); | 
|  |  | 
|  | // channelCount must be 2. | 
|  | if (channel_count != 2) { | 
|  | exception_state.ThrowDOMException( | 
|  | kNotSupportedError, | 
|  | "ConvolverNode: channelCount cannot be changed from 2"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConvolverHandler::SetChannelCountMode(const String& mode, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  | BaseAudioContext::GraphAutoLocker locker(Context()); | 
|  |  | 
|  | // channcelCountMode must be 'clamped-max'. | 
|  | if (mode != "clamped-max") { | 
|  | exception_state.ThrowDOMException( | 
|  | kNotSupportedError, | 
|  | "ConvolverNode: channelCountMode cannot be changed from 'clamped-max'"); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ConvolverHandler::CheckNumberOfChannelsForInput(AudioNodeInput* input) { | 
|  | DCHECK(Context()->IsAudioThread()); | 
|  | DCHECK(Context()->IsGraphOwner()); | 
|  |  | 
|  | DCHECK(input); | 
|  | DCHECK_EQ(input, &this->Input(0)); | 
|  | if (input != &this->Input(0)) | 
|  | return; | 
|  |  | 
|  | if (buffer_) { | 
|  | unsigned number_of_output_channels = ComputeNumberOfOutputChannels( | 
|  | input->NumberOfChannels(), buffer_->numberOfChannels()); | 
|  |  | 
|  | if (IsInitialized() && | 
|  | number_of_output_channels != Output(0).NumberOfChannels()) { | 
|  | // We're already initialized but the channel count has changed. | 
|  | Uninitialize(); | 
|  | } | 
|  |  | 
|  | if (!IsInitialized()) { | 
|  | // This will propagate the channel count to any nodes connected further | 
|  | // downstream in the graph. | 
|  | Output(0).SetNumberOfChannels(number_of_output_channels); | 
|  | Initialize(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Update the input's internal bus if needed. | 
|  | AudioHandler::CheckNumberOfChannelsForInput(input); | 
|  | } | 
|  | // ---------------------------------------------------------------- | 
|  |  | 
|  | ConvolverNode::ConvolverNode(BaseAudioContext& context) : AudioNode(context) { | 
|  | SetHandler(ConvolverHandler::Create(*this, context.sampleRate())); | 
|  | } | 
|  |  | 
|  | ConvolverNode* ConvolverNode::Create(BaseAudioContext& context, | 
|  | ExceptionState& exception_state) { | 
|  | DCHECK(IsMainThread()); | 
|  |  | 
|  | if (context.IsContextClosed()) { | 
|  | context.ThrowExceptionForClosedState(exception_state); | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | return new ConvolverNode(context); | 
|  | } | 
|  |  | 
|  | ConvolverNode* ConvolverNode::Create(BaseAudioContext* context, | 
|  | const ConvolverOptions& options, | 
|  | ExceptionState& exception_state) { | 
|  | ConvolverNode* node = Create(*context, exception_state); | 
|  |  | 
|  | if (!node) | 
|  | return nullptr; | 
|  |  | 
|  | node->HandleChannelOptions(options, exception_state); | 
|  |  | 
|  | // It is important to set normalize first because setting the buffer will | 
|  | // examing the normalize attribute to see if normalization needs to be done. | 
|  | node->setNormalize(!options.disableNormalization()); | 
|  | if (options.hasBuffer()) | 
|  | node->setBuffer(options.buffer(), exception_state); | 
|  | return node; | 
|  | } | 
|  |  | 
|  | ConvolverHandler& ConvolverNode::GetConvolverHandler() const { | 
|  | return static_cast<ConvolverHandler&>(Handler()); | 
|  | } | 
|  |  | 
|  | AudioBuffer* ConvolverNode::buffer() const { | 
|  | return GetConvolverHandler().Buffer(); | 
|  | } | 
|  |  | 
|  | void ConvolverNode::setBuffer(AudioBuffer* new_buffer, | 
|  | ExceptionState& exception_state) { | 
|  | GetConvolverHandler().SetBuffer(new_buffer, exception_state); | 
|  | } | 
|  |  | 
|  | bool ConvolverNode::normalize() const { | 
|  | return GetConvolverHandler().Normalize(); | 
|  | } | 
|  |  | 
|  | void ConvolverNode::setNormalize(bool normalize) { | 
|  | GetConvolverHandler().SetNormalize(normalize); | 
|  | } | 
|  |  | 
|  | }  // namespace blink |