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/midi_in.hpp>
|
|
|
|
|
#include <libremidi/detail/midi_stream_decoder.hpp>
|
|
|
|
|
|
|
|
|
|
namespace libremidi
|
|
|
|
|
{
|
|
|
|
|
class midi_in_core final
|
|
|
|
|
: public midi1::in_api
|
2024-10-28 19:24:18 +01:00
|
|
|
, public coremidi_data
|
2024-10-09 02:01:23 +02:00
|
|
|
, public error_handler
|
|
|
|
|
{
|
|
|
|
|
public:
|
2024-10-28 19:24:18 +01:00
|
|
|
using midi_api::client_open_;
|
2024-10-09 02:01:23 +02:00
|
|
|
struct
|
|
|
|
|
: input_configuration
|
|
|
|
|
, coremidi_input_configuration
|
|
|
|
|
{
|
|
|
|
|
} configuration;
|
|
|
|
|
|
|
|
|
|
midi_in_core(input_configuration&& conf, coremidi_input_configuration&& apiconf)
|
|
|
|
|
: configuration{std::move(conf), std::move(apiconf)}
|
|
|
|
|
{
|
|
|
|
|
if (auto result = init_client(configuration); 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));
|
|
|
|
|
client_open_ = from_osstatus(result);
|
2024-10-09 02:01:23 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2024-10-28 19:24:18 +01:00
|
|
|
|
|
|
|
|
client_open_ = stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
~midi_in_core() override
|
|
|
|
|
{
|
|
|
|
|
// Close a connection if it exists.
|
|
|
|
|
midi_in_core::close_port();
|
|
|
|
|
|
|
|
|
|
if (this->endpoint)
|
|
|
|
|
MIDIEndpointDispose(this->endpoint);
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
close_client(*this);
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
libremidi::API get_current_api() const noexcept override { return libremidi::API::COREMIDI; }
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error open_port(const input_port& info, std::string_view portName) override
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
|
|
|
|
|
|
|
|
|
|
auto source = locate_object(*this, info, kMIDIObjectType_Source);
|
|
|
|
|
if (source == 0)
|
2024-10-28 19:24:18 +01:00
|
|
|
return std::errc::invalid_argument;
|
2024-10-09 02:01:23 +02:00
|
|
|
|
|
|
|
|
// Create our local sink
|
|
|
|
|
MIDIPortRef port;
|
|
|
|
|
OSStatus result = MIDIInputPortCreate(
|
|
|
|
|
this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &port);
|
|
|
|
|
|
|
|
|
|
if (result != noErr)
|
|
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
close_client(*this);
|
|
|
|
|
libremidi_handle_error(
|
|
|
|
|
this->configuration, "error creating macOS MIDI input port: "
|
2024-10-09 02:01:23 +02:00
|
|
|
+ std::to_string(result));
|
2024-10-28 19:24:18 +01:00
|
|
|
return from_osstatus(result);
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make the connection.
|
|
|
|
|
if (result = MIDIPortConnectSource(port, source, nullptr); result != noErr)
|
|
|
|
|
{
|
|
|
|
|
MIDIPortDispose(port);
|
2024-10-28 19:24:18 +01:00
|
|
|
close_client(*this);
|
|
|
|
|
libremidi_handle_error(
|
|
|
|
|
this->configuration, "error connecting macOS MIDI input port.");
|
|
|
|
|
return from_osstatus(result);
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save our api-specific port information.
|
|
|
|
|
this->port = port;
|
2024-10-28 19:24:18 +01:00
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error open_virtual_port(std::string_view portName) override
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
|
|
|
|
// Create a virtual MIDI input destination.
|
|
|
|
|
MIDIEndpointRef endpoint;
|
|
|
|
|
OSStatus result = MIDIDestinationCreate(
|
|
|
|
|
this->client, toCFString(portName).get(), midiInputCallback, (void*)this, &endpoint);
|
|
|
|
|
|
|
|
|
|
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 virtual macOS MIDI "
|
2024-10-09 02:01:23 +02:00
|
|
|
"destination.");
|
2024-10-28 19:24:18 +01:00
|
|
|
return from_osstatus(result);
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save our api-specific connection information.
|
|
|
|
|
this->endpoint = endpoint;
|
2024-10-28 19:24:18 +01:00
|
|
|
return stdx::error{};
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
stdx::error close_port() override
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
return coremidi_data::close_port();
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timestamp absolute_timestamp() const noexcept override
|
|
|
|
|
{
|
|
|
|
|
return coremidi_data::time_in_nanos(LIBREMIDI_AUDIO_GET_CURRENT_HOST_TIME());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void midiInputCallback(const MIDIPacketList* list, void* procRef, void* /*srcRef*/)
|
|
|
|
|
{
|
|
|
|
|
static constexpr timestamp_backend_info timestamp_info{
|
|
|
|
|
.has_absolute_timestamps = true,
|
|
|
|
|
.absolute_is_monotonic = false,
|
|
|
|
|
.has_samples = false,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto& self = *(midi_in_core*)procRef;
|
|
|
|
|
|
|
|
|
|
const MIDIPacket* packet = &list->packet[0];
|
|
|
|
|
for (unsigned int i = 0; i < list->numPackets; ++i)
|
|
|
|
|
{
|
|
|
|
|
// My interpretation of the CoreMIDI documentation: all message
|
|
|
|
|
// types, except sysex, are complete within a packet and there may
|
|
|
|
|
// be several of them in a single packet. Sysex messages can be
|
|
|
|
|
// broken across multiple packets and PacketLists but are bundled
|
|
|
|
|
// alone within each packet (these packets do not contain other
|
|
|
|
|
// message types). If sysex messages are split across multiple
|
|
|
|
|
// MIDIPacketLists, they must be handled by multiple calls to this
|
|
|
|
|
// function.
|
|
|
|
|
|
2024-10-28 19:24:18 +01:00
|
|
|
if (packet->length > 0)
|
2024-10-09 02:01:23 +02:00
|
|
|
{
|
2024-10-28 19:24:18 +01:00
|
|
|
auto to_ns = [packet] { return time_in_nanos(packet->timeStamp); };
|
|
|
|
|
self.m_processing.on_bytes_multi(
|
|
|
|
|
{packet->data, packet->data + packet->length},
|
|
|
|
|
self.m_processing.timestamp<timestamp_info>(to_ns, 0));
|
2024-10-09 02:01:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
packet = MIDIPacketNext(packet);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
midi1::input_state_machine m_processing{this->configuration};
|
|
|
|
|
};
|
|
|
|
|
}
|