const std = @import("std"); const lv2 = @import("lv2_helpers.zig"); const c = lv2.c; const plugins = @import("plugin.zig"); /// Approximate size of the BMP header. pub const BMPHeaderSize: usize = 82000; 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.?; } ///Read a single frame from a file into an interleaved buffer. ///If more channels are required than are available in the file, the remaining ///channels are distributed in a round-robin fashion (LRLRL). fn sread(file_opt: ?*c.SNDFILE, 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; } fn getEndPos(path: []const u8) !usize { var file = try std.fs.File.openRead(path); defer file.close(); return file.getEndPos(); } 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, position: 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); // now that we have everything setup, we need to make the part where we // just copy the original image and the part where we run the plugin // over the image. const end_pos = (try getEndPos(self.path)) - BMPHeaderSize; std.debug.warn("file end: {}\n", end_pos); const seek_pos = position.seekPos(end_pos); std.debug.warn("start {} end {}\n", seek_pos.start, seek_pos.end); var i: usize = 0; while (i < end_pos) : (i += 1) { // if we're still on the bmp header phase, copy bytes // if we aren't in the range from seek_pos, copy bytes // if we are in the range, run lilv plugin // TODO speed this up by not reading byte-by-byte. // TODO check value of this _ = c.sf_readf_float(self.sndfile, rctx.in_buf.ptr, 1); if (i < BMPHeaderSize or !seek_pos.contains(i)) { _ = c.sf_writef_float(out_file, rctx.in_buf.ptr, 1); } else { lv2.lilv_instance_run(rctx.instance, 1); var count = c.sf_writef_float(out_file, rctx.out_buf.ptr, 1); if (count != 1) { std.debug.warn( "Failed to write to output file at idx {}.\n", i, ); // maybe we return an error? } } } _ = c.sf_close(out_file); } };