summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio/AudioNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/AudioNode.cpp')
-rw-r--r--dom/media/webaudio/AudioNode.cpp608
1 files changed, 608 insertions, 0 deletions
diff --git a/dom/media/webaudio/AudioNode.cpp b/dom/media/webaudio/AudioNode.cpp
new file mode 100644
index 0000000000..f5c56d0da6
--- /dev/null
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -0,0 +1,608 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNode.h"
+#include "mozilla/ErrorResult.h"
+#include "AudioNodeTrack.h"
+#include "AudioNodeEngine.h"
+#include "mozilla/dom/AudioParam.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+
+namespace mozilla::dom {
+
+static const uint32_t INVALID_PORT = 0xffffffff;
+static uint32_t gId = 0;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper)
+ tmp->DisconnectFromGraph();
+ if (tmp->mContext) {
+ tmp->mContext->UnregisterNode(tmp);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParams)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParams)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(AudioNode, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(AudioNode, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioNode)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+AudioNode::AudioNode(AudioContext* aContext, uint32_t aChannelCount,
+ ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation)
+ : DOMEventTargetHelper(aContext->GetParentObject()),
+ mContext(aContext),
+ mChannelCount(aChannelCount),
+ mChannelCountMode(aChannelCountMode),
+ mChannelInterpretation(aChannelInterpretation),
+ mId(gId++),
+ mPassThrough(false) {
+ MOZ_ASSERT(aContext);
+ aContext->RegisterNode(this);
+}
+
+AudioNode::~AudioNode() {
+ MOZ_ASSERT(mInputNodes.IsEmpty());
+ MOZ_ASSERT(mOutputNodes.IsEmpty());
+ MOZ_ASSERT(mOutputParams.IsEmpty());
+ MOZ_ASSERT(!mTrack,
+ "The webaudio-node-demise notification must have been sent");
+ if (mContext) {
+ mContext->UnregisterNode(this);
+ }
+}
+
+void AudioNode::Initialize(const AudioNodeOptions& aOptions, ErrorResult& aRv) {
+ if (aOptions.mChannelCount.WasPassed()) {
+ SetChannelCount(aOptions.mChannelCount.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (aOptions.mChannelCountMode.WasPassed()) {
+ SetChannelCountModeValue(aOptions.mChannelCountMode.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (aOptions.mChannelInterpretation.WasPassed()) {
+ SetChannelInterpretationValue(aOptions.mChannelInterpretation.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+}
+
+size_t AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // Not owned:
+ // - mContext
+ // - mTrack
+ size_t amount = 0;
+
+ amount += mInputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mInputNodes.Length(); i++) {
+ amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // Just measure the array. The entire audio node graph is measured via the
+ // MediaTrackGraph's tracks, so we don't want to double-count the elements.
+ amount += mOutputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mOutputParams.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mOutputParams.Length(); i++) {
+ amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+template <class InputNode>
+static size_t FindIndexOfNode(const nsTArray<InputNode>& aInputNodes,
+ const AudioNode* aNode) {
+ for (size_t i = 0; i < aInputNodes.Length(); ++i) {
+ if (aInputNodes[i].mInputNode == aNode) {
+ return i;
+ }
+ }
+ return nsTArray<InputNode>::NoIndex;
+}
+
+template <class InputNode>
+static size_t FindIndexOfNodeWithPorts(const nsTArray<InputNode>& aInputNodes,
+ const AudioNode* aNode,
+ uint32_t aInputPort,
+ uint32_t aOutputPort) {
+ for (size_t i = 0; i < aInputNodes.Length(); ++i) {
+ if (aInputNodes[i].mInputNode == aNode &&
+ aInputNodes[i].mInputPort == aInputPort &&
+ aInputNodes[i].mOutputPort == aOutputPort) {
+ return i;
+ }
+ }
+ return nsTArray<InputNode>::NoIndex;
+}
+
+void AudioNode::DisconnectFromGraph() {
+ MOZ_ASSERT(mRefCnt.get() > mInputNodes.Length(),
+ "Caller should be holding a reference");
+
+ // The idea here is that we remove connections one by one, and at each step
+ // the graph is in a valid state.
+
+ // Disconnect inputs. We don't need them anymore.
+ while (!mInputNodes.IsEmpty()) {
+ InputNode inputNode = mInputNodes.PopLastElement();
+ inputNode.mInputNode->mOutputNodes.RemoveElement(this);
+ }
+
+ while (!mOutputNodes.IsEmpty()) {
+ RefPtr<AudioNode> output = mOutputNodes.PopLastElement();
+ size_t inputIndex = FindIndexOfNode(output->mInputNodes, this);
+ // It doesn't matter which one we remove, since we're going to remove all
+ // entries for this node anyway.
+ output->mInputNodes.RemoveElementAt(inputIndex);
+ // This effects of this connection will remain.
+ output->NotifyHasPhantomInput();
+ }
+
+ while (!mOutputParams.IsEmpty()) {
+ RefPtr<AudioParam> output = mOutputParams.PopLastElement();
+ size_t inputIndex = FindIndexOfNode(output->InputNodes(), this);
+ // It doesn't matter which one we remove, since we're going to remove all
+ // entries for this node anyway.
+ output->RemoveInputNode(inputIndex);
+ }
+
+ DestroyMediaTrack();
+}
+
+AudioNode* AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return nullptr;
+ }
+
+ if (aInput >= aDestination.NumberOfInputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Input index %u is out of bounds", aInput));
+ return nullptr;
+ }
+
+ if (Context() != aDestination.Context()) {
+ aRv.ThrowInvalidAccessError(
+ "Can't connect nodes from different AudioContexts");
+ return nullptr;
+ }
+
+ if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput,
+ aOutput) !=
+ nsTArray<AudioNode::InputNode>::NoIndex) {
+ // connection already exists.
+ return &aDestination;
+ }
+
+ WEB_AUDIO_API_LOG("%f: %s %u Connect() to %s %u", Context()->CurrentTime(),
+ NodeType(), Id(), aDestination.NodeType(),
+ aDestination.Id());
+
+ // The MediaTrackGraph will handle cycle detection. We don't need to do it
+ // here.
+
+ mOutputNodes.AppendElement(&aDestination);
+ InputNode* input = aDestination.mInputNodes.AppendElement();
+ input->mInputNode = this;
+ input->mInputPort = aInput;
+ input->mOutputPort = aOutput;
+ AudioNodeTrack* destinationTrack = aDestination.mTrack;
+ if (mTrack && destinationTrack) {
+ // Connect tracks in the MediaTrackGraph
+ MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
+ MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
+ input->mTrackPort = destinationTrack->AllocateInputPort(
+ mTrack, static_cast<uint16_t>(aInput), static_cast<uint16_t>(aOutput));
+ }
+ aDestination.NotifyInputsChanged();
+
+ return &aDestination;
+}
+
+void AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ if (Context() != aDestination.GetParentObject()) {
+ aRv.ThrowInvalidAccessError(
+ "Can't connect a node to an AudioParam from a different AudioContext");
+ return;
+ }
+
+ if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), this, INVALID_PORT,
+ aOutput) !=
+ nsTArray<AudioNode::InputNode>::NoIndex) {
+ // connection already exists.
+ return;
+ }
+
+ mOutputParams.AppendElement(&aDestination);
+ InputNode* input = aDestination.AppendInputNode();
+ input->mInputNode = this;
+ input->mInputPort = INVALID_PORT;
+ input->mOutputPort = aOutput;
+
+ mozilla::MediaTrack* track = aDestination.Track();
+ MOZ_ASSERT(track->AsProcessedTrack());
+ ProcessedMediaTrack* ps = static_cast<ProcessedMediaTrack*>(track);
+ if (mTrack) {
+ // Setup our track as an input to the AudioParam's track
+ MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
+ input->mTrackPort =
+ ps->AllocateInputPort(mTrack, 0, static_cast<uint16_t>(aOutput));
+ }
+}
+
+void AudioNode::SendDoubleParameterToTrack(uint32_t aIndex, double aValue) {
+ MOZ_ASSERT(mTrack, "How come we don't have a track here?");
+ mTrack->SetDoubleParameter(aIndex, aValue);
+}
+
+void AudioNode::SendInt32ParameterToTrack(uint32_t aIndex, int32_t aValue) {
+ MOZ_ASSERT(mTrack, "How come we don't have a track here?");
+ mTrack->SetInt32Parameter(aIndex, aValue);
+}
+
+void AudioNode::SendChannelMixingParametersToTrack() {
+ if (mTrack) {
+ mTrack->SetChannelMixingParameters(mChannelCount, mChannelCountMode,
+ mChannelInterpretation);
+ }
+}
+
+template <>
+bool AudioNode::DisconnectFromOutputIfConnected<AudioNode>(
+ uint32_t aOutputNodeIndex, uint32_t aInputIndex) {
+ WEB_AUDIO_API_LOG("%f: %s %u Disconnect()", Context()->CurrentTime(),
+ NodeType(), Id());
+
+ AudioNode* destination = mOutputNodes[aOutputNodeIndex];
+
+ MOZ_ASSERT(aOutputNodeIndex < mOutputNodes.Length());
+ MOZ_ASSERT(aInputIndex < destination->InputNodes().Length());
+
+ // An upstream node may be starting to play on the graph thread, and the
+ // engine for a downstream node may be sending a PlayingRefChangeHandler
+ // ADDREF message to this (main) thread. Wait for a round trip before
+ // releasing nodes, to give engines receiving sound now time to keep their
+ // nodes alive.
+ class RunnableRelease final : public Runnable {
+ public:
+ explicit RunnableRelease(already_AddRefed<AudioNode> aNode)
+ : mozilla::Runnable("RunnableRelease"), mNode(aNode) {}
+
+ NS_IMETHOD Run() override {
+ mNode = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioNode> mNode;
+ };
+
+ InputNode& input = destination->mInputNodes[aInputIndex];
+ if (input.mInputNode != this) {
+ return false;
+ }
+
+ // Remove one instance of 'dest' from mOutputNodes. There could be
+ // others, and it's not correct to remove them all since some of them
+ // could be for different output ports.
+ RefPtr<AudioNode> output = std::move(mOutputNodes[aOutputNodeIndex]);
+ mOutputNodes.RemoveElementAt(aOutputNodeIndex);
+ // Destroying the InputNode here sends a message to the graph thread
+ // to disconnect the tracks, which should be sent before the
+ // RunAfterPendingUpdates() call below.
+ destination->mInputNodes.RemoveElementAt(aInputIndex);
+ output->NotifyInputsChanged();
+ if (mTrack) {
+ nsCOMPtr<nsIRunnable> runnable = new RunnableRelease(output.forget());
+ mTrack->RunAfterPendingUpdates(runnable.forget());
+ }
+ return true;
+}
+
+template <>
+bool AudioNode::DisconnectFromOutputIfConnected<AudioParam>(
+ uint32_t aOutputParamIndex, uint32_t aInputIndex) {
+ MOZ_ASSERT(aOutputParamIndex < mOutputParams.Length());
+
+ AudioParam* destination = mOutputParams[aOutputParamIndex];
+
+ MOZ_ASSERT(aInputIndex < destination->InputNodes().Length());
+
+ const InputNode& input = destination->InputNodes()[aInputIndex];
+ if (input.mInputNode != this) {
+ return false;
+ }
+ destination->RemoveInputNode(aInputIndex);
+ // Remove one instance of 'dest' from mOutputParams. There could be
+ // others, and it's not correct to remove them all since some of them
+ // could be for different output ports.
+ mOutputParams.RemoveElementAt(aOutputParamIndex);
+ return true;
+}
+
+template <>
+const nsTArray<AudioNode::InputNode>&
+AudioNode::InputsForDestination<AudioNode>(uint32_t aOutputNodeIndex) const {
+ return mOutputNodes[aOutputNodeIndex]->InputNodes();
+}
+
+template <>
+const nsTArray<AudioNode::InputNode>&
+AudioNode::InputsForDestination<AudioParam>(uint32_t aOutputNodeIndex) const {
+ return mOutputParams[aOutputNodeIndex]->InputNodes();
+}
+
+template <typename DestinationType, typename Predicate>
+bool AudioNode::DisconnectMatchingDestinationInputs(uint32_t aDestinationIndex,
+ Predicate aPredicate) {
+ bool wasConnected = false;
+ uint32_t inputCount =
+ InputsForDestination<DestinationType>(aDestinationIndex).Length();
+
+ for (int32_t inputIndex = inputCount - 1; inputIndex >= 0; --inputIndex) {
+ const InputNode& input =
+ InputsForDestination<DestinationType>(aDestinationIndex)[inputIndex];
+ if (aPredicate(input)) {
+ if (DisconnectFromOutputIfConnected<DestinationType>(aDestinationIndex,
+ inputIndex)) {
+ wasConnected = true;
+ break;
+ }
+ }
+ }
+ return wasConnected;
+}
+
+void AudioNode::Disconnect(ErrorResult& aRv) {
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+}
+
+void AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, ErrorResult& aRv) {
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ if (aInput >= aDestination.NumberOfInputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Input index %u is out of bounds", aInput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput, aInput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput &&
+ aInputNode.mInputPort == aInput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioParam& aDestination, ErrorResult& aRv) {
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputParams[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from an AudioParam we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputParams[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from an AudioParam we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::DestroyMediaTrack() {
+ if (mTrack) {
+ // Remove the node pointer on the engine.
+ AudioNodeTrack* ns = mTrack;
+ MOZ_ASSERT(ns, "How come we don't have a track here?");
+ MOZ_ASSERT(ns->Engine()->NodeMainThread() == this,
+ "Invalid node reference");
+ ns->Engine()->ClearNode();
+
+ mTrack->Destroy();
+ mTrack = nullptr;
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsAutoString id;
+ id.AppendPrintf("%u", mId);
+ obs->NotifyObservers(nullptr, "webaudio-node-demise", id.get());
+ }
+ }
+}
+
+void AudioNode::RemoveOutputParam(AudioParam* aParam) {
+ mOutputParams.RemoveElement(aParam);
+}
+
+bool AudioNode::PassThrough() const {
+ MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
+ return mPassThrough;
+}
+
+void AudioNode::SetPassThrough(bool aPassThrough) {
+ MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
+ mPassThrough = aPassThrough;
+ if (mTrack) {
+ mTrack->SetPassThrough(mPassThrough);
+ }
+}
+
+AudioParam* AudioNode::CreateAudioParam(uint32_t aIndex, const nsAString& aName,
+ float aDefaultValue, float aMinValue,
+ float aMaxValue) {
+ return *mParams.AppendElement(
+ new AudioParam(this, aIndex, aName, aDefaultValue, aMinValue, aMaxValue));
+}
+
+} // namespace mozilla::dom