// Normal constructor: allocates for a given fftSize. FFTFrame::FFTFrame(unsigned fftSize) : m_FFTSize(fftSize) , m_log2FFTSize(static_cast<unsigned>(log2(fftSize))) , m_forwardPlan(0) , m_backwardPlan(0) , m_data(2 * (3 + unpackedFFTWDataSize(fftSize))) // enough space for real and imaginary data plus 16-byte alignment padding { // We only allow power of two. ASSERT(1UL << m_log2FFTSize == m_FFTSize); // FFTW won't create a plan without being able to look at non-null // pointers for the input and output data; it wants to be able to // see whether these arrays are aligned properly for vector // operations. Ideally we would use fftw_malloc and fftw_free for // the input and output arrays to ensure proper alignment for SIMD // operations, so that we don't have to specify FFTW_UNALIGNED // when creating the plan. However, since we don't have control // over the alignment of the array passed to doFFT / doInverseFFT, // we would need to memcpy it in to or out of the FFTFrame, adding // overhead. For the time being, we just assume unaligned data and // pass a temporary pointer down. float temporary; m_forwardPlan = fftwPlanForSize(fftSize, Forward, &temporary, realData(), imagData()); m_backwardPlan = fftwPlanForSize(fftSize, Backward, realData(), imagData(), &temporary); }
void FFTFrame::addConstantGroupDelay(double sampleFrameDelay) { int halfSize = fftSize() / 2; float* realP = realData(); float* imagP = imagData(); const double kSamplePhaseDelay = (2.0 * piDouble) / double(fftSize()); double phaseAdj = -sampleFrameDelay * kSamplePhaseDelay; // Add constant group delay for (int i = 1; i < halfSize; i++) { Complex c(realP[i], imagP[i]); double mag = abs(c); double phase = arg(c); phase += i * phaseAdj; Complex c2 = std::polar(mag, phase); realP[i] = static_cast<float>(c2.real()); imagP[i] = static_cast<float>(c2.imag()); } }
// Copy constructor. FFTFrame::FFTFrame(const FFTFrame& frame) : m_FFTSize(frame.m_FFTSize) , m_log2FFTSize(frame.m_log2FFTSize) , m_forwardPlan(0) , m_backwardPlan(0) , m_data(2 * (3 + unpackedFFTWDataSize(fftSize()))) // enough space for real and imaginary data plus 16-byte alignment padding { // See the normal constructor for an explanation of the temporary pointer. float temporary; m_forwardPlan = fftwPlanForSize(m_FFTSize, Forward, &temporary, realData(), imagData()); m_backwardPlan = fftwPlanForSize(m_FFTSize, Backward, realData(), imagData(), &temporary); // Copy/setup frame data. size_t nbytes = sizeof(float) * unpackedFFTWDataSize(fftSize()); memcpy(realData(), frame.realData(), nbytes); memcpy(imagData(), frame.imagData(), nbytes); }
double FFTFrame::extractAverageGroupDelay() { float* realP = realData(); float* imagP = imagData(); double aveSum = 0.0; double weightSum = 0.0; double lastPhase = 0.0; int halfSize = fftSize() / 2; const double kSamplePhaseDelay = (2.0 * piDouble) / double(fftSize()); // Calculate weighted average group delay for (int i = 0; i < halfSize; i++) { Complex c(realP[i], imagP[i]); double mag = abs(c); double phase = arg(c); double deltaPhase = phase - lastPhase; lastPhase = phase; // Unwrap if (deltaPhase < -piDouble) deltaPhase += 2.0 * piDouble; if (deltaPhase > piDouble) deltaPhase -= 2.0 * piDouble; aveSum += mag * deltaPhase; weightSum += mag; } // Note how we invert the phase delta wrt frequency since this is how group delay is defined double ave = aveSum / weightSum; double aveSampleDelay = -ave / kSamplePhaseDelay; // Leave 20 sample headroom (for leading edge of impulse) if (aveSampleDelay > 20.0) aveSampleDelay -= 20.0; // Remove average group delay (minus 20 samples for headroom) addConstantGroupDelay(-aveSampleDelay); // Remove DC offset realP[0] = 0.0f; return aveSampleDelay; }
// Copy constructor FFTFrame::FFTFrame(const FFTFrame& frame) : m_FFTSize(frame.m_FFTSize), m_log2FFTSize(frame.m_log2FFTSize), m_realData(frame.m_FFTSize), m_imagData(frame.m_FFTSize), m_FFTSetup(frame.m_FFTSetup) { // Setup frame data m_frame.realp = m_realData.data(); m_frame.imagp = m_imagData.data(); // Copy/setup frame data unsigned nbytes = sizeof(float) * m_FFTSize; memcpy(realData(), frame.m_frame.realp, nbytes); memcpy(imagData(), frame.m_frame.imagp, nbytes); }
// Copy constructor. FFTFrame::FFTFrame(const FFTFrame& frame) : m_FFTSize(frame.m_FFTSize) , m_log2FFTSize(frame.m_log2FFTSize) , m_realData(unpackedFFTDataSize(frame.m_FFTSize)) , m_imagData(unpackedFFTDataSize(frame.m_FFTSize)) { m_complexData = WTF::fastNewArray<GstFFTF32Complex>(unpackedFFTDataSize(m_FFTSize)); int fftLength = gst_fft_next_fast_length(m_FFTSize); m_fft = gst_fft_f32_new(fftLength, FALSE); m_inverseFft = gst_fft_f32_new(fftLength, TRUE); // Copy/setup frame data. memcpy(realData(), frame.realData(), sizeof(float) * unpackedFFTDataSize(m_FFTSize)); memcpy(imagData(), frame.imagData(), sizeof(float) * unpackedFFTDataSize(m_FFTSize)); }
// Copy constructor. FFTFrame::FFTFrame(const FFTFrame& frame) : m_FFTSize(frame.m_FFTSize), m_log2FFTSize(frame.m_log2FFTSize), m_realData(frame.m_FFTSize / 2), m_imagData(frame.m_FFTSize / 2), m_forwardContext(nullptr), m_inverseContext(nullptr), m_complexData(frame.m_FFTSize) { m_forwardContext = contextForSize(m_FFTSize, DFT_R2C); m_inverseContext = contextForSize(m_FFTSize, IDFT_C2R); // Copy/setup frame data. unsigned nbytes = sizeof(float) * (m_FFTSize / 2); memcpy(realData(), frame.realData(), nbytes); memcpy(imagData(), frame.imagData(), nbytes); }
void FFTFrame::doFFT(float* data) { fftwf_execute_split_dft_r2c(m_forwardPlan, data, realData(), imagData()); // Scale the frequency domain data to match vecLib's scale factor // on the Mac. FIXME: if we change the definition of FFTFrame to // eliminate this scale factor then this code will need to change. // Also, if this loop turns out to be hot then we should use SSE // or other intrinsics to accelerate it. float scaleFactor = 2; unsigned length = unpackedFFTWDataSize(fftSize()); float* realData = this->realData(); float* imagData = this->imagData(); for (unsigned i = 0; i < length; ++i) { realData[i] = realData[i] * scaleFactor; imagData[i] = imagData[i] * scaleFactor; } // Move the Nyquist component to the location expected by the // FFTFrame API. imagData[0] = realData[length - 1]; }
void FFTFrame::interpolateFrequencyComponents(const FFTFrame& frame1, const FFTFrame& frame2, double interp) { // FIXME : with some work, this method could be optimized float* realP = realData(); float* imagP = imagData(); const float* realP1 = frame1.realData(); const float* imagP1 = frame1.imagData(); const float* realP2 = frame2.realData(); const float* imagP2 = frame2.imagData(); m_FFTSize = frame1.fftSize(); m_log2FFTSize = frame1.log2FFTSize(); double s1base = (1.0 - interp); double s2base = interp; double phaseAccum = 0.0; double lastPhase1 = 0.0; double lastPhase2 = 0.0; realP[0] = static_cast<float>(s1base * realP1[0] + s2base * realP2[0]); imagP[0] = static_cast<float>(s1base * imagP1[0] + s2base * imagP2[0]); int n = m_FFTSize / 2; for (int i = 1; i < n; ++i) { Complex c1(realP1[i], imagP1[i]); Complex c2(realP2[i], imagP2[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 deltaPhase1 = phase1 - lastPhase1; double deltaPhase2 = phase2 - lastPhase2; lastPhase1 = phase1; lastPhase2 = phase2; // Unwrap phase deltas if (deltaPhase1 > piDouble) deltaPhase1 -= 2.0 * piDouble; if (deltaPhase1 < -piDouble) deltaPhase1 += 2.0 * piDouble; if (deltaPhase2 > piDouble) deltaPhase2 -= 2.0 * piDouble; if (deltaPhase2 < -piDouble) deltaPhase2 += 2.0 * piDouble; // Blend group-delays double deltaPhaseBlend; if (deltaPhase1 - deltaPhase2 > piDouble) deltaPhaseBlend = s1 * deltaPhase1 + s2 * (2.0 * piDouble + deltaPhase2); else if (deltaPhase2 - deltaPhase1 > piDouble) deltaPhaseBlend = s1 * (2.0 * piDouble + deltaPhase1) + s2 * deltaPhase2; else deltaPhaseBlend = s1 * deltaPhase1 + s2 * deltaPhase2; phaseAccum += deltaPhaseBlend; // Unwrap if (phaseAccum > piDouble) phaseAccum -= 2.0 * piDouble; if (phaseAccum < -piDouble) phaseAccum += 2.0 * piDouble; Complex c = std::polar(mag, phaseAccum); realP[i] = static_cast<float>(c.real()); imagP[i] = static_cast<float>(c.imag()); } }
float* FFTFrame::imagData() const { // Imaginary data is stored following the real data with enough padding for 16-byte alignment. return const_cast<float*>(realData() + unpackedFFTWDataSize(fftSize()) + 3); }