26#include "DenormalGuard.h"
43 constexpr double kPi = 3.14159265358979323846;
44 return index % 2 == 0 ? 0.0 : 2.0 / (kPi *
static_cast<double>(index));
90 assert(sampleRate > 0.0);
91 if (!(sampleRate > 0.0))
return;
93 sampleRate_ = sampleRate;
112 delay_[
static_cast<size_t>(writePos_)] = input;
113 delay_[
static_cast<size_t>(writePos_ + kTaps)] = input;
117 const T* window = delay_.data() + writePos_ + 1;
125 const T re = window[kCenter];
127 writePos_ = (writePos_ + 1 == kTaps) ? 0 : (writePos_ + 1);
138 std::span<T> outReal,
139 std::span<T> outImag)
noexcept
142 assert(outReal.size() >= input.size() && outImag.size() >= input.size());
145 const size_t numSamples =
146 std::min(input.size(), std::min(outReal.size(), outImag.size()));
147 for (
size_t i = 0; i < numSamples; ++i)
149 const auto res =
process(input[i]);
150 outReal[i] = res.real;
151 outImag[i] = res.imag;
168 static constexpr int kTaps = 191;
169 static constexpr int kCenter = kTaps / 2;
174 [[nodiscard]]
static const std::array<T, kTaps>& reversedKernel() noexcept
176 static const std::array<T, kTaps> kernel = []
178 std::array<T, kTaps> k {};
179 constexpr double kPi = 3.14159265358979323846;
180 for (
int i = 0; i < kTaps; ++i)
184 const int n = i - kCenter;
187 - 0.5 * std::cos(2.0 * kPi *
static_cast<double>(i) /
static_cast<double>(kTaps - 1))
188 + 0.08 * std::cos(4.0 * kPi *
static_cast<double>(i) /
static_cast<double>(kTaps - 1));
189 k[
static_cast<size_t>(kTaps - 1 - i)] =
static_cast<T
>(ideal * w);
196 double sampleRate_ = 0.0;
197 bool isPrepared_ =
false;
202 const T* kernelData_ = reversedKernel().data();
207 std::array<T, kTaps * 2> delay_{};
233template <FloatType T>
246 for (
auto& s : sections_) s = {};
253 double re = 0.0, im = 0.0;
254 step(
static_cast<double>(input), re, im);
255 return {
static_cast<T
>(re),
static_cast<T
>(im) };
262 double re = 0.0, im = 0.0;
263 step(
static_cast<double>(input), re, im);
264 return static_cast<T
>(std::sqrt(re * re + im * im));
277 constexpr int kChunk = 64;
280 for (
int start = 0; start < numSamples; start += kChunk)
282 const int n = std::min(kChunk, numSamples - start);
283 for (
int i = 0; i < n; ++i)
284 re[i] = im[i] =
static_cast<double>(in[start + i]);
285 for (
int k = 0; k < kSections; ++k)
286 Section::runPair(sections_[
static_cast<size_t>(k)], re, kReal[k],
287 sections_[
static_cast<size_t>(kSections + k)], im, kImag[k], n);
288 for (
int i = 0; i < n; ++i)
290 const double imDelayed = delayedImag_;
291 delayedImag_ = im[i];
292 out[start + i] =
static_cast<T
>(std::sqrt(re[i] * re[i] + imDelayed * imDelayed));
298 static constexpr int kSections = 7;
300 inline void step(
double input,
double& re,
double& im)
noexcept
304 for (
int k = 0; k < kSections; ++k)
306 re = sections_[
static_cast<size_t>(k)].
process(re, kReal[k]);
307 quad = sections_[
static_cast<size_t>(kSections + k)].
process(quad, kImag[k]);
316 double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;
320 inline double process(
double x,
double c)
noexcept
322 const double y = (c * x - x2) + c * y2;
331 static inline void runPair(Section& p,
double* dp,
double cp,
332 Section& q,
double* dq,
double cq,
int n)
noexcept
334 double px1 = p.x1, px2 = p.x2, py1 = p.y1, py2 = p.y2;
335 double qx1 = q.x1, qx2 = q.x2, qy1 = q.y1, qy2 = q.y2;
336 for (
int i = 0; i < n; ++i)
338 const double xp = dp[i];
339 const double xq = dq[i];
340 const double yp = (cp * xp - px2) + cp * py2;
341 const double yq = (cq * xq - qx2) + cq * qy2;
342 px2 = px1; px1 = xp; py2 = py1; py1 = yp;
343 qx2 = qx1; qx1 = xq; qy2 = qy1; qy1 = yq;
347 p.x1 = px1; p.x2 = px2; p.y1 = py1; p.y2 = py2;
348 q.x1 = qx1; q.x2 = qx2; q.y1 = qy1; q.y2 = qy2;
352 static constexpr double kReal[kSections] = {
353 0.0849750635881296, 0.5135957829646070, 0.8197816632773615, 0.9420612863757530,
354 0.9822486171863649, 0.9947076541867014, 0.9986500580725346 };
355 static constexpr double kImag[kSections] = {
356 0.2887645620083060, 0.6955314021190576, 0.8968265992273647, 0.9678214299445055,
357 0.9902622348692460, 0.9971984927047717, 0.9995996402566010 };
359 std::array<Section, 2 * kSections> sections_ {};
360 double delayedImag_ = 0.0;
RAII scope guard to disable denormalised (subnormal) floating-point numbers.
Zero-latency analytic pair from two allpass chains (a 90-degree phase-difference network).
Result process(T input) noexcept
Processes one sample into the analytic pair. RT-safe.
void reset() noexcept
Clears the allpass states. RT-safe.
T magnitude(T input) noexcept
Processes one sample and returns the analytic magnitude sqrt(real^2 + imag^2): a ripple-free envelope...
void magnitudeBlock(const T *in, T *out, int numSamples) noexcept
Analytic magnitude of a block: out[i] = |analytic(in[i])|.
90-degree phase-differencing network (analytic-signal generator).
static constexpr int getLatencySamples() noexcept
FIR group-delay latency applied to both outputs, in samples.
void reset() noexcept
Resets the delay line. Mandatory when seeking or starting playback.
void prepare(double sampleRate) noexcept
Prepares the transformer. The FIR kernel is sample-rate independent; sampleRate is accepted for API s...
Result process(T input) noexcept
Processes one sample, returning the analytic signal {real, imag}.
void processBlock(std::span< const T > input, std::span< T > outReal, std::span< T > outImag) noexcept
Processes a block of samples. Optimized for CPU cache.
double hilbertIdealImpulse(std::int64_t index) noexcept
float dotProduct(const float *DSPARK_RESTRICT a, const float *DSPARK_RESTRICT b, int count) noexcept
Computes the dot product of two arrays.
Main namespace for the DSPark framework.
T real
In-phase component (allpass-filtered input).
T imag
Quadrature component, 90 degrees behind real (like the Hilbert transform of a cosine).
T real
In-phase component (input delayed by the FIR group delay).
T imag
Quadrature component (90-deg shifted).