const std = @import("std"); const plugin = @import("plugin.zig"); pub const c = @cImport({ @cInclude("sndfile.h"); @cInclude("lilv/lilv.h"); @cInclude("lv2.h"); }); pub fn Lv2Core(comptime ns: []const u8) []const u8 { const LV2_CORE_URI = "http://lv2plug.in/ns/lv2core"; return LV2_CORE_URI ++ ns ++ [_]u8{0}; } const LV2_CORE__InputPort = Lv2Core("#InputPort"); const LV2_CORE__OutputPort = Lv2Core("#OutputPort"); const LV2_CORE__AudioPort = Lv2Core("#AudioPort"); const LV2_CORE__ControlPort = Lv2Core("#ControlPort"); const LV2_CORE__connectionOptional = Lv2Core("#connectionOptional"); pub fn lilv_instance_connect_port( instance: [*c]c.LilvInstance, port_index: u32, data_location: ?*c_void, ) void { instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location); } pub fn lilv_instance_activate(instance: [*c]c.LilvInstance) void { if (instance.?.*.lv2_descriptor.?.*.activate != null) { instance.?.*.lv2_descriptor.?.*.activate.?(instance.?.*.lv2_handle); } } pub fn lilv_instance_run(instance: [*c]c.LilvInstance, sample_count: u32) void { instance.?.*.lv2_descriptor.?.*.run.?(instance.?.*.lv2_handle, sample_count); } pub fn lilv_instance_deactivate(instance: [*c]c.LilvInstance) void { if (instance.?.*.lv2_descriptor.?.*.deactivate != null) { instance.?.*.lv2_descriptor.?.*.deactivate.?(instance.?.*.lv2_handle); } } pub const PortType = enum { Control, Audio, }; pub const Port = struct { lilv_port: ?*const c.LilvPort, ptype: PortType, index: u32, value: f32, is_input: bool, optional: bool, }; /// Setup ports for a given plugin. Gives an array to pointers of Port structs. /// This setup is required so we link the plugin to the ports later on, and /// also link our buffers, and control values. pub fn setupPorts(ctx: *plugin.Context) ![]Port { var world = ctx.world; const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin); var ports = try ctx.allocator.alloc(Port, n_ports); for (ports) |_, idx| { var port: *Port = &ports[idx]; port.* = Port{ .lilv_port = null, .ptype = .Control, .index = @as(f32, 0), .value = @as(f32, 0), .is_input = false, .optional = false, }; } var values: []f32 = try ctx.allocator.alloc(f32, n_ports); defer ctx.allocator.free(values); c.lilv_plugin_get_port_ranges_float(ctx.plugin, null, null, values.ptr); var lv2_InputPort = c.lilv_new_uri(world, LV2_CORE__InputPort.ptr); defer std.heap.c_allocator.destroy(lv2_InputPort); var lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr); defer std.heap.c_allocator.destroy(lv2_OutputPort); var lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr); defer std.heap.c_allocator.destroy(lv2_AudioPort); var lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr); defer std.heap.c_allocator.destroy(lv2_ControlPort); var lv2_connectionOptional = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr); defer std.heap.c_allocator.destroy(lv2_connectionOptional); var i: u32 = 0; while (i < n_ports) : (i += 1) { var port: *Port = &ports[i]; const lport = c.lilv_plugin_get_port_by_index(ctx.plugin, i).?; port.lilv_port = lport; port.index = i; if (std.math.isNan(values[i])) { port.value = @as(f32, 0); } else { port.value = values[i]; } port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connectionOptional); if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) { port.is_input = true; } else if (!c.lilv_port_is_a(ctx.plugin, lport, lv2_OutputPort) and !port.optional) { std.debug.warn("Port {} is neither input or output\n", .{i}); return error.UnassignedIOPort; } // check if port is an audio or control port if (c.lilv_port_is_a(ctx.plugin, lport, lv2_ControlPort)) { port.ptype = .Control; } else if (c.lilv_port_is_a(ctx.plugin, lport, lv2_AudioPort)) { port.ptype = .Audio; if (port.is_input) { ctx.n_audio_in += 1; } else { ctx.n_audio_out += 1; } } else if (!port.optional) { std.debug.warn("Port {} has unsupported type\n", .{i}); return error.UnsupportedPortType; } } return ports; }