blob: 55a3610b629537a549e08cd8700f2f6f8cb80381 [file] [log] [blame]
/*
* 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/modules/webaudio/audio_node_input.h"
#include "third_party/blink/renderer/modules/webaudio/audio_node_options.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/instance_counters.h"
#if DEBUG_AUDIONODE_REFERENCES
#include <stdio.h>
#endif
namespace blink {
AudioHandler::AudioHandler(NodeType node_type,
AudioNode& node,
float sample_rate)
: is_initialized_(false),
node_type_(kNodeTypeUnknown),
node_(&node),
context_(node.context()),
deferred_task_handler_(&context_->GetDeferredTaskHandler()),
last_processing_time_(-1),
last_non_silent_time_(0),
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
}
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 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(AudioNodeInput::Create(*this));
}
void AudioHandler::AddOutput(unsigned number_of_channels) {
DCHECK(IsMainThread());
outputs_.push_back(AudioNodeOutput::Create(this, number_of_channels));
GetNode()->DidAddOutput(NumberOfOutputs());
}
AudioNodeInput& AudioHandler::Input(unsigned i) {
return *inputs_[i];
}
AudioNodeOutput& AudioHandler::Output(unsigned i) {
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<unsigned long>(
"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<double>(Context()->sampleRate());
}
}
}
void AudioHandler::CheckNumberOfChannelsForInput(AudioNodeInput* input) {
DCHECK(Context()->IsAudioThread());
deferred_task_handler_->AssertGraphOwner();
DCHECK(inputs_.Contains(input));
if (!inputs_.Contains(input))
return;
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();
// 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::BreakConnection() {
// The actual work for deref happens completely within the audio context's
// graph lock. In the case of the audio thread, we must use a tryLock to
// avoid glitches.
bool has_lock = false;
if (Context()->IsAudioThread()) {
// Real-time audio thread must not contend lock (to avoid glitches).
has_lock = Context()->TryLock();
} else {
Context()->lock();
has_lock = true;
}
if (has_lock) {
BreakConnectionWithLock();
Context()->unlock();
} else {
// We were unable to get the lock, so put this in a list to finish up
// later.
DCHECK(Context()->IsAudioThread());
Context()->GetDeferredTaskHandler().AddDeferredBreakConnection(*this);
}
}
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)
: 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();
if (context()->HasRealtimeConstraint()) {
// Add the handler to the orphan list if the context is not
// uninitialized (Nothing will clean up the orphan list if the context
// is uninitialized.) These will get cleaned up in the post render task
// if audio thread is running or when the context is colleced (in
// the worst case).
if (!context()->IsContextClosed()) {
context()->GetDeferredTaskHandler().AddRenderingOrphanHandler(
std::move(handler_));
}
} else {
// For an offline context, only need to save the handler when the
// context is running. The change in the context state is
// synchronous with the main thread (even though the offline
// thread is not synchronized to the main thread).
if (context()->ContextState() == BaseAudioContext::kRunning) {
context()->GetDeferredTaskHandler().AddRenderingOrphanHandler(
std::move(handler_));
}
}
}
void AudioNode::SetHandler(scoped_refptr<AudioHandler> handler) {
DCHECK(handler);
handler_ = std::move(handler);
#if DEBUG_AUDIONODE_REFERENCES
fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::AudioNode %16p\n", context(),
this, handler_->GetNodeType(), handler_.get());
#endif
}
AudioHandler& AudioNode::Handler() const {
return *handler_;
}
void AudioNode::Trace(blink::Visitor* visitor) {
visitor->Trace(context_);
visitor->Trace(connected_nodes_);
visitor->Trace(connected_params_);
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());
if (context()->IsContextClosed()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Cannot connect after the context has been closed.");
return nullptr;
}
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<HeapHashSet<Member<AudioNode>>>();
}
connected_nodes_[output_index]->insert(destination);
Handler().UpdatePullStatusIfNeeded();
return destination;
}
void AudioNode::connect(AudioParam* param,
unsigned output_index,
ExceptionState& exception_state) {
DCHECK(IsMainThread());
BaseAudioContext::GraphAutoLocker locker(context());
if (context()->IsContextClosed()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"Cannot connect after the context has been closed.");
return;
}
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<HeapHashSet<Member<AudioParam>>>();
}
connected_params_[output_index]->insert(param);
Handler().UpdatePullStatusIfNeeded();
}
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(&param);
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();
}
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();
}
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();
}
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();
}
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();
}
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();
}
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();
}
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