diff --git a/bindings/zig/examples/zig_api.zig b/bindings/zig/examples/zig_api.zig index 6060a3d1..698af454 100644 --- a/bindings/zig/examples/zig_api.zig +++ b/bindings/zig/examples/zig_api.zig @@ -1,7 +1,6 @@ const std = @import("std"); const lm = @import("libremidi"); - const EnumeratedPorts = extern struct { in_ports: [256]?*lm.midi.In.Port = @splat(null), out_ports: [256]?*lm.midi.Out.Port = @splat(null), @@ -9,10 +8,41 @@ const EnumeratedPorts = extern struct { out_port_count: usize = 0, }; -pub fn main() !void { +pub fn print_identifier(name: []const u8, ident: lm.PortInformation.Identifier) void { + std.debug.print(" {s}: ", .{name}); + const tagged = ident.asTagged(); + switch (tagged) { + .none => std.debug.print("none\n", .{}), + .uuid => |uuid| { + std.debug.print("uuid: ", .{}); + for (uuid, 0..) |b, i| { + std.debug.print("{}", .{b}); + if (i != uuid.len - 1) std.debug.print("-", .{}); + } + std.debug.print("\n", .{}); + }, + .string => |s| std.debug.print("{s}\n", .{s}), + .uint64 => |v| std.debug.print("{d}\n", .{v}), + } +} +pub fn print_port_info(info: lm.PortInformation) void { + print_identifier("container_identification", info.container_identifier); + print_identifier("device_identification", info.device_identifier); + + std.debug.print(" client_handle: {d}\n", .{info.client_handle}); + std.debug.print(" port_handle: {d}\n", .{info.port_handle}); + std.debug.print(" manufacturer: {s}\n", .{info.manufacturer}); + std.debug.print(" device_name: {s}\n", .{info.device_name}); + std.debug.print(" port_name: {s}\n", .{info.port_name}); + std.debug.print(" display_name: {s}\n", .{info.display_name}); + + std.debug.print(" port type: {}\n", .{info.type}); +} + +pub fn main() !void { std.debug.print("Hello from libremidi Zig(ified) API example!\n", .{}); - std.debug.print("libremidi version: {s}\n\n", .{ lm.getVersion() }); + std.debug.print("libremidi version: {s}\n\n", .{lm.getVersion()}); var e: EnumeratedPorts = .{}; @@ -44,7 +74,7 @@ pub fn main() !void { .conf_type = .input, .api = .alsa_seq, }); - defer midi_in.free(); + defer midi_in.deinit(); const midi_out: *lm.midi.Out = try .init(&.{ .version = .midi1, @@ -54,23 +84,24 @@ pub fn main() !void { .conf_type = .output, .api = .alsa_seq, }); - defer midi_out.free(); - + defer midi_out.deinit(); for (0..99) |_| std.time.sleep(1e9); // sleep 1s, 100 times } fn free_observer(observer: *lm.Observer, e: *EnumeratedPorts) void { + for (e.in_ports) |maybe_port| if (maybe_port) |port| port.deinit(); + for (e.out_ports) |maybe_port| if (maybe_port) |port| port.deinit(); - for (e.in_ports) |maybe_port| if (maybe_port) |port| port.free(); - for (e.out_ports) |maybe_port| if (maybe_port) |port| port.free(); - - observer.free(); + observer.deinit(); } export fn on_input_port_found(ctx: ?*anyopaque, port: *lm.midi.In.Port) callconv(.C) void { - - std.debug.print("input: {s}\n", .{ port.getName() catch "" }); + std.debug.print("input: {s}\n", .{port.getName() catch ""}); + const info_maybe = port.getInformation() catch null; + if (info_maybe) |info| { + print_port_info(info); + } var e: *EnumeratedPorts = @ptrCast(@alignCast(ctx)); e.in_ports[e.in_port_count] = port.clone() catch null; @@ -78,8 +109,11 @@ export fn on_input_port_found(ctx: ?*anyopaque, port: *lm.midi.In.Port) callconv } export fn on_output_port_found(ctx: ?*anyopaque, port: *lm.midi.Out.Port) callconv(.C) void { - - std.debug.print("output: {s}\n", .{ port.getName() catch "" }); + std.debug.print("output: {s}\n", .{port.getName() catch ""}); + const info_maybe = port.getInformation() catch null; + if (info_maybe) |info| { + print_port_info(info); + } var e: *EnumeratedPorts = @ptrCast(@alignCast(ctx)); e.out_ports[e.out_port_count] = port.clone() catch null; diff --git a/bindings/zig/libremidi.zig b/bindings/zig/libremidi.zig index 81bce76d..f038f88d 100644 --- a/bindings/zig/libremidi.zig +++ b/bindings/zig/libremidi.zig @@ -6,7 +6,6 @@ fn errnoFromInt(rc: anytype) E { return @enumFromInt(-rc); } - extern fn libremidi_get_version() [*:0]const u8; pub const getVersion = libremidi_get_version; @@ -35,7 +34,6 @@ pub const Api = enum(c.libremidi_api) { dummy = c.DUMMY, - extern fn libremidi_api_identifier(self: Api) [*:0]const u8; pub const getId = libremidi_api_identifier; @@ -46,7 +44,6 @@ pub const Api = enum(c.libremidi_api) { pub const getById = libremidi_get_compiled_api_by_identifier; pub const Config = extern struct { - api: Api = .unspecified, conf_type: enum(@FieldType(c.libremidi_api_configuration, "configuration_type")) { @@ -59,13 +56,12 @@ pub const Api = enum(c.libremidi_api) { }; fn Callback(comptime P: type) type { - return (?*const fn(ctx: P, api: Api) callconv(.C) void); + return (?*const fn (ctx: P, api: Api) callconv(.C) void); } }; pub const Timestamp = extern struct { - inner: c.libremidi_timestamp = 0, - + inner: c.libremidi_timestamp = 0, pub const Mode = enum(c.enum_libremidi_timestamp_mode) { no_timestamp = c.NoTimestamp, @@ -80,17 +76,87 @@ pub const Timestamp = extern struct { fn Callback(comptime P: type) type { return extern struct { context: P = null, - callback: (?*const fn(ctx: P, ts: Timestamp) callconv(.C) Timestamp) = null, + callback: (?*const fn (ctx: P, ts: Timestamp) callconv(.C) Timestamp) = null, }; } }; +pub const PortInformation = extern struct { + pub const Type = enum(u8) { + unknown = c.PORT_UNKNOWN, + software = c.PORT_SOFTWARE, + loopback = c.PORT_LOOPBACK, + hardware = c.PORT_HARDWARE, + usb = c.PORT_USB, + bluetooth = c.PORT_BLUETOOTH, + pci = c.PORT_PCI, + network = c.PORT_NETWORK, + }; + + pub const Identifier = extern struct { + const Type = enum(u8) { + none = c.LIBREMIDI_ID_NONE, + uuid = c.LIBREMIDI_ID_UUID, + string = c.LIBREMIDI_ID_STRING, + uint64 = c.LIBREMIDI_ID_UINT64, + }; + + value_type: Identifier.Type, + value: extern union { + uuid: [16]u8, + string: [*:0]const u8, + u64: u64, + }, + + pub const Tagged = union(Identifier.Type) { + none: void, + uuid: [16]u8, + string: []const u8, + uint64: u64, + }; + + pub fn asTagged(self: Identifier) Tagged { + return switch (self.value_type) { + .none => .{ .none = {} }, + .uuid => .{ .uuid = self.value.uuid }, + .string => .{ .string = std.mem.span(self.value.string) }, + .uint64 => .{ .uint64 = self.value.u64 }, + }; + } + + pub fn eql(a: Identifier, b: Identifier) bool { + const at = a.asTagged(); + const bt = b.asTagged(); + + if (@as(Identifier.Type, at) != @as(Identifier.Type, bt)) return false; + + return switch (at) { + .none => true, + .uuid => std.mem.eql(u8, &at.uuid, &bt.uuid), + .string => std.mem.eql(u8, at.string, bt.string), + .uint64 => at.uint64 == bt.uint64, + }; + } + }; + + client_handle: u64, + container_identifier: Identifier, + device_identifier: Identifier, + port_handle: u64, + + manufacturer: [*:0]const u8, + device_name: [*:0]const u8, + port_name: [*:0]const u8, + display_name: [*:0]const u8, + + type: Type, +}; + pub const Observer = opaque { const Ctx = ?*anyopaque; extern fn libremidi_midi_observer_new(conf: ?*const Config, api: ?*const Api.Config, out: *?*Observer) c_int; pub fn init(conf: ?*const Config, api: ?*const Api.Config) !*Observer { - var handle: ?*Observer = undefined; switch (errnoFromInt(libremidi_midi_observer_new(conf, api, &handle))) { .SUCCESS => return handle.?, @@ -119,7 +185,7 @@ pub const Observer = opaque { } extern fn libremidi_midi_observer_free(self: *Observer) c_int; - pub fn free(self: *Observer) void { + pub fn deinit(self: *Observer) void { switch (libremidi_midi_observer_free(self)) { 0 => return, else => unreachable, @@ -127,7 +193,6 @@ pub const Observer = opaque { } pub const Config = extern struct { - on_error: ErrCallback(Ctx) = .{ .context = null, .callback = null }, on_warning: ErrCallback(Ctx) = .{ .context = null, .callback = null }, input_added: InputCallback(Ctx) = .{ .context = null, .callback = null }, @@ -139,13 +204,12 @@ pub const Observer = opaque { track_any: bool = false, notify_in_constructor: bool = false, - fn ErrCallback(comptime P: type) type { return extern struct { const Loc = ?*const anyopaque; context: P = null, - callback: (?*const fn(ctx: P, err: [*:0]const u8, err_len: usize, source_location: Loc) callconv(.C) void) = null, + callback: (?*const fn (ctx: P, err: [*:0]const u8, err_len: usize, source_location: Loc) callconv(.C) void) = null, }; } @@ -166,15 +230,16 @@ pub const Observer = opaque { }; pub const midi = struct { - pub const Config = extern struct { const Ctx = ?*anyopaque; version: enum(@FieldType(c.libremidi_midi_configuration, "version")) { none = 0, - midi1 = c.MIDI1, midi1_raw = c.MIDI1_RAW, - midi2 = c.MIDI2, midi2_raw = c.MIDI2_RAW, + midi1 = c.MIDI1, + midi1_raw = c.MIDI1_RAW, + midi2 = c.MIDI2, + midi2_raw = c.MIDI2_RAW, } = .none, port: extern union { @@ -199,22 +264,19 @@ pub const midi = struct { ignore_sensing: bool = false, timestamps: Timestamp.Mode = .no_timestamp, - fn ErrCallback(comptime P: type) type { return extern struct { const Loc = ?*const anyopaque; context: P = null, - callback: (?*const fn(ctx: P, err: [*:0]const u8, err_len: usize, source_location: Loc) callconv(.C) void) = null, + callback: (?*const fn (ctx: P, err: [*:0]const u8, err_len: usize, source_location: Loc) callconv(.C) void) = null, }; } }; pub const In = opaque { - extern fn libremidi_midi_in_new(conf: ?*const Config, api: ?*const Api.Config, out: *?*In) c_int; pub fn init(conf: ?*const Config, api: ?*const Api.Config) !*In { - var handle: ?*In = undefined; switch (errnoFromInt(libremidi_midi_in_new(conf, api, &handle))) { .SUCCESS => return handle.?, @@ -229,7 +291,7 @@ pub const midi = struct { switch (libremidi_midi_in_is_connected(self)) { 0 => return false, 1 => return true, - -@intFromEnum(E.INVAL) => return error.InvalidArgument, + -@as(c_int, @intFromEnum(E.INVAL)) => return error.InvalidArgument, else => unreachable, } } @@ -237,25 +299,22 @@ pub const midi = struct { extern fn libremidi_midi_in_absolute_timestamp(self: *In) Timestamp; pub fn getAbsoluteTimestamp(self: *In) !Timestamp { switch (libremidi_midi_in_absolute_timestamp(self)) { - -@intFromEnum(E.INVAL) => return error.InvalidArgument, + -@as(c_int, @intFromEnum(E.INVAL)) => return error.InvalidArgument, else => |ts| return ts, } } extern fn libremidi_midi_in_free(self: *In) c_int; - pub fn free(self: *In) void { + pub fn deinit(self: *In) void { switch (libremidi_midi_in_free(self)) { 0 => return, else => unreachable, } } - pub const Port = opaque { - extern fn libremidi_midi_in_port_clone(self: *Port, dest: *?*Port) c_int; pub fn clone(self: *Port) !*Port { - var handle: ?*Port = undefined; switch (errnoFromInt(libremidi_midi_in_port_clone(self, &handle))) { .SUCCESS => return handle.?, @@ -265,7 +324,7 @@ pub const midi = struct { } extern fn libremidi_midi_in_port_free(self: *Port) c_int; - pub fn free(self: *Port) void { + pub fn deinit(self: *Port) void { switch (libremidi_midi_in_port_free(self)) { 0 => return, else => unreachable, @@ -274,7 +333,6 @@ pub const midi = struct { extern fn libremidi_midi_in_port_name(self: *Port, name: *[*:0]const u8, len: *usize) c_int; pub fn getName(self: *Port) ![:0]const u8 { - var name: [:0]const u8 = undefined; switch (errnoFromInt(libremidi_midi_in_port_name(self, &name.ptr, &name.len))) { .SUCCESS => return name, @@ -283,17 +341,25 @@ pub const midi = struct { } } + extern fn libremidi_midi_in_port_information(self: *Port, info: *PortInformation) c_int; + pub fn getInformation(self: *Port) !PortInformation { + var info: PortInformation = undefined; + switch (errnoFromInt(libremidi_midi_in_port_information(self, &info))) { + .SUCCESS => return info, + .INVAL => return error.InvalidArgument, + else => unreachable, + } + } + fn Callback(comptime P: type) type { - return (?*const fn(ctx: P, port: *Port) callconv(.C) void); + return (?*const fn (ctx: P, port: *Port) callconv(.C) void); } }; }; pub const Out = opaque { - extern fn libremidi_midi_out_new(conf: ?*const Config, api: ?*const Api.Config, out: *?*Out) c_int; pub fn init(conf: ?*const Config, api: ?*const Api.Config) !*Out { - var handle: ?*Out = undefined; switch (errnoFromInt(libremidi_midi_out_new(conf, api, &handle))) { .SUCCESS => return handle.?, @@ -308,7 +374,7 @@ pub const midi = struct { switch (libremidi_midi_out_is_connected(self)) { 0 => return false, 1 => return true, - -@intFromEnum(E.INVAL) => return error.InvalidArgument, + -@as(c_int, @intFromEnum(E.INVAL)) => return error.InvalidArgument, else => unreachable, } } @@ -354,7 +420,7 @@ pub const midi = struct { } extern fn libremidi_midi_out_free(self: *Out) c_int; - pub fn free(self: *Out) void { + pub fn deinit(self: *Out) void { switch (libremidi_midi_out_free(self)) { 0 => return, else => unreachable, @@ -362,10 +428,8 @@ pub const midi = struct { } pub const Port = opaque { - extern fn libremidi_midi_out_port_clone(self: *Port, dest: *?*Port) c_int; pub fn clone(self: *Port) !*Port { - var handle: ?*Port = undefined; switch (errnoFromInt(libremidi_midi_out_port_clone(self, &handle))) { .SUCCESS => return handle.?, @@ -375,7 +439,7 @@ pub const midi = struct { } extern fn libremidi_midi_out_port_free(self: *Port) c_int; - pub fn free(self: *Port) void { + pub fn deinit(self: *Port) void { switch (libremidi_midi_out_port_free(self)) { 0 => return, else => unreachable, @@ -384,7 +448,6 @@ pub const midi = struct { extern fn libremidi_midi_out_port_name(self: *Port, name: *[*:0]const u8, len: *usize) c_int; pub fn getName(self: *Port) ![:0]const u8 { - var name: [:0]const u8 = undefined; switch (errnoFromInt(libremidi_midi_out_port_name(self, &name.ptr, &name.len))) { .SUCCESS => return name, @@ -393,8 +456,18 @@ pub const midi = struct { } } + extern fn libremidi_midi_out_port_information(self: *Port, info: *PortInformation) c_int; + pub fn getInformation(self: *Port) !PortInformation { + var info: PortInformation = undefined; + switch (errnoFromInt(libremidi_midi_out_port_information(self, &info))) { + .SUCCESS => return info, + .INVAL => return error.InvalidArgument, + else => unreachable, + } + } + fn Callback(comptime P: type) type { - return (?*const fn(ctx: P, port: *Port) callconv(.C) void); + return (?*const fn (ctx: P, port: *Port) callconv(.C) void); } }; }; @@ -411,7 +484,7 @@ pub const midi = struct { fn Callback(comptime P: type) type { return extern struct { context: P = null, - callback: (?*const fn(ctx: P, ts: Timestamp, msg: Message, len: usize) callconv(.C) void) = null, + callback: (?*const fn (ctx: P, ts: Timestamp, msg: Message, len: usize) callconv(.C) void) = null, }; } }; @@ -428,7 +501,7 @@ pub const midi = struct { fn Callback(comptime P: type) type { return extern struct { context: P, - callback: (?*const fn(ctx: P, ts: Timestamp, msg: Message, len: usize) callconv(.C) void), + callback: (?*const fn (ctx: P, ts: Timestamp, msg: Message, len: usize) callconv(.C) void), }; } }; diff --git a/examples/c_api.c b/examples/c_api.c index 876f3756..6d4002fd 100644 --- a/examples/c_api.c +++ b/examples/c_api.c @@ -1,17 +1,17 @@ #include +#include #include #include #include #if defined(_WIN32) - #include +#include #else - #include +#include #endif -void sleep_ms(int milliseconds) -{ +void sleep_ms(int milliseconds) { #if defined(_WIN32) Sleep(milliseconds); #else @@ -19,79 +19,115 @@ void sleep_ms(int milliseconds) #endif } -typedef struct enumerated_ports -{ - libremidi_midi_in_port* in_ports[256]; - libremidi_midi_out_port* out_ports[256]; +typedef struct enumerated_ports { + libremidi_midi_in_port *in_ports[256]; + libremidi_midi_out_port *out_ports[256]; int in_port_count; int out_port_count; } enumerated_ports; -void on_input_port_found(void* ctx, const libremidi_midi_in_port* port) -{ - const char* name = NULL; - size_t len = 0; +static const char *safe_str(const char *s) { return s ? s : "(null)"; } + +static void print_identifier(const char *label, + const libremidi_identifier *id) { + switch (id->value_type) { + case LIBREMIDI_ID_NONE: + printf(" %s: (none)\n", label); + break; + + case LIBREMIDI_ID_UUID: + printf(" %s (uuid): ", label); + for (int i = 0; i < 16; ++i) + printf("%02x", (unsigned)id->value.uuid.bytes[i]); + printf("\n"); + break; + + case LIBREMIDI_ID_STRING: + printf(" %s: %s\n", label, safe_str(id->value.string)); + break; + + case LIBREMIDI_ID_UINT64: + printf(" %s: %" PRIu64 "\n", label, (uint64_t)id->value.u64); + break; + + default: + printf(" %s: (unknown type %u)\n", label, (unsigned)id->value_type); + break; + } +} - int ret = libremidi_midi_in_port_name(port, &name, &len); - if (ret != 0) +static void print_port_info(const libremidi_port_information *info) { + print_identifier("container_identification", &info->container_identifier); + print_identifier("device_identification", &info->device_identifier); + + printf(" client_handle: %" PRId64 "\n", (int64_t)info->client_handle); + printf(" port_handle: %" PRId64 "\n", (int64_t)info->port_handle); + printf(" manufacturer: %s\n", safe_str(info->manufacturer)); + printf(" device_name: %s\n", safe_str(info->device_name)); + printf(" port_name: %s\n", safe_str(info->port_name)); + printf(" display_name: %s\n", safe_str(info->display_name)); + + printf(" port type: %d\n", (uint8_t)info->type); +} + +void on_input_port_found(void *ctx, const libremidi_midi_in_port *port) { + libremidi_port_information info = {0}; + if (libremidi_midi_in_port_information(port, &info) != 0) return; - printf("input: %s\n", name); + printf("input: %s\n", safe_str(info.port_name)); + print_port_info(&info); fflush(stdout); - enumerated_ports* e = (enumerated_ports*)ctx; + enumerated_ports *e = (enumerated_ports *)ctx; libremidi_midi_in_port_clone(port, &e->in_ports[e->in_port_count]); e->in_port_count++; } -void on_output_port_found(void* ctx, const libremidi_midi_out_port* port) -{ - const char* name = NULL; - size_t len = 0; - - int ret = libremidi_midi_out_port_name(port, &name, &len); - if (ret != 0) +void on_output_port_found(void *ctx, const libremidi_midi_out_port *port) { + libremidi_port_information info = {0}; + if (libremidi_midi_out_port_information(port, &info) != 0) return; - printf("output: %s\n", name); + printf("output: %s\n", safe_str(info.port_name)); + print_port_info(&info); fflush(stdout); - enumerated_ports* e = (enumerated_ports*)ctx; + enumerated_ports *e = (enumerated_ports *)ctx; libremidi_midi_out_port_clone(port, &e->out_ports[e->out_port_count]); e->out_port_count++; } -void on_midi1_message( - void* ctx, libremidi_timestamp ts, const libremidi_midi1_symbol* msg, size_t len) -{ +void on_midi1_message(void *ctx, libremidi_timestamp ts, + const libremidi_midi1_symbol *msg, size_t len) { printf("%#02x %#02x %#02x \n", (int)msg[0], (int)msg[1], (int)msg[2]); fflush(stdout); } -void on_midi2_message( - void* ctx, libremidi_timestamp ts, const libremidi_midi2_symbol* msg, size_t len) -{ +void on_midi2_message(void *ctx, libremidi_timestamp ts, + const libremidi_midi2_symbol *msg, size_t len) { printf("%#02x %#02x %#02x\n", (int)msg[0], (int)msg[1], (int)msg[2]); fflush(stdout); } -int enumerate_ports(libremidi_midi_observer_handle* observer, struct enumerated_ports* e) -{ +int enumerate_ports(libremidi_midi_observer_handle *observer, + struct enumerated_ports *e) { int ret = 0; - ret = libremidi_midi_observer_enumerate_input_ports(observer, e, on_input_port_found); + ret = libremidi_midi_observer_enumerate_input_ports(observer, e, + on_input_port_found); if (ret != 0) return ret; - ret = libremidi_midi_observer_enumerate_output_ports(observer, e, on_output_port_found); + ret = libremidi_midi_observer_enumerate_output_ports(observer, e, + on_output_port_found); if (ret != 0) return ret; return 0; } -int main(void) -{ +int main(void) { int ret = 0; /// Create an observer for MIDI ports @@ -119,15 +155,15 @@ int main(void) observer_api_conf.configuration_type = Observer; observer_api_conf.api = ALSA_SEQ; - libremidi_midi_observer_handle* observer = NULL; - ret = libremidi_midi_observer_new(&observer_conf, &observer_api_conf, &observer); + libremidi_midi_observer_handle *observer = NULL; + ret = libremidi_midi_observer_new(&observer_conf, &observer_api_conf, + &observer); if (ret != 0) return ret; ret = enumerate_ports(observer, &e); - if (ret != 0) - { + if (ret != 0) { libremidi_midi_observer_free(observer); return ret; } @@ -150,7 +186,7 @@ int main(void) midi_in_api_conf.configuration_type = Input; midi_in_api_conf.api = ALSA_SEQ; - libremidi_midi_in_handle* midi_in = NULL; + libremidi_midi_in_handle *midi_in = NULL; ret = libremidi_midi_in_new(&midi_in_conf, &midi_in_api_conf, &midi_in); if (ret != 0) goto free_observer; @@ -173,7 +209,7 @@ int main(void) midi_out_api_conf.configuration_type = Output; midi_out_api_conf.api = ALSA_SEQ; - libremidi_midi_out_handle* midi_out = NULL; + libremidi_midi_out_handle *midi_out = NULL; ret = libremidi_midi_out_new(&midi_out_conf, &midi_out_api_conf, &midi_out); if (ret != 0) goto free_midi_in; diff --git a/include/libremidi/libremidi-c.cpp b/include/libremidi/libremidi-c.cpp index 5b76f347..a91e90e9 100644 --- a/include/libremidi/libremidi-c.cpp +++ b/include/libremidi/libremidi-c.cpp @@ -9,6 +9,45 @@ #include #include +template +void set_id_from_variant(const Variant& var, libremidi_identifier* out) +{ + if (!out) + return; + + out->value_type = LIBREMIDI_ID_NONE; + out->value.string = nullptr; + + std::visit([&](auto&& v) { + using T = std::decay_t; + if constexpr (std::is_same_v) + { + out->value_type = LIBREMIDI_ID_UUID; + std::memcpy(out->value.uuid.bytes, v.bytes, 16); + } + else if constexpr (std::is_same_v) + { + out->value_type = LIBREMIDI_ID_STRING; + out->value.string = v.c_str(); // safe: string outlives this assignment + } + else if constexpr (std::is_same_v) + { + out->value_type = LIBREMIDI_ID_STRING; + out->value.string = v; // just assign the pointer + } + else if constexpr (std::is_same_v) + { + out->value_type = LIBREMIDI_ID_UINT64; + out->value.u64 = v; + } + else + { + out->value_type = LIBREMIDI_ID_NONE; + out->value.string = nullptr; + } + }, var); +} + struct libremidi_midi_observer_handle { libremidi::observer self; @@ -36,6 +75,45 @@ static void assign_error_callback(const auto& src, auto& dst) }; } } + +static int +get_port_information(const libremidi::port_information* port, libremidi_port_information* info) +{ + if (!port || !info) + return -EINVAL; + + std::memset(info, 0, sizeof(*info)); + + info->client_handle = static_cast(static_cast(port->client)); + info->port_handle = static_cast(static_cast(port->port)); + + set_id_from_variant(port->container, &info->container_identifier); + set_id_from_variant(port->device, &info->device_identifier); + + info->manufacturer = port->manufacturer.c_str(); + info->device_name = port->device_name.c_str(); + info->port_name = port->port_name.c_str(); + info->display_name = port->display_name.c_str(); + + static const std::unordered_map + port_type_map + = {{libremidi::port_information::software, PORT_SOFTWARE}, + {libremidi::port_information::loopback, PORT_LOOPBACK}, + {libremidi::port_information::hardware, PORT_HARDWARE}, + {libremidi::port_information::usb, PORT_USB}, + {libremidi::port_information::bluetooth, PORT_BLUETOOTH}, + {libremidi::port_information::pci, PORT_PCI}, + {libremidi::port_information::network, PORT_NETWORK}}; + + auto it = port_type_map.find(port->type); + if (it != port_type_map.end()) + info->type = it->second; + else + info->type = PORT_UNKNOWN; + + return 0; +} + } extern "C" { @@ -123,6 +201,13 @@ int libremidi_midi_in_port_name(const libremidi_midi_in_port* port, const char** return 0; } +int libremidi_midi_in_port_information( + const libremidi_midi_in_port* port, libremidi_port_information* info) +{ + auto p = reinterpret_cast(port); + return libremidi::get_port_information(p, info); +} + int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst) { @@ -152,6 +237,13 @@ int libremidi_midi_out_port_name( return 0; } +int libremidi_midi_out_port_information( + const libremidi_midi_out_port* port, libremidi_port_information* info) +{ + auto p = reinterpret_cast(port); + return libremidi::get_port_information(p, info); +} + int libremidi_midi_observer_new( const libremidi_observer_configuration* c, libremidi_api_configuration* api, libremidi_midi_observer_handle** out) @@ -280,7 +372,8 @@ int libremidi_midi_in_new( conf.on_message = [cb = c->on_midi1_message](const libremidi::message& msg) { cb.callback(cb.context, msg.timestamp, msg.bytes.data(), msg.size()); }; - else if (c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI1_RAW && c->on_midi1_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi1_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -323,7 +416,8 @@ int libremidi_midi_in_new( conf.on_message = [cb = c->on_midi2_message](const libremidi::ump& msg) { cb.callback(cb.context, msg.timestamp, msg.data, msg.size()); }; - else if (c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) + else if ( + c->version == libremidi_midi_configuration::MIDI2_RAW && c->on_midi2_raw_data.callback) { conf.on_raw_data = [cb = c->on_midi2_raw_data](std::span msg, int64_t ts) { cb.callback(cb.context, ts, msg.data(), msg.size()); @@ -470,7 +564,8 @@ int libremidi_midi_out_send_message( return res != stdx::error{} ? -EIO : 0; } -int libremidi_midi_out_send_ump(libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) +int libremidi_midi_out_send_ump( + libremidi_midi_out_handle* out, const libremidi_midi2_symbol* msg, size_t sz) { if (!out || !msg || sz > std::numeric_limits::max()) return -EINVAL; diff --git a/include/libremidi/libremidi-c.h b/include/libremidi/libremidi-c.h index c43d7e12..53a8fee5 100644 --- a/include/libremidi/libremidi-c.h +++ b/include/libremidi/libremidi-c.h @@ -1,4 +1,5 @@ #pragma once + #include #include @@ -32,6 +33,66 @@ typedef struct libremidi_midi_in_handle libremidi_midi_in_handle; typedef struct libremidi_midi_out_handle libremidi_midi_out_handle; typedef struct libremidi_midi_observer_handle libremidi_midi_observer_handle; +typedef union libremidi_midi_port +{ + libremidi_midi_in_port* in; + libremidi_midi_out_port* out; +} libremidi_midi_port; + +typedef int64_t libremidi_client_handle; +typedef int64_t libremidi_port_handle; + +typedef struct libremidi_uuid +{ + uint8_t bytes[16]; +} libremidi_uuid; + +enum libremidi_identifier_type : uint8_t +{ + LIBREMIDI_ID_NONE = 0, + LIBREMIDI_ID_UUID, + LIBREMIDI_ID_STRING, + LIBREMIDI_ID_UINT64 +}; + +typedef struct libremidi_identifier +{ + enum libremidi_identifier_type value_type; + union + { + libremidi_uuid uuid; + const char* string; + uint64_t u64; + } value; +} libremidi_identifier; + +enum libremidi_port_type : uint8_t +{ + PORT_UNKNOWN = 0, + PORT_SOFTWARE = (1 << 1), + PORT_LOOPBACK = (1 << 2), + PORT_HARDWARE = (1 << 3), + PORT_USB = (1 << 4), + PORT_BLUETOOTH = (1 << 5), + PORT_PCI = (1 << 6), + PORT_NETWORK = (1 << 7) +}; + +typedef struct libremidi_port_information +{ + libremidi_client_handle client_handle; + libremidi_identifier container_identifier; + libremidi_identifier device_identifier; + libremidi_port_handle port_handle; + + const char* manufacturer; + const char* device_name; + const char* port_name; + const char* display_name; + + enum libremidi_port_type type; +} libremidi_port_information; + typedef struct libremidi_api_configuration libremidi_api_configuration; enum libremidi_timestamp_mode @@ -198,6 +259,10 @@ LIBREMIDI_EXPORT int libremidi_midi_in_port_name( const libremidi_midi_in_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_in_port_information( + const libremidi_midi_in_port* port, libremidi_port_information* info); + LIBREMIDI_EXPORT int libremidi_midi_out_port_clone( const libremidi_midi_out_port* port, libremidi_midi_out_port** dst); @@ -209,6 +274,10 @@ LIBREMIDI_EXPORT int libremidi_midi_out_port_name( const libremidi_midi_out_port* port, const char** name, size_t* len); +LIBREMIDI_EXPORT +int libremidi_midi_out_port_information( + const libremidi_midi_out_port* port, libremidi_port_information* info); + /// Observer API LIBREMIDI_EXPORT int libremidi_midi_observer_new(