Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
breakpoint_envelope.hpp
Go to the documentation of this file.
1#pragma once
2
21
23
24#include <algorithm>
25#include <array>
26#include <cmath>
27#include <cstddef>
28#include <cstdint>
29#include <limits>
30#include <span>
31#include <type_traits>
32
33namespace pulp::signal {
34
45
46template <typename SampleType = float> struct BreakpointEnvelopePointT {
48 double time_ms = 0.0;
50 SampleType value = SampleType{0};
53};
54
57
58template <typename SampleType = float, std::size_t MaxPoints = 16> class BreakpointEnvelopeT {
59 static_assert(std::is_floating_point_v<SampleType>);
60 static_assert(MaxPoints >= 2);
61
62 public:
64
65 static constexpr double kDefaultSampleRate = 48000.0;
66 static constexpr double kMinSampleRate = 1.0;
67 static constexpr double kMaxSampleRate = 768000.0;
68 static constexpr double kMaxProgramTimeMs = 86400000.0; // one day
69 static constexpr std::uint32_t kLoopForever = std::numeric_limits<std::uint32_t>::max();
70
72 points_[0] = {};
73 points_[1] = {1.0, SampleType{1}, {}};
74 point_count_ = 2;
75 rebuild_sample_positions_();
76 reset();
77 }
78
79 [[nodiscard]] BreakpointEnvelopeStatus prepare(double sample_rate) noexcept {
80 if (!std::isfinite(sample_rate) || sample_rate < kMinSampleRate ||
83 sample_rate_ = sample_rate;
84 rebuild_sample_positions_();
85 reset();
87 }
88
91 [[nodiscard]] BreakpointEnvelopeStatus configure(std::span<const Point> points) noexcept {
92 if (points.size() < 2)
94 if (points.size() > MaxPoints)
96
97 double previous_time = 0.0;
98 for (std::size_t i = 0; i < points.size(); ++i) {
99 const auto& point = points[i];
100 if (!std::isfinite(point.time_ms) || point.time_ms < 0.0 ||
101 point.time_ms > kMaxProgramTimeMs)
103 if (i == 0 && point.time_ms != 0.0)
105 if (i > 0 && point.time_ms < previous_time)
107 if (!std::isfinite(static_cast<double>(point.value)))
109 previous_time = point.time_ms;
110 }
111
112 std::array<Point, MaxPoints> replacement{};
113 for (std::size_t i = 0; i < points.size(); ++i) {
114 replacement[i] = points[i];
115 replacement[i].curve_to_next = sanitize_modulation_curve(points[i].curve_to_next);
116 }
117 points_ = replacement;
118 point_count_ = points.size();
119 loop_enabled_ = false;
120 loop_repeat_count_ = 0;
121 rebuild_sample_positions_();
122 reset();
124 }
125
128 [[nodiscard]] BreakpointEnvelopeStatus set_loop(std::size_t first_point, std::size_t last_point,
129 std::uint32_t repeat_count) noexcept {
130 if (repeat_count == 0) {
131 loop_enabled_ = false;
132 loop_repeat_count_ = 0;
134 }
135 if (first_point >= last_point || last_point >= point_count_)
137 loop_first_point_ = first_point;
138 loop_last_point_ = last_point;
139 loop_repeat_count_ = repeat_count;
140 loop_enabled_ = true;
142 }
143
144 void clear_loop() noexcept {
145 loop_enabled_ = false;
146 loop_repeat_count_ = 0;
147 }
148
149 void trigger() noexcept {
150 active_ = true;
151 pending_loop_restart_ = false;
152 current_segment_ = 0;
153 segment_position_ = 0;
154 loops_completed_ = 0;
155 value_ = points_[0].value;
156 skip_zero_length_segments_();
157 }
158
159 void reset() noexcept {
160 active_ = false;
161 pending_loop_restart_ = false;
162 current_segment_ = 0;
163 segment_position_ = 0;
164 loops_completed_ = 0;
165 value_ = points_[0].value;
166 }
167
168 SampleType next() noexcept {
169 if (!active_)
170 return value_;
171 if (pending_loop_restart_) {
172 restart_loop_();
173 skip_zero_length_segments_();
174 }
175 if (!active_)
176 return value_;
177
178 const std::uint64_t length = segment_length_samples_(current_segment_);
179 ++segment_position_;
180 const SampleType progress = length == 0 ? SampleType{1}
181 : static_cast<SampleType>(segment_position_) /
182 static_cast<SampleType>(length);
183 value_ = interpolate_modulation_curve(points_[current_segment_].value,
184 points_[current_segment_ + 1].value, progress,
185 points_[current_segment_].curve_to_next);
186 if (segment_position_ >= length) {
187 finish_segment_();
188 if (active_ && !pending_loop_restart_)
189 skip_zero_length_segments_();
190 }
191 return value_;
192 }
193
194 void process(std::span<SampleType> output) noexcept {
195 for (auto& sample : output)
196 sample = next();
197 }
198
199 SampleType current() const noexcept {
200 return value_;
201 }
202 bool active() const noexcept {
203 return active_;
204 }
205 std::size_t point_count() const noexcept {
206 return point_count_;
207 }
208 std::size_t current_segment() const noexcept {
209 return current_segment_;
210 }
211 std::uint64_t segment_position_samples() const noexcept {
212 return segment_position_;
213 }
214 std::uint32_t loops_completed() const noexcept {
215 return loops_completed_;
216 }
217 double sample_rate() const noexcept {
218 return sample_rate_;
219 }
220 std::span<const Point> points() const noexcept {
221 return {points_.data(), point_count_};
222 }
223
224 private:
225 std::uint64_t time_to_samples_(double time_ms) const noexcept {
226 const long double samples =
227 static_cast<long double>(time_ms) * static_cast<long double>(sample_rate_) / 1000.0L;
228 return static_cast<std::uint64_t>(std::llround(samples));
229 }
230
231 void rebuild_sample_positions_() noexcept {
232 for (std::size_t i = 0; i < point_count_; ++i)
233 point_samples_[i] = time_to_samples_(points_[i].time_ms);
234 }
235
236 std::uint64_t segment_length_samples_(std::size_t segment) const noexcept {
237 return point_samples_[segment + 1] - point_samples_[segment];
238 }
239
240 bool should_repeat_loop_() const noexcept {
241 return loop_enabled_ && current_segment_ + 1 == loop_last_point_ &&
242 (loop_repeat_count_ == kLoopForever || loops_completed_ < loop_repeat_count_);
243 }
244
245 void finish_segment_() noexcept {
246 if (should_repeat_loop_()) {
247 if (loops_completed_ != kLoopForever)
248 ++loops_completed_;
249 pending_loop_restart_ = true;
250 return;
251 }
252
253 ++current_segment_;
254 segment_position_ = 0;
255 if (current_segment_ + 1 >= point_count_) {
256 active_ = false;
257 current_segment_ = point_count_ - 1;
258 return;
259 }
260 }
261
262 void restart_loop_() noexcept {
263 pending_loop_restart_ = false;
264 current_segment_ = loop_first_point_;
265 segment_position_ = 0;
266 value_ = points_[loop_first_point_].value;
267 }
268
269 void skip_zero_length_segments_() noexcept {
270 for (std::size_t visited = 0; active_ && visited < MaxPoints; ++visited) {
271 if (current_segment_ + 1 >= point_count_) {
272 active_ = false;
273 current_segment_ = point_count_ - 1;
274 value_ = points_[point_count_ - 1].value;
275 return;
276 }
277 if (segment_length_samples_(current_segment_) != 0)
278 return;
279 value_ = points_[current_segment_ + 1].value;
280 finish_segment_();
281 if (pending_loop_restart_)
282 restart_loop_();
283 }
284 if (active_) {
285 // A zero-time infinite loop has no observable duration. Quiesce
286 // rather than spinning or consuming unbounded callback work.
287 active_ = false;
288 pending_loop_restart_ = false;
289 }
290 }
291
292 std::array<Point, MaxPoints> points_{};
293 std::array<std::uint64_t, MaxPoints> point_samples_{};
294 std::size_t point_count_ = 0;
295 std::size_t current_segment_ = 0;
296 std::size_t loop_first_point_ = 0;
297 std::size_t loop_last_point_ = 1;
298 std::uint64_t segment_position_ = 0;
299 std::uint32_t loop_repeat_count_ = 0;
300 std::uint32_t loops_completed_ = 0;
301 double sample_rate_ = kDefaultSampleRate;
302 SampleType value_ = SampleType{0};
303 bool active_ = false;
304 bool loop_enabled_ = false;
305 bool pending_loop_restart_ = false;
306};
307
310
311} // namespace pulp::signal
Definition breakpoint_envelope.hpp:58
SampleType current() const noexcept
Definition breakpoint_envelope.hpp:199
std::size_t point_count() const noexcept
Definition breakpoint_envelope.hpp:205
double sample_rate() const noexcept
Definition breakpoint_envelope.hpp:217
void trigger() noexcept
Definition breakpoint_envelope.hpp:149
BreakpointEnvelopeStatus configure(std::span< const Point > points) noexcept
Replaces the complete program transactionally.
Definition breakpoint_envelope.hpp:91
static constexpr double kDefaultSampleRate
Definition breakpoint_envelope.hpp:65
std::uint64_t segment_position_samples() const noexcept
Definition breakpoint_envelope.hpp:211
bool active() const noexcept
Definition breakpoint_envelope.hpp:202
static constexpr double kMaxProgramTimeMs
Definition breakpoint_envelope.hpp:68
void clear_loop() noexcept
Definition breakpoint_envelope.hpp:144
void process(std::span< SampleType > output) noexcept
Definition breakpoint_envelope.hpp:194
BreakpointEnvelopeStatus set_loop(std::size_t first_point, std::size_t last_point, std::uint32_t repeat_count) noexcept
Repeats the range from first_point through last_point.
Definition breakpoint_envelope.hpp:128
SampleType next() noexcept
Definition breakpoint_envelope.hpp:168
BreakpointEnvelopeT() noexcept
Definition breakpoint_envelope.hpp:71
std::size_t current_segment() const noexcept
Definition breakpoint_envelope.hpp:208
BreakpointEnvelopeStatus prepare(double sample_rate) noexcept
Definition breakpoint_envelope.hpp:79
static constexpr double kMinSampleRate
Definition breakpoint_envelope.hpp:66
static constexpr std::uint32_t kLoopForever
Definition breakpoint_envelope.hpp:69
static constexpr double kMaxSampleRate
Definition breakpoint_envelope.hpp:67
std::uint32_t loops_completed() const noexcept
Definition breakpoint_envelope.hpp:214
std::span< const Point > points() const noexcept
Definition breakpoint_envelope.hpp:220
BreakpointEnvelopePointT< SampleType > Point
Definition breakpoint_envelope.hpp:63
void reset() noexcept
Definition breakpoint_envelope.hpp:159
A shared, allocation-free curve vocabulary for modulation segments.
Definition additive_bank.hpp:218
SampleType interpolate_modulation_curve(SampleType start, SampleType end, ProgressType progress, ModulationCurve curve={}) noexcept
Definition modulation_curve.hpp:97
ModulationCurve sanitize_modulation_curve(ModulationCurve curve) noexcept
Definition modulation_curve.hpp:42
@ invalid_sample_rate
Definition auto_ducked_send.hpp:18
@ i
Definition formant_filter_bank.hpp:20
BreakpointEnvelopeT< float > BreakpointEnvelope
Definition breakpoint_envelope.hpp:308
BreakpointEnvelopePointT< double > BreakpointEnvelopePoint64
Definition breakpoint_envelope.hpp:56
BreakpointEnvelopeT< double > BreakpointEnvelope64
Definition breakpoint_envelope.hpp:309
BreakpointEnvelopeStatus
Definition breakpoint_envelope.hpp:35
@ invalid_time
Definition breakpoint_envelope.hpp:39
@ invalid_value
Definition breakpoint_envelope.hpp:41
@ ok
Definition breakpoint_envelope.hpp:36
@ invalid_sample_rate
Definition breakpoint_envelope.hpp:42
@ too_few_points
Definition breakpoint_envelope.hpp:37
@ invalid_loop
Definition breakpoint_envelope.hpp:43
@ times_not_ordered
Definition breakpoint_envelope.hpp:40
@ too_many_points
Definition breakpoint_envelope.hpp:38
BreakpointEnvelopePointT< float > BreakpointEnvelopePoint
Definition breakpoint_envelope.hpp:55
@ progress
Definition finite_stretch_builder.hpp:30
Definition breakpoint_envelope.hpp:46
ModulationCurve curve_to_next
Definition breakpoint_envelope.hpp:52
float value
Definition breakpoint_envelope.hpp:50
double time_ms
Definition breakpoint_envelope.hpp:48
Curve strength is dimensionless and legal in [0, 1].
Definition modulation_curve.hpp:37