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