Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
osc_parameter_map.hpp
Go to the documentation of this file.
1#pragma once
2
52
53#include <algorithm>
54#include <array>
55#include <cmath>
56#include <cstddef>
57#include <cstdint>
58#include <cstring>
59#include <string_view>
60#include <variant>
61
62#include <pulp/osc/bundle.hpp>
63#include <pulp/osc/osc.hpp>
65#include <pulp/state/store.hpp>
66
67namespace pulp::osc {
68
73 float in_min = 0.0f;
74 float in_max = 1.0f;
75 float out_min = 0.0f;
76 float out_max = 1.0f;
77
82 float apply(float value) const {
83 if (!std::isfinite(value))
84 return out_min;
85 const float span = in_max - in_min;
86 if (span == 0.0f)
87 return out_min;
88 const float t = std::clamp((value - in_min) / span, 0.0f, 1.0f);
89 return out_min + t * (out_max - out_min);
90 }
91};
92
96inline constexpr std::size_t kOscPatternCapacity = 96;
97
99 public:
101 static constexpr std::size_t kMaxMappings = 64;
102
109 static bool pattern_fits(std::string_view pattern) {
110 return pattern.size() + 1 <= kOscPatternCapacity
111 && pattern.find('\0') == std::string_view::npos;
112 }
113
116 static bool is_wildcard(std::string_view pattern) {
117 return pattern.find_first_of("*?[{") != std::string_view::npos;
118 }
119
120 // ── UI thread ──
121
124 bool set_mapping(std::string_view pattern, pulp::state::ParamID id) {
125 return set_mapping(pattern, id, OscMapScale{});
126 }
127
131 bool set_mapping(std::string_view pattern, pulp::state::ParamID id, OscMapScale scale) {
132 Command cmd{Command::Set, id, clamp_scale(scale), {}};
133 if (!store_pattern(cmd.pattern, pattern))
134 return false;
135 return commands_.try_push(cmd);
136 }
137
142
145 commands_.try_push(Command{Command::Arm, id, clamp_scale(scale), {}});
146 }
147
150 commands_.try_push(Command{Command::Clear, id, OscMapScale{}, {}});
151 }
152
153 // ── dispatching thread ──
154
156 void pump() {
157 Command cmd{};
158 while (commands_.try_pop(cmd)) {
159 switch (cmd.type) {
160 case Command::Set:
161 insert(std::string_view{cmd.pattern.data()}, cmd.id, cmd.scale);
162 break;
163 case Command::Arm:
164 learn_armed_ = true;
165 learn_target_ = cmd.id;
166 learn_scale_ = cmd.scale;
167 break;
168 case Command::Clear:
169 remove_target(cmd.id);
170 break;
171 }
172 }
173 }
174
183 template <typename Apply>
184 void route(std::string_view address, float value, Apply&& apply) {
185 if (learn_armed_ && pattern_fits(address)) {
186 insert(address, learn_target_, learn_scale_);
187 learn_armed_ = false;
188 }
189 for (std::size_t i = 0; i < count_; ++i) {
190 const auto& m = map_[i];
191 if (matches(m, address))
192 apply(m.id, m.scale.apply(value));
193 }
194 }
195
197 void handle_value(pulp::state::StateStore& store, std::string_view address, float value) {
198 route(address, value, [&store](pulp::state::ParamID id, float normalized) {
199 store.set_normalized(id, normalized);
200 });
201 }
202
208 float value = 0.0f;
209 if (!numeric_argument(msg, value))
210 return;
211 handle_value(store, msg.address, value);
212 }
213
217 bool learn_armed() const {
218 return learn_armed_;
219 }
220
223 std::size_t mapping_count() const {
224 return count_;
225 }
226
227 private:
228 using Pattern = std::array<char, kOscPatternCapacity>;
229
230 struct Command {
231 enum Type : std::uint8_t { Set, Arm, Clear } type;
233 OscMapScale scale;
234 Pattern pattern;
235 };
236 struct Mapping {
237 Pattern pattern;
239 OscMapScale scale;
240 bool wildcard;
241 };
242
245 static bool numeric_argument(const Message& msg, float& out) {
246 if (msg.args.empty())
247 return false;
248 if (const auto* f = std::get_if<float>(&msg.args[0])) {
249 out = *f;
250 return true;
251 }
252 if (const auto* i = std::get_if<std::int32_t>(&msg.args[0])) {
253 out = static_cast<float>(*i);
254 return true;
255 }
256 return false;
257 }
258
259 static OscMapScale clamp_scale(OscMapScale scale) {
260 scale.in_min = std::isfinite(scale.in_min) ? scale.in_min : 0.0f;
261 scale.in_max = std::isfinite(scale.in_max) ? scale.in_max : 1.0f;
262 scale.out_min =
263 std::isfinite(scale.out_min) ? std::clamp(scale.out_min, 0.0f, 1.0f) : 0.0f;
264 scale.out_max =
265 std::isfinite(scale.out_max) ? std::clamp(scale.out_max, 0.0f, 1.0f) : 1.0f;
266 return scale;
267 }
268
269 static bool store_pattern(Pattern& dest, std::string_view pattern) {
270 if (!pattern_fits(pattern))
271 return false;
272 dest.fill('\0');
273 std::memcpy(dest.data(), pattern.data(), pattern.size());
274 return true;
275 }
276
277 static bool matches(const Mapping& m, std::string_view address) {
278 const std::string_view pattern{m.pattern.data()};
279 // A literal address is a byte compare; only wildcard syntax needs the
280 // pattern matcher, which is not allocation-free.
281 return m.wildcard ? address_matches(pattern, address) : pattern == address;
282 }
283
284 void insert(std::string_view pattern, pulp::state::ParamID id, OscMapScale scale) {
285 for (std::size_t i = 0; i < count_; ++i) {
286 if (std::string_view{map_[i].pattern.data()} == pattern) {
287 map_[i].id = id;
288 map_[i].scale = scale;
289 return;
290 }
291 }
292 if (count_ >= kMaxMappings)
293 return;
294 Mapping& m = map_[count_];
295 if (!store_pattern(m.pattern, pattern))
296 return;
297 m.id = id;
298 m.scale = scale;
299 m.wildcard = is_wildcard(pattern);
300 ++count_;
301 }
302
303 void remove_target(pulp::state::ParamID id) {
304 std::size_t w = 0;
305 for (std::size_t i = 0; i < count_; ++i)
306 if (map_[i].id != id)
307 map_[w++] = map_[i];
308 count_ = w;
309 }
310
311 std::array<Mapping, kMaxMappings> map_{};
312 std::size_t count_ = 0;
313 bool learn_armed_ = false;
314 pulp::state::ParamID learn_target_ = 0;
315 OscMapScale learn_scale_{};
316 pulp::runtime::SpscQueue<Command, 64> commands_;
317};
318
319} // namespace pulp::osc
Definition osc_parameter_map.hpp:98
void route(std::string_view address, float value, Apply &&apply)
Route one address / value pair to a caller-supplied sink.
Definition osc_parameter_map.hpp:184
bool set_mapping(std::string_view pattern, pulp::state::ParamID id, OscMapScale scale)
Bind pattern → parameter through scale.
Definition osc_parameter_map.hpp:131
bool set_mapping(std::string_view pattern, pulp::state::ParamID id)
Bind pattern → parameter over the full normalized range, treating the surface as sending 0....
Definition osc_parameter_map.hpp:124
static bool pattern_fits(std::string_view pattern)
True if pattern can be held verbatim in a mapping's fixed-capacity, NUL-terminated storage.
Definition osc_parameter_map.hpp:109
void arm_learn(pulp::state::ParamID id, OscMapScale scale)
Arm learn: the next incoming address binds to id through scale.
Definition osc_parameter_map.hpp:144
void handle_message(pulp::state::StateStore &store, const Message &msg)
Route one OSC message, taking its first numeric argument as the value.
Definition osc_parameter_map.hpp:207
void clear(pulp::state::ParamID id)
Remove every mapping that targets id.
Definition osc_parameter_map.hpp:149
void arm_learn(pulp::state::ParamID id)
Arm learn: the next incoming address binds to id over the full range.
Definition osc_parameter_map.hpp:139
void handle_value(pulp::state::StateStore &store, std::string_view address, float value)
Route one address / value pair into store from a control thread.
Definition osc_parameter_map.hpp:197
std::size_t mapping_count() const
Number of installed mappings, up to kMaxMappings; further bindings are dropped.
Definition osc_parameter_map.hpp:223
static bool is_wildcard(std::string_view pattern)
True if pattern uses OSC wildcard syntax and must therefore be matched with address_matches rather th...
Definition osc_parameter_map.hpp:116
void pump()
Drain queued UI commands. Call before dispatching a batch of messages.
Definition osc_parameter_map.hpp:156
static constexpr std::size_t kMaxMappings
Largest number of simultaneous bindings.
Definition osc_parameter_map.hpp:101
bool learn_armed() const
True while an armed learn is still waiting for an address.
Definition osc_parameter_map.hpp:217
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
void set_normalized(ParamID id, float normalized)
Write a parameter from a normalized [0, 1] value (lock-free).
@ f
Definition pitch.hpp:25
Definition bundle.hpp:13
bool address_matches(std::string_view pattern, std::string_view address)
OSC address pattern matching per OSC 1.0 spec.
constexpr std::size_t kOscPatternCapacity
Longest OSC address pattern a mapping can hold, including the terminator.
Definition osc_parameter_map.hpp:96
@ i
Definition formant_filter_bank.hpp:20
void scale(const float *a, float scalar, float *dst, std::size_t count) noexcept
uint32_t ParamID
Unique parameter identifier, stable across plugin versions.
Definition parameter.hpp:55
@ m
Definition input_events.hpp:40
@ w
Definition input_events.hpp:41
Definition osc.hpp:39
std::string address
Definition osc.hpp:40
The incoming value range an OSC surface sends, and the normalized [0, 1] window it drives.
Definition osc_parameter_map.hpp:72
float out_min
Definition osc_parameter_map.hpp:75
float in_max
Definition osc_parameter_map.hpp:74
float apply(float value) const
Map a raw OSC argument onto this window.
Definition osc_parameter_map.hpp:82
float out_max
Definition osc_parameter_map.hpp:76
float in_min
Definition osc_parameter_map.hpp:73