MIDI-CI Discovery manager — handles CI message exchange. More...
#include <midi_ci.hpp>
Public Member Functions | |
| CiDiscovery () | |
| RT-safe (constructs an MUID via std::mt19937; the random_device seed is one-shot at construction). | |
| ~CiDiscovery () | |
| RT-safe. | |
| void | set_device_info (const CiDeviceInfo &info) |
| RT-safe. Trivial copy of the small CiDeviceInfo struct. | |
| const CiDeviceInfo & | device_info () const |
| RT-safe. Returns a reference; no allocation. | |
| MUID | local_muid () const |
| RT-safe. Returns a 4-byte struct by value. | |
| 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. | |
| std::vector< uint8_t > | create_discovery_inquiry () const |
| NOT RT-safe — allocates and returns a SysEx wire buffer. | |
| std::vector< uint8_t > | create_profile_inquiry (MUID destination) const |
| NOT RT-safe — allocates and returns a SysEx wire buffer. | |
| const std::vector< CiDeviceInfo > & | discovered_devices () const |
| RT-safe. | |
| void | add_profile (const ProfileState &profile) |
| NOT RT-safe — std::vector::push_back may reallocate. | |
| const std::vector< ProfileState > & | profiles () const |
| RT-safe. Returns a reference; no allocation. | |
| bool | enable_profile (const ProfileId &id) |
| NOT RT-safe — invokes the on_profile_changed callback which may allocate. | |
| bool | disable_profile (const ProfileId &id) |
| NOT RT-safe — see enable_profile. | |
| void | set_property (std::string_view resource, std::string_view value) |
| NOT RT-safe — std::map::insert and string construction both allocate. | |
| 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 the search key. | |
| void | add_function_block (const FunctionBlockInfo &block) |
| NOT RT-safe — std::vector::push_back may reallocate. | |
| const std::vector< FunctionBlockInfo > & | function_blocks () const |
| RT-safe. Returns a reference; no allocation. | |
| const FunctionBlockInfo * | find_function_block (FunctionBlockId id) const |
| RT-safe. | |
| std::vector< uint8_t > | create_profile_added_report (MUID destination, const ProfileId &id) |
| NOT RT-safe — allocates the wire bytes. | |
| std::vector< uint8_t > | create_profile_removed_report (MUID destination, const ProfileId &id) |
| NOT RT-safe — allocates the wire bytes. | |
| 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. | |
| std::vector< ProfileDetails > | profile_details_for (const ProfileId &id) const |
| NOT RT-safe — allocates a result vector and copies stored data. | |
| std::vector< uint8_t > | create_profile_details_inquiry (MUID destination, const ProfileId &id, uint8_t target) const |
| NOT RT-safe — allocates the wire bytes. | |
| const std::vector< ProfileDetails > & | discovered_profile_details () const |
| RT-safe. | |
| 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::vector< uint8_t > | create_profile_specific_data (MUID destination, const ProfileId &id, const std::vector< uint8_t > &data) const |
| PeSubscriptionManager & | subscription_manager () |
| NOT RT-safe — lazily allocates the underlying PeSubscriptionManager (heap allocation through std::make_unique). | |
| const PeSubscriptionManager & | subscription_manager () const |
| NOT RT-safe — see non-const overload. | |
| 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() (allocates a result vector), then invokes the on_pe_notify callback once per binding (callback body unknown to us). | |
Public Attributes | |
| 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. | |
| std::function< void(const CiDeviceInfo &)> | on_device_discovered |
| Callbacks. | |
| std::function< void(const ProfileId &, bool enabled)> | on_profile_changed |
| std::function< void(MUID source, const ProfileId &)> | on_profile_added |
| std::function< void(MUID source, const ProfileId &)> | on_profile_removed |
| std::function< void(MUID source, const ProfileDetails &)> | on_profile_details |
| PeNotifyCallback | on_pe_notify |
| Fires when a Subscribe peer should receive a Notify. | |
MIDI-CI Discovery manager — handles CI message exchange.
RT-safety contract (audited 2026-05-26): every public method is annotated below. As a rule, CI is a UI/main thread surface (SysEx is slow, JSON parsing allocates, subscription state mutates). Call from the audio thread only methods explicitly tagged RT-safe.
| pulp::midi::CiDiscovery::CiDiscovery | ( | ) |
RT-safe (constructs an MUID via std::mt19937; the random_device seed is one-shot at construction).
Construct from a non-RT thread.
| pulp::midi::CiDiscovery::~CiDiscovery | ( | ) |
RT-safe.
Destructor frees the optional PeSubscriptionManager (heap free) — not RT-safe to call from the audio thread.
|
inline |
NOT RT-safe — std::vector::push_back may reallocate.
Call from the main / setup thread before the responder is exposed to peers.
|
inline |
NOT RT-safe — std::vector::push_back may reallocate.
| std::vector< uint8_t > pulp::midi::CiDiscovery::create_discovery_inquiry | ( | ) | const |
NOT RT-safe — allocates and returns a SysEx wire buffer.
| std::vector< uint8_t > pulp::midi::CiDiscovery::create_profile_added_report | ( | MUID | destination, |
| const ProfileId & | id ) |
NOT RT-safe — allocates the wire bytes.
Send this from the responder when a profile becomes available so connected inquirers can update their cache without re-polling.
| std::vector< uint8_t > pulp::midi::CiDiscovery::create_profile_details_inquiry | ( | MUID | destination, |
| const ProfileId & | id, | ||
| uint8_t | target ) const |
NOT RT-safe — allocates the wire bytes.
| std::vector< uint8_t > pulp::midi::CiDiscovery::create_profile_inquiry | ( | MUID | destination | ) | const |
NOT RT-safe — allocates and returns a SysEx wire buffer.
| std::vector< uint8_t > pulp::midi::CiDiscovery::create_profile_removed_report | ( | MUID | destination, |
| const ProfileId & | id ) |
NOT RT-safe — allocates the wire bytes.
Mirror of the above for a profile that's just been removed.
|
inline |
| std::vector< uint8_t > pulp::midi::CiDiscovery::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.
|
inline |
RT-safe. Returns a reference; no allocation.
| bool pulp::midi::CiDiscovery::disable_profile | ( | const ProfileId & | id | ) |
NOT RT-safe — see enable_profile.
|
inline |
RT-safe.
Returns a reference to the internal vector; no allocation. The vector itself may grow on a non-RT thread via process_message(); readers should treat this as a snapshot, not a live view.
|
inline |
RT-safe.
Returns a reference; no allocation. The vector mutates on the dispatch thread; treat as a snapshot.
| bool pulp::midi::CiDiscovery::enable_profile | ( | const ProfileId & | id | ) |
NOT RT-safe — invokes the on_profile_changed callback which may allocate.
The internal lookup is O(n) but allocation-free.
|
inline |
RT-safe.
Linear scan over the function-block vector — small N (≤ 32 by spec).
|
inline |
RT-safe. Returns a reference; no allocation.
| std::optional< std::string > pulp::midi::CiDiscovery::get_property | ( | std::string_view | resource | ) | const |
RT-safe with caveats: the lookup uses heterogeneous std::less<> so no string allocation occurs for the search key.
HOWEVER the return path constructs a std::string from the stored value, which allocates. Do not call from the audio thread. Use subscription_manager().subscribers_of() only on a non-RT thread.
|
inline |
RT-safe. Returns a 4-byte struct by value.
| std::size_t pulp::midi::CiDiscovery::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() (allocates a result vector), then invokes the on_pe_notify callback once per binding (callback body unknown to us).
Drive Notify from the same non-RT thread that produced the resource change.
| std::vector< uint8_t > pulp::midi::CiDiscovery::process_message | ( | const uint8_t * | data, |
| size_t | size ) |
NOT RT-safe — allocates a response vector and dispatches into handle_* methods that allocate.
Call from the MIDI / main thread, never from the audio callback. Returns an empty vector when no reply is warranted.
| std::vector< ProfileDetails > pulp::midi::CiDiscovery::profile_details_for | ( | const ProfileId & | id | ) | const |
NOT RT-safe — allocates a result vector and copies stored data.
|
inline |
RT-safe. Returns a reference; no allocation.
|
inline |
RT-safe. Trivial copy of the small CiDeviceInfo struct.
| void pulp::midi::CiDiscovery::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 pulp::midi::CiDiscovery::set_property | ( | std::string_view | resource, |
| std::string_view | value ) |
NOT RT-safe — std::map::insert and string construction both allocate.
Call from the main / property-mutation thread.
| PeSubscriptionManager & pulp::midi::CiDiscovery::subscription_manager | ( | ) |
NOT RT-safe — lazily allocates the underlying PeSubscriptionManager (heap allocation through std::make_unique).
Construct or warm up the manager from the main thread before any RT thread observes it.
| const PeSubscriptionManager & pulp::midi::CiDiscovery::subscription_manager | ( | ) | const |
NOT RT-safe — see non-const overload.
The const overload performs the same lazy allocation because sub_mgr_ is mutable.
| std::function<void(const CiDeviceInfo&)> pulp::midi::CiDiscovery::on_device_discovered |
Callbacks.
| PeNotifyCallback pulp::midi::CiDiscovery::on_pe_notify |
Fires when a Subscribe peer should receive a Notify.
Set this to wire the manager's fan-out to the actual MIDI transport. Subscribe/Notify dispatcher.
The callback itself is invoked from notify() / handle_notify() and is therefore on the same thread as those calls — NOT the audio thread.
| std::function<void(const ProfileId&, bool enabled)> pulp::midi::CiDiscovery::on_profile_changed |
| std::function<void(MUID source, const ProfileDetails&)> pulp::midi::CiDiscovery::on_profile_details |
| std::function<void(MUID source, const ProfileId& id, const std::vector<uint8_t>& data)> pulp::midi::CiDiscovery::on_profile_specific_data |
Fires when a Profile-Specific Data message arrives.
The callback runs on the same thread that called process_message() — NOT the audio thread.