Line data Source code
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 "ConvolverNode.h"
8 : #include "mozilla/dom/ConvolverNodeBinding.h"
9 : #include "nsAutoPtr.h"
10 : #include "AlignmentUtils.h"
11 : #include "AudioNodeEngine.h"
12 : #include "AudioNodeStream.h"
13 : #include "blink/Reverb.h"
14 : #include "PlayingRefChangeHandler.h"
15 :
16 : namespace mozilla {
17 : namespace dom {
18 :
19 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer)
20 :
21 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ConvolverNode)
22 0 : NS_INTERFACE_MAP_END_INHERITING(AudioNode)
23 :
24 0 : NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode)
25 0 : NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode)
26 :
27 0 : class ConvolverNodeEngine final : public AudioNodeEngine
28 : {
29 : typedef PlayingRefChangeHandler PlayingRefChanged;
30 : public:
31 0 : ConvolverNodeEngine(AudioNode* aNode, bool aNormalize)
32 0 : : AudioNodeEngine(aNode)
33 : , mBufferLength(0)
34 : , mLeftOverData(INT32_MIN)
35 : , mSampleRate(0.0f)
36 0 : , mUseBackgroundThreads(!aNode->Context()->IsOffline())
37 0 : , mNormalize(aNormalize)
38 : {
39 0 : }
40 :
41 : enum Parameters {
42 : BUFFER_LENGTH,
43 : SAMPLE_RATE,
44 : NORMALIZE
45 : };
46 0 : void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override
47 : {
48 0 : switch (aIndex) {
49 : case BUFFER_LENGTH:
50 : // BUFFER_LENGTH is the first parameter that we set when setting a new buffer,
51 : // so we should be careful to invalidate the rest of our state here.
52 0 : mBuffer = nullptr;
53 0 : mSampleRate = 0.0f;
54 0 : mBufferLength = aParam;
55 0 : mLeftOverData = INT32_MIN;
56 0 : break;
57 : case SAMPLE_RATE:
58 0 : mSampleRate = aParam;
59 0 : break;
60 : case NORMALIZE:
61 0 : mNormalize = !!aParam;
62 0 : break;
63 : default:
64 0 : NS_ERROR("Bad ConvolverNodeEngine Int32Parameter");
65 : }
66 0 : }
67 0 : void SetDoubleParameter(uint32_t aIndex, double aParam) override
68 : {
69 0 : switch (aIndex) {
70 : case SAMPLE_RATE:
71 0 : mSampleRate = aParam;
72 0 : AdjustReverb();
73 0 : break;
74 : default:
75 0 : NS_ERROR("Bad ConvolverNodeEngine DoubleParameter");
76 : }
77 0 : }
78 0 : void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) override
79 : {
80 0 : mBuffer = aBuffer;
81 0 : AdjustReverb();
82 0 : }
83 :
84 0 : void AdjustReverb()
85 : {
86 : // Note about empirical tuning (this is copied from Blink)
87 : // The maximum FFT size affects reverb performance and accuracy.
88 : // If the reverb is single-threaded and processes entirely in the real-time audio thread,
89 : // it's important not to make this too high. In this case 8192 is a good value.
90 : // But, the Reverb object is multi-threaded, so we want this as high as possible without losing too much accuracy.
91 : // Very large FFTs will have worse phase errors. Given these constraints 32768 is a good compromise.
92 0 : const size_t MaxFFTSize = 32768;
93 :
94 0 : if (!mBuffer || !mBufferLength || !mSampleRate) {
95 0 : mReverb = nullptr;
96 0 : mLeftOverData = INT32_MIN;
97 0 : return;
98 : }
99 :
100 0 : mReverb = new WebCore::Reverb(mBuffer, mBufferLength,
101 0 : MaxFFTSize, 2, mUseBackgroundThreads,
102 0 : mNormalize, mSampleRate);
103 : }
104 :
105 0 : void ProcessBlock(AudioNodeStream* aStream,
106 : GraphTime aFrom,
107 : const AudioBlock& aInput,
108 : AudioBlock* aOutput,
109 : bool* aFinished) override
110 : {
111 0 : if (!mReverb) {
112 0 : aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
113 0 : return;
114 : }
115 :
116 0 : AudioBlock input = aInput;
117 0 : if (aInput.IsNull()) {
118 0 : if (mLeftOverData > 0) {
119 0 : mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
120 0 : input.AllocateChannels(1);
121 0 : WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE);
122 : } else {
123 0 : if (mLeftOverData != INT32_MIN) {
124 0 : mLeftOverData = INT32_MIN;
125 0 : aStream->ScheduleCheckForInactive();
126 : RefPtr<PlayingRefChanged> refchanged =
127 0 : new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
128 0 : aStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(
129 0 : refchanged.forget());
130 : }
131 0 : aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
132 0 : return;
133 : }
134 : } else {
135 0 : if (aInput.mVolume != 1.0f) {
136 : // Pre-multiply the input's volume
137 0 : uint32_t numChannels = aInput.ChannelCount();
138 0 : input.AllocateChannels(numChannels);
139 0 : for (uint32_t i = 0; i < numChannels; ++i) {
140 0 : const float* src = static_cast<const float*>(aInput.mChannelData[i]);
141 0 : float* dest = input.ChannelFloatsForWrite(i);
142 0 : AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest);
143 : }
144 : }
145 :
146 0 : if (mLeftOverData <= 0) {
147 : RefPtr<PlayingRefChanged> refchanged =
148 0 : new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF);
149 0 : aStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(
150 0 : refchanged.forget());
151 : }
152 0 : mLeftOverData = mBufferLength;
153 0 : MOZ_ASSERT(mLeftOverData > 0);
154 : }
155 0 : aOutput->AllocateChannels(2);
156 :
157 0 : mReverb->process(&input, aOutput);
158 : }
159 :
160 0 : bool IsActive() const override
161 : {
162 0 : return mLeftOverData != INT32_MIN;
163 : }
164 :
165 0 : size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
166 : {
167 0 : size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
168 0 : if (mBuffer && !mBuffer->IsShared()) {
169 0 : amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
170 : }
171 :
172 0 : if (mReverb) {
173 0 : amount += mReverb->sizeOfIncludingThis(aMallocSizeOf);
174 : }
175 :
176 0 : return amount;
177 : }
178 :
179 0 : size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
180 : {
181 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
182 : }
183 :
184 : private:
185 : RefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
186 : nsAutoPtr<WebCore::Reverb> mReverb;
187 : int32_t mBufferLength;
188 : int32_t mLeftOverData;
189 : float mSampleRate;
190 : bool mUseBackgroundThreads;
191 : bool mNormalize;
192 : };
193 :
194 0 : ConvolverNode::ConvolverNode(AudioContext* aContext)
195 : : AudioNode(aContext,
196 : 2,
197 : ChannelCountMode::Clamped_max,
198 : ChannelInterpretation::Speakers)
199 0 : , mNormalize(true)
200 : {
201 0 : ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
202 0 : mStream = AudioNodeStream::Create(aContext, engine,
203 : AudioNodeStream::NO_STREAM_FLAGS,
204 0 : aContext->Graph());
205 0 : }
206 :
207 : /* static */ already_AddRefed<ConvolverNode>
208 0 : ConvolverNode::Create(JSContext* aCx, AudioContext& aAudioContext,
209 : const ConvolverOptions& aOptions,
210 : ErrorResult& aRv)
211 : {
212 0 : if (aAudioContext.CheckClosed(aRv)) {
213 0 : return nullptr;
214 : }
215 :
216 0 : RefPtr<ConvolverNode> audioNode = new ConvolverNode(&aAudioContext);
217 :
218 0 : audioNode->Initialize(aOptions, aRv);
219 0 : if (NS_WARN_IF(aRv.Failed())) {
220 0 : return nullptr;
221 : }
222 :
223 : // This must be done before setting the buffer.
224 0 : audioNode->SetNormalize(!aOptions.mDisableNormalization);
225 :
226 0 : if (aOptions.mBuffer.WasPassed()) {
227 0 : MOZ_ASSERT(aCx);
228 0 : audioNode->SetBuffer(aCx, aOptions.mBuffer.Value(), aRv);
229 0 : if (NS_WARN_IF(aRv.Failed())) {
230 0 : return nullptr;
231 : }
232 : }
233 :
234 0 : return audioNode.forget();
235 : }
236 :
237 : size_t
238 0 : ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
239 : {
240 0 : size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
241 0 : if (mBuffer) {
242 : // NB: mBuffer might be shared with the associated engine, by convention
243 : // the AudioNode will report.
244 0 : amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
245 : }
246 0 : return amount;
247 : }
248 :
249 : size_t
250 0 : ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
251 : {
252 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
253 : }
254 :
255 : JSObject*
256 0 : ConvolverNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
257 : {
258 0 : return ConvolverNodeBinding::Wrap(aCx, this, aGivenProto);
259 : }
260 :
261 : void
262 0 : ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv)
263 : {
264 0 : if (aBuffer) {
265 0 : switch (aBuffer->NumberOfChannels()) {
266 : case 1:
267 : case 2:
268 : case 4:
269 : // Supported number of channels
270 0 : break;
271 : default:
272 0 : aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
273 0 : return;
274 : }
275 : }
276 :
277 0 : mBuffer = aBuffer;
278 :
279 : // Send the buffer to the stream
280 0 : AudioNodeStream* ns = mStream;
281 0 : MOZ_ASSERT(ns, "Why don't we have a stream here?");
282 0 : if (mBuffer) {
283 0 : uint32_t length = mBuffer->Length();
284 : RefPtr<ThreadSharedFloatArrayBufferList> data =
285 0 : mBuffer->GetThreadSharedChannelsForRate(aCx);
286 0 : if (data && length < WEBAUDIO_BLOCK_SIZE) {
287 : // For very small impulse response buffers, we need to pad the
288 : // buffer with 0 to make sure that the Reverb implementation
289 : // has enough data to compute FFTs from.
290 0 : length = WEBAUDIO_BLOCK_SIZE;
291 : RefPtr<ThreadSharedFloatArrayBufferList> paddedBuffer =
292 0 : new ThreadSharedFloatArrayBufferList(data->GetChannels());
293 0 : void* channelData = malloc(sizeof(float) * length * data->GetChannels() + 15);
294 0 : float* alignedChannelData = ALIGNED16(channelData);
295 0 : ASSERT_ALIGNED16(alignedChannelData);
296 0 : for (uint32_t i = 0; i < data->GetChannels(); ++i) {
297 0 : PodCopy(alignedChannelData + length * i, data->GetData(i), mBuffer->Length());
298 0 : PodZero(alignedChannelData + length * i + mBuffer->Length(), WEBAUDIO_BLOCK_SIZE - mBuffer->Length());
299 0 : paddedBuffer->SetData(i, (i == 0) ? channelData : nullptr, free, alignedChannelData);
300 : }
301 0 : data = paddedBuffer;
302 : }
303 0 : SendInt32ParameterToStream(ConvolverNodeEngine::BUFFER_LENGTH, length);
304 0 : SendDoubleParameterToStream(ConvolverNodeEngine::SAMPLE_RATE,
305 0 : mBuffer->SampleRate());
306 0 : ns->SetBuffer(data.forget());
307 : } else {
308 0 : ns->SetBuffer(nullptr);
309 : }
310 : }
311 :
312 : void
313 0 : ConvolverNode::SetNormalize(bool aNormalize)
314 : {
315 0 : mNormalize = aNormalize;
316 0 : SendInt32ParameterToStream(ConvolverNodeEngine::NORMALIZE, aNormalize);
317 0 : }
318 :
319 : } // namespace dom
320 : } // namespace mozilla
|