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