aboutsummaryrefslogtreecommitdiffstats
path: root/alc/effects/pshifter.cpp
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2023-01-19 13:44:33 -0800
committerChris Robinson <[email protected]>2023-01-19 13:44:33 -0800
commitf80470bb228f8680917c6c4cd5306d9b5176cb3c (patch)
tree1251c83aff60d6580d862ad6ae4905c8137da819 /alc/effects/pshifter.cpp
parentd6e79c9023ad66986fcfe16caef15d8e8b14a20f (diff)
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.
Diffstat (limited to 'alc/effects/pshifter.cpp')
-rw-r--r--alc/effects/pshifter.cpp75
1 files changed, 37 insertions, 38 deletions
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<double>;
+using complex_f = std::complex<float>;
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<double,StftSize> mData;
+ alignas(16) std::array<float,StftSize> mData;
Windower()
{
@@ -67,7 +67,7 @@ struct Windower {
{
constexpr double scale{al::numbers::pi / double{StftSize}};
const double val{std::sin((static_cast<double>(i)+0.5) * scale)};
- mData[i] = mData[StftSize-1-i] = val * val;
+ mData[i] = mData[StftSize-1-i] = static_cast<float>(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<double,StftSize> mFIFO;
- std::array<double,StftHalfSize+1> mLastPhase;
- std::array<double,StftHalfSize+1> mSumPhase;
- std::array<double,StftSize> mOutputAccum;
+ std::array<float,StftSize> mFIFO;
+ std::array<float,StftHalfSize+1> mLastPhase;
+ std::array<float,StftHalfSize+1> mSumPhase;
+ std::array<float,StftSize> mOutputAccum;
- std::array<complex_d,StftSize> mFftBuffer;
+ std::array<complex_f,StftSize> mFftBuffer;
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
std::array<FrequencyBin,StftHalfSize+1> 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<float>(tune) / 1200.0f)};
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
- mPitchShift = mPitchShiftI * double{1.0/MixerFracOne};
+ mPitchShift = static_cast<float>(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<float>*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<float>(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<double>(k % OversampleFactor);
- double tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles};
+ const auto bin_offset = static_cast<float>(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<float>;
+ int qpd{float2int(tmp)};
+ tmp -= static_cast<float>(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<double>(k) + tmp;
+ mAnalysisBuffer[k].FreqBin = static_cast<float>(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<float>;
+ int qpd{float2int(tmp)};
+ tmp -= static_cast<float>(qpd + (qpd%2));
+ mSumPhase[k] = tmp * al::numbers::pi_v<float>;
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. */