/* * 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 "third_party/blink/renderer/modules/webaudio/audio_node.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_audio_node_options.h" #include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h" #include "third_party/blink/renderer/modules/webaudio/audio_node_input.h" #include "third_party/blink/renderer/modules/webaudio/audio_node_output.h" #include "third_party/blink/renderer/modules/webaudio/audio_node_wiring.h" #include "third_party/blink/renderer/modules/webaudio/audio_param.h" #include "third_party/blink/renderer/modules/webaudio/base_audio_context.h" #include "third_party/blink/renderer/platform/bindings/exception_messages.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/instrumentation/instance_counters.h" #if DEBUG_AUDIONODE_REFERENCES #include #endif namespace blink { AudioHandler::AudioHandler(NodeType node_type, AudioNode& node, float sample_rate) : last_processing_time_(-1), last_non_silent_time_(0), is_initialized_(false), node_type_(kNodeTypeUnknown), node_(&node), context_(node.context()), deferred_task_handler_(&context_->GetDeferredTaskHandler()), connection_ref_count_(0), is_disabled_(false), channel_count_(2) { SetNodeType(node_type); SetInternalChannelCountMode(kMax); SetInternalChannelInterpretation(AudioBus::kSpeakers); #if DEBUG_AUDIONODE_REFERENCES if (!is_node_count_initialized_) { is_node_count_initialized_ = true; atexit(AudioHandler::PrintNodeCounts); } #endif InstanceCounters::IncrementCounter(InstanceCounters::kAudioHandlerCounter); #if DEBUG_AUDIONODE_REFERENCES fprintf( stderr, "[%16p]: %16p: %2d: AudioHandler::AudioHandler() %d [%d] total: %u\n", Context(), this, GetNodeType(), connection_ref_count_, node_count_[GetNodeType()], InstanceCounters::CounterValue(InstanceCounters::kAudioHandlerCounter)); #endif node.context()->WarnIfContextClosed(this); } AudioHandler::~AudioHandler() { DCHECK(IsMainThread()); InstanceCounters::DecrementCounter(InstanceCounters::kAudioHandlerCounter); #if DEBUG_AUDIONODE_REFERENCES --node_count_[GetNodeType()]; fprintf( stderr, "[%16p]: %16p: %2d: AudioHandler::~AudioHandler() %d [%d] remaining: " "%u\n", Context(), this, GetNodeType(), connection_ref_count_, node_count_[GetNodeType()], InstanceCounters::CounterValue(InstanceCounters::kAudioHandlerCounter)); #endif } void AudioHandler::Initialize() { DCHECK_EQ(new_channel_count_mode_, channel_count_mode_); DCHECK_EQ(new_channel_interpretation_, channel_interpretation_); is_initialized_ = true; } void AudioHandler::Uninitialize() { is_initialized_ = false; } void AudioHandler::Dispose() { DCHECK(IsMainThread()); deferred_task_handler_->AssertGraphOwner(); deferred_task_handler_->RemoveChangedChannelCountMode(this); deferred_task_handler_->RemoveChangedChannelInterpretation(this); deferred_task_handler_->RemoveAutomaticPullNode(this); for (auto& output : outputs_) output->Dispose(); } AudioNode* AudioHandler::GetNode() const { DCHECK(IsMainThread()); return node_; } BaseAudioContext* AudioHandler::Context() const { return context_; } String AudioHandler::NodeTypeName() const { switch (node_type_) { case kNodeTypeDestination: return "AudioDestinationNode"; case kNodeTypeOscillator: return "OscillatorNode"; case kNodeTypeAudioBufferSource: return "AudioBufferSourceNode"; case kNodeTypeMediaElementAudioSource: return "MediaElementAudioSourceNode"; case kNodeTypeMediaStreamAudioDestination: return "MediaStreamAudioDestinationNode"; case kNodeTypeMediaStreamAudioSource: return "MediaStreamAudioSourceNode"; case kNodeTypeScriptProcessor: return "ScriptProcessorNode"; case kNodeTypeBiquadFilter: return "BiquadFilterNode"; case kNodeTypePanner: return "PannerNode"; case kNodeTypeStereoPanner: return "StereoPannerNode"; case kNodeTypeConvolver: return "ConvolverNode"; case kNodeTypeDelay: return "DelayNode"; case kNodeTypeGain: return "GainNode"; case kNodeTypeChannelSplitter: return "ChannelSplitterNode"; case kNodeTypeChannelMerger: return "ChannelMergerNode"; case kNodeTypeAnalyser: return "AnalyserNode"; case kNodeTypeDynamicsCompressor: return "DynamicsCompressorNode"; case kNodeTypeWaveShaper: return "WaveShaperNode"; case kNodeTypeIIRFilter: return "IIRFilterNode"; case kNodeTypeConstantSource: return "ConstantSourceNode"; case kNodeTypeAudioWorklet: return "AudioWorkletNode"; case kNodeTypeUnknown: case kNodeTypeEnd: default: NOTREACHED(); return "UnknownNode"; } } void AudioHandler::SetNodeType(NodeType type) { // Don't allow the node type to be changed to a different node type, after // it's already been set. And the new type can't be unknown or end. DCHECK_EQ(node_type_, kNodeTypeUnknown); DCHECK_NE(type, kNodeTypeUnknown); DCHECK_NE(type, kNodeTypeEnd); node_type_ = type; #if DEBUG_AUDIONODE_REFERENCES ++node_count_[type]; fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::AudioHandler [%3d]\n", Context(), this, GetNodeType(), node_count_[GetNodeType()]); #endif } void AudioHandler::AddInput() { inputs_.push_back(std::make_unique(*this)); } void AudioHandler::AddOutput(unsigned number_of_channels) { DCHECK(IsMainThread()); outputs_.push_back( std::make_unique(this, number_of_channels)); GetNode()->DidAddOutput(NumberOfOutputs()); } AudioNodeInput& AudioHandler::Input(unsigned i) { return *inputs_[i]; } AudioNodeOutput& AudioHandler::Output(unsigned i) { return *outputs_[i]; } const AudioNodeOutput& AudioHandler::Output(unsigned i) const { return *outputs_[i]; } unsigned AudioHandler::ChannelCount() { return channel_count_; } void AudioHandler::SetInternalChannelCountMode(ChannelCountMode mode) { channel_count_mode_ = mode; new_channel_count_mode_ = mode; } void AudioHandler::SetInternalChannelInterpretation( AudioBus::ChannelInterpretation interpretation) { channel_interpretation_ = interpretation; new_channel_interpretation_ = interpretation; } void AudioHandler::SetChannelCount(unsigned channel_count, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(Context()); if (channel_count > 0 && channel_count <= BaseAudioContext::MaxNumberOfChannels()) { if (channel_count_ != channel_count) { channel_count_ = channel_count; if (channel_count_mode_ != kMax) UpdateChannelsForInputs(); } } else { exception_state.ThrowDOMException( DOMExceptionCode::kNotSupportedError, ExceptionMessages::IndexOutsideRange( "channel count", channel_count, 1, ExceptionMessages::kInclusiveBound, BaseAudioContext::MaxNumberOfChannels(), ExceptionMessages::kInclusiveBound)); } } String AudioHandler::GetChannelCountMode() { // Because we delay the actual setting of the mode to the pre or post // rendering phase, we want to return the value that was set, not the actual // current mode. switch (new_channel_count_mode_) { case kMax: return "max"; case kClampedMax: return "clamped-max"; case kExplicit: return "explicit"; } NOTREACHED(); return ""; } void AudioHandler::SetChannelCountMode(const String& mode, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(Context()); ChannelCountMode old_mode = channel_count_mode_; if (mode == "max") { new_channel_count_mode_ = kMax; } else if (mode == "clamped-max") { new_channel_count_mode_ = kClampedMax; } else if (mode == "explicit") { new_channel_count_mode_ = kExplicit; } else { NOTREACHED(); } if (new_channel_count_mode_ != old_mode) Context()->GetDeferredTaskHandler().AddChangedChannelCountMode(this); } String AudioHandler::ChannelInterpretation() { // Because we delay the actual setting of the interpreation to the pre or // post rendering phase, we want to return the value that was set, not the // actual current interpretation. switch (new_channel_interpretation_) { case AudioBus::kSpeakers: return "speakers"; case AudioBus::kDiscrete: return "discrete"; } NOTREACHED(); return ""; } void AudioHandler::SetChannelInterpretation(const String& interpretation, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(Context()); AudioBus::ChannelInterpretation old_mode = channel_interpretation_; if (interpretation == "speakers") { new_channel_interpretation_ = AudioBus::kSpeakers; } else if (interpretation == "discrete") { new_channel_interpretation_ = AudioBus::kDiscrete; } else { NOTREACHED(); } if (new_channel_interpretation_ != old_mode) Context()->GetDeferredTaskHandler().AddChangedChannelInterpretation(this); } void AudioHandler::UpdateChannelsForInputs() { for (auto& input : inputs_) input->ChangedOutputs(); } void AudioHandler::ProcessIfNecessary(uint32_t frames_to_process) { DCHECK(Context()->IsAudioThread()); if (!IsInitialized()) return; // Ensure that we only process once per rendering quantum. // This handles the "fanout" problem where an output is connected to multiple // inputs. The first time we're called during this time slice we process, but // after that we don't want to re-process, instead our output(s) will already // have the results cached in their bus; double current_time = Context()->currentTime(); if (last_processing_time_ != current_time) { // important to first update this time because of feedback loops in the // rendering graph. last_processing_time_ = current_time; PullInputs(frames_to_process); bool silent_inputs = InputsAreSilent(); if (silent_inputs && PropagatesSilence()) { SilenceOutputs(); // AudioParams still need to be processed so that the value can be updated // if there are automations or so that the upstream nodes get pulled if // any are connected to the AudioParam. ProcessOnlyAudioParams(frames_to_process); } else { // Unsilence the outputs first because the processing of the node may // cause the outputs to go silent and we want to propagate that hint to // the downstream nodes. (For example, a Gain node with a gain of 0 will // want to silence its output.) UnsilenceOutputs(); Process(frames_to_process); } if (!silent_inputs) { // Update |last_non_silent_time| AFTER processing this block. // Doing it before causes |PropagateSilence()| to be one render // quantum longer than necessary. last_non_silent_time_ = (Context()->CurrentSampleFrame() + frames_to_process) / static_cast(Context()->sampleRate()); } } } void AudioHandler::CheckNumberOfChannelsForInput(AudioNodeInput* input) { DCHECK(Context()->IsAudioThread()); deferred_task_handler_->AssertGraphOwner(); DCHECK(inputs_.Contains(input)); input->UpdateInternalBus(); } bool AudioHandler::PropagatesSilence() const { return last_non_silent_time_ + LatencyTime() + TailTime() < Context()->currentTime(); } void AudioHandler::PullInputs(uint32_t frames_to_process) { DCHECK(Context()->IsAudioThread()); // Process all of the AudioNodes connected to our inputs. for (auto& input : inputs_) input->Pull(nullptr, frames_to_process); } bool AudioHandler::InputsAreSilent() { for (auto& input : inputs_) { if (!input->Bus()->IsSilent()) return false; } return true; } void AudioHandler::SilenceOutputs() { for (auto& output : outputs_) output->Bus()->Zero(); } void AudioHandler::UnsilenceOutputs() { for (auto& output : outputs_) output->Bus()->ClearSilentFlag(); } void AudioHandler::EnableOutputsIfNecessary() { DCHECK(IsMainThread()); deferred_task_handler_->AssertGraphOwner(); // We're enabling outputs for this handler. Remove this from the tail // processing list (if it's there) so that we don't inadvertently disable the // outputs later on when the tail processing time has elapsed. Context()->GetDeferredTaskHandler().RemoveTailProcessingHandler(this, false); #if DEBUG_AUDIONODE_REFERENCES > 1 fprintf(stderr, "[%16p]: %16p: %2d: EnableOutputsIfNecessary: is_disabled %d count " "%d output size %u\n", Context(), this, GetNodeType(), is_disabled_, connection_ref_count_, outputs_.size()); #endif if (is_disabled_ && connection_ref_count_ > 0) { is_disabled_ = false; for (auto& output : outputs_) output->Enable(); } } void AudioHandler::DisableOutputsIfNecessary() { // This function calls other functions that require graph ownership, // so assert that this needs graph ownership too. deferred_task_handler_->AssertGraphOwner(); #if DEBUG_AUDIONODE_REFERENCES > 1 fprintf(stderr, "[%16p]: %16p: %2d: DisableOutputsIfNecessary is_disabled %d count %d" " tail %d\n", Context(), this, GetNodeType(), is_disabled_, connection_ref_count_, RequiresTailProcessing()); #endif // Disable outputs if appropriate. We do this if the number of connections is // 0 or 1. The case of 0 is from deref() where there are no connections left. // The case of 1 is from AudioNodeInput::disable() where we want to disable // outputs when there's only one connection left because we're ready to go // away, but can't quite yet. if (connection_ref_count_ <= 1 && !is_disabled_) { // Still may have JavaScript references, but no more "active" connection // references, so put all of our outputs in a "dormant" disabled state. // Garbage collection may take a very long time after this time, so the // "dormant" disabled nodes should not bog down the rendering... // As far as JavaScript is concerned, our outputs must still appear to be // connected. But internally our outputs should be disabled from the inputs // they're connected to. disable() can recursively deref connections (and // call disable()) down a whole chain of connected nodes. // If a node requires tail processing, we defer the disabling of // the outputs so that the tail for the node can be output. // Otherwise, we can disable the outputs right away. if (RequiresTailProcessing()) { if (deferred_task_handler_->AcceptsTailProcessing()) deferred_task_handler_->AddTailProcessingHandler(this); } else { DisableOutputs(); } } } void AudioHandler::DisableOutputs() { is_disabled_ = true; for (auto& output : outputs_) output->Disable(); } void AudioHandler::MakeConnection() { deferred_task_handler_->AssertGraphOwner(); connection_ref_count_++; #if DEBUG_AUDIONODE_REFERENCES fprintf( stderr, "[%16p]: %16p: %2d: AudioHandler::MakeConnection %3d [%3d] @%.15g\n", Context(), this, GetNodeType(), connection_ref_count_, node_count_[GetNodeType()], Context()->currentTime()); #endif // See the disabling code in disableOutputsIfNecessary(). This handles // the case where a node is being re-connected after being used at least // once and disconnected. In this case, we need to re-enable. EnableOutputsIfNecessary(); } void AudioHandler::BreakConnectionWithLock() { deferred_task_handler_->AssertGraphOwner(); connection_ref_count_--; #if DEBUG_AUDIONODE_REFERENCES fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::BreakConnectionWitLock %3d [%3d] " "@%.15g\n", Context(), this, GetNodeType(), connection_ref_count_, node_count_[GetNodeType()], Context()->currentTime()); #endif if (!connection_ref_count_) DisableOutputsIfNecessary(); } #if DEBUG_AUDIONODE_REFERENCES bool AudioHandler::is_node_count_initialized_ = false; int AudioHandler::node_count_[kNodeTypeEnd]; void AudioHandler::PrintNodeCounts() { fprintf(stderr, "\n\n"); fprintf(stderr, "===========================\n"); fprintf(stderr, "AudioNode: reference counts\n"); fprintf(stderr, "===========================\n"); for (unsigned i = 0; i < kNodeTypeEnd; ++i) fprintf(stderr, "%2d: %d\n", i, node_count_[i]); fprintf(stderr, "===========================\n\n\n"); } #endif // DEBUG_AUDIONODE_REFERENCES #if DEBUG_AUDIONODE_REFERENCES > 1 void AudioHandler::TailProcessingDebug(const char* note, bool flag) { fprintf(stderr, "[%16p]: %16p: %2d: %s %d @%.15g flag=%d", Context(), this, GetNodeType(), note, connection_ref_count_, Context()->currentTime(), flag); // If we're on the audio thread, we can print out the tail and // latency times (because these methods can only be called from the // audio thread.) if (Context()->IsAudioThread()) { fprintf(stderr, ", tail=%.15g + %.15g, last=%.15g\n", TailTime(), LatencyTime(), last_non_silent_time_); } fprintf(stderr, "\n"); } void AudioHandler::AddTailProcessingDebug() { TailProcessingDebug("addTail", false); } void AudioHandler::RemoveTailProcessingDebug(bool disable_outputs) { TailProcessingDebug("remTail", disable_outputs); } #endif // DEBUG_AUDIONODE_REFERENCES > 1 void AudioHandler::UpdateChannelCountMode() { channel_count_mode_ = new_channel_count_mode_; UpdateChannelsForInputs(); } void AudioHandler::UpdateChannelInterpretation() { channel_interpretation_ = new_channel_interpretation_; } unsigned AudioHandler::NumberOfOutputChannels() const { // This should only be called for ScriptProcessorNodes which are the only // nodes where you can have an output with 0 channels. All other nodes have // have at least one output channel, so there's no reason other nodes should // ever call this function. DCHECK(0) << "numberOfOutputChannels() not valid for node type " << GetNodeType(); return 1; } // ---------------------------------------------------------------- AudioNode::AudioNode(BaseAudioContext& context) : InspectorHelperMixin(context.GraphTracer(), context.Uuid()), context_(context), deferred_task_handler_(&context.GetDeferredTaskHandler()), handler_(nullptr) {} AudioNode::~AudioNode() { // The graph lock is required to destroy the handler. And we can't use // |context_| to touch it, since that object may also be a dead heap object. { DeferredTaskHandler::GraphAutoLocker locker(*deferred_task_handler_); handler_ = nullptr; } } void AudioNode::Dispose() { DCHECK(IsMainThread()); #if DEBUG_AUDIONODE_REFERENCES fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::dispose %16p @%g\n", context(), this, Handler().GetNodeType(), handler_.get(), context()->currentTime()); #endif BaseAudioContext::GraphAutoLocker locker(context()); Handler().Dispose(); // Add the handler to the orphan list if the context is pulling on the audio // graph. This keeps the handler alive until it can be deleted at a safe // point (in pre/post handler task). If graph isn't being pulled, we can // delete the handler now since nothing on the audio thread will be touching // it. DCHECK(context()); if (context()->IsPullingAudioGraph()) { context()->GetDeferredTaskHandler().AddRenderingOrphanHandler( std::move(handler_)); } // Notify the inspector that this node is going away. The actual clean up // will be done in the subclass implementation. ReportWillBeDestroyed(); } void AudioNode::SetHandler(scoped_refptr handler) { DCHECK(handler); handler_ = std::move(handler); // Unless the node is an AudioDestinationNode, notify the inspector that the // construction is completed. The actual report will be done in the subclass // implementation. (A destination node is owned by the context and will be // reported by it.) if (handler_->GetNodeType() != AudioHandler::NodeType::kNodeTypeDestination) ReportDidCreate(); #if DEBUG_AUDIONODE_REFERENCES fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::AudioNode %16p\n", context(), this, handler_->GetNodeType(), handler_.get()); #endif } bool AudioNode::ContainsHandler() const { return handler_.get(); } AudioHandler& AudioNode::Handler() const { return *handler_; } void AudioNode::Trace(Visitor* visitor) const { visitor->Trace(context_); visitor->Trace(connected_nodes_); visitor->Trace(connected_params_); InspectorHelperMixin::Trace(visitor); EventTargetWithInlineData::Trace(visitor); } void AudioNode::HandleChannelOptions(const AudioNodeOptions* options, ExceptionState& exception_state) { DCHECK(IsMainThread()); if (options->hasChannelCount()) setChannelCount(options->channelCount(), exception_state); if (options->hasChannelCountMode()) setChannelCountMode(options->channelCountMode(), exception_state); if (options->hasChannelInterpretation()) setChannelInterpretation(options->channelInterpretation(), exception_state); } BaseAudioContext* AudioNode::context() const { return context_; } AudioNode* AudioNode::connect(AudioNode* destination, unsigned output_index, unsigned input_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); context()->WarnForConnectionIfContextClosed(); if (!destination) { exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, "invalid destination node."); return nullptr; } // Sanity check input and output indices. if (output_index >= numberOfOutputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, "output index (" + String::Number(output_index) + ") exceeds number of outputs (" + String::Number(numberOfOutputs()) + ")."); return nullptr; } if (destination && input_index >= destination->numberOfInputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, "input index (" + String::Number(input_index) + ") exceeds number of inputs (" + String::Number(destination->numberOfInputs()) + ")."); return nullptr; } if (context() != destination->context()) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidAccessError, "cannot connect to a destination " "belonging to a different audio context."); return nullptr; } // ScriptProcessorNodes with 0 output channels can't be connected to any // destination. If there are no output channels, what would the destination // receive? Just disallow this. if (Handler().GetNodeType() == AudioHandler::kNodeTypeScriptProcessor && Handler().NumberOfOutputChannels() == 0) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, "cannot connect a ScriptProcessorNode " "with 0 output channels to any " "destination node."); return nullptr; } AudioNodeWiring::Connect(Handler().Output(output_index), destination->Handler().Input(input_index)); if (!connected_nodes_[output_index]) { connected_nodes_[output_index] = MakeGarbageCollected>>(); } connected_nodes_[output_index]->insert(destination); Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidConnectNodes(this, destination, output_index, input_index); return destination; } void AudioNode::connect(AudioParam* param, unsigned output_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); context()->WarnForConnectionIfContextClosed(); if (!param) { exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError, "invalid AudioParam."); return; } if (output_index >= numberOfOutputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, "output index (" + String::Number(output_index) + ") exceeds number of outputs (" + String::Number(numberOfOutputs()) + ")."); return; } if (context() != param->Context()) { exception_state.ThrowDOMException( DOMExceptionCode::kSyntaxError, "cannot connect to an AudioParam " "belonging to a different audio context."); return; } AudioNodeWiring::Connect(Handler().Output(output_index), param->Handler()); if (!connected_params_[output_index]) { connected_params_[output_index] = MakeGarbageCollected>>(); } connected_params_[output_index]->insert(param); Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidConnectNodeParam(this, param, output_index); } void AudioNode::DisconnectAllFromOutput(unsigned output_index) { Handler().Output(output_index).DisconnectAll(); connected_nodes_[output_index] = nullptr; connected_params_[output_index] = nullptr; } bool AudioNode::DisconnectFromOutputIfConnected( unsigned output_index, AudioNode& destination, unsigned input_index_of_destination) { AudioNodeOutput& output = Handler().Output(output_index); AudioNodeInput& input = destination.Handler().Input(input_index_of_destination); if (!AudioNodeWiring::IsConnected(output, input)) return false; AudioNodeWiring::Disconnect(output, input); connected_nodes_[output_index]->erase(&destination); return true; } bool AudioNode::DisconnectFromOutputIfConnected(unsigned output_index, AudioParam& param) { AudioNodeOutput& output = Handler().Output(output_index); if (!AudioNodeWiring::IsConnected(output, param.Handler())) return false; AudioNodeWiring::Disconnect(output, param.Handler()); connected_params_[output_index]->erase(¶m); return true; } void AudioNode::disconnect() { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); // Disconnect all outgoing connections. for (unsigned i = 0; i < numberOfOutputs(); ++i) DisconnectAllFromOutput(i); Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodes(this); } void AudioNode::disconnect(unsigned output_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); // Sanity check on the output index. if (output_index >= numberOfOutputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, ExceptionMessages::IndexOutsideRange( "output index", output_index, 0u, ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1, ExceptionMessages::kInclusiveBound)); return; } // Disconnect all outgoing connections from the given output. DisconnectAllFromOutput(output_index); Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodes(this, nullptr, output_index); } void AudioNode::disconnect(AudioNode* destination, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); unsigned number_of_disconnections = 0; // FIXME: Can this be optimized? ChannelSplitter and ChannelMerger can have // 32 ports and that requires 1024 iterations to validate entire connections. for (unsigned output_index = 0; output_index < numberOfOutputs(); ++output_index) { for (unsigned input_index = 0; input_index < destination->Handler().NumberOfInputs(); ++input_index) { if (DisconnectFromOutputIfConnected(output_index, *destination, input_index)) number_of_disconnections++; } } // If there is no connection to the destination, throw an exception. if (number_of_disconnections == 0) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidAccessError, "the given destination is not connected."); return; } Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodes(this, destination); } void AudioNode::disconnect(AudioNode* destination, unsigned output_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); if (output_index >= numberOfOutputs()) { // The output index is out of range. Throw an exception. exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, ExceptionMessages::IndexOutsideRange( "output index", output_index, 0u, ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1, ExceptionMessages::kInclusiveBound)); return; } // If the output index is valid, proceed to disconnect. unsigned number_of_disconnections = 0; // Sanity check on destination inputs and disconnect when possible. for (unsigned input_index = 0; input_index < destination->numberOfInputs(); ++input_index) { if (DisconnectFromOutputIfConnected(output_index, *destination, input_index)) number_of_disconnections++; } // If there is no connection to the destination, throw an exception. if (number_of_disconnections == 0) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidAccessError, "output (" + String::Number(output_index) + ") is not connected to the given destination."); } Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodes(this, destination, output_index); } void AudioNode::disconnect(AudioNode* destination, unsigned output_index, unsigned input_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); if (output_index >= numberOfOutputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, ExceptionMessages::IndexOutsideRange( "output index", output_index, 0u, ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1, ExceptionMessages::kInclusiveBound)); return; } if (input_index >= destination->Handler().NumberOfInputs()) { exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, ExceptionMessages::IndexOutsideRange( "input index", input_index, 0u, ExceptionMessages::kInclusiveBound, destination->numberOfInputs() - 1, ExceptionMessages::kInclusiveBound)); return; } // If both indices are valid, proceed to disconnect. if (!DisconnectFromOutputIfConnected(output_index, *destination, input_index)) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidAccessError, "output (" + String::Number(output_index) + ") is not connected to the input (" + String::Number(input_index) + ") of the destination."); return; } Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodes( this, destination, output_index, input_index); } void AudioNode::disconnect(AudioParam* destination_param, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); // The number of disconnection made. unsigned number_of_disconnections = 0; // Check if the node output is connected the destination AudioParam. // Disconnect if connected and increase |numberOfDisconnectios| by 1. for (unsigned output_index = 0; output_index < Handler().NumberOfOutputs(); ++output_index) { if (DisconnectFromOutputIfConnected(output_index, *destination_param)) number_of_disconnections++; } // Throw an exception when there is no valid connection to the destination. if (number_of_disconnections == 0) { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidAccessError, "the given AudioParam is not connected."); return; } Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodeParam(this, destination_param); } void AudioNode::disconnect(AudioParam* destination_param, unsigned output_index, ExceptionState& exception_state) { DCHECK(IsMainThread()); BaseAudioContext::GraphAutoLocker locker(context()); if (output_index >= Handler().NumberOfOutputs()) { // The output index is out of range. Throw an exception. exception_state.ThrowDOMException( DOMExceptionCode::kIndexSizeError, ExceptionMessages::IndexOutsideRange( "output index", output_index, 0u, ExceptionMessages::kInclusiveBound, numberOfOutputs() - 1, ExceptionMessages::kInclusiveBound)); return; } // If the output index is valid, proceed to disconnect. if (!DisconnectFromOutputIfConnected(output_index, *destination_param)) { exception_state.ThrowDOMException( DOMExceptionCode::kInvalidAccessError, "specified destination AudioParam and node output (" + String::Number(output_index) + ") are not connected."); return; } Handler().UpdatePullStatusIfNeeded(); GraphTracer().DidDisconnectNodeParam(this, destination_param, output_index); } unsigned AudioNode::numberOfInputs() const { return Handler().NumberOfInputs(); } unsigned AudioNode::numberOfOutputs() const { return Handler().NumberOfOutputs(); } unsigned AudioNode::channelCount() const { return Handler().ChannelCount(); } void AudioNode::setChannelCount(unsigned count, ExceptionState& exception_state) { Handler().SetChannelCount(count, exception_state); } String AudioNode::channelCountMode() const { return Handler().GetChannelCountMode(); } void AudioNode::setChannelCountMode(const String& mode, ExceptionState& exception_state) { Handler().SetChannelCountMode(mode, exception_state); } String AudioNode::channelInterpretation() const { return Handler().ChannelInterpretation(); } void AudioNode::setChannelInterpretation(const String& interpretation, ExceptionState& exception_state) { Handler().SetChannelInterpretation(interpretation, exception_state); } const AtomicString& AudioNode::InterfaceName() const { return event_target_names::kAudioNode; } ExecutionContext* AudioNode::GetExecutionContext() const { return context()->GetExecutionContext(); } void AudioNode::DidAddOutput(unsigned number_of_outputs) { connected_nodes_.push_back(nullptr); DCHECK_EQ(number_of_outputs, connected_nodes_.size()); connected_params_.push_back(nullptr); DCHECK_EQ(number_of_outputs, connected_params_.size()); } } // namespace blink