DSPark 1.8.0
Header-only C++20 DSP for real-time and offline audio
Loading...
Searching...
No Matches
ConvolutionRate.h
1// DSPark - Professional Audio DSP Framework
2// Copyright (c) 2026 Cristian Moresi - MIT License
3
4#pragma once
5
12#include "../AudioBuffer.h"
13#include "../Convolver.h"
14#include "../FIRFilter.h"
15#include <algorithm>
16#include <cassert>
17#include <cmath>
18#include <cstddef>
19#include <vector>
20
21namespace dspark::detail
22{
23class ConvolutionRate final
24{
25 Convolver<double> up_, referenceUp_, down_;
26 AudioBuffer<double> buffer_, reference_;
27 int factor_, latency_ = 0;
28 bool withReference_;
29
30 public:
31 struct Settings
32 {
33 int filterSpan = 16384;
34 int partitionFrames = 512;
35 double beta = 18.5;
36 double cutoff = 0.5;
37 };
38
39 [[nodiscard]] static bool valid(int factor, Settings settings) noexcept
40 {
41 return factor >= 1 && factor <= 16 && (factor & (factor - 1)) == 0 &&
42 settings.filterSpan >= 2 && settings.filterSpan <= 65536 &&
43 (settings.filterSpan & 1) == 0 && settings.partitionFrames >= 16 &&
44 settings.partitionFrames <= 4096 &&
45 (settings.partitionFrames & (settings.partitionFrames - 1)) == 0 &&
46 std::isfinite(settings.beta) && settings.beta >= 0 && settings.beta <= 30 &&
47 std::isfinite(settings.cutoff) && settings.cutoff > 0 && settings.cutoff <= 0.5;
48 }
49
50 // Cumulative setup bound, including temporary FIR designs, FFT plans, vector
51 // headers and allocation alignment/proxy overhead. Not a live-payload estimate.
52 [[nodiscard]] static std::size_t allocationBound(int factor, Settings settings,
53 bool withReference) noexcept
54 {
55 assert(valid(factor, settings));
56 const auto buffers = static_cast<std::size_t>(withReference ? 2 : 1);
57 const auto storage = buffers * 256 * static_cast<std::size_t>(factor) * sizeof(double);
58 if (factor == 1)
59 return storage;
60 const auto block = static_cast<std::size_t>(settings.partitionFrames * factor);
61 const auto length = static_cast<std::size_t>(settings.filterSpan * factor + 1);
62 return storage + 6 * (length * sizeof(double) + 64) +
63 (buffers + 1) * convolutionAllocationBound(block, length);
64 }
65
66 // Shared preflight for a fresh Core Convolver, including temporary FFT plans.
67 [[nodiscard]] static std::size_t convolutionAllocationBound(std::size_t block,
68 std::size_t length) noexcept
69 {
70 const auto partitions = (length + block - 1) / block;
71 return ((4 * partitions + 17) * block + 4 * partitions + 2) * sizeof(double) +
72 2 * partitions * sizeof(std::vector<double>) + sizeof(FFTReal<double>) + 2048 +
73 (2 * partitions + 12) * 64;
74 }
75
76 ConvolutionRate(int factor, Settings settings, bool withReference)
77 : factor_(factor), withReference_(withReference)
78 {
79 assert(valid(factor, settings));
80 buffer_.resize(1, 256 * factor);
81 if (withReference)
82 reference_.resize(1, 256 * factor);
83 if (factor == 1)
84 return;
85 const int block = settings.partitionFrames * factor;
86 const int length = settings.filterSpan * factor + 1;
87 auto up = FIRDesign<double>::lowPass(1., settings.cutoff / factor, length, settings.beta);
88 auto down = FIRDesign<double>::lowPass(1., settings.cutoff / factor, length, settings.beta);
89 for (auto &value : up)
90 value *= factor;
91 up_.prepare(block, up.data(), length);
92 if (withReference)
93 referenceUp_.prepare(block, up.data(), length);
94 down_.prepare(block, down.data(), length);
95 latency_ = (length - 1 + 2 * block) / factor;
96 }
97
98 [[nodiscard]] int latency() const noexcept
99 {
100 return latency_;
101 }
102
103 void reset() noexcept
104 {
105 up_.reset();
106 referenceUp_.reset();
107 down_.reset();
108 }
109
110 double *upsampleReference(const double *input, int count) noexcept
111 {
112 assert(withReference_ && count >= 0 && count <= 256);
113 auto *out = reference_.getChannel(0);
114 std::fill_n(out, count * factor_, 0.);
115 for (int i = 0; i < count; ++i)
116 out[i * factor_] = input[i];
117 if (factor_ > 1)
118 referenceUp_.processInPlace(out, count * factor_);
119 return out;
120 }
121
123 {
124 const int count = input.getNumSamples();
125 assert(input.getNumChannels() == 1 && count <= 256);
126 auto *out = buffer_.getChannel(0);
127 std::fill_n(out, count * factor_, 0.);
128 for (int i = 0; i < count; ++i)
129 out[i * factor_] = input.getChannel(0)[i];
130 if (factor_ > 1)
131 up_.processInPlace(out, count * factor_);
132 return buffer_.toView().getSubView(0, count * factor_);
133 }
134
136 {
137 const int count = output.getNumSamples();
138 assert(output.getNumChannels() == 1 && count <= 256);
139 auto *data = buffer_.getChannel(0);
140 if (factor_ > 1)
141 down_.processInPlace(data, count * factor_);
142 for (int i = 0; i < count; ++i)
143 output.getChannel(0)[i] = data[i * factor_];
144 }
145};
146} // namespace dspark::detail
Non-owning view over audio channel data.
Definition AudioBuffer.h:50
Owning audio buffer with contiguous, 32-byte aligned storage.
AudioBufferView< T, MaxChannels > toView() noexcept
Returns a non-owning mutable view of this buffer. The view's channel capacity is propagated from MaxC...
T * getChannel(int ch) noexcept
Returns a pointer to the sample data.
void resize(int numChannels, int numSamples)
Allocates the buffer for the given dimensions.
Real-time partitioned convolution using overlap-save with FFT.
Definition Convolver.h:68
FFT optimised for real-valued input signals (the common audio case).
Definition FFT.h:452
static std::vector< T > lowPass(double sampleRate, double cutoffHz, int numTaps, T beta=T(5))
Designs a low-pass FIR filter.
Definition FIRFilter.h:89
static std::size_t allocationBound(int factor, Settings settings, bool withReference) noexcept
AudioBufferView< double > upsample(AudioBufferView< double > input) noexcept
double * upsampleReference(const double *input, int count) noexcept
static bool valid(int factor, Settings settings) noexcept
static std::size_t convolutionAllocationBound(std::size_t block, std::size_t length) noexcept
ConvolutionRate(int factor, Settings settings, bool withReference)
void downsample(AudioBufferView< double > output) noexcept