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 NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer)
20
21 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConvolverNode)
22 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
23
24 NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode)
25 NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode)
26
27 class ConvolverNodeEngine final : public AudioNodeEngine {
28 typedef PlayingRefChangeHandler PlayingRefChanged;
29
30 public:
ConvolverNodeEngine(AudioNode * aNode,bool aNormalize)31 ConvolverNodeEngine(AudioNode* aNode, bool aNormalize)
32 : AudioNodeEngine(aNode),
33 mLeftOverData(INT32_MIN),
34 mSampleRate(0.0f),
35 mUseBackgroundThreads(!aNode->Context()->IsOffline()),
36 mNormalize(aNormalize) {}
37
38 enum Parameters { SAMPLE_RATE, NORMALIZE };
SetInt32Parameter(uint32_t aIndex,int32_t aParam)39 void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
40 switch (aIndex) {
41 case NORMALIZE:
42 mNormalize = !!aParam;
43 break;
44 default:
45 NS_ERROR("Bad ConvolverNodeEngine Int32Parameter");
46 }
47 }
SetDoubleParameter(uint32_t aIndex,double aParam)48 void SetDoubleParameter(uint32_t aIndex, double aParam) override {
49 switch (aIndex) {
50 case SAMPLE_RATE:
51 mSampleRate = aParam;
52 // The buffer is passed after the sample rate.
53 // mReverb will be set using this sample rate when the buffer is
54 // received.
55 break;
56 default:
57 NS_ERROR("Bad ConvolverNodeEngine DoubleParameter");
58 }
59 }
SetBuffer(AudioChunk && aBuffer)60 void SetBuffer(AudioChunk&& aBuffer) override {
61 // Note about empirical tuning (this is copied from Blink)
62 // The maximum FFT size affects reverb performance and accuracy.
63 // If the reverb is single-threaded and processes entirely in the real-time
64 // audio thread, it's important not to make this too high. In this case
65 // 8192 is a good value. But, the Reverb object is multi-threaded, so we
66 // want this as high as possible without losing too much accuracy. Very
67 // large FFTs will have worse phase errors. Given these constraints 32768 is
68 // a good compromise.
69 const size_t MaxFFTSize = 32768;
70
71 mLeftOverData = INT32_MIN; // reset
72
73 if (aBuffer.IsNull() || !mSampleRate) {
74 mReverb = nullptr;
75 return;
76 }
77
78 mReverb = new WebCore::Reverb(aBuffer, MaxFFTSize, mUseBackgroundThreads,
79 mNormalize, mSampleRate);
80 }
81
ProcessBlock(AudioNodeStream * aStream,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)82 void ProcessBlock(AudioNodeStream* aStream, GraphTime aFrom,
83 const AudioBlock& aInput, AudioBlock* aOutput,
84 bool* aFinished) override {
85 if (!mReverb) {
86 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
87 return;
88 }
89
90 AudioBlock input = aInput;
91 if (aInput.IsNull()) {
92 if (mLeftOverData > 0) {
93 mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
94 input.AllocateChannels(1);
95 WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE);
96 } else {
97 if (mLeftOverData != INT32_MIN) {
98 mLeftOverData = INT32_MIN;
99 aStream->ScheduleCheckForInactive();
100 RefPtr<PlayingRefChanged> refchanged =
101 new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
102 aStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(
103 refchanged.forget());
104 }
105 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
106 return;
107 }
108 } else {
109 if (aInput.mVolume != 1.0f) {
110 // Pre-multiply the input's volume
111 uint32_t numChannels = aInput.ChannelCount();
112 input.AllocateChannels(numChannels);
113 for (uint32_t i = 0; i < numChannels; ++i) {
114 const float* src = static_cast<const float*>(aInput.mChannelData[i]);
115 float* dest = input.ChannelFloatsForWrite(i);
116 AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest);
117 }
118 }
119
120 if (mLeftOverData <= 0) {
121 RefPtr<PlayingRefChanged> refchanged =
122 new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF);
123 aStream->Graph()->DispatchToMainThreadAfterStreamStateUpdate(
124 refchanged.forget());
125 }
126 mLeftOverData = mReverb->impulseResponseLength();
127 MOZ_ASSERT(mLeftOverData > 0);
128 }
129 aOutput->AllocateChannels(2);
130
131 mReverb->process(&input, aOutput);
132 }
133
IsActive() const134 bool IsActive() const override { return mLeftOverData != INT32_MIN; }
135
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const136 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
137 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
138
139 if (mReverb) {
140 amount += mReverb->sizeOfIncludingThis(aMallocSizeOf);
141 }
142
143 return amount;
144 }
145
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const146 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
147 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
148 }
149
150 private:
151 nsAutoPtr<WebCore::Reverb> mReverb;
152 int32_t mLeftOverData;
153 float mSampleRate;
154 bool mUseBackgroundThreads;
155 bool mNormalize;
156 };
157
ConvolverNode(AudioContext * aContext)158 ConvolverNode::ConvolverNode(AudioContext* aContext)
159 : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
160 ChannelInterpretation::Speakers),
161 mNormalize(true) {
162 ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
163 mStream = AudioNodeStream::Create(
164 aContext, engine, AudioNodeStream::NO_STREAM_FLAGS, aContext->Graph());
165 }
166
Create(JSContext * aCx,AudioContext & aAudioContext,const ConvolverOptions & aOptions,ErrorResult & aRv)167 /* static */ already_AddRefed<ConvolverNode> ConvolverNode::Create(
168 JSContext* aCx, AudioContext& aAudioContext,
169 const ConvolverOptions& aOptions, ErrorResult& aRv) {
170 if (aAudioContext.CheckClosed(aRv)) {
171 return nullptr;
172 }
173
174 RefPtr<ConvolverNode> audioNode = new ConvolverNode(&aAudioContext);
175
176 audioNode->Initialize(aOptions, aRv);
177 if (NS_WARN_IF(aRv.Failed())) {
178 return nullptr;
179 }
180
181 // This must be done before setting the buffer.
182 audioNode->SetNormalize(!aOptions.mDisableNormalization);
183
184 if (aOptions.mBuffer.WasPassed()) {
185 MOZ_ASSERT(aCx);
186 audioNode->SetBuffer(aCx, aOptions.mBuffer.Value(), aRv);
187 if (NS_WARN_IF(aRv.Failed())) {
188 return nullptr;
189 }
190 }
191
192 return audioNode.forget();
193 }
194
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const195 size_t ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
196 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
197 if (mBuffer) {
198 // NB: mBuffer might be shared with the associated engine, by convention
199 // the AudioNode will report.
200 amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
201 }
202 return amount;
203 }
204
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const205 size_t ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
206 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
207 }
208
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)209 JSObject* ConvolverNode::WrapObject(JSContext* aCx,
210 JS::Handle<JSObject*> aGivenProto) {
211 return ConvolverNodeBinding::Wrap(aCx, this, aGivenProto);
212 }
213
SetBuffer(JSContext * aCx,AudioBuffer * aBuffer,ErrorResult & aRv)214 void ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer,
215 ErrorResult& aRv) {
216 if (aBuffer) {
217 switch (aBuffer->NumberOfChannels()) {
218 case 1:
219 case 2:
220 case 4:
221 // Supported number of channels
222 break;
223 default:
224 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
225 return;
226 }
227 }
228
229 // Send the buffer to the stream
230 AudioNodeStream* ns = mStream;
231 MOZ_ASSERT(ns, "Why don't we have a stream here?");
232 if (aBuffer) {
233 AudioChunk data = aBuffer->GetThreadSharedChannelsForRate(aCx);
234 if (data.mBufferFormat == AUDIO_FORMAT_S16) {
235 // Reverb expects data in float format.
236 // Convert on the main thread so as to minimize allocations on the audio
237 // thread.
238 // Reverb will dispose of the buffer once initialized, so convert here
239 // and leave the smaller arrays in the AudioBuffer.
240 // There is currently no value in providing 16/32-byte aligned data
241 // because PadAndMakeScaledDFT() will copy the data (without SIMD
242 // instructions) to aligned arrays for the FFT.
243 RefPtr<SharedBuffer> floatBuffer = SharedBuffer::Create(
244 sizeof(float) * data.mDuration * data.ChannelCount());
245 if (!floatBuffer) {
246 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
247 return;
248 }
249 auto floatData = static_cast<float*>(floatBuffer->Data());
250 for (size_t i = 0; i < data.ChannelCount(); ++i) {
251 ConvertAudioSamples(data.ChannelData<int16_t>()[i], floatData,
252 data.mDuration);
253 data.mChannelData[i] = floatData;
254 floatData += data.mDuration;
255 }
256 data.mBuffer = Move(floatBuffer);
257 data.mBufferFormat = AUDIO_FORMAT_FLOAT32;
258 }
259 SendDoubleParameterToStream(ConvolverNodeEngine::SAMPLE_RATE,
260 aBuffer->SampleRate());
261 ns->SetBuffer(Move(data));
262 } else {
263 ns->SetBuffer(AudioChunk());
264 }
265
266 mBuffer = aBuffer;
267 }
268
SetNormalize(bool aNormalize)269 void ConvolverNode::SetNormalize(bool aNormalize) {
270 mNormalize = aNormalize;
271 SendInt32ParameterToStream(ConvolverNodeEngine::NORMALIZE, aNormalize);
272 }
273
274 } // namespace dom
275 } // namespace mozilla
276