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()); } }
void FFTFrame::doInverseFFT(float* data) { unsigned length = unpackedFFTWDataSize(fftSize()); float* realData = this->realData(); float* imagData = this->imagData(); // Move the Nyquist component to the location expected by FFTW. realData[length - 1] = imagData[0]; imagData[length - 1] = 0; imagData[0] = 0; fftwf_execute_split_dft_c2r(m_backwardPlan, realData, imagData, data); // Restore the original scaling of the time domain data. // FIXME: if we change the definition of FFTFrame to eliminate the // 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 = 1.0 / (2.0 * fftSize()); unsigned n = fftSize(); for (unsigned i = 0; i < n; ++i) data[i] *= scaleFactor; // Move the Nyquist component back to the location expected by the // FFTFrame API. imagData[0] = realData[length - 1]; }
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; }
double HRTFPanner::tailTime() const { // Because HRTFPanner is implemented with a DelayKernel and a FFTConvolver, the tailTime of the HRTFPanner // is the sum of the tailTime of the DelayKernel and the tailTime of the FFTConvolver, which is MaxDelayTimeSeconds // and fftSize() / 2, respectively. return MaxDelayTimeSeconds + (fftSize() / 2) / static_cast<double>(sampleRate()); }
/** Update RBW and FFT overlab labels */ void DockFft::updateInfoLabels(void) { float rate; float size; float rbw; float ovr; float sps; if (m_sample_rate == 0.f) return; rate = fftRate(); size = fftSize(); rbw = m_sample_rate / size; if (rbw < 1.e3f) ui->fftRbwLabel->setText(QString("RBW: %1 Hz").arg(rbw, 0, 'f', 1)); else if (rbw < 1.e6f) ui->fftRbwLabel->setText(QString("RBW: %1 kHz").arg(1.e-3 * rbw, 0, 'f', 1)); else ui->fftRbwLabel->setText(QString("RBW: %1 MHz").arg(1.e-6 * rbw, 0, 'f', 1)); sps = size * rate; if (sps <= m_sample_rate) ovr = 0; else ovr = 100 * (sps / m_sample_rate - 1.f); ui->fftOvrLabel->setText(QString("Overlap: %1%").arg(ovr, 0, 'f', 0)); }
void FFTFrame::multiply(const FFTFrame& frame) { FFTFrame& frame1 = *this; FFTFrame& frame2 = const_cast<FFTFrame&>(frame); float* realP1 = frame1.realData(); float* imagP1 = frame1.imagData(); const float* realP2 = frame2.realData(); const float* imagP2 = frame2.imagData(); // Scale accounts the peculiar scaling of vecLib on the Mac. // This ensures the right scaling all the way back to inverse FFT. // FIXME: if we change the scaling on the Mac then this scale // factor will need to change too. float scale = 0.5f; // Multiply the packed DC/nyquist component realP1[0] *= scale * realP2[0]; imagP1[0] *= scale * imagP2[0]; // Complex multiplication. If this loop turns out to be hot then // we should use SSE or other intrinsics to accelerate it. unsigned halfSize = fftSize() / 2; for (unsigned i = 1; i < halfSize; ++i) { float realResult = realP1[i] * realP2[i] - imagP1[i] * imagP2[i]; float imagResult = realP1[i] * imagP2[i] + imagP1[i] * realP2[i]; realP1[i] = scale * realResult; imagP1[i] = scale * imagResult; } }
void FFTFrame::multiply(const FFTFrame& frame) { FFTFrame& frame1 = *this; FFTFrame& frame2 = const_cast<FFTFrame&>(frame); float* realP1 = frame1.realData(); float* imagP1 = frame1.imagData(); const float* realP2 = frame2.realData(); const float* imagP2 = frame2.imagData(); unsigned halfSize = fftSize() / 2; float real0 = realP1[0]; float imag0 = imagP1[0]; VectorMath::zvmul(realP1, imagP1, realP2, imagP2, realP1, imagP1, halfSize); // Multiply the packed DC/nyquist component realP1[0] = real0 * realP2[0]; imagP1[0] = imag0 * imagP2[0]; // Scale accounts the peculiar scaling of vecLib on the Mac. // This ensures the right scaling all the way back to inverse FFT. // FIXME: if we change the scaling on the Mac then this scale // factor will need to change too. float scale = 0.5f; VectorMath::vsmul(realP1, 1, &scale, realP1, 1, halfSize); VectorMath::vsmul(imagP1, 1, &scale, imagP1, 1, halfSize); }
void FFTFrame::doPaddedFFT(const float* data, size_t dataSize) { // Zero-pad the impulse response AudioFloatArray paddedResponse(fftSize()); // zero-initialized paddedResponse.copyToRange(data, 0, dataSize); // Get the frequency-domain version of padded response doFFT(paddedResponse.data()); }
// 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); }
PassOwnPtr<AudioChannel> HRTFKernel::createImpulseResponse() { OwnPtr<AudioChannel> channel = adoptPtr(new AudioChannel(fftSize())); FFTFrame fftFrame(*m_fftFrame); // Add leading delay back in. fftFrame.addConstantGroupDelay(m_frameDelay); fftFrame.doInverseFFT(channel->mutableData()); return channel.release(); }
int HRTFPanner::maxTailFrames() const { // Although the ideal tail time would be the length of the impulse // response, there is additional tail time from the approximations in the // implementation. Because HRTFPanner is implemented with a DelayKernel // and a FFTConvolver, the tailTime of the HRTFPanner is the sum of the // tailTime of the DelayKernel and the tailTime of the FFTConvolver. // The FFTConvolver has a tail time of fftSize(), including latency of // fftSize()/2. return m_delayLine.MaxDelayTicks() + fftSize(); }
/** Save FFT settings. */ void DockFft::saveSettings(QSettings *settings) { int intval; if (!settings) return; settings->beginGroup("fft"); intval = fftSize(); if (intval != DEFAULT_FFT_SIZE) settings->setValue("fft_size", intval); else settings->remove("fft_size"); intval = fftRate(); if (intval != DEFAULT_FFT_RATE) settings->setValue("fft_rate", fftRate()); else settings->remove("fft_rate"); if (ui->fftAvgSlider->value() != DEFAULT_FFT_AVG) settings->setValue("averaging", ui->fftAvgSlider->value()); else settings->remove("averaging"); if (ui->fftSplitSlider->value() != DEFAULT_FFT_SPLIT) settings->setValue("split", ui->fftSplitSlider->value()); else settings->remove("split"); QColor fftColor = ui->colorPicker->currentColor(); if (fftColor != QColor(0xFF,0xFF,0xFF,0xFF)) settings->setValue("pandapter_color", fftColor); else settings->remove("pandapter_color"); if (ui->fillButton->isChecked()) settings->setValue("pandapter_fill", true); else settings->remove("pandapter_fill"); if (ui->reflevelSlider->value() != DEFAULT_FFT_REF_LEVEL) settings->setValue("reference_level", ui->reflevelSlider->value()); else settings->remove("reference_level"); if (ui->rangeSlider->value() != DEFAULT_FFT_RANGE) settings->setValue("fft_range", ui->rangeSlider->value()); else settings->remove("fft_range"); settings->endGroup(); }
/*! \brief Select new FFT size in the combo box. * \param rate The new FFT size. * \returns The actual FFT size selected. */ int DockFft::setFftSize(int fft_size) { int idx = -1; QString size_str = QString::number(fft_size); qDebug() << __func__ << "to" << size_str; idx = ui->fftSizeComboBox->findText(size_str, Qt::MatchExactly); if(idx != -1) ui->fftSizeComboBox->setCurrentIndex(idx); return fftSize(); }
void FFTFrame::multiply(const FFTFrame& frame) { FFTFrame& frame1 = *this; FFTFrame& frame2 = const_cast<FFTFrame&>(frame); float* realP1 = frame1.realData(); float* imagP1 = frame1.imagData(); const float* realP2 = frame2.realData(); const float* imagP2 = frame2.imagData(); unsigned halfSize = fftSize() / 2; float real0 = realP1[0]; float imag0 = imagP1[0]; VectorMath::zvmul(realP1, imagP1, realP2, imagP2, realP1, imagP1, halfSize); // Multiply the packed DC/nyquist component realP1[0] = real0 * realP2[0]; imagP1[0] = imag0 * imagP2[0]; }
const float* FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP) { size_t halfSize = fftSize() / 2; // WEBAUDIO_BLOCK_SIZE must be an exact multiple of halfSize, // halfSize must be a multiple of WEBAUDIO_BLOCK_SIZE // and > WEBAUDIO_BLOCK_SIZE. MOZ_ASSERT(halfSize % WEBAUDIO_BLOCK_SIZE == 0 && WEBAUDIO_BLOCK_SIZE <= halfSize); // Copy samples to input buffer (note contraint above!) float* inputP = m_inputBuffer.Elements(); MOZ_ASSERT(sourceP && inputP && m_readWriteIndex + WEBAUDIO_BLOCK_SIZE <= m_inputBuffer.Length()); memcpy(inputP + m_readWriteIndex, sourceP, sizeof(float) * WEBAUDIO_BLOCK_SIZE); float* outputP = m_outputBuffer.Elements(); m_readWriteIndex += WEBAUDIO_BLOCK_SIZE; // Check if it's time to perform the next FFT if (m_readWriteIndex == halfSize) { // The input buffer is now filled (get frequency-domain version) m_frame.PerformFFT(m_inputBuffer.Elements()); m_frame.Multiply(*fftKernel); m_frame.GetInverseWithoutScaling(m_outputBuffer.Elements()); // Overlap-add 1st half from previous time AudioBufferAddWithScale(m_lastOverlapBuffer.Elements(), 1.0f, m_outputBuffer.Elements(), halfSize); // Finally, save 2nd half of result MOZ_ASSERT(m_outputBuffer.Length() == 2 * halfSize && m_lastOverlapBuffer.Length() == halfSize); memcpy(m_lastOverlapBuffer.Elements(), m_outputBuffer.Elements() + halfSize, sizeof(float) * halfSize); // Reset index back to start for next time m_readWriteIndex = 0; } return outputP + m_readWriteIndex; }
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]; }
bool PowerSpectrum::initializeInternal(const SignalBank &input) { ffts_.clear(); //number of windows int nWindows = (int)windowSizes_.size(); LOUDNESS_ASSERT(input.getNChannels() == nWindows, name_ << ": Number of channels do not match number of windows"); LOUDNESS_ASSERT((int)bandFreqsHz_.size() == (nWindows + 1), name_ << ": Number of frequency bands should equal number of input channels + 1."); LOUDNESS_ASSERT(!anyAscendingValues(windowSizes_), name_ << ": Window lengths must be in descending order."); //work out FFT configuration (constrain to power of 2) int largestWindowSize = input.getNSamples(); vector<int> fftSize(nWindows, nextPowerOfTwo(largestWindowSize)); if(sampleSpectrumUniformly_) { ffts_.push_back(unique_ptr<FFT> (new FFT(fftSize[0]))); ffts_[0] -> initialize(); } else { for(int w=0; w<nWindows; w++) { fftSize[w] = nextPowerOfTwo(windowSizes_[w]); ffts_.push_back(unique_ptr<FFT> (new FFT(fftSize[w]))); ffts_[w] -> initialize(); } } //desired bins indices (lo and hi) per band bandBinIndices_.resize(nWindows); normFactor_.resize(nWindows); int fs = input.getFs(); int nBins = 0; for(int i=0; i<nWindows; i++) { //bin indices to use for compiled spectrum bandBinIndices_[i].resize(2); //These are NOT the nearest components but satisfies f_k in [f_lo, f_hi) bandBinIndices_[i][0] = ceil(bandFreqsHz_[i]*fftSize[i]/fs); // use < bandBinIndices_[i][1] to exclude upper bin bandBinIndices_[i][1] = ceil(bandFreqsHz_[i+1]*fftSize[i]/fs); LOUDNESS_ASSERT(bandBinIndices_[i][1]>0, name_ << ": No components found in band number " << i); //exclude DC and Nyquist if found int nyqIdx = (fftSize[i]/2) + (fftSize[i]%2); if(bandBinIndices_[i][0]==0) { LOUDNESS_WARNING(name_ << ": DC found...excluding."); bandBinIndices_[i][0] = 1; } if((bandBinIndices_[i][1]-1) >= nyqIdx) { LOUDNESS_WARNING(name_ << ": Bin is >= nyquist...excluding."); bandBinIndices_[i][1] = nyqIdx; } nBins += bandBinIndices_[i][1]-bandBinIndices_[i][0]; //Power spectrum normalisation Real refSquared = referenceValue_ * referenceValue_; switch (normalisation_) { case NONE: normFactor_[i] = 1.0 / refSquared; break; case ENERGY: normFactor_[i] = 2.0/(fftSize[i] * refSquared); break; case AVERAGE_POWER: normFactor_[i] = 2.0/(fftSize[i] * windowSizes_[i] * refSquared); break; default: normFactor_[i] = 2.0/(fftSize[i] * refSquared); } LOUDNESS_DEBUG(name_ << ": Normalisation factor : " << normFactor_[i]); } //total number of bins in the output spectrum LOUDNESS_DEBUG(name_ << ": Total number of bins comprising the output spectrum: " << nBins); //initialize the output SignalBank output_.initialize(input.getNEars(), nBins, 1, fs); output_.setFrameRate(input.getFrameRate()); //output frequencies in Hz int j = 0, k = 0; for(int i=0; i<nWindows; i++) { j = bandBinIndices_[i][0]; while(j < bandBinIndices_[i][1]) output_.setCentreFreq(k++, (j++)*fs/(Real)fftSize[i]); LOUDNESS_DEBUG(name_ << ": Included freq Hz (band low): " << fs * bandBinIndices_[i][0] / float(fftSize[i]) << ": Included freq Hz (band high): " << fs * (bandBinIndices_[i][1] - 1) / float(fftSize[i])); } return 1; }
size_t FFTConvolver::latencyFrames() const { return std::max<size_t>(fftSize()/2, WEBAUDIO_BLOCK_SIZE) - WEBAUDIO_BLOCK_SIZE; }
double HRTFPanner::latencyTime() const { // The latency of a FFTConvolver is also fftSize() / 2, and is in addition to // its tailTime of the same value. return (fftSize() / 2) / static_cast<double>(sampleRate()); }
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); }
void FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP, float* destP, size_t framesToProcess) { size_t halfSize = fftSize() / 2; // framesToProcess must be an exact multiple of halfSize, // or halfSize is a multiple of framesToProcess when halfSize > framesToProcess. bool isGood = !(halfSize % framesToProcess && framesToProcess % halfSize); MOZ_ASSERT(isGood); if (!isGood) return; size_t numberOfDivisions = halfSize <= framesToProcess ? (framesToProcess / halfSize) : 1; size_t divisionSize = numberOfDivisions == 1 ? framesToProcess : halfSize; for (size_t i = 0; i < numberOfDivisions; ++i, sourceP += divisionSize, destP += divisionSize) { // Copy samples to input buffer (note contraint above!) float* inputP = m_inputBuffer.Elements(); // Sanity check bool isCopyGood1 = sourceP && inputP && m_readWriteIndex + divisionSize <= m_inputBuffer.Length(); MOZ_ASSERT(isCopyGood1); if (!isCopyGood1) return; memcpy(inputP + m_readWriteIndex, sourceP, sizeof(float) * divisionSize); // Copy samples from output buffer float* outputP = m_outputBuffer.Elements(); // Sanity check bool isCopyGood2 = destP && outputP && m_readWriteIndex + divisionSize <= m_outputBuffer.Length(); MOZ_ASSERT(isCopyGood2); if (!isCopyGood2) return; memcpy(destP, outputP + m_readWriteIndex, sizeof(float) * divisionSize); m_readWriteIndex += divisionSize; // Check if it's time to perform the next FFT if (m_readWriteIndex == halfSize) { // The input buffer is now filled (get frequency-domain version) m_frame.PerformFFT(m_inputBuffer.Elements()); m_frame.Multiply(*fftKernel); m_frame.GetInverseWithoutScaling(m_outputBuffer.Elements()); // Overlap-add 1st half from previous time AudioBufferAddWithScale(m_lastOverlapBuffer.Elements(), 1.0f, m_outputBuffer.Elements(), halfSize); // Finally, save 2nd half of result bool isCopyGood3 = m_outputBuffer.Length() == 2 * halfSize && m_lastOverlapBuffer.Length() == halfSize; MOZ_ASSERT(isCopyGood3); if (!isCopyGood3) return; memcpy(m_lastOverlapBuffer.Elements(), m_outputBuffer.Elements() + halfSize, sizeof(float) * halfSize); // Reset index back to start for next time m_readWriteIndex = 0; } } }