Index: Source/modules/webaudio/PeriodicWave.cpp |
diff --git a/Source/modules/webaudio/PeriodicWave.cpp b/Source/modules/webaudio/PeriodicWave.cpp |
index 5700c3073a9fc1cb375053eddf762367713dc0a6..9fe1903229f831eedbb52ee79014cdc2883558e9 100644 |
--- a/Source/modules/webaudio/PeriodicWave.cpp |
+++ b/Source/modules/webaudio/PeriodicWave.cpp |
@@ -37,11 +37,14 @@ |
namespace blink { |
-const unsigned PeriodicWaveSize = 4096; // This must be a power of two. |
-const unsigned NumberOfRanges = 36; // There should be 3 * log2(PeriodicWaveSize) 1/3 octave ranges. |
-const float CentsPerRange = 1200 / 3; // 1/3 Octave. |
+// The number of bands per octave. Each octave will have this many entries in the wave tables. |
+const unsigned kNumberOfOctaveBands = 3; |
-const unsigned PeriodicWave::kMaxPeriodicWaveArraySize = PeriodicWaveSize / 2; |
+// The max length of a periodic wave. This must be a power of two greater than or equal to 2048 and |
+// must be supported by the FFT routines. |
+const unsigned kMaxPeriodicWaveSize = 16384; |
+ |
+const float CentsPerRange = 1200 / kNumberOfOctaveBands; |
using namespace VectorMath; |
@@ -88,13 +91,36 @@ PeriodicWave* PeriodicWave::createTriangle(float sampleRate) |
PeriodicWave::PeriodicWave(float sampleRate) |
: m_sampleRate(sampleRate) |
- , m_periodicWaveSize(PeriodicWaveSize) |
- , m_numberOfRanges(NumberOfRanges) |
, m_centsPerRange(CentsPerRange) |
{ |
float nyquist = 0.5 * m_sampleRate; |
m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials(); |
- m_rateScale = m_periodicWaveSize / m_sampleRate; |
+ m_rateScale = periodicWaveSize() / m_sampleRate; |
+ // Compute the number of ranges needed to cover the entire frequency range, assuming |
+ // kNumberOfOctaveBands per octave. |
+ m_numberOfRanges = 0.5 + kNumberOfOctaveBands * log2f(periodicWaveSize()); |
+} |
+ |
+unsigned PeriodicWave::periodicWaveSize() const |
+{ |
+ // Choose an appropriate wave size for the given sample rate. This allows us to use shorter |
+ // FFTs when possible to limit the complexity. The breakpoints here are somewhat arbitrary, but |
+ // we want sample rates around 44.1 kHz or so to have a size of 4096 to preserve backward |
+ // compatibility. |
+ if (m_sampleRate <= 24000) { |
+ return 2048; |
+ } |
+ |
+ if (m_sampleRate <= 88200) { |
+ return 4096; |
+ } |
+ |
+ return kMaxPeriodicWaveSize; |
+} |
+ |
+unsigned PeriodicWave::maxNumberOfPartials() const |
+{ |
+ return periodicWaveSize() / 2; |
} |
void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, float*& lowerWaveData, float*& higherWaveData, float& tableInterpolationFactor) |
@@ -110,13 +136,13 @@ void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, f |
float pitchRange = 1 + centsAboveLowestFrequency / m_centsPerRange; |
pitchRange = std::max(pitchRange, 0.0f); |
- pitchRange = std::min(pitchRange, static_cast<float>(m_numberOfRanges - 1)); |
+ pitchRange = std::min(pitchRange, static_cast<float>(numberOfRanges() - 1)); |
// The words "lower" and "higher" refer to the table data having the lower and higher numbers of partials. |
// It's a little confusing since the range index gets larger the more partials we cull out. |
// So the lower table data will have a larger range index. |
unsigned rangeIndex1 = static_cast<unsigned>(pitchRange); |
- unsigned rangeIndex2 = rangeIndex1 < m_numberOfRanges - 1 ? rangeIndex1 + 1 : rangeIndex1; |
+ unsigned rangeIndex2 = rangeIndex1 < numberOfRanges() - 1 ? rangeIndex1 + 1 : rangeIndex1; |
lowerWaveData = m_bandLimitedTables[rangeIndex2]->data(); |
higherWaveData = m_bandLimitedTables[rangeIndex1]->data(); |
@@ -125,11 +151,6 @@ void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, f |
tableInterpolationFactor = pitchRange - rangeIndex1; |
} |
-unsigned PeriodicWave::maxNumberOfPartials() const |
-{ |
- return m_periodicWaveSize / 2; |
-} |
- |
unsigned PeriodicWave::numberOfPartialsForRange(unsigned rangeIndex) const |
{ |
// Number of cents below nyquist where we cull partials. |
@@ -151,53 +172,44 @@ void PeriodicWave::createBandLimitedTables(const float* realData, const float* i |
{ |
float normalizationScale = 1; |
- unsigned fftSize = m_periodicWaveSize; |
+ unsigned fftSize = periodicWaveSize(); |
unsigned halfSize = fftSize / 2; |
unsigned i; |
numberOfComponents = std::min(numberOfComponents, halfSize); |
- m_bandLimitedTables.reserveCapacity(m_numberOfRanges); |
+ m_bandLimitedTables.reserveCapacity(numberOfRanges()); |
- for (unsigned rangeIndex = 0; rangeIndex < m_numberOfRanges; ++rangeIndex) { |
+ for (unsigned rangeIndex = 0; rangeIndex < numberOfRanges(); ++rangeIndex) { |
// This FFTFrame is used to cull partials (represented by frequency bins). |
FFTFrame frame(fftSize); |
float* realP = frame.realData(); |
float* imagP = frame.imagData(); |
- // Copy from loaded frequency data and scale. |
- float scale = fftSize; |
- vsmul(realData, 1, &scale, realP, 1, numberOfComponents); |
- vsmul(imagData, 1, &scale, imagP, 1, numberOfComponents); |
- |
- // If fewer components were provided than 1/2 FFT size, then clear the remaining bins. |
- for (i = numberOfComponents; i < halfSize; ++i) { |
- realP[i] = 0; |
- imagP[i] = 0; |
- } |
- |
- // Generate complex conjugate because of the way the inverse FFT is defined. |
+ // Copy from loaded frequency data and generate the complex conjugate because of the way the |
+ // inverse FFT is defined versus the values in the arrays. Note also that although the IFFT |
+ // does a scaling by 1/N, we take care of this when the normalization scaling is done. |
float minusOne = -1; |
- vsmul(imagP, 1, &minusOne, imagP, 1, halfSize); |
+ memcpy(realP, realData, numberOfComponents * sizeof(*realP)); |
+ vsmul(imagData, 1, &minusOne, imagP, 1, numberOfComponents); |
- // Find the starting bin where we should start culling. |
- // We need to clear out the highest frequencies to band-limit the waveform. |
+ // Find the starting bin where we should start culling. We need to clear out the highest |
+ // frequencies to band-limit the waveform. |
unsigned numberOfPartials = numberOfPartialsForRange(rangeIndex); |
- // Cull the aliasing partials for this pitch range. |
- for (i = numberOfPartials + 1; i < halfSize; ++i) { |
+ // If fewer components were provided than 1/2 FFT size, then clear the remaining bins. |
+ // We also need to cull the aliasing partials for this pitch range. |
+ for (i = std::min(numberOfComponents, numberOfPartials + 1); i < halfSize; ++i) { |
realP[i] = 0; |
imagP[i] = 0; |
} |
- // Clear packed-nyquist if necessary. |
- if (numberOfPartials < halfSize) |
- imagP[0] = 0; |
- // Clear any DC-offset. |
+ // Clear packed-nyquist and any DC-offset. |
realP[0] = 0; |
+ imagP[0] = 0; |
// Create the band-limited table. |
- OwnPtr<AudioFloatArray> table = adoptPtr(new AudioFloatArray(m_periodicWaveSize)); |
+ OwnPtr<AudioFloatArray> table = adoptPtr(new AudioFloatArray(periodicWaveSize())); |
m_bandLimitedTables.append(table.release()); |
// Apply an inverse FFT to generate the time-domain table data. |
@@ -207,14 +219,14 @@ void PeriodicWave::createBandLimitedTables(const float* realData, const float* i |
// For the first range (which has the highest power), calculate its peak value then compute normalization scale. |
if (!rangeIndex) { |
float maxValue; |
- vmaxmgv(data, 1, &maxValue, m_periodicWaveSize); |
+ vmaxmgv(data, 1, &maxValue, fftSize); |
if (maxValue) |
normalizationScale = 1.0f / maxValue; |
} |
// Apply normalization scale. |
- vsmul(data, 1, &normalizationScale, data, 1, m_periodicWaveSize); |
+ vsmul(data, 1, &normalizationScale, data, 1, fftSize); |
} |
} |