DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
OfflineBoxSmoother.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
10#include "../OfflineProcessing.h"
11#if DSPARK_HAS_OFFLINE
12#include "BoxAverage.h"
13#include <algorithm>
14#include <array>
15#include <memory>
16
17namespace dspark::detail
18{
20{
21 public:
22 void prepare(OfflineSession &job, std::int64_t span, bool active)
23 {
24 if (span < 1 || span > 1000000)
26 span_ = span;
27 double normalization = 1;
28 for (int i = 0; i < 4; ++i)
29 {
30 lengths_[i] = static_cast<int>(span_ / 4 + (i < span_ % 4) + 1);
31 normalization *= lengths_[i];
32 }
33 if (active)
34 {
35 weights_ = job.allocate<double>(static_cast<std::uint64_t>(span_ + 1));
36 // Coefficients of four finite geometric series, by inclusion-exclusion.
37 // Integer arithmetic preserves exact nonnegative tap counts; the
38 // setup bound above keeps all cubic products and sums within int64.
39 for (std::int64_t k = 0; k <= span_; ++k)
40 {
41 std::int64_t ways = 0;
42 for (unsigned mask = 0; mask < 16; ++mask)
43 {
44 std::int64_t remaining = k;
45 int sign = 1;
46 for (int i = 0; i < 4; ++i)
47 if (mask & (1u << i))
48 {
49 remaining -= lengths_[i];
50 sign = -sign;
51 }
52 if (remaining >= 0)
53 ways += sign * ((remaining + 1) * (remaining + 2) * (remaining + 3) / 6);
54 }
55 weights_[static_cast<std::size_t>(k)] = static_cast<double>(ways) / normalization;
56 }
57 }
58 }
59 [[nodiscard]] std::int64_t span() const noexcept
60 {
61 return span_;
62 }
63 [[nodiscard]] const double *weights() const noexcept
64 {
65 return weights_.get();
66 }
67 [[nodiscard]] std::size_t retainedBytes() const noexcept
68 {
69 return weights_ ? static_cast<std::size_t>(span_ + 1) * sizeof(double) : 0;
70 }
71 class Cursor final
72 {
73 public:
74 Cursor(const OfflineBoxSmoother &filter, OfflineSession &job) : filter_(filter)
75 {
76 if (!filter.weights_)
77 return;
78 storage_ = job.allocate<double>(static_cast<std::uint64_t>(filter.span_ + 4));
79 int offset = 0;
80 for (int i = 0; i < 4; ++i)
81 {
82 boxes_[i] = storage_.get() + offset;
83 inverse_[i] = 1.0 / filter.lengths_[i];
84 offset += filter.lengths_[i];
85 }
86 }
87 void reset() noexcept
88 {
89 if (storage_)
90 std::fill_n(storage_.get(), static_cast<std::size_t>(filter_.span_ + 4), 0.0);
91 sums_.fill(0);
92 indices_.fill(0);
93 zeroRun_ = 0;
94 boxesCleared_ = false;
95 }
96 [[nodiscard]] double advance(double raw, std::int64_t frame) noexcept
97 {
98 double value = raw;
99 for (int i = 0; i < 4; ++i)
100 {
101 const double incoming = value;
102 value = advanceBoxAverage(incoming, boxes_[i][indices_[i]], sums_[i], inverse_[i]);
103 boxes_[i][indices_[i]] = incoming;
104 if (++indices_[i] == filter_.lengths_[i])
105 indices_[i] = 0;
106 }
107 zeroRun_ = raw == 0 ? std::min(zeroRun_ + 1, static_cast<int>(filter_.span_ + 1)) : 0;
108 if (raw != 0)
109 boxesCleared_ = false;
110 if (zeroRun_ > filter_.span_)
111 {
112 sums_.fill(0);
113 value = 0;
114 // All mathematical history entries are zero. Clear roundoff
115 // once, so long silent spans stay constant-work exact identity.
116 if (!boxesCleared_)
117 {
118 std::fill_n(storage_.get(), static_cast<std::size_t>(filter_.span_ + 4), 0.0);
119 boxesCleared_ = true;
120 }
121 }
122 if ((frame & 4095) == 4095)
123 {
124 for (int i = 0; i < 4; ++i)
125 {
126 sums_[i] = 0;
127 for (int k = 0; k < filter_.lengths_[i]; ++k)
128 sums_[i] += boxes_[i][k];
129 }
130 value = sums_[3] * inverse_[3];
131 }
132 return value;
133 }
134
135 private:
136 const OfflineBoxSmoother &filter_;
137 std::unique_ptr<double[]> storage_;
138 std::array<double *, 4> boxes_{};
139 std::array<double, 4> sums_{}, inverse_{};
140 std::array<int, 4> indices_{};
141 int zeroRun_ = 0;
142 bool boxesCleared_ = false;
143 };
144
145 private:
146 std::int64_t span_ = 1;
147 std::array<int, 4> lengths_{{1, 1, 1, 1}};
148 std::unique_ptr<double[]> weights_;
149};
150} // namespace dspark::detail
151#endif // DSPARK_HAS_OFFLINE
double advance(double raw, std::int64_t frame) noexcept
Cursor(const OfflineBoxSmoother &filter, OfflineSession &job)
void prepare(OfflineSession &job, std::int64_t span, bool active)
const double * weights() const noexcept
std::int64_t span() const noexcept
std::size_t retainedBytes() const noexcept
std::unique_ptr< U[]> allocate(std::uint64_t count)
void offlineFail(OfflineStatus status)
double advanceBoxAverage(double incoming, double outgoing, double &sum, double inverseLength) noexcept
Definition BoxAverage.h:12