15 float feedback, uint32_t feed_samples)
17 , m_feedback_amount(feedback)
18 , m_feed_samples(
std::
max<uint32_t>(feed_samples, 1))
19 , m_history(m_feed_samples)
33 proc->set_feedback(amount);
57 clone->set_default_processor(proc->clone());
69 : m_feedback_amount(feedback)
70 , m_feed_samples(
std::
max<uint32_t>(feed_samples, 1))
71 , m_default_combine(true)
73 m_combine = [
this](
double x, std::span<const double> taps, std::span<double>) {
80 m_taps.push_back(std::move(tap));
85 : m_combine(
std::move(combine))
86 , m_coefficients(
std::move(coefficients))
92 : m_step(
std::move(combine))
93 , m_coefficients(
std::move(coefficients))
99 : m_kernel(
std::move(kernel))
100 , m_coefficients(
std::move(coefficients))
112 m_taps.front().map.reset();
127 m_step = std::move(combine);
143 m_order = std::clamp<size_t>(order, 1, 7);
160 m_taps.reserve(lags.size());
161 for (
const double lag : lags) {
163 tap.
samples = std::max(lag, 1.0);
165 m_taps.push_back(std::move(tap));
176 if (tap >=
m_taps.size()) {
178 "FeedbackProcessor::set_lag: tap out of range (tap={}, tap_count={})", tap,
m_taps.size());
181 m_taps[tap].map = std::make_shared<const Kinesis::TimeMap>(std::move(seconds));
200 return found->second.coefs;
214 copy->m_default_combine =
false;
226 for (
const auto& tap :
m_taps) {
227 needed = std::max(needed, tap.max_samples);
230 m_capacity =
static_cast<size_t>(std::ceil(needed)) + extra;
236 State& state = found->second;
245 if (
auto feedback_buffer = std::dynamic_pointer_cast<FeedbackBuffer>(
buffer)) {
246 state.
ring = &feedback_buffer->get_history_buffer();
268 return std::dynamic_pointer_cast<AudioBuffer>(
buffer) !=
nullptr;
273 const auto whole =
static_cast<size_t>(lag);
274 const double fraction = lag -
static_cast<double>(whole);
275 const double nearer = ring[whole - 1];
277 if (fraction <= 0.0) {
280 return nearer + (ring[whole] - nearer) * fraction;
285 const size_t capacity = ring.
capacity();
286 const double clamped = std::clamp(
position, 0.0,
static_cast<double>(capacity - 1));
289 const auto whole =
static_cast<size_t>(clamped);
290 const double fraction = clamped -
static_cast<double>(whole);
291 const double nearer = ring[whole];
292 if (fraction <= 0.0 || whole + 1 >= capacity) {
295 return nearer + (ring[whole + 1] - nearer) * fraction;
298 const size_t taps = std::min(order + 1, capacity);
299 std::array<double, 8> weights {};
301 const auto centre =
static_cast<size_t>(clamped);
302 const size_t before = (taps - 1) / 2;
303 const size_t first = std::min(centre > before ? centre - before : 0, capacity - taps);
308 for (
size_t k = 0; k < taps; ++k) {
309 sum += weights[k] * ring[first + k];
316 auto audio = std::dynamic_pointer_cast<AudioBuffer>(
buffer);
321 const bool use_step =
static_cast<bool>(
m_step);
323 if (!use_step && !use_kernel && !
m_combine) {
327 auto& data = audio->get_data();
328 const size_t samples = data.size();
329 const size_t taps =
m_taps.size();
330 if (samples == 0 || taps == 0) {
335 auto& ring = *state.
ring;
341 if (state.
values.size() != taps) {
342 state.
values.assign(taps, 0.0);
343 state.
window.assign(taps + 1, 0.0);
345 state.
lag_now.assign(taps, 1.0);
350 const bool foreign = source &&
static_cast<const Buffer*
>(source.get()) !=
buffer.get();
355 auto& source_ring = source->get_history_buffer();
356 if (source_ring.capacity() <
m_capacity + samples) {
359 read_ring = &source_ring;
360 offset =
static_cast<double>(source->get_write_count()) -
static_cast<double>(state.
elapsed);
365 const auto seconds =
static_cast<double>(state.
elapsed) /
rate;
366 const double lowest = foreign ? std::max(1.0,
static_cast<double>(samples)) : 1.0;
367 const double highest = std::max(
static_cast<double>((foreign ?
m_capacity : ring.capacity()) - extra), lowest);
369 for (
size_t t = 0; t < taps; ++t) {
371 state.
lag_now[t] = std::clamp(raw, lowest, highest);
378 const double inverse = 1.0 /
static_cast<double>(samples);
379 const bool plain_read = !foreign &&
m_order <= 1;
381 for (
size_t i = 0; i < samples; ++i) {
382 const double ramp =
static_cast<double>(i + 1) * inverse;
383 const double shift = foreign ?
offset -
static_cast<double>(i) : 0.0;
385 for (
size_t t = 0; t < taps; ++t) {
390 const std::span<const double> tap_values(state.
values);
391 const std::span<double> coefs(state.
coefs);
396 const auto [step_out, step_carry] =
m_step(data[i], tap_values, coefs);
399 }
else if (use_kernel) {
400 state.
window[0] = data[i];
416 if (
auto owner = std::dynamic_pointer_cast<FeedbackBuffer>(
buffer)) {
417 owner->mark_written(samples);
Digital filter coefficient design for MayaFlux::Kinesis.
std::optional< glm::vec3 > position
std::shared_ptr< NetworkGeometryBuffer > buffer
std::vector< float > * out
virtual std::vector< double > & get_data()
Gets mutable access to the buffer's underlying audio data.
virtual uint32_t get_num_samples() const
Gets the current capacity of the audio buffer.
std::shared_ptr< BufferProcessor > m_default_processor
Default audio transformation processor for this buffer.
std::shared_ptr< BufferProcessingChain > get_processing_chain() override
Gets the audio transformation chain attached to this buffer.
Concrete audio implementation of the Buffer interface for double-precision audio data.
Backend-agnostic interface for sequential data storage and transformation.
void process_default() override
Applies the default audio transformation to the buffer's data.
FeedbackBuffer(uint32_t channel_id, uint32_t num_samples, float feedback=0.5F, uint32_t feed_samples=512)
void set_feedback(float amount)
Memory::HistoryBuffer< double > m_history
std::shared_ptr< BufferProcessor > create_default_processor() override
Creates a default audio transformation processor for this buffer type.
void set_feed_samples(uint32_t samples)
std::shared_ptr< AudioBuffer > clone_to(uint32_t channel) override
Clone for another channel with its own ring and its own processor state.
FeedbackProcessor(float feedback=0.5F, uint32_t feed_samples=512)
Single tap, output = input + feedback * tap.
std::function< Step(double, std::span< const double >, std::span< double >)> CombineStep
Combiner returning an output and a carried value.
std::shared_ptr< FeedbackProcessor > clone() const
Same configuration, no attached state.
std::vector< Tap > m_taps
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is detached from a buffer.
void set_lag_source(const std::shared_ptr< FeedbackBuffer > &source)
Read the taps from another FeedbackBuffer's ring.
std::weak_ptr< FeedbackBuffer > m_lag_source
static double read_at(const Memory::HistoryBuffer< double > &ring, double position, size_t order) noexcept
void set_combine(Combine combine)
std::unordered_map< const Buffer *, State > m_states
void set_lag(size_t tap, Kinesis::TimeMap seconds, double max_seconds)
Drive one tap's lag from a TimeMap.
void set_interpolation(size_t order)
Interpolation used for fractional lags.
std::vector< double > m_coefficients
void processing_function(const std::shared_ptr< Buffer > &buffer) override
The core processing function that must be implemented by derived classes.
void recompute_capacity()
void set_feed_samples(uint32_t samples)
Lag of the first tap, in samples.
State & state_for(const std::shared_ptr< Buffer > &buffer)
std::function< double(double, std::span< const double >, std::span< double >)> Combine
Combiner returning one value, written to the buffer and carried.
std::span< double > coefficients(const std::shared_ptr< Buffer > &buffer)
The coefficient array of one attached buffer, empty if it is not attached.
void set_lags(const std::vector< double > &lags)
Replace all taps with fixed lags in samples.
void set_coefficients(std::vector< double > coefficients)
Set the initial coefficient array and reset every attached buffer's copy.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
bool is_compatible_with(const std::shared_ptr< Buffer > &buffer) const override
Checks if this processor can handle the specified buffer type.
static double read(const Memory::HistoryBuffer< double > &ring, double lag)
std::function< double(std::span< double >, std::span< double >)> WindowKernel
A function of one window and one array, as in Kinesis::Discrete::Kernels.
void resize(size_t new_capacity)
Resize buffer capacity.
size_t capacity() const
Get buffer capacity.
History buffer for difference equations and recursive relations.
uint64_t s_registered_sample_rate
Global default sample rate.
@ Configuration
Configuration and parameter updates.
@ Buffers
Buffers, Managers, processors and processing chains.
void lagrange_weights(double delay, std::span< double > weights) noexcept
Lagrange interpolation weights written to an array, without allocating.
std::vector< double > coefs
std::vector< double > lag_prev
Memory::HistoryBuffer< double > * ring
std::vector< double > values
Memory::HistoryBuffer< double > own
std::vector< double > window
std::vector< double > lag_now
Typed, composable, stateless callable from domain D to range R.