DelayNode.cpp (8030B)
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 "DelayNode.h" 8 9 #include "AudioDestinationNode.h" 10 #include "AudioNodeEngine.h" 11 #include "AudioNodeTrack.h" 12 #include "DelayBuffer.h" 13 #include "PlayingRefChangeHandler.h" 14 #include "Tracing.h" 15 #include "WebAudioUtils.h" 16 #include "mozilla/dom/DelayNodeBinding.h" 17 18 namespace mozilla::dom { 19 20 NS_IMPL_CYCLE_COLLECTION_INHERITED(DelayNode, AudioNode, mDelay) 21 22 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayNode) 23 NS_INTERFACE_MAP_END_INHERITING(AudioNode) 24 25 NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode) 26 NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode) 27 28 class DelayNodeEngine final : public AudioNodeEngine { 29 typedef PlayingRefChangeHandler PlayingRefChanged; 30 31 public: 32 DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, 33 float aMaxDelayTicks) 34 : AudioNodeEngine(aNode), 35 mDestination(aDestination->Track()) 36 // Keep the default value in sync with the default value in 37 // DelayNode::DelayNode. 38 , 39 mDelay(0.f) 40 // Use a smoothing range of 20ms 41 , 42 mBuffer( 43 std::max(aMaxDelayTicks, static_cast<float>(WEBAUDIO_BLOCK_SIZE))), 44 mMaxDelay(aMaxDelayTicks), 45 mHaveProducedBeforeInput(false), 46 mLeftOverData(INT32_MIN) {} 47 48 DelayNodeEngine* AsDelayNodeEngine() override { return this; } 49 50 enum Parameters { 51 DELAY, 52 }; 53 void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override { 54 MOZ_ASSERT(mDestination); 55 aEvent.ConvertToTicks(mDestination); 56 57 switch (aIndex) { 58 case DELAY: 59 mDelay.InsertEvent<int64_t>(aEvent); 60 break; 61 default: 62 NS_ERROR("Bad DelayNodeEngine TimelineParameter"); 63 } 64 } 65 66 void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom, 67 const AudioBlock& aInput, AudioBlock* aOutput, 68 bool* aFinished) override { 69 MOZ_ASSERT(aTrack->mSampleRate == mDestination->mSampleRate); 70 TRACE("DelayNodeEngine::ProcessBlock"); 71 72 if (!aInput.IsSilentOrSubnormal()) { 73 if (mLeftOverData <= 0) { 74 RefPtr<PlayingRefChanged> refchanged = 75 new PlayingRefChanged(aTrack, PlayingRefChanged::ADDREF); 76 aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); 77 } 78 mLeftOverData = mBuffer.MaxDelayTicks(); 79 } else if (mLeftOverData > 0) { 80 mLeftOverData -= WEBAUDIO_BLOCK_SIZE; 81 } else { 82 if (mLeftOverData != INT32_MIN) { 83 mLeftOverData = INT32_MIN; 84 aTrack->ScheduleCheckForInactive(); 85 86 // Delete our buffered data now we no longer need it 87 mBuffer.Reset(); 88 89 RefPtr<PlayingRefChanged> refchanged = 90 new PlayingRefChanged(aTrack, PlayingRefChanged::RELEASE); 91 aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget()); 92 } 93 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 94 return; 95 } 96 97 mBuffer.Write(aInput); 98 99 // Skip output update if mLastChunks has already been set by 100 // ProduceBlockBeforeInput() when in a cycle. 101 if (!mHaveProducedBeforeInput) { 102 UpdateOutputBlock(aTrack, aFrom, aOutput, 0.0); 103 } 104 mHaveProducedBeforeInput = false; 105 mBuffer.NextBlock(); 106 } 107 108 void UpdateOutputBlock(AudioNodeTrack* aTrack, GraphTime aFrom, 109 AudioBlock* aOutput, float minDelay) { 110 float maxDelay = mMaxDelay; 111 float sampleRate = aTrack->mSampleRate; 112 ChannelInterpretation channelInterpretation = 113 aTrack->GetChannelInterpretation(); 114 if (mDelay.HasSimpleValue()) { 115 // If this DelayNode is in a cycle, make sure the delay value is at least 116 // one block, even if that is greater than maxDelay. 117 float delayFrames = mDelay.GetValue() * sampleRate; 118 float delayFramesClamped = 119 std::max(minDelay, std::min(delayFrames, maxDelay)); 120 mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation); 121 } else { 122 // Compute the delay values for the duration of the input AudioChunk 123 // If this DelayNode is in a cycle, make sure the delay value is at least 124 // one block. 125 TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom); 126 float values[WEBAUDIO_BLOCK_SIZE]; 127 mDelay.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE); 128 129 float computedDelay[WEBAUDIO_BLOCK_SIZE]; 130 for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { 131 float delayAtTick = values[counter] * sampleRate; 132 float delayAtTickClamped = 133 std::max(minDelay, std::min(delayAtTick, maxDelay)); 134 computedDelay[counter] = delayAtTickClamped; 135 } 136 mBuffer.Read(computedDelay, aOutput, channelInterpretation); 137 } 138 } 139 140 void ProduceBlockBeforeInput(AudioNodeTrack* aTrack, GraphTime aFrom, 141 AudioBlock* aOutput) override { 142 if (mLeftOverData <= 0) { 143 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); 144 } else { 145 UpdateOutputBlock(aTrack, aFrom, aOutput, WEBAUDIO_BLOCK_SIZE); 146 } 147 mHaveProducedBeforeInput = true; 148 } 149 150 bool IsActive() const override { return mLeftOverData != INT32_MIN; } 151 152 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override { 153 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); 154 // Not owned: 155 // - mDestination - probably not owned 156 // - mDelay - shares ref with AudioNode, don't count 157 amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); 158 return amount; 159 } 160 161 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override { 162 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 163 } 164 165 RefPtr<AudioNodeTrack> mDestination; 166 AudioParamTimeline mDelay; 167 DelayBuffer mBuffer; 168 float mMaxDelay; 169 bool mHaveProducedBeforeInput; 170 // How much data we have in our buffer which needs to be flushed out when our 171 // inputs finish. 172 int32_t mLeftOverData; 173 }; 174 175 DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay) 176 : AudioNode(aContext, 2, ChannelCountMode::Max, 177 ChannelInterpretation::Speakers) { 178 mDelay = CreateAudioParam(DelayNodeEngine::DELAY, u"delayTime"_ns, 0.0f, 0.f, 179 aMaxDelay); 180 DelayNodeEngine* engine = new DelayNodeEngine( 181 this, aContext->Destination(), aContext->SampleRate() * aMaxDelay); 182 mTrack = AudioNodeTrack::Create( 183 aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph()); 184 } 185 186 /* static */ 187 already_AddRefed<DelayNode> DelayNode::Create(AudioContext& aAudioContext, 188 const DelayOptions& aOptions, 189 ErrorResult& aRv) { 190 if (aOptions.mMaxDelayTime <= 0. || aOptions.mMaxDelayTime >= 180.) { 191 aRv.ThrowNotSupportedError( 192 nsPrintfCString("\"maxDelayTime\" (%g) is not in the range (0,180)", 193 aOptions.mMaxDelayTime)); 194 return nullptr; 195 } 196 197 RefPtr<DelayNode> audioNode = 198 new DelayNode(&aAudioContext, aOptions.mMaxDelayTime); 199 200 audioNode->Initialize(aOptions, aRv); 201 if (NS_WARN_IF(aRv.Failed())) { 202 return nullptr; 203 } 204 205 audioNode->DelayTime()->SetInitialValue(aOptions.mDelayTime); 206 return audioNode.forget(); 207 } 208 209 size_t DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { 210 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); 211 amount += mDelay->SizeOfIncludingThis(aMallocSizeOf); 212 return amount; 213 } 214 215 size_t DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { 216 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); 217 } 218 219 JSObject* DelayNode::WrapObject(JSContext* aCx, 220 JS::Handle<JSObject*> aGivenProto) { 221 return DelayNode_Binding::Wrap(aCx, this, aGivenProto); 222 } 223 224 } // namespace mozilla::dom