Example #1
0
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];
}
Example #3
0
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;
}
Example #4
0
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());
}
Example #5
0
/** 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));
}
Example #6
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;
    }
}
Example #7
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();

    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);
}
Example #8
0
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();
}
Example #11
0
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();
}
Example #12
0
/** 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();
}
Example #13
0
/*! \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();
}
Example #14
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();

  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];
}
Example #15
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;
}
Example #16
0
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];
}
Example #17
0
    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;
    }
Example #18
0
size_t FFTConvolver::latencyFrames() const
{
    return std::max<size_t>(fftSize()/2, WEBAUDIO_BLOCK_SIZE) -
        WEBAUDIO_BLOCK_SIZE;
}
Example #19
0
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());
}
Example #20
0
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);
}
Example #21
0
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;
        }
    }
}