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/FFTFrame.h" 34 #include "platform/audio/VectorMath.h" 35 36 #include <algorithm> 37 #include <limits.h> 38 #include "wtf/Complex.h" 39 #include "wtf/Float32Array.h" 40 #include "wtf/MainThread.h" 41 #include "wtf/MathExtras.h" 42 #include "wtf/Uint8Array.h" 43 44 using namespace std; 45 46 namespace WebCore { 47 48 const double RealtimeAnalyser::DefaultSmoothingTimeConstant = 0.8; 49 const double RealtimeAnalyser::DefaultMinDecibels = -100; 50 const double RealtimeAnalyser::DefaultMaxDecibels = -30; 51 52 const unsigned RealtimeAnalyser::DefaultFFTSize = 2048; 53 // All FFT implementations are expected to handle power-of-two sizes MinFFTSize <= size <= MaxFFTSize. 54 const unsigned RealtimeAnalyser::MinFFTSize = 32; 55 const unsigned RealtimeAnalyser::MaxFFTSize = 2048; 56 const unsigned RealtimeAnalyser::InputBufferSize = RealtimeAnalyser::MaxFFTSize * 2; 57 58 RealtimeAnalyser::RealtimeAnalyser() 59 : m_inputBuffer(InputBufferSize) 60 , m_writeIndex(0) 61 , m_fftSize(DefaultFFTSize) 62 , m_magnitudeBuffer(DefaultFFTSize / 2) 63 , m_smoothingTimeConstant(DefaultSmoothingTimeConstant) 64 , m_minDecibels(DefaultMinDecibels) 65 , m_maxDecibels(DefaultMaxDecibels) 66 { 67 m_analysisFrame = adoptPtr(new FFTFrame(DefaultFFTSize)); 68 } 69 70 void RealtimeAnalyser::reset() 71 { 72 m_writeIndex = 0; 73 m_inputBuffer.zero(); 74 m_magnitudeBuffer.zero(); 75 } 76 77 bool RealtimeAnalyser::setFftSize(size_t size) 78 { 79 ASSERT(isMainThread()); 80 81 // Only allow powers of two. 82 unsigned log2size = static_cast<unsigned>(log2(size)); 83 bool isPOT(1UL << log2size == size); 84 85 if (!isPOT || size > MaxFFTSize || size < MinFFTSize) 86 return false; 87 88 if (m_fftSize != size) { 89 m_analysisFrame = adoptPtr(new FFTFrame(size)); 90 // m_magnitudeBuffer has size = fftSize / 2 because it contains floats reduced from complex values in m_analysisFrame. 91 m_magnitudeBuffer.allocate(size / 2); 92 m_fftSize = size; 93 } 94 95 return true; 96 } 97 98 void RealtimeAnalyser::writeInput(AudioBus* bus, size_t framesToProcess) 99 { 100 bool isBusGood = bus && bus->numberOfChannels() > 0 && bus->channel(0)->length() >= framesToProcess; 101 ASSERT(isBusGood); 102 if (!isBusGood) 103 return; 104 105 // FIXME : allow to work with non-FFTSize divisible chunking 106 bool isDestinationGood = m_writeIndex < m_inputBuffer.size() && m_writeIndex + framesToProcess <= m_inputBuffer.size(); 107 ASSERT(isDestinationGood); 108 if (!isDestinationGood) 109 return; 110 111 // Perform real-time analysis 112 const float* source = bus->channel(0)->data(); 113 float* dest = m_inputBuffer.data() + m_writeIndex; 114 115 // The source has already been sanity checked with isBusGood above. 116 memcpy(dest, source, sizeof(float) * framesToProcess); 117 118 // Sum all channels in one if numberOfChannels > 1. 119 unsigned numberOfChannels = bus->numberOfChannels(); 120 if (numberOfChannels > 1) { 121 for (unsigned i = 1; i < numberOfChannels; i++) { 122 source = bus->channel(i)->data(); 123 VectorMath::vadd(dest, 1, source, 1, dest, 1, framesToProcess); 124 } 125 const float scale = 1.0 / numberOfChannels; 126 VectorMath::vsmul(dest, 1, &scale, dest, 1, framesToProcess); 127 } 128 129 m_writeIndex += framesToProcess; 130 if (m_writeIndex >= InputBufferSize) 131 m_writeIndex = 0; 132 } 133 134 namespace { 135 136 void applyWindow(float* p, size_t n) 137 { 138 ASSERT(isMainThread()); 139 140 // Blackman window 141 double alpha = 0.16; 142 double a0 = 0.5 * (1 - alpha); 143 double a1 = 0.5; 144 double a2 = 0.5 * alpha; 145 146 for (unsigned i = 0; i < n; ++i) { 147 double x = static_cast<double>(i) / static_cast<double>(n); 148 double window = a0 - a1 * cos(2 * piDouble * x) + a2 * cos(4 * piDouble * x); 149 p[i] *= float(window); 150 } 151 } 152 153 } // namespace 154 155 void RealtimeAnalyser::doFFTAnalysis() 156 { 157 ASSERT(isMainThread()); 158 159 // Unroll the input buffer into a temporary buffer, where we'll apply an analysis window followed by an FFT. 160 size_t fftSize = this->fftSize(); 161 162 AudioFloatArray temporaryBuffer(fftSize); 163 float* inputBuffer = m_inputBuffer.data(); 164 float* tempP = temporaryBuffer.data(); 165 166 // Take the previous fftSize values from the input buffer and copy into the temporary buffer. 167 unsigned writeIndex = m_writeIndex; 168 if (writeIndex < fftSize) { 169 memcpy(tempP, inputBuffer + writeIndex - fftSize + InputBufferSize, sizeof(*tempP) * (fftSize - writeIndex)); 170 memcpy(tempP + fftSize - writeIndex, inputBuffer, sizeof(*tempP) * writeIndex); 171 } else 172 memcpy(tempP, inputBuffer + writeIndex - fftSize, sizeof(*tempP) * fftSize); 173 174 175 // Window the input samples. 176 applyWindow(tempP, fftSize); 177 178 // Do the analysis. 179 m_analysisFrame->doFFT(tempP); 180 181 float* realP = m_analysisFrame->realData(); 182 float* imagP = m_analysisFrame->imagData(); 183 184 // Blow away the packed nyquist component. 185 imagP[0] = 0; 186 187 // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor). 188 const double magnitudeScale = 1.0 / DefaultFFTSize; 189 190 // A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes. 191 double k = m_smoothingTimeConstant; 192 k = max(0.0, k); 193 k = min(1.0, k); 194 195 // Convert the analysis data from complex to magnitude and average with the previous result. 196 float* destination = magnitudeBuffer().data(); 197 size_t n = magnitudeBuffer().size(); 198 for (size_t i = 0; i < n; ++i) { 199 Complex c(realP[i], imagP[i]); 200 double scalarMagnitude = abs(c) * magnitudeScale; 201 destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude); 202 } 203 } 204 205 void RealtimeAnalyser::getFloatFrequencyData(Float32Array* destinationArray) 206 { 207 ASSERT(isMainThread()); 208 209 if (!destinationArray) 210 return; 211 212 doFFTAnalysis(); 213 214 // Convert from linear magnitude to floating-point decibels. 215 const double minDecibels = m_minDecibels; 216 unsigned sourceLength = magnitudeBuffer().size(); 217 size_t len = min(sourceLength, destinationArray->length()); 218 if (len > 0) { 219 const float* source = magnitudeBuffer().data(); 220 float* destination = destinationArray->data(); 221 222 for (unsigned i = 0; i < len; ++i) { 223 float linearValue = source[i]; 224 double dbMag = !linearValue ? minDecibels : AudioUtilities::linearToDecibels(linearValue); 225 destination[i] = float(dbMag); 226 } 227 } 228 } 229 230 void RealtimeAnalyser::getByteFrequencyData(Uint8Array* destinationArray) 231 { 232 ASSERT(isMainThread()); 233 234 if (!destinationArray) 235 return; 236 237 doFFTAnalysis(); 238 239 // Convert from linear magnitude to unsigned-byte decibels. 240 unsigned sourceLength = magnitudeBuffer().size(); 241 size_t len = min(sourceLength, destinationArray->length()); 242 if (len > 0) { 243 const double rangeScaleFactor = m_maxDecibels == m_minDecibels ? 1 : 1 / (m_maxDecibels - m_minDecibels); 244 const double minDecibels = m_minDecibels; 245 246 const float* source = magnitudeBuffer().data(); 247 unsigned char* destination = destinationArray->data(); 248 249 for (unsigned i = 0; i < len; ++i) { 250 float linearValue = source[i]; 251 double dbMag = !linearValue ? minDecibels : AudioUtilities::linearToDecibels(linearValue); 252 253 // The range m_minDecibels to m_maxDecibels will be scaled to byte values from 0 to UCHAR_MAX. 254 double scaledValue = UCHAR_MAX * (dbMag - minDecibels) * rangeScaleFactor; 255 256 // Clip to valid range. 257 if (scaledValue < 0) 258 scaledValue = 0; 259 if (scaledValue > UCHAR_MAX) 260 scaledValue = UCHAR_MAX; 261 262 destination[i] = static_cast<unsigned char>(scaledValue); 263 } 264 } 265 } 266 267 void RealtimeAnalyser::getByteTimeDomainData(Uint8Array* destinationArray) 268 { 269 ASSERT(isMainThread()); 270 271 if (!destinationArray) 272 return; 273 274 unsigned fftSize = this->fftSize(); 275 size_t len = min(fftSize, destinationArray->length()); 276 if (len > 0) { 277 bool isInputBufferGood = m_inputBuffer.size() == InputBufferSize && m_inputBuffer.size() > fftSize; 278 ASSERT(isInputBufferGood); 279 if (!isInputBufferGood) 280 return; 281 282 float* inputBuffer = m_inputBuffer.data(); 283 unsigned char* destination = destinationArray->data(); 284 285 unsigned writeIndex = m_writeIndex; 286 287 for (unsigned i = 0; i < len; ++i) { 288 // Buffer access is protected due to modulo operation. 289 float value = inputBuffer[(i + writeIndex - fftSize + InputBufferSize) % InputBufferSize]; 290 291 // Scale from nominal -1 -> +1 to unsigned byte. 292 double scaledValue = 128 * (value + 1); 293 294 // Clip to valid range. 295 if (scaledValue < 0) 296 scaledValue = 0; 297 if (scaledValue > UCHAR_MAX) 298 scaledValue = UCHAR_MAX; 299 300 destination[i] = static_cast<unsigned char>(scaledValue); 301 } 302 } 303 } 304 305 } // namespace WebCore 306 307 #endif // ENABLE(WEB_AUDIO) 308