AudioNode.cpp (20292B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "AudioNode.h" 8 9 #include "AudioNodeEngine.h" 10 #include "AudioNodeTrack.h" 11 #include "mozilla/ErrorResult.h" 12 #include "mozilla/Services.h" 13 #include "mozilla/dom/AudioParam.h" 14 #include "nsIObserverService.h" 15 16 namespace mozilla::dom { 17 18 static const uint32_t INVALID_PORT = 0xffffffff; 19 static uint32_t gId = 0; 20 21 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode) 22 23 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper) 24 tmp->DisconnectFromGraph(); 25 if (tmp->mContext) { 26 tmp->mContext->UnregisterNode(tmp); 27 } 28 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) 29 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParams) 30 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes) 31 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams) 32 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 33 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 34 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode, 35 DOMEventTargetHelper) 36 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) 37 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParams) 38 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes) 39 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams) 40 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 41 42 NS_IMPL_ADDREF_INHERITED(AudioNode, DOMEventTargetHelper) 43 NS_IMPL_RELEASE_INHERITED(AudioNode, DOMEventTargetHelper) 44 45 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioNode) 46 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 47 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 48 49 AudioNode::AudioNode(AudioContext* aContext, uint32_t aChannelCount, 50 ChannelCountMode aChannelCountMode, 51 ChannelInterpretation aChannelInterpretation) 52 : DOMEventTargetHelper(aContext->GetParentObject()), 53 mContext(aContext), 54 mChannelCount(aChannelCount), 55 mChannelCountMode(aChannelCountMode), 56 mChannelInterpretation(aChannelInterpretation), 57 mId(gId++), 58 mPassThrough(false) { 59 MOZ_ASSERT(aContext); 60 aContext->RegisterNode(this); 61 } 62 63 AudioNode::~AudioNode() { 64 MOZ_ASSERT(mInputNodes.IsEmpty()); 65 MOZ_ASSERT(mOutputNodes.IsEmpty()); 66 MOZ_ASSERT(mOutputParams.IsEmpty()); 67 MOZ_ASSERT(!mTrack, 68 "The webaudio-node-demise notification must have been sent"); 69 if (mContext) { 70 mContext->UnregisterNode(this); 71 } 72 } 73 74 void AudioNode::Initialize(const AudioNodeOptions& aOptions, ErrorResult& aRv) { 75 if (aOptions.mChannelCount.WasPassed()) { 76 SetChannelCount(aOptions.mChannelCount.Value(), aRv); 77 if (NS_WARN_IF(aRv.Failed())) { 78 return; 79 } 80 } 81 82 if (aOptions.mChannelCountMode.WasPassed()) { 83 SetChannelCountModeValue(aOptions.mChannelCountMode.Value(), aRv); 84 if (NS_WARN_IF(aRv.Failed())) { 85 return; 86 } 87 } 88 89 if (aOptions.mChannelInterpretation.WasPassed()) { 90 SetChannelInterpretationValue(aOptions.mChannelInterpretation.Value(), aRv); 91 if (NS_WARN_IF(aRv.Failed())) { 92 return; 93 } 94 } 95 } 96 97 size_t AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 98 // Not owned: 99 // - mContext 100 // - mTrack 101 size_t amount = 0; 102 103 amount += mInputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf); 104 for (size_t i = 0; i < mInputNodes.Length(); i++) { 105 amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf); 106 } 107 108 // Just measure the array. The entire audio node graph is measured via the 109 // MediaTrackGraph's tracks, so we don't want to double-count the elements. 110 amount += mOutputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf); 111 112 amount += mOutputParams.ShallowSizeOfExcludingThis(aMallocSizeOf); 113 for (size_t i = 0; i < mOutputParams.Length(); i++) { 114 amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf); 115 } 116 117 return amount; 118 } 119 120 size_t AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 121 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 122 } 123 124 template <class InputNode> 125 static size_t FindIndexOfNode(const nsTArray<InputNode>& aInputNodes, 126 const AudioNode* aNode) { 127 for (size_t i = 0; i < aInputNodes.Length(); ++i) { 128 if (aInputNodes[i].mInputNode == aNode) { 129 return i; 130 } 131 } 132 return nsTArray<InputNode>::NoIndex; 133 } 134 135 template <class InputNode> 136 static size_t FindIndexOfNodeWithPorts(const nsTArray<InputNode>& aInputNodes, 137 const AudioNode* aNode, 138 uint32_t aInputPort, 139 uint32_t aOutputPort) { 140 for (size_t i = 0; i < aInputNodes.Length(); ++i) { 141 if (aInputNodes[i].mInputNode == aNode && 142 aInputNodes[i].mInputPort == aInputPort && 143 aInputNodes[i].mOutputPort == aOutputPort) { 144 return i; 145 } 146 } 147 return nsTArray<InputNode>::NoIndex; 148 } 149 150 void AudioNode::DisconnectFromGraph() { 151 MOZ_ASSERT(mRefCnt.get() > mInputNodes.Length(), 152 "Caller should be holding a reference"); 153 154 // The idea here is that we remove connections one by one, and at each step 155 // the graph is in a valid state. 156 157 // Disconnect inputs. We don't need them anymore. 158 while (!mInputNodes.IsEmpty()) { 159 InputNode inputNode = mInputNodes.PopLastElement(); 160 inputNode.mInputNode->mOutputNodes.RemoveElement(this); 161 } 162 163 while (!mOutputNodes.IsEmpty()) { 164 RefPtr<AudioNode> output = mOutputNodes.PopLastElement(); 165 size_t inputIndex = FindIndexOfNode(output->mInputNodes, this); 166 // It doesn't matter which one we remove, since we're going to remove all 167 // entries for this node anyway. 168 output->mInputNodes.RemoveElementAt(inputIndex); 169 // This effects of this connection will remain. 170 output->NotifyHasPhantomInput(); 171 } 172 173 while (!mOutputParams.IsEmpty()) { 174 RefPtr<AudioParam> output = mOutputParams.PopLastElement(); 175 size_t inputIndex = FindIndexOfNode(output->InputNodes(), this); 176 // It doesn't matter which one we remove, since we're going to remove all 177 // entries for this node anyway. 178 output->RemoveInputNode(inputIndex); 179 } 180 181 DestroyMediaTrack(); 182 } 183 184 AudioNode* AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput, 185 uint32_t aInput, ErrorResult& aRv) { 186 if (aOutput >= NumberOfOutputs()) { 187 aRv.ThrowIndexSizeError( 188 nsPrintfCString("Output index %u is out of bounds", aOutput)); 189 return nullptr; 190 } 191 192 if (aInput >= aDestination.NumberOfInputs()) { 193 aRv.ThrowIndexSizeError( 194 nsPrintfCString("Input index %u is out of bounds", aInput)); 195 return nullptr; 196 } 197 198 if (Context() != aDestination.Context()) { 199 aRv.ThrowInvalidAccessError( 200 "Can't connect nodes from different AudioContexts"); 201 return nullptr; 202 } 203 204 if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput, 205 aOutput) != 206 nsTArray<AudioNode::InputNode>::NoIndex) { 207 // connection already exists. 208 return &aDestination; 209 } 210 211 WEB_AUDIO_API_LOG("{:f}: {} {} Connect() to {} {}", Context()->CurrentTime(), 212 NodeType(), Id(), aDestination.NodeType(), 213 aDestination.Id()); 214 215 // The MediaTrackGraph will handle cycle detection. We don't need to do it 216 // here. 217 218 mOutputNodes.AppendElement(&aDestination); 219 InputNode* input = aDestination.mInputNodes.AppendElement(); 220 input->mInputNode = this; 221 input->mInputPort = aInput; 222 input->mOutputPort = aOutput; 223 AudioNodeTrack* destinationTrack = aDestination.mTrack; 224 if (mTrack && destinationTrack) { 225 // Connect tracks in the MediaTrackGraph 226 MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number"); 227 MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); 228 input->mTrackPort = destinationTrack->AllocateInputPort( 229 mTrack, static_cast<uint16_t>(aInput), static_cast<uint16_t>(aOutput)); 230 } 231 aDestination.NotifyInputsChanged(); 232 233 return &aDestination; 234 } 235 236 void AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput, 237 ErrorResult& aRv) { 238 if (aOutput >= NumberOfOutputs()) { 239 aRv.ThrowIndexSizeError( 240 nsPrintfCString("Output index %u is out of bounds", aOutput)); 241 return; 242 } 243 244 if (Context() != aDestination.GetParentObject()) { 245 aRv.ThrowInvalidAccessError( 246 "Can't connect a node to an AudioParam from a different AudioContext"); 247 return; 248 } 249 250 if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), this, INVALID_PORT, 251 aOutput) != 252 nsTArray<AudioNode::InputNode>::NoIndex) { 253 // connection already exists. 254 return; 255 } 256 257 mOutputParams.AppendElement(&aDestination); 258 InputNode* input = aDestination.AppendInputNode(); 259 input->mInputNode = this; 260 input->mInputPort = INVALID_PORT; 261 input->mOutputPort = aOutput; 262 263 mozilla::MediaTrack* track = aDestination.Track(); 264 MOZ_ASSERT(track->AsProcessedTrack()); 265 ProcessedMediaTrack* ps = static_cast<ProcessedMediaTrack*>(track); 266 if (mTrack) { 267 // Setup our track as an input to the AudioParam's track 268 MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number"); 269 input->mTrackPort = 270 ps->AllocateInputPort(mTrack, 0, static_cast<uint16_t>(aOutput)); 271 } 272 } 273 274 void AudioNode::SendDoubleParameterToTrack(uint32_t aIndex, double aValue) { 275 MOZ_ASSERT(mTrack, "How come we don't have a track here?"); 276 mTrack->SetDoubleParameter(aIndex, aValue); 277 } 278 279 void AudioNode::SendInt32ParameterToTrack(uint32_t aIndex, int32_t aValue) { 280 MOZ_ASSERT(mTrack, "How come we don't have a track here?"); 281 mTrack->SetInt32Parameter(aIndex, aValue); 282 } 283 284 void AudioNode::SendChannelMixingParametersToTrack() { 285 if (mTrack) { 286 mTrack->SetChannelMixingParameters(mChannelCount, mChannelCountMode, 287 mChannelInterpretation); 288 } 289 } 290 291 template <> 292 bool AudioNode::DisconnectFromOutputIfConnected<AudioNode>( 293 uint32_t aOutputNodeIndex, uint32_t aInputIndex) { 294 WEB_AUDIO_API_LOG("{:f}: {} {} Disconnect()", Context()->CurrentTime(), 295 NodeType(), Id()); 296 297 AudioNode* destination = mOutputNodes[aOutputNodeIndex]; 298 299 MOZ_ASSERT(aOutputNodeIndex < mOutputNodes.Length()); 300 MOZ_ASSERT(aInputIndex < destination->InputNodes().Length()); 301 302 // An upstream node may be starting to play on the graph thread, and the 303 // engine for a downstream node may be sending a PlayingRefChangeHandler 304 // ADDREF message to this (main) thread. Wait for a round trip before 305 // releasing nodes, to give engines receiving sound now time to keep their 306 // nodes alive. 307 class RunnableRelease final : public Runnable { 308 public: 309 explicit RunnableRelease(already_AddRefed<AudioNode> aNode) 310 : mozilla::Runnable("RunnableRelease"), mNode(aNode) {} 311 312 NS_IMETHOD Run() override { 313 mNode = nullptr; 314 return NS_OK; 315 } 316 317 private: 318 RefPtr<AudioNode> mNode; 319 }; 320 321 InputNode& input = destination->mInputNodes[aInputIndex]; 322 if (input.mInputNode != this) { 323 return false; 324 } 325 326 // Remove one instance of 'dest' from mOutputNodes. There could be 327 // others, and it's not correct to remove them all since some of them 328 // could be for different output ports. 329 RefPtr<AudioNode> output = std::move(mOutputNodes[aOutputNodeIndex]); 330 mOutputNodes.RemoveElementAt(aOutputNodeIndex); 331 // Destroying the InputNode here sends a message to the graph thread 332 // to disconnect the tracks, which should be sent before the 333 // RunAfterPendingUpdates() call below. 334 destination->mInputNodes.RemoveElementAt(aInputIndex); 335 output->NotifyInputsChanged(); 336 if (mTrack) { 337 nsCOMPtr<nsIRunnable> runnable = new RunnableRelease(output.forget()); 338 mTrack->RunAfterPendingUpdates(runnable.forget()); 339 } 340 return true; 341 } 342 343 template <> 344 bool AudioNode::DisconnectFromOutputIfConnected<AudioParam>( 345 uint32_t aOutputParamIndex, uint32_t aInputIndex) { 346 MOZ_ASSERT(aOutputParamIndex < mOutputParams.Length()); 347 348 AudioParam* destination = mOutputParams[aOutputParamIndex]; 349 350 MOZ_ASSERT(aInputIndex < destination->InputNodes().Length()); 351 352 const InputNode& input = destination->InputNodes()[aInputIndex]; 353 if (input.mInputNode != this) { 354 return false; 355 } 356 destination->RemoveInputNode(aInputIndex); 357 // Remove one instance of 'dest' from mOutputParams. There could be 358 // others, and it's not correct to remove them all since some of them 359 // could be for different output ports. 360 mOutputParams.RemoveElementAt(aOutputParamIndex); 361 return true; 362 } 363 364 template <> 365 const nsTArray<AudioNode::InputNode>& 366 AudioNode::InputsForDestination<AudioNode>(uint32_t aOutputNodeIndex) const { 367 return mOutputNodes[aOutputNodeIndex]->InputNodes(); 368 } 369 370 template <> 371 const nsTArray<AudioNode::InputNode>& 372 AudioNode::InputsForDestination<AudioParam>(uint32_t aOutputNodeIndex) const { 373 return mOutputParams[aOutputNodeIndex]->InputNodes(); 374 } 375 376 template <typename DestinationType, typename Predicate> 377 bool AudioNode::DisconnectMatchingDestinationInputs(uint32_t aDestinationIndex, 378 Predicate aPredicate) { 379 bool wasConnected = false; 380 uint32_t inputCount = 381 InputsForDestination<DestinationType>(aDestinationIndex).Length(); 382 383 for (int32_t inputIndex = inputCount - 1; inputIndex >= 0; --inputIndex) { 384 const InputNode& input = 385 InputsForDestination<DestinationType>(aDestinationIndex)[inputIndex]; 386 if (aPredicate(input)) { 387 if (DisconnectFromOutputIfConnected<DestinationType>(aDestinationIndex, 388 inputIndex)) { 389 wasConnected = true; 390 break; 391 } 392 } 393 } 394 return wasConnected; 395 } 396 397 void AudioNode::Disconnect(ErrorResult& aRv) { 398 for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0; 399 --outputIndex) { 400 DisconnectMatchingDestinationInputs<AudioNode>( 401 outputIndex, [](const InputNode&) { return true; }); 402 } 403 404 for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0; 405 --outputIndex) { 406 DisconnectMatchingDestinationInputs<AudioParam>( 407 outputIndex, [](const InputNode&) { return true; }); 408 } 409 } 410 411 void AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv) { 412 if (aOutput >= NumberOfOutputs()) { 413 aRv.ThrowIndexSizeError( 414 nsPrintfCString("Output index %u is out of bounds", aOutput)); 415 return; 416 } 417 418 for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0; 419 --outputIndex) { 420 DisconnectMatchingDestinationInputs<AudioNode>( 421 outputIndex, [aOutput](const InputNode& aInputNode) { 422 return aInputNode.mOutputPort == aOutput; 423 }); 424 } 425 426 for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0; 427 --outputIndex) { 428 DisconnectMatchingDestinationInputs<AudioParam>( 429 outputIndex, [aOutput](const InputNode& aInputNode) { 430 return aInputNode.mOutputPort == aOutput; 431 }); 432 } 433 } 434 435 void AudioNode::Disconnect(AudioNode& aDestination, ErrorResult& aRv) { 436 bool wasConnected = false; 437 438 for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0; 439 --outputIndex) { 440 if (mOutputNodes[outputIndex] != &aDestination) { 441 continue; 442 } 443 wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>( 444 outputIndex, [](const InputNode&) { return true; }); 445 } 446 447 if (!wasConnected) { 448 aRv.ThrowInvalidAccessError( 449 "Trying to disconnect from a node we're not connected to"); 450 return; 451 } 452 } 453 454 void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput, 455 ErrorResult& aRv) { 456 if (aOutput >= NumberOfOutputs()) { 457 aRv.ThrowIndexSizeError( 458 nsPrintfCString("Output index %u is out of bounds", aOutput)); 459 return; 460 } 461 462 bool wasConnected = false; 463 464 for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0; 465 --outputIndex) { 466 if (mOutputNodes[outputIndex] != &aDestination) { 467 continue; 468 } 469 wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>( 470 outputIndex, [aOutput](const InputNode& aInputNode) { 471 return aInputNode.mOutputPort == aOutput; 472 }); 473 } 474 475 if (!wasConnected) { 476 aRv.ThrowInvalidAccessError( 477 "Trying to disconnect from a node we're not connected to"); 478 return; 479 } 480 } 481 482 void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput, 483 uint32_t aInput, ErrorResult& aRv) { 484 if (aOutput >= NumberOfOutputs()) { 485 aRv.ThrowIndexSizeError( 486 nsPrintfCString("Output index %u is out of bounds", aOutput)); 487 return; 488 } 489 490 if (aInput >= aDestination.NumberOfInputs()) { 491 aRv.ThrowIndexSizeError( 492 nsPrintfCString("Input index %u is out of bounds", aInput)); 493 return; 494 } 495 496 bool wasConnected = false; 497 498 for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0; 499 --outputIndex) { 500 if (mOutputNodes[outputIndex] != &aDestination) { 501 continue; 502 } 503 wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>( 504 outputIndex, [aOutput, aInput](const InputNode& aInputNode) { 505 return aInputNode.mOutputPort == aOutput && 506 aInputNode.mInputPort == aInput; 507 }); 508 } 509 510 if (!wasConnected) { 511 aRv.ThrowInvalidAccessError( 512 "Trying to disconnect from a node we're not connected to"); 513 return; 514 } 515 } 516 517 void AudioNode::Disconnect(AudioParam& aDestination, ErrorResult& aRv) { 518 bool wasConnected = false; 519 520 for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0; 521 --outputIndex) { 522 if (mOutputParams[outputIndex] != &aDestination) { 523 continue; 524 } 525 wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>( 526 outputIndex, [](const InputNode&) { return true; }); 527 } 528 529 if (!wasConnected) { 530 aRv.ThrowInvalidAccessError( 531 "Trying to disconnect from an AudioParam we're not connected to"); 532 return; 533 } 534 } 535 536 void AudioNode::Disconnect(AudioParam& aDestination, uint32_t aOutput, 537 ErrorResult& aRv) { 538 if (aOutput >= NumberOfOutputs()) { 539 aRv.ThrowIndexSizeError( 540 nsPrintfCString("Output index %u is out of bounds", aOutput)); 541 return; 542 } 543 544 bool wasConnected = false; 545 546 for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0; 547 --outputIndex) { 548 if (mOutputParams[outputIndex] != &aDestination) { 549 continue; 550 } 551 wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>( 552 outputIndex, [aOutput](const InputNode& aInputNode) { 553 return aInputNode.mOutputPort == aOutput; 554 }); 555 } 556 557 if (!wasConnected) { 558 aRv.ThrowInvalidAccessError( 559 "Trying to disconnect from an AudioParam we're not connected to"); 560 return; 561 } 562 } 563 564 void AudioNode::DestroyMediaTrack() { 565 if (mTrack) { 566 // Remove the node pointer on the engine. 567 AudioNodeTrack* ns = mTrack; 568 MOZ_ASSERT(ns, "How come we don't have a track here?"); 569 MOZ_ASSERT(ns->Engine()->NodeMainThread() == this, 570 "Invalid node reference"); 571 ns->Engine()->ClearNode(); 572 573 mTrack->Destroy(); 574 mTrack = nullptr; 575 576 nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); 577 if (obs) { 578 nsAutoString id; 579 id.AppendPrintf("%u", mId); 580 obs->NotifyObservers(nullptr, "webaudio-node-demise", id.get()); 581 } 582 } 583 } 584 585 void AudioNode::RemoveOutputParam(AudioParam* aParam) { 586 mOutputParams.RemoveElement(aParam); 587 } 588 589 bool AudioNode::PassThrough() const { 590 MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1); 591 return mPassThrough; 592 } 593 594 void AudioNode::SetPassThrough(bool aPassThrough) { 595 MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1); 596 mPassThrough = aPassThrough; 597 if (mTrack) { 598 mTrack->SetPassThrough(mPassThrough); 599 } 600 } 601 602 AudioParam* AudioNode::CreateAudioParam(uint32_t aIndex, const nsAString& aName, 603 float aDefaultValue, float aMinValue, 604 float aMaxValue) { 605 return *mParams.AppendElement( 606 new AudioParam(this, aIndex, aName, aDefaultValue, aMinValue, aMaxValue)); 607 } 608 609 } // namespace mozilla::dom