2024-10-09 02:01:23 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <libremidi/backends/coremidi/config.hpp>
|
|
|
|
|
#include <libremidi/backends/coremidi/helpers.hpp>
|
|
|
|
|
#include <libremidi/detail/observer.hpp>
|
|
|
|
|
|
|
|
|
|
#include <CoreMIDI/CoreMIDI.h>
|
|
|
|
|
|
|
|
|
|
#include <bit>
|
|
|
|
|
|
|
|
|
|
namespace libremidi
|
|
|
|
|
{
|
|
|
|
|
class observer_core
|
|
|
|
|
: public observer_api
|
|
|
|
|
, public error_handler
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
struct
|
|
|
|
|
: observer_configuration
|
|
|
|
|
, coremidi_observer_configuration
|
|
|
|
|
{
|
|
|
|
|
} configuration;
|
|
|
|
|
|
|
|
|
|
explicit observer_core(observer_configuration&& conf, coremidi_observer_configuration&& apiconf)
|
|
|
|
|
: configuration{std::move(conf), std::move(apiconf)}
|
|
|
|
|
{
|
|
|
|
|
if (configuration.client_name.empty())
|
|
|
|
|
configuration.client_name = "libremidi observer";
|
|
|
|
|
|
|
|
|
|
if (!configuration.has_callbacks())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
auto result = MIDIClientCreate(
|
|
|
|
|
toCFString(configuration.client_name).get(),
|
|
|
|
|
+[](const MIDINotification* message, void* ctx) {
|
|
|
|
|
((observer_core*)ctx)->notify(message);
|
|
|
|
|
},
|
|
|
|
|
this, &client);
|
|
|
|
|
|
|
|
|
|
if (result != noErr)
|
|
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
libremidi_handle_error(
|
2024-10-09 02:01:23 +02:00
|
|
|
this->configuration,
|
2024-10-28 19:24:18 +01:00
|
|
|
"error creating MIDI client object: " + std::to_string(result));
|
2024-10-09 02:01:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (configuration.on_create_context)
|
|
|
|
|
configuration.on_create_context(client);
|
|
|
|
|
|
|
|
|
|
if (configuration.notify_in_constructor)
|
|
|
|
|
{
|
|
|
|
|
if (this->configuration.input_added)
|
|
|
|
|
for (auto& p : get_input_ports())
|
|
|
|
|
this->configuration.input_added(p);
|
|
|
|
|
|
|
|
|
|
if (this->configuration.output_added)
|
|
|
|
|
for (auto& p : get_output_ports())
|
|
|
|
|
this->configuration.output_added(p);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
|
|
|
|
|
|
|
|
|
template <bool Input>
|
|
|
|
|
auto to_port_info(MIDIObjectRef obj) const noexcept
|
|
|
|
|
-> std::optional<std::conditional_t<Input, input_port, output_port>>
|
|
|
|
|
{
|
|
|
|
|
MIDIEntityRef e{};
|
|
|
|
|
MIDIEndpointGetEntity(obj, &e);
|
|
|
|
|
bool physical = bool(e);
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
bool ok = this->configuration.track_any;
|
2024-10-09 02:01:23 +02:00
|
|
|
if (physical && this->configuration.track_hardware)
|
2024-10-28 19:24:18 +01:00
|
|
|
ok |= true;
|
2024-10-09 02:01:23 +02:00
|
|
|
else if ((!physical) && this->configuration.track_virtual)
|
2024-10-28 19:24:18 +01:00
|
|
|
ok |= true;
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
|
return {};
|
|
|
|
|
|
2025-06-13 19:06:09 +02:00
|
|
|
// Get the MIDI device from the entity
|
|
|
|
|
libremidi::port_information::port_type type{};
|
|
|
|
|
libremidi::container_identifier usb_location_id{};
|
|
|
|
|
libremidi::device_identifier usb_vendor_product{};
|
|
|
|
|
{
|
|
|
|
|
MIDIDeviceRef device{};
|
|
|
|
|
if (MIDIEntityGetDevice(e, &device) == noErr)
|
|
|
|
|
{
|
|
|
|
|
if (device)
|
|
|
|
|
{
|
|
|
|
|
using enum libremidi::port_information::port_type;
|
|
|
|
|
SInt32 devid_res{};
|
|
|
|
|
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBLocationID"), &devid_res) == noErr)
|
|
|
|
|
{
|
|
|
|
|
type = (libremidi::port_information::port_type)(hardware | usb);
|
|
|
|
|
usb_location_id = (uint64_t)devid_res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SInt32 vendor_res{};
|
|
|
|
|
if (MIDIObjectGetIntegerProperty(device, CFSTR("USBVendorProduct"), &vendor_res)
|
|
|
|
|
== noErr)
|
|
|
|
|
{
|
|
|
|
|
type = (libremidi::port_information::port_type)(hardware | usb);
|
|
|
|
|
usb_vendor_product = (uint64_t)vendor_res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const auto driver = get_string_property(device, kMIDIPropertyDriverOwner);
|
|
|
|
|
if (driver == "com.apple.AppleMIDIUSBDriver")
|
|
|
|
|
type = (libremidi::port_information::port_type)(hardware | usb);
|
|
|
|
|
if (driver == "com.apple.AppleMIDIBluetoothDriver")
|
|
|
|
|
type = (libremidi::port_information::port_type)(hardware | bluetooth);
|
|
|
|
|
if (driver == "com.apple.AppleMIDIIACDriver")
|
|
|
|
|
type = (libremidi::port_information::port_type)(software);
|
|
|
|
|
if (driver == "com.apple.AppleMIDIRTPDriver")
|
|
|
|
|
type = (libremidi::port_information::port_type)(network);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-09 02:01:23 +02:00
|
|
|
return std::conditional_t<Input, input_port, output_port>{
|
|
|
|
|
{.client = (std::uintptr_t)this->client,
|
2025-06-13 19:06:09 +02:00
|
|
|
.container = usb_location_id,
|
|
|
|
|
.device = usb_vendor_product,
|
2024-10-09 02:01:23 +02:00
|
|
|
.port = std::bit_cast<uint32_t>(get_int_property(obj, kMIDIPropertyUniqueID)),
|
|
|
|
|
.manufacturer = get_string_property(obj, kMIDIPropertyManufacturer),
|
|
|
|
|
.device_name = get_string_property(obj, kMIDIPropertyModel),
|
|
|
|
|
.port_name = get_string_property(obj, kMIDIPropertyName),
|
2025-06-13 19:06:09 +02:00
|
|
|
.display_name = get_string_property(obj, kMIDIPropertyDisplayName),
|
|
|
|
|
.type = type}};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<libremidi::input_port> get_input_ports() const noexcept override
|
|
|
|
|
{
|
|
|
|
|
std::vector<libremidi::input_port> ret;
|
|
|
|
|
|
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
|
|
|
|
for (ItemCount i = 0; i < MIDIGetNumberOfSources(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (auto p = to_port_info<true>(MIDIGetSource(i)))
|
|
|
|
|
ret.push_back(std::move(*p));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<libremidi::output_port> get_output_ports() const noexcept override
|
|
|
|
|
{
|
|
|
|
|
std::vector<libremidi::output_port> ret;
|
|
|
|
|
|
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
|
|
|
|
for (ItemCount i = 0; i < MIDIGetNumberOfDestinations(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (auto p = to_port_info<false>(MIDIGetDestination(i)))
|
|
|
|
|
ret.push_back(std::move(*p));
|
|
|
|
|
}
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void notify(const MIDINotification* message)
|
|
|
|
|
{
|
|
|
|
|
switch (message->messageID)
|
|
|
|
|
{
|
|
|
|
|
case kMIDIMsgObjectAdded: {
|
|
|
|
|
auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
|
|
|
|
|
|
|
|
|
|
switch (obj->childType)
|
|
|
|
|
{
|
|
|
|
|
case kMIDIObjectType_Source:
|
|
|
|
|
if (auto& cb = configuration.input_added)
|
|
|
|
|
if (auto p = to_port_info<true>(obj->child))
|
|
|
|
|
cb(std::move(*p));
|
|
|
|
|
break;
|
|
|
|
|
case kMIDIObjectType_Destination:
|
|
|
|
|
if (auto& cb = configuration.output_added)
|
|
|
|
|
if (auto p = to_port_info<false>(obj->child))
|
|
|
|
|
cb(std::move(*p));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case kMIDIMsgObjectRemoved: {
|
|
|
|
|
auto obj = reinterpret_cast<const MIDIObjectAddRemoveNotification*>(message);
|
|
|
|
|
|
|
|
|
|
switch (obj->childType)
|
|
|
|
|
{
|
|
|
|
|
case kMIDIObjectType_Source:
|
|
|
|
|
if (auto& cb = configuration.input_removed)
|
|
|
|
|
if (auto p = to_port_info<true>(obj->child))
|
|
|
|
|
cb(std::move(*p));
|
|
|
|
|
break;
|
|
|
|
|
case kMIDIObjectType_Destination:
|
|
|
|
|
if (auto& cb = configuration.output_removed)
|
|
|
|
|
if (auto p = to_port_info<false>(obj->child))
|
|
|
|
|
cb(std::move(*p));
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~observer_core() { MIDIClientDispose(this->client); }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
MIDIClientRef client{};
|
|
|
|
|
};
|
|
|
|
|
}
|