From f80470bb228f8680917c6c4cd5306d9b5176cb3c Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Thu, 19 Jan 2023 13:44:33 -0800 Subject: Increase the pitch shifter oversample factor to 8 And use 32-bit float processing. Float precision doesn't seem to be detrimental to the overall quality, while 8x oversampling seems to help against the harmonics. --- alc/effects/pshifter.cpp | 75 ++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 38 deletions(-) (limited to 'alc/effects/pshifter.cpp') diff --git a/alc/effects/pshifter.cpp b/alc/effects/pshifter.cpp index 121cdcd3..731a5baf 100644 --- a/alc/effects/pshifter.cpp +++ b/alc/effects/pshifter.cpp @@ -47,18 +47,18 @@ struct ContextBase; namespace { using uint = unsigned int; -using complex_d = std::complex; +using complex_f = std::complex; constexpr size_t StftSize{1024}; constexpr size_t StftHalfSize{StftSize >> 1}; -constexpr size_t OversampleFactor{4}; +constexpr size_t OversampleFactor{8}; static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size"); constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ struct Windower { - alignas(16) std::array mData; + alignas(16) std::array mData; Windower() { @@ -67,7 +67,7 @@ struct Windower { { constexpr double scale{al::numbers::pi / double{StftSize}}; const double val{std::sin((static_cast(i)+0.5) * scale)}; - mData[i] = mData[StftSize-1-i] = val * val; + mData[i] = mData[StftSize-1-i] = static_cast(val * val); } } }; @@ -75,8 +75,8 @@ const Windower gWindow{}; struct FrequencyBin { - double Magnitude; - double FreqBin; + float Magnitude; + float FreqBin; }; @@ -85,15 +85,15 @@ struct PshifterState final : public EffectState { size_t mCount; size_t mPos; uint mPitchShiftI; - double mPitchShift; + float mPitchShift; /* Effects buffers */ - std::array mFIFO; - std::array mLastPhase; - std::array mSumPhase; - std::array mOutputAccum; + std::array mFIFO; + std::array mLastPhase; + std::array mSumPhase; + std::array mOutputAccum; - std::array mFftBuffer; + std::array mFftBuffer; std::array mAnalysisBuffer; std::array mSynthesisBuffer; @@ -120,13 +120,13 @@ void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&) mCount = 0; mPos = StftSize - StftStep; mPitchShiftI = MixerFracOne; - mPitchShift = 1.0; + mPitchShift = 1.0f; - mFIFO.fill(0.0); - mLastPhase.fill(0.0); - mSumPhase.fill(0.0); - mOutputAccum.fill(0.0); - mFftBuffer.fill(complex_d{}); + mFIFO.fill(0.0f); + mLastPhase.fill(0.0f); + mSumPhase.fill(0.0f); + mOutputAccum.fill(0.0f); + mFftBuffer.fill(complex_f{}); mAnalysisBuffer.fill(FrequencyBin{}); mSynthesisBuffer.fill(FrequencyBin{}); @@ -140,7 +140,7 @@ void PshifterState::update(const ContextBase*, const EffectSlot *slot, const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2); - mPitchShift = mPitchShiftI * double{1.0/MixerFracOne}; + mPitchShift = static_cast(mPitchShiftI) * float{1.0f/MixerFracOne}; static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); @@ -158,7 +158,7 @@ void PshifterState::process(const size_t samplesToDo, /* Cycle offset per update expected of each frequency bin (bin 0 is none, * bin 1 is x1, bin 2 is x2, etc). */ - constexpr double expected_cycles{al::numbers::pi*2.0 / OversampleFactor}; + constexpr float expected_cycles{al::numbers::pi_v*2.0f / OversampleFactor}; for(size_t base{0u};base < samplesToDo;) { @@ -168,8 +168,7 @@ void PshifterState::process(const size_t samplesToDo, * samples. */ auto fifo_iter = mFIFO.begin()+mPos + mCount; - std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base, - [](double d) noexcept -> float { return static_cast(d); }); + std::copy_n(fifo_iter, todo, mBufferOut.begin()+base); std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter); mCount += todo; @@ -194,8 +193,8 @@ void PshifterState::process(const size_t samplesToDo, */ for(size_t k{0u};k < StftHalfSize+1;k++) { - const double magnitude{std::abs(mFftBuffer[k])}; - const double phase{std::arg(mFftBuffer[k])}; + const float magnitude{std::abs(mFftBuffer[k])}; + const float phase{std::arg(mFftBuffer[k])}; /* Compute the phase difference from the last update and subtract * the expected phase difference for this bin. @@ -204,20 +203,20 @@ void PshifterState::process(const size_t samplesToDo, * 1/OversampleFactor for every frequency bin. So, the offset wraps * every 'OversampleFactor' bin. */ - const auto bin_offset = static_cast(k % OversampleFactor); - double tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles}; + const auto bin_offset = static_cast(k % OversampleFactor); + float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles}; /* Store the actual phase for the next update. */ mLastPhase[k] = phase; /* Normalize from pi, and wrap the delta between -1 and +1. */ - tmp *= al::numbers::inv_pi; - int qpd{double2int(tmp)}; - tmp -= qpd + (qpd%2); + tmp *= al::numbers::inv_pi_v; + int qpd{float2int(tmp)}; + tmp -= static_cast(qpd + (qpd%2)); /* Get deviation from bin frequency (-0.5 to +0.5), and account for * oversampling. */ - tmp *= 0.5 * OversampleFactor; + tmp *= 0.5f * OversampleFactor; /* Compute the k-th partials' frequency bin target and store the * magnitude and frequency bin in the analysis buffer. We don't @@ -225,7 +224,7 @@ void PshifterState::process(const size_t samplesToDo, * the bin. */ mAnalysisBuffer[k].Magnitude = magnitude; - mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; + mAnalysisBuffer[k].FreqBin = static_cast(k) + tmp; } /* Shift the frequency bins according to the pitch adjustment, @@ -256,16 +255,16 @@ void PshifterState::process(const size_t samplesToDo, /* Calculate the actual delta phase for this bin's target frequency * bin, and accumulate it to get the actual bin phase. */ - double tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles}; + float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles}; /* Wrap between -pi and +pi for the sum. If mSumPhase is left to * grow indefinitely, it will lose precision and produce less exact * phase over time. */ - tmp *= al::numbers::inv_pi; - int qpd{double2int(tmp)}; - tmp -= qpd + (qpd%2); - mSumPhase[k] = tmp * al::numbers::pi; + tmp *= al::numbers::inv_pi_v; + int qpd{float2int(tmp)}; + tmp -= static_cast(qpd + (qpd%2)); + mSumPhase[k] = tmp * al::numbers::pi_v; mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]); } @@ -277,7 +276,7 @@ void PshifterState::process(const size_t samplesToDo, */ inverse_fft(al::as_span(mFftBuffer)); - static constexpr double scale{3.0 / OversampleFactor / StftSize}; + 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; for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) @@ -285,7 +284,7 @@ void PshifterState::process(const size_t samplesToDo, /* Copy out the accumulated result, then clear for the next iteration. */ std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); - std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0); + std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f); } /* Now, mix the processed sound data to the output. */ -- cgit v1.2.3