const std = @import("std"); const lv2 = @import("lv2_helpers.zig"); const c = lv2.c; const log = std.log.scoped(.scritcher_plugin); const ImageError = @import("image.zig").ImageError; /// Control port pub const Param = struct { /// Port symbol sym: []const u8, /// Control value value: f32, }; /// List of parameters to be set to control ports. pub const ParamList = std.ArrayList(Param); pub const ParamMap = std.StringHashMap(f32); /// Represents an absolute position in the image. pub const SeekPos = struct { start: usize, end: usize, pub fn contains(self: SeekPos, idx: usize) bool { return (self.start <= idx) and (idx <= self.end); } }; /// Represents a relative position in the image pub const Position = struct { split: usize, index: usize, pub fn seekPos(self: Position, total_size: usize) SeekPos { std.debug.assert(self.index <= self.split); var tot = total_size / self.split; return SeekPos{ .start = self.index * tot, .end = (self.index + 1) * tot, }; } }; /// Represents the starting context for a single plugin run. pub const Context = struct { allocator: std.mem.Allocator, world: *c.LilvWorld, plugin: *const c.LilvPlugin, // they should both be 1. n_audio_in: usize = 0, n_audio_out: usize = 0, pub fn deinit(self: *Context) void { c.lilv_world_free(self.world); } }; /// Specific run context for non-plugins. pub const RunBuffers = struct { // we use [2]f32 to account for stereo plugins, however // we only use in_buf[0] and out_buf[0], and don't use the // (supposedly) right side of neither input or output. in: [2]f32 = [_]f32{0} ** 2, out: [2]f32 = [_]f32{0} ** 2, }; /// Represents the specific run context of plugin instantation. pub const RunContext = struct { buffers: RunBuffers, instance: *c.LilvInstance, pub fn init( allocator: std.mem.Allocator, plugin: *const c.LilvPlugin, ) !RunContext { _ = allocator; // TODO batch RunBuffers? var instance = c.lilv_plugin_instantiate(plugin, @as(f64, 44100), null); errdefer c.lilv_instance_free(instance); if (instance == null) { return ImageError.InstantiateFail; } return RunContext{ .buffers = RunBuffers{}, .instance = instance.?, }; } pub fn deinit(self: *RunContext) void { c.lilv_instance_free(self.instance); } pub fn connectPorts(self: *RunContext, ports: []lv2.Port) void { var i: usize = 0; var o: usize = 0; for (ports) |_, p_idx| { var p = @intCast(u32, p_idx); var port: *lv2.Port = &ports[p_idx]; switch (port.ptype) { .Control => lv2.lilv_instance_connect_port(self.instance, p, &port.value), .Audio => { if (port.is_input) { lv2.lilv_instance_connect_port( self.instance, p, &self.buffers.in[i], ); i += 1; } else { lv2.lilv_instance_connect_port( self.instance, p, &self.buffers.out[o], ); o += 1; } }, // else => lv2.lilv_instance_connect_port(self.instance, p, null), } } } }; pub fn makeContext(allocator: std.mem.Allocator, plugin_uri: []const u8) !Context { const cstr_plugin_uri = try std.cstr.addNullByte(allocator, plugin_uri); defer allocator.free(cstr_plugin_uri); var world: *c.LilvWorld = c.lilv_world_new().?; errdefer c.lilv_world_free(world); c.lilv_world_load_all(world); var uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse { log.debug("Invalid plugin URI <{s}>", .{plugin_uri}); return ImageError.InvalidPlugin; }; defer c.lilv_node_free(uri); const plugins: *const c.LilvPlugins = c.lilv_world_get_all_plugins(world).?; var plugin: *const c.LilvPlugin = c.lilv_plugins_get_by_uri(plugins, uri) orelse { log.debug("Plugin <{s}> not found", .{plugin_uri}); return ImageError.UnknownPlugin; }; return Context{ .allocator = allocator, .world = world, .plugin = plugin, }; }