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