Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
sequencer_state_channel.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <atomic>
5#include <cstdint>
6#include <optional>
7#include <type_traits>
8
12
57namespace pulp::state {
58
59// ── Shared scalar types (dimension-independent) ──────────────────────────────
60using Epoch = std::uint64_t;
61using EngineSequence = std::uint64_t;
62using ClientSequence = std::uint64_t;
63using EditTransactionId = std::uint32_t;
64
69struct StepCell {
70 std::uint8_t flags = 0;
71 std::uint8_t velocity = 100; // 0..127
72 std::uint8_t probability = 127; // 0..127
73 std::int8_t pitch_offset = 0; // semitones
74 std::uint16_t gate_ticks = 24; // gate length in ticks
75 std::uint8_t ratchet = 1; // sub-hits per step
76 std::uint8_t reserved = 0;
77
78 static constexpr std::uint8_t kEnabledBit = 0x01;
79 constexpr bool enabled() const { return (flags & kEnabledBit) != 0; }
80};
81
86 std::uint64_t sample_time = 0;
87 double ppq_position = 0.0;
88 std::uint32_t block_index = 0;
89 std::uint8_t active_pattern = 0;
90 std::uint8_t active_step = 0;
91 std::uint8_t playing = 0; // bool as byte for trivial-copy stability
92 std::uint8_t reserved = 0;
93};
94static_assert(std::is_trivially_copyable_v<PlayheadState>,
95 "PlayheadState must be trivially copyable for SeqLock");
96
97// ── SequencerConfig — the compile-time dimension/type bundle ─────────────────
102template <std::size_t Lanes, std::size_t Steps, std::size_t Patterns,
103 class Cell = StepCell, class Playhead = PlayheadState,
104 std::size_t CommandDepth = 1024, std::size_t AppliedDepth = 1024>
106 static constexpr std::size_t lanes = Lanes;
107 static constexpr std::size_t steps = Steps;
108 static constexpr std::size_t patterns = Patterns;
109 static constexpr std::size_t command_depth = CommandDepth;
110 static constexpr std::size_t applied_depth = AppliedDepth;
112 using playhead_type = Playhead;
113
114 static_assert(Lanes >= 1 && Steps >= 1 && Patterns >= 1,
115 "dimensions must be >= 1");
116 static_assert(Lanes <= 255 && Steps <= 255 && Patterns <= 255,
117 "wire indices are uint8_t; dims are capped at 255");
118 static_assert(CommandDepth > 0 && AppliedDepth > 0, "FIFO depths must be > 0");
119 // NOTE: do NOT require trivially-default-constructible — the reference
120 // StepCell has default member initializers, so it is trivially copyable but
121 // not trivially default constructible. The operational contract is:
122 // trivially copyable (memcpy/TripleBuffer/FIFO), copy-assignable
123 // (TripleBuffer::write assigns), and cheaply default-constructible
124 // (SpscQueue::try_pop default-constructs the element on the audio thread).
125 static_assert(std::is_trivially_copyable_v<Cell> &&
126 std::is_copy_assignable_v<Cell> &&
127 std::is_default_constructible_v<Cell>,
128 "Cell must be trivially copyable, copy-assignable, and cheaply "
129 "default-constructible (its default ctor runs on audio-thread pop)");
130 static_assert(std::is_trivially_copyable_v<Playhead> &&
131 std::is_copy_assignable_v<Playhead>,
132 "Playhead must be trivially copyable + copy-assignable (SeqLock)");
133};
134
135// ── Reference dimensions (sized to the reference sequencer shape) ────────────
136inline constexpr std::uint8_t kPatternCount = 32;
137inline constexpr std::uint8_t kLaneCount = 12;
138inline constexpr std::uint8_t kStepCount = 32;
139
140// FIFO depths. Commands and echoes are small and drained every UI/audio tick;
141// these are generous relative to a realistic edit burst. Overflow is handled
142// (command submit fails; echo overflow forces a snapshot resync), never UB.
143inline constexpr std::size_t kCommandQueueCapacity = 1024;
144inline constexpr std::size_t kAppliedQueueCapacity = 1024;
145
146// ── Dirty-region descriptor (UI invalidation granularity) ────────────────────
155
158 std::uint8_t pattern = 0;
159 std::uint8_t lane = 0;
160 std::uint8_t first_step = 0;
161 std::uint8_t step_count = 0;
162};
163
164// ── UI -> audio: edit commands ───────────────────────────────────────────────
172
173enum class ClearScope : std::uint8_t { Cell, Lane, Pattern, All };
174
177enum class GesturePhase : std::uint8_t { None, Begin, Update, End, Cancel };
178
181template <class Config>
183 std::uint8_t pattern = 0;
184 std::uint8_t lane = 0;
185 std::uint8_t step = 0;
186 typename Config::cell_type cell{};
187};
188struct ClearEdit {
190 std::uint8_t pattern = 0;
191 std::uint8_t lane = 0;
192 std::uint8_t step = 0;
193};
195 std::uint8_t pattern = 0;
196 std::uint8_t lane = 0;
197 std::uint32_t seed = 0;
198 std::uint8_t density = 64; // 0..127 probability of a hit per step
199 std::uint8_t min_velocity = 64;
200 std::uint8_t max_velocity = 127;
201};
203 std::uint8_t pattern = 0;
204 std::uint8_t length = 0; // active step count; 0 = full (Config::steps)
205};
207 std::uint8_t pattern = 0;
208};
209
210template <class Config>
226
227// ── Authoritative state model ────────────────────────────────────────────────
228template <class Config>
230 std::uint8_t length = static_cast<std::uint8_t>(Config::steps); // active steps (<= Steps)
231 std::array<std::array<typename Config::cell_type, Config::steps>, Config::lanes> lanes{};
232};
233
236template <class Config>
237struct SnapshotT {
238 std::uint32_t schema_version = 1;
239 Epoch epoch = 0; // engine-controlled; monotonic per publish
240 EngineSequence engine_sequence = 0; // last applied edit folded into this snapshot
241 std::uint8_t active_pattern = 0;
242 std::uint8_t active_lane_count = static_cast<std::uint8_t>(Config::lanes);
243 std::uint8_t active_pattern_count = static_cast<std::uint8_t>(Config::patterns);
244 std::array<PatternStateT<Config>, Config::patterns> patterns{};
245};
246
247// ── audio -> UI: applied echoes ──────────────────────────────────────────────
248
252template <class Config>
254 std::uint8_t pattern = 0;
255 std::uint8_t lane = 0;
256 std::uint8_t first_step = 0;
257 std::uint8_t step_count = 0;
258 std::array<typename Config::cell_type, Config::steps> cells{};
259};
260
267
268template <class Config>
285
286// ── The channel ──────────────────────────────────────────────────────────────
300template <class Config>
302public:
303 using ConfigType = Config;
310 using Playhead = typename Config::playhead_type;
311
313
314 // ── UI / message thread ──────────────────────────────────────────────────
315
318 bool ui_try_submit(const Command& command) noexcept {
319 return commands_.try_push(command);
320 }
321
324 std::optional<AppliedEdit> ui_try_pop_applied() noexcept {
325 return applied_.try_pop();
326 }
327
329 Playhead ui_read_playhead() const noexcept { return playhead_.read(); }
330
337 const Snapshot& ui_read_latest_snapshot() noexcept { return snapshot_.read(); }
338
344 return resync_epoch_.load(std::memory_order_acquire);
345 }
346
348 return applied_.telemetry();
349 }
351 return commands_.telemetry();
352 }
353
357 bool debug_claim_ui_consumer() noexcept {
358#ifndef NDEBUG
359 bool expected = false;
360 return ui_consumer_claimed_.compare_exchange_strong(expected, true,
361 std::memory_order_acq_rel);
362#else
363 return true;
364#endif
365 }
367#ifndef NDEBUG
368 ui_consumer_claimed_.store(false, std::memory_order_release);
369#endif
370 }
371
372 // ── Engine (audio) thread ─────────────────────────────────────────────────
373
375 std::optional<Command> audio_try_pop_command() noexcept {
376 return commands_.try_pop();
377 }
378
384 bool audio_try_publish_applied(const AppliedEdit& edit) noexcept {
385 return applied_.try_push(edit);
386 }
387
390 void audio_publish_snapshot(const Snapshot& snapshot) noexcept {
391 snapshot_.write(snapshot);
392 }
393
395 void audio_publish_playhead(const Playhead& playhead) noexcept {
396 playhead_.write(playhead);
397 }
398
403 void audio_mark_resync_required(Epoch epoch) noexcept {
404 Epoch prev = resync_epoch_.load(std::memory_order_relaxed);
405 while (epoch > prev &&
406 !resync_epoch_.compare_exchange_weak(prev, epoch,
407 std::memory_order_release,
408 std::memory_order_relaxed)) {
409 // prev reloaded by compare_exchange_weak; retry until we win or
410 // another (larger) value already satisfies the bar.
411 }
412 }
413
414private:
417 runtime::TripleBuffer<Snapshot> snapshot_{};
418 runtime::SeqLock<Playhead> playhead_{};
419 std::atomic<Epoch> resync_epoch_{0};
420#ifndef NDEBUG
421 std::atomic<bool> ui_consumer_claimed_{false};
422#endif
423};
424
425// ── Reference config + aliases ───────────────────────────────────────────────
433
441
442// ── Frozen layout tripwires (source/API compat only — NOT byte persistence) ──
443// These flag accidental layout drift of the reference transport types. They are
444// NOT a promise that a Snapshot may be memcpy'd to disk/IPC; persist via a codec.
445static_assert(std::is_trivially_copyable_v<StepCell>);
446static_assert(std::is_trivially_copyable_v<DirtyRegion>);
447static_assert(std::is_trivially_copyable_v<StepEditCommand> &&
448 std::is_copy_assignable_v<StepEditCommand> &&
449 std::is_default_constructible_v<StepEditCommand>,
450 "Command must be trivially copyable + copy-assignable + default-constructible "
451 "for the SPSC FIFO");
452static_assert(std::is_trivially_copyable_v<AppliedEdit> &&
453 std::is_copy_assignable_v<AppliedEdit> &&
454 std::is_default_constructible_v<AppliedEdit>,
455 "AppliedEdit must be trivially copyable + copy-assignable + default-constructible "
456 "for the SPSC FIFO");
457static_assert(std::is_trivially_copyable_v<Snapshot> &&
458 std::is_copy_assignable_v<Snapshot> &&
459 std::is_default_constructible_v<Snapshot>,
460 "Snapshot must be trivially copyable + copy-assignable for TripleBuffer");
461
462} // namespace pulp::state
Definition spsc_queue.hpp:23
Ownership: the engine (audio) thread is the single reader of the command queue and the single writer ...
Definition sequencer_state_channel.hpp:301
Epoch ui_resync_required_epoch() const noexcept
The snapshot epoch the UI must reach before trusting incremental echoes again.
Definition sequencer_state_channel.hpp:343
runtime::SpscQueueTelemetry ui_applied_telemetry() const noexcept
Definition sequencer_state_channel.hpp:347
runtime::SpscQueueTelemetry ui_command_telemetry() const noexcept
Definition sequencer_state_channel.hpp:350
PatternStateT< Config > PatternState
Definition sequencer_state_channel.hpp:305
bool debug_claim_ui_consumer() noexcept
Debug-only single-UI-consumer guard.
Definition sequencer_state_channel.hpp:357
typename Config::playhead_type Playhead
Definition sequencer_state_channel.hpp:310
void audio_publish_snapshot(const Snapshot &snapshot) noexcept
Publish the full authoritative snapshot (initial load / preset / bulk / resync).
Definition sequencer_state_channel.hpp:390
Playhead ui_read_playhead() const noexcept
Coherent read of the latest playhead. Lock-free, allocation-free.
Definition sequencer_state_channel.hpp:329
bool ui_try_submit(const Command &command) noexcept
Submit an edit command.
Definition sequencer_state_channel.hpp:318
StepEditCommandT< Config > Command
Definition sequencer_state_channel.hpp:306
SnapshotT< Config > Snapshot
Definition sequencer_state_channel.hpp:304
SetCellEditT< Config > SetCellEdit
Definition sequencer_state_channel.hpp:309
void debug_release_ui_consumer() noexcept
Definition sequencer_state_channel.hpp:366
Config ConfigType
Definition sequencer_state_channel.hpp:303
const Snapshot & ui_read_latest_snapshot() noexcept
The latest full snapshot.
Definition sequencer_state_channel.hpp:337
std::optional< Command > audio_try_pop_command() noexcept
Pop the next pending edit command to apply. Lock-free, allocation-free.
Definition sequencer_state_channel.hpp:375
StepRangeAppliedT< Config > StepRangeApplied
Definition sequencer_state_channel.hpp:308
void audio_publish_playhead(const Playhead &playhead) noexcept
Publish the latest playhead. Lock-free, allocation-free; call per block.
Definition sequencer_state_channel.hpp:395
void audio_mark_resync_required(Epoch epoch) noexcept
Signal that incremental echoes were lost and the UI must resync to at least epoch.
Definition sequencer_state_channel.hpp:403
bool audio_try_publish_applied(const AppliedEdit &edit) noexcept
Publish an applied echo.
Definition sequencer_state_channel.hpp:384
AppliedEditT< Config > AppliedEdit
Definition sequencer_state_channel.hpp:307
std::optional< AppliedEdit > ui_try_pop_applied() noexcept
Pop the next applied echo to replay onto the UI render copy, or nullopt.
Definition sequencer_state_channel.hpp:324
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
DirtyKind
Definition sequencer_state_channel.hpp:147
@ FullSnapshot
Definition sequencer_state_channel.hpp:149
@ Cell
Definition sequencer_state_channel.hpp:153
@ Lane
Definition sequencer_state_channel.hpp:151
@ StepRange
Definition sequencer_state_channel.hpp:152
@ Pattern
Definition sequencer_state_channel.hpp:150
@ None
Definition sequencer_state_channel.hpp:148
SnapshotT< ReferenceSequencerConfig > Snapshot
Definition sequencer_state_channel.hpp:435
std::uint64_t ClientSequence
Definition sequencer_state_channel.hpp:62
SetCellEditT< ReferenceSequencerConfig > SetCellEdit
Definition sequencer_state_channel.hpp:440
constexpr std::uint8_t kLaneCount
Definition sequencer_state_channel.hpp:137
@ None
Definition modulation_lane.hpp:54
ClearScope
Definition sequencer_state_channel.hpp:173
@ Cell
Definition sequencer_state_channel.hpp:173
@ All
Definition sequencer_state_channel.hpp:173
AppliedEditT< ReferenceSequencerConfig > AppliedEdit
Definition sequencer_state_channel.hpp:438
constexpr std::uint8_t kPatternCount
Definition sequencer_state_channel.hpp:136
std::uint32_t EditTransactionId
Definition sequencer_state_channel.hpp:63
AppliedEditKind
Definition sequencer_state_channel.hpp:261
@ CommandRejected
Definition sequencer_state_channel.hpp:265
@ PatternLengthChanged
Definition sequencer_state_channel.hpp:263
@ ActivePatternChanged
Definition sequencer_state_channel.hpp:264
@ StepRangeChanged
Definition sequencer_state_channel.hpp:262
StepRangeAppliedT< ReferenceSequencerConfig > StepRangeApplied
Definition sequencer_state_channel.hpp:439
constexpr std::uint8_t kStepCount
Definition sequencer_state_channel.hpp:138
SequencerStateChannelT< ReferenceSequencerConfig > SequencerStateChannel
Definition sequencer_state_channel.hpp:434
std::uint64_t EngineSequence
Definition sequencer_state_channel.hpp:61
StepEditCommandT< ReferenceSequencerConfig > StepEditCommand
Definition sequencer_state_channel.hpp:437
constexpr std::size_t kCommandQueueCapacity
Definition sequencer_state_channel.hpp:143
constexpr std::size_t kAppliedQueueCapacity
Definition sequencer_state_channel.hpp:144
PatternStateT< ReferenceSequencerConfig > PatternState
Definition sequencer_state_channel.hpp:436
SequencerConfig< kLaneCount, kStepCount, kPatternCount, StepCell > ReferenceSequencerConfig
The canonical way to name the transport types is via the channel's member typedefs (e....
Definition sequencer_state_channel.hpp:431
StepEditKind
Definition sequencer_state_channel.hpp:165
@ SwitchPattern
Definition sequencer_state_channel.hpp:170
@ SetPatternLength
Definition sequencer_state_channel.hpp:169
@ RandomizeLane
Definition sequencer_state_channel.hpp:168
@ SetCell
Definition sequencer_state_channel.hpp:166
@ Clear
Definition sequencer_state_channel.hpp:167
std::uint64_t Epoch
Definition sequencer_state_channel.hpp:60
GesturePhase
Host-undo gesture bracketing hint.
Definition sequencer_state_channel.hpp:177
@ Update
Definition sequencer_state_channel.hpp:177
@ Begin
Definition sequencer_state_channel.hpp:177
@ None
Definition sequencer_state_channel.hpp:177
@ End
Definition sequencer_state_channel.hpp:177
@ Cancel
Definition sequencer_state_channel.hpp:177
Lock-free sequence-lock for coherent multi-field snapshots.
Definition spsc_queue.hpp:12
Definition sequencer_state_channel.hpp:269
union pulp::state::AppliedEditT::Payload payload
AppliedEditKind kind
Definition sequencer_state_channel.hpp:274
ClientSequence client_sequence
Definition sequencer_state_channel.hpp:272
EngineSequence engine_sequence
Definition sequencer_state_channel.hpp:270
EditTransactionId transaction_id
Definition sequencer_state_channel.hpp:273
DirtyRegion dirty
Definition sequencer_state_channel.hpp:275
Epoch snapshot_epoch
Definition sequencer_state_channel.hpp:271
Definition sequencer_state_channel.hpp:188
ClearScope scope
Definition sequencer_state_channel.hpp:189
std::uint8_t pattern
Definition sequencer_state_channel.hpp:190
std::uint8_t lane
Definition sequencer_state_channel.hpp:191
std::uint8_t step
Definition sequencer_state_channel.hpp:192
Definition sequencer_state_channel.hpp:156
std::uint8_t lane
Definition sequencer_state_channel.hpp:159
std::uint8_t first_step
Definition sequencer_state_channel.hpp:160
DirtyKind kind
Definition sequencer_state_channel.hpp:157
std::uint8_t pattern
Definition sequencer_state_channel.hpp:158
std::uint8_t step_count
Definition sequencer_state_channel.hpp:161
Definition sequencer_state_channel.hpp:229
std::uint8_t length
Definition sequencer_state_channel.hpp:230
std::array< std::array< typename ReferenceSequencerConfig::cell_type, ReferenceSequencerConfig::steps >, ReferenceSequencerConfig::lanes > lanes
Definition sequencer_state_channel.hpp:231
Volatile, high-rate transport/playhead state.
Definition sequencer_state_channel.hpp:85
std::uint8_t playing
Definition sequencer_state_channel.hpp:91
std::uint32_t block_index
Definition sequencer_state_channel.hpp:88
std::uint8_t reserved
Definition sequencer_state_channel.hpp:92
std::uint8_t active_pattern
Definition sequencer_state_channel.hpp:89
std::uint8_t active_step
Definition sequencer_state_channel.hpp:90
double ppq_position
Definition sequencer_state_channel.hpp:87
std::uint64_t sample_time
Definition sequencer_state_channel.hpp:86
Definition sequencer_state_channel.hpp:194
std::uint8_t lane
Definition sequencer_state_channel.hpp:196
std::uint8_t min_velocity
Definition sequencer_state_channel.hpp:199
std::uint8_t pattern
Definition sequencer_state_channel.hpp:195
std::uint32_t seed
Definition sequencer_state_channel.hpp:197
std::uint8_t density
Definition sequencer_state_channel.hpp:198
std::uint8_t max_velocity
Definition sequencer_state_channel.hpp:200
Lanes/Steps/Patterns are MAXIMUM CAPACITY (active extent lives in the Snapshot).
Definition sequencer_state_channel.hpp:105
static constexpr std::size_t lanes
Definition sequencer_state_channel.hpp:106
static constexpr std::size_t steps
Definition sequencer_state_channel.hpp:107
Cell cell_type
Definition sequencer_state_channel.hpp:111
static constexpr std::size_t applied_depth
Definition sequencer_state_channel.hpp:110
static constexpr std::size_t command_depth
Definition sequencer_state_channel.hpp:109
Playhead playhead_type
Definition sequencer_state_channel.hpp:112
static constexpr std::size_t patterns
Definition sequencer_state_channel.hpp:108
SetCell carries a full cell by value, so it is templated on the Config's cell type.
Definition sequencer_state_channel.hpp:182
ReferenceSequencerConfig::cell_type cell
Definition sequencer_state_channel.hpp:186
std::uint8_t pattern
Definition sequencer_state_channel.hpp:183
std::uint8_t step
Definition sequencer_state_channel.hpp:185
std::uint8_t lane
Definition sequencer_state_channel.hpp:184
Definition sequencer_state_channel.hpp:202
std::uint8_t pattern
Definition sequencer_state_channel.hpp:203
std::uint8_t length
Definition sequencer_state_channel.hpp:204
The full non-param state, published for bulk replacement / resync only.
Definition sequencer_state_channel.hpp:237
Epoch epoch
Definition sequencer_state_channel.hpp:239
EngineSequence engine_sequence
Definition sequencer_state_channel.hpp:240
std::uint32_t schema_version
Definition sequencer_state_channel.hpp:238
std::array< PatternStateT< ReferenceSequencerConfig >, ReferenceSequencerConfig::patterns > patterns
Definition sequencer_state_channel.hpp:244
std::uint8_t active_pattern
Definition sequencer_state_channel.hpp:241
std::uint8_t active_lane_count
Definition sequencer_state_channel.hpp:242
std::uint8_t active_pattern_count
Definition sequencer_state_channel.hpp:243
One step in one lane.
Definition sequencer_state_channel.hpp:69
std::uint8_t velocity
Definition sequencer_state_channel.hpp:71
std::int8_t pitch_offset
Definition sequencer_state_channel.hpp:73
std::uint8_t flags
Definition sequencer_state_channel.hpp:70
std::uint8_t ratchet
Definition sequencer_state_channel.hpp:75
std::uint8_t reserved
Definition sequencer_state_channel.hpp:76
std::uint16_t gate_ticks
Definition sequencer_state_channel.hpp:74
static constexpr std::uint8_t kEnabledBit
Definition sequencer_state_channel.hpp:78
std::uint8_t probability
Definition sequencer_state_channel.hpp:72
constexpr bool enabled() const
Definition sequencer_state_channel.hpp:79
Definition sequencer_state_channel.hpp:211
StepEditKind kind
Definition sequencer_state_channel.hpp:215
ClientSequence client_sequence
Definition sequencer_state_channel.hpp:212
EditTransactionId transaction_id
Definition sequencer_state_channel.hpp:213
GesturePhase gesture_phase
Definition sequencer_state_channel.hpp:214
union pulp::state::StepEditCommandT::Payload payload
The exact cells the engine wrote for a step-range edit.
Definition sequencer_state_channel.hpp:253
std::uint8_t step_count
Definition sequencer_state_channel.hpp:257
std::uint8_t lane
Definition sequencer_state_channel.hpp:255
std::uint8_t pattern
Definition sequencer_state_channel.hpp:254
std::array< typename ReferenceSequencerConfig::cell_type, ReferenceSequencerConfig::steps > cells
Definition sequencer_state_channel.hpp:258
std::uint8_t first_step
Definition sequencer_state_channel.hpp:256
Definition sequencer_state_channel.hpp:206
std::uint8_t pattern
Definition sequencer_state_channel.hpp:207
Lock-free latest-value publication between two threads.
SwitchPatternEdit active_pattern
Definition sequencer_state_channel.hpp:280
constexpr Payload()
Definition sequencer_state_channel.hpp:282
SetPatternLengthEdit pattern_length
Definition sequencer_state_channel.hpp:279
std::uint32_t reject_reason
Definition sequencer_state_channel.hpp:281
StepRangeAppliedT< Config > step_range
Definition sequencer_state_channel.hpp:278
constexpr Payload()
Definition sequencer_state_channel.hpp:223
ClearEdit clear
Definition sequencer_state_channel.hpp:219
SwitchPatternEdit switch_pattern
Definition sequencer_state_channel.hpp:222
RandomizeLaneEdit randomize_lane
Definition sequencer_state_channel.hpp:220
SetCellEditT< Config > set_cell
Definition sequencer_state_channel.hpp:218
SetPatternLengthEdit set_pattern_length
Definition sequencer_state_channel.hpp:221