const std = @import("std"); const lv2 = @import("lv2_helpers.zig"); const c = lv2.c; const plugins = @import("plugin.zig"); pub const ImageError = error{ OpenFail, InvalidPlugin, UnknownPlugin, InvalidSymbol, InstantiateFail, }; /// Low level integration function with libsndfile. fn sopen( allocator: *std.mem.Allocator, path: []const u8, mode: i32, fmt: *c.SF_INFO, ) !*c.SNDFILE { var cstr_path = try std.cstr.addNullByte(allocator, path); defer allocator.free(cstr_path); var file = c.sf_open(cstr_path.ptr, mode, fmt); const st: i32 = c.sf_error(file); if (st != 0) { std.debug.warn( "Failed to open {} ({})\n", path, c.sf_error_number(st), ); return ImageError.OpenFail; } return file.?; } fn temporaryName(allocator: *std.mem.Allocator) ![]u8 { const template_start = "/temp/temp_"; const template = "/tmp/temp_XXXXXX"; var nam = try allocator.alloc(u8, template.len); std.mem.copy(u8, nam, template); var r = std.rand.DefaultPrng.init(std.time.timestamp()); var fill = nam[template_start.len..nam.len]; var i: usize = 0; while (i < 100) : (i += 1) { // generate a random uppercase letter, that is, 65 + random number. for (fill) |_, f_idx| { var idx = @intCast(u8, r.random.uintLessThan(u5, 24)); var letter = u8(65) + idx; fill[f_idx] = letter; } // if we fail to access it, we assume it doesn't exist and return it. std.fs.File.access(nam) catch |err| { if (err == error.FileNotFound) { return nam; } }; } return error.TempGenFail; } fn mkSfInfo() c.SF_INFO { return c.SF_INFO{ .frames = c_int(0), .samplerate = c_int(44100), .channels = c_int(1), .format = c.SF_FORMAT_ULAW | c.SF_FORMAT_RAW | c.SF_ENDIAN_BIG, .sections = c_int(0), .seekable = c_int(0), }; } pub const Image = struct { allocator: *std.mem.Allocator, sndfile: *c.SNDFILE, path: []const u8, /// Open a BMP file. pub fn open(allocator: *std.mem.Allocator, path: []const u8) !*Image { var in_fmt = mkSfInfo(); var sndfile = try sopen(allocator, path, c.SFM_READ, &in_fmt); var image = try allocator.create(Image); image.* = Image{ .allocator = allocator, .sndfile = sndfile, .path = path, }; return image; } pub fn close(self: *Image) void { var st: i32 = c.sf_close(self.sndfile); if (st != 0) { std.debug.warn( "Failed to close {} ({})\n", self.path, c.sf_error_number(st), ); } } pub fn read(self: *Image, file_chans: c_int, buf: []f32) bool { var file = file_opt.?; const n_read: c.sf_count_t = c.sf_readf_float(file, buf.ptr, 1); const buf_chans = @intCast(c_int, buf.len); var i = file_chans - 1; while (i < buf_chans) : (i += 1) { //buf[@intCast(usize, i)] = buf[i % file_chans]; buf[@intCast(usize, i)] = buf[@intCast(usize, @mod(i, file_chans))]; } return n_read == 1; } /// Run a plugin over the image. /// This setups a new lilv world/plugin among other things. /// The internal SNDFILE pointer is modified to point to the output of the /// plugin run. pub fn runPlugin( self: *Image, plugin_uri: []const u8, pos: plugins.Position, params: plugins.ParamList, ) !void { var ctx = try plugins.makeContext(self.allocator, plugin_uri); std.debug.warn("\tworld: {}\n", ctx.world); std.debug.warn("\tplugin: {}\n", ctx.plugin); var ports = try lv2.setupPorts(&ctx); if (ctx.n_audio_in != 1) { std.debug.warn("plugin <{}> does not accept mono input.\n", plugin_uri); return ImageError.InvalidPlugin; } // now, for each param for the plugin, we find its port, and set // the value for the port there. var it = params.iterator(); while (it.next()) |param| { var sym_cstr = try std.cstr.addNullByte(self.allocator, param.sym); defer self.allocator.free(sym_cstr); var sym = c.lilv_new_string(ctx.world, sym_cstr.ptr); const port = c.lilv_plugin_get_port_by_symbol(ctx.plugin, sym) orelse blk: { std.debug.warn("assert fail: symbol not found on port"); return ImageError.InvalidSymbol; }; c.lilv_node_free(sym); var idx = c.lilv_port_get_index(ctx.plugin, port); std.debug.warn( "sym={}, idx={} to val={}\n", param.sym, idx, param.value, ); ports[idx].value = param.value; } // now we need to generate a temporary file and put the output of // running the plugin on that file var tmpnam = try temporaryName(self.allocator); std.debug.warn("temporary name: {}\n", tmpnam); var out_fmt = mkSfInfo(); var out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt); var rctx = try plugins.RunContext.init(self.allocator, ctx.plugin); rctx.connectPorts(ports); lv2.lilv_instance_activate(rctx.instance); } };