DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
RetimedDcBlock.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
15#include "../FIRFilter.h"
16#include "ContinuousClip.h"
17#include "DcBlock.h"
18#include <array>
19#include <cassert>
20#include <cmath>
21#include <utility>
22#include <vector>
23namespace dspark::detail
24{
26{
27 using Series = std::vector<long double>;
28 std::array<DcBlockState, 16> states_{};
29 FIRFilter<double> filter_;
30 int stride_ = 1, write_ = 0, latency_ = 0;
31 double pole_ = .9995;
32 static Series multiply(const Series &a, const Series &b)
33 {
34 Series result(a.size());
35 for (std::size_t i = 0; i < a.size(); ++i)
36 for (std::size_t j = 0; i + j < a.size(); ++j)
37 result[i + j] += a[i] * b[j];
38 return result;
39 }
40 static Series halfAngle(const Series &h)
41 {
42 auto square = multiply(h, h);
43 Series root(h.size()), product(h.size()), result(h.size());
44 product[0] = 1;
45 for (std::size_t i = 1; i < h.size(); ++i)
46 product[i] = 4 * square[i - 1] - (i >= 2 ? 4 * square[i - 2] : 0);
47 root[0] = 1;
48 for (std::size_t i = 1; i < h.size(); ++i)
49 {
50 long double sum = 0;
51 for (std::size_t j = 1; j < i; ++j)
52 sum += root[j] * root[i - j];
53 root[i] = (product[i] - sum) / 2;
54 }
55 root[0] += 1;
56 for (std::size_t i = 0; i < h.size(); ++i)
57 {
58 long double value = h[i];
59 for (std::size_t j = 1; j <= i; ++j)
60 value -= root[j] * result[i - j];
61 result[i] = value / root[0];
62 }
63 return result;
64 }
65 static std::vector<double> ratioTaps(const Series &h, double pole)
66 {
67 const int order = static_cast<int>(h.size()) - 1, center = order + 1;
68 Series basis(static_cast<std::size_t>(2 * center + 1)), symmetric(basis.size());
69 basis[center] = 1;
70 for (int k = 0; k <= order; ++k)
71 {
72 for (std::size_t i = 0; i < basis.size(); ++i)
73 symmetric[i] += h[k] * basis[i];
74 if (k == order)
75 break;
76 Series next(basis.size());
77 for (int j = center - k; j <= center + k; ++j)
78 {
79 next[j - 1] -= .25L * basis[j];
80 next[j] += .5L * basis[j];
81 next[j + 1] -= .25L * basis[j];
82 }
83 basis = std::move(next);
84 }
85 std::vector<double> taps(basis.size());
86 taps[center] = (1 + pole) / 2;
87 const long double b = (1 - pole) / 2;
88 for (std::size_t i = 1; i + 1 < taps.size(); ++i)
89 {
90 taps[i - 1] += static_cast<double>(b * symmetric[i] / 2);
91 taps[i + 1] -= static_cast<double>(b * symmetric[i] / 2);
92 }
93 return taps;
94 }
95
96 public:
97 [[nodiscard]] static int filterLength(int factor, int sourceFactor) noexcept
98 {
99 if (factor >= sourceFactor)
100 return 0;
101 int stages = 0;
102 for (int ratio = sourceFactor / factor; ratio > 1; ratio /= 2)
103 ++stages;
104 return 1 + 2 * stages * (factor <= 2 ? 25 : 13);
105 }
106 // Cumulative coefficients/temporary series/FIR requests, with allocation
107 // alignment and debug-vector overhead. Factors have the constructor's bounds.
108 [[nodiscard]] static std::size_t allocationBound(int factor, int sourceFactor) noexcept
109 {
110 const int length = filterLength(factor, sourceFactor);
111 if (length == 0)
112 return 0;
113 const std::size_t order = factor <= 2 ? 24 : 12, n = order + 1, taps = 2 * n + 1;
114 const auto stages = static_cast<std::size_t>((length - 1) / (2 * n));
115 const auto series =
116 (stages + 1 + 4 * (stages - 1)) * n * sizeof(long double) + stages * sizeof(Series);
117 const auto kernels =
118 stages * (order + 2) * taps * sizeof(long double) +
119 (stages * taps + 1 + stages + n * stages * (stages + 1)) * sizeof(double);
120 const auto fir =
121 static_cast<std::size_t>(length) * (sizeof(std::atomic<double>) + 4 * sizeof(double)) +
122 sizeof(int);
123 return series + kernels + fir + (stages * order + 9 * stages + 4) * 64;
124 }
125 static std::vector<double> coefficients(int factor, int sourceFactor)
126 {
127 if (factor >= sourceFactor)
128 return {1.};
129 const int ratio = sourceFactor / factor, order = factor <= 2 ? 24 : 12;
130 int stages = 0;
131 while ((1 << stages) < ratio)
132 ++stages;
133 std::vector<Series> series(static_cast<std::size_t>(stages),
134 Series(static_cast<std::size_t>(order + 1)));
135 for (int k = 0; k <= order; ++k)
136 series[0][k] = continuous_clip::choose(2 * k + 2, k + 1) / (2 * std::pow(4.L, k + 1));
137 for (int i = 1; i < stages; ++i)
138 series[i] = halfAngle(series[i - 1]);
139 std::vector<double> result{1.};
140 for (int i = stages - 1; i >= 0; --i)
141 {
142 const double pole = std::pow(.9995, ratio / (1 << (i + 1)));
143 auto taps = ratioTaps(series[i], pole);
144 std::vector<double> combined(result.size() + taps.size() - 1);
145 for (std::size_t j = 0; j < result.size(); ++j)
146 for (std::size_t k = 0; k < taps.size(); ++k)
147 combined[j + k] += result[j] * taps[k];
148 result = std::move(combined);
149 }
150 return result;
151 }
152 RetimedDcBlock(int factor, int sourceFactor)
153 {
154 assert(factor >= 1 && factor <= 16 && (factor & (factor - 1)) == 0);
155 assert(sourceFactor >= 1 && sourceFactor <= 16 && (sourceFactor & (sourceFactor - 1)) == 0);
156 if (factor >= sourceFactor)
157 {
158 stride_ = factor / sourceFactor;
159 return;
160 }
161 pole_ = std::pow(.9995, sourceFactor / factor);
162 const auto taps = coefficients(factor, sourceFactor);
163 latency_ = static_cast<int>(taps.size() / 2);
164 filter_.prepare(static_cast<int>(taps.size()), 1);
165 filter_.setCoefficients(taps);
166 }
167 [[nodiscard]] int latency() const noexcept
168 {
169 return latency_;
170 }
171 void reset() noexcept
172 {
173 states_ = {};
174 write_ = 0;
175 filter_.reset();
176 }
177 double process(double x) noexcept
178 {
179 const double y = dcBlockStep(pole_, states_[write_], x);
180 write_ = (write_ + 1) % stride_;
181 return latency_ ? filter_.processSample(y, 0) : y;
182 }
183};
184} // namespace dspark::detail
FIR filter using direct-form convolution with a mirrored delay line.
Definition FIRFilter.h:364
void reset() noexcept
Resets all delay lines to zero, clearing the filter's memory.
Definition FIRFilter.h:452
void prepare(int maxTaps, int numChannels)
Pre-allocates memory and initializes the delay lines.
Definition FIRFilter.h:376
T processSample(T input, int channel) noexcept
Processes a single sample through the FIR filter.
Definition FIRFilter.h:518
void setCoefficients(std::span< const T > coeffs) noexcept
Sets the filter coefficients asynchronously.
Definition FIRFilter.h:418
double process(double x) noexcept
static std::vector< double > coefficients(int factor, int sourceFactor)
RetimedDcBlock(int factor, int sourceFactor)
static std::size_t allocationBound(int factor, int sourceFactor) noexcept
static int filterLength(int factor, int sourceFactor) noexcept
double dcBlockStep(double pole, DcBlockState &state, double input) noexcept
Applies (1 - z^-1) / (1 - pole * z^-1), with a pole in [0, 1).
Definition DcBlock.h:24