| /* |
| * 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/AudioNode.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/inspector/InstanceCounters.h" |
| #include "modules/webaudio/AudioNodeInput.h" |
| #include "modules/webaudio/AudioNodeOptions.h" |
| #include "modules/webaudio/AudioNodeOutput.h" |
| #include "modules/webaudio/AudioParam.h" |
| #include "modules/webaudio/BaseAudioContext.h" |
| #include "wtf/Atomics.h" |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| #include <stdio.h> |
| #endif |
| |
| namespace blink { |
| |
| AudioHandler::AudioHandler(NodeType nodeType, AudioNode& node, float sampleRate) |
| : m_isInitialized(false), |
| m_nodeType(NodeTypeUnknown), |
| m_node(&node), |
| m_context(node.context()), |
| m_sampleRate(sampleRate), |
| m_lastProcessingTime(-1), |
| m_lastNonSilentTime(-1), |
| m_connectionRefCount(0), |
| m_isDisabled(false), |
| m_channelCount(2) { |
| setNodeType(nodeType); |
| setInternalChannelCountMode(Max); |
| setInternalChannelInterpretation(AudioBus::Speakers); |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| if (!s_isNodeCountInitialized) { |
| s_isNodeCountInitialized = true; |
| atexit(AudioHandler::printNodeCounts); |
| } |
| #endif |
| InstanceCounters::incrementCounter(InstanceCounters::AudioHandlerCounter); |
| } |
| |
| AudioHandler::~AudioHandler() { |
| DCHECK(isMainThread()); |
| // dispose() should be called. |
| DCHECK(!node()); |
| InstanceCounters::decrementCounter(InstanceCounters::AudioHandlerCounter); |
| #if DEBUG_AUDIONODE_REFERENCES |
| --s_nodeCount[getNodeType()]; |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::~AudioHandler() %d [%d]\n", |
| context(), this, getNodeType(), m_connectionRefCount, |
| s_nodeCount[getNodeType()]); |
| #endif |
| } |
| |
| void AudioHandler::initialize() { |
| DCHECK_EQ(m_newChannelCountMode, m_channelCountMode); |
| DCHECK_EQ(m_newChannelInterpretation, m_channelInterpretation); |
| |
| m_isInitialized = true; |
| } |
| |
| void AudioHandler::uninitialize() { |
| m_isInitialized = false; |
| } |
| |
| void AudioHandler::clearInternalStateWhenDisabled() {} |
| |
| void AudioHandler::dispose() { |
| DCHECK(isMainThread()); |
| ASSERT(context()->isGraphOwner()); |
| |
| context()->deferredTaskHandler().removeChangedChannelCountMode(this); |
| context()->deferredTaskHandler().removeChangedChannelInterpretation(this); |
| context()->deferredTaskHandler().removeAutomaticPullNode(this); |
| for (auto& output : m_outputs) |
| output->dispose(); |
| m_node = nullptr; |
| } |
| |
| AudioNode* AudioHandler::node() const { |
| DCHECK(isMainThread()); |
| return m_node; |
| } |
| |
| BaseAudioContext* AudioHandler::context() const { |
| return m_context; |
| } |
| |
| String AudioHandler::nodeTypeName() const { |
| switch (m_nodeType) { |
| case NodeTypeDestination: |
| return "AudioDestinationNode"; |
| case NodeTypeOscillator: |
| return "OscillatorNode"; |
| case NodeTypeAudioBufferSource: |
| return "AudioBufferSourceNode"; |
| case NodeTypeMediaElementAudioSource: |
| return "MediaElementAudioSourceNode"; |
| case NodeTypeMediaStreamAudioDestination: |
| return "MediaStreamAudioDestinationNode"; |
| case NodeTypeMediaStreamAudioSource: |
| return "MediaStreamAudioSourceNode"; |
| case NodeTypeJavaScript: |
| return "ScriptProcessorNode"; |
| case NodeTypeBiquadFilter: |
| return "BiquadFilterNode"; |
| case NodeTypePanner: |
| return "PannerNode"; |
| case NodeTypeStereoPanner: |
| return "StereoPannerNode"; |
| case NodeTypeConvolver: |
| return "ConvolverNode"; |
| case NodeTypeDelay: |
| return "DelayNode"; |
| case NodeTypeGain: |
| return "GainNode"; |
| case NodeTypeChannelSplitter: |
| return "ChannelSplitterNode"; |
| case NodeTypeChannelMerger: |
| return "ChannelMergerNode"; |
| case NodeTypeAnalyser: |
| return "AnalyserNode"; |
| case NodeTypeDynamicsCompressor: |
| return "DynamicsCompressorNode"; |
| case NodeTypeWaveShaper: |
| return "WaveShaperNode"; |
| case NodeTypeUnknown: |
| case NodeTypeEnd: |
| default: |
| ASSERT_NOT_REACHED(); |
| 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(m_nodeType, NodeTypeUnknown); |
| DCHECK_NE(type, NodeTypeUnknown); |
| DCHECK_NE(type, NodeTypeEnd); |
| |
| m_nodeType = type; |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| ++s_nodeCount[type]; |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::AudioHandler [%3d]\n", |
| context(), this, getNodeType(), s_nodeCount[getNodeType()]); |
| #endif |
| } |
| |
| void AudioHandler::addInput() { |
| m_inputs.append(AudioNodeInput::create(*this)); |
| } |
| |
| void AudioHandler::addOutput(unsigned numberOfChannels) { |
| DCHECK(isMainThread()); |
| m_outputs.append(AudioNodeOutput::create(this, numberOfChannels)); |
| node()->didAddOutput(numberOfOutputs()); |
| } |
| |
| AudioNodeInput& AudioHandler::input(unsigned i) { |
| return *m_inputs[i]; |
| } |
| |
| AudioNodeOutput& AudioHandler::output(unsigned i) { |
| return *m_outputs[i]; |
| } |
| |
| unsigned long AudioHandler::channelCount() { |
| return m_channelCount; |
| } |
| |
| void AudioHandler::setInternalChannelCountMode(ChannelCountMode mode) { |
| m_channelCountMode = mode; |
| m_newChannelCountMode = mode; |
| } |
| |
| void AudioHandler::setInternalChannelInterpretation( |
| AudioBus::ChannelInterpretation interpretation) { |
| m_channelInterpretation = interpretation; |
| m_newChannelInterpretation = interpretation; |
| } |
| |
| void AudioHandler::setChannelCount(unsigned long channelCount, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (channelCount > 0 && |
| channelCount <= BaseAudioContext::maxNumberOfChannels()) { |
| if (m_channelCount != channelCount) { |
| m_channelCount = channelCount; |
| if (m_channelCountMode != Max) |
| updateChannelsForInputs(); |
| } |
| } else { |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| ExceptionMessages::indexOutsideRange<unsigned long>( |
| "channel count", channelCount, 1, ExceptionMessages::InclusiveBound, |
| BaseAudioContext::maxNumberOfChannels(), |
| ExceptionMessages::InclusiveBound)); |
| } |
| } |
| |
| String AudioHandler::channelCountMode() { |
| // 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 (m_newChannelCountMode) { |
| case Max: |
| return "max"; |
| case ClampedMax: |
| return "clamped-max"; |
| case Explicit: |
| return "explicit"; |
| } |
| ASSERT_NOT_REACHED(); |
| return ""; |
| } |
| |
| void AudioHandler::setChannelCountMode(const String& mode, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| ChannelCountMode oldMode = m_channelCountMode; |
| |
| if (mode == "max") { |
| m_newChannelCountMode = Max; |
| } else if (mode == "clamped-max") { |
| m_newChannelCountMode = ClampedMax; |
| } else if (mode == "explicit") { |
| m_newChannelCountMode = Explicit; |
| } else { |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (m_newChannelCountMode != oldMode) |
| context()->deferredTaskHandler().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 (m_newChannelInterpretation) { |
| case AudioBus::Speakers: |
| return "speakers"; |
| case AudioBus::Discrete: |
| return "discrete"; |
| } |
| ASSERT_NOT_REACHED(); |
| return ""; |
| } |
| |
| void AudioHandler::setChannelInterpretation(const String& interpretation, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| AudioBus::ChannelInterpretation oldMode = m_channelInterpretation; |
| |
| if (interpretation == "speakers") { |
| m_newChannelInterpretation = AudioBus::Speakers; |
| } else if (interpretation == "discrete") { |
| m_newChannelInterpretation = AudioBus::Discrete; |
| } else { |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (m_newChannelInterpretation != oldMode) |
| context()->deferredTaskHandler().addChangedChannelInterpretation(this); |
| } |
| |
| void AudioHandler::updateChannelsForInputs() { |
| for (auto& input : m_inputs) |
| input->changedOutputs(); |
| } |
| |
| void AudioHandler::processIfNecessary(size_t framesToProcess) { |
| 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 currentTime = context()->currentTime(); |
| if (m_lastProcessingTime != currentTime) { |
| // important to first update this time because of feedback loops in the |
| // rendering graph. |
| m_lastProcessingTime = currentTime; |
| |
| pullInputs(framesToProcess); |
| |
| bool silentInputs = inputsAreSilent(); |
| if (!silentInputs) |
| m_lastNonSilentTime = |
| (context()->currentSampleFrame() + framesToProcess) / |
| static_cast<double>(m_sampleRate); |
| |
| if (silentInputs && propagatesSilence()) { |
| silenceOutputs(); |
| } 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(framesToProcess); |
| } |
| } |
| } |
| |
| void AudioHandler::checkNumberOfChannelsForInput(AudioNodeInput* input) { |
| DCHECK(context()->isAudioThread()); |
| ASSERT(context()->isGraphOwner()); |
| |
| DCHECK(m_inputs.contains(input)); |
| if (!m_inputs.contains(input)) |
| return; |
| |
| input->updateInternalBus(); |
| } |
| |
| double AudioHandler::tailTime() const { |
| return 0; |
| } |
| |
| double AudioHandler::latencyTime() const { |
| return 0; |
| } |
| |
| bool AudioHandler::propagatesSilence() const { |
| return m_lastNonSilentTime + latencyTime() + tailTime() < |
| context()->currentTime(); |
| } |
| |
| void AudioHandler::pullInputs(size_t framesToProcess) { |
| DCHECK(context()->isAudioThread()); |
| |
| // Process all of the AudioNodes connected to our inputs. |
| for (auto& input : m_inputs) |
| input->pull(0, framesToProcess); |
| } |
| |
| bool AudioHandler::inputsAreSilent() { |
| for (auto& input : m_inputs) { |
| if (!input->bus()->isSilent()) |
| return false; |
| } |
| return true; |
| } |
| |
| void AudioHandler::silenceOutputs() { |
| for (auto& output : m_outputs) |
| output->bus()->zero(); |
| } |
| |
| void AudioHandler::unsilenceOutputs() { |
| for (auto& output : m_outputs) |
| output->bus()->clearSilentFlag(); |
| } |
| |
| void AudioHandler::enableOutputsIfNecessary() { |
| if (m_isDisabled && m_connectionRefCount > 0) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| m_isDisabled = false; |
| for (auto& output : m_outputs) |
| output->enable(); |
| } |
| } |
| |
| void AudioHandler::disableOutputsIfNecessary() { |
| // 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 (m_connectionRefCount <= 1 && !m_isDisabled) { |
| // 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. |
| |
| // TODO(rtoy,hongchan): we need special cases the convolver, delay, biquad, |
| // and IIR since they have a significant tail-time and shouldn't be |
| // disconnected simply because they no longer have any input connections. |
| // This needs to be handled more generally where AudioNodes have a tailTime |
| // attribute. Then the AudioNode only needs to remain "active" for tailTime |
| // seconds after there are no longer any active connections. |
| if (getNodeType() != NodeTypeConvolver && getNodeType() != NodeTypeDelay && |
| getNodeType() != NodeTypeBiquadFilter && |
| getNodeType() != NodeTypeIIRFilter) { |
| m_isDisabled = true; |
| clearInternalStateWhenDisabled(); |
| for (auto& output : m_outputs) |
| output->disable(); |
| } |
| } |
| } |
| |
| void AudioHandler::makeConnection() { |
| atomicIncrement(&m_connectionRefCount); |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::ref %3d [%3d]\n", |
| context(), this, getNodeType(), m_connectionRefCount, |
| s_nodeCount[getNodeType()]); |
| #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 hasLock = false; |
| if (context()->isAudioThread()) { |
| // Real-time audio thread must not contend lock (to avoid glitches). |
| hasLock = context()->tryLock(); |
| } else { |
| context()->lock(); |
| hasLock = true; |
| } |
| |
| if (hasLock) { |
| 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()->deferredTaskHandler().addDeferredBreakConnection(*this); |
| } |
| } |
| |
| void AudioHandler::breakConnectionWithLock() { |
| atomicDecrement(&m_connectionRefCount); |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioHandler::deref %3d [%3d]\n", |
| context(), this, getNodeType(), m_connectionRefCount, |
| s_nodeCount[getNodeType()]); |
| #endif |
| |
| if (!m_connectionRefCount) |
| disableOutputsIfNecessary(); |
| } |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| |
| bool AudioHandler::s_isNodeCountInitialized = false; |
| int AudioHandler::s_nodeCount[NodeTypeEnd]; |
| |
| void AudioHandler::printNodeCounts() { |
| fprintf(stderr, "\n\n"); |
| fprintf(stderr, "===========================\n"); |
| fprintf(stderr, "AudioNode: reference counts\n"); |
| fprintf(stderr, "===========================\n"); |
| |
| for (unsigned i = 0; i < NodeTypeEnd; ++i) |
| fprintf(stderr, "%2d: %d\n", i, s_nodeCount[i]); |
| |
| fprintf(stderr, "===========================\n\n\n"); |
| } |
| |
| #endif // DEBUG_AUDIONODE_REFERENCES |
| |
| void AudioHandler::updateChannelCountMode() { |
| m_channelCountMode = m_newChannelCountMode; |
| updateChannelsForInputs(); |
| } |
| |
| void AudioHandler::updateChannelInterpretation() { |
| m_channelInterpretation = m_newChannelInterpretation; |
| } |
| |
| 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) |
| : m_context(context), m_handler(nullptr) { |
| ThreadState::current()->registerPreFinalizer(this); |
| } |
| |
| void AudioNode::dispose() { |
| DCHECK(isMainThread()); |
| #if DEBUG_AUDIONODE_REFERENCES |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::dispose %16p\n", context(), |
| this, handler().getNodeType(), m_handler.get()); |
| #endif |
| BaseAudioContext::AutoLocker locker(context()); |
| handler().dispose(); |
| if (context()->contextState() == BaseAudioContext::Running) |
| context()->deferredTaskHandler().addRenderingOrphanHandler( |
| m_handler.release()); |
| } |
| |
| void AudioNode::setHandler(PassRefPtr<AudioHandler> handler) { |
| DCHECK(handler); |
| m_handler = handler; |
| |
| #if DEBUG_AUDIONODE_REFERENCES |
| fprintf(stderr, "[%16p]: %16p: %2d: AudioNode::AudioNode %16p\n", context(), |
| this, m_handler->getNodeType(), m_handler.get()); |
| #endif |
| } |
| |
| AudioHandler& AudioNode::handler() const { |
| return *m_handler; |
| } |
| |
| DEFINE_TRACE(AudioNode) { |
| visitor->trace(m_context); |
| visitor->trace(m_connectedNodes); |
| visitor->trace(m_connectedParams); |
| EventTargetWithInlineData::trace(visitor); |
| } |
| |
| void AudioNode::handleChannelOptions(const AudioNodeOptions& options, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| |
| if (options.hasChannelCount()) |
| setChannelCount(options.channelCount(), exceptionState); |
| if (options.hasChannelCountMode()) |
| setChannelCountMode(options.channelCountMode(), exceptionState); |
| if (options.hasChannelInterpretation()) |
| setChannelInterpretation(options.channelInterpretation(), exceptionState); |
| } |
| |
| BaseAudioContext* AudioNode::context() const { |
| return m_context; |
| } |
| |
| AudioNode* AudioNode::connect(AudioNode* destination, |
| unsigned outputIndex, |
| unsigned inputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (context()->isContextClosed()) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Cannot connect after the context has been closed."); |
| return nullptr; |
| } |
| |
| if (!destination) { |
| exceptionState.throwDOMException(SyntaxError, "invalid destination node."); |
| return nullptr; |
| } |
| |
| // Sanity check input and output indices. |
| if (outputIndex >= numberOfOutputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, "output index (" + String::number(outputIndex) + |
| ") exceeds number of outputs (" + |
| String::number(numberOfOutputs()) + ")."); |
| return nullptr; |
| } |
| |
| if (destination && inputIndex >= destination->numberOfInputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, "input index (" + String::number(inputIndex) + |
| ") exceeds number of inputs (" + |
| String::number(destination->numberOfInputs()) + |
| ")."); |
| return nullptr; |
| } |
| |
| if (context() != destination->context()) { |
| exceptionState.throwDOMException(SyntaxError, |
| "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::NodeTypeJavaScript && |
| handler().numberOfOutputChannels() == 0) { |
| exceptionState.throwDOMException(InvalidAccessError, |
| "cannot connect a ScriptProcessorNode " |
| "with 0 output channels to any " |
| "destination node."); |
| return nullptr; |
| } |
| |
| destination->handler() |
| .input(inputIndex) |
| .connect(handler().output(outputIndex)); |
| if (!m_connectedNodes[outputIndex]) |
| m_connectedNodes[outputIndex] = new HeapHashSet<Member<AudioNode>>(); |
| m_connectedNodes[outputIndex]->add(destination); |
| |
| // Let context know that a connection has been made. |
| context()->incrementConnectionCount(); |
| |
| return destination; |
| } |
| |
| void AudioNode::connect(AudioParam* param, |
| unsigned outputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (context()->isContextClosed()) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Cannot connect after the context has been closed."); |
| return; |
| } |
| |
| if (!param) { |
| exceptionState.throwDOMException(SyntaxError, "invalid AudioParam."); |
| return; |
| } |
| |
| if (outputIndex >= numberOfOutputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, "output index (" + String::number(outputIndex) + |
| ") exceeds number of outputs (" + |
| String::number(numberOfOutputs()) + ")."); |
| return; |
| } |
| |
| if (context() != param->context()) { |
| exceptionState.throwDOMException(SyntaxError, |
| "cannot connect to an AudioParam " |
| "belonging to a different audio context."); |
| return; |
| } |
| |
| param->handler().connect(handler().output(outputIndex)); |
| if (!m_connectedParams[outputIndex]) |
| m_connectedParams[outputIndex] = new HeapHashSet<Member<AudioParam>>(); |
| m_connectedParams[outputIndex]->add(param); |
| } |
| |
| void AudioNode::disconnectAllFromOutput(unsigned outputIndex) { |
| handler().output(outputIndex).disconnectAll(); |
| m_connectedNodes[outputIndex] = nullptr; |
| m_connectedParams[outputIndex] = nullptr; |
| } |
| |
| bool AudioNode::disconnectFromOutputIfConnected( |
| unsigned outputIndex, |
| AudioNode& destination, |
| unsigned inputIndexOfDestination) { |
| AudioNodeOutput& output = handler().output(outputIndex); |
| AudioNodeInput& input = destination.handler().input(inputIndexOfDestination); |
| if (!output.isConnectedToInput(input)) |
| return false; |
| output.disconnectInput(input); |
| m_connectedNodes[outputIndex]->remove(&destination); |
| return true; |
| } |
| |
| bool AudioNode::disconnectFromOutputIfConnected(unsigned outputIndex, |
| AudioParam& param) { |
| AudioNodeOutput& output = handler().output(outputIndex); |
| if (!output.isConnectedToAudioParam(param.handler())) |
| return false; |
| output.disconnectAudioParam(param.handler()); |
| m_connectedParams[outputIndex]->remove(¶m); |
| return true; |
| } |
| |
| void AudioNode::disconnect() { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| // Disconnect all outgoing connections. |
| for (unsigned i = 0; i < numberOfOutputs(); ++i) |
| disconnectAllFromOutput(i); |
| } |
| |
| void AudioNode::disconnect(unsigned outputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| // Sanity check on the output index. |
| if (outputIndex >= numberOfOutputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| ExceptionMessages::indexOutsideRange( |
| "output index", outputIndex, 0u, ExceptionMessages::InclusiveBound, |
| numberOfOutputs() - 1, ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| // Disconnect all outgoing connections from the given output. |
| disconnectAllFromOutput(outputIndex); |
| } |
| |
| void AudioNode::disconnect(AudioNode* destination, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| unsigned numberOfDisconnections = 0; |
| |
| // FIXME: Can this be optimized? ChannelSplitter and ChannelMerger can have |
| // 32 ports and that requires 1024 iterations to validate entire connections. |
| for (unsigned outputIndex = 0; outputIndex < numberOfOutputs(); |
| ++outputIndex) { |
| for (unsigned inputIndex = 0; |
| inputIndex < destination->handler().numberOfInputs(); ++inputIndex) { |
| if (disconnectFromOutputIfConnected(outputIndex, *destination, |
| inputIndex)) |
| numberOfDisconnections++; |
| } |
| } |
| |
| // If there is no connection to the destination, throw an exception. |
| if (numberOfDisconnections == 0) { |
| exceptionState.throwDOMException(InvalidAccessError, |
| "the given destination is not connected."); |
| return; |
| } |
| } |
| |
| void AudioNode::disconnect(AudioNode* destination, |
| unsigned outputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (outputIndex >= numberOfOutputs()) { |
| // The output index is out of range. Throw an exception. |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| ExceptionMessages::indexOutsideRange( |
| "output index", outputIndex, 0u, ExceptionMessages::InclusiveBound, |
| numberOfOutputs() - 1, ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| |
| // If the output index is valid, proceed to disconnect. |
| unsigned numberOfDisconnections = 0; |
| // Sanity check on destination inputs and disconnect when possible. |
| for (unsigned inputIndex = 0; inputIndex < destination->numberOfInputs(); |
| ++inputIndex) { |
| if (disconnectFromOutputIfConnected(outputIndex, *destination, inputIndex)) |
| numberOfDisconnections++; |
| } |
| |
| // If there is no connection to the destination, throw an exception. |
| if (numberOfDisconnections == 0) { |
| exceptionState.throwDOMException( |
| InvalidAccessError, "output (" + String::number(outputIndex) + |
| ") is not connected to the given destination."); |
| } |
| } |
| |
| void AudioNode::disconnect(AudioNode* destination, |
| unsigned outputIndex, |
| unsigned inputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (outputIndex >= numberOfOutputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| ExceptionMessages::indexOutsideRange( |
| "output index", outputIndex, 0u, ExceptionMessages::InclusiveBound, |
| numberOfOutputs() - 1, ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| |
| if (inputIndex >= destination->handler().numberOfInputs()) { |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| ExceptionMessages::indexOutsideRange( |
| "input index", inputIndex, 0u, ExceptionMessages::InclusiveBound, |
| destination->numberOfInputs() - 1, |
| ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| |
| // If both indices are valid, proceed to disconnect. |
| if (!disconnectFromOutputIfConnected(outputIndex, *destination, inputIndex)) { |
| exceptionState.throwDOMException(InvalidAccessError, |
| "output (" + String::number(outputIndex) + |
| ") is not connected to the input (" + |
| String::number(inputIndex) + |
| ") of the destination."); |
| return; |
| } |
| } |
| |
| void AudioNode::disconnect(AudioParam* destinationParam, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| // The number of disconnection made. |
| unsigned numberOfDisconnections = 0; |
| |
| // Check if the node output is connected the destination AudioParam. |
| // Disconnect if connected and increase |numberOfDisconnectios| by 1. |
| for (unsigned outputIndex = 0; outputIndex < handler().numberOfOutputs(); |
| ++outputIndex) { |
| if (disconnectFromOutputIfConnected(outputIndex, *destinationParam)) |
| numberOfDisconnections++; |
| } |
| |
| // Throw an exception when there is no valid connection to the destination. |
| if (numberOfDisconnections == 0) { |
| exceptionState.throwDOMException(InvalidAccessError, |
| "the given AudioParam is not connected."); |
| return; |
| } |
| } |
| |
| void AudioNode::disconnect(AudioParam* destinationParam, |
| unsigned outputIndex, |
| ExceptionState& exceptionState) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| if (outputIndex >= handler().numberOfOutputs()) { |
| // The output index is out of range. Throw an exception. |
| exceptionState.throwDOMException( |
| IndexSizeError, |
| ExceptionMessages::indexOutsideRange( |
| "output index", outputIndex, 0u, ExceptionMessages::InclusiveBound, |
| numberOfOutputs() - 1, ExceptionMessages::InclusiveBound)); |
| return; |
| } |
| |
| // If the output index is valid, proceed to disconnect. |
| if (!disconnectFromOutputIfConnected(outputIndex, *destinationParam)) { |
| exceptionState.throwDOMException( |
| InvalidAccessError, |
| "specified destination AudioParam and node output (" + |
| String::number(outputIndex) + ") are not connected."); |
| return; |
| } |
| } |
| |
| void AudioNode::disconnectWithoutException(unsigned outputIndex) { |
| DCHECK(isMainThread()); |
| BaseAudioContext::AutoLocker locker(context()); |
| |
| // Sanity check input and output indices. |
| if (outputIndex >= handler().numberOfOutputs()) |
| return; |
| disconnectAllFromOutput(outputIndex); |
| } |
| |
| unsigned AudioNode::numberOfInputs() const { |
| return handler().numberOfInputs(); |
| } |
| |
| unsigned AudioNode::numberOfOutputs() const { |
| return handler().numberOfOutputs(); |
| } |
| |
| unsigned long AudioNode::channelCount() const { |
| return handler().channelCount(); |
| } |
| |
| void AudioNode::setChannelCount(unsigned long count, |
| ExceptionState& exceptionState) { |
| handler().setChannelCount(count, exceptionState); |
| } |
| |
| String AudioNode::channelCountMode() const { |
| return handler().channelCountMode(); |
| } |
| |
| void AudioNode::setChannelCountMode(const String& mode, |
| ExceptionState& exceptionState) { |
| handler().setChannelCountMode(mode, exceptionState); |
| } |
| |
| String AudioNode::channelInterpretation() const { |
| return handler().channelInterpretation(); |
| } |
| |
| void AudioNode::setChannelInterpretation(const String& interpretation, |
| ExceptionState& exceptionState) { |
| handler().setChannelInterpretation(interpretation, exceptionState); |
| } |
| |
| const AtomicString& AudioNode::interfaceName() const { |
| return EventTargetNames::AudioNode; |
| } |
| |
| ExecutionContext* AudioNode::getExecutionContext() const { |
| return context()->getExecutionContext(); |
| } |
| |
| void AudioNode::didAddOutput(unsigned numberOfOutputs) { |
| m_connectedNodes.append(nullptr); |
| DCHECK_EQ(numberOfOutputs, m_connectedNodes.size()); |
| m_connectedParams.append(nullptr); |
| DCHECK_EQ(numberOfOutputs, m_connectedParams.size()); |
| } |
| |
| } // namespace blink |