Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
param_attachment.hpp
Go to the documentation of this file.
1#pragma once
2
5
10#include <memory>
11
12namespace pulp::view {
13
21inline std::pair<std::unique_ptr<Knob>, state::Binding>
22attach_knob(state::StateStore& store, state::ParamID id, float size = 60.0f) {
23 state::Binding binding(store, id);
24 auto knob = std::make_unique<Knob>();
25 auto* info = binding.info();
26
27 if (info) {
28 knob->set_label(info->name);
29 knob->set_id(info->name);
30
31 auto unit = info->unit;
32 auto range = info->range;
33 knob->set_format([unit, range](float norm) {
34 float val = range.denormalize(norm);
35 char buf[32];
36 if (std::abs(val) >= 100) std::snprintf(buf, sizeof(buf), "%.0f", val);
37 else if (std::abs(val) >= 10) std::snprintf(buf, sizeof(buf), "%.1f", val);
38 else std::snprintf(buf, sizeof(buf), "%.2f", val);
39 return std::string(buf) + (unit.empty() ? "" : " " + unit);
40 });
41 }
42
43 knob->set_value(binding.get_normalized());
44 knob->flex().preferred_width = size;
45 knob->flex().preferred_height = size;
46
47 // Wire change callback
48 auto param_id = id;
49 knob->on_change = [&store, param_id](float norm) {
50 store.set_normalized(param_id, norm);
51 };
52 // Bracket the drag in a host gesture so DAWs RECORD automation from UI
53 // moves: begin → value changes → end. The store's gesture callbacks map to
54 // the host (AU Begin/EndParameterChangeGesture, VST3 begin/endEdit, CLAP
55 // gesture events). Without this a knob emits value changes with no "touch"
56 // and hosts like Logic don't capture them into an automation lane.
57 knob->on_gesture_begin = [&store, param_id]() { store.begin_gesture(param_id); };
58 knob->on_gesture_end = [&store, param_id]() { store.end_gesture(param_id); };
59
60 return {std::move(knob), std::move(binding)};
61}
62
64inline std::pair<std::unique_ptr<Fader>, state::Binding>
66 state::Binding binding(store, id);
67 auto fader = std::make_unique<Fader>();
68 auto* info = binding.info();
69
70 if (info) {
71 fader->set_label(info->name);
72 fader->set_id(info->name);
73 }
74
75 fader->set_value(binding.get_normalized());
76
77 auto param_id = id;
78 fader->on_change = [&store, param_id](float norm) {
79 store.set_normalized(param_id, norm);
80 };
81 // Gesture-bracket the drag so hosts record UI automation (see attach_knob).
82 fader->on_gesture_begin = [&store, param_id]() { store.begin_gesture(param_id); };
83 fader->on_gesture_end = [&store, param_id]() { store.end_gesture(param_id); };
84
85 return {std::move(fader), std::move(binding)};
86}
87
89inline std::pair<std::unique_ptr<Toggle>, state::Binding>
91 state::Binding binding(store, id);
92 auto toggle = std::make_unique<Toggle>();
93 auto* info = binding.info();
94
95 if (info) {
96 toggle->set_label(info->name);
97 toggle->set_id(info->name);
98 }
99
100 // Snap (don't animate) the initial seed: the editor must paint the stored
101 // state on the first frame — a freshly opened plugin, or a single headless
102 // screenshot, never advances the animation clock.
103 toggle->set_on(binding.get_normalized() > 0.5f, /*animate=*/false);
104
105 auto param_id = id;
106 toggle->on_toggle = [&store, param_id](bool on) {
107 // Discrete change bracketed in a one-shot gesture so hosts record it.
108 store.begin_gesture(param_id);
109 store.set_normalized(param_id, on ? 1.0f : 0.0f);
110 store.end_gesture(param_id);
111 };
112
113 return {std::move(toggle), std::move(binding)};
114}
115
117inline std::pair<std::unique_ptr<ComboBox>, state::Binding>
119 std::vector<std::string> items) {
120 state::Binding binding(store, id);
121 auto combo = std::make_unique<ComboBox>();
122 auto* info = binding.info();
123
124 if (info) combo->set_id(info->name);
125 combo->set_items(std::move(items));
126 combo->set_selected(static_cast<int>(binding.get()));
127
128 auto param_id = id;
129 combo->on_change = [&store, param_id](int index) {
130 // Discrete change bracketed in a one-shot gesture so hosts record it.
131 store.begin_gesture(param_id);
132 store.set_value(param_id, static_cast<float>(index));
133 store.end_gesture(param_id);
134 };
135
136 return {std::move(combo), std::move(binding)};
137}
138
146inline void poll_bindings(std::vector<state::Binding>& bindings) {
147 for (auto& b : bindings) b.poll();
148}
149
150// ── External (lambda-backed) attachments ─────────────────────────────────────
151// The same widgets, bound to an arbitrary get/set source instead of a StateStore
152// parameter — e.g. a host-side value in an embedding app. The widget writes
153// through the config's setter; the returned ExternalBinding is the host-side
154// handle for poll()/notify()/gestures/on_change. See state::ExternalBinding.
155
156inline std::pair<std::unique_ptr<Knob>, state::ExternalBinding>
158 auto setter = cfg.set;
159 auto range = cfg.range;
160 auto begin = cfg.begin_gesture;
161 auto end = cfg.end_gesture;
162 std::string name = cfg.name;
163 std::string unit = cfg.unit;
164
165 state::ExternalBinding binding(std::move(cfg));
166 auto knob = std::make_unique<Knob>();
167 if (!name.empty()) {
168 knob->set_label(name);
169 knob->set_id(name);
170 }
171 knob->set_format([unit, range](float norm) {
172 float val = range.denormalize(norm);
173 char buf[32];
174 if (std::abs(val) >= 100) std::snprintf(buf, sizeof(buf), "%.0f", val);
175 else if (std::abs(val) >= 10) std::snprintf(buf, sizeof(buf), "%.1f", val);
176 else std::snprintf(buf, sizeof(buf), "%.2f", val);
177 return std::string(buf) + (unit.empty() ? "" : " " + unit);
178 });
179 knob->set_value(binding.get_normalized());
180 knob->flex().preferred_width = size;
181 knob->flex().preferred_height = size;
182 knob->on_change = [setter, range](float norm) {
183 if (setter) setter(range.denormalize(norm));
184 };
185 // Bracket the drag in the host's gesture callbacks so DAWs record UI moves
186 // as automation (same rationale as the StateStore overload above).
187 knob->on_gesture_begin = [begin]() { if (begin) begin(); };
188 knob->on_gesture_end = [end]() { if (end) end(); };
189 return {std::move(knob), std::move(binding)};
190}
191
192inline std::pair<std::unique_ptr<Fader>, state::ExternalBinding>
194 auto setter = cfg.set;
195 auto range = cfg.range;
196 auto begin = cfg.begin_gesture;
197 auto end = cfg.end_gesture;
198 std::string name = cfg.name;
199
200 state::ExternalBinding binding(std::move(cfg));
201 auto fader = std::make_unique<Fader>();
202 if (!name.empty()) {
203 fader->set_label(name);
204 fader->set_id(name);
205 }
206 fader->set_value(binding.get_normalized());
207 fader->on_change = [setter, range](float norm) {
208 if (setter) setter(range.denormalize(norm));
209 };
210 fader->on_gesture_begin = [begin]() { if (begin) begin(); };
211 fader->on_gesture_end = [end]() { if (end) end(); };
212 return {std::move(fader), std::move(binding)};
213}
214
215inline std::pair<std::unique_ptr<Toggle>, state::ExternalBinding>
217 auto setter = cfg.set;
218 auto range = cfg.range;
219 auto begin = cfg.begin_gesture;
220 auto end = cfg.end_gesture;
221 std::string name = cfg.name;
222
223 state::ExternalBinding binding(std::move(cfg));
224 auto toggle = std::make_unique<Toggle>();
225 if (!name.empty()) {
226 toggle->set_label(name);
227 toggle->set_id(name);
228 }
229 // Snap (don't animate) the initial seed so the first frame / a headless
230 // screenshot paints the stored state (matches the StateStore overload).
231 toggle->set_on(binding.get_normalized() > 0.5f, /*animate=*/false);
232 toggle->on_toggle = [setter, range, begin, end](bool on) {
233 // Discrete change bracketed in a one-shot gesture so hosts record it.
234 if (begin) begin();
235 if (setter) setter(range.denormalize(on ? 1.0f : 0.0f));
236 if (end) end();
237 };
238 return {std::move(toggle), std::move(binding)};
239}
240
241} // namespace pulp::view
Reactive parameter binding for UI integration.
Reactive binding between a UI widget and a StateStore parameter.
Definition binding.hpp:43
float get_normalized() const
Read the parameter's value mapped to [0, 1].
Definition binding.hpp:70
const ParamInfo * info() const
Get the immutable metadata for the bound parameter.
Definition binding.hpp:141
float get() const
Read the parameter's current value.
Definition binding.hpp:54
Reactive binding between a UI widget and an arbitrary external value source, interface-compatible wit...
Definition external_binding.hpp:55
float get_normalized() const
Read the value mapped to [0, 1] through the configured range.
Definition external_binding.hpp:81
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
void set_value(ParamID id, float value)
Write a parameter's base value (lock-free).
void set_normalized(ParamID id, float normalized)
Write a parameter from a normalized [0, 1] value (lock-free).
void end_gesture(ParamID id)
Signal the host that the current gesture has ended.
void begin_gesture(ParamID id)
Signal the host that a gesture (drag, click) has begun on a parameter.
Lambda-backed parameter binding for values NOT owned by a StateStore.
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
Hover-cursor tracking for platform window hosts.
Definition fwd.hpp:27
std::pair< std::unique_ptr< Knob >, state::Binding > attach_knob(state::StateStore &store, state::ParamID id, float size=60.0f)
Attach a Knob widget to a parameter.
Definition param_attachment.hpp:22
std::pair< std::unique_ptr< Toggle >, state::Binding > attach_toggle(state::StateStore &store, state::ParamID id)
Attach a Toggle widget to a boolean parameter.
Definition param_attachment.hpp:90
void poll_bindings(std::vector< state::Binding > &bindings)
Poll all bindings in a vector to sync with host automation.
Definition param_attachment.hpp:146
std::pair< std::unique_ptr< ComboBox >, state::Binding > attach_combo(state::StateStore &store, state::ParamID id, std::vector< std::string > items)
Attach a ComboBox to an integer/stepped parameter.
Definition param_attachment.hpp:118
std::pair< std::unique_ptr< Fader >, state::Binding > attach_fader(state::StateStore &store, state::ParamID id)
Attach a Fader widget to a parameter.
Definition param_attachment.hpp:65
@ fader
Definition design_ir.hpp:261
@ toggle
Definition design_ir.hpp:272
@ knob
Definition design_ir.hpp:260
@ b
Definition input_events.hpp:39
@ end
Definition geometry.hpp:370
@ info
Definition design_ir.hpp:695
@ on
Definition accessibility.hpp:132
Configuration for an ExternalBinding: the getter/setter that back a UI control whose value is NOT a S...
Definition external_binding.hpp:21
std::string unit
Definition external_binding.hpp:35
std::function< void(float)> set
Write a new plain value. Required for an editable control.
Definition external_binding.hpp:25
std::function< void()> end_gesture
Definition external_binding.hpp:32
std::string name
Display metadata for the widget (label / value formatting). Optional.
Definition external_binding.hpp:34
ParamRange range
Maps plain <-> normalized [0,1].
Definition external_binding.hpp:28
std::function< void()> begin_gesture
Optional host-undo gesture boundaries, called exactly like StateStore gestures around a drag/edit.
Definition external_binding.hpp:31
Additional UI components: ComboBox, Tooltip, ProgressBar, CallOutBox, TabPanel, ListBox,...