Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
step_grid_view.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cassert>
5#include <cstdint>
6#include <optional>
7#include <vector>
8
10#include <pulp/view/view.hpp>
11
37namespace pulp::view {
38
39// ── Non-template base ────────────────────────────────────────────────────────
40class StepGridViewBase : public View {
41 public:
43 struct CellVisual {
44 bool enabled = false;
45 float intensity = 0.0f; // velocity / 127, drives fill alpha/height
46 bool muted = false; // step index >= pattern length
47 };
48
51 struct PumpResult {
52 bool requested_repaint = false;
53 bool full_rebuild = false; // a resync or active-pattern change rebuilt all visible cells
54 int rebuilt_cells = 0; // visible cells whose CellVisual was recomputed
55 int echoes_applied = 0; // non-stale echoes replayed into the render copy
56 bool resynced = false; // a snapshot resync occurred this pump
57 };
58
59 struct Cell {
60 std::uint8_t lane;
61 std::uint8_t step;
62 };
63
65
68 void set_displayed_pattern(std::uint8_t pattern);
69 std::uint8_t displayed_pattern() const {
70 return displayed_pattern_;
71 }
72
76
77 const CellVisual& cell_visual(std::uint8_t lane, std::uint8_t step) const {
78 return visible_cache_[index(lane, step)];
79 }
81 return last_engine_sequence_;
82 }
83
84 void paint(canvas::Canvas& canvas) override;
85 void on_mouse_down(Point pos) override;
86 void on_mouse_drag(Point pos) override;
87 void on_mouse_up(Point pos) override;
88 void on_frame_clock_changed() override;
89 bool wants_mouse_input() const override {
90 return true;
91 }
92
93 // Geometry (public for tests / layout).
94 std::optional<Cell> cell_at(Point pos) const;
95 Rect cell_rect(std::uint8_t lane, std::uint8_t step) const;
96 Rect playhead_column_rect(std::uint8_t step) const;
97
98 protected:
99 StepGridViewBase(std::uint8_t lanes, std::uint8_t steps, std::uint8_t patterns);
100
101 // ── Hooks the templated shim implements (the only Config-typed surface) ──
103 struct EchoResult {
106 bool full_rebuild = false; // e.g. pattern-length change rebuilt all visible cells
108 };
115 std::uint8_t active_pattern = 0;
116 std::uint8_t active_step = 0;
117 bool playing = false;
118 };
119
120 virtual bool channel_bound() const = 0;
133 virtual int rebuild_visible() = 0;
134 virtual void submit_set_cell(std::uint8_t lane, std::uint8_t step, bool enabled,
136
137 // ── Shared state the shim reads/writes through these accessors ──
138 std::size_t index(std::uint8_t lane, std::uint8_t step) const {
139 return static_cast<std::size_t>(lane) * steps_ + step;
140 }
141 void set_visible(std::uint8_t lane, std::uint8_t step, const CellVisual& v) {
142 visible_cache_[index(lane, step)] = v;
143 }
144 void record_dirty(std::uint8_t lane, std::uint8_t step) {
145 pump_dirty_cells_.push_back(Cell{lane, step});
146 }
147 std::uint8_t lanes() const {
148 return lanes_;
149 }
150 std::uint8_t steps() const {
151 return steps_;
152 }
153 std::uint8_t patterns() const {
154 return patterns_;
155 }
157 return current_txn_;
158 }
160 return next_client_seq_++;
161 }
163 needs_initial_resync_ = true;
164 }
165
168
169 std::uint8_t displayed_pattern_ = 0;
170
171 private:
172 std::uint8_t lanes_, steps_, patterns_;
173
174 void subscribe_probe();
175 void unsubscribe_probe();
176
177 // UI-owned visual cache + replay bookkeeping (advanced only by engine echoes).
178 std::vector<CellVisual> visible_cache_; // lanes_ * steps_
179 state::EngineSequence last_engine_sequence_ = 0;
180 state::Epoch local_epoch_ = 0;
181 bool needs_initial_resync_ = true;
182
183 // Per-pump dirty accounting for bounded invalidation.
184 std::vector<Cell> pump_dirty_cells_;
185 bool pump_full_ = false;
186
187 // Playhead overlay state (kept separate from cell visuals).
188 std::uint8_t playhead_step_ = 0;
189 std::uint8_t playhead_pattern_ = 0;
190 bool playhead_visible_ = false;
191
192 // Drag edit state: the value the drag paints, so a swipe sets a run coherently.
193 bool dragging_ = false;
194 bool drag_sets_enabled_ = true;
195 std::optional<Cell> last_drag_cell_{};
196
197 // Command sequencing.
198 state::ClientSequence next_client_seq_ = 1;
199 state::EditTransactionId next_txn_ = 1;
200 state::EditTransactionId current_txn_ = 0;
201
202 // FrameClock activity subscription (must be explicitly unsubscribed).
203 int probe_id_ = -1;
204 FrameClock* subscribed_clock_ = nullptr;
205};
206
207// ── Cell policy: cell type -> visual AND the edit a click applies ────────────
211template <class Cell> struct DefaultCellPolicy {
212 static_assert(sizeof(Cell) == 0,
213 "Provide a CellPolicy<YourCell> with static visual(const Cell&, "
214 "bool muted) -> StepGridViewBase::CellVisual and static "
215 "make_set_cell(const Cell&, bool enabled) -> Cell.");
216};
217
218template <> struct DefaultCellPolicy<state::StepCell> {
221 v.enabled = c.enabled();
222 v.intensity = std::clamp(c.velocity / 127.0f, 0.0f, 1.0f);
223 v.muted = muted;
224 return v;
225 }
226 static state::StepCell make_set_cell(const state::StepCell& old, bool enabled) {
227 state::StepCell c = old;
228 if (enabled) {
229 c.flags = static_cast<std::uint8_t>(c.flags | state::StepCell::kEnabledBit);
230 if (c.velocity == 0)
231 c.velocity = 100;
232 } else {
233 c.flags = static_cast<std::uint8_t>(c.flags & ~state::StepCell::kEnabledBit);
234 }
235 return c;
236 }
237};
238
239// ── Templated shim ───────────────────────────────────────────────────────────
240template <class Config, class CellPolicy = DefaultCellPolicy<typename Config::cell_type>>
241class StepGridViewT final : public StepGridViewBase {
242 public:
244 using Snapshot = typename Channel::Snapshot;
245
247 : StepGridViewBase(static_cast<std::uint8_t>(Config::lanes),
248 static_cast<std::uint8_t>(Config::steps),
249 static_cast<std::uint8_t>(Config::patterns)) {}
250
251 ~StepGridViewT() override {
252 // Stop the probe BEFORE typed members die (it would else pure-virtual-call
253 // pump() on a half-destroyed object), then release the consumer claim.
254 detach_probe();
255 if (channel_)
256 channel_->debug_release_ui_consumer();
257 }
258
262 if (channel_ == channel)
263 return;
264 if (channel_)
265 channel_->debug_release_ui_consumer();
266 channel_ = channel;
267 if (channel_) {
268 const bool claimed = channel_->debug_claim_ui_consumer();
269 (void)claimed;
270 // Exactly one UI-side consumer per channel (single-consumer echo FIFO).
271 assert(claimed && "another StepGridView already consumes this channel");
272 }
274 }
275 Channel* channel() const {
276 return channel_;
277 }
278
279 const Snapshot& render_snapshot() const {
280 return render_;
281 }
282
283 protected:
284 bool channel_bound() const override {
285 return channel_ != nullptr;
286 }
287
289 return channel_->ui_resync_required_epoch();
290 }
291
293 render_ = channel_->ui_read_latest_snapshot();
294 const int n = rebuild_visible();
295 return {render_.epoch, render_.engine_sequence, n};
296 }
297
299 auto e = channel_->ui_try_pop_applied();
300 if (!e)
301 return {EchoStatus::Drained, 0, false, 0};
302 if (e->engine_sequence <= min_exclusive)
303 return {EchoStatus::SkippedStale, e->engine_sequence, false, 0};
304
305 EchoResult res;
307 res.engine_sequence = e->engine_sequence;
308
309 switch (e->kind) {
311 const auto& sr = e->payload.step_range;
312 if (sr.pattern >= patterns() || sr.lane >= lanes())
313 break;
314 auto& lane = render_.patterns[sr.pattern].lanes[sr.lane];
315 for (std::uint8_t i = 0; i < sr.step_count; ++i) {
316 std::uint8_t step = static_cast<std::uint8_t>(sr.first_step + i);
317 if (step >= steps())
318 break;
319 lane[step] = sr.cells[i];
320 }
321 if (sr.pattern != displayed_pattern_)
322 break;
323 for (std::uint8_t i = 0; i < sr.step_count; ++i) {
324 std::uint8_t step = static_cast<std::uint8_t>(sr.first_step + i);
325 if (step >= steps())
326 break;
327 set_visible(sr.lane, step, visual_for(lane[step], step));
328 record_dirty(sr.lane, step);
329 ++res.rebuilt_cells;
330 }
331 break;
332 }
334 const auto& pl = e->payload.pattern_length;
335 if (pl.pattern >= patterns())
336 break;
337 render_.patterns[pl.pattern].length = pl.length;
338 if (pl.pattern != displayed_pattern_)
339 break;
340 res.full_rebuild = true; // every cell's muted flag may flip
342 break;
343 }
345 render_.active_pattern = e->payload.active_pattern.pattern;
346 break;
348 break;
349 }
350 return res;
351 }
352
354 // The reference/default playhead exposes active_pattern/active_step/playing.
355 // A custom Config::playhead_type must expose the same three fields.
356 const auto ph = channel_->ui_read_playhead();
357 return {ph.active_pattern, ph.active_step, ph.playing != 0};
358 }
359
360 int rebuild_visible() override {
361 const auto& pat = render_.patterns[displayed_pattern_];
362 int n = 0;
363 for (std::uint8_t lane = 0; lane < lanes(); ++lane)
364 for (std::uint8_t step = 0; step < steps(); ++step) {
365 set_visible(lane, step, visual_for(pat.lanes[lane][step], step));
366 ++n;
367 }
368 return n;
369 }
370
371 void submit_set_cell(std::uint8_t lane, std::uint8_t step, bool enabled,
372 state::GesturePhase phase) override {
373 if (!channel_ || lane >= lanes() || step >= steps())
374 return;
375 typename Channel::Command cmd;
378 cmd.gesture_phase = phase;
380 typename Channel::SetCellEdit e;
381 e.pattern = displayed_pattern_;
382 e.lane = lane;
383 e.step = step;
384 e.cell = CellPolicy::make_set_cell(render_.patterns[displayed_pattern_].lanes[lane][step],
385 enabled);
386 cmd.payload.set_cell = e;
387 channel_->ui_try_submit(cmd); // failure is fine: no authoritative change occurs
388 }
389
390 private:
391 CellVisual visual_for(const typename Config::cell_type& c, std::uint8_t step) const {
392 const bool muted = step >= render_.patterns[displayed_pattern_].length;
393 return CellPolicy::visual(c, muted);
394 }
395
396 Channel* channel_ = nullptr;
397 Snapshot render_{};
398};
399
400// The blessed reference grid: 12x32x32 StepCell.
402
403} // namespace pulp::view
Definition canvas.hpp:240
Ownership: the engine (audio) thread is the single reader of the command queue and the single writer ...
Definition sequencer_state_channel.hpp:301
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
Definition frame_clock.hpp:14
virtual ResyncPoint resync_from_snapshot()=0
Replace the render copy from the channel's latest snapshot and rebuild the whole visible cache; retur...
void request_initial_resync()
Definition step_grid_view.hpp:162
virtual int rebuild_visible()=0
Rebuild the whole visible cache for the displayed pattern; return the count.
state::EngineSequence last_engine_sequence() const
Definition step_grid_view.hpp:80
Rect playhead_column_rect(std::uint8_t step) const
void on_mouse_down(Point pos) override
void on_mouse_up(Point pos) override
void set_displayed_pattern(std::uint8_t pattern)
Which pattern is shown/edited.
state::ClientSequence next_client_sequence()
Definition step_grid_view.hpp:159
virtual PlayheadProjection read_playhead() const =0
void paint(canvas::Canvas &canvas) override
EchoStatus
Definition step_grid_view.hpp:102
@ Applied
Definition step_grid_view.hpp:102
@ SkippedStale
Definition step_grid_view.hpp:102
@ Drained
Definition step_grid_view.hpp:102
std::uint8_t lanes() const
Definition step_grid_view.hpp:147
std::uint8_t steps() const
Definition step_grid_view.hpp:150
std::size_t index(std::uint8_t lane, std::uint8_t step) const
Definition step_grid_view.hpp:138
std::uint8_t displayed_pattern() const
Definition step_grid_view.hpp:69
Rect cell_rect(std::uint8_t lane, std::uint8_t step) const
virtual void submit_set_cell(std::uint8_t lane, std::uint8_t step, bool enabled, state::GesturePhase)=0
virtual bool channel_bound() const =0
virtual EchoResult apply_next_echo(state::EngineSequence min_exclusive)=0
Pop the next applied echo.
std::uint8_t displayed_pattern_
Definition step_grid_view.hpp:169
void on_frame_clock_changed() override
The FrameClock reachable from this view (via frame_clock()) may have changed — a clock was installed ...
StepGridViewBase(std::uint8_t lanes, std::uint8_t steps, std::uint8_t patterns)
state::EditTransactionId current_txn() const
Definition step_grid_view.hpp:156
virtual state::Epoch channel_resync_required_epoch() const =0
bool wants_mouse_input() const override
True if this widget handles mouse down/drag through virtual methods even when it does not expose JS p...
Definition step_grid_view.hpp:89
PumpResult pump()
Drain the channel and update the render copy + visual cache.
void record_dirty(std::uint8_t lane, std::uint8_t step)
Definition step_grid_view.hpp:144
void set_visible(std::uint8_t lane, std::uint8_t step, const CellVisual &v)
Definition step_grid_view.hpp:141
std::optional< Cell > cell_at(Point pos) const
void detach_probe()
The shim MUST call this in its destructor (before its typed members die) so the FrameClock probe can'...
void on_mouse_drag(Point pos) override
const CellVisual & cell_visual(std::uint8_t lane, std::uint8_t step) const
Definition step_grid_view.hpp:77
std::uint8_t patterns() const
Definition step_grid_view.hpp:153
Definition step_grid_view.hpp:241
int rebuild_visible() override
Rebuild the whole visible cache for the displayed pattern; return the count.
Definition step_grid_view.hpp:360
void set_channel(Channel *channel)
Bind the channel this view reads/writes.
Definition step_grid_view.hpp:261
const Snapshot & render_snapshot() const
Definition step_grid_view.hpp:279
EchoResult apply_next_echo(state::EngineSequence min_exclusive) override
Pop the next applied echo.
Definition step_grid_view.hpp:298
PlayheadProjection read_playhead() const override
Definition step_grid_view.hpp:353
~StepGridViewT() override
Definition step_grid_view.hpp:251
typename Channel::Snapshot Snapshot
Definition step_grid_view.hpp:244
ResyncPoint resync_from_snapshot() override
Replace the render copy from the channel's latest snapshot and rebuild the whole visible cache; retur...
Definition step_grid_view.hpp:292
bool channel_bound() const override
Definition step_grid_view.hpp:284
state::SequencerStateChannelT< Config > Channel
Definition step_grid_view.hpp:243
void submit_set_cell(std::uint8_t lane, std::uint8_t step, bool enabled, state::GesturePhase phase) override
Definition step_grid_view.hpp:371
state::Epoch channel_resync_required_epoch() const override
Definition step_grid_view.hpp:288
Channel * channel() const
Definition step_grid_view.hpp:275
StepGridViewT()
Definition step_grid_view.hpp:246
bool enabled() const
CSS :disabled equivalent — blocks input, reduces opacity.
Definition view.hpp:715
Definition affine_transform.hpp:24
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
std::uint64_t ClientSequence
Definition sequencer_state_channel.hpp:62
std::uint32_t EditTransactionId
Definition sequencer_state_channel.hpp:63
@ 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
std::uint64_t EngineSequence
Definition sequencer_state_channel.hpp:61
@ SetCell
Definition sequencer_state_channel.hpp:166
std::uint64_t Epoch
Definition sequencer_state_channel.hpp:60
GesturePhase
Host-undo gesture bracketing hint.
Definition sequencer_state_channel.hpp:177
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
StepGridViewT< state::ReferenceSequencerConfig > StepGridView
Definition step_grid_view.hpp:401
@ c
Definition input_events.hpp:39
@ n
Definition input_events.hpp:40
@ i
Definition input_events.hpp:40
@ v
Definition input_events.hpp:41
@ e
Definition input_events.hpp:39
STL namespace.
One step in one lane.
Definition sequencer_state_channel.hpp:69
static constexpr std::uint8_t kEnabledBit
Definition sequencer_state_channel.hpp:78
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
static StepGridViewBase::CellVisual visual(const state::StepCell &c, bool muted)
Definition step_grid_view.hpp:219
static state::StepCell make_set_cell(const state::StepCell &old, bool enabled)
Definition step_grid_view.hpp:226
Primary template is deliberately undefined-usable: a custom cell type must supply its own CellPolicy ...
Definition step_grid_view.hpp:211
Definition geometry.hpp:20
Axis-aligned rectangle in continuous (float) coordinates.
Definition geometry.hpp:52
Precomputed per-cell visual, recomputed only for dirtied regions.
Definition step_grid_view.hpp:43
bool enabled
Definition step_grid_view.hpp:44
bool muted
Definition step_grid_view.hpp:46
float intensity
Definition step_grid_view.hpp:45
Definition step_grid_view.hpp:59
std::uint8_t lane
Definition step_grid_view.hpp:60
std::uint8_t step
Definition step_grid_view.hpp:61
Definition step_grid_view.hpp:103
int rebuilt_cells
Definition step_grid_view.hpp:107
bool full_rebuild
Definition step_grid_view.hpp:106
EchoStatus status
Definition step_grid_view.hpp:104
state::EngineSequence engine_sequence
Definition step_grid_view.hpp:105
Definition step_grid_view.hpp:114
std::uint8_t active_pattern
Definition step_grid_view.hpp:115
std::uint8_t active_step
Definition step_grid_view.hpp:116
bool playing
Definition step_grid_view.hpp:117
Result of one pump(), surfaced for headless tests (a hostless view cannot observe request_repaint()).
Definition step_grid_view.hpp:51
int echoes_applied
Definition step_grid_view.hpp:55
bool requested_repaint
Definition step_grid_view.hpp:52
bool resynced
Definition step_grid_view.hpp:56
bool full_rebuild
Definition step_grid_view.hpp:53
int rebuilt_cells
Definition step_grid_view.hpp:54
Definition step_grid_view.hpp:109
state::EngineSequence engine_sequence
Definition step_grid_view.hpp:111
state::Epoch epoch
Definition step_grid_view.hpp:110
int rebuilt_cells
Definition step_grid_view.hpp:112
SetCellEditT< Config > set_cell
Definition sequencer_state_channel.hpp:218