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 "mozilla/dom/AnalyserNode.h"
8 #include "mozilla/dom/AnalyserNodeBinding.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "mozilla/Mutex.h"
12 #include "mozilla/PodOperations.h"
13 #include "nsMathUtils.h"
14 
15 namespace mozilla {
16 
17 static const uint32_t MAX_FFT_SIZE = 32768;
18 static const size_t CHUNK_COUNT = MAX_FFT_SIZE >> WEBAUDIO_BLOCK_SIZE_BITS;
19 static_assert(MAX_FFT_SIZE == CHUNK_COUNT * WEBAUDIO_BLOCK_SIZE,
20               "MAX_FFT_SIZE must be a multiple of WEBAUDIO_BLOCK_SIZE");
21 static_assert((CHUNK_COUNT & (CHUNK_COUNT - 1)) == 0,
22               "CHUNK_COUNT must be power of 2 for remainder behavior");
23 
24 namespace dom {
25 
26 class AnalyserNodeEngine final : public AudioNodeEngine {
27   class TransferBuffer final : public Runnable {
28    public:
TransferBuffer(AudioNodeTrack * aTrack,const AudioChunk & aChunk)29     TransferBuffer(AudioNodeTrack* aTrack, const AudioChunk& aChunk)
30         : Runnable("dom::AnalyserNodeEngine::TransferBuffer"),
31           mTrack(aTrack),
32           mChunk(aChunk) {}
33 
Run()34     NS_IMETHOD Run() override {
35       RefPtr<AnalyserNode> node =
36           static_cast<AnalyserNode*>(mTrack->Engine()->NodeMainThread());
37       if (node) {
38         node->AppendChunk(mChunk);
39       }
40       return NS_OK;
41     }
42 
43    private:
44     RefPtr<AudioNodeTrack> mTrack;
45     AudioChunk mChunk;
46   };
47 
48  public:
AnalyserNodeEngine(AnalyserNode * aNode)49   explicit AnalyserNodeEngine(AnalyserNode* aNode) : AudioNodeEngine(aNode) {
50     MOZ_ASSERT(NS_IsMainThread());
51   }
52 
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)53   virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
54                             const AudioBlock& aInput, AudioBlock* aOutput,
55                             bool* aFinished) override {
56     *aOutput = aInput;
57 
58     if (aInput.IsNull()) {
59       // If AnalyserNode::mChunks has only null chunks, then there is no need
60       // to send further null chunks.
61       if (mChunksToProcess == 0) {
62         return;
63       }
64 
65       --mChunksToProcess;
66       if (mChunksToProcess == 0) {
67         aTrack->ScheduleCheckForInactive();
68       }
69 
70     } else {
71       // This many null chunks will be required to empty AnalyserNode::mChunks.
72       mChunksToProcess = CHUNK_COUNT;
73     }
74 
75     RefPtr<TransferBuffer> transfer =
76         new TransferBuffer(aTrack, aInput.AsAudioChunk());
77     mAbstractMainThread->Dispatch(transfer.forget());
78   }
79 
IsActive() const80   virtual bool IsActive() const override { return mChunksToProcess != 0; }
81 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const82   virtual size_t SizeOfIncludingThis(
83       MallocSizeOf aMallocSizeOf) const override {
84     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
85   }
86 
87   uint32_t mChunksToProcess = 0;
88 };
89 
90 /* static */
Create(AudioContext & aAudioContext,const AnalyserOptions & aOptions,ErrorResult & aRv)91 already_AddRefed<AnalyserNode> AnalyserNode::Create(
92     AudioContext& aAudioContext, const AnalyserOptions& aOptions,
93     ErrorResult& aRv) {
94   RefPtr<AnalyserNode> analyserNode = new AnalyserNode(&aAudioContext);
95 
96   analyserNode->Initialize(aOptions, aRv);
97   if (NS_WARN_IF(aRv.Failed())) {
98     return nullptr;
99   }
100 
101   analyserNode->SetFftSize(aOptions.mFftSize, aRv);
102   if (NS_WARN_IF(aRv.Failed())) {
103     return nullptr;
104   }
105 
106   analyserNode->SetMinAndMaxDecibels(aOptions.mMinDecibels,
107                                      aOptions.mMaxDecibels, aRv);
108   if (NS_WARN_IF(aRv.Failed())) {
109     return nullptr;
110   }
111 
112   analyserNode->SetSmoothingTimeConstant(aOptions.mSmoothingTimeConstant, aRv);
113   if (NS_WARN_IF(aRv.Failed())) {
114     return nullptr;
115   }
116 
117   return analyserNode.forget();
118 }
119 
AnalyserNode(AudioContext * aContext)120 AnalyserNode::AnalyserNode(AudioContext* aContext)
121     : AudioNode(aContext, 2, ChannelCountMode::Max,
122                 ChannelInterpretation::Speakers),
123       mAnalysisBlock(2048),
124       mMinDecibels(-100.),
125       mMaxDecibels(-30.),
126       mSmoothingTimeConstant(.8) {
127   mTrack =
128       AudioNodeTrack::Create(aContext, new AnalyserNodeEngine(this),
129                              AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
130 
131   // Enough chunks must be recorded to handle the case of fftSize being
132   // increased to maximum immediately before getFloatTimeDomainData() is
133   // called, for example.
134   Unused << mChunks.SetLength(CHUNK_COUNT, fallible);
135 
136   AllocateBuffer();
137 }
138 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const139 size_t AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
140   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
141   amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
142   amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
143   amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
144   return amount;
145 }
146 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const147 size_t AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
148   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
149 }
150 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)151 JSObject* AnalyserNode::WrapObject(JSContext* aCx,
152                                    JS::Handle<JSObject*> aGivenProto) {
153   return AnalyserNode_Binding::Wrap(aCx, this, aGivenProto);
154 }
155 
SetFftSize(uint32_t aValue,ErrorResult & aRv)156 void AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv) {
157   // Disallow values that are not a power of 2 and outside the [32,32768] range
158   if (aValue < 32 || aValue > MAX_FFT_SIZE || (aValue & (aValue - 1)) != 0) {
159     aRv.ThrowIndexSizeError(nsPrintfCString(
160         "FFT size %u is not a power of two in the range 32 to 32768", aValue));
161     return;
162   }
163   if (FftSize() != aValue) {
164     mAnalysisBlock.SetFFTSize(aValue);
165     AllocateBuffer();
166   }
167 }
168 
SetMinDecibels(double aValue,ErrorResult & aRv)169 void AnalyserNode::SetMinDecibels(double aValue, ErrorResult& aRv) {
170   if (aValue >= mMaxDecibels) {
171     aRv.ThrowIndexSizeError(nsPrintfCString(
172         "%g is not strictly smaller than current maxDecibels (%g)", aValue,
173         mMaxDecibels));
174     return;
175   }
176   mMinDecibels = aValue;
177 }
178 
SetMaxDecibels(double aValue,ErrorResult & aRv)179 void AnalyserNode::SetMaxDecibels(double aValue, ErrorResult& aRv) {
180   if (aValue <= mMinDecibels) {
181     aRv.ThrowIndexSizeError(nsPrintfCString(
182         "%g is not strictly larger than current minDecibels (%g)", aValue,
183         mMinDecibels));
184     return;
185   }
186   mMaxDecibels = aValue;
187 }
188 
SetMinAndMaxDecibels(double aMinValue,double aMaxValue,ErrorResult & aRv)189 void AnalyserNode::SetMinAndMaxDecibels(double aMinValue, double aMaxValue,
190                                         ErrorResult& aRv) {
191   if (aMinValue >= aMaxValue) {
192     aRv.ThrowIndexSizeError(nsPrintfCString(
193         "minDecibels value (%g) must be smaller than maxDecibels value (%g)",
194         aMinValue, aMaxValue));
195     return;
196   }
197   mMinDecibels = aMinValue;
198   mMaxDecibels = aMaxValue;
199 }
200 
SetSmoothingTimeConstant(double aValue,ErrorResult & aRv)201 void AnalyserNode::SetSmoothingTimeConstant(double aValue, ErrorResult& aRv) {
202   if (aValue < 0 || aValue > 1) {
203     aRv.ThrowIndexSizeError(
204         nsPrintfCString("%g is not in the range [0, 1]", aValue));
205     return;
206   }
207   mSmoothingTimeConstant = aValue;
208 }
209 
GetFloatFrequencyData(const Float32Array & aArray)210 void AnalyserNode::GetFloatFrequencyData(const Float32Array& aArray) {
211   if (!FFTAnalysis()) {
212     // Might fail to allocate memory
213     return;
214   }
215 
216   aArray.ComputeState();
217 
218   float* buffer = aArray.Data();
219   size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
220 
221   for (size_t i = 0; i < length; ++i) {
222     buffer[i] = WebAudioUtils::ConvertLinearToDecibels(
223         mOutputBuffer[i], -std::numeric_limits<float>::infinity());
224   }
225 }
226 
GetByteFrequencyData(const Uint8Array & aArray)227 void AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray) {
228   if (!FFTAnalysis()) {
229     // Might fail to allocate memory
230     return;
231   }
232 
233   const double rangeScaleFactor = 1.0 / (mMaxDecibels - mMinDecibels);
234 
235   aArray.ComputeState();
236 
237   unsigned char* buffer = aArray.Data();
238   size_t length = std::min(size_t(aArray.Length()), mOutputBuffer.Length());
239 
240   for (size_t i = 0; i < length; ++i) {
241     const double decibels =
242         WebAudioUtils::ConvertLinearToDecibels(mOutputBuffer[i], mMinDecibels);
243     // scale down the value to the range of [0, UCHAR_MAX]
244     const double scaled = std::max(
245         0.0, std::min(double(UCHAR_MAX),
246                       UCHAR_MAX*(decibels - mMinDecibels) * rangeScaleFactor));
247     buffer[i] = static_cast<unsigned char>(scaled);
248   }
249 }
250 
GetFloatTimeDomainData(const Float32Array & aArray)251 void AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray) {
252   aArray.ComputeState();
253 
254   float* buffer = aArray.Data();
255   size_t length = std::min(aArray.Length(), FftSize());
256 
257   GetTimeDomainData(buffer, length);
258 }
259 
GetByteTimeDomainData(const Uint8Array & aArray)260 void AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray) {
261   aArray.ComputeState();
262 
263   size_t length = std::min(aArray.Length(), FftSize());
264 
265   AlignedTArray<float> tmpBuffer;
266   if (!tmpBuffer.SetLength(length, fallible)) {
267     return;
268   }
269 
270   GetTimeDomainData(tmpBuffer.Elements(), length);
271 
272   unsigned char* buffer = aArray.Data();
273   for (size_t i = 0; i < length; ++i) {
274     const float value = tmpBuffer[i];
275     // scale the value to the range of [0, UCHAR_MAX]
276     const float scaled =
277         std::max(0.0f, std::min(float(UCHAR_MAX), 128.0f * (value + 1.0f)));
278     buffer[i] = static_cast<unsigned char>(scaled);
279   }
280 }
281 
FFTAnalysis()282 bool AnalyserNode::FFTAnalysis() {
283   AlignedTArray<float> tmpBuffer;
284   size_t fftSize = FftSize();
285   if (!tmpBuffer.SetLength(fftSize, fallible)) {
286     return false;
287   }
288 
289   float* inputBuffer = tmpBuffer.Elements();
290   GetTimeDomainData(inputBuffer, fftSize);
291   ApplyBlackmanWindow(inputBuffer, fftSize);
292   mAnalysisBlock.PerformFFT(inputBuffer);
293 
294   // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT
295   // scaling factor).
296   const double magnitudeScale = 1.0 / fftSize;
297 
298   for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) {
299     double scalarMagnitude =
300         NS_hypot(mAnalysisBlock.RealData(i), mAnalysisBlock.ImagData(i)) *
301         magnitudeScale;
302     mOutputBuffer[i] = mSmoothingTimeConstant * mOutputBuffer[i] +
303                        (1.0 - mSmoothingTimeConstant) * scalarMagnitude;
304   }
305 
306   return true;
307 }
308 
ApplyBlackmanWindow(float * aBuffer,uint32_t aSize)309 void AnalyserNode::ApplyBlackmanWindow(float* aBuffer, uint32_t aSize) {
310   double alpha = 0.16;
311   double a0 = 0.5 * (1.0 - alpha);
312   double a1 = 0.5;
313   double a2 = 0.5 * alpha;
314 
315   for (uint32_t i = 0; i < aSize; ++i) {
316     double x = double(i) / aSize;
317     double window = a0 - a1 * cos(2 * M_PI * x) + a2 * cos(4 * M_PI * x);
318     aBuffer[i] *= window;
319   }
320 }
321 
AllocateBuffer()322 bool AnalyserNode::AllocateBuffer() {
323   bool result = true;
324   if (mOutputBuffer.Length() != FrequencyBinCount()) {
325     if (!mOutputBuffer.SetLength(FrequencyBinCount(), fallible)) {
326       return false;
327     }
328     memset(mOutputBuffer.Elements(), 0, sizeof(float) * FrequencyBinCount());
329   }
330   return result;
331 }
332 
AppendChunk(const AudioChunk & aChunk)333 void AnalyserNode::AppendChunk(const AudioChunk& aChunk) {
334   if (mChunks.Length() == 0) {
335     return;
336   }
337 
338   ++mCurrentChunk;
339   mChunks[mCurrentChunk & (CHUNK_COUNT - 1)] = aChunk;
340 }
341 
342 // Reads into aData the oldest aLength samples of the fftSize most recent
343 // samples.
GetTimeDomainData(float * aData,size_t aLength)344 void AnalyserNode::GetTimeDomainData(float* aData, size_t aLength) {
345   size_t fftSize = FftSize();
346   MOZ_ASSERT(aLength <= fftSize);
347 
348   if (mChunks.Length() == 0) {
349     PodZero(aData, aLength);
350     return;
351   }
352 
353   size_t readChunk =
354       mCurrentChunk - ((fftSize - 1) >> WEBAUDIO_BLOCK_SIZE_BITS);
355   size_t readIndex = (0 - fftSize) & (WEBAUDIO_BLOCK_SIZE - 1);
356   MOZ_ASSERT(readIndex == 0 || readIndex + fftSize == WEBAUDIO_BLOCK_SIZE);
357 
358   for (size_t writeIndex = 0; writeIndex < aLength;) {
359     const AudioChunk& chunk = mChunks[readChunk & (CHUNK_COUNT - 1)];
360     const size_t channelCount = chunk.ChannelCount();
361     size_t copyLength =
362         std::min<size_t>(aLength - writeIndex, WEBAUDIO_BLOCK_SIZE);
363     float* dataOut = &aData[writeIndex];
364 
365     if (channelCount == 0) {
366       PodZero(dataOut, copyLength);
367     } else {
368       float scale = chunk.mVolume / channelCount;
369       {  // channel 0
370         auto channelData =
371             static_cast<const float*>(chunk.mChannelData[0]) + readIndex;
372         AudioBufferCopyWithScale(channelData, scale, dataOut, copyLength);
373       }
374       for (uint32_t i = 1; i < channelCount; ++i) {
375         auto channelData =
376             static_cast<const float*>(chunk.mChannelData[i]) + readIndex;
377         AudioBufferAddWithScale(channelData, scale, dataOut, copyLength);
378       }
379     }
380 
381     readChunk++;
382     writeIndex += copyLength;
383   }
384 }
385 
386 }  // namespace dom
387 }  // namespace mozilla
388