#pragma once #include #include #include #include #include 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) { libremidi_handle_error( this->configuration, "error creating MIDI client object: " + std::to_string(result)); 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 auto to_port_info(MIDIObjectRef obj) const noexcept -> std::optional> { MIDIEntityRef e{}; MIDIEndpointGetEntity(obj, &e); bool physical = bool(e); bool ok = this->configuration.track_any; if (physical && this->configuration.track_hardware) ok |= true; else if ((!physical) && this->configuration.track_virtual) ok |= true; if (!ok) return {}; // 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); } } } return std::conditional_t{ {.client = (std::uintptr_t)this->client, .container = usb_location_id, .device = usb_vendor_product, .port = std::bit_cast(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), .display_name = get_string_property(obj, kMIDIPropertyDisplayName), .type = type}}; } std::vector get_input_ports() const noexcept override { std::vector ret; CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); for (ItemCount i = 0; i < MIDIGetNumberOfSources(); i++) { if (auto p = to_port_info(MIDIGetSource(i))) ret.push_back(std::move(*p)); } return ret; } std::vector get_output_ports() const noexcept override { std::vector ret; CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false); for (ItemCount i = 0; i < MIDIGetNumberOfDestinations(); i++) { if (auto p = to_port_info(MIDIGetDestination(i))) ret.push_back(std::move(*p)); } return ret; } void notify(const MIDINotification* message) { switch (message->messageID) { case kMIDIMsgObjectAdded: { auto obj = reinterpret_cast(message); switch (obj->childType) { case kMIDIObjectType_Source: if (auto& cb = configuration.input_added) if (auto p = to_port_info(obj->child)) cb(std::move(*p)); break; case kMIDIObjectType_Destination: if (auto& cb = configuration.output_added) if (auto p = to_port_info(obj->child)) cb(std::move(*p)); break; default: break; } break; } case kMIDIMsgObjectRemoved: { auto obj = reinterpret_cast(message); switch (obj->childType) { case kMIDIObjectType_Source: if (auto& cb = configuration.input_removed) if (auto p = to_port_info(obj->child)) cb(std::move(*p)); break; case kMIDIObjectType_Destination: if (auto& cb = configuration.output_removed) if (auto p = to_port_info(obj->child)) cb(std::move(*p)); break; default: break; } break; } default: break; } } ~observer_core() { MIDIClientDispose(this->client); } private: MIDIClientRef client{}; }; }