MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
FeedbackBuffer.hpp
Go to the documentation of this file.
1#pragma once
2
5
8
9namespace MayaFlux::Buffers {
10
11/**
12 * @class FeedbackBuffer
13 * @brief AudioBuffer that owns and publishes a history ring
14 *
15 * The ring holds the values the buffer's feedback processors carry from one
16 * sample to the next, newest at index 0. Processors attached to a
17 * FeedbackBuffer use this ring; processors on any other AudioBuffer keep
18 * their own. The default processor is a single-tap FeedbackProcessor:
19 * output = input + feedback * (value feed_samples ago).
20 */
21class MAYAFLUX_API FeedbackBuffer : public AudioBuffer {
22public:
23 /**
24 * @param channel_id Channel this buffer serves
25 * @param num_samples Samples per block
26 * @param feedback Gain of the default processor
27 * @param feed_samples Lag of the default processor, in samples (minimum 1)
28 */
29 FeedbackBuffer(uint32_t channel_id, uint32_t num_samples,
30 float feedback = 0.5F, uint32_t feed_samples = 512);
31
32 using AudioBuffer::clone_to;
33
34 [[nodiscard]] inline float get_feedback() const { return m_feedback_amount; }
35
36 void set_feedback(float amount);
37
38 [[nodiscard]] inline Memory::HistoryBuffer<double>& get_history_buffer() { return m_history; }
39
40 [[nodiscard]] inline const Memory::HistoryBuffer<double>& get_history_buffer() const { return m_history; }
41
42 void process_default() override;
43
44 void set_feed_samples(uint32_t samples);
45
46 [[nodiscard]] inline uint32_t get_feed_samples() const { return m_feed_samples; }
47
48 /**
49 * @brief Clone for another channel with its own ring and its own processor state
50 */
51 std::shared_ptr<AudioBuffer> clone_to(uint32_t channel) override;
52
53 /**
54 * @brief Total samples pushed into the ring since construction
55 *
56 * Lets a processor on another buffer line its own position up with this
57 * ring whether or not this buffer has been processed yet in the cycle.
58 */
59 [[nodiscard]] inline uint64_t get_write_count() const { return m_write_count; }
60
61 /**
62 * @brief Record that a processor pushed samples into the ring
63 * @param samples Number of samples pushed
64 */
65 inline void mark_written(uint64_t samples) { m_write_count += samples; }
66
67protected:
68 std::shared_ptr<BufferProcessor> create_default_processor() override;
69
70private:
74 uint64_t m_write_count { 0 };
75};
76
77/**
78 * @class FeedbackProcessor
79 * @brief Combines each sample with lagged values of its own carried stream
80 *
81 * Per sample the processor reads one value per tap from a ring of carried
82 * values, calls the combiner with the current sample, those tap values and
83 * the coefficient array, writes the result to the buffer and pushes the
84 * carried value into the ring. Lags start at one sample, so recursion
85 * within a block is possible. State (ring, coefficient array, lag ramps) is
86 * kept per attached buffer, so one processor can serve many buffers and
87 * clones never share history.
88 *
89 * Works on any AudioBuffer. On a FeedbackBuffer the ring is the buffer's own.
90 */
91class MAYAFLUX_API FeedbackProcessor : public BufferProcessor {
92public:
93 /**
94 * @brief Result of a combiner that carries a different value than it outputs
95 *
96 * out goes to the buffer, carry goes into the ring. Feedback carries the
97 * output, feedforward carries the input.
98 */
99 struct Step {
100 double out;
101 double carry;
102 };
103
104 /**
105 * @brief Combiner returning one value, written to the buffer and carried
106 * @param x Current sample
107 * @param taps Lagged values, one per tap
108 * @param coefficients This buffer's coefficient array, free to read and write
109 */
110 using Combine = std::function<double(double, std::span<const double>, std::span<double>)>;
111
112 /**
113 * @brief Combiner returning an output and a carried value
114 */
115 using CombineStep = std::function<Step(double, std::span<const double>, std::span<double>)>;
116
117 /**
118 * @brief A function of one window and one array, as in Kinesis::Discrete::Kernels
119 *
120 * Called with the window [x, tap 0, tap 1, ...] and the coefficient array,
121 * returns the value written to the buffer and carried. Any kernel in
122 * Discrete/Kernels fits, so weighted_sum with {1, g} is the feedback
123 * x + g * tap0.
124 */
125 using WindowKernel = std::function<double(std::span<double>, std::span<double>)>;
126
127 /**
128 * @brief Single tap, output = input + feedback * tap
129 * @param feedback Gain
130 * @param feed_samples Lag in samples, minimum 1
131 */
132 FeedbackProcessor(float feedback = 0.5F, uint32_t feed_samples = 512);
133
134 /**
135 * @param combine Combiner
136 * @param lags Tap lags in samples, fractional lags are interpolated linearly
137 * @param coefficients Initial coefficient array, copied to each attached buffer
138 */
139 FeedbackProcessor(Combine combine, const std::vector<double>& lags, std::vector<double> coefficients = {});
140
141 FeedbackProcessor(CombineStep combine, const std::vector<double>& lags, std::vector<double> coefficients = {});
142
143 FeedbackProcessor(WindowKernel kernel, const std::vector<double>& lags, std::vector<double> coefficients = {});
144
145 void processing_function(const std::shared_ptr<Buffer>& buffer) override;
146 void on_attach(const std::shared_ptr<Buffer>& buffer) override;
147 void on_detach(const std::shared_ptr<Buffer>& buffer) override;
148 [[nodiscard]] bool is_compatible_with(const std::shared_ptr<Buffer>& buffer) const override;
149
150 void set_feedback(float amount) { m_feedback_amount = amount; }
151
152 [[nodiscard]] float get_feedback() const { return m_feedback_amount; }
153
154 /**
155 * @brief Lag of the first tap, in samples
156 */
157 void set_feed_samples(uint32_t samples);
158
159 [[nodiscard]] uint32_t get_feed_samples() const { return m_feed_samples; }
160
161 void set_combine(Combine combine);
162
163 void set_combine(CombineStep combine);
164
165 void set_combine(WindowKernel kernel);
166
167 /**
168 * @brief Interpolation used for fractional lags
169 * @param order 1 is linear, the default; higher orders (up to 7) fit a
170 * Lagrange polynomial through order + 1 neighbouring ring entries
171 *
172 * Whole-sample lags read the same under every order.
173 */
174 void set_interpolation(size_t order);
175
176 [[nodiscard]] size_t get_interpolation() const { return m_order; }
177
178 /**
179 * @brief Read the taps from another FeedbackBuffer's ring
180 * @param source Buffer whose ring holds the lagged values
181 *
182 * The processor's own buffer is still the one it modifies and the one it
183 * pushes its carried values into. Lags measure samples behind this
184 * buffer's own position, are held to at least one block, and line up with
185 * the source through the two write counters, so the result does not
186 * depend on which of the two buffers is processed first in a cycle. The
187 * source's ring is grown to hold the lags when needed. Only a weak
188 * reference is kept.
189 */
190 void set_lag_source(const std::shared_ptr<FeedbackBuffer>& source);
191
192 void clear_lag_source();
193
194 /**
195 * @brief Replace all taps with fixed lags in samples
196 */
197 void set_lags(const std::vector<double>& lags);
198
199 /**
200 * @brief Drive one tap's lag from a TimeMap
201 * @param tap Tap index
202 * @param seconds Lag in seconds as a function of seconds processed so far
203 * @param max_seconds Largest lag the map may return, sizes the ring
204 *
205 * Evaluated once per block and ramped per sample across the block.
206 * @throws std::out_of_range if tap is not an existing tap
207 */
208 void set_lag(size_t tap, Kinesis::TimeMap seconds, double max_seconds);
209
210 /**
211 * @brief Set the initial coefficient array and reset every attached buffer's copy
212 */
213 void set_coefficients(std::vector<double> coefficients);
214
215 /**
216 * @brief The coefficient array of one attached buffer, empty if it is not attached
217 */
218 [[nodiscard]] std::span<double> coefficients(const std::shared_ptr<Buffer>& buffer);
219
220 /**
221 * @brief Same configuration, no attached state
222 */
223 [[nodiscard]] std::shared_ptr<FeedbackProcessor> clone() const;
224
225private:
226 struct Tap {
227 double samples { 1.0 };
228 std::shared_ptr<const Kinesis::TimeMap> map;
229 double max_samples { 1.0 };
230 };
231
232 struct State {
235 std::vector<double> coefs;
236 std::vector<double> values;
237 std::vector<double> lag_prev;
238 std::vector<double> lag_now;
239 std::vector<double> window;
240 uint64_t elapsed { 0 };
241 bool primed { false };
242 };
243
244 State& state_for(const std::shared_ptr<Buffer>& buffer);
245
246 void recompute_capacity();
247
248 static double read(const Memory::HistoryBuffer<double>& ring, double lag);
249
250 static double read_at(const Memory::HistoryBuffer<double>& ring, double position, size_t order) noexcept;
251
252 float m_feedback_amount { 0.5F };
253 uint32_t m_feed_samples { 512 };
254 bool m_default_combine { false };
255
259 size_t m_order { 1 };
260 std::weak_ptr<FeedbackBuffer> m_lag_source;
261 std::vector<Tap> m_taps;
262 std::vector<double> m_coefficients;
263 size_t m_capacity { 1 };
264
265 std::unordered_map<const Buffer*, State> m_states;
266};
267
268} // namespace MayaFlux::Buffers
std::optional< glm::vec3 > position
Definition Decoder.cpp:93
std::shared_ptr< NetworkGeometryBuffer > buffer
Concrete audio implementation of the Buffer interface for double-precision audio data.
Central computational transformation interface for continuous buffer processing.
uint64_t get_write_count() const
Total samples pushed into the ring since construction.
const Memory::HistoryBuffer< double > & get_history_buffer() const
Memory::HistoryBuffer< double > m_history
Memory::HistoryBuffer< double > & get_history_buffer()
void mark_written(uint64_t samples)
Record that a processor pushed samples into the ring.
AudioBuffer that owns and publishes a history ring.
std::function< Step(double, std::span< const double >, std::span< double >)> CombineStep
Combiner returning an output and a carried value.
std::weak_ptr< FeedbackBuffer > m_lag_source
std::unordered_map< const Buffer *, State > m_states
std::function< double(double, std::span< const double >, std::span< double >)> Combine
Combiner returning one value, written to the buffer and carried.
std::function< double(std::span< double >, std::span< double >)> WindowKernel
A function of one window and one array, as in Kinesis::Discrete::Kernels.
Combines each sample with lagged values of its own carried stream.
History buffer for difference equations and recursive relations.
Result of a combiner that carries a different value than it outputs.
std::shared_ptr< const Kinesis::TimeMap > map
Typed, composable, stateless callable from domain D to range R.
Definition Tendency.hpp:22