• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010, Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 
25 #include "config.h"
26 
27 #if ENABLE(WEB_AUDIO)
28 
29 #include "modules/webaudio/RealtimeAnalyser.h"
30 
31 #include "platform/audio/AudioBus.h"
32 #include "platform/audio/AudioUtilities.h"
33 #include "platform/audio/VectorMath.h"
34 
35 #include <algorithm>
36 #include <limits.h>
37 #include "wtf/Complex.h"
38 #include "wtf/Float32Array.h"
39 #include "wtf/MainThread.h"
40 #include "wtf/MathExtras.h"
41 #include "wtf/Uint8Array.h"
42 
43 using namespace std;
44 
45 namespace WebCore {
46 
47 const double RealtimeAnalyser::DefaultSmoothingTimeConstant  = 0.8;
48 const double RealtimeAnalyser::DefaultMinDecibels = -100;
49 const double RealtimeAnalyser::DefaultMaxDecibels = -30;
50 
51 const unsigned RealtimeAnalyser::DefaultFFTSize = 2048;
52 // All FFT implementations are expected to handle power-of-two sizes MinFFTSize <= size <= MaxFFTSize.
53 const unsigned RealtimeAnalyser::MinFFTSize = 32;
54 const unsigned RealtimeAnalyser::MaxFFTSize = 2048;
55 const unsigned RealtimeAnalyser::InputBufferSize = RealtimeAnalyser::MaxFFTSize * 2;
56 
RealtimeAnalyser()57 RealtimeAnalyser::RealtimeAnalyser()
58     : m_inputBuffer(InputBufferSize)
59     , m_writeIndex(0)
60     , m_fftSize(DefaultFFTSize)
61     , m_magnitudeBuffer(DefaultFFTSize / 2)
62     , m_smoothingTimeConstant(DefaultSmoothingTimeConstant)
63     , m_minDecibels(DefaultMinDecibels)
64     , m_maxDecibels(DefaultMaxDecibels)
65 {
66     m_analysisFrame = adoptPtr(new FFTFrame(DefaultFFTSize));
67 }
68 
setFftSize(size_t size)69 bool RealtimeAnalyser::setFftSize(size_t size)
70 {
71     ASSERT(isMainThread());
72 
73     // Only allow powers of two.
74     unsigned log2size = static_cast<unsigned>(log2(size));
75     bool isPOT(1UL << log2size == size);
76 
77     if (!isPOT || size > MaxFFTSize || size < MinFFTSize)
78         return false;
79 
80     if (m_fftSize != size) {
81         m_analysisFrame = adoptPtr(new FFTFrame(size));
82         // m_magnitudeBuffer has size = fftSize / 2 because it contains floats reduced from complex values in m_analysisFrame.
83         m_magnitudeBuffer.allocate(size / 2);
84         m_fftSize = size;
85     }
86 
87     return true;
88 }
89 
writeInput(AudioBus * bus,size_t framesToProcess)90 void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess)
91 {
92     bool isBusGood = bus && bus->numberOfChannels() > 0 && bus->channel(0)->length() >= framesToProcess;
93     ASSERT(isBusGood);
94     if (!isBusGood)
95         return;
96 
97     // FIXME : allow to work with non-FFTSize divisible chunking
98     bool isDestinationGood = m_writeIndex < m_inputBuffer.size() && m_writeIndex + framesToProcess <= m_inputBuffer.size();
99     ASSERT(isDestinationGood);
100     if (!isDestinationGood)
101         return;
102 
103     // Perform real-time analysis
104     const float* source = bus->channel(0)->data();
105     float* dest = m_inputBuffer.data() + m_writeIndex;
106 
107     // The source has already been sanity checked with isBusGood above.
108     memcpy(dest, source, sizeof(float) * framesToProcess);
109 
110     // Sum all channels in one if numberOfChannels > 1.
111     unsigned numberOfChannels = bus->numberOfChannels();
112     if (numberOfChannels > 1) {
113         for (unsigned i = 1; i < numberOfChannels; i++) {
114             source = bus->channel(i)->data();
115             VectorMath::vadd(dest, 1, source, 1, dest, 1, framesToProcess);
116         }
117         const float scale =  1.0 / numberOfChannels;
118         VectorMath::vsmul(dest, 1, &scale, dest, 1, framesToProcess);
119     }
120 
121     m_writeIndex += framesToProcess;
122     if (m_writeIndex >= InputBufferSize)
123         m_writeIndex = 0;
124 }
125 
126 namespace {
127 
applyWindow(float * p,size_t n)128 void applyWindow(float* p, size_t n)
129 {
130     ASSERT(isMainThread());
131 
132     // Blackman window
133     double alpha = 0.16;
134     double a0 = 0.5 * (1 - alpha);
135     double a1 = 0.5;
136     double a2 = 0.5 * alpha;
137 
138     for (unsigned i = 0; i < n; ++i) {
139         double x = static_cast<double>(i) / static_cast<double>(n);
140         double window = a0 - a1 * cos(twoPiDouble * x) + a2 * cos(twoPiDouble * 2.0 * x);
141         p[i] *= float(window);
142     }
143 }
144 
145 } // namespace
146 
doFFTAnalysis()147 void RealtimeAnalyser::doFFTAnalysis()
148 {
149     ASSERT(isMainThread());
150 
151     // Unroll the input buffer into a temporary buffer, where we'll apply an analysis window followed by an FFT.
152     size_t fftSize = this->fftSize();
153 
154     AudioFloatArray temporaryBuffer(fftSize);
155     float* inputBuffer = m_inputBuffer.data();
156     float* tempP = temporaryBuffer.data();
157 
158     // Take the previous fftSize values from the input buffer and copy into the temporary buffer.
159     unsigned writeIndex = m_writeIndex;
160     if (writeIndex < fftSize) {
161         memcpy(tempP, inputBuffer + writeIndex - fftSize + InputBufferSize, sizeof(*tempP) * (fftSize - writeIndex));
162         memcpy(tempP + fftSize - writeIndex, inputBuffer, sizeof(*tempP) * writeIndex);
163     } else
164         memcpy(tempP, inputBuffer + writeIndex - fftSize, sizeof(*tempP) * fftSize);
165 
166 
167     // Window the input samples.
168     applyWindow(tempP, fftSize);
169 
170     // Do the analysis.
171     m_analysisFrame->doFFT(tempP);
172 
173     float* realP = m_analysisFrame->realData();
174     float* imagP = m_analysisFrame->imagData();
175 
176     // Blow away the packed nyquist component.
177     imagP[0] = 0;
178 
179     // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor).
180     const double magnitudeScale = 1.0 / fftSize;
181 
182     // A value of 0 does no averaging with the previous result.  Larger values produce slower, but smoother changes.
183     double k = m_smoothingTimeConstant;
184     k = max(0.0, k);
185     k = min(1.0, k);
186 
187     // Convert the analysis data from complex to magnitude and average with the previous result.
188     float* destination = magnitudeBuffer().data();
189     size_t n = magnitudeBuffer().size();
190     for (size_t i = 0; i < n; ++i) {
191         Complex c(realP[i], imagP[i]);
192         double scalarMagnitude = abs(c) * magnitudeScale;
193         destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude);
194     }
195 }
196 
getFloatFrequencyData(Float32Array * destinationArray)197 void RealtimeAnalyser::getFloatFrequencyData(Float32Array* destinationArray)
198 {
199     ASSERT(isMainThread());
200 
201     if (!destinationArray)
202         return;
203 
204     doFFTAnalysis();
205 
206     // Convert from linear magnitude to floating-point decibels.
207     const double minDecibels = m_minDecibels;
208     unsigned sourceLength = magnitudeBuffer().size();
209     size_t len = min(sourceLength, destinationArray->length());
210     if (len > 0) {
211         const float* source = magnitudeBuffer().data();
212         float* destination = destinationArray->data();
213 
214         for (unsigned i = 0; i < len; ++i) {
215             float linearValue = source[i];
216             double dbMag = !linearValue ? minDecibels : AudioUtilities::linearToDecibels(linearValue);
217             destination[i] = float(dbMag);
218         }
219     }
220 }
221 
getByteFrequencyData(Uint8Array * destinationArray)222 void RealtimeAnalyser::getByteFrequencyData(Uint8Array* destinationArray)
223 {
224     ASSERT(isMainThread());
225 
226     if (!destinationArray)
227         return;
228 
229     doFFTAnalysis();
230 
231     // Convert from linear magnitude to unsigned-byte decibels.
232     unsigned sourceLength = magnitudeBuffer().size();
233     size_t len = min(sourceLength, destinationArray->length());
234     if (len > 0) {
235         const double rangeScaleFactor = m_maxDecibels == m_minDecibels ? 1 : 1 / (m_maxDecibels - m_minDecibels);
236         const double minDecibels = m_minDecibels;
237 
238         const float* source = magnitudeBuffer().data();
239         unsigned char* destination = destinationArray->data();
240 
241         for (unsigned i = 0; i < len; ++i) {
242             float linearValue = source[i];
243             double dbMag = !linearValue ? minDecibels : AudioUtilities::linearToDecibels(linearValue);
244 
245             // The range m_minDecibels to m_maxDecibels will be scaled to byte values from 0 to UCHAR_MAX.
246             double scaledValue = UCHAR_MAX * (dbMag - minDecibels) * rangeScaleFactor;
247 
248             // Clip to valid range.
249             if (scaledValue < 0)
250                 scaledValue = 0;
251             if (scaledValue > UCHAR_MAX)
252                 scaledValue = UCHAR_MAX;
253 
254             destination[i] = static_cast<unsigned char>(scaledValue);
255         }
256     }
257 }
258 
getFloatTimeDomainData(Float32Array * destinationArray)259 void RealtimeAnalyser::getFloatTimeDomainData(Float32Array* destinationArray)
260 {
261     ASSERT(isMainThread());
262 
263     if (!destinationArray)
264         return;
265 
266     unsigned fftSize = this->fftSize();
267     size_t len = min(fftSize, destinationArray->length());
268     if (len > 0) {
269         bool isInputBufferGood = m_inputBuffer.size() == InputBufferSize && m_inputBuffer.size() > fftSize;
270         ASSERT(isInputBufferGood);
271         if (!isInputBufferGood)
272             return;
273 
274         float* inputBuffer = m_inputBuffer.data();
275         float* destination = destinationArray->data();
276 
277         unsigned writeIndex = m_writeIndex;
278 
279         for (unsigned i = 0; i < len; ++i) {
280             // Buffer access is protected due to modulo operation.
281             float value = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize];
282 
283             destination[i] = value;
284         }
285     }
286 }
287 
getByteTimeDomainData(Uint8Array * destinationArray)288 void RealtimeAnalyser::getByteTimeDomainData(Uint8Array* destinationArray)
289 {
290     ASSERT(isMainThread());
291 
292     if (!destinationArray)
293         return;
294 
295     unsigned fftSize = this->fftSize();
296     size_t len = min(fftSize, destinationArray->length());
297     if (len > 0) {
298         bool isInputBufferGood = m_inputBuffer.size() == InputBufferSize && m_inputBuffer.size() > fftSize;
299         ASSERT(isInputBufferGood);
300         if (!isInputBufferGood)
301             return;
302 
303         float* inputBuffer = m_inputBuffer.data();
304         unsigned char* destination = destinationArray->data();
305 
306         unsigned writeIndex = m_writeIndex;
307 
308         for (unsigned i = 0; i < len; ++i) {
309             // Buffer access is protected due to modulo operation.
310             float value = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize];
311 
312             // Scale from nominal -1 -> +1 to unsigned byte.
313             double scaledValue = 128 * (value + 1);
314 
315             // Clip to valid range.
316             if (scaledValue < 0)
317                 scaledValue = 0;
318             if (scaledValue > UCHAR_MAX)
319                 scaledValue = UCHAR_MAX;
320 
321             destination[i] = static_cast<unsigned char>(scaledValue);
322         }
323     }
324 }
325 
326 } // namespace WebCore
327 
328 #endif // ENABLE(WEB_AUDIO)
329