Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
audio_matrix_mixer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "detail/audio_range.hpp"
4
5#include <algorithm>
6#include <array>
7#include <cmath>
8#include <cstddef>
9#include <limits>
10#include <memory>
11#include <new>
12#include <span>
13#include <type_traits>
14#include <vector>
15
16namespace pulp::signal {
17
18namespace detail {
19
20template <std::size_t Inputs, std::size_t Outputs>
21inline constexpr bool
22 valid_audio_matrix_capacity = Inputs > 0 && Outputs > 0 &&
23 Inputs <= std::numeric_limits<std::size_t>::max() / Outputs;
24
25} // namespace detail
26
31
41template <typename SampleType = float, std::size_t MaxInputs = 16, std::size_t MaxOutputs = 16,
42 typename Allocator = std::allocator<SampleType>>
45 public:
46 static_assert(std::is_floating_point_v<SampleType>);
47 static constexpr std::size_t max_inputs = MaxInputs;
48 static constexpr std::size_t max_outputs = MaxOutputs;
49 static constexpr SampleType max_abs_gain = SampleType{64};
50
51 explicit AudioMatrixMixerT(const Allocator& allocator = Allocator{}) : scratch_(allocator) {}
52
53 bool prepare(std::size_t max_block_size) {
54 if (max_block_size == 0 ||
55 max_block_size > std::numeric_limits<std::size_t>::max() / MaxInputs)
56 return false;
57 const auto scratch_samples = MaxInputs * max_block_size;
58 if (scratch_samples > scratch_.max_size())
59 return false;
60 std::vector<SampleType, Allocator> replacement(scratch_.get_allocator());
61#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
62 try {
63#endif
64 replacement.assign(scratch_samples, SampleType{0});
65#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
66 } catch (const std::bad_alloc&) {
67 return false;
68 }
69#endif
70 scratch_.swap(replacement);
71 max_block_size_ = max_block_size;
72 settle_ramps();
73 return true;
74 }
75
76 bool set_dimensions(std::size_t inputs, std::size_t outputs) noexcept {
77 if (inputs == 0 || outputs == 0 || inputs > MaxInputs || outputs > MaxOutputs)
78 return false;
79 if (inputs == inputs_ && outputs == outputs_)
80 return true;
81 inputs_ = inputs;
82 outputs_ = outputs;
83 cells_.fill({});
84 return true;
85 }
86
90 policy_ = policy;
91 }
93 return policy_;
94 }
95
96 bool set_gain(std::size_t output, std::size_t input, SampleType gain) noexcept {
97 return set_gain_ramped(output, input, gain, 0);
98 }
99
104 bool set_gain_ramped(std::size_t output, std::size_t input, SampleType gain,
105 std::size_t ramp_samples) noexcept {
106 if (output >= outputs_ || input >= inputs_)
107 return false;
108 if (!std::isfinite(gain)) {
109 gain = SampleType{0};
110 ++nonfinite_gain_count_;
111 }
112 if (std::abs(gain) > max_abs_gain) {
113 ++out_of_range_gain_count_;
114 return false;
115 }
116 auto& cell = cells_[index(output, input)];
117 cell.start = cell.current;
118 cell.target = gain;
119 cell.total = ramp_samples;
120 cell.elapsed = 0;
121 if (ramp_samples == 0) {
122 cell.current = gain;
123 cell.start = gain;
124 }
125 return true;
126 }
127
128 SampleType gain(std::size_t output, std::size_t input) const noexcept {
129 return output < outputs_ && input < inputs_ ? cells_[index(output, input)].current
130 : SampleType{0};
131 }
132 SampleType target_gain(std::size_t output, std::size_t input) const noexcept {
133 return output < outputs_ && input < inputs_ ? cells_[index(output, input)].target
134 : SampleType{0};
135 }
136 std::size_t input_count() const noexcept {
137 return inputs_;
138 }
139 std::size_t output_count() const noexcept {
140 return outputs_;
141 }
142 std::size_t max_block_size() const noexcept {
143 return max_block_size_;
144 }
145 std::size_t nonfinite_gain_count() const noexcept {
146 return nonfinite_gain_count_;
147 }
148 std::size_t out_of_range_gain_count() const noexcept {
149 return out_of_range_gain_count_;
150 }
151
154 void reset() noexcept {
155 std::fill(scratch_.begin(), scratch_.end(), SampleType{0});
156 settle_ramps();
157 }
158
159 bool process(const SampleType* const* inputs, std::size_t input_count,
160 SampleType* const* outputs, std::size_t output_count,
161 std::size_t frames) noexcept {
162 if (inputs == nullptr || outputs == nullptr || input_count != inputs_ ||
163 output_count != outputs_ || frames > max_block_size_ || scratch_.empty())
164 return false;
165 for (std::size_t input = 0; input < inputs_; ++input)
166 if (inputs[input] == nullptr)
167 return false;
168 for (std::size_t output = 0; output < outputs_; ++output) {
169 if (outputs[output] == nullptr)
170 return false;
171 for (std::size_t other = output + 1; other < outputs_; ++other)
172 if (detail::audio_ranges_overlap(outputs[output], outputs[other], frames))
173 return false;
174 }
175
176 for (std::size_t input = 0; input < inputs_; ++input)
177 std::copy_n(inputs[input], frames, scratch_.data() + input * max_block_size_);
178
179 if (policy_ == MatrixHeadroomPolicy::Raw) {
180 for (std::size_t frame = 0; frame < frames; ++frame) {
181 for (std::size_t output = 0; output < outputs_; ++output) {
182 SampleType sum{};
183 for (std::size_t input = 0; input < inputs_; ++input)
184 sum += scratch_[input * max_block_size_ + frame] *
185 cells_[index(output, input)].current;
186 outputs[output][frame] = sum;
187 }
188 advance_ramps();
189 }
190 return true;
191 }
192
193 using AccumulatorType =
194 std::conditional_t<(sizeof(SampleType) < sizeof(double)), double, long double>;
195 std::array<AccumulatorType, MaxOutputs> row_scales{};
196 for (std::size_t frame = 0; frame < frames; ++frame) {
197 for (std::size_t output = 0; output < outputs_; ++output) {
198 AccumulatorType absolute_sum{};
199 for (std::size_t input = 0; input < inputs_; ++input)
200 absolute_sum += static_cast<AccumulatorType>(
201 std::abs(cells_[index(output, input)].current));
202 row_scales[output] =
203 AccumulatorType{1} / std::max(AccumulatorType{1}, absolute_sum);
204 }
205
206 for (std::size_t output = 0; output < outputs_; ++output) {
207 AccumulatorType sum{};
208 for (std::size_t input = 0; input < inputs_; ++input)
209 sum += static_cast<AccumulatorType>(
210 scratch_[input * max_block_size_ + frame]) *
211 static_cast<AccumulatorType>(
212 cells_[index(output, input)].current);
213 outputs[output][frame] =
214 static_cast<SampleType>(sum * row_scales[output]);
215 }
216 advance_ramps();
217 }
218 return true;
219 }
220
221 private:
222 struct GainCell {
223 SampleType current{};
224 SampleType start{};
225 SampleType target{};
226 std::size_t total = 0;
227 std::size_t elapsed = 0;
228 };
229
230 static constexpr std::size_t index(std::size_t output, std::size_t input) noexcept {
231 return output * MaxInputs + input;
232 }
233
234 void advance_ramps() noexcept {
235 for (auto& cell : cells_) {
236 if (cell.elapsed >= cell.total)
237 continue;
238 ++cell.elapsed;
239 if (cell.elapsed == cell.total) {
240 cell.current = cell.target;
241 continue;
242 }
243 using ProgressType =
244 std::conditional_t<(sizeof(SampleType) < sizeof(double)), double, long double>;
245 const auto progress =
246 static_cast<ProgressType>(cell.elapsed) / static_cast<ProgressType>(cell.total);
247 const auto start = static_cast<ProgressType>(cell.start);
248 const auto target = static_cast<ProgressType>(cell.target);
249 auto current = static_cast<SampleType>(start + (target - start) * progress);
250 if ((cell.start < cell.target && current >= cell.target) ||
251 (cell.start > cell.target && current <= cell.target))
252 current = std::nextafter(cell.target, cell.start);
253 cell.current = current;
254 }
255 }
256
257 void settle_ramps() noexcept {
258 for (auto& cell : cells_) {
259 cell.current = cell.target;
260 cell.start = cell.target;
261 cell.total = 0;
262 cell.elapsed = 0;
263 }
264 }
265
266 std::size_t inputs_ = 0;
267 std::size_t outputs_ = 0;
268 std::size_t max_block_size_ = 0;
269 std::size_t nonfinite_gain_count_ = 0;
270 std::size_t out_of_range_gain_count_ = 0;
272 std::array<GainCell, MaxInputs * MaxOutputs> cells_{};
273 std::vector<SampleType, Allocator> scratch_;
274};
275
278
279} // namespace pulp::signal
Fixed-capacity signed audio routing matrix with sample-continuous gain ramps.
Definition audio_matrix_mixer.hpp:44
void reset() noexcept
Clears scratch and settles active ramps at their targets.
Definition audio_matrix_mixer.hpp:154
float gain(std::size_t output, std::size_t input) const noexcept
Definition audio_matrix_mixer.hpp:128
static constexpr std::size_t max_inputs
Definition audio_matrix_mixer.hpp:47
bool set_gain(std::size_t output, std::size_t input, SampleType gain) noexcept
Definition audio_matrix_mixer.hpp:96
bool set_gain_ramped(std::size_t output, std::size_t input, SampleType gain, std::size_t ramp_samples) noexcept
The old value is used for the first sample.
Definition audio_matrix_mixer.hpp:104
void set_headroom_policy(MatrixHeadroomPolicy policy) noexcept
Headroom policy is structural, not automatable: change it while stopped or accept a block-boundary le...
Definition audio_matrix_mixer.hpp:89
AudioMatrixMixerT(const Allocator &allocator=Allocator{})
Definition audio_matrix_mixer.hpp:51
std::size_t max_block_size() const noexcept
Definition audio_matrix_mixer.hpp:142
SampleType target_gain(std::size_t output, std::size_t input) const noexcept
Definition audio_matrix_mixer.hpp:132
std::size_t output_count() const noexcept
Definition audio_matrix_mixer.hpp:139
bool set_dimensions(std::size_t inputs, std::size_t outputs) noexcept
Definition audio_matrix_mixer.hpp:76
bool process(const SampleType *const *inputs, std::size_t input_count, SampleType *const *outputs, std::size_t output_count, std::size_t frames) noexcept
Definition audio_matrix_mixer.hpp:159
static constexpr float max_abs_gain
Definition audio_matrix_mixer.hpp:49
std::size_t input_count() const noexcept
Definition audio_matrix_mixer.hpp:136
bool prepare(std::size_t max_block_size)
Definition audio_matrix_mixer.hpp:53
std::size_t out_of_range_gain_count() const noexcept
Definition audio_matrix_mixer.hpp:148
static constexpr std::size_t max_outputs
Definition audio_matrix_mixer.hpp:48
MatrixHeadroomPolicy headroom_policy() const noexcept
Definition audio_matrix_mixer.hpp:92
std::size_t nonfinite_gain_count() const noexcept
Definition audio_matrix_mixer.hpp:145
Round-2 effect-wave vocabulary.
Definition analysis_frontends.hpp:50
constexpr bool valid_audio_matrix_capacity
Definition audio_matrix_mixer.hpp:22
Definition additive_bank.hpp:218
AudioMatrixMixerT< double > AudioMatrixMixer64
Definition audio_matrix_mixer.hpp:277
MatrixHeadroomPolicy
Raw applies the signed matrix exactly.
Definition audio_matrix_mixer.hpp:30
@ Raw
Definition audio_matrix_mixer.hpp:30
@ NormalizePeak
Definition audio_matrix_mixer.hpp:30
AudioMatrixMixerT< float > AudioMatrixMixer
Definition audio_matrix_mixer.hpp:276
@ progress
Definition finite_stretch_builder.hpp:30
@ start
Definition geometry.hpp:370