25template <
typename SampleType =
float>
37 max_taps_ = std::clamp(
max_taps, 1, kMaxTaps);
39 history_.assign(
static_cast<std::size_t
>(max_taps_), 0.0);
40 weights_.assign(
static_cast<std::size_t
>(max_taps_), 0.0);
41 published_.assign(
static_cast<std::size_t
>(kPublishBuffers * max_taps_), 0.0);
42 active_taps_ = max_taps_;
44 publish_index_.store(0, std::memory_order_relaxed);
45 reader_index_.store(-1, std::memory_order_relaxed);
46 for (
auto& count : published_taps_)
47 count.store(0, std::memory_order_relaxed);
52 if (taps < 1 || taps > max_taps_)
return false;
61 const double v = finite_or(
static_cast<double>(mu), 0.5);
62 mu_ = std::clamp(v, kParameterFloor, 2.0 - kParameterFloor);
67 const double v = finite_or(
static_cast<double>(eps), 1.0e-8);
68 denominator_floor_ = std::max(v, kParameterFloor);
74 const double v = finite_or(
static_cast<double>(leakage), 0.0);
75 leakage_ = std::clamp(v, 0.0, 1.0 - kParameterFloor);
81 std::fill(history_.begin(), history_.end(), 0.0);
82 std::fill(weights_.begin(), weights_.end(), 0.0);
92 if (max_taps_ == 0 || sample_rate_ <= 0.0 ||
93 !std::isfinite(
static_cast<double>(primary)) ||
94 !std::isfinite(
static_cast<double>(
reference)))
97 const double primary_value = flush_denormal(
static_cast<double>(primary));
98 const double reference_value = flush_denormal(
static_cast<double>(
reference));
99 history_[
static_cast<std::size_t
>(write_pos_)] = reference_value;
100 write_pos_ = (write_pos_ + 1) % max_taps_;
101 double estimate = 0.0;
103 for (
int i = 0;
i < active_taps_; ++
i) {
104 const double x = history_at(
i);
105 estimate += weights_[
static_cast<std::size_t
>(
i)] * x;
108 const double error = primary_value - estimate;
109 if (adapt_enabled_) {
110 const double step = mu_ * error / std::max(denominator_floor_ + power,
111 std::numeric_limits<double>::min());
112 const double retain = 1.0 - leakage_;
113 for (
int i = 0;
i < active_taps_; ++
i) {
114 double& w = weights_[
static_cast<std::size_t
>(
i)];
118 w = retain * (w + step * history_at(
i));
119 if (!std::isfinite(w) || std::abs(w) <= kDenormalFloor) w = 0.0;
123 return {
static_cast<SampleType
>(estimate),
static_cast<SampleType
>(error)};
127 Result* out,
int n)
noexcept {
128 if (n <= 0 || primary ==
nullptr ||
reference ==
nullptr || out ==
nullptr)
return;
133 int& active_taps_out)
const noexcept {
134 if (dest ==
nullptr || capacity < 0)
return false;
135 for (
int attempt = 0; attempt < kSnapshotAttempts; ++attempt) {
136 const int index = publish_index_.load(std::memory_order_seq_cst);
137 if (index < 0 || index >= kPublishBuffers)
continue;
143 reader_index_.store(index, std::memory_order_seq_cst);
144 if (publish_index_.load(std::memory_order_seq_cst) != index) {
145 reader_index_.store(-1, std::memory_order_seq_cst);
148 const int count = published_taps_[
static_cast<std::size_t
>(index)].load(
149 std::memory_order_acquire);
150 if (count < 0 || count > capacity) {
151 reader_index_.store(-1, std::memory_order_seq_cst);
154 const double* source = published_.data() +
static_cast<std::size_t
>(index * max_taps_);
156 reader_index_.store(-1, std::memory_order_seq_cst);
157 active_taps_out = count;
164 int max_taps() const noexcept {
return max_taps_; }
171 return static_cast<std::size_t
>(kPublishBuffers + 2) *
172 static_cast<std::size_t
>(max_taps_) *
sizeof(double);
176 static constexpr int kMaxTaps = 8192;
177 static constexpr int kPublishBuffers = 3;
178 static constexpr int kSnapshotAttempts = 4;
179 static constexpr double kParameterFloor = 1.0e-12;
180 static constexpr double kDenormalFloor = 1.0e-18;
182 static double finite_or(
double v,
double fallback)
noexcept {
183 return std::isfinite(v) ? v : fallback;
186 static double flush_denormal(
double v)
noexcept {
187 return std::abs(v) <= kDenormalFloor ? 0.0 : v;
190 double history_at(
int delay)
const noexcept {
191 int index = write_pos_ - 1 -
delay;
192 if (index < 0) index += max_taps_ * ((-index / max_taps_) + 1);
193 return history_[
static_cast<std::size_t
>(index % max_taps_)];
196 void publish_current() noexcept {
197 if (max_taps_ == 0)
return;
198 const int current = publish_index_.load(std::memory_order_seq_cst);
199 const int reader = reader_index_.load(std::memory_order_seq_cst);
200 int target = (current + 1) % kPublishBuffers;
201 if (target == reader) target = (target + 1) % kPublishBuffers;
202 double* destination = published_.data() +
static_cast<std::size_t
>(target * max_taps_);
203 std::memcpy(destination, weights_.data(),
204 static_cast<std::size_t
>(active_taps_) *
sizeof(
double));
205 if (active_taps_ < max_taps_)
206 std::fill(destination + active_taps_, destination + max_taps_, 0.0);
207 published_taps_[
static_cast<std::size_t
>(target)].store(
208 active_taps_, std::memory_order_release);
209 publish_index_.store(target, std::memory_order_seq_cst);
212 double sample_rate_ = 0.0;
214 int active_taps_ = 0;
217 double denominator_floor_ = 1.0e-8;
218 double leakage_ = 0.0;
219 bool adapt_enabled_ =
true;
220 std::vector<double> history_;
221 std::vector<double> weights_;
222 std::vector<double> published_;
223 mutable std::atomic<int> publish_index_{0};
224 mutable std::atomic<int> reader_index_{-1};
225 mutable std::array<std::atomic<int>, kPublishBuffers> published_taps_{};
Definition nlms_adaptive_filter.hpp:26
void prepare(double sample_rate, int max_taps)
Allocate fixed storage.
Definition nlms_adaptive_filter.hpp:36
void set_step_size(SampleType mu) noexcept
Clamp finite mu into the open NLMS stability interval (0, 2).
Definition nlms_adaptive_filter.hpp:60
void process_block(const SampleType *primary, const SampleType *reference, Result *out, int n) noexcept
Definition nlms_adaptive_filter.hpp:126
int max_taps() const noexcept
Definition nlms_adaptive_filter.hpp:164
void set_adapt_enabled(bool enabled) noexcept
Definition nlms_adaptive_filter.hpp:78
bool set_active_taps(int taps) noexcept
Definition nlms_adaptive_filter.hpp:51
Result process_sample(SampleType primary, SampleType reference) noexcept
Definition nlms_adaptive_filter.hpp:91
void set_denominator_floor(SampleType eps) noexcept
Clamp the normalization floor to a finite, positive lower bound.
Definition nlms_adaptive_filter.hpp:66
bool try_snapshot_coefficients(SampleType *dest, int capacity, int &active_taps_out) const noexcept
Definition nlms_adaptive_filter.hpp:132
int active_taps() const noexcept
Definition nlms_adaptive_filter.hpp:163
void reset() noexcept
Definition nlms_adaptive_filter.hpp:80
std::size_t retained_bytes() const noexcept
Definition nlms_adaptive_filter.hpp:167
double sample_rate() const noexcept
Definition nlms_adaptive_filter.hpp:165
int latency_samples() const noexcept
Definition nlms_adaptive_filter.hpp:166
void set_leakage(SampleType leakage) noexcept
Per-sample loss in [0, 1): the complete normalized update is retained by (1 - leakage),...
Definition nlms_adaptive_filter.hpp:73
Definition additive_bank.hpp:218
@ delay
Definition envelope.hpp:57
NlmsAdaptiveFilterT< float > NlmsAdaptiveFilter
Definition nlms_adaptive_filter.hpp:228
@ reference
Platform sine; the compatibility default.
Definition fast_math.hpp:40
@ i
Definition formant_filter_bank.hpp:20
NlmsAdaptiveFilterT< double > NlmsAdaptiveFilter64
Definition nlms_adaptive_filter.hpp:229
Definition nlms_adaptive_filter.hpp:28
SampleType estimate
Definition nlms_adaptive_filter.hpp:29
SampleType error
Definition nlms_adaptive_filter.hpp:30