Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
midi_ci.hpp
Go to the documentation of this file.
1#pragma once
2
3// MIDI-CI (Capability Inquiry) — MIDI 2.0 device discovery, profiles, property exchange.
4// Operates over UMP (Universal MIDI Packets) for CI message encoding/decoding.
5
6#include <string>
7#include <string_view>
8#include <vector>
9#include <map>
10#include <memory>
11#include <functional>
12#include <cstdint>
13#include <optional>
14
15namespace pulp::midi {
16
17class PeSubscriptionManager; // defined in midi_ci_pe.hpp
18struct PeSubscription;
19
50
52struct MUID {
53 uint32_t value = 0;
54
55 static MUID generate();
56 static MUID broadcast() { return {0x0FFFFFFF}; }
57 bool is_broadcast() const { return value == 0x0FFFFFFF; }
58 bool operator==(const MUID& o) const { return value == o.value; }
59};
60
66using PeNotifyCallback = std::function<void(MUID subscriber_muid,
67 std::string_view resource,
68 std::string_view header_json,
69 const std::vector<uint8_t>& payload)>;
70
74 uint32_t manufacturer_id = 0; // 3-byte SysEx ID
75 uint16_t family_id = 0;
76 uint16_t model_id = 0;
77 uint32_t software_version = 0;
78 uint8_t ci_version = 2; // CI version supported
79 uint8_t max_sysex_size = 128;
80};
81
83struct ProfileId {
84 uint8_t bank = 0;
85 uint8_t number = 0;
86 uint8_t version = 0;
87 uint8_t level = 0;
88 uint8_t reserved = 0;
89
90 bool operator==(const ProfileId& o) const {
91 return bank == o.bank && number == o.number && version == o.version && level == o.level;
92 }
93};
94
98 bool enabled = false;
99 uint16_t channel_count = 0; // 0 = group-wide
100};
101
109 uint8_t value = 0x7F;
110
111 static FunctionBlockId broadcast() { return {0x7F}; }
112 bool is_broadcast() const { return value == 0x7F; }
113 bool operator==(const FunctionBlockId& o) const { return value == o.value; }
114};
115
121 std::string name;
122 uint8_t first_group = 0;
123 uint8_t group_count = 1;
124 bool active = true;
125 bool is_midi1 = false;
126 bool ui_hint_receiver = true;
127 bool ui_hint_sender = true;
128};
129
136 uint8_t target = 0;
137 std::vector<uint8_t> data;
138};
139
148public:
156
158 void set_device_info(const CiDeviceInfo& info) { local_info_ = info; }
160 const CiDeviceInfo& device_info() const { return local_info_; }
161
163 MUID local_muid() const { return local_info_.muid; }
164
169 std::vector<uint8_t> process_message(const uint8_t* data, size_t size);
170
172 std::vector<uint8_t> create_discovery_inquiry() const;
173
175 std::vector<uint8_t> create_profile_inquiry(MUID destination) const;
176
180 const std::vector<CiDeviceInfo>& discovered_devices() const { return discovered_; }
181
183 void add_profile(const ProfileState& profile) { profiles_.push_back(profile); }
185 const std::vector<ProfileState>& profiles() const { return profiles_; }
186
189 bool enable_profile(const ProfileId& id);
191 bool disable_profile(const ProfileId& id);
192
195 void set_property(std::string_view resource, std::string_view value);
201 std::optional<std::string> get_property(std::string_view resource) const;
202
203 // ── Function Block support (CI 1.2 §3.2) ──────────────────────────
204 //
205 // Function Blocks are MIDI 2.0's logical grouping for UMP endpoints.
206 // CI uses them to scope profile + property state per-block so a single
207 // endpoint can publish (for example) a piano profile on block 0 and a
208 // drum-pad profile on block 1 without the two state machines
209 // colliding. Pulp's Function Block surface is responder-side metadata
210 // today; the consumer/inquirer side ships as part of UMP endpoint
211 // discovery in `pulp::midi::UmpSession`.
212
216 function_blocks_.push_back(block);
217 }
218
219 const std::vector<FunctionBlockInfo>& function_blocks() const {
220 return function_blocks_;
221 }
222
225 for (auto& fb : function_blocks_) {
226 if (fb.id == id) return &fb;
227 }
228 return nullptr;
229 }
230
231 // ── Profile Inquiry / Add / Remove (CI 1.2 §7) ────────────────────
232 //
233 // CI 1.2 distinguishes Inquiry (request the full list), Reply
234 // (responder publishes the list), Added Report (responder pushes
235 // "I now expose this profile"), and Removed Report (responder
236 // pushes "this profile is gone"). Pulp owned Inquiry + Reply
237 // already (`add_profile` / `profiles()` / `handle_profile_inquiry`);
238 // the explicit Add / Remove helpers below complete the wire surface.
239
243 std::vector<uint8_t> create_profile_added_report(MUID destination,
244 const ProfileId& id);
245
248 std::vector<uint8_t> create_profile_removed_report(MUID destination,
249 const ProfileId& id);
250
251 // ── Profile Details (CI 1.2 §7.6) ─────────────────────────────────
252 //
253 // Profile Details Inquiry asks "give me more info about profile X
254 // (target byte Y)"; Reply carries an opaque payload defined by the
255 // profile spec. Pulp publishes details via `set_profile_details()`
256 // and answers inquiries from the local store. Inquirers consume
257 // replies via `discovered_profile_details()`.
258
261 void set_profile_details(const ProfileId& id, uint8_t target,
262 const std::vector<uint8_t>& data);
263
265 std::vector<ProfileDetails> profile_details_for(const ProfileId& id) const;
266
268 std::vector<uint8_t> create_profile_details_inquiry(MUID destination,
269 const ProfileId& id,
270 uint8_t target) const;
271
274 const std::vector<ProfileDetails>& discovered_profile_details() const {
275 return discovered_details_;
276 }
277
278 // ── Profile-Specific Data (CI 1.2 §7.7) ───────────────────────────
279 //
280 // Profile-Specific Data is the catch-all for profile-defined runtime
281 // messaging. The wire envelope is fixed — F0 7E ... 0x2F src dst
282 // profile_id(5) length(2) data ... F7 — and the payload semantics
283 // are defined by the profile spec, not by CI.
284
286 std::vector<uint8_t> create_profile_specific_data(MUID destination,
287 const ProfileId& id,
288 const uint8_t* data,
289 std::size_t size) const;
290
291 inline std::vector<uint8_t> create_profile_specific_data(
292 MUID destination, const ProfileId& id,
293 const std::vector<uint8_t>& data) const {
294 return create_profile_specific_data(destination, id, data.data(), data.size());
295 }
296
300 std::function<void(MUID source, const ProfileId& id,
301 const std::vector<uint8_t>& data)>
303
305 std::function<void(const CiDeviceInfo&)> on_device_discovered;
306 std::function<void(const ProfileId&, bool enabled)> on_profile_changed;
307 std::function<void(MUID source, const ProfileId&)> on_profile_added;
308 std::function<void(MUID source, const ProfileId&)> on_profile_removed;
309 std::function<void(MUID source, const ProfileDetails&)> on_profile_details;
310
319
328
334 std::size_t notify(std::string_view resource,
335 std::string_view header_json,
336 const std::vector<uint8_t>& payload);
337
338private:
339 CiDeviceInfo local_info_;
340 std::vector<CiDeviceInfo> discovered_;
341 std::vector<ProfileState> profiles_;
342 std::vector<FunctionBlockInfo> function_blocks_;
343 // Local profile-details store, keyed by (bank, number, version, level, target)
344 // to allow multiple target bytes per profile id.
345 std::vector<ProfileDetails> local_details_;
346 std::vector<ProfileDetails> discovered_details_;
347 std::map<std::string, std::string, std::less<>> properties_;
348 mutable std::unique_ptr<PeSubscriptionManager> sub_mgr_;
349
350 std::vector<uint8_t> handle_discovery(const uint8_t* data, size_t size);
351 std::vector<uint8_t> handle_profile_inquiry(const uint8_t* data, size_t size);
352 std::vector<uint8_t> handle_profile_added(const uint8_t* data, size_t size);
353 std::vector<uint8_t> handle_profile_removed(const uint8_t* data, size_t size);
354 std::vector<uint8_t> handle_profile_details_inquiry(const uint8_t* data, size_t size);
355 void handle_profile_details_reply(const uint8_t* data, size_t size);
356 void handle_profile_specific_data(const uint8_t* data, size_t size);
357 std::vector<uint8_t> handle_subscribe(const uint8_t* data, size_t size);
358 void handle_notify(const uint8_t* data, size_t size);
359};
360
361} // namespace pulp::midi
void add_profile(const ProfileState &profile)
NOT RT-safe — std::vector::push_back may reallocate.
Definition midi_ci.hpp:183
const std::vector< CiDeviceInfo > & discovered_devices() const
RT-safe.
Definition midi_ci.hpp:180
CiDiscovery()
RT-safe (constructs an MUID via std::mt19937; the random_device seed is one-shot at construction).
std::vector< uint8_t > create_discovery_inquiry() const
NOT RT-safe — allocates and returns a SysEx wire buffer.
std::vector< uint8_t > create_profile_specific_data(MUID destination, const ProfileId &id, const uint8_t *data, std::size_t size) const
NOT RT-safe — allocates the wire bytes.
std::function< void(const CiDeviceInfo &)> on_device_discovered
Callbacks.
Definition midi_ci.hpp:305
std::function< void(MUID source, const ProfileId &)> on_profile_removed
Definition midi_ci.hpp:308
std::vector< uint8_t > create_profile_inquiry(MUID destination) const
NOT RT-safe — allocates and returns a SysEx wire buffer.
std::vector< uint8_t > process_message(const uint8_t *data, size_t size)
NOT RT-safe — allocates a response vector and dispatches into handle_* methods that allocate.
const std::vector< ProfileState > & profiles() const
RT-safe. Returns a reference; no allocation.
Definition midi_ci.hpp:185
const std::vector< FunctionBlockInfo > & function_blocks() const
RT-safe. Returns a reference; no allocation.
Definition midi_ci.hpp:219
std::vector< uint8_t > create_profile_removed_report(MUID destination, const ProfileId &id)
NOT RT-safe — allocates the wire bytes.
const std::vector< ProfileDetails > & discovered_profile_details() const
RT-safe.
Definition midi_ci.hpp:274
std::function< void(MUID source, const ProfileId &id, const std::vector< uint8_t > &data)> on_profile_specific_data
Fires when a Profile-Specific Data message arrives.
Definition midi_ci.hpp:302
bool disable_profile(const ProfileId &id)
NOT RT-safe — see enable_profile.
std::vector< uint8_t > create_profile_added_report(MUID destination, const ProfileId &id)
NOT RT-safe — allocates the wire bytes.
std::vector< ProfileDetails > profile_details_for(const ProfileId &id) const
NOT RT-safe — allocates a result vector and copies stored data.
std::optional< std::string > get_property(std::string_view resource) const
RT-safe with caveats: the lookup uses heterogeneous std::less<> so no string allocation occurs for th...
std::function< void(const ProfileId &, bool enabled)> on_profile_changed
Definition midi_ci.hpp:306
bool enable_profile(const ProfileId &id)
NOT RT-safe — invokes the on_profile_changed callback which may allocate.
void set_profile_details(const ProfileId &id, uint8_t target, const std::vector< uint8_t > &data)
NOT RT-safe — copies into a std::map; the value's std::vector<uint8_t> is also copied.
void set_property(std::string_view resource, std::string_view value)
NOT RT-safe — std::map::insert and string construction both allocate.
const CiDeviceInfo & device_info() const
RT-safe. Returns a reference; no allocation.
Definition midi_ci.hpp:160
std::vector< uint8_t > create_profile_details_inquiry(MUID destination, const ProfileId &id, uint8_t target) const
NOT RT-safe — allocates the wire bytes.
std::function< void(MUID source, const ProfileId &)> on_profile_added
Definition midi_ci.hpp:307
std::size_t notify(std::string_view resource, std::string_view header_json, const std::vector< uint8_t > &payload)
NOT RT-safe — invokes subscription_manager() (potentially lazy allocation) and subscribers_of() (allo...
const PeSubscriptionManager & subscription_manager() const
NOT RT-safe — see non-const overload.
std::function< void(MUID source, const ProfileDetails &)> on_profile_details
Definition midi_ci.hpp:309
PeNotifyCallback on_pe_notify
Fires when a Subscribe peer should receive a Notify.
Definition midi_ci.hpp:318
void set_device_info(const CiDeviceInfo &info)
RT-safe. Trivial copy of the small CiDeviceInfo struct.
Definition midi_ci.hpp:158
const FunctionBlockInfo * find_function_block(FunctionBlockId id) const
RT-safe.
Definition midi_ci.hpp:224
MUID local_muid() const
RT-safe. Returns a 4-byte struct by value.
Definition midi_ci.hpp:163
std::vector< uint8_t > create_profile_specific_data(MUID destination, const ProfileId &id, const std::vector< uint8_t > &data) const
Definition midi_ci.hpp:291
PeSubscriptionManager & subscription_manager()
NOT RT-safe — lazily allocates the underlying PeSubscriptionManager (heap allocation through std::mak...
void add_function_block(const FunctionBlockInfo &block)
NOT RT-safe — std::vector::push_back may reallocate.
Definition midi_ci.hpp:215
Authoritative subscription registry on the responder side.
Definition midi_ci_pe.hpp:224
Definition custom_node_events.hpp:26
CiMessageType
MIDI-CI message types (sub-IDs).
Definition midi_ci.hpp:27
@ ProcessReply
Definition midi_ci.hpp:48
@ ProfileDisable
Pulp legacy Profile Disabled report.
Definition midi_ci.hpp:36
@ InvalidateMUID
Definition midi_ci.hpp:30
@ DiscoveryReply
Definition midi_ci.hpp:29
@ ProfileInquiry
Definition midi_ci.hpp:33
@ PropertySetInquiry
Definition midi_ci.hpp:42
@ PropertySubscribeInquiry
Definition midi_ci.hpp:44
@ ProfileReply
Definition midi_ci.hpp:34
@ ProfileDetailsReply
Profile Details Reply (CI 1.2 §7.6).
Definition midi_ci.hpp:38
@ ProfileDetailsInquiry
Profile Details Inquiry (CI 1.2 §7.6).
Definition midi_ci.hpp:37
@ PropertyNotify
Definition midi_ci.hpp:46
@ PropertySubscribeReply
Definition midi_ci.hpp:45
@ ProfileSetOff
Set Profile Off (CI 1.2 §7.5).
Definition midi_ci.hpp:32
@ PropertySetReply
Definition midi_ci.hpp:43
@ ProcessInquiry
Definition midi_ci.hpp:47
@ PropertyGetInquiry
Definition midi_ci.hpp:40
@ ProfileSpecificData
Profile-Specific Data (CI 1.2 §7.7).
Definition midi_ci.hpp:39
@ PropertyGetReply
Definition midi_ci.hpp:41
@ ProfileEnable
Pulp legacy Profile Enabled report.
Definition midi_ci.hpp:35
@ DiscoveryInquiry
Definition midi_ci.hpp:28
@ ProfileSetOn
Set Profile On (CI 1.2 §7.4).
Definition midi_ci.hpp:31
std::function< void(MUID subscriber_muid, std::string_view resource, std::string_view header_json, const std::vector< uint8_t > &payload)> PeNotifyCallback
Notify callback — fires when a Subscribe peer should be notified of a resource change.
Definition midi_ci.hpp:66
CI device identity (from discovery).
Definition midi_ci.hpp:72
uint8_t max_sysex_size
Definition midi_ci.hpp:79
uint16_t model_id
Definition midi_ci.hpp:76
uint16_t family_id
Definition midi_ci.hpp:75
uint32_t manufacturer_id
Definition midi_ci.hpp:74
uint8_t ci_version
Definition midi_ci.hpp:78
MUID muid
Definition midi_ci.hpp:73
uint32_t software_version
Definition midi_ci.hpp:77
UMP Function Block identifier (MIDI 2.0 §A) — a Function Block is a logical grouping of UMP groups th...
Definition midi_ci.hpp:108
uint8_t value
0x7F = broadcast / "all blocks"
Definition midi_ci.hpp:109
bool operator==(const FunctionBlockId &o) const
Definition midi_ci.hpp:113
bool is_broadcast() const
Definition midi_ci.hpp:112
static FunctionBlockId broadcast()
Definition midi_ci.hpp:111
Description of a single Function Block as advertised by an endpoint.
Definition midi_ci.hpp:119
std::string name
UMP Function Block Name (UTF-8).
Definition midi_ci.hpp:121
FunctionBlockId id
Definition midi_ci.hpp:120
uint8_t first_group
First UMP group included (0..15).
Definition midi_ci.hpp:122
bool ui_hint_receiver
Definition midi_ci.hpp:126
bool ui_hint_sender
Definition midi_ci.hpp:127
bool is_midi1
true = MIDI 1 protocol; false = MIDI 2
Definition midi_ci.hpp:125
uint8_t group_count
Number of UMP groups in the block.
Definition midi_ci.hpp:123
bool active
Whether the block is currently advertised.
Definition midi_ci.hpp:124
MIDI-CI MUID (Message Unique Identifier) — 28-bit random ID.
Definition midi_ci.hpp:52
bool operator==(const MUID &o) const
Definition midi_ci.hpp:58
static MUID generate()
bool is_broadcast() const
Definition midi_ci.hpp:57
static MUID broadcast()
Definition midi_ci.hpp:56
uint32_t value
Definition midi_ci.hpp:53
One active subscription state — owned by PeSubscriptionManager.
Definition midi_ci_pe.hpp:205
Profile Details Inquiry/Reply payload (CI 1.2 §7.6) — per-profile metadata the responder publishes wh...
Definition midi_ci.hpp:134
std::vector< uint8_t > data
Profile-defined data payload.
Definition midi_ci.hpp:137
uint8_t target
Profile-defined target byte.
Definition midi_ci.hpp:136
ProfileId id
Definition midi_ci.hpp:135
CI Profile identifier (5 bytes).
Definition midi_ci.hpp:83
uint8_t level
Definition midi_ci.hpp:87
uint8_t version
Definition midi_ci.hpp:86
uint8_t reserved
Definition midi_ci.hpp:88
uint8_t number
Definition midi_ci.hpp:85
uint8_t bank
Definition midi_ci.hpp:84
bool operator==(const ProfileId &o) const
Definition midi_ci.hpp:90
CI Profile state.
Definition midi_ci.hpp:96
uint16_t channel_count
Definition midi_ci.hpp:99
ProfileId id
Definition midi_ci.hpp:97
bool enabled
Definition midi_ci.hpp:98