Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
step_edit_reducer.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <optional>
4#include <type_traits>
5
7
23namespace pulp::state {
24
26template <class Config>
27std::optional<AppliedEditT<Config>> apply_step_edit(
28 SnapshotT<Config>& snapshot,
29 const StepEditCommandT<Config>& cmd,
30 EngineSequence& engine_seq) {
31 static_assert(std::is_same_v<typename Config::cell_type, StepCell>,
32 "apply_step_edit is the reference reducer (StepCell only). A "
33 "custom cell type must supply its own reducer with the same "
34 "signature: (Snapshot&, const Command&, EngineSequence&) -> "
35 "optional<AppliedEdit>.");
36
39 constexpr std::uint8_t P = static_cast<std::uint8_t>(Config::patterns);
40 constexpr std::uint8_t L = static_cast<std::uint8_t>(Config::lanes);
41 constexpr std::uint8_t S = static_cast<std::uint8_t>(Config::steps);
42
43 AppliedEdit echo{};
44 echo.engine_sequence = ++engine_seq;
45 echo.snapshot_epoch = snapshot.epoch;
48
49 switch (cmd.kind) {
51 const auto& e = cmd.payload.set_cell;
52 if (e.pattern >= P || e.lane >= L || e.step >= S) {
54 echo.payload.reject_reason = 1;
55 return echo;
56 }
57 snapshot.patterns[e.pattern].lanes[e.lane][e.step] = e.cell;
59 echo.dirty = {DirtyKind::Cell, e.pattern, e.lane, e.step, 1};
61 sr.pattern = e.pattern; sr.lane = e.lane; sr.first_step = e.step;
62 sr.step_count = 1; sr.cells[0] = e.cell;
63 echo.payload.step_range = sr;
64 return echo;
65 }
67 const auto& e = cmd.payload.clear;
68 if (e.pattern >= P || e.lane >= L || e.step >= S) {
70 echo.payload.reject_reason = 1;
71 return echo;
72 }
73 snapshot.patterns[e.pattern].lanes[e.lane][e.step] = StepCell{};
75 echo.dirty = {DirtyKind::Cell, e.pattern, e.lane, e.step, 1};
77 sr.pattern = e.pattern; sr.lane = e.lane; sr.first_step = e.step;
78 sr.step_count = 1; sr.cells[0] = StepCell{};
79 echo.payload.step_range = sr;
80 return echo;
81 }
83 const auto& e = cmd.payload.randomize_lane;
84 if (e.pattern >= P || e.lane >= L) {
86 echo.payload.reject_reason = 1;
87 return echo;
88 }
89 // Deterministic PRNG so the echo carries the exact cells the UI replays
90 // — it never re-runs this algorithm.
91 std::uint32_t s = e.seed ? e.seed : 1u;
92 auto next = [&s]() { s ^= s << 13; s ^= s >> 17; s ^= s << 5; return s; };
94 sr.pattern = e.pattern; sr.lane = e.lane; sr.first_step = 0;
95 sr.step_count = S;
96 const std::uint8_t span =
97 static_cast<std::uint8_t>(1u + e.max_velocity - e.min_velocity);
98 for (std::uint8_t st = 0; st < S; ++st) {
99 StepCell c{};
100 const bool on = (next() % 128u) < e.density;
101 c.flags = on ? StepCell::kEnabledBit : 0;
102 c.velocity = static_cast<std::uint8_t>(
103 e.min_velocity + (span ? next() % span : 0u));
104 snapshot.patterns[e.pattern].lanes[e.lane][st] = c;
105 sr.cells[st] = c;
106 }
108 echo.dirty = {DirtyKind::Lane, e.pattern, e.lane, 0, S};
109 echo.payload.step_range = sr;
110 return echo;
111 }
113 const auto& e = cmd.payload.set_pattern_length;
114 if (e.pattern >= P) {
116 echo.payload.reject_reason = 1;
117 return echo;
118 }
119 // Active step count. Normalize 0 (or an over-capacity value) to full here
120 // so the view (muted = step >= length) and playback see one consistent
121 // value — never a snapshot where 0 means "full" to one and "all muted" to
122 // the other.
123 const std::uint8_t len = (e.length == 0 || e.length > S) ? S : e.length;
124 snapshot.patterns[e.pattern].length = len;
126 echo.dirty = {DirtyKind::Pattern, e.pattern, 0, 0, 0};
127 echo.payload.pattern_length = {e.pattern, len};
128 return echo;
129 }
131 const auto& e = cmd.payload.switch_pattern;
132 if (e.pattern >= P) {
134 echo.payload.reject_reason = 1;
135 return echo;
136 }
137 snapshot.active_pattern = e.pattern;
139 echo.dirty = {DirtyKind::FullSnapshot, 0, 0, 0, 0};
140 echo.payload.active_pattern = e;
141 return echo;
142 }
143 }
144 return std::nullopt;
145}
146
154template <class Config, class Reducer>
156 SnapshotT<Config>& snapshot,
157 Epoch& epoch, EngineSequence& engine_seq,
158 Reducer&& reduce) {
159 while (auto cmd = channel.audio_try_pop_command()) {
160 auto echo = reduce(snapshot, *cmd, engine_seq);
161 if (!echo) continue;
162 if (!channel.audio_try_publish_applied(*echo)) {
163 // Applied FIFO overflow: fold the latest engine_sequence into a fresh
164 // snapshot, bump the epoch, publish it, THEN raise the resync bar.
165 snapshot.engine_sequence = engine_seq;
166 snapshot.epoch = ++epoch;
167 channel.audio_publish_snapshot(snapshot);
168 channel.audio_mark_resync_required(snapshot.epoch);
169 }
170 }
171}
172
174template <class Config>
176 SnapshotT<Config>& snapshot,
177 Epoch& epoch, EngineSequence& engine_seq) {
178 drain_and_apply(channel, snapshot, epoch, engine_seq,
180 EngineSequence& es) { return apply_step_edit<Config>(s, c, es); });
181}
182
183} // namespace pulp::state
Ownership: the engine (audio) thread is the single reader of the command queue and the single writer ...
Definition sequencer_state_channel.hpp:301
void audio_publish_snapshot(const Snapshot &snapshot) noexcept
Publish the full authoritative snapshot (initial load / preset / bulk / resync).
Definition sequencer_state_channel.hpp:390
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
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
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
@ FullSnapshot
Definition sequencer_state_channel.hpp:149
@ Cell
Definition sequencer_state_channel.hpp:153
@ Lane
Definition sequencer_state_channel.hpp:151
@ Pattern
Definition sequencer_state_channel.hpp:150
AppliedEditT< ReferenceSequencerConfig > AppliedEdit
Definition sequencer_state_channel.hpp:438
@ 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
void drain_and_apply(SequencerStateChannelT< Config > &channel, SnapshotT< Config > &snapshot, Epoch &epoch, EngineSequence &engine_seq, Reducer &&reduce)
Drain the command FIFO and apply each command with reduce, publishing the echoes.
Definition step_edit_reducer.hpp:155
StepRangeAppliedT< ReferenceSequencerConfig > StepRangeApplied
Definition sequencer_state_channel.hpp:439
std::uint64_t EngineSequence
Definition sequencer_state_channel.hpp:61
std::optional< AppliedEditT< Config > > apply_step_edit(SnapshotT< Config > &snapshot, const StepEditCommandT< Config > &cmd, EngineSequence &engine_seq)
Reference reducer for the StepCell grid. Returns nullopt only for a no-op.
Definition step_edit_reducer.hpp:27
@ 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
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
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::array< PatternStateT< Config >, Config::patterns > patterns
Definition sequencer_state_channel.hpp:244
std::uint8_t active_pattern
Definition sequencer_state_channel.hpp:241
One step in one lane.
Definition sequencer_state_channel.hpp:69
static constexpr std::uint8_t kEnabledBit
Definition sequencer_state_channel.hpp:78
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
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 Config::cell_type, Config::steps > cells
Definition sequencer_state_channel.hpp:258
std::uint8_t first_step
Definition sequencer_state_channel.hpp:256
SwitchPatternEdit active_pattern
Definition sequencer_state_channel.hpp:280
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
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