blob: 3357941ddeca68452365112dba490b1ee084c660 [file] [log] [blame]
/*
* Copyright (C) 2010 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/audio/fft_frame.h"
#include <complex>
#include <memory>
#include "third_party/blink/renderer/platform/audio/vector_math.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
#ifndef NDEBUG
#include <stdio.h>
#endif
namespace blink {
void FFTFrame::DoPaddedFFT(const float* data, size_t data_size) {
// Zero-pad the impulse response
AudioFloatArray padded_response(FftSize()); // zero-initialized
padded_response.CopyToRange(data, 0, data_size);
// Get the frequency-domain version of padded response
DoFFT(padded_response.Data());
}
std::unique_ptr<FFTFrame> FFTFrame::CreateInterpolatedFrame(
const FFTFrame& frame1,
const FFTFrame& frame2,
double x) {
std::unique_ptr<FFTFrame> new_frame =
std::make_unique<FFTFrame>(frame1.FftSize());
new_frame->InterpolateFrequencyComponents(frame1, frame2, x);
// In the time-domain, the 2nd half of the response must be zero, to avoid
// circular convolution aliasing...
int fft_size = new_frame->FftSize();
AudioFloatArray buffer(fft_size);
new_frame->DoInverseFFT(buffer.Data());
buffer.ZeroRange(fft_size / 2, fft_size);
// Put back into frequency domain.
new_frame->DoFFT(buffer.Data());
return new_frame;
}
void FFTFrame::InterpolateFrequencyComponents(const FFTFrame& frame1,
const FFTFrame& frame2,
double interp) {
// FIXME : with some work, this method could be optimized
float* real_p = RealData();
float* imag_p = ImagData();
const float* real_p1 = frame1.RealData();
const float* imag_p1 = frame1.ImagData();
const float* real_p2 = frame2.RealData();
const float* imag_p2 = frame2.ImagData();
fft_size_ = frame1.FftSize();
log2fft_size_ = frame1.Log2FFTSize();
double s1base = (1.0 - interp);
double s2base = interp;
double phase_accum = 0.0;
double last_phase1 = 0.0;
double last_phase2 = 0.0;
real_p[0] = static_cast<float>(s1base * real_p1[0] + s2base * real_p2[0]);
imag_p[0] = static_cast<float>(s1base * imag_p1[0] + s2base * imag_p2[0]);
int n = fft_size_ / 2;
for (int i = 1; i < n; ++i) {
std::complex<double> c1(real_p1[i], imag_p1[i]);
std::complex<double> c2(real_p2[i], imag_p2[i]);
double mag1 = abs(c1);
double mag2 = abs(c2);
// Interpolate magnitudes in decibels
double mag1db = 20.0 * log10(mag1);
double mag2db = 20.0 * log10(mag2);
double s1 = s1base;
double s2 = s2base;
double magdbdiff = mag1db - mag2db;
// Empirical tweak to retain higher-frequency zeroes
double threshold = (i > 16) ? 5.0 : 2.0;
if (magdbdiff < -threshold && mag1db < 0.0) {
s1 = pow(s1, 0.75);
s2 = 1.0 - s1;
} else if (magdbdiff > threshold && mag2db < 0.0) {
s2 = pow(s2, 0.75);
s1 = 1.0 - s2;
}
// Average magnitude by decibels instead of linearly
double magdb = s1 * mag1db + s2 * mag2db;
double mag = pow(10.0, 0.05 * magdb);
// Now, deal with phase
double phase1 = arg(c1);
double phase2 = arg(c2);
double delta_phase1 = phase1 - last_phase1;
double delta_phase2 = phase2 - last_phase2;
last_phase1 = phase1;
last_phase2 = phase2;
// Unwrap phase deltas
if (delta_phase1 > kPiDouble)
delta_phase1 -= kTwoPiDouble;
if (delta_phase1 < -kPiDouble)
delta_phase1 += kTwoPiDouble;
if (delta_phase2 > kPiDouble)
delta_phase2 -= kTwoPiDouble;
if (delta_phase2 < -kPiDouble)
delta_phase2 += kTwoPiDouble;
// Blend group-delays
double delta_phase_blend;
if (delta_phase1 - delta_phase2 > kPiDouble) {
delta_phase_blend =
s1 * delta_phase1 + s2 * (kTwoPiDouble + delta_phase2);
} else if (delta_phase2 - delta_phase1 > kPiDouble) {
delta_phase_blend =
s1 * (kTwoPiDouble + delta_phase1) + s2 * delta_phase2;
} else {
delta_phase_blend = s1 * delta_phase1 + s2 * delta_phase2;
}
phase_accum += delta_phase_blend;
// Unwrap
if (phase_accum > kPiDouble)
phase_accum -= kTwoPiDouble;
if (phase_accum < -kPiDouble)
phase_accum += kTwoPiDouble;
std::complex<double> c = std::polar(mag, phase_accum);
real_p[i] = static_cast<float>(c.real());
imag_p[i] = static_cast<float>(c.imag());
}
}
double FFTFrame::ExtractAverageGroupDelay() {
float* real_p = RealData();
float* imag_p = ImagData();
double ave_sum = 0.0;
double weight_sum = 0.0;
double last_phase = 0.0;
int half_size = FftSize() / 2;
const double sample_phase_delay =
kTwoPiDouble / static_cast<double>(FftSize());
// Calculate weighted average group delay
for (int i = 0; i < half_size; i++) {
std::complex<double> c(real_p[i], imag_p[i]);
double mag = abs(c);
double phase = arg(c);
double delta_phase = phase - last_phase;
last_phase = phase;
// Unwrap
if (delta_phase < -kPiDouble)
delta_phase += kTwoPiDouble;
if (delta_phase > kPiDouble)
delta_phase -= kTwoPiDouble;
ave_sum += mag * delta_phase;
weight_sum += mag;
}
// Note how we invert the phase delta wrt frequency since this is how group
// delay is defined
double ave = ave_sum / weight_sum;
double ave_sample_delay = -ave / sample_phase_delay;
// Leave 20 sample headroom (for leading edge of impulse)
if (ave_sample_delay > 20.0)
ave_sample_delay -= 20.0;
// Remove average group delay (minus 20 samples for headroom)
AddConstantGroupDelay(-ave_sample_delay);
// Remove DC offset
real_p[0] = 0.0f;
return ave_sample_delay;
}
void FFTFrame::AddConstantGroupDelay(double sample_frame_delay) {
int half_size = FftSize() / 2;
float* real_p = RealData();
float* imag_p = ImagData();
const double sample_phase_delay =
kTwoPiDouble / static_cast<double>(FftSize());
double phase_adj = -sample_frame_delay * sample_phase_delay;
// Add constant group delay
for (int i = 1; i < half_size; i++) {
std::complex<double> c(real_p[i], imag_p[i]);
double mag = abs(c);
double phase = arg(c);
phase += i * phase_adj;
std::complex<double> c2 = std::polar(mag, phase);
real_p[i] = static_cast<float>(c2.real());
imag_p[i] = static_cast<float>(c2.imag());
}
}
void FFTFrame::Multiply(const FFTFrame& frame) {
FFTFrame& frame1 = *this;
const FFTFrame& frame2 = frame;
float* real_p1 = frame1.RealData();
float* imag_p1 = frame1.ImagData();
const float* real_p2 = frame2.RealData();
const float* imag_p2 = frame2.ImagData();
unsigned half_size = FftSize() / 2;
float real0 = real_p1[0];
float imag0 = imag_p1[0];
vector_math::Zvmul(real_p1, imag_p1, real_p2, imag_p2, real_p1, imag_p1,
half_size);
// Multiply the packed DC/nyquist component
real_p1[0] = real0 * real_p2[0];
imag_p1[0] = imag0 * imag_p2[0];
}
} // namespace blink