content/media/webaudio/blink/PeriodicWave.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /*
michael@0 2 * Copyright (C) 2012 Google Inc. All rights reserved.
michael@0 3 *
michael@0 4 * Redistribution and use in source and binary forms, with or without
michael@0 5 * modification, are permitted provided that the following conditions
michael@0 6 * are met:
michael@0 7 *
michael@0 8 * 1. Redistributions of source code must retain the above copyright
michael@0 9 * notice, this list of conditions and the following disclaimer.
michael@0 10 * 2. Redistributions in binary form must reproduce the above copyright
michael@0 11 * notice, this list of conditions and the following disclaimer in the
michael@0 12 * documentation and/or other materials provided with the distribution.
michael@0 13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
michael@0 14 * its contributors may be used to endorse or promote products derived
michael@0 15 * from this software without specific prior written permission.
michael@0 16 *
michael@0 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
michael@0 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
michael@0 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
michael@0 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
michael@0 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
michael@0 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
michael@0 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
michael@0 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
michael@0 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
michael@0 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
michael@0 27 */
michael@0 28
michael@0 29 #include "PeriodicWave.h"
michael@0 30 #include <algorithm>
michael@0 31 #include <cmath>
michael@0 32 #include "mozilla/FFTBlock.h"
michael@0 33
michael@0 34 const unsigned PeriodicWaveSize = 4096; // This must be a power of two.
michael@0 35 const unsigned NumberOfRanges = 36; // There should be 3 * log2(PeriodicWaveSize) 1/3 octave ranges.
michael@0 36 const float CentsPerRange = 1200 / 3; // 1/3 Octave.
michael@0 37
michael@0 38 using namespace mozilla;
michael@0 39 using mozilla::dom::OscillatorType;
michael@0 40
michael@0 41 namespace WebCore {
michael@0 42
michael@0 43 PeriodicWave* PeriodicWave::create(float sampleRate,
michael@0 44 const float* real,
michael@0 45 const float* imag,
michael@0 46 size_t numberOfComponents)
michael@0 47 {
michael@0 48 bool isGood = real && imag && numberOfComponents > 0 &&
michael@0 49 numberOfComponents <= PeriodicWaveSize;
michael@0 50 MOZ_ASSERT(isGood);
michael@0 51 if (isGood) {
michael@0 52 PeriodicWave* periodicWave = new PeriodicWave(sampleRate);
michael@0 53 periodicWave->createBandLimitedTables(real, imag, numberOfComponents);
michael@0 54 return periodicWave;
michael@0 55 }
michael@0 56 return 0;
michael@0 57 }
michael@0 58
michael@0 59 PeriodicWave* PeriodicWave::createSine(float sampleRate)
michael@0 60 {
michael@0 61 PeriodicWave* periodicWave = new PeriodicWave(sampleRate);
michael@0 62 periodicWave->generateBasicWaveform(OscillatorType::Sine);
michael@0 63 return periodicWave;
michael@0 64 }
michael@0 65
michael@0 66 PeriodicWave* PeriodicWave::createSquare(float sampleRate)
michael@0 67 {
michael@0 68 PeriodicWave* periodicWave = new PeriodicWave(sampleRate);
michael@0 69 periodicWave->generateBasicWaveform(OscillatorType::Square);
michael@0 70 return periodicWave;
michael@0 71 }
michael@0 72
michael@0 73 PeriodicWave* PeriodicWave::createSawtooth(float sampleRate)
michael@0 74 {
michael@0 75 PeriodicWave* periodicWave = new PeriodicWave(sampleRate);
michael@0 76 periodicWave->generateBasicWaveform(OscillatorType::Sawtooth);
michael@0 77 return periodicWave;
michael@0 78 }
michael@0 79
michael@0 80 PeriodicWave* PeriodicWave::createTriangle(float sampleRate)
michael@0 81 {
michael@0 82 PeriodicWave* periodicWave = new PeriodicWave(sampleRate);
michael@0 83 periodicWave->generateBasicWaveform(OscillatorType::Triangle);
michael@0 84 return periodicWave;
michael@0 85 }
michael@0 86
michael@0 87 PeriodicWave::PeriodicWave(float sampleRate)
michael@0 88 : m_sampleRate(sampleRate)
michael@0 89 , m_periodicWaveSize(PeriodicWaveSize)
michael@0 90 , m_numberOfRanges(NumberOfRanges)
michael@0 91 , m_centsPerRange(CentsPerRange)
michael@0 92 {
michael@0 93 float nyquist = 0.5 * m_sampleRate;
michael@0 94 m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials();
michael@0 95 m_rateScale = m_periodicWaveSize / m_sampleRate;
michael@0 96 }
michael@0 97
michael@0 98 size_t PeriodicWave::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
michael@0 99 {
michael@0 100 size_t amount = aMallocSizeOf(this);
michael@0 101
michael@0 102 amount += m_bandLimitedTables.SizeOfExcludingThis(aMallocSizeOf);
michael@0 103 for (size_t i = 0; i < m_bandLimitedTables.Length(); i++) {
michael@0 104 if (m_bandLimitedTables[i]) {
michael@0 105 amount += m_bandLimitedTables[i]->SizeOfIncludingThis(aMallocSizeOf);
michael@0 106 }
michael@0 107 }
michael@0 108
michael@0 109 return amount;
michael@0 110 }
michael@0 111
michael@0 112 void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, float* &lowerWaveData, float* &higherWaveData, float& tableInterpolationFactor)
michael@0 113 {
michael@0 114 // Negative frequencies are allowed, in which case we alias
michael@0 115 // to the positive frequency.
michael@0 116 fundamentalFrequency = fabsf(fundamentalFrequency);
michael@0 117
michael@0 118 // Calculate the pitch range.
michael@0 119 float ratio = fundamentalFrequency > 0 ? fundamentalFrequency / m_lowestFundamentalFrequency : 0.5;
michael@0 120 float centsAboveLowestFrequency = logf(ratio)/logf(2.0f) * 1200;
michael@0 121
michael@0 122 // Add one to round-up to the next range just in time to truncate
michael@0 123 // partials before aliasing occurs.
michael@0 124 float pitchRange = 1 + centsAboveLowestFrequency / m_centsPerRange;
michael@0 125
michael@0 126 pitchRange = std::max(pitchRange, 0.0f);
michael@0 127 pitchRange = std::min(pitchRange, static_cast<float>(m_numberOfRanges - 1));
michael@0 128
michael@0 129 // The words "lower" and "higher" refer to the table data having
michael@0 130 // the lower and higher numbers of partials. It's a little confusing
michael@0 131 // since the range index gets larger the more partials we cull out.
michael@0 132 // So the lower table data will have a larger range index.
michael@0 133 unsigned rangeIndex1 = static_cast<unsigned>(pitchRange);
michael@0 134 unsigned rangeIndex2 = rangeIndex1 < m_numberOfRanges - 1 ? rangeIndex1 + 1 : rangeIndex1;
michael@0 135
michael@0 136 lowerWaveData = m_bandLimitedTables[rangeIndex2]->Elements();
michael@0 137 higherWaveData = m_bandLimitedTables[rangeIndex1]->Elements();
michael@0 138
michael@0 139 // Ranges from 0 -> 1 to interpolate between lower -> higher.
michael@0 140 tableInterpolationFactor = pitchRange - rangeIndex1;
michael@0 141 }
michael@0 142
michael@0 143 unsigned PeriodicWave::maxNumberOfPartials() const
michael@0 144 {
michael@0 145 return m_periodicWaveSize / 2;
michael@0 146 }
michael@0 147
michael@0 148 unsigned PeriodicWave::numberOfPartialsForRange(unsigned rangeIndex) const
michael@0 149 {
michael@0 150 // Number of cents below nyquist where we cull partials.
michael@0 151 float centsToCull = rangeIndex * m_centsPerRange;
michael@0 152
michael@0 153 // A value from 0 -> 1 representing what fraction of the partials to keep.
michael@0 154 float cullingScale = pow(2, -centsToCull / 1200);
michael@0 155
michael@0 156 // The very top range will have all the partials culled.
michael@0 157 unsigned numberOfPartials = cullingScale * maxNumberOfPartials();
michael@0 158
michael@0 159 return numberOfPartials;
michael@0 160 }
michael@0 161
michael@0 162 // Convert into time-domain wave buffers.
michael@0 163 // One table is created for each range for non-aliasing playback
michael@0 164 // at different playback rates. Thus, higher ranges have more
michael@0 165 // high-frequency partials culled out.
michael@0 166 void PeriodicWave::createBandLimitedTables(const float* realData, const float* imagData, unsigned numberOfComponents)
michael@0 167 {
michael@0 168 float normalizationScale = 1;
michael@0 169
michael@0 170 unsigned fftSize = m_periodicWaveSize;
michael@0 171 unsigned halfSize = fftSize / 2 + 1;
michael@0 172 unsigned i;
michael@0 173
michael@0 174 numberOfComponents = std::min(numberOfComponents, halfSize);
michael@0 175
michael@0 176 m_bandLimitedTables.SetCapacity(m_numberOfRanges);
michael@0 177
michael@0 178 for (unsigned rangeIndex = 0; rangeIndex < m_numberOfRanges; ++rangeIndex) {
michael@0 179 // This FFTBlock is used to cull partials (represented by frequency bins).
michael@0 180 FFTBlock frame(fftSize);
michael@0 181 nsAutoArrayPtr<float> realP(new float[halfSize]);
michael@0 182 nsAutoArrayPtr<float> imagP(new float[halfSize]);
michael@0 183
michael@0 184 // Copy from loaded frequency data and scale.
michael@0 185 float scale = fftSize;
michael@0 186 AudioBufferCopyWithScale(realData, scale, realP, numberOfComponents);
michael@0 187 AudioBufferCopyWithScale(imagData, scale, imagP, numberOfComponents);
michael@0 188
michael@0 189 // If fewer components were provided than 1/2 FFT size,
michael@0 190 // then clear the remaining bins.
michael@0 191 for (i = numberOfComponents; i < halfSize; ++i) {
michael@0 192 realP[i] = 0;
michael@0 193 imagP[i] = 0;
michael@0 194 }
michael@0 195
michael@0 196 // Generate complex conjugate because of the way the
michael@0 197 // inverse FFT is defined.
michael@0 198 float minusOne = -1;
michael@0 199 AudioBufferInPlaceScale(imagP, minusOne, halfSize);
michael@0 200
michael@0 201 // Find the starting bin where we should start culling.
michael@0 202 // We need to clear out the highest frequencies to band-limit
michael@0 203 // the waveform.
michael@0 204 unsigned numberOfPartials = numberOfPartialsForRange(rangeIndex);
michael@0 205
michael@0 206 // Cull the aliasing partials for this pitch range.
michael@0 207 for (i = numberOfPartials + 1; i < halfSize; ++i) {
michael@0 208 realP[i] = 0;
michael@0 209 imagP[i] = 0;
michael@0 210 }
michael@0 211 // Clear nyquist if necessary.
michael@0 212 if (numberOfPartials < halfSize)
michael@0 213 realP[halfSize-1] = 0;
michael@0 214
michael@0 215 // Clear any DC-offset.
michael@0 216 realP[0] = 0;
michael@0 217
michael@0 218 // Clear values which have no effect.
michael@0 219 imagP[0] = 0;
michael@0 220 imagP[halfSize-1] = 0;
michael@0 221
michael@0 222 // Create the band-limited table.
michael@0 223 AudioFloatArray* table = new AudioFloatArray(m_periodicWaveSize);
michael@0 224 m_bandLimitedTables.AppendElement(table);
michael@0 225
michael@0 226 // Apply an inverse FFT to generate the time-domain table data.
michael@0 227 float* data = m_bandLimitedTables[rangeIndex]->Elements();
michael@0 228 frame.PerformInverseFFT(realP, imagP, data);
michael@0 229
michael@0 230 // For the first range (which has the highest power), calculate
michael@0 231 // its peak value then compute normalization scale.
michael@0 232 if (!rangeIndex) {
michael@0 233 float maxValue;
michael@0 234 maxValue = AudioBufferPeakValue(data, m_periodicWaveSize);
michael@0 235
michael@0 236 if (maxValue)
michael@0 237 normalizationScale = 1.0f / maxValue;
michael@0 238 }
michael@0 239
michael@0 240 // Apply normalization scale.
michael@0 241 AudioBufferInPlaceScale(data, normalizationScale, m_periodicWaveSize);
michael@0 242 }
michael@0 243 }
michael@0 244
michael@0 245 void PeriodicWave::generateBasicWaveform(OscillatorType shape)
michael@0 246 {
michael@0 247 const float piFloat = M_PI;
michael@0 248 unsigned fftSize = periodicWaveSize();
michael@0 249 unsigned halfSize = fftSize / 2 + 1;
michael@0 250
michael@0 251 AudioFloatArray real(halfSize);
michael@0 252 AudioFloatArray imag(halfSize);
michael@0 253 float* realP = real.Elements();
michael@0 254 float* imagP = imag.Elements();
michael@0 255
michael@0 256 // Clear DC and Nyquist.
michael@0 257 realP[0] = 0;
michael@0 258 imagP[0] = 0;
michael@0 259 realP[halfSize-1] = 0;
michael@0 260 imagP[halfSize-1] = 0;
michael@0 261
michael@0 262 for (unsigned n = 1; n < halfSize; ++n) {
michael@0 263 float omega = 2 * piFloat * n;
michael@0 264 float invOmega = 1 / omega;
michael@0 265
michael@0 266 // Fourier coefficients according to standard definition.
michael@0 267 float a; // Coefficient for cos().
michael@0 268 float b; // Coefficient for sin().
michael@0 269
michael@0 270 // Calculate Fourier coefficients depending on the shape.
michael@0 271 // Note that the overall scaling (magnitude) of the waveforms
michael@0 272 // is normalized in createBandLimitedTables().
michael@0 273 switch (shape) {
michael@0 274 case OscillatorType::Sine:
michael@0 275 // Standard sine wave function.
michael@0 276 a = 0;
michael@0 277 b = (n == 1) ? 1 : 0;
michael@0 278 break;
michael@0 279 case OscillatorType::Square:
michael@0 280 // Square-shaped waveform with the first half its maximum value
michael@0 281 // and the second half its minimum value.
michael@0 282 a = 0;
michael@0 283 b = invOmega * ((n & 1) ? 2 : 0);
michael@0 284 break;
michael@0 285 case OscillatorType::Sawtooth:
michael@0 286 // Sawtooth-shaped waveform with the first half ramping from
michael@0 287 // zero to maximum and the second half from minimum to zero.
michael@0 288 a = 0;
michael@0 289 b = -invOmega * cos(0.5 * omega);
michael@0 290 break;
michael@0 291 case OscillatorType::Triangle:
michael@0 292 // Triangle-shaped waveform going from its maximum value to
michael@0 293 // its minimum value then back to the maximum value.
michael@0 294 a = (4 - 4 * cos(0.5 * omega)) / (n * n * piFloat * piFloat);
michael@0 295 b = 0;
michael@0 296 break;
michael@0 297 default:
michael@0 298 NS_NOTREACHED("invalid oscillator type");
michael@0 299 a = 0;
michael@0 300 b = 0;
michael@0 301 break;
michael@0 302 }
michael@0 303
michael@0 304 realP[n] = a;
michael@0 305 imagP[n] = b;
michael@0 306 }
michael@0 307
michael@0 308 createBandLimitedTables(realP, imagP, halfSize);
michael@0 309 }
michael@0 310
michael@0 311 } // namespace WebCore

mercurial