Skip to content

Commit

Permalink
Use PFFFT for the pitch shifter
Browse files Browse the repository at this point in the history
  • Loading branch information
kcat committed Oct 25, 2023
1 parent a90d8a6 commit 62e0d91
Showing 1 changed file with 36 additions and 13 deletions.
49 changes: 36 additions & 13 deletions alc/effects/pshifter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
#include <iterator>

#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
Expand All @@ -40,6 +39,7 @@
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "pffft.h"

struct ContextBase;

Expand Down Expand Up @@ -74,6 +74,12 @@ struct Windower {
const Windower gWindow{};


struct PFFFTSetupDeleter {
void operator()(PFFFT_Setup *ptr) { pffft_destroy_setup(ptr); }
};
using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;


struct FrequencyBin {
float Magnitude;
float FreqBin;
Expand All @@ -93,7 +99,9 @@ struct PshifterState final : public EffectState {
std::array<float,StftHalfSize+1> mSumPhase;
std::array<float,StftSize> mOutputAccum;

std::array<complex_f,StftSize> mFftBuffer;
PFFFTSetupPtr mFft;
alignas(16) std::array<float,StftSize> mFftBuffer;
alignas(16) std::array<float,StftSize> mFftWorkBuffer;

std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
Expand Down Expand Up @@ -126,12 +134,15 @@ void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
mLastPhase.fill(0.0f);
mSumPhase.fill(0.0f);
mOutputAccum.fill(0.0f);
mFftBuffer.fill(complex_f{});
mFftBuffer.fill(0.0f);
mAnalysisBuffer.fill(FrequencyBin{});
mSynthesisBuffer.fill(FrequencyBin{});

std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);

if(!mFft)
mFft = PFFFTSetupPtr{pffft_new_setup(StftSize, PFFFT_REAL)};
}

void PshifterState::update(const ContextBase*, const EffectSlot *slot,
Expand Down Expand Up @@ -186,15 +197,19 @@ void PshifterState::process(const size_t samplesToDo,
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k)
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
forward_fft(al::span{mFftBuffer});
pffft_transform_ordered(mFft.get(), mFftBuffer.data(), mFftBuffer.data(),
mFftWorkBuffer.data(), PFFFT_FORWARD);

/* Analyze the obtained data. Since the real FFT is symmetric, only
* StftHalfSize+1 samples are needed.
*/
for(size_t k{0u};k < StftHalfSize+1;k++)
for(size_t k{0u};k < StftHalfSize+1;++k)
{
const float magnitude{std::abs(mFftBuffer[k])};
const float phase{std::arg(mFftBuffer[k])};
const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} :
(k == StftHalfSize) ? complex_f{mFftBuffer[1]} :
complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]};
const float magnitude{std::abs(cplx)};
const float phase{std::arg(cplx)};

/* Compute the phase difference from the last update and subtract
* the expected phase difference for this bin.
Expand Down Expand Up @@ -266,21 +281,29 @@ void PshifterState::process(const size_t samplesToDo,
tmp -= static_cast<float>(qpd + (qpd%2));
mSumPhase[k] = tmp * al::numbers::pi_v<float>;

mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]);
const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])};
if(k == 0)
mFftBuffer[0] = cplx.real();
else if(k == StftHalfSize)
mFftBuffer[1] = cplx.real();
else
{
mFftBuffer[k*2 + 0] = cplx.real();
mFftBuffer[k*2 + 1] = cplx.imag();
}
}
for(size_t k{StftHalfSize+1};k < StftSize;++k)
mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]);

/* Apply an inverse FFT to get the time-domain signal, and accumulate
* for the output with windowing.
*/
inverse_fft(al::span{mFftBuffer});
pffft_transform_ordered(mFft.get(), mFftBuffer.data(), mFftBuffer.data(),
mFftWorkBuffer.data(), PFFFT_BACKWARD);

static constexpr float scale{3.0f / OversampleFactor / StftSize};
for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;
for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;

/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos);
Expand Down

0 comments on commit 62e0d91

Please sign in to comment.