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/. */
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"
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
;
31 DelayNodeEngine(AudioNode
* aNode
, AudioDestinationNode
* aDestination
,
33 : AudioNodeEngine(aNode
),
34 mDestination(aDestination
->Track())
35 // Keep the default value in sync with the default value in
36 // DelayNode::DelayNode.
39 // Use a smoothing range of 20ms
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; }
52 void RecvTimelineEvent(uint32_t aIndex
, AudioTimelineEvent
& aEvent
) override
{
53 MOZ_ASSERT(mDestination
);
54 WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent
, mDestination
);
58 mDelay
.InsertEvent
<int64_t>(aEvent
);
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
;
81 if (mLeftOverData
!= INT32_MIN
) {
82 mLeftOverData
= INT32_MIN
;
83 aTrack
->ScheduleCheckForInactive();
85 // Delete our buffered data now we no longer need it
88 RefPtr
<PlayingRefChanged
> refchanged
=
89 new PlayingRefChanged(aTrack
, PlayingRefChanged::RELEASE
);
90 aTrack
->Graph()->DispatchToMainThreadStableState(refchanged
.forget());
92 aOutput
->SetNull(WEBAUDIO_BLOCK_SIZE
);
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;
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
);
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
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
);
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
);
154 // - mDestination - probably not owned
155 // - mDelay - shares ref with AudioNode, don't count
156 amount
+= mBuffer
.SizeOfExcludingThis(aMallocSizeOf
);
160 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf
) const override
{
161 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf
);
164 RefPtr
<AudioNodeTrack
> mDestination
;
165 AudioParamTimeline mDelay
;
168 bool mHaveProducedBeforeInput
;
169 // How much data we have in our buffer which needs to be flushed out when our
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
,
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());
186 already_AddRefed
<DelayNode
> DelayNode::Create(AudioContext
& aAudioContext
,
187 const DelayOptions
& aOptions
,
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
));
196 RefPtr
<DelayNode
> audioNode
=
197 new DelayNode(&aAudioContext
, aOptions
.mMaxDelayTime
);
199 audioNode
->Initialize(aOptions
, aRv
);
200 if (NS_WARN_IF(aRv
.Failed())) {
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
);
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