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