kfr

Fast, modern C++ DSP framework, FFT, Sample Rate Conversion, FIR/IIR/Biquad Filters (SSE, AVX, AVX-512, ARM NEON)
Log | Files | Refs | README

commit 54d9b5bc9c8f7cefe0641d442a60aefeb8c62621
parent 2f8fe8d864a3e592e44868865eaa58453b77311f
Author: [email protected] <[email protected]>
Date:   Thu, 25 Aug 2016 02:17:24 +0300

Zero latency biquad filter

Diffstat:
Minclude/kfr/dsp/biquad.hpp | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 115 insertions(+), 13 deletions(-)

diff --git a/include/kfr/dsp/biquad.hpp b/include/kfr/dsp/biquad.hpp @@ -90,19 +90,25 @@ struct biquad_params namespace internal { template <typename T, size_t filters, KFR_ARCH_DEP> -struct biquad_block +struct biquad_state { vec<T, filters> s1; vec<T, filters> s2; + vec<T, filters> out; + constexpr biquad_state() noexcept : s1(0), s2(0), out(0) {} +}; + +template <typename T, size_t filters, KFR_ARCH_DEP> +struct biquad_block +{ vec<T, filters> a1; vec<T, filters> a2; vec<T, filters> b0; vec<T, filters> b1; vec<T, filters> b2; - vec<T, filters> out; - biquad_block() : s1(0), s2(0), a1(0), a2(0), b0(1), b1(0), b2(0), out(0) {} - biquad_block(const biquad_params<T>* bq, size_t count) : s1(0), s2(0), out(0) + constexpr biquad_block() noexcept : a1(0), a2(0), b0(1), b1(0), b2(0) {} + constexpr biquad_block(const biquad_params<T>* bq, size_t count) noexcept { count = count > filters ? filters : count; for (size_t i = 0; i < count; i++) @@ -124,7 +130,7 @@ struct biquad_block } template <size_t count> - biquad_block(const biquad_params<T> (&bq)[count]) : biquad_block(bq, count) + constexpr biquad_block(const biquad_params<T> (&bq)[count]) noexcept : biquad_block(bq, count) { static_assert(count <= filters, "count > filters"); } @@ -148,20 +154,103 @@ struct expression_biquads : public expression<E1> CMT_LOOP_UNROLL for (size_t i = 0; i < width; i++) { - bq.out = process(bq, insertleft(in[i], bq.out)); - out(i) = bq.out[filters - 1]; + state.out = process(bq, state, insertleft(in[i], state.out)); + out(i) = state.out[filters - 1]; } return out; } - KFR_SINTRIN vec<T, filters> process(biquad_block<T, filters>& bq, vec<T, filters> in) + KFR_SINTRIN vec<T, filters> process(const biquad_block<T, filters>& bq, biquad_state<T, filters>& state, + vec<T, filters> in) { - const vec<T, filters> out = bq.b0 * in + bq.s1; - bq.s1 = bq.s2 + bq.b1 * in - bq.a1 * out; - bq.s2 = bq.b2 * in - bq.a2 * out; + const vec<T, filters> out = bq.b0 * in + state.s1; + state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; + state.s2 = bq.b2 * in - bq.a2 * out; return out; } - mutable biquad_block<T, filters> bq; + biquad_block<T, filters> bq; + mutable biquad_state<T, filters> state; +}; + +template <size_t filters, typename T, typename E1, KFR_ARCH_DEP> +struct expression_biquads_zl : expression<E1> +{ + using value_type = T; + + expression_biquads_zl(const biquad_block<T, filters>& bq, E1&& e1) + : expression<E1>(std::forward<E1>(e1)), bq(bq), block_end(0) + { + } + + CMT_INLINE void begin_block(size_t size) const + { + block_end = size; + for (size_t i = 0; i < filters - 1; i++) + { + const vec<T, 1> in = this->argument_first(i, vec_t<T, 1>()); + state.out = process(bq, state, insertleft(in[i], state.out)); + } + } + CMT_INLINE void end_block(size_t) const { state = saved_state; } + + template <size_t width> + KFR_INTRIN vec<T, width> operator()(cinput_t, size_t index, vec_t<T, width> t) const + { + index += filters - 1; + vec<T, width> out; + if (index + width <= block_end) + { + const vec<T, width> in = this->argument_first(index, t); + + CMT_LOOP_UNROLL + for (size_t i = 0; i < width; i++) + { + state.out = process(bq, state, insertleft(in[i], state.out)); + out(i) = state.out[filters - 1]; + } + if (index + width == block_end) + saved_state = state; + } + else if (index >= block_end) + { + CMT_LOOP_UNROLL + for (size_t i = 0; i < width; i++) + { + state.out = process(bq, state, insertleft(T(0), state.out)); + out(i) = state.out[filters - 1]; + } + } + else + { + size_t i = 0; + for (; i < std::min(width, block_end - index); i++) + { + const vec<T, 1> in = this->argument_first(index + i, vec_t<T, 1>()); + state.out = process(bq, state, insertleft(in[i], state.out)); + out(i) = state.out[filters - 1]; + } + saved_state = state; + for (; i < width; i++) + { + state.out = process(bq, state, insertleft(T(0), state.out)); + out(i) = state.out[filters - 1]; + } + } + return out; + } + KFR_SINTRIN vec<T, filters> process(const biquad_block<T, filters>& bq, biquad_state<T, filters>& state, + vec<T, filters> in) + { + const vec<T, filters> out = bq.b0 * in + state.s1; + state.s1 = state.s2 + bq.b1 * in - bq.a1 * out; + state.s2 = bq.b2 * in - bq.a2 * out; + return out; + } + biquad_block<T, filters> bq; + + mutable biquad_state<T, filters> state; + mutable biquad_state<T, filters> saved_state; + mutable size_t block_end; }; } @@ -181,7 +270,7 @@ CMT_INLINE internal::expression_biquads<1, T, internal::arg<E1>> biquad(const bi * @brief Returns template expressions that applies cascade of biquad filters to the input. * @param bq Array of biquad coefficients * @param e1 Input expression - * @note The current implementation introduces delay of N - 1 samples, where N is the filter count. + * @note This implementation introduces delay of N - 1 samples, where N is the filter count. */ template <size_t filters, typename T, typename E1> CMT_INLINE internal::expression_biquads<filters, T, internal::arg<E1>> biquad( @@ -189,4 +278,17 @@ CMT_INLINE internal::expression_biquads<filters, T, internal::arg<E1>> biquad( { return internal::expression_biquads<filters, T, internal::arg<E1>>(bq, std::forward<E1>(e1)); } + +/** + * @brief Returns template expressions that applies cascade of biquad filters to the input. + * @param bq Array of biquad coefficients + * @param e1 Input expression + * @note This implementation has zero latency + */ +template <size_t filters, typename T, typename E1> +CMT_INLINE internal::expression_biquads_zl<filters, T, internal::arg<E1>> biquad_zl( + const biquad_params<T> (&bq)[filters], E1&& e1) +{ + return internal::expression_biquads_zl<filters, T, internal::arg<E1>>(bq, std::forward<E1>(e1)); +} }