From b39475d3acea7ac42fc49a4f6aacf9e7a4567318 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Sun, 19 Jul 2026 01:24:31 +0300 Subject: [PATCH] Replace audio::feedforward/feedback filters with general linear filters --- .../include/psemek/audio/effect/filter.hpp | 9 +- libs/audio/source/effect/filter.cpp | 103 +++++++----------- 2 files changed, 45 insertions(+), 67 deletions(-) diff --git a/libs/audio/include/psemek/audio/effect/filter.hpp b/libs/audio/include/psemek/audio/effect/filter.hpp index 6d65bb52..9915cf32 100644 --- a/libs/audio/include/psemek/audio/effect/filter.hpp +++ b/libs/audio/include/psemek/audio/effect/filter.hpp @@ -1,15 +1,14 @@ #pragma once #include +#include namespace psemek::audio { - // Turns x[n] into y[n] = a0*x[n] + a1*x[n-1] - stream_ptr feedforward_filter(stream_ptr stream, float a0, float a1); - - // Turns x[n] into y[n] = a0*x[n] + b1*y[n-1] - stream_ptr feedback_filter(stream_ptr stream, float a0, float b1); + // Turns x[n] into y[n] = b0*x[n] + b1*x[n-1] + ... - a1*y[n-1] - a2*y[n-2] - ... + // NB: a[0] is implicitly 1 and is omitted + stream_ptr linear_filter(stream_ptr stream, util::array a, util::array b); stream_ptr low_pass_filter(stream_ptr stream); stream_ptr high_pass_filter(stream_ptr stream); diff --git a/libs/audio/source/effect/filter.cpp b/libs/audio/source/effect/filter.cpp index d54b496a..41713670 100644 --- a/libs/audio/source/effect/filter.cpp +++ b/libs/audio/source/effect/filter.cpp @@ -6,14 +6,19 @@ namespace psemek::audio namespace { - struct feedforward_filter_impl + struct linear_filter_impl : stream { - feedforward_filter_impl(stream_ptr stream, float a0, float a1) + linear_filter_impl(stream_ptr stream, util::array a, util::array b) : stream_(std::move(stream)) - , a0_(a0) - , a1_(a1) - {} + , a_(std::move(a)) + , b_(std::move(b)) + { + prev_in_[0].resize(b_.size(), 0.f); + prev_in_[1].resize(b_.size(), 0.f); + prev_out_[0].resize(a_.size(), 0.f); + prev_out_[1].resize(a_.size(), 0.f); + } std::optional length() const override { @@ -24,13 +29,32 @@ namespace psemek::audio { std::size_t count = stream_->read(samples); + // TODO: optimize? Maybe split into 3 loops (first N, main part, last N) to + // get rid of shift_right on each iteration for (std::size_t i = 0; i < count; i += 2) { - std::swap(prev_[0], samples[i + 0]); - std::swap(prev_[1], samples[i + 1]); + for (std::size_t s : {0, 1}) + { + if (!prev_in_[s].empty()) + { + std::shift_right(prev_in_[s].begin(), prev_in_[s].end(), 1); + prev_in_[s][0] = samples[i + s]; + } - samples[i + 0] = prev_[0] * a0_ + samples[i + 0] * a1_; - samples[i + 1] = prev_[1] * a0_ + samples[i + 1] * a1_; + float value = 0.f; + for (std::size_t j = 0; j < b_.size(); ++j) + value += prev_in_[s][j] * b_[j]; + for (std::size_t j = 0; j < a_.size(); ++j) + value -= prev_out_[s][j] * a_[j]; + + samples[i + s] = value; + + if (!prev_out_[s].empty()) + { + std::shift_right(prev_out_[s].begin(), prev_out_[s].end(), 1); + prev_out_[s][0] = value; + } + } } return count; @@ -43,72 +67,27 @@ namespace psemek::audio private: stream_ptr stream_; - float a0_; - float a1_; - float prev_[2]{0.f}; - }; - - struct feedback_filter_impl - : stream - { - feedback_filter_impl(stream_ptr stream, float a0, float b1) - : stream_(std::move(stream)) - , a0_(a0) - , b1_(b1) - {} - - std::optional length() const override - { - return stream_->length(); - } - - std::size_t read(util::span samples) override - { - std::size_t count = stream_->read(samples); - - for (std::size_t i = 0; i < count; i += 2) - { - samples[i + 0] = prev_[0] * b1_ + samples[i + 0] * a0_; - samples[i + 1] = prev_[1] * b1_ + samples[i + 1] * a0_; - prev_[0] = samples[i + 0]; - prev_[1] = samples[i + 1]; - } - - return count; - } - - std::size_t played() const override - { - return stream_->played(); - } - - private: - stream_ptr stream_; - float a0_; - float b1_; - float prev_[2]{0.f}; + util::array a_; + util::array b_; + util::array prev_in_[2]; + util::array prev_out_[2]; }; } - stream_ptr feedforward_filter(stream_ptr stream, float a0, float a1) + stream_ptr linear_filter(stream_ptr stream, util::array a, util::array b) { - return std::make_shared(std::move(stream), a0, a1); - } - - stream_ptr feedback_filter(stream_ptr stream, float a0, float b1) - { - return std::make_shared(std::move(stream), a0, b1); + return std::make_shared(std::move(stream), std::move(a), std::move(b)); } stream_ptr low_pass_filter(stream_ptr stream) { - return feedforward_filter(std::move(stream), 1.f, 1.f); + return linear_filter(std::move(stream), {}, {1.f, 1.f}); } stream_ptr high_pass_filter(stream_ptr stream) { - return feedforward_filter(std::move(stream), 1.f, -1.f); + return linear_filter(std::move(stream), {}, {1.f, -1.f}); } }