Sound Byte Libs 0.5.1-121-g3358a44
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
segment.hpp
Go to the documentation of this file.
1// sbl/dsp/widgets/segment.hpp — Multi-segment envelope generator (float)
2//
3// SegmentGenerator advances through a sequence of timed segments, each
4// interpolating between a start level and a target level with a curve-shaped
5// phase. An optional sustain index causes the generator to hold after that
6// segment completes, resuming on gate_off().
7//
8// The curve engine is selected at compile time via template parameter —
9// zero runtime overhead, no vtables.
10//
11// All values are float: levels [0.0, 1.0], curve [-1.0, 1.0], time in ms.
12//
13// See FDP-014 and FDP-016 for design rationale.
14
15#ifndef SBL_DSP_WIDGETS_SEGMENT_HPP_
16#define SBL_DSP_WIDGETS_SEGMENT_HPP_
17
18#include <cstdint>
19
22
23namespace sbl::dsp::widgets {
24
25// ─── Segment ──────────────────────────────────────────────────────────
26
27struct Segment {
28 float target; // Target level [0.0, 1.0]
29 float curve; // Shape: -1.0 = concave, 0.0 = linear, 1.0 = convex
30 float time_ms; // Duration in milliseconds (0 = instant)
31};
32
33// ─── State ────────────────────────────────────────────────────────────
34
35enum class SegmentState : uint8_t {
36 Idle, // Output held at 0, waiting for gate_on
37 Running, // Advancing through a timed segment
38 Sustain, // Holding level until gate_off
39 Complete // All segments finished, output held at final level
40};
41
42// ─── SegmentGenerator ─────────────────────────────────────────────────
43//
44// Template on CurveEngine for compile-time selection of curve shaping.
45// Default is FloatRationalWarp (zero flash, analytical, good character).
46
47template<typename CurveEngine = primitives::FloatDefaultWarp>
49public:
50 /// @note All public methods are ISR-safe — bounded computation, no I/O.
51
52 static constexpr uint8_t MAX_SEGMENTS = 8;
53
54 // ── Configuration ─────────────────────────────────────────────
55
56 /// Configure the segment sequence.
57 /// @param segments Array of segments (copied internally, up to MAX_SEGMENTS)
58 /// @param count Number of segments
59 /// @param sustain_index Segment index to hold at (-1 = no sustain)
60 void configure(const Segment* segments, uint8_t count, int8_t sustain_index = -1) {
61 segment_count_ = (count > MAX_SEGMENTS) ? MAX_SEGMENTS : count;
62 for (uint8_t i = 0; i < segment_count_; ++i) {
63 segments_[i] = segments[i];
64 }
65 sustain_index_ = sustain_index;
66 reset();
67 }
68
69 // ── Gate control ──────────────────────────────────────────────
70
71 /// Start the envelope from segment 0. If already running, restarts
72 /// from the current level (retrigger behavior).
73 void gate_on() {
74 if (segment_count_ == 0) return;
75 gate_strength_ = 1.0f;
76 prev_gate_ = 1.0f;
77 start_level_ = (state_ == SegmentState::Idle) ? 0.0f : level_;
78 state_ = SegmentState::Running;
79 begin_segment(0);
80 }
81
82 /// Start with velocity — sets gate_strength to the given value.
83 void gate_on(float velocity) {
84 if (segment_count_ == 0) return;
85 gate_strength_ = velocity;
86 prev_gate_ = velocity;
87 start_level_ = (state_ == SegmentState::Idle) ? 0.0f : level_;
88 state_ = SegmentState::Running;
89 begin_segment(0);
90 }
91
92 /// Release the envelope. If in sustain, jumps to the next segment
93 /// (typically release). If running pre-sustain, jumps to release.
94 /// If no sustain configured, does nothing.
95 void gate_off() {
96 gate_strength_ = 0.0f;
97 prev_gate_ = 0.0f;
98
99 if (sustain_index_ < 0) return;
100
101 uint8_t release_index = static_cast<uint8_t>(sustain_index_ + 1);
102 if (release_index >= segment_count_) return;
103
104 if (state_ == SegmentState::Sustain ||
105 (state_ == SegmentState::Running && current_index_ <= sustain_index_)) {
106 start_level_ = level_;
107 state_ = SegmentState::Running;
108 begin_segment(release_index);
109 }
110 }
111
112 /// Accept a normalized gate signal. Detects edges to trigger/release.
113 /// The gate value is stored for strength access (velocity/amplitude).
114 void set_gate_su(float gate) {
115 if (prev_gate_ == 0.0f && gate > 0.0f) {
116 gate_strength_ = gate;
117 prev_gate_ = gate;
118 // Rising edge — trigger attack
119 if (segment_count_ == 0) return;
120 start_level_ = (state_ == SegmentState::Idle) ? 0.0f : level_;
121 state_ = SegmentState::Running;
122 begin_segment(0);
123 return;
124 }
125 if (prev_gate_ > 0.0f && gate == 0.0f) {
126 gate_strength_ = 0.0f;
127 prev_gate_ = 0.0f;
128 // Falling edge — release
129 if (sustain_index_ < 0) return;
130 uint8_t release_index = static_cast<uint8_t>(sustain_index_ + 1);
131 if (release_index >= segment_count_) return;
132 if (state_ == SegmentState::Sustain ||
133 (state_ == SegmentState::Running && current_index_ <= sustain_index_)) {
134 start_level_ = level_;
135 state_ = SegmentState::Running;
136 begin_segment(release_index);
137 }
138 return;
139 }
140 prev_gate_ = gate;
141 }
142
143 /// Retrigger: restart from current level without requiring gate_off first.
144 void retrigger() {
145 if (segment_count_ == 0) return;
146 start_level_ = level_;
147 state_ = SegmentState::Running;
148 begin_segment(0);
149 }
150
151 // ── Processing ────────────────────────────────────────────────
152
153 /// Fill output buffer with envelope levels (float [0.0, 1.0]).
154 void process(float* out, uint16_t frames) {
155 for (uint16_t i = 0; i < frames; ++i) {
156 if (state_ == SegmentState::Running) {
157 float next_phase = phase_ + phase_increment_;
158 if (next_phase >= 1.0f) {
159 // Segment complete — snap to target
160 level_ = segments_[current_index_].target;
161 start_level_ = level_;
162 advance_to_next();
163 } else {
164 phase_ = next_phase;
165 level_ = compute_level(phase_);
166 }
167 }
168 out[i] = level_;
169 }
170 }
171
172 // ── Queries ───────────────────────────────────────────────────
173
174 float level() const { return level_; }
175 float gate_strength() const { return gate_strength_; }
176
177 bool active() const {
178 return state_ != SegmentState::Idle && state_ != SegmentState::Complete;
179 }
180
181 SegmentState state() const { return state_; }
182 uint8_t current_segment_index() const { return current_index_; }
183
184private:
185 // ── Segment storage ───────────────────────────────────────────
186 Segment segments_[MAX_SEGMENTS] = {};
187 uint8_t segment_count_ = 0;
188 int8_t sustain_index_ = -1;
189
190 // ── Runtime state ─────────────────────────────────────────────
191 uint8_t current_index_ = 0;
193 float level_ = 0.0f;
194 float start_level_ = 0.0f;
195 float phase_ = 0.0f;
196 float phase_increment_ = 0.0f;
197 float prev_gate_ = 0.0f;
198 float gate_strength_ = 0.0f;
199
200 // ── Internal helpers ──────────────────────────────────────────
201
202 void reset() {
203 state_ = SegmentState::Idle;
204 level_ = 0.0f;
205 start_level_ = 0.0f;
206 current_index_ = 0;
207 phase_ = 0.0f;
208 phase_increment_ = 0.0f;
209 prev_gate_ = 0.0f;
210 gate_strength_ = 0.0f;
211 }
212
213 void begin_segment(uint8_t index) {
214 current_index_ = index;
215 phase_ = 0.0f;
216
217 // Instant segment: snap to target and advance
218 if (segments_[index].time_ms <= 0.0f) {
219 level_ = segments_[index].target;
220 start_level_ = level_;
221 advance_to_next();
222 return;
223 }
224
225 float time_samples = segments_[index].time_ms * 0.001f * types::SAMPLE_RATE_F;
226 phase_increment_ = 1.0f / time_samples;
227 }
228
229 void advance_to_next() {
230 // Check for sustain hold
231 if (current_index_ == sustain_index_) {
232 state_ = SegmentState::Sustain;
233 return;
234 }
235
236 // Move to next segment
237 uint8_t next = current_index_ + 1;
238 if (next >= segment_count_) {
239 state_ = SegmentState::Complete;
240 return;
241 }
242
243 begin_segment(next);
244 }
245
246 float compute_level(float phase) const {
247 float warped = CurveEngine::warp(phase, segments_[current_index_].curve);
248 return start_level_ + (segments_[current_index_].target - start_level_) * warped;
249 }
250};
251
252} // namespace sbl::dsp::widgets
253
254#endif // SBL_DSP_WIDGETS_SEGMENT_HPP_
void configure(const Segment *segments, uint8_t count, int8_t sustain_index=-1)
Definition segment.hpp:60
void process(float *out, uint16_t frames)
Fill output buffer with envelope levels (float [0.0, 1.0]).
Definition segment.hpp:154
void retrigger()
Retrigger: restart from current level without requiring gate_off first.
Definition segment.hpp:144
void gate_on(float velocity)
Start with velocity — sets gate_strength to the given value.
Definition segment.hpp:83
static constexpr uint8_t MAX_SEGMENTS
Definition segment.hpp:52
Fixed-point constants and audio sample types.
float SAMPLE_RATE_F
Definition fixed.hpp:17
Black boxes with _su ports.
Definition channel.hpp:49
Float warp engines for envelope/parameter shaping.