Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
reverse_buffer.hpp
Go to the documentation of this file.
1#pragma once
2
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <cstddef>
10#include <limits>
11#include <new>
12#include <stdexcept>
13#include <type_traits>
14#include <utility>
15#include <vector>
16
17namespace pulp::signal {
18
20
29template <typename SampleType = float> class ReverseBufferT {
30 public:
31 static_assert(std::is_floating_point_v<SampleType>);
32
33 static constexpr std::size_t kMinimumWindowSamples = 2;
34 static constexpr std::size_t kMaximumWindowSamples = 8u * 1024u * 1024u;
35
36 struct Config {
37 std::size_t window_samples = 1024;
40 std::size_t boundary_fade_samples = 0;
41 };
42
43 [[nodiscard]] bool prepare(std::size_t maximum_window_samples) {
44 if (!valid_maximum(maximum_window_samples) ||
45 config_.window_samples > maximum_window_samples)
46 return false;
47
48 std::array<std::vector<SampleType>, 2> replacement;
49#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
50 try {
51#endif
52 replacement[0].assign(maximum_window_samples, SampleType{});
53 replacement[1].assign(maximum_window_samples, SampleType{});
54#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
55 } catch (const std::bad_alloc&) {
56 return false;
57 } catch (const std::length_error&) {
58 return false;
59 }
60#endif
61
62 buffers_ = std::move(replacement);
63 maximum_window_samples_ = maximum_window_samples;
64 prepared_ = true;
66 return true;
67 }
68
71 [[nodiscard]] bool configure(Config replacement) noexcept {
72 if (!valid_config(replacement) ||
73 (prepared_ && replacement.window_samples > maximum_window_samples_))
74 return false;
75 config_ = replacement;
77 return true;
78 }
79
80 Config config() const noexcept {
81 return config_;
82 }
83 bool prepared() const noexcept {
84 return prepared_;
85 }
86 std::size_t maximum_window_samples() const noexcept {
87 return maximum_window_samples_;
88 }
89 std::size_t retained_bytes() const noexcept {
90 return prepared_ ? 2u * maximum_window_samples_ * sizeof(SampleType) : 0u;
91 }
92
93 int latency_samples() const noexcept {
94 return prepared_ && !bypassed_ ? static_cast<int>(config_.window_samples) : 0;
95 }
96 int startup_latency_samples() const noexcept {
97 return prepared_ ? static_cast<int>(config_.window_samples) : 0;
98 }
99 int tail_samples() const noexcept {
100 if (!prepared_ || bypassed_)
101 return 0;
102 return static_cast<int>(2u * config_.window_samples - 1u);
103 }
104
105 ReverseBufferState state() const noexcept {
106 if (!prepared_)
108 if (bypassed_)
111 }
112
113 std::size_t position() const noexcept {
114 return position_;
115 }
116
120 void set_bypassed(bool bypassed) noexcept {
121 if (bypassed_ == bypassed)
122 return;
123 bypassed_ = bypassed;
125 }
126 bool bypassed() const noexcept {
127 return bypassed_;
128 }
129
133 void discard_history() noexcept {
134 capture_buffer_ = 0;
135 playback_buffer_ = 1;
136 position_ = 0;
137 playback_ready_ = false;
138 }
139
140 void reset() noexcept {
142 }
143
144 SampleType process_sample(SampleType input) noexcept {
145 if (!std::isfinite(static_cast<double>(input))) {
147 return SampleType{};
148 }
149 if (bypassed_)
150 return input;
151 if (!prepared_)
152 return SampleType{};
153
154 buffers_[capture_buffer_][position_] = input;
155 SampleType output{};
156 if (playback_ready_) {
157 const auto reverse_position = config_.window_samples - 1u - position_;
158 const auto gain = static_cast<SampleType>(boundary_gain(position_));
159 output = buffers_[playback_buffer_][reverse_position] * gain;
160 if (!std::isfinite(static_cast<double>(output))) {
162 return SampleType{};
163 }
164 }
165
166 ++position_;
167 if (position_ == config_.window_samples) {
168 playback_buffer_ = capture_buffer_;
169 capture_buffer_ = 1u - capture_buffer_;
170 position_ = 0;
171 playback_ready_ = true;
172 }
173 return output;
174 }
175
176 void process_block(const SampleType* input, SampleType* output,
177 std::size_t sample_count) noexcept {
178 if (input == nullptr || output == nullptr)
179 return;
180 for (std::size_t i = 0; i < sample_count; ++i) {
181 const auto sample = input[i];
182 output[i] = process_sample(sample);
183 }
184 }
185
186 private:
187 static bool valid_maximum(std::size_t samples) noexcept {
188 return samples >= kMinimumWindowSamples && samples <= kMaximumWindowSamples;
189 }
190
191 static bool valid_config(Config config) noexcept {
192 if (!valid_maximum(config.window_samples) ||
194 return false;
196 }
197
198 double boundary_gain(std::size_t playback_position) const noexcept {
199 const auto fade = config_.boundary_fade_samples;
200 if (fade == 0u)
201 return 1.0;
202 const auto from_end = config_.window_samples - 1u - playback_position;
203 const auto nearest_boundary = std::min(playback_position, from_end);
204 if (nearest_boundary >= fade)
205 return 1.0;
206 const double phase = static_cast<double>(nearest_boundary) / static_cast<double>(fade);
207 constexpr double kPi = 3.14159265358979323846264338327950288;
208 return 0.5 * (1.0 - std::cos(kPi * phase));
209 }
210
211 Config config_{};
212 std::array<std::vector<SampleType>, 2> buffers_{};
213 std::size_t maximum_window_samples_ = 0;
214 std::size_t capture_buffer_ = 0;
215 std::size_t playback_buffer_ = 1;
216 std::size_t position_ = 0;
217 bool playback_ready_ = false;
218 bool prepared_ = false;
219 bool bypassed_ = false;
220};
221
224
225} // namespace pulp::signal
A mono streaming reverse-window processor.
Definition reverse_buffer.hpp:29
int latency_samples() const noexcept
Definition reverse_buffer.hpp:93
SampleType process_sample(SampleType input) noexcept
Definition reverse_buffer.hpp:144
static constexpr std::size_t kMinimumWindowSamples
Definition reverse_buffer.hpp:33
bool prepared() const noexcept
Definition reverse_buffer.hpp:83
bool configure(Config replacement) noexcept
Publishes the complete transport configuration.
Definition reverse_buffer.hpp:71
void process_block(const SampleType *input, SampleType *output, std::size_t sample_count) noexcept
Definition reverse_buffer.hpp:176
bool bypassed() const noexcept
Definition reverse_buffer.hpp:126
std::size_t maximum_window_samples() const noexcept
Definition reverse_buffer.hpp:86
ReverseBufferState state() const noexcept
Definition reverse_buffer.hpp:105
void reset() noexcept
Definition reverse_buffer.hpp:140
std::size_t position() const noexcept
Definition reverse_buffer.hpp:113
std::size_t retained_bytes() const noexcept
Definition reverse_buffer.hpp:89
Config config() const noexcept
Definition reverse_buffer.hpp:80
bool prepare(std::size_t maximum_window_samples)
Definition reverse_buffer.hpp:43
static constexpr std::size_t kMaximumWindowSamples
Definition reverse_buffer.hpp:34
void discard_history() noexcept
Discards capture/playback state in constant time without changing the prepared capacity,...
Definition reverse_buffer.hpp:133
void set_bypassed(bool bypassed) noexcept
Hard transport bypass.
Definition reverse_buffer.hpp:120
int startup_latency_samples() const noexcept
Definition reverse_buffer.hpp:96
int tail_samples() const noexcept
Definition reverse_buffer.hpp:99
Definition additive_bank.hpp:218
ReverseBufferT< float > ReverseBuffer
Definition reverse_buffer.hpp:222
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
ReverseBufferT< double > ReverseBuffer64
Definition reverse_buffer.hpp:223
ReverseBufferState
Definition reverse_buffer.hpp:19
@ bypassed
Definition reverse_buffer.hpp:19
@ unprepared
Definition reverse_buffer.hpp:19
@ running
Definition reverse_buffer.hpp:19
@ priming
Definition reverse_buffer.hpp:19
@ running
Maintain running sums as frames arrive; the latch only divides.
Definition freeze_hold.hpp:136
Definition reverse_buffer.hpp:36
std::size_t window_samples
Definition reverse_buffer.hpp:37
std::size_t boundary_fade_samples
Zero applies an exact rectangular window.
Definition reverse_buffer.hpp:40