Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
comb_filter.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9#include <cmath>
10#include <complex>
11#include <cstddef>
12#include <limits>
13#include <optional>
14#include <type_traits>
15
16namespace pulp::signal {
17
19
20inline constexpr bool is_valid_comb_filter_mode(CombFilterMode mode) noexcept {
21 return mode == CombFilterMode::feedforward || mode == CombFilterMode::feedback ||
23}
24
30
39
40template <typename SampleType> struct CombFilterSampleResult {
41 SampleType sample{};
43
44 [[nodiscard]] explicit operator bool() const noexcept {
46 }
47};
48
51 std::size_t processed_frames = 0;
52 std::size_t fault_count = 0;
53
54 [[nodiscard]] explicit operator bool() const noexcept {
56 }
57};
58
61inline std::complex<double> comb_filter_response(CombFilterMode mode,
62 std::size_t delay_samples, double gain,
63 double omega) noexcept {
64 constexpr auto nan = std::numeric_limits<double>::quiet_NaN();
65 if (!is_valid_comb_filter_mode(mode) || delay_samples < 2u || !std::isfinite(gain) ||
66 !std::isfinite(omega) || std::abs(gain) > 1.0 ||
68 std::abs(gain) > 0.999))
69 return {nan, nan};
70
71 const auto delayed = std::polar(1.0, -omega * static_cast<double>(delay_samples));
73 return 1.0 + gain * delayed;
74 const auto denominator = 1.0 - gain * delayed;
75 if (mode == CombFilterMode::feedback)
76 return 1.0 / denominator;
77 return (delayed - gain) / denominator;
78}
79
92template <typename SampleType = float> class CombFilterT {
93 static_assert(std::is_floating_point_v<SampleType>);
94
95 public:
96 static constexpr double maximum_recursive_gain = 0.999;
97 static constexpr std::size_t minimum_delay_samples = 2u;
98
99 [[nodiscard]] bool prepare(std::size_t maximum_delay_samples) {
100 if (!history_.prepare(maximum_delay_samples))
101 return false;
102 maximum_delay_samples_ = maximum_delay_samples;
103 prepared_ = true;
104 configured_ = false;
105 fault_count_ = 0;
106 return true;
107 }
108
109 [[nodiscard]] bool configure(const CombFilterConfig& candidate) noexcept {
110 if (!prepared_ || !valid_config(candidate))
111 return false;
112 config_ = candidate;
113 configured_ = true;
114 history_.discard_history();
115 return true;
116 }
117
118 void reset() noexcept {
119 history_.reset();
120 fault_count_ = 0;
121 }
122
123 [[nodiscard]] bool prepared() const noexcept { return prepared_; }
124 [[nodiscard]] bool configured() const noexcept { return configured_; }
125 [[nodiscard]] std::size_t maximum_delay_samples() const noexcept {
126 return prepared_ ? maximum_delay_samples_ : 0u;
127 }
128 [[nodiscard]] const CombFilterConfig& config() const noexcept { return config_; }
129 [[nodiscard]] std::size_t fault_count() const noexcept { return fault_count_; }
130 void clear_fault_count() noexcept { fault_count_ = 0; }
131
134 [[nodiscard]] static constexpr std::size_t processing_latency_samples() noexcept {
135 return 0u;
136 }
137
140 [[nodiscard]] std::optional<std::size_t> tail_samples() const noexcept {
141 if (!configured_)
142 return std::nullopt;
143 if (config_.mode == CombFilterMode::feedforward)
144 return config_.gain == 0.0 ? 0u : config_.delay_samples;
145 if (config_.mode == CombFilterMode::feedback)
146 return config_.gain == 0.0 ? std::optional<std::size_t>{0u} : std::nullopt;
147 return config_.gain == 0.0 ? std::optional{config_.delay_samples} : std::nullopt;
148 }
149
150 [[nodiscard]] CombFilterSampleResult<SampleType> process(SampleType input) noexcept {
151 if (!prepared_)
153 if (!configured_)
155 if (!std::isfinite(input))
157
158 const auto delayed = history_.read_lagrange3_at(
159 static_cast<double>(config_.delay_samples));
160 if (!delayed)
162
163 const auto x = static_cast<long double>(input);
164 const auto d = static_cast<long double>(delayed.sample);
165 const auto g = static_cast<long double>(config_.gain);
166 long double output = 0.0L;
167 long double stored = 0.0L;
168 if (config_.mode == CombFilterMode::feedforward) {
169 output = x + g * d;
170 stored = x;
171 } else if (config_.mode == CombFilterMode::feedback) {
172 output = x + g * d;
173 stored = output;
174 } else {
175 stored = x + g * d;
176 output = d - g * stored;
177 }
178
179 const auto limit = static_cast<long double>(std::numeric_limits<SampleType>::max());
180 if (!std::isfinite(output) || !std::isfinite(stored) || std::abs(output) > limit ||
181 std::abs(stored) > limit)
183 // Snap the recursive state to zero so a decaying tail does not park the
184 // feedback and allpass paths in denormal arithmetic after input stops.
185 if (history_.push(snap_to_zero(static_cast<SampleType>(stored))) != FractionalDelayStatus::ok)
187 return {static_cast<SampleType>(output), CombFilterStatus::ok};
188 }
189
190 [[nodiscard]] CombFilterBlockResult process(const SampleType* input, SampleType* output,
191 std::size_t frames) noexcept {
192 if (frames == 0u)
193 return {};
194 if (input == nullptr || output == nullptr)
196 if (!prepared_)
198 if (!configured_)
200
201 CombFilterBlockResult result{};
202 for (std::size_t i = 0; i < frames; ++i) {
203 const auto current_input = input[i];
204 const auto current = process(current_input);
205 output[i] = current.sample;
206 ++result.processed_frames;
207 if (!current) {
208 if (result.status == CombFilterStatus::ok)
209 result.status = current.status;
210 ++result.fault_count;
211 }
212 }
213 return result;
214 }
215
216 private:
217 [[nodiscard]] bool valid_config(const CombFilterConfig& candidate) const noexcept {
218 if (!is_valid_comb_filter_mode(candidate.mode) ||
219 candidate.delay_samples < minimum_delay_samples ||
220 candidate.delay_samples > maximum_delay_samples_ || !std::isfinite(candidate.gain))
221 return false;
222 if (candidate.mode == CombFilterMode::feedforward)
223 return std::abs(candidate.gain) <= 1.0;
224 return std::abs(candidate.gain) <= maximum_recursive_gain;
225 }
226
227 [[nodiscard]] CombFilterSampleResult<SampleType> fail(CombFilterStatus status) noexcept {
228 history_.discard_history();
229 ++fault_count_;
230 return {{}, status};
231 }
232
233 FractionalDelayHistoryT<SampleType> history_{};
234 CombFilterConfig config_{};
235 std::size_t maximum_delay_samples_ = 0;
236 std::size_t fault_count_ = 0;
237 bool prepared_ = false;
238 bool configured_ = false;
239};
240
243
244} // namespace pulp::signal
A fixed-capacity, integer-delay comb-filter family.
Definition comb_filter.hpp:92
static constexpr std::size_t minimum_delay_samples
Definition comb_filter.hpp:97
bool prepare(std::size_t maximum_delay_samples)
Definition comb_filter.hpp:99
bool configure(const CombFilterConfig &candidate) noexcept
Definition comb_filter.hpp:109
CombFilterSampleResult< SampleType > process(SampleType input) noexcept
Definition comb_filter.hpp:150
std::size_t maximum_delay_samples() const noexcept
Definition comb_filter.hpp:125
CombFilterBlockResult process(const SampleType *input, SampleType *output, std::size_t frames) noexcept
Definition comb_filter.hpp:190
static constexpr double maximum_recursive_gain
Definition comb_filter.hpp:96
void reset() noexcept
Definition comb_filter.hpp:118
bool configured() const noexcept
Definition comb_filter.hpp:124
bool prepared() const noexcept
Definition comb_filter.hpp:123
std::size_t fault_count() const noexcept
Definition comb_filter.hpp:129
std::optional< std::size_t > tail_samples() const noexcept
Finite tail in samples, or nullopt for a recursive infinite tail.
Definition comb_filter.hpp:140
const CombFilterConfig & config() const noexcept
Definition comb_filter.hpp:128
static constexpr std::size_t processing_latency_samples() noexcept
Comb structures retain a direct path and therefore add no fixed host compensation latency.
Definition comb_filter.hpp:134
void clear_fault_count() noexcept
Definition comb_filter.hpp:130
Prepared fractional-delay storage with Thiran-1 and Lagrange-3/5 reads.
Definition additive_bank.hpp:218
CombFilterMode
Definition comb_filter.hpp:18
@ allpass
Definition comb_filter.hpp:18
@ feedforward
Definition comb_filter.hpp:18
@ feedback
Definition comb_filter.hpp:18
CombFilterT< double > CombFilter64
Definition comb_filter.hpp:242
@ ok
Definition fractional_delay.hpp:34
CombFilterStatus
Definition comb_filter.hpp:31
@ ok
Definition comb_filter.hpp:32
@ non_finite_input
Definition comb_filter.hpp:36
@ output_out_of_range
Definition comb_filter.hpp:37
@ not_prepared
Definition comb_filter.hpp:33
@ invalid_argument
Definition comb_filter.hpp:35
@ not_configured
Definition comb_filter.hpp:34
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
CombFilterT< float > CombFilter
Definition comb_filter.hpp:241
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
constexpr bool is_valid_comb_filter_mode(CombFilterMode mode) noexcept
Definition comb_filter.hpp:20
@ ok
Definition breakpoint_envelope.hpp:36
std::complex< double > comb_filter_response(CombFilterMode mode, std::size_t delay_samples, double gain, double omega) noexcept
Returns H(e^jw) for the documented mode equations.
Definition comb_filter.hpp:61
Definition comb_filter.hpp:49
std::size_t fault_count
Definition comb_filter.hpp:52
std::size_t processed_frames
Definition comb_filter.hpp:51
CombFilterStatus status
Definition comb_filter.hpp:50
Definition comb_filter.hpp:25
std::size_t delay_samples
Definition comb_filter.hpp:27
double gain
Definition comb_filter.hpp:28
CombFilterMode mode
Definition comb_filter.hpp:26
Definition comb_filter.hpp:40
SampleType sample
Definition comb_filter.hpp:41
CombFilterStatus status
Definition comb_filter.hpp:42