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