scritcher/src/image.zig

328 lines
9.7 KiB
Zig
Raw Normal View History

const std = @import("std");
2019-07-09 16:21:07 +00:00
const lv2 = @import("lv2_helpers.zig");
const c = lv2.c;
2019-07-09 03:04:01 +00:00
const plugins = @import("plugin.zig");
2019-07-09 19:52:46 +00:00
/// Approximate size of the BMP header.
pub const BMPHeaderSize: usize = 82000;
/// Buffer size for main copying
pub const BufferSize: usize = 60000;
2019-07-09 03:04:01 +00:00
pub const ImageError = error{
OpenFail,
InvalidPlugin,
UnknownPlugin,
InvalidSymbol,
2019-07-09 18:15:02 +00:00
InstantiateFail,
WriteFail,
2019-07-09 03:04:01 +00:00
};
/// 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 swrite(file: *c.SNDFILE, buf: [*]f32, frames: i64) !void {
const count = c.sf_writef_float(file, buf, frames);
2019-07-09 21:34:05 +00:00
if (count != frames) {
std.debug.warn("Wanted to read {}, got {}\n", frames, count);
return ImageError.WriteFail;
2019-07-09 21:34:05 +00:00
}
}
2019-07-09 19:52:46 +00:00
fn getEndPos(path: []const u8) !usize {
var file = try std.fs.File.openRead(path);
defer file.close();
return file.getEndPos();
}
2019-07-09 17:29:54 +00:00
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;
}
2019-07-09 18:15:02 +00:00
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,
2019-07-10 02:04:05 +00:00
/// Pointer to the underlying libsndfile's SNDFILE struct.
sndfile: *c.SNDFILE,
2019-07-10 02:04:05 +00:00
/// The original image file path.
path: []const u8,
2019-07-10 02:04:05 +00:00
/// Represents the current path being worked on.
curpath: []const u8,
/// Open a BMP file.
pub fn open(allocator: *std.mem.Allocator, path: []const u8) !*Image {
2019-07-09 18:15:02 +00:00
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,
2019-07-10 02:04:05 +00:00
.curpath = 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),
);
}
}
2019-07-09 03:04:01 +00:00
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;
}
fn copyBytes(
self: *Image,
out_file: *c.SNDFILE,
buf: []f32,
start: usize,
end: usize,
) !void {
const total_bytes = end - start;
var i: usize = start;
// we do sf_seek() calls to make sure we are actually on the start
// and actually end at the end position for the file.
_ = c.sf_seek(self.sndfile, @intCast(i64, start), c.SEEK_SET);
while (i <= end) : (i += buf.len) {
std.debug.warn("i={}, buf.len={}, end={}\n", i, buf.len, end);
const excess = @intCast(i64, i + buf.len) - @intCast(i64, end);
var read_bytes: i64 = undefined;
var view: []f32 = buf[0..buf.len];
if (excess > 0) {
std.debug.warn(
"excess of {} bytes, reading {} instead\n",
excess,
@intCast(i64, buf.len) - excess,
);
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, buf.len) - excess);
view = buf[0..@intCast(usize, excess)];
} else {
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, buf.len));
}
try swrite(out_file, view.ptr, @intCast(i64, view.len));
}
_ = c.sf_seek(self.sndfile, @intCast(i64, end), c.SEEK_SET);
}
2019-07-09 17:29:54 +00:00
/// 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.
2019-07-09 03:04:01 +00:00
pub fn runPlugin(
self: *Image,
plugin_uri: []const u8,
2019-07-09 19:52:46 +00:00
position: plugins.Position,
2019-07-09 03:04:01 +00:00
params: plugins.ParamList,
) !void {
var ctx = try plugins.makeContext(self.allocator, plugin_uri);
defer ctx.deinit();
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;
}
// TODO check n_audio_out
// now, for each param for the plugin, we find its port, and set
// the value for the port there.
var it = params.iterator();
2019-07-09 16:21:07 +00:00
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: {
2019-07-10 19:06:44 +00:00
std.debug.warn("assert fail: symbol {} not found on port\n", param.sym);
return ImageError.InvalidSymbol;
};
c.lilv_node_free(sym);
var idx = c.lilv_port_get_index(ctx.plugin, port);
std.debug.warn(
"\tset sym={}, idx={} to val={}\n",
param.sym,
idx,
param.value,
);
ports[idx].value = param.value;
2019-07-09 16:21:07 +00:00
}
2019-07-09 17:29:54 +00:00
// 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("\trunning plugin from '{}' to '{}'\n", self.curpath, tmpnam);
2019-07-09 18:15:02 +00:00
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);
defer rctx.deinit();
2019-07-09 18:15:02 +00:00
rctx.connectPorts(ports);
lv2.lilv_instance_activate(rctx.instance);
2019-07-09 19:52:46 +00:00
// 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 file_end = try getEndPos(self.curpath);
const seek_pos = position.seekPos(file_end);
std.debug.warn("\tstart {} end {}\n", seek_pos.start, seek_pos.end);
2019-07-09 21:34:05 +00:00
// make sure we start from 0
_ = c.sf_seek(self.sndfile, 0, c.SEEK_SET);
// there are four main stages:
// - the bmp header copy
// - pre-plugin
// - plugin
// - post-plugin
var file_copy_buf = try self.allocator.alloc(f32, BufferSize);
defer self.allocator.free(file_copy_buf);
// pre-plugin copy, merged with bmp header copy
try self.copyBytes(
out_file,
file_copy_buf,
usize(0),
2019-07-11 13:51:00 +00:00
seek_pos.start + @mod(seek_pos.start, BufferSize),
);
2019-07-09 21:34:05 +00:00
2019-07-11 13:51:00 +00:00
_ = c.sf_seek(self.sndfile, @intCast(i64, seek_pos.start), c.SEEK_SET);
var i: usize = seek_pos.start;
std.debug.warn("\tseek pos start: {} end: {}\n", seek_pos.start, seek_pos.end);
2019-07-09 21:34:05 +00:00
while (i <= seek_pos.end) : (i += 1) {
const read_bytes = c.sf_readf_float(self.sndfile, rctx.in_buf.ptr, 1);
if (read_bytes == 0) {
std.debug.warn("WARN! reached EOF at idx={}\n", i);
break;
}
2019-07-09 21:34:05 +00:00
lv2.lilv_instance_run(rctx.instance, 1);
try swrite(out_file, rctx.out_buf.ptr, 1);
2019-07-09 21:34:05 +00:00
}
// post-plugin copy
try self.copyBytes(
out_file,
file_copy_buf,
seek_pos.end + 1,
2019-07-11 13:51:00 +00:00
file_end + @mod(file_end, BufferSize),
);
c.sf_write_sync(out_file);
_ = c.sf_close(out_file);
2019-07-10 02:04:05 +00:00
_ = c.sf_close(self.sndfile);
// reopen the file as SFM_READ so we can run plugin chains etc
self.sndfile = try sopen(self.allocator, tmpnam, c.SFM_READ, &out_fmt);
2019-07-10 02:04:05 +00:00
self.curpath = tmpnam;
}
pub fn saveTo(self: *Image, out_path: []const u8) !void {
std.debug.warn("saved to '{}'\n", out_path);
try std.fs.copyFile(self.curpath, out_path);
2019-07-09 03:04:01 +00:00
}
};