aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2023-10-18 13:22:46 -0700
committerChris Robinson <[email protected]>2023-10-18 13:22:46 -0700
commit63ad14119d74aa61ed7210d27098bdb41313efec (patch)
tree8768653db08bcdbb572629d642fef155a7c88109
parent34ac44d03499dfe65db417493f0c4f4693e24dd5 (diff)
Remove the output delay on the UHJ and SuperStereo IIR decoders
-rw-r--r--core/uhjfilter.cpp61
-rw-r--r--core/uhjfilter.h30
2 files changed, 53 insertions, 38 deletions
diff --git a/core/uhjfilter.cpp b/core/uhjfilter.cpp
index 2ae79d30..a8b75494 100644
--- a/core/uhjfilter.cpp
+++ b/core/uhjfilter.cpp
@@ -157,6 +157,19 @@ constexpr std::array<float,4> Filter2Coeff{{
} // namespace
+void UhjAllPassFilter::processOne(const al::span<const float, 4> coeffs, float x)
+{
+ auto state = mState;
+ for(size_t i{0};i < 4;++i)
+ {
+ const float y{x*coeffs[i] + state[i].z[0]};
+ state[i].z[0] = state[i].z[1];
+ state[i].z[1] = y*coeffs[i] - x;
+ x = y;
+ }
+ mState = state;
+}
+
void UhjAllPassFilter::process(const al::span<const float,4> coeffs,
const al::span<const float> src, const bool updateState, float *RESTRICT dst)
{
@@ -490,11 +503,11 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
/* S = Left + Right */
- for(size_t i{0};i < samplesToDo;++i)
+ for(size_t i{0};i < samplesToDo+sInputPadding;++i)
mS[i] = left[i] + right[i];
/* D = Left - Right */
- for(size_t i{0};i < samplesToDo;++i)
+ for(size_t i{0};i < samplesToDo+sInputPadding;++i)
mD[i] = left[i] - right[i];
}
@@ -503,14 +516,13 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
/* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */
- std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(),
+ std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput, mTemp.begin(),
[](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; });
- mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput);
+ if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]);
+ mFilter2DT.process(Filter2Coeff, {mTemp.data()+1, samplesToDo}, updateState, xoutput);
/* Apply filter1 to S and store in mTemp. */
- mTemp[0] = mDelayS;
- mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
- if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
+ mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data());
/* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */
for(size_t i{0};i < samplesToDo;++i)
@@ -523,12 +535,11 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
/* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */
std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput,
[](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; });
- mTemp[0] = mDelayDT;
- mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1);
- if(updateState) LIKELY mDelayDT = mTemp[samplesToDo];
+ mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data());
/* Precompute j*S and store in youtput. */
- mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
+ if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
+ mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput);
/* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */
for(size_t i{0};i < samplesToDo;++i)
@@ -540,14 +551,14 @@ void UhjDecoderIIR::decode(const al::span<float*> samples, const size_t samplesT
float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])};
/* Apply filter1 to Q and store in mTemp. */
- mTemp[0] = mDelayQ;
- mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1);
- if(updateState) LIKELY mDelayQ = mTemp[samplesToDo];
+ mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data());
/* Z = 1.023332*Q */
for(size_t i{0};i < samplesToDo;++i)
zoutput[i] = 1.023332f*mTemp[i];
}
+
+ mFirstRun = false;
}
@@ -647,7 +658,7 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
const float *RESTRICT left{al::assume_aligned<16>(samples[0])};
const float *RESTRICT right{al::assume_aligned<16>(samples[1])};
- for(size_t i{0};i < samplesToDo;++i)
+ for(size_t i{0};i < samplesToDo+sInputPadding;++i)
mS[i] = left[i] + right[i];
/* Pre-apply the width factor to the difference signal D. Smoothly
@@ -657,7 +668,7 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth};
if(wtarget == wcurrent || !updateState)
{
- for(size_t i{0};i < samplesToDo;++i)
+ for(size_t i{0};i < samplesToDo+sInputPadding;++i)
mD[i] = (left[i] - right[i]) * wcurrent;
mCurrentWidth = wcurrent;
}
@@ -670,6 +681,8 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi);
fi += 1.0f;
}
+ for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i)
+ mD[i] = (left[i] - right[i]) * wtarget;
mCurrentWidth = wtarget;
}
}
@@ -679,12 +692,11 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
float *RESTRICT youtput{al::assume_aligned<16>(samples[2])};
/* Apply filter1 to S and store in mTemp. */
- mTemp[0] = mDelayS;
- mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1);
- if(updateState) LIKELY mDelayS = mTemp[samplesToDo];
+ mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data());
/* Precompute j*D and store in xoutput. */
- mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput);
+ if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]);
+ mFilter2D.process(Filter2Coeff, {mD.data()+1, samplesToDo}, updateState, xoutput);
/* W = 0.6098637*S - 0.6896511*j*w*D */
for(size_t i{0};i < samplesToDo;++i)
@@ -694,16 +706,17 @@ void UhjStereoDecoderIIR::decode(const al::span<float*> samples, const size_t sa
xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i];
/* Precompute j*S and store in youtput. */
- mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput);
+ if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]);
+ mFilter2S.process(Filter2Coeff, {mS.data()+1, samplesToDo}, updateState, youtput);
/* Apply filter1 to D and store in mTemp. */
- mTemp[0] = mDelayD;
- mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1);
- if(updateState) LIKELY mDelayD = mTemp[samplesToDo];
+ mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data());
/* Y = 1.6822415*w*D - 0.2156194*j*S */
for(size_t i{0};i < samplesToDo;++i)
youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i];
+
+ mFirstRun = false;
}
diff --git a/core/uhjfilter.h b/core/uhjfilter.h
index b8b1e96c..f886edbe 100644
--- a/core/uhjfilter.h
+++ b/core/uhjfilter.h
@@ -29,6 +29,7 @@ struct UhjAllPassFilter {
};
std::array<AllPassState,4> mState;
+ void processOne(const al::span<const float,4> coeffs, float x);
void process(const al::span<const float,4> coeffs, const al::span<const float> src,
const bool update, float *RESTRICT dst);
};
@@ -163,17 +164,18 @@ struct UhjDecoder final : public DecoderBase {
};
struct UhjDecoderIIR final : public DecoderBase {
- /* FIXME: These IIR decoder filters actually have a 1-sample delay on the
- * non-filtered components, which is not reflected in the source latency
- * value. sInputPadding is 0, however, because it doesn't need any extra
- * input samples.
+ /* These IIR decoder filters normally have a 1-sample delay on the non-
+ * filtered components. However, the filtered components are made to skip
+ * the first output sample and take one future sample, which puts it ahead
+ * by one sample. The first filtered output sample is cut to align it with
+ * the first non-filtered sample, similar to the FIR filters.
*/
- static constexpr size_t sInputPadding{0};
+ static constexpr size_t sInputPadding{1};
- alignas(16) std::array<float,BufferLineSize> mS{};
- alignas(16) std::array<float,BufferLineSize> mD{};
- alignas(16) std::array<float,BufferLineSize+1> mTemp{};
- float mDelayS{}, mDelayDT{}, mDelayQ{};
+ bool mFirstRun{true};
+ alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
+ alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
+ alignas(16) std::array<float,BufferLineSize+sInputPadding> mTemp{};
UhjAllPassFilter mFilter1S;
UhjAllPassFilter mFilter2DT;
@@ -214,14 +216,14 @@ struct UhjStereoDecoder final : public DecoderBase {
};
struct UhjStereoDecoderIIR final : public DecoderBase {
- static constexpr size_t sInputPadding{0};
+ static constexpr size_t sInputPadding{1};
+ bool mFirstRun{true};
float mCurrentWidth{-1.0f};
- alignas(16) std::array<float,BufferLineSize> mS{};
- alignas(16) std::array<float,BufferLineSize> mD{};
- alignas(16) std::array<float,BufferLineSize+1> mTemp{};
- float mDelayS{}, mDelayD{};
+ alignas(16) std::array<float,BufferLineSize+sInputPadding> mS{};
+ alignas(16) std::array<float,BufferLineSize+sInputPadding> mD{};
+ alignas(16) std::array<float,BufferLineSize> mTemp{};
UhjAllPassFilter mFilter1S;
UhjAllPassFilter mFilter2D;