Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
midi_ci_pe.hpp
Go to the documentation of this file.
1#pragma once
2
3// MIDI-CI Property Exchange (PE) — multipart Get/Set messaging plus
4// Subscribe/Notify management on top of the discovery layer.
5//
6// The PE wire format wraps every Get/Set/Subscribe Inquiry and Reply in
7// SysEx envelopes whose body looks like:
8//
9// request_id : 1 byte (1..0x7F; 0 reserved)
10// header_length : 2 bytes 7-bit LE
11// header_data : N bytes Mcoded7-encoded JSON UTF-8
12// total_chunks : 2 bytes 7-bit LE
13// chunk_number : 2 bytes 7-bit LE (1-indexed)
14// payload_length : 2 bytes 7-bit LE
15// payload_data : N bytes Mcoded7-encoded resource bytes
16//
17// Pulp implements the framing + Mcoded7 + JSON-header construction here.
18// Optional zlib payload encoding (MIDI-CI v1.2 §6.3) is exposed through
19// pe_compress()/pe_decompress(); those helpers operate on payload bytes only
20// and do not touch the SysEx envelope or Mcoded7 framing.
21//
22// This file is intentionally header-only-ish: it owns no I/O. The caller
23// drives SysEx in/out around it.
24
25#include <cstddef>
26#include <cstdint>
27#include <functional>
28#include <optional>
29#include <string>
30#include <string_view>
31#include <unordered_map>
32#include <vector>
33
34#include <pulp/midi/midi_ci.hpp>
35
36namespace pulp::midi {
37
39enum class PeMessageType : uint8_t {
40 GetInquiry = 0x34,
41 GetReply = 0x35,
42 SetInquiry = 0x36,
43 SetReply = 0x37,
46 Notify = 0x3F,
47};
48
52struct PeChunk {
53 uint8_t request_id = 0;
54 uint16_t total_chunks = 0;
55 uint16_t chunk_number = 0;
56 std::string header_json;
57 std::vector<uint8_t> payload;
58};
59
70std::vector<uint8_t> pe_build_message(PeMessageType pe_type,
71 uint8_t ci_version,
72 MUID source,
73 MUID destination,
74 uint8_t request_id,
75 std::string_view header_json,
76 uint16_t total_chunks,
77 uint16_t chunk_number,
78 const uint8_t* payload,
79 std::size_t payload_size);
80
81inline std::vector<uint8_t> pe_build_message(PeMessageType pe_type,
82 uint8_t ci_version,
83 MUID source,
84 MUID destination,
85 uint8_t request_id,
86 std::string_view header_json,
87 uint16_t total_chunks,
88 uint16_t chunk_number,
89 const std::vector<uint8_t>& payload) {
90 return pe_build_message(pe_type, ci_version, source, destination,
91 request_id, header_json, total_chunks,
92 chunk_number, payload.data(), payload.size());
93}
94
101std::optional<PeChunk> pe_parse_message(const uint8_t* data, std::size_t size);
102
108std::vector<std::vector<uint8_t>>
110 uint8_t ci_version,
111 MUID source,
112 MUID destination,
113 uint8_t request_id,
114 std::string_view header_json,
115 const uint8_t* payload,
116 std::size_t payload_size,
117 std::size_t max_payload_bytes);
118
129public:
133 std::optional<PeChunk> push(PeChunk chunk);
134
137 void cancel(uint8_t request_id);
138
141 std::size_t pending_transfers() const { return slots_.size(); }
142
143private:
144 struct Slot {
145 uint16_t total = 0;
146 uint16_t received = 0;
147 std::string header_json;
148 std::vector<std::vector<uint8_t>> chunks;
149 std::vector<bool> seen;
150 };
151 std::unordered_map<uint8_t, Slot> slots_;
152};
153
154// ── zlib payload encoding (MIDI-CI 1.2 §6.3) ────────────────────────────
155//
156// PE payloads may be zlib-wrapped (RFC 1950) prior to Mcoded7 encoding
157// when the JSON header advertises `"compressed": "zlib"`. Both helpers
158// are payload-only — they do not touch the SysEx envelope or the
159// Mcoded7 framing.
160//
161// NOT RT-safe — both calls heap-allocate the output buffer and run
162// `deflate` / `inflate` internally.
163
165std::optional<std::vector<uint8_t>> pe_compress(const uint8_t* data, std::size_t size);
166
167inline std::optional<std::vector<uint8_t>> pe_compress(const std::vector<uint8_t>& payload) {
168 return pe_compress(payload.data(), payload.size());
169}
170
172std::optional<std::vector<uint8_t>> pe_decompress(const uint8_t* data, std::size_t size);
173
174inline std::optional<std::vector<uint8_t>> pe_decompress(const std::vector<uint8_t>& blob) {
175 return pe_decompress(blob.data(), blob.size());
176}
177
187std::string pe_header_make(std::string_view resource,
188 std::string_view command = {},
189 int status = 200);
190
197bool pe_header_parse(std::string_view json,
198 std::string* resource,
199 std::string* command,
200 int* status);
201
202// ── Subscription management ─────────────────────────────────────────────
203
206 std::string subscription_id;
207 std::string resource;
209};
210
225public:
229 std::string subscribe(std::string_view resource, MUID subscriber);
230
232 bool unsubscribe(std::string_view subscription_id);
233
238 std::vector<PeSubscription> subscribers_of(std::string_view resource) const;
239
243 const std::vector<PeSubscription>& all() const { return subs_; }
244
245private:
246 std::vector<PeSubscription> subs_;
247 uint64_t next_id_ = 1;
248};
249
250} // namespace pulp::midi
Reassemble a stream of incoming PeChunks for the same request_id.
Definition midi_ci_pe.hpp:128
std::size_t pending_transfers() const
RT-safe.
Definition midi_ci_pe.hpp:141
void cancel(uint8_t request_id)
NOT RT-safe — unordered_map::erase may rehash and frees the per-slot vectors.
std::optional< PeChunk > push(PeChunk chunk)
NOT RT-safe — inserts/erases on unordered_map, resizes per-slot vector<bool> and vector<vector<uint8_...
Authoritative subscription registry on the responder side.
Definition midi_ci_pe.hpp:224
std::string subscribe(std::string_view resource, MUID subscriber)
NOT RT-safe — allocates a new PeSubscription (two std::strings), pushes onto subs_,...
bool unsubscribe(std::string_view subscription_id)
NOT RT-safe — linear scan + std::vector::erase.
std::vector< PeSubscription > subscribers_of(std::string_view resource) const
NOT RT-safe — allocates the result std::vector<PeSubscription>, even when the call ends up empty (the...
const std::vector< PeSubscription > & all() const
Non-owning live view of the registry.
Definition midi_ci_pe.hpp:243
Definition custom_node_events.hpp:26
PeMessageType
PE sub-IDs (MIDI-CI sub-ID #2 byte). Mirrors CiMessageType for PE.
Definition midi_ci_pe.hpp:39
@ SubscribeReply
Definition midi_ci_pe.hpp:45
@ SetInquiry
Definition midi_ci_pe.hpp:42
@ SetReply
Definition midi_ci_pe.hpp:43
@ Notify
Definition midi_ci_pe.hpp:46
@ SubscribeInquiry
Definition midi_ci_pe.hpp:44
@ GetInquiry
Definition midi_ci_pe.hpp:40
@ GetReply
Definition midi_ci_pe.hpp:41
std::string pe_header_make(std::string_view resource, std::string_view command={}, int status=200)
Builds a minimal PE JSON header.
std::optional< PeChunk > pe_parse_message(const uint8_t *data, std::size_t size)
Parse a single PE SysEx envelope into a PeChunk.
bool pe_header_parse(std::string_view json, std::string *resource, std::string *command, int *status)
Parse a PE JSON header.
std::vector< std::vector< uint8_t > > pe_split_into_chunks(PeMessageType pe_type, uint8_t ci_version, MUID source, MUID destination, uint8_t request_id, std::string_view header_json, const uint8_t *payload, std::size_t payload_size, std::size_t max_payload_bytes)
Split a logical PE Get/Set into wire-ready chunks bounded by max_payload_bytes of decoded resource pe...
std::vector< uint8_t > pe_build_message(PeMessageType pe_type, uint8_t ci_version, MUID source, MUID destination, uint8_t request_id, std::string_view header_json, uint16_t total_chunks, uint16_t chunk_number, const uint8_t *payload, std::size_t payload_size)
Builder for PE message wire bytes.
std::optional< std::vector< uint8_t > > pe_decompress(const uint8_t *data, std::size_t size)
Decompress a zlib-wrapped PE payload. Returns nullopt on failure.
std::optional< std::vector< uint8_t > > pe_compress(const uint8_t *data, std::size_t size)
Compress a PE payload using zlib (RFC 1950). Returns nullopt on failure.
MIDI-CI MUID (Message Unique Identifier) — 28-bit random ID.
Definition midi_ci.hpp:52
A single PE chunk, after Mcoded7 decode of header + payload.
Definition midi_ci_pe.hpp:52
uint16_t chunk_number
1-indexed
Definition midi_ci_pe.hpp:55
std::vector< uint8_t > payload
Decoded resource bytes.
Definition midi_ci_pe.hpp:57
std::string header_json
Decoded JSON UTF-8.
Definition midi_ci_pe.hpp:56
uint16_t total_chunks
Definition midi_ci_pe.hpp:54
uint8_t request_id
Definition midi_ci_pe.hpp:53
One active subscription state — owned by PeSubscriptionManager.
Definition midi_ci_pe.hpp:205
MUID subscriber
Definition midi_ci_pe.hpp:208
std::string resource
Definition midi_ci_pe.hpp:207
std::string subscription_id
Server-allocated, opaque to client.
Definition midi_ci_pe.hpp:206