Compare commits

...

45 commits

Author SHA1 Message Date
3af4dc575a fix command ptr alignment 2024-06-01 16:53:01 -03:00
c0da5e68b7 base port to zig 0.12 2024-06-01 15:46:37 -03:00
c73b98a356 fix for zig 0.11 2023-08-04 21:50:59 -03:00
7cc93c2976 zig fmt 2023-08-04 21:34:51 -03:00
ed67a52b15 fixes for some version of zig 2023-08-04 21:34:17 -03:00
0f6ac055a4 remove extra newlines from log calls 2022-10-08 15:14:56 -03:00
43dad638f2 use stage1 2022-10-08 15:06:14 -03:00
ef6c68705d port to latest zig 2022-10-08 15:05:15 -03:00
6b2ce7e425 properly fix memleaks 2022-04-28 00:28:07 -03:00
2796b654e5 port main() code path to latest zig 2022-04-28 00:08:52 -03:00
1987c4d497 fix command parsing 2022-04-27 20:01:14 -03:00
5d1bcf33ee fixes for latest zig: a lot of stuff 2022-04-27 20:01:12 -03:00
c817170a04 port to latest zig: use std.log 2022-04-27 20:01:09 -03:00
268be1074c port to latest zig fs api 2022-04-27 20:01:06 -03:00
d7d4385242 fix memory leak on string arguments 2022-04-27 20:01:00 -03:00
b195577dee prevent memleaks on parse errors 2021-04-04 20:47:18 -03:00
ea3850b99d add test for parse errors 2021-04-04 18:15:42 -03:00
b28f208e65 add error for non-lv2 args not getting enough args 2021-04-04 17:56:59 -03:00
2a801a129b add note about comptime hash map 2021-04-04 17:45:14 -03:00
78cc7fab4b fix tests 2021-04-04 17:08:53 -03:00
a9e4c5823a remove implemented TODO 2021-04-03 23:22:22 -03:00
79d7e137d2 refactor into 'doRun' function 2021-04-03 23:21:50 -03:00
73c5214146 add 'help' command 2021-04-03 23:20:17 -03:00
32b01976d8 change default scri path to 'run' cli command 2021-04-03 23:05:56 -03:00
9937365433 update to latest zig 2021-04-03 22:16:04 -03:00
667e6cbdac zig fmt pass 2021-04-03 21:09:44 -03:00
2acb45fdca build.zig: fix for latest zig 2021-04-03 21:09:30 -03:00
ebf716de17 fix type for Runner.args 2020-11-08 18:47:40 -03:00
5e8093b26f free lv2 ports slice 2020-08-18 21:02:43 -03:00
7097334201 turn on gpa 2020-08-18 21:00:16 -03:00
c2834f8254 properly free Image contents 2020-08-18 21:00:07 -03:00
77cceab288 make Image free its paths 2020-08-18 20:57:33 -03:00
9527426d48 remove memleak when getting args 2020-08-18 20:54:04 -03:00
0cd47be7ac remove need to dupe strings to command structs 2020-08-18 20:53:54 -03:00
708ef45600 fix use-after-free on repl 2020-08-18 20:41:57 -03:00
f61e9b013f make CommandList free its command pointers 2020-08-18 20:40:06 -03:00
4adf80e51a fix some memory leaks 2020-08-18 17:58:04 -03:00
353d8d6947 fix for latest zig 2020-08-18 17:49:23 -03:00
8aa65958d0 cast ptr 2020-07-23 16:32:33 -03:00
fa7e993ec2 zig fmt pass 2020-07-23 16:31:32 -03:00
973d6e63c5 Merge pull request 'Better LILV/LV2 integration' (#15) from better-lilv into master
Reviewed-on: #15
2020-06-09 22:38:08 +00:00
8d312cd987 use lv2.h provided at root of /usr/include
fixes ubuntu issues
2020-06-02 22:24:43 -03:00
c80a6d0df7 fix typos and actually add dirs to step 2020-06-02 21:54:39 -03:00
0ba2c5ffcc add basics of searching for lilv 2020-06-02 21:42:26 -03:00
18924adac4 Merge pull request 'Use comptime for fully declarative LV2 and Custom commands' (#14) from declarative-commands into master
Reviewed-on: #14
2020-06-02 21:37:46 +00:00
12 changed files with 489 additions and 322 deletions

2
.gitignore vendored
View file

@ -1,3 +1,5 @@
zig-cache/ zig-cache/
zig-out/
*.mp3 *.mp3
*.wav *.wav
build_runner.zig

View file

@ -1,7 +1,6 @@
const builds = @import("std").build; const std = @import("std");
const Builder = @import("std").build.Builder;
fn setupLinks(step: *builds.LibExeObjStep) void { fn setupLinks(step: *std.Build.Step.Compile) void {
step.linkSystemLibrary("c"); step.linkSystemLibrary("c");
step.linkSystemLibrary("lilv-0"); step.linkSystemLibrary("lilv-0");
@ -11,27 +10,64 @@ fn setupLinks(step: *builds.LibExeObjStep) void {
step.linkSystemLibrary("GraphicsMagickWand"); step.linkSystemLibrary("GraphicsMagickWand");
step.linkSystemLibrary("GraphicsMagick"); step.linkSystemLibrary("GraphicsMagick");
step.addIncludeDir("/usr/include/lilv-0"); step.addIncludePath(.{ .path = "/usr/include/GraphicsMagick" });
step.addIncludeDir("/usr/include/GraphicsMagick"); step.addIncludePath(.{ .path = "/usr/include" });
const possible_lilv_include_dirs = [_][]const u8{
"/usr/include/lilv-0/lilv",
"/usr/include/lilv-0",
};
var found_any_lilv = false;
for (possible_lilv_include_dirs) |possible_lilv_dir| {
var possible_dir = std.fs.cwd().openDir(possible_lilv_dir, .{}) catch |err| {
std.debug.print("possible lilv {s} fail: {s}\n", .{ possible_lilv_dir, @errorName(err) });
continue;
};
possible_dir.close();
found_any_lilv = true;
std.debug.print("found lilv at '{s}'\n", .{possible_lilv_dir});
step.addIncludePath(.{ .path = possible_lilv_dir });
}
if (!found_any_lilv) {
std.debug.print("No LILV library was found :(\n", .{});
@panic("no lilv found");
}
} }
pub fn build(b: *Builder) void { pub fn build(b: *std.Build) void {
const mode = b.standardReleaseOptions(); const target = b.standardTargetOptions(.{});
const exe = b.addExecutable("scritcher", "src/main.zig"); const optimize = b.standardOptimizeOption(.{});
exe.setBuildMode(mode);
exe.install();
const exe = b.addExecutable(.{
.name = "scritcher",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
setupLinks(exe); setupLinks(exe);
b.installArtifact(exe);
const test_obj_step = b.addTest("src/main.zig"); const run_cmd = b.addRunArtifact(exe);
setupLinks(test_obj_step);
const run_cmd = exe.run(); if (b.args) |args| {
run_cmd.step.dependOn(b.getInstallStep()); run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app"); const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step); run_step.dependOn(&run_cmd.step);
const test_step = b.step("test", "Run tests"); const test_step = b.addTest(.{
test_step.dependOn(&test_obj_step.step); .root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
setupLinks(test_step);
const run_unit_tests = b.addRunArtifact(test_step);
const test_cmd = b.step("test", "run unit tests");
test_cmd.dependOn(&run_unit_tests.step);
} }

View file

@ -1,11 +1,10 @@
const std = @import("std"); const std = @import("std");
const log = std.log.scoped(.scritcher_bmp);
pub const BMPValidError = error{InvalidMagic}; pub const BMPValidError = error{InvalidMagic};
const VALID_MAGICS = [_][]const u8{ const VALID_MAGICS = [_][]const u8{
"BM", "BM",
"BA", "BA",
"CI", "CI",
"CP", "CP",
"IC", "IC",
@ -20,7 +19,7 @@ pub fn magicValid(magic: []const u8) !void {
} }
if (!valid) { if (!valid) {
std.debug.warn("\tINVALID HEADER: '{}'\n", .{magic}); log.debug("\tINVALID HEADER: '{s}'", .{magic});
return BMPValidError.InvalidMagic; return BMPValidError.InvalidMagic;
} }
} }

View file

@ -4,6 +4,7 @@ const lv2 = @import("lv2_helpers.zig");
const plugins = @import("plugin.zig"); const plugins = @import("plugin.zig");
const image = @import("image.zig"); const image = @import("image.zig");
const log = std.log.scoped(.scritcher_custom);
const c = lv2.c; const c = lv2.c;
const RunBuffers = plugins.RunBuffers; const RunBuffers = plugins.RunBuffers;
@ -11,20 +12,20 @@ const RunBuffers = plugins.RunBuffers;
pub const RandomNoise = struct { pub const RandomNoise = struct {
r: std.rand.DefaultPrng, r: std.rand.DefaultPrng,
rand_buf: ?[]f32 = null, rand_buf: ?[]f32 = null,
allocator: ?*std.mem.Allocator = null, allocator: ?std.mem.Allocator = null,
cnt: usize = 0, cnt: usize = 0,
pub fn init( pub fn init(
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
params: var, params: anytype,
) ?RandomNoise { ) ?RandomNoise {
var r = std.rand.DefaultPrng.init(params.seed); var r = std.rand.DefaultPrng.init(params.seed);
if (params.fill_bytes > 0) { if (params.fill_bytes > 0) {
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
for (rand_buf) |_, idx| { for (rand_buf, 0..) |_, idx| {
rand_buf[idx] = r.random.float(f32); rand_buf[idx] = r.random().float(f32);
} }
return RandomNoise{ return RandomNoise{
@ -51,7 +52,7 @@ pub const RandomNoise = struct {
bufs.out[0] = rand_buf[self.cnt]; bufs.out[0] = rand_buf[self.cnt];
self.cnt += 1; self.cnt += 1;
} else { } else {
bufs.out[0] = self.r.random.float(f32); bufs.out[0] = self.r.random().float(f32);
} }
} }
}; };
@ -59,20 +60,20 @@ pub const RandomNoise = struct {
pub const WildNoise = struct { pub const WildNoise = struct {
r: std.rand.DefaultPrng, r: std.rand.DefaultPrng,
rand_buf: ?[]f32 = null, rand_buf: ?[]f32 = null,
allocator: ?*std.mem.Allocator = null, allocator: ?std.mem.Allocator = null,
cnt: usize = 0, cnt: usize = 0,
pub fn init( pub fn init(
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
params: var, params: anytype,
) ?WildNoise { ) ?WildNoise {
var r = std.rand.DefaultPrng.init(params.seed); var r = std.rand.DefaultPrng.init(params.seed);
if (params.fill_bytes > 0) { if (params.fill_bytes > 0) {
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
for (rand_buf) |_, idx| { for (rand_buf, 0..) |_, idx| {
rand_buf[idx] = @intToFloat(f32, r.random.int(u1)); rand_buf[idx] = @as(f32, @floatFromInt(r.random().int(u1)));
} }
return WildNoise{ return WildNoise{
@ -98,7 +99,7 @@ pub const WildNoise = struct {
bufs.out[0] = rand_buf[self.cnt]; bufs.out[0] = rand_buf[self.cnt];
self.cnt += 1; self.cnt += 1;
} else { } else {
bufs.out[0] = @intToFloat(f32, self.r.random.int(u1)); bufs.out[0] = @as(f32, @floatFromInt(self.r.random().int(u1)));
} }
} }
}; };
@ -111,15 +112,18 @@ pub const Write = struct {
data: f32, data: f32,
pub fn init( pub fn init(
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
params: var, params: anytype,
) Write { ) Write {
_ = allocator;
return Write{ return Write{
.data = params.data, .data = params.data,
}; };
} }
pub fn deinit(self: *Write) void {} pub fn deinit(self: *Write) void {
_ = self;
}
pub fn run(self: *Write, bufs: *RunBuffers) void { pub fn run(self: *Write, bufs: *RunBuffers) void {
bufs.out[0] = self.data; bufs.out[0] = self.data;
@ -127,13 +131,13 @@ pub const Write = struct {
}; };
pub const Embed = struct { pub const Embed = struct {
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
filepath: []const u8, filepath: []const u8,
sndfile: *c.SNDFILE = undefined, sndfile: *c.SNDFILE = undefined,
buf: []f32 = undefined, buf: []f32 = undefined,
pub fn init(allocator: *std.mem.Allocator, params: var) @This() { pub fn init(allocator: std.mem.Allocator, params: anytype) @This() {
return Embed{ return Embed{
.allocator = allocator, .allocator = allocator,
.filepath = params.path, .filepath = params.path,
@ -159,10 +163,12 @@ pub const Embed = struct {
image.sseek(self.sndfile, 0); image.sseek(self.sndfile, 0);
self.buf = try self.allocator.alloc(f32, @intCast(usize, in_fmt.channels)); self.buf = try self.allocator.alloc(f32, @as(usize, @intCast(in_fmt.channels)));
} }
pub fn deinit(self: *@This()) void {} pub fn deinit(self: *@This()) void {
_ = self;
}
pub fn run(self: *@This(), bufs: *RunBuffers) void { pub fn run(self: *@This(), bufs: *RunBuffers) void {
const read_bytes = c.sf_readf_float(self.sndfile, self.buf.ptr, 1); const read_bytes = c.sf_readf_float(self.sndfile, self.buf.ptr, 1);
@ -174,7 +180,7 @@ pub const Embed = struct {
if (read_bytes < 0) { if (read_bytes < 0) {
const st: i32 = c.sf_error(self.sndfile); const st: i32 = c.sf_error(self.sndfile);
std.debug.warn("Failed to read {} ({})\n", .{ log.debug("Failed to read {s} ({s})", .{
self.filepath, self.filepath,
c.sf_error_number(st), c.sf_error_number(st),
}); });

View file

@ -3,6 +3,7 @@ const lv2 = @import("lv2_helpers.zig");
const c = lv2.c; const c = lv2.c;
const bmp = @import("bmp_valid.zig"); const bmp = @import("bmp_valid.zig");
const log = std.log.scoped(.scritcher_image);
const plugins = @import("plugin.zig"); const plugins = @import("plugin.zig");
/// Buffer size for main image copying. /// Buffer size for main image copying.
@ -20,19 +21,19 @@ pub const ImageError = error{
/// Low level integration function with libsndfile. /// Low level integration function with libsndfile.
pub fn sopen( pub fn sopen(
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
path: []const u8, path: []const u8,
mode: i32, mode: i32,
fmt: *c.SF_INFO, fmt: *c.SF_INFO,
) !*c.SNDFILE { ) !*c.SNDFILE {
var cstr_path = try std.cstr.addNullByte(allocator, path); const cstr_path = try allocator.dupeZ(u8, path);
defer allocator.free(cstr_path); defer allocator.free(cstr_path);
var file = c.sf_open(cstr_path.ptr, mode, fmt); const file = c.sf_open(cstr_path.ptr, mode, fmt);
const st: i32 = c.sf_error(file); const st: i32 = c.sf_error(file);
if (st != 0) { if (st != 0) {
std.debug.warn("Failed to open {} ({})\n", .{ log.debug("Failed to open {s} ({s})", .{
path, path,
c.sf_error_number(st), c.sf_error_number(st),
}); });
@ -42,12 +43,12 @@ pub fn sopen(
const frames_on_end = c.sf_seek(file, 0, c.SEEK_END); const frames_on_end = c.sf_seek(file, 0, c.SEEK_END);
_ = c.sf_seek(file, 0, c.SEEK_SET); _ = c.sf_seek(file, 0, c.SEEK_SET);
std.testing.expectEqual(fmt.frames, frames_on_end); try std.testing.expectEqual(fmt.frames, frames_on_end);
const frames_on_end_by_end = c.sf_seek(file, frames_on_end, c.SEEK_SET); const frames_on_end_by_end = c.sf_seek(file, frames_on_end, c.SEEK_SET);
std.testing.expectEqual(frames_on_end, frames_on_end_by_end); try std.testing.expectEqual(frames_on_end, frames_on_end_by_end);
std.debug.warn("frames on end: {}, frame on end (2): {}\n", .{ frames_on_end, frames_on_end_by_end }); log.debug("frames on end: {}, frame on end (2): {}", .{ frames_on_end, frames_on_end_by_end });
return file.?; return file.?;
} }
@ -56,30 +57,30 @@ pub fn swrite(file: *c.SNDFILE, buf: [*]f32, frames: i64) !void {
const count = c.sf_writef_float(file, buf, frames); const count = c.sf_writef_float(file, buf, frames);
if (count != frames) { if (count != frames) {
std.debug.warn("Wanted to write {}, got {}\n", .{ frames, count }); log.debug("Wanted to write {}, got {}", .{ frames, count });
return ImageError.WriteFail; return ImageError.WriteFail;
} }
} }
pub fn sseek(file: *c.SNDFILE, offset: usize) void { pub fn sseek(file: *c.SNDFILE, offset: usize) void {
const offset_i64 = @intCast(i64, offset); const offset_i64 = @as(i64, @intCast(offset));
const frames = c.sf_seek(file, offset_i64, c.SEEK_SET); const frames = c.sf_seek(file, offset_i64, c.SEEK_SET);
const frames_current = c.sf_seek(file, 0, c.SEEK_CUR); const frames_current = c.sf_seek(file, 0, c.SEEK_CUR);
std.testing.expectEqual(frames, frames_current); std.debug.assert(frames == frames_current);
if (frames != offset_i64) { if (frames != offset_i64) {
std.debug.warn("failed to seek to {} (seeked {} frames, offset_i64={})\n", .{ offset, frames, offset_i64 }); log.debug("failed to seek to {} (seeked {} frames, offset_i64={})", .{ offset, frames, offset_i64 });
} }
} }
/// Caller owns the returned memory. /// Caller owns the returned memory.
pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 { pub fn temporaryName(allocator: std.mem.Allocator) ![]u8 {
const template_start = "/temp/temp_"; const template_start = "/temp/temp_";
const template = "/tmp/temp_XXXXXXXXXXXXXXXXXXXXX"; const template = "/tmp/temp_XXXXXXXXXXXXXXXXXXXXX";
var nam = try allocator.alloc(u8, template.len); var nam = try allocator.alloc(u8, template.len);
std.mem.copy(u8, nam, template); std.mem.copyForwards(u8, nam, template);
const seed = @truncate(u64, @bitCast(u128, std.time.nanoTimestamp())); const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
var r = std.rand.DefaultPrng.init(seed); var r = std.rand.DefaultPrng.init(seed);
var fill = nam[template_start.len..nam.len]; var fill = nam[template_start.len..nam.len];
@ -87,17 +88,17 @@ pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 {
var i: usize = 0; var i: usize = 0;
while (i < 100) : (i += 1) { while (i < 100) : (i += 1) {
// generate a random uppercase letter, that is, 65 + random number. // generate a random uppercase letter, that is, 65 + random number.
for (fill) |_, f_idx| { for (fill, 0..) |_, f_idx| {
var idx = @intCast(u8, r.random.uintLessThan(u5, 24)); const idx = @as(u8, @intCast(r.random().uintLessThan(u5, 24)));
var letter = @as(u8, 65) + idx; const letter = @as(u8, 65) + idx;
fill[f_idx] = letter; fill[f_idx] = letter;
} }
// if we fail to access it, we assume it doesn't exist and return it. // if we fail to access it, we assume it doesn't exist and return it.
var tmp_file: std.fs.File = std.fs.cwd().openFile( var tmp_file: std.fs.File = std.fs.cwd().openFile(
nam, nam,
.{ .read = true, .write = false }, .{ .mode = .read_only },
) catch |err| blk: { ) catch |err| {
if (err == error.FileNotFound) return nam else continue; if (err == error.FileNotFound) return nam else continue;
}; };
@ -121,7 +122,7 @@ pub fn mkSfInfo() c.SF_INFO {
} }
pub const Image = struct { pub const Image = struct {
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
/// Pointer to the underlying libsndfile's SNDFILE struct. /// Pointer to the underlying libsndfile's SNDFILE struct.
sndfile: *c.SNDFILE, sndfile: *c.SNDFILE,
@ -136,11 +137,11 @@ pub const Image = struct {
curpath: []const u8, curpath: []const u8,
/// Open a BMP image for later. /// Open a BMP image for later.
pub fn open(allocator: *std.mem.Allocator, path: []const u8) !*Image { pub fn open(allocator: std.mem.Allocator, path: []const u8) !*Image {
var in_fmt = mkSfInfo(); var in_fmt = mkSfInfo();
var sndfile = try sopen(allocator, path, c.SFM_READ, &in_fmt); const sndfile = try sopen(allocator, path, c.SFM_READ, &in_fmt);
var image = try allocator.create(Image); const image = try allocator.create(Image);
std.debug.assert(in_fmt.frames > @as(i64, 0)); std.debug.assert(in_fmt.frames > @as(i64, 0));
std.debug.assert(in_fmt.seekable == @as(i32, 1)); std.debug.assert(in_fmt.seekable == @as(i32, 1));
@ -150,7 +151,7 @@ pub const Image = struct {
.sndfile = sndfile, .sndfile = sndfile,
.path = path, .path = path,
.curpath = path, .curpath = path,
.frames = @intCast(usize, in_fmt.frames), .frames = @as(usize, @intCast(in_fmt.frames)),
}; };
return image; return image;
@ -159,10 +160,10 @@ pub const Image = struct {
pub fn clone(self: *Image) !*Image { pub fn clone(self: *Image) !*Image {
var in_fmt = mkSfInfo(); var in_fmt = mkSfInfo();
// clone sndfile // clone sndfile
var sndfile = try sopen(self.allocator, self.curpath, c.SFM_READ, &in_fmt); const sndfile = try sopen(self.allocator, self.curpath, c.SFM_READ, &in_fmt);
std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames)); std.debug.assert(self.frames == @as(usize, @intCast(in_fmt.frames)));
var image = try self.allocator.create(Image); const image = try self.allocator.create(Image);
std.debug.assert(in_fmt.frames > @as(i64, 0)); std.debug.assert(in_fmt.frames > @as(i64, 0));
std.debug.assert(in_fmt.seekable == @as(i32, 1)); std.debug.assert(in_fmt.seekable == @as(i32, 1));
@ -172,35 +173,37 @@ pub const Image = struct {
.sndfile = sndfile, .sndfile = sndfile,
.path = self.path, .path = self.path,
.curpath = self.curpath, .curpath = self.curpath,
.frames = @intCast(usize, in_fmt.frames), .frames = @as(usize, @intCast(in_fmt.frames)),
}; };
return image; return image;
} }
pub fn close(self: *Image) void { pub fn close(self: *Image) void {
//self.allocator.free(self.path); const st: i32 = c.sf_close(self.sndfile);
//self.allocator.free(self.curpath);
var st: i32 = c.sf_close(self.sndfile);
if (st != 0) { if (st != 0) {
std.debug.warn("Failed to close {} ({})\n", .{ log.debug("Failed to close {s} ({s})", .{
self.path, self.path,
c.sf_error_number(st), c.sf_error_number(st),
}); });
} }
self.allocator.free(self.path);
self.allocator.free(self.curpath);
var allocator = self.allocator;
self.* = undefined;
allocator.destroy(self);
} }
pub fn read(self: *Image, file_chans: c_int, buf: []f32) bool { 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(self.sndfile, buf.ptr, 1);
const buf_chans = @as(c_int, @intCast(buf.len));
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; var i = file_chans - 1;
while (i < buf_chans) : (i += 1) { while (i < buf_chans) : (i += 1) {
//buf[@intCast(usize, i)] = buf[i % file_chans]; //buf[@intCast(usize, i)] = buf[i % file_chans];
buf[@intCast(usize, i)] = buf[@intCast(usize, @mod(i, file_chans))]; buf[@as(usize, @intCast(i))] = buf[@as(usize, @intCast(@mod(i, file_chans)))];
} }
return n_read == 1; return n_read == 1;
@ -224,7 +227,7 @@ pub const Image = struct {
sseek(out_file, start); sseek(out_file, start);
while (i <= end) : (i += buf.len) { while (i <= end) : (i += buf.len) {
std.debug.warn("\t\ti={}, buf.len={}, end={}\n", .{ i, buf.len, end }); log.debug("\t\ti={d}, buf.len={d}, end={d}", .{ i, buf.len, end });
sseek(self.sndfile, i); sseek(self.sndfile, i);
sseek(out_file, i); sseek(out_file, i);
@ -234,13 +237,13 @@ pub const Image = struct {
var view: []f32 = buf[0..buf.len]; var view: []f32 = buf[0..buf.len];
if (bytes_until_end < buf.len) { if (bytes_until_end < buf.len) {
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, bytes_until_end)); read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @as(i64, @intCast(bytes_until_end)));
view = buf[0..bytes_until_end]; view = buf[0..bytes_until_end];
} else { } else {
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, buf.len)); read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @as(i64, @intCast(buf.len)));
} }
try swrite(out_file, view.ptr, @intCast(i64, view.len)); try swrite(out_file, view.ptr, @as(i64, @intCast(view.len)));
} }
sseek(self.sndfile, end); sseek(self.sndfile, end);
@ -249,8 +252,8 @@ pub const Image = struct {
fn getSeekPos(self: *Image, position: plugins.Position) plugins.SeekPos { fn getSeekPos(self: *Image, position: plugins.Position) plugins.SeekPos {
const file_end = self.frames; const file_end = self.frames;
var seek_pos = position.seekPos(file_end); const seek_pos = position.seekPos(file_end);
std.debug.warn("\tstart {} end {}\n", .{ seek_pos.start, seek_pos.end }); log.debug("\tstart {d} end {d}", .{ seek_pos.start, seek_pos.end });
return seek_pos; return seek_pos;
} }
@ -261,9 +264,9 @@ pub const Image = struct {
// std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames)); // std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames));
self.curpath = path; self.curpath = path;
self.frames = @intCast(usize, in_fmt.frames); self.frames = @as(usize, @intCast(in_fmt.frames));
std.debug.warn("\timage: reopened on '{}' (frames={}, fmt.frames={})\n", .{ log.debug("\timage: reopened on '{s}' (frames={d}, fmt.frames={d})", .{
self.curpath, self.curpath,
self.frames, self.frames,
in_fmt.frames, in_fmt.frames,
@ -271,7 +274,7 @@ pub const Image = struct {
} }
pub fn checkValid(self: *Image) !void { pub fn checkValid(self: *Image) !void {
var file = try std.fs.cwd().openFile(self.path, .{ .read = true }); var file = try std.fs.cwd().openFile(self.path, .{ .mode = .read_only });
defer file.close(); defer file.close();
// main bmp header: // main bmp header:
@ -303,33 +306,34 @@ pub const Image = struct {
defer ctx.deinit(); defer ctx.deinit();
var ports = try lv2.setupPorts(&ctx); var ports = try lv2.setupPorts(&ctx);
defer ctx.allocator.free(ports);
if (ctx.n_audio_in > 2) { if (ctx.n_audio_in > 2) {
std.debug.warn("plugin <{}> has more than two inputs.\n", .{plugin_uri}); log.debug("plugin <{s}> has more than two inputs.", .{plugin_uri});
return ImageError.InvalidPlugin; return ImageError.InvalidPlugin;
} }
if (ctx.n_audio_out > 2) { if (ctx.n_audio_out > 2) {
std.debug.warn("plugin <{}> has more than two outputs.\n", .{plugin_uri}); log.debug("plugin <{s}> has more than two outputs.", .{plugin_uri});
return ImageError.InvalidPlugin; return ImageError.InvalidPlugin;
} }
// now, for each param for the plugin, we find its port, and set // now, for each param for the plugin, we find its port, and set
// the value for the port there. // the value for the port there.
for (params.items) |param| { for (params.items) |param| {
var sym_cstr = try std.cstr.addNullByte(self.allocator, param.sym); const sym_cstr = try self.allocator.dupeZ(u8, param.sym);
defer self.allocator.free(sym_cstr); defer self.allocator.free(sym_cstr);
var sym = c.lilv_new_string(ctx.world, sym_cstr.ptr); const sym = c.lilv_new_string(ctx.world, sym_cstr.ptr);
const port = c.lilv_plugin_get_port_by_symbol(ctx.plugin, sym) orelse blk: { const port = c.lilv_plugin_get_port_by_symbol(ctx.plugin, sym) orelse {
std.debug.warn("assert fail: symbol {} not found on port\n", .{param.sym}); log.debug("assert fail: symbol {s} not found on port", .{param.sym});
return ImageError.InvalidSymbol; return ImageError.InvalidSymbol;
}; };
c.lilv_node_free(sym); c.lilv_node_free(sym);
var idx = c.lilv_port_get_index(ctx.plugin, port); const idx = c.lilv_port_get_index(ctx.plugin, port);
std.debug.warn("\tset sym={}, idx={} to val={}\n", .{ log.debug("\tset sym={s}, idx={d} to val={}", .{
param.sym, param.sym,
idx, idx,
param.value, param.value,
@ -339,11 +343,11 @@ pub const Image = struct {
// now we need to generate a temporary file and put the output of // now we need to generate a temporary file and put the output of
// running the plugin on that file // running the plugin on that file
var tmpnam = try temporaryName(self.allocator); const tmpnam = try temporaryName(self.allocator);
std.debug.warn("\trunning plugin from '{}' to '{}'\n", .{ self.curpath, tmpnam }); log.debug("\trunning plugin from '{s}' to '{s}'", .{ self.curpath, tmpnam });
var out_fmt = mkSfInfo(); var out_fmt = mkSfInfo();
var out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt); const out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
var rctx = try plugins.RunContext.init(self.allocator, ctx.plugin); var rctx = try plugins.RunContext.init(self.allocator, ctx.plugin);
defer rctx.deinit(); defer rctx.deinit();
@ -373,10 +377,10 @@ pub const Image = struct {
sseek(self.sndfile, seek_pos.start); sseek(self.sndfile, seek_pos.start);
var i: usize = seek_pos.start; var i: usize = seek_pos.start;
std.debug.warn("\tseek pos start: {} end: {}\n", .{ seek_pos.start, seek_pos.end }); log.debug("\tseek pos start: {d} end: {d}", .{ seek_pos.start, seek_pos.end });
var inbuf = &rctx.buffers.in; var inbuf = &rctx.buffers.in;
var outbuf = &rctx.buffers.out; const outbuf = &rctx.buffers.out;
while (i <= seek_pos.end) : (i += 1) { while (i <= seek_pos.end) : (i += 1) {
inbuf[0] = 0; inbuf[0] = 0;
@ -384,7 +388,7 @@ pub const Image = struct {
const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1); const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1);
if (read_bytes == 0) { if (read_bytes == 0) {
std.debug.warn("WARN! reached EOF at idx={}\n", .{i}); log.debug("WARN! reached EOF at idx={d}", .{i});
break; break;
} }
@ -412,12 +416,12 @@ pub const Image = struct {
try self.reopen(tmpnam); try self.reopen(tmpnam);
try self.checkValid(); try self.checkValid();
var time_taken = timer.read(); const time_taken = timer.read();
std.debug.warn("\ttook {d:.2}ms running plugin\n", .{time_taken / std.time.us_per_ms}); log.debug("\ttook {d:.2}ms running plugin", .{time_taken / std.time.us_per_ms});
} }
pub fn saveTo(self: *Image, out_path: []const u8) !void { pub fn saveTo(self: *Image, out_path: []const u8) !void {
std.debug.warn("\timg: copy from '{}' to '{}'\n", .{ self.curpath, out_path }); log.debug("\timg: copy from '{s}' to '{s}'", .{ self.curpath, out_path });
try std.fs.copyFileAbsolute(self.curpath, out_path, .{}); try std.fs.copyFileAbsolute(self.curpath, out_path, .{});
} }
@ -425,9 +429,9 @@ pub const Image = struct {
self: *Image, self: *Image,
comptime Plugin: type, comptime Plugin: type,
position: plugins.Position, position: plugins.Position,
extra: var, extra: anytype,
) !void { ) !void {
var plugin_opt: ?Plugin = Plugin.init(self.allocator, extra); const plugin_opt: ?Plugin = Plugin.init(self.allocator, extra);
if (plugin_opt == null) { if (plugin_opt == null) {
return ImageError.PluginLoadFail; return ImageError.PluginLoadFail;
} }
@ -444,11 +448,11 @@ pub const Image = struct {
// the code here is a copypaste of runPlugin() without the specific // the code here is a copypaste of runPlugin() without the specific
// lilv things. // lilv things.
var tmpnam = try temporaryName(self.allocator); const tmpnam = try temporaryName(self.allocator);
std.debug.warn("\trunning CUSTOM plugin from '{}' to '{}'\n", .{ self.curpath, tmpnam }); log.debug("\trunning CUSTOM plugin from '{s}' to '{s}'", .{ self.curpath, tmpnam });
var out_fmt = mkSfInfo(); var out_fmt = mkSfInfo();
var out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt); const out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
var bufs = plugins.RunBuffers{}; var bufs = plugins.RunBuffers{};
const seek_pos = self.getSeekPos(position); const seek_pos = self.getSeekPos(position);
@ -472,15 +476,15 @@ pub const Image = struct {
sseek(self.sndfile, seek_pos.start); sseek(self.sndfile, seek_pos.start);
var i: usize = seek_pos.start; var i: usize = seek_pos.start;
std.debug.warn("\tseek pos start: {} end: {}\n", .{ seek_pos.start, seek_pos.end }); log.debug("\tseek pos start: {d} end: {d}", .{ seek_pos.start, seek_pos.end });
var inbuf = &bufs.in; const inbuf = &bufs.in;
var outbuf = &bufs.out; const outbuf = &bufs.out;
while (i <= seek_pos.end) : (i += 1) { while (i <= seek_pos.end) : (i += 1) {
const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1); const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1);
if (read_bytes == 0) { if (read_bytes == 0) {
std.debug.warn("WARN! reached EOF at idx={}\n", .{i}); log.debug("WARN! reached EOF at idx={d}", .{i});
break; break;
} }

View file

@ -3,6 +3,7 @@ const std = @import("std");
const plugin = @import("plugin.zig"); const plugin = @import("plugin.zig");
const custom = @import("custom.zig"); const custom = @import("custom.zig");
const log = std.log.scoped(.scritcher_lang);
pub const ParseError = error{ParseFail}; pub const ParseError = error{ParseFail};
pub const CommandType = enum { pub const CommandType = enum {
@ -93,7 +94,7 @@ pub const Command = struct {
rotate, rotate,
}; };
pub fn tagToType(tag: Tag) type { pub fn tagToType(comptime tag: Tag) type {
return switch (tag) { return switch (tag) {
.noop => Noop, .noop => Noop,
.load => Load, .load => Load,
@ -135,8 +136,6 @@ pub const Command = struct {
.embed => Embed, .embed => Embed,
.rotate => Rotate, .rotate => Rotate,
else => @panic("TODO"),
}; };
} }
@ -144,11 +143,23 @@ pub const Command = struct {
if (base.tag != T.base_tag) if (base.tag != T.base_tag)
return null; return null;
return @fieldParentPtr(T, "base", base); //const baseInt = @intFromPtr(base);
//log.debug("casting from {d}", .{baseInt});
//log.debug("aligns from 8? {d}", .{baseInt % 8});
//log.debug("align T: {d} {s}", .{ @alignOf(*T), @typeName(T) });
//log.debug("align base: {d} {s}", .{ @alignOf(*const @This()), @typeName(@This()) });
const base_aligned: *const @This() = @alignCast(base);
const parented = @as(*const T, @alignCast(@fieldParentPtr("base", base_aligned)));
const ptr: *const T = @alignCast(parented);
//log.debug("align: {d}\n", .{@alignOf(@TypeOf(ptr))});
return ptr;
} }
pub fn print(base: *const @This()) void { pub fn print(base: *const @This()) void {
std.debug.warn("tag: {}\n", .{base.tag}); log.debug("tag: {}", .{base.tag});
} }
pub const Noop = struct { pub const Noop = struct {
@ -159,7 +170,7 @@ pub const Command = struct {
pub const Load = struct { pub const Load = struct {
pub const base_tag = Tag.load; pub const base_tag = Tag.load;
base: Command, base: Command,
path: []u8, path: []const u8,
}; };
pub const Quicksave = struct { pub const Quicksave = struct {
@ -201,33 +212,25 @@ pub const Command = struct {
pub const Amp = LV2Command( pub const Amp = LV2Command(
.amp, .amp,
"http://lv2plug.in/plugins/eg-amp", "http://lv2plug.in/plugins/eg-amp",
struct { struct { gain: f32 },
gain: f32
},
); );
pub const RFlanger = LV2Command( pub const RFlanger = LV2Command(
.rflanger, .rflanger,
"http://plugin.org.uk/swh-plugins/retroFlange", "http://plugin.org.uk/swh-plugins/retroFlange",
struct { struct { delay_depth_avg: f32, law_freq: f32 },
delay_depth_avg: f32, law_freq: f32
},
); );
pub const Eq = LV2Command( pub const Eq = LV2Command(
.rflanger, .rflanger,
"http://plugin.org.uk/swh-plugins/dj_eq_mono", "http://plugin.org.uk/swh-plugins/dj_eq_mono",
struct { struct { lo: f32, mid: f32, hi: f32 },
lo: f32, mid: f32, hi: f32
},
); );
pub const Phaser = LV2Command( pub const Phaser = LV2Command(
.phaser, .phaser,
"http://plugin.org.uk/swh-plugins/lfoPhaser", "http://plugin.org.uk/swh-plugins/lfoPhaser",
struct { struct { lfo_rate: f32, lfo_depth: f32, fb: f32, spread: f32 },
lfo_rate: f32, lfo_depth: f32, fb: f32, spread: f32
},
); );
pub const Mbeq = LV2Command( pub const Mbeq = LV2Command(
@ -380,9 +383,7 @@ pub const Command = struct {
pub const Foverdrive = LV2Command(.foverdrive, "http://plugin.org.uk/swh-plugins/foverdrive", struct { pub const Foverdrive = LV2Command(.foverdrive, "http://plugin.org.uk/swh-plugins/foverdrive", struct {
drive: f32, drive: f32,
}); });
pub const Thruzero = LV2Command(.thruzero, "http://drobilla.net/plugins/mda/ThruZero", struct { pub const Thruzero = LV2Command(.thruzero, "http://drobilla.net/plugins/mda/ThruZero", struct { rate: f32, mix: f32, feedback: f32, depth_mod: f32 });
rate: f32, mix: f32, feedback: f32, depth_mod: f32
});
pub const Gverb = LV2Command(.gverb, "http://plugin.org.uk/swh-plugins/gverb", struct { pub const Gverb = LV2Command(.gverb, "http://plugin.org.uk/swh-plugins/gverb", struct {
roomsize: f32, roomsize: f32,
@ -476,50 +477,94 @@ pub const Command = struct {
}); });
}; };
pub const CommandList = std.ArrayList(*Command); const CmdArrayList = std.ArrayList(*Command);
pub const CommandList = struct {
list: CmdArrayList,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.list = CmdArrayList.init(allocator),
};
}
pub fn deinit(self: *Self) void {
for (self.list.items) |cmd_ptr| {
inline for (@typeInfo(Command.Tag).Enum.fields) |field| {
if (cmd_ptr.tag == @field(Command.Tag, field.name)) {
const actual_tag =
@field(Command.Tag, field.name);
// if we find a match on the tag, we can get the type
const typ = Command.tagToType(actual_tag);
const inner_command = cmd_ptr.cast(typ).?;
inline for (@typeInfo(typ).Struct.fields) |cmd_field| {
switch (cmd_field.type) {
[]u8, []const u8 => self.list.allocator.free(@field(inner_command, cmd_field.name)),
else => {},
}
}
}
}
//TODO this is ian invalid free
//self.list.allocator.destroy(cmd_ptr);
}
self.list.deinit();
}
pub fn append(self: *Self, cmd: *Command) !void {
return try self.list.append(cmd);
}
};
/// A parser. /// A parser.
pub const Lang = struct { pub const Lang = struct {
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
has_error: bool = false, has_error: bool = false,
line: usize = 0, line: usize = 0,
pub fn init(allocator: *std.mem.Allocator) Lang { pub fn init(allocator: std.mem.Allocator) Lang {
return Lang{ return Lang{
.allocator = allocator, .allocator = allocator,
}; };
} }
pub fn deinit(self: *Lang) void {} pub fn deinit(self: *Lang) void {
_ = self;
}
pub fn reset(self: *Lang) void { pub fn reset(self: *Lang) void {
self.has_error = false; self.has_error = false;
self.line = 0; self.line = 0;
} }
fn doError(self: *Lang, comptime fmt: []const u8, args: var) void {
std.debug.warn("ERROR! at line {}: ", .{self.line}); fn doError(self: *Lang, comptime fmt: []const u8, args: anytype) void {
std.debug.warn(fmt, args); log.warn("ERROR! at line {}: ", .{self.line});
std.debug.warn("\n", .{}); log.warn(fmt, args);
self.has_error = true; self.has_error = true;
} }
fn parseCommandArguments( fn parseCommandArguments(
self: *@This(), self: *@This(),
comptime command_struct: type, comptime command_struct: type,
tok_it: *std.mem.TokenIterator, tok_it: *std.mem.SplitIterator(u8, .sequence),
commands: *CommandList, commands: *CommandList,
) !void { ) !void {
// Based on the command struct fields, we can parse the arguments. // Based on the command struct fields, we can parse the arguments.
var cmd = try self.allocator.create(command_struct); var cmd = try self.allocator.create(command_struct);
// we already add the command to the list to prevent memory leaks
// by commands that error out
try commands.append(&cmd.base);
const is_lv2_command = switch (command_struct.base_tag) { const is_lv2_command = switch (command_struct.base_tag) {
.noop, .load, .quicksave, .runqs, .rotate => false, .noop, .load, .quicksave, .runqs, .rotate => false,
else => true, else => true,
}; };
// TODO: crash when no arguments are left but we still need
// arguments...
if (is_lv2_command) { if (is_lv2_command) {
const split = tok_it.next(); const split = tok_it.next();
if (split == null) { if (split == null) {
@ -539,16 +584,16 @@ pub const Lang = struct {
inline for (@typeInfo(@TypeOf(cmd.parameters)).Struct.fields) |cmd_field| { inline for (@typeInfo(@TypeOf(cmd.parameters)).Struct.fields) |cmd_field| {
const maybe_arg = tok_it.next(); const maybe_arg = tok_it.next();
if (maybe_arg == null) { if (maybe_arg == null) {
self.doError("Expected parameter for {}, got nothing", .{cmd_field.name}); self.doError("Expected parameter for {s}, got nothing", .{cmd_field.name});
return; return;
} }
const arg = maybe_arg.?; const arg = maybe_arg.?;
const arg_value = switch (cmd_field.field_type) { const arg_value = switch (cmd_field.type) {
f32 => try std.fmt.parseFloat(f32, arg), f32 => try std.fmt.parseFloat(f32, arg),
u64 => try std.fmt.parseInt(u64, arg, 10), u64 => try std.fmt.parseInt(u64, arg, 10),
usize => try std.fmt.parseInt(usize, arg, 10), usize => try std.fmt.parseInt(usize, arg, 10),
[]const u8 => try self.allocator.dupe(u8, arg), []const u8 => @as([]const u8, try self.allocator.dupe(u8, arg)),
else => @compileError("parameter struct has unsupported type " ++ @typeName(cmd_field.field_type)), else => @compileError("parameter struct has unsupported type " ++ @typeName(cmd_field.field_type)),
}; };
@ -561,17 +606,23 @@ pub const Lang = struct {
continue; continue;
} }
} }
const arg = tok_it.next().?;
const argument_value = switch (cmd_field.field_type) { const arg_opt = tok_it.next();
if (arg_opt == null) {
self.doError("Expected parameter for {s}, got nothing", .{cmd_field.name});
return;
}
const arg = arg_opt.?;
const argument_value = switch (cmd_field.type) {
usize => try std.fmt.parseInt(usize, arg, 10), usize => try std.fmt.parseInt(usize, arg, 10),
i32 => try std.fmt.parseInt(i32, arg, 10), i32 => try std.fmt.parseInt(i32, arg, 10),
f32 => try std.fmt.parseFloat(f32, arg), f32 => try std.fmt.parseFloat(f32, arg),
[]u8 => try self.allocator.dupe(u8, arg), []const u8 => @as([]const u8, try self.allocator.dupe(u8, arg)),
[]const u8 => try self.allocator.dupe(u8, arg),
else => @compileError("Invalid parameter type (" ++ @typeName(cmd_field.field_type) ++ ") left on command struct " ++ @typeName(command_struct) ++ "."), else => @compileError("Invalid parameter type (" ++ @typeName(cmd_field.field_type) ++ ") left on command struct " ++ @typeName(command_struct) ++ "."),
}; };
std.debug.warn("parsing {}, arg of type {} => {}\n", .{ log.debug("parsing {s}, arg of type {s} => {any}", .{
@typeName(command_struct), @typeName(command_struct),
@typeName(@TypeOf(argument_value)), @typeName(@TypeOf(argument_value)),
argument_value, argument_value,
@ -583,14 +634,13 @@ pub const Lang = struct {
cmd.base.tag = command_struct.base_tag; cmd.base.tag = command_struct.base_tag;
const command = cmd.base.cast(command_struct).?; const command = cmd.base.cast(command_struct).?;
std.debug.warn("cmd: {}\n", .{command}); log.debug("cmd: {}", .{command});
try commands.append(&cmd.base);
} }
pub fn parse(self: *Lang, data: []const u8) !CommandList { pub fn parse(self: *Lang, data: []const u8) !CommandList {
var splitted_it = std.mem.split(data, ";"); var splitted_it = std.mem.split(u8, data, ";");
var cmds = CommandList.init(self.allocator); var cmds = CommandList.init(self.allocator);
errdefer cmds.deinit();
while (splitted_it.next()) |stmt_orig| { while (splitted_it.next()) |stmt_orig| {
self.line += 1; self.line += 1;
@ -601,9 +651,9 @@ pub const Lang = struct {
if (std.mem.startsWith(u8, stmt, "#")) continue; if (std.mem.startsWith(u8, stmt, "#")) continue;
// TODO better tokenizer instead of just tokenize(" ")...maybe???? // TODO better tokenizer instead of just tokenize(" ")...maybe????
var tok_it = std.mem.tokenize(stmt, " "); var tok_it = std.mem.splitSequence(u8, stmt, " ");
var cmd_opt = tok_it.next(); const cmd_opt = tok_it.next();
if (cmd_opt == null) { if (cmd_opt == null) {
self.doError("No command given", .{}); self.doError("No command given", .{});
continue; continue;
@ -613,38 +663,51 @@ pub const Lang = struct {
var found: bool = false; var found: bool = false;
inline for (@typeInfo(Command).Struct.decls) |cmd_struct_decl| { inline for (@typeInfo(Command).Struct.decls) |cmd_struct_decl| {
switch (cmd_struct_decl.data) { const struct_name = cmd_struct_decl.name;
.Type => |typ| switch (@typeInfo(typ)) { const cmd_struct_type = @field(Command, struct_name);
.Struct => {},
else => continue, const info_of_info = @typeInfo(@TypeOf(cmd_struct_type));
},
switch (info_of_info) {
.Type => {},
else => continue,
}
const info = @typeInfo(cmd_struct_type);
switch (info) {
.Struct => {},
else => continue, else => continue,
} }
const struct_name = cmd_struct_decl.name;
comptime var lowered_command_name = [_]u8{0} ** struct_name.len; comptime var lowered_command_name = [_]u8{0} ** struct_name.len;
var runtime_lowered_command_name = [_]u8{0} ** struct_name.len;
comptime { comptime {
for (struct_name) |c, i| { for (struct_name, 0..) |c, i| {
lowered_command_name[i] = std.ascii.toLower(c); lowered_command_name[i] = std.ascii.toLower(c);
} }
} }
const c_l = lowered_command_name;
std.mem.copyForwards(u8, &runtime_lowered_command_name, &c_l);
// if we have a match, we know the proper struct type // if we have a match, we know the proper struct type
// to use. this actually works compared to storing command_struct // to use. this actually works compared to storing command_struct
// in a variable because then that variable must be comptime. // in a variable because then that variable must be comptime.
// the drawback of this approach is that our emitted code is basically linear // the drawback of this approach is that our emitted code is basically linear
// because we don't use the hashmap anymore. maybe #5359 can help. // because we don't use the hashmap anymore.
//
// Attempting to use ComptimeHashMap hits compiler bugs and I'm
// not sure if we can map strings to *types* in it.
if (std.mem.eql(u8, &lowered_command_name, command_string)) { if ((!found) and std.mem.eql(u8, &runtime_lowered_command_name, command_string)) {
found = true; found = true;
const cmd_struct_type = cmd_struct_decl.data.Type;
try self.parseCommandArguments(cmd_struct_type, &tok_it, &cmds); try self.parseCommandArguments(cmd_struct_type, &tok_it, &cmds);
} }
} }
if (!found) { if (!found) {
self.doError("Unknown command '{}' ({})", .{ command_string, command_string.len }); self.doError("Unknown command '{s}' ({d} bytes)", .{ command_string, command_string.len });
continue; continue;
} }
} }
@ -656,18 +719,18 @@ pub const Lang = struct {
}; };
test "noop" { test "noop" {
var lang = Lang.init(std.heap.direct_allocator); var lang = Lang.init(std.testing.allocator);
defer lang.deinit(); defer lang.deinit();
var cmds = try lang.parse("noop;"); var cmds = try lang.parse("noop;");
defer cmds.deinit(); defer cmds.deinit();
std.testing.expectEqual(cmds.len, 1); try std.testing.expectEqual(cmds.list.items.len, 1);
std.testing.expectEqual(cmds.items[0].command, .Noop); try std.testing.expectEqual(cmds.list.items[0].tag, .noop);
} }
test "load, phaser, quicksave" { test "load, phaser, quicksave" {
var lang = Lang.init(std.heap.direct_allocator); var lang = Lang.init(std.testing.allocator);
defer lang.deinit(); defer lang.deinit();
const prog = const prog =
@ -679,8 +742,21 @@ test "load, phaser, quicksave" {
var cmds = try lang.parse(prog); var cmds = try lang.parse(prog);
defer cmds.deinit(); defer cmds.deinit();
std.testing.expectEqual(cmds.len, 3); try std.testing.expectEqual(cmds.list.items.len, 3);
std.testing.expectEqual(cmds.items[0].command, .Load); try std.testing.expectEqual(cmds.list.items[0].tag, .load);
std.testing.expectEqual(cmds.items[1].command, .Phaser); try std.testing.expectEqual(cmds.list.items[1].tag, .phaser);
std.testing.expectEqual(cmds.items[2].command, .Quicksave); try std.testing.expectEqual(cmds.list.items[2].tag, .quicksave);
}
test "load, phaser with errors, quicksave" {
var lang = Lang.init(std.testing.allocator);
defer lang.deinit();
const prog =
\\load :0;
\\phaser 3 1 25;
\\quicksave;
;
try std.testing.expectError(error.ParseFail, lang.parse(prog));
} }

View file

@ -1,10 +1,12 @@
const std = @import("std"); const std = @import("std");
const plugin = @import("plugin.zig"); const plugin = @import("plugin.zig");
const log = std.log.scoped(.scritcher_lv2);
pub const c = @cImport({ pub const c = @cImport({
@cInclude("sndfile.h"); @cInclude("sndfile.h");
@cInclude("lilv/lilv.h"); @cInclude("lilv/lilv.h");
@cInclude("lv2/core/lv2.h"); @cInclude("lv2.h");
}); });
pub fn Lv2Core(comptime ns: []const u8) []const u8 { pub fn Lv2Core(comptime ns: []const u8) []const u8 {
@ -21,7 +23,7 @@ const LV2_CORE__connectionOptional = Lv2Core("#connectionOptional");
pub fn lilv_instance_connect_port( pub fn lilv_instance_connect_port(
instance: [*c]c.LilvInstance, instance: [*c]c.LilvInstance,
port_index: u32, port_index: u32,
data_location: ?*c_void, data_location: ?*anyopaque,
) void { ) void {
instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location); instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location);
} }
@ -59,14 +61,16 @@ pub const Port = struct {
/// Setup ports for a given plugin. Gives an array to pointers of Port structs. /// 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 /// This setup is required so we link the plugin to the ports later on, and
/// also link our buffers, and control values. /// also link our buffers, and control values.
///
/// Caller owns returned memory.
pub fn setupPorts(ctx: *plugin.Context) ![]Port { pub fn setupPorts(ctx: *plugin.Context) ![]Port {
var world = ctx.world; const world = ctx.world;
const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin); const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin);
var ports = try ctx.allocator.alloc(Port, n_ports); var ports = try ctx.allocator.alloc(Port, n_ports);
for (ports) |_, idx| { for (ports, 0..) |_, idx| {
var port: *Port = &ports[idx]; const port: *Port = &ports[idx];
port.* = Port{ port.* = Port{
.lilv_port = null, .lilv_port = null,
.ptype = .Control, .ptype = .Control,
@ -77,24 +81,24 @@ pub fn setupPorts(ctx: *plugin.Context) ![]Port {
}; };
} }
var values: []f32 = try ctx.allocator.alloc(f32, n_ports); const values: []f32 = try ctx.allocator.alloc(f32, n_ports);
defer ctx.allocator.free(values); defer ctx.allocator.free(values);
c.lilv_plugin_get_port_ranges_float(ctx.plugin, null, null, values.ptr); 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); const lv2_InputPort = c.lilv_new_uri(world, LV2_CORE__InputPort.ptr).?;
defer std.heap.c_allocator.destroy(lv2_InputPort); //defer std.heap.c_allocator.destroy(lv2_InputPort);
var lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr); const lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr).?;
defer std.heap.c_allocator.destroy(lv2_OutputPort); //defer std.heap.c_allocator.destroy(lv2_OutputPort);
var lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr); const lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr).?;
defer std.heap.c_allocator.destroy(lv2_AudioPort); //defer std.heap.c_allocator.destroy(lv2_AudioPort);
var lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr); const lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr).?;
defer std.heap.c_allocator.destroy(lv2_ControlPort); //defer std.heap.c_allocator.destroy(lv2_ControlPort);
var lv2_connectionOptional = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr); const lv2_connection_string = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr).?;
defer std.heap.c_allocator.destroy(lv2_connectionOptional); //defer std.heap.c_allocator.destroy(lv2_connection_string);
var i: u32 = 0; var i: u32 = 0;
while (i < n_ports) : (i += 1) { while (i < n_ports) : (i += 1) {
@ -111,12 +115,12 @@ pub fn setupPorts(ctx: *plugin.Context) ![]Port {
port.value = values[i]; port.value = values[i];
} }
port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connectionOptional); port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connection_string);
if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) { if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) {
port.is_input = true; port.is_input = true;
} else if (!c.lilv_port_is_a(ctx.plugin, lport, lv2_OutputPort) and !port.optional) { } 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}); log.debug("Port {d} is neither input or output", .{i});
return error.UnassignedIOPort; return error.UnassignedIOPort;
} }
@ -132,7 +136,7 @@ pub fn setupPorts(ctx: *plugin.Context) ![]Port {
ctx.n_audio_out += 1; ctx.n_audio_out += 1;
} }
} else if (!port.optional) { } else if (!port.optional) {
std.debug.warn("Port {} has unsupported type\n", .{i}); log.debug("Port {d} has unsupported type", .{i});
return error.UnsupportedPortType; return error.UnsupportedPortType;
} }
} }

View file

@ -2,6 +2,7 @@
const std = @import("std"); const std = @import("std");
const images = @import("image.zig"); const images = @import("image.zig");
const log = std.log.scoped(.scritcher_magick);
const Image = images.Image; const Image = images.Image;
const mc = @cImport({ const mc = @cImport({
@ -14,7 +15,7 @@ pub const MagickContext = struct {
pub fn init() !MagickContext { pub fn init() !MagickContext {
mc.InitializeMagick(null); mc.InitializeMagick(null);
var wand = mc.NewMagickWand(); const wand = mc.NewMagickWand();
if (wand == null) return error.WandCreateFail; if (wand == null) return error.WandCreateFail;
return MagickContext{ return MagickContext{
@ -28,6 +29,7 @@ pub const MagickContext = struct {
} }
pub fn doErr(self: *MagickContext) !void { pub fn doErr(self: *MagickContext) !void {
_ = self;
return error.WandError; return error.WandError;
} }
}; };
@ -36,10 +38,10 @@ fn magickLoad(image: *Image) !MagickContext {
var mctx = try MagickContext.init(); var mctx = try MagickContext.init();
errdefer mctx.deinit(); errdefer mctx.deinit();
var curpath = try std.cstr.addNullByte(image.allocator, image.curpath); const curpath = try image.allocator.dupeZ(u8, image.curpath);
defer image.allocator.free(curpath); defer image.allocator.free(curpath);
std.debug.warn("loading '{}'\n", .{curpath}); log.debug("loading '{s}'", .{curpath});
if (mc.MagickReadImage(mctx.wand, curpath.ptr) != 1) if (mc.MagickReadImage(mctx.wand, curpath.ptr) != 1)
return error.MagickReadFail; return error.MagickReadFail;
@ -50,16 +52,16 @@ fn magickLoad(image: *Image) !MagickContext {
fn magickSave(image: *Image, wand: *mc.MagickWand) !void { fn magickSave(image: *Image, wand: *mc.MagickWand) !void {
const allocator = image.allocator; const allocator = image.allocator;
var tmpnam = try images.temporaryName(allocator); const tmpnam = try images.temporaryName(allocator);
var c_tmpnam = try std.cstr.addNullByte(allocator, tmpnam); const c_tmpnam = try allocator.dupeZ(u8, tmpnam);
defer allocator.free(c_tmpnam); defer allocator.free(c_tmpnam);
std.debug.warn("\tmagick: saving to '{}'..", .{c_tmpnam}); log.debug("\tmagick: saving to '{s}'..", .{c_tmpnam});
if (mc.MagickWriteImage(wand, c_tmpnam.ptr) != 1) if (mc.MagickWriteImage(wand, c_tmpnam.ptr) != 1)
return error.MagickWriteFail; return error.MagickWriteFail;
std.debug.warn("OK\n", .{}); log.debug("OK", .{});
try image.reopen(tmpnam); try image.reopen(tmpnam);
} }
@ -70,7 +72,7 @@ pub fn runRotate(image: *Image, deg: f32, bgfill: []const u8) !void {
var mctx = try magickLoad(image); var mctx = try magickLoad(image);
defer mctx.deinit(); defer mctx.deinit();
var bg = mc.NewPixelWand(); const bg = mc.NewPixelWand();
defer mc.DestroyPixelWand(bg); defer mc.DestroyPixelWand(bg);
if (mc.PixelSetColor(bg, bgfill.ptr) != 1) if (mc.PixelSetColor(bg, bgfill.ptr) != 1)

View file

@ -3,6 +3,8 @@ const langs = @import("lang.zig");
const runners = @import("runner.zig"); const runners = @import("runner.zig");
const printer = @import("printer.zig"); const printer = @import("printer.zig");
const log = std.log.scoped(.scritcher);
test "scritcher" { test "scritcher" {
_ = @import("lang.zig"); _ = @import("lang.zig");
_ = @import("runner.zig"); _ = @import("runner.zig");
@ -16,30 +18,30 @@ const readline = @cImport({
@cInclude("readline/history.h"); @cInclude("readline/history.h");
}); });
fn wrapInCmdList(allocator: *std.mem.Allocator, cmd: *langs.Command) !langs.CommandList { fn wrapInCmdList(allocator: std.mem.Allocator, cmd: *langs.Command) !langs.CommandList {
var cmds = langs.CommandList.init(allocator); var cmds = langs.CommandList.init(allocator);
try cmds.append(cmd); try cmds.append(cmd);
return cmds; return cmds;
} }
fn copyCommandToHeap(allocator: *std.mem.Allocator, command: langs.Command, comptime tag: langs.Command.Tag) !*langs.Command { fn copyCommandToHeap(allocator: std.mem.Allocator, command: langs.Command, comptime tag: langs.Command.Tag) !*langs.Command {
const CommandStruct = langs.Command.tagToType(tag); const CommandStruct = langs.Command.tagToType(tag);
const casted = command.cast(CommandStruct).?; const casted = command.cast(CommandStruct).?;
var heap_cmd = try allocator.create(CommandStruct); var heap_cmd = try allocator.create(CommandStruct);
@memcpy( heap_cmd.* = casted.*;
@ptrCast([*]u8, &heap_cmd), log.debug("casted: {}", .{casted});
@ptrCast([*]const u8, &casted), log.debug("heap_cmd: {}", .{heap_cmd});
@sizeOf(CommandStruct),
);
return &heap_cmd.base; return &heap_cmd.base;
} }
pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { pub fn doRepl(allocator: std.mem.Allocator, args_it: anytype) !void {
var stdout_file = std.io.getStdOut(); var stdout_file = std.io.getStdOut();
const stdout = &stdout_file.outStream(); const stdout = &stdout_file.writer();
const scri_path = try (args_it.next(allocator) orelse @panic("expected scri path")); const scri_path = (args_it.next() orelse @panic("expected scri path"));
errdefer allocator.free(scri_path);
defer allocator.free(scri_path);
var file_read_opt: ?std.fs.File = std.fs.cwd().openFile(scri_path, .{}) catch |err| blk: { var file_read_opt: ?std.fs.File = std.fs.cwd().openFile(scri_path, .{}) catch |err| blk: {
if (err == error.FileNotFound) break :blk null; if (err == error.FileNotFound) break :blk null;
@ -54,18 +56,18 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
defer lang.deinit(); defer lang.deinit();
if (total_bytes > 0) { if (total_bytes > 0) {
// this MUST BE long lived (a reference to it is kept inside const scri_existing = try allocator.alloc(u8, total_bytes);
// existing_cmds, and then passed along to cmds),
// we can't defer them here
var scri_existing = try allocator.alloc(u8, total_bytes);
_ = try file_read_opt.?.read(scri_existing); _ = try file_read_opt.?.read(scri_existing);
defer allocator.free(scri_existing);
// we can defer this because we copy the Command structs back to cmds // we can't defer this directly because we copy the
// Command pointers to the cmds list. running deinit() directly
// would cause those pointers to be freed.
var existing_cmds = try lang.parse(scri_existing); var existing_cmds = try lang.parse(scri_existing);
defer existing_cmds.deinit(); defer existing_cmds.list.deinit();
// copy the existing command list into the repl's command list // copy the existing command list into the repl's command list
for (existing_cmds.items) |existing_cmd| { for (existing_cmds.list.items) |existing_cmd| {
try cmds.append(existing_cmd); try cmds.append(existing_cmd);
} }
} else { } else {
@ -75,7 +77,7 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
// TODO: deliberate memleak here. we only allocate this // TODO: deliberate memleak here. we only allocate this
// command once, for the start of the file, so. // command once, for the start of the file, so.
var load_cmd = try allocator.create(langs.Command.Load); var load_cmd = try allocator.create(langs.Command.Load);
std.mem.copy(u8, load_cmd.path, ":0"); load_cmd.path = ":0";
load_cmd.base.tag = langs.Command.Tag.load; load_cmd.base.tag = langs.Command.Tag.load;
// taking address is fine, because load_cmd lives in the lifetime // taking address is fine, because load_cmd lives in the lifetime
@ -87,14 +89,14 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
file_read.close(); file_read.close();
} }
var file = try std.fs.cwd().openFile(scri_path, .{ var file = try std.fs.cwd().createFile(scri_path, .{
.write = true,
.read = false, .read = false,
.truncate = true,
}); });
defer file.close(); defer file.close();
var out = file.outStream(); var out = file.writer();
var stream = &out; const stream = &out;
// since we opened the file for writing, it becomes empty, so, to ensure // since we opened the file for writing, it becomes empty, so, to ensure
// we don't fuck up later on, we print cmds before starting the repl // we don't fuck up later on, we print cmds before starting the repl
@ -118,7 +120,7 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
// run the load command // run the load command
try runner.runCommands(cmds, true); try runner.runCommands(cmds, true);
const wanted_runner: []const u8 = std.os.getenv("SCRITCHER_RUNNER") orelse "ristretto"; const wanted_runner: []const u8 = std.posix.getenv("SCRITCHER_RUNNER") orelse "ristretto";
var runqs_cmd = langs.Command.RunQS{ var runqs_cmd = langs.Command.RunQS{
.base = langs.Command{ .tag = langs.Command.Tag.runqs }, .base = langs.Command{ .tag = langs.Command.Tag.runqs },
@ -130,13 +132,13 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
var rd_line = readline.readline("> "); var rd_line = readline.readline("> ");
if (rd_line == null) { if (rd_line == null) {
std.debug.warn("leaving from eof\n", .{}); log.debug("leaving from eof", .{});
break; break;
} }
readline.add_history(rd_line); readline.add_history(rd_line);
//defer std.heap.c_allocator.destroy(rd_line); //defer std.heap.c_allocator.destroy(rd_line);
var line = rd_line[0..std.mem.len(rd_line)]; const line = rd_line[0..std.mem.len(rd_line)];
if (std.mem.eql(u8, line, "push")) { if (std.mem.eql(u8, line, "push")) {
const heap_cmd = switch (current.tag) { const heap_cmd = switch (current.tag) {
@ -198,21 +200,20 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
try printer.printList(cmds, stream); try printer.printList(cmds, stream);
continue; continue;
} else if (std.mem.eql(u8, line, "quit") or std.mem.eql(u8, line, "q")) { } else if (std.mem.eql(u8, line, "quit") or std.mem.eql(u8, line, "q")) {
std.debug.warn("leaving\n", .{}); log.debug("leaving", .{});
break; break;
} else if (std.mem.startsWith(u8, line, "#")) { } else if (std.mem.startsWith(u8, line, "#")) {
continue; continue;
} }
var cmds_parsed = lang.parse(line) catch |err| { var cmds_parsed = lang.parse(line) catch |err| {
std.debug.warn("repl: error while parsing: {}\n", .{err}); log.debug("repl: error while parsing: {}", .{err});
continue; continue;
}; };
// no command? ignore! // no command? ignore!
if (cmds_parsed.items.len == 0) continue; if (cmds_parsed.list.items.len == 0) continue;
current = cmds_parsed.list.items[0].*;
current = cmds_parsed.items[0].*;
// by cloning the parent runner, we can iteratively write // by cloning the parent runner, we can iteratively write
// whatever command we want and only commit the good results // whatever command we want and only commit the good results
@ -229,25 +230,21 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
} }
} }
pub fn main() !void { fn doHelp() void {
const allocator = std.heap.page_allocator; log.debug("scritcher!", .{});
log.debug("usage: scritcher [run|help|repl]", .{});
log.debug("\tscritcher run path_to_script.scri path_to_input_file.bmp", .{});
log.debug("\tscritcher repl path_to_script.scri path_to_input_file.bmp", .{});
}
fn doRun(allocator: std.mem.Allocator, args_it: anytype) !void {
var lang = langs.Lang.init(allocator); var lang = langs.Lang.init(allocator);
defer lang.deinit(); defer lang.deinit();
var runner = runners.Runner.init(allocator, false); var runner = runners.Runner.init(allocator, false);
defer runner.deinit(); defer runner.deinit();
var args_it = std.process.args(); const scri_path = (args_it.next() orelse @panic("run: expected scri path"));
// TODO print help
_ = try (args_it.next(allocator) orelse @panic("expected exe name"));
const scri_path = try (args_it.next(allocator) orelse @panic("expected scri path or 'repl'"));
if (std.mem.eql(u8, scri_path, "repl")) {
return try doRepl(allocator, &args_it);
}
var file = try std.fs.cwd().openFile(scri_path, .{}); var file = try std.fs.cwd().openFile(scri_path, .{});
defer file.close(); defer file.close();
@ -255,7 +252,7 @@ pub fn main() !void {
// sadly, we read it all into memory. such is life // sadly, we read it all into memory. such is life
const total_bytes = try file.getEndPos(); const total_bytes = try file.getEndPos();
var data = try allocator.alloc(u8, total_bytes); const data = try allocator.alloc(u8, total_bytes);
defer allocator.free(data); defer allocator.free(data);
_ = try file.read(data); _ = try file.read(data);
@ -264,3 +261,33 @@ pub fn main() !void {
try runner.runCommands(cmds, true); try runner.runCommands(cmds, true);
} }
pub fn main() !void {
var allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
defer {
_ = allocator_instance.deinit();
}
const allocator = allocator_instance.allocator();
var args_it = try std.process.argsWithAllocator(allocator);
defer args_it.deinit();
_ = args_it.skip();
const cli_command_opt = args_it.next();
if (cli_command_opt == null) {
return doHelp();
}
const cli_command = cli_command_opt.?;
if (std.mem.eql(u8, cli_command, "help")) {
return doHelp();
} else if (std.mem.eql(u8, cli_command, "repl")) {
return try doRepl(allocator, &args_it);
} else if (std.mem.eql(u8, cli_command, "run")) {
return try doRun(allocator, &args_it);
} else {
log.debug("unknown command: '{s}'", .{cli_command});
return error.UnknownCommand;
}
}

View file

@ -3,6 +3,7 @@ const std = @import("std");
const lv2 = @import("lv2_helpers.zig"); const lv2 = @import("lv2_helpers.zig");
const c = lv2.c; const c = lv2.c;
const log = std.log.scoped(.scritcher_plugin);
const ImageError = @import("image.zig").ImageError; const ImageError = @import("image.zig").ImageError;
/// Control port /// Control port
@ -35,7 +36,7 @@ pub const Position = struct {
pub fn seekPos(self: Position, total_size: usize) SeekPos { pub fn seekPos(self: Position, total_size: usize) SeekPos {
std.debug.assert(self.index <= self.split); std.debug.assert(self.index <= self.split);
var tot = total_size / self.split; const tot = total_size / self.split;
return SeekPos{ return SeekPos{
.start = self.index * tot, .start = self.index * tot,
@ -46,7 +47,7 @@ pub const Position = struct {
/// Represents the starting context for a single plugin run. /// Represents the starting context for a single plugin run.
pub const Context = struct { pub const Context = struct {
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
world: *c.LilvWorld, world: *c.LilvWorld,
plugin: *const c.LilvPlugin, plugin: *const c.LilvPlugin,
@ -74,10 +75,12 @@ pub const RunContext = struct {
instance: *c.LilvInstance, instance: *c.LilvInstance,
pub fn init( pub fn init(
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
plugin: *const c.LilvPlugin, plugin: *const c.LilvPlugin,
) !RunContext { ) !RunContext {
var instance = c.lilv_plugin_instantiate(plugin, @as(f64, 44100), null); _ = allocator; // TODO batch RunBuffers?
const instance = c.lilv_plugin_instantiate(plugin, @as(f64, 44100), null);
errdefer c.lilv_instance_free(instance); errdefer c.lilv_instance_free(instance);
if (instance == null) { if (instance == null) {
@ -98,13 +101,13 @@ pub const RunContext = struct {
var i: usize = 0; var i: usize = 0;
var o: usize = 0; var o: usize = 0;
for (ports) |_, p_idx| { for (ports, 0..) |_, p_idx| {
var p = @intCast(u32, p_idx); const p = @as(u32, @intCast(p_idx));
var port: *lv2.Port = &ports[p_idx]; var port: *lv2.Port = &ports[p_idx];
switch (port.ptype) { switch (port.ptype) {
.Control => lv2.lilv_instance_connect_port(self.instance, p, &port.value), .Control => lv2.lilv_instance_connect_port(self.instance, p, &port.value),
.Audio => blk: { .Audio => {
if (port.is_input) { if (port.is_input) {
lv2.lilv_instance_connect_port( lv2.lilv_instance_connect_port(
self.instance, self.instance,
@ -121,31 +124,31 @@ pub const RunContext = struct {
o += 1; o += 1;
} }
}, },
else => lv2.lilv_instance_connect_port(self.instance, p, null), // else => lv2.lilv_instance_connect_port(self.instance, p, null),
} }
} }
} }
}; };
pub fn makeContext(allocator: *std.mem.Allocator, plugin_uri: []const u8) !Context { pub fn makeContext(allocator: std.mem.Allocator, plugin_uri: []const u8) !Context {
const cstr_plugin_uri = try std.cstr.addNullByte(allocator, plugin_uri); const cstr_plugin_uri = try allocator.dupeZ(u8, plugin_uri);
defer allocator.free(cstr_plugin_uri); defer allocator.free(cstr_plugin_uri);
var world: *c.LilvWorld = c.lilv_world_new().?; const world: *c.LilvWorld = c.lilv_world_new().?;
errdefer c.lilv_world_free(world); errdefer c.lilv_world_free(world);
c.lilv_world_load_all(world); c.lilv_world_load_all(world);
var uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse blk: { const uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse {
std.debug.warn("Invalid plugin URI <{}>\n", .{plugin_uri}); log.debug("Invalid plugin URI <{s}>", .{plugin_uri});
return ImageError.InvalidPlugin; return ImageError.InvalidPlugin;
}; };
defer c.lilv_node_free(uri); defer c.lilv_node_free(uri);
const plugins: *const c.LilvPlugins = c.lilv_world_get_all_plugins(world); 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 blk: { const plugin: *const c.LilvPlugin = c.lilv_plugins_get_by_uri(plugins, uri) orelse {
std.debug.warn("Plugin <{}> not found\n", .{plugin_uri}); log.debug("Plugin <{s}> not found", .{plugin_uri});
return ImageError.UnknownPlugin; return ImageError.UnknownPlugin;
}; };

View file

@ -1,54 +1,51 @@
const std = @import("std"); const std = @import("std");
const langs = @import("lang.zig"); const langs = @import("lang.zig");
fn printCommandWithParams(stream: var, command: var) !void { const log = std.log.scoped(.scritcher_printer);
fn printCommandWithParams(stream: anytype, command: anytype) !void {
const Parameters = @TypeOf(command.parameters); const Parameters = @TypeOf(command.parameters);
try stream.print(" {} {}", .{ command.split, command.index }); try stream.print(" {d} {d}", .{ command.split, command.index });
inline for (@typeInfo(Parameters).Struct.fields) |field| { inline for (@typeInfo(Parameters).Struct.fields) |field| {
if (field.field_type == f32 or field.field_type == f64) { if (field.type == f32 or field.type == f64) {
try stream.print(" {}", .{@field(command.parameters, field.name)});
} else if (field.type == usize or field.type == u64) {
try stream.print(" {d}", .{@field(command.parameters, field.name)}); try stream.print(" {d}", .{@field(command.parameters, field.name)});
} else { } else {
try stream.print(" {}", .{@field(command.parameters, field.name)}); try stream.print(" {s}", .{@field(command.parameters, field.name)});
} }
} }
} }
fn printCommand(stream: var, cmd: *langs.Command, comptime tag: langs.Command.Tag) !void { fn printCommand(stream: anytype, cmd: *langs.Command, comptime tag: langs.Command.Tag) !void {
const CommandStruct = langs.Command.tagToType(tag); const CommandStruct = langs.Command.tagToType(tag);
const casted = cmd.cast(CommandStruct).?; const casted = cmd.cast(CommandStruct).?;
// TODO move this to Tag method?
const is_typed = switch (tag) {
.noop, .load, .quicksave, .runqs, .rotate => false,
else => true,
};
const ctype = CommandStruct.command_type; const ctype = CommandStruct.command_type;
switch (ctype) { switch (ctype) {
.lv2_command => try printCommandWithParams(stream, casted), .lv2_command => try printCommandWithParams(stream, casted),
.custom_command => try printCommandWithParams(stream, casted), .custom_command => try printCommandWithParams(stream, casted),
else => @panic("TODO support command type"),
} }
} }
pub fn printList(list: langs.CommandList, stream: var) !void { pub fn printList(list: langs.CommandList, stream: anytype) !void {
for (list.items) |cmd| { for (list.list.items) |cmd| {
const command = @tagName(cmd.tag); const command = @tagName(cmd.tag);
try stream.print("{}", .{command}); try stream.print("{s}", .{command});
switch (cmd.tag) { switch (cmd.tag) {
.load => { .load => {
const load = cmd.cast(langs.Command.Load).?; const load = cmd.cast(langs.Command.Load).?;
try stream.print(" {}", .{load.path}); try stream.print(" {s}", .{load.path});
}, },
.runqs => { .runqs => {
const runqs = cmd.cast(langs.Command.RunQS).?; const runqs = cmd.cast(langs.Command.RunQS).?;
try stream.print(" {}", .{runqs.program}); try stream.print(" {s}", .{runqs.program});
}, },
.noop, .quicksave => {}, .noop, .quicksave => {},
.rotate => { .rotate => {
const rotate = cmd.cast(langs.Command.Rotate).?; const rotate = cmd.cast(langs.Command.Rotate).?;
try stream.print(" {d} {}", .{ rotate.deg, rotate.bgfill }); try stream.print(" {d} {s}", .{ rotate.deg, rotate.bgfill });
}, },
.amp => try printCommand(stream, cmd, .amp), .amp => try printCommand(stream, cmd, .amp),

View file

@ -5,6 +5,8 @@ const plugin = @import("plugin.zig");
const custom = @import("custom.zig"); const custom = @import("custom.zig");
const magick = @import("magick.zig"); const magick = @import("magick.zig");
const log = std.log.scoped(.scritcher_runner);
const Position = plugin.Position; const Position = plugin.Position;
const ParamList = plugin.ParamList; const ParamList = plugin.ParamList;
const ParamMap = plugin.ParamMap; const ParamMap = plugin.ParamMap;
@ -18,7 +20,7 @@ pub const RunError = error{
}; };
pub const Runner = struct { pub const Runner = struct {
allocator: *std.mem.Allocator, allocator: std.mem.Allocator,
/// The currently opened image in the runner /// The currently opened image in the runner
image: ?*Image = null, image: ?*Image = null,
@ -26,9 +28,9 @@ pub const Runner = struct {
/// If the runner is in REPL mode /// If the runner is in REPL mode
repl: bool, repl: bool,
args: [][]u8, args: []const [:0]u8,
pub fn init(allocator: *std.mem.Allocator, repl: bool) Runner { pub fn init(allocator: std.mem.Allocator, repl: bool) Runner {
return Runner{ return Runner{
.allocator = allocator, .allocator = allocator,
.repl = repl, .repl = repl,
@ -40,10 +42,12 @@ pub const Runner = struct {
if (self.image) |image| { if (self.image) |image| {
image.close(); image.close();
} }
std.process.argsFree(self.allocator, self.args);
} }
pub fn clone(self: *Runner) !Runner { pub fn clone(self: *Runner) !Runner {
var cloned_image = if (self.image) |image| try image.clone() else null; const cloned_image = if (self.image) |image| try image.clone() else null;
return Runner{ return Runner{
.allocator = self.allocator, .allocator = self.allocator,
.image = cloned_image, .image = cloned_image,
@ -53,6 +57,7 @@ pub const Runner = struct {
} }
fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 { fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 {
std.debug.assert(load_path.len > 0);
if (load_path[0] == ':') { if (load_path[0] == ':') {
// parse the index from 1 to end // parse the index from 1 to end
var index = try std.fmt.parseInt(usize, load_path[1..], 10); var index = try std.fmt.parseInt(usize, load_path[1..], 10);
@ -63,19 +68,20 @@ pub const Runner = struct {
// 'scritcher repl ./script ./image' // 'scritcher repl ./script ./image'
// ':0' should ALWAYS point to the image. // ':0' should ALWAYS point to the image.
if (self.repl) index += 3 else index += 2; if (self.repl) index += 3 else index += 3;
std.debug.warn("ARGS!! {} \n", .{self.args.len}); for (self.args, 0..) |arg, idx| {
for (self.args) |arg, idx| { log.debug("arg{d} = {s}", .{ idx, arg });
std.debug.warn("arg{} = {}\n", .{ idx, arg });
} }
std.debug.warn("fetch arg idx={}, val={}\n", .{ index, self.args[index] }); log.debug("fetch arg idx={d}", .{index});
log.debug("fetch arg val={s}", .{self.args[index]});
return self.args[index]; return self.args[index];
} else { } else {
return load_path; return load_path;
} }
} }
// Caller owns returned memory.
fn resolveArgPath(self: *Runner, path_or_argidx: []const u8) ![]const u8 { fn resolveArgPath(self: *Runner, path_or_argidx: []const u8) ![]const u8 {
const path = try self.resolveArg(path_or_argidx); const path = try self.resolveArg(path_or_argidx);
const resolved_path = try std.fs.path.resolve( const resolved_path = try std.fs.path.resolve(
@ -87,8 +93,8 @@ pub const Runner = struct {
} }
fn loadCmd(self: *Runner, path_or_argidx: []const u8) !void { fn loadCmd(self: *Runner, path_or_argidx: []const u8) !void {
var load_path = try self.resolveArgPath(path_or_argidx); const load_path = try self.resolveArgPath(path_or_argidx);
std.debug.warn("\tload path: {}\n", .{load_path}); log.debug("\tload path: {s}", .{load_path});
// we could use ImageMagick to convert from X to BMP // we could use ImageMagick to convert from X to BMP
// but i can't find an easy way to do things in memory. // but i can't find an easy way to do things in memory.
@ -98,13 +104,14 @@ pub const Runner = struct {
// krita/gimp and make it export a bmp and while in the program you can // krita/gimp and make it export a bmp and while in the program you can
// apply filters, etc. // apply filters, etc.
if (!std.mem.endsWith(u8, load_path, ".bmp") and !std.mem.endsWith(u8, load_path, ".ppm")) { if (!std.mem.endsWith(u8, load_path, ".bmp") and !std.mem.endsWith(u8, load_path, ".ppm")) {
std.debug.warn("Only BMP files are allowed to be loaded. Got path '{}'\n", .{load_path}); log.debug("Only BMP files are allowed to be loaded. Got path '{s}'", .{load_path});
return RunError.NoBMP; return RunError.NoBMP;
} }
// we don't copy load_path into a temporary file because we're already // we don't copy load_path into a temporary file because we're already
// loading it under the SFM_READ mode, which won't cause any destructive // loading it under the SFM_READ mode, which won't cause any destructive
// operations on the file. // operations on the file.
if (self.image) |image| image.close();
self.image = try Image.open(self.allocator, load_path); self.image = try Image.open(self.allocator, load_path);
} }
@ -112,15 +119,16 @@ pub const Runner = struct {
if (self.image) |image| { if (self.image) |image| {
return image; return image;
} else { } else {
std.debug.warn("image is required!\n", .{}); log.debug("image is required!", .{});
return RunError.ImageRequired; return RunError.ImageRequired;
} }
} }
/// Caller owns returned memory.
fn makeGlitchedPath(self: *Runner) ![]const u8 { fn makeGlitchedPath(self: *Runner) ![]const u8 {
// we want to transform basename, if it is "x.bmp" to "x_gN.bmp", where // we want to transform basename, if it is "x.bmp" to "x_gN.bmp", where
// N is the maximum non-used integer. // N is the maximum non-used integer.
var image = try self.getImage(); const image = try self.getImage();
const basename = std.fs.path.basename(image.path); const basename = std.fs.path.basename(image.path);
const dirname = std.fs.path.dirname(image.path).?; const dirname = std.fs.path.dirname(image.path).?;
@ -133,7 +141,7 @@ pub const Runner = struct {
// starts_with would be "x_g", we want to find all files in the directory // starts_with would be "x_g", we want to find all files in the directory
// that start with that name. // that start with that name.
const starts_with = try std.fmt.allocPrint(self.allocator, "{}_g", .{ const starts_with = try std.fmt.allocPrint(self.allocator, "{s}_g", .{
basename[0..period_idx], basename[0..period_idx],
}); });
defer self.allocator.free(starts_with); defer self.allocator.free(starts_with);
@ -144,7 +152,7 @@ pub const Runner = struct {
while (try it.next()) |entry| { while (try it.next()) |entry| {
switch (entry.kind) { switch (entry.kind) {
.File => blk: { .file => blk: {
if (!std.mem.startsWith(u8, entry.name, starts_with)) break :blk {}; if (!std.mem.startsWith(u8, entry.name, starts_with)) break :blk {};
// we want to get the N in x_gN.ext // we want to get the N in x_gN.ext
@ -158,6 +166,8 @@ pub const Runner = struct {
// if N isn't a number, we just ignore that file // if N isn't a number, we just ignore that file
const idx_str = entry.name[entry_gidx + 2 .. entry_pidx]; const idx_str = entry.name[entry_gidx + 2 .. entry_pidx];
const idx = std.fmt.parseInt(usize, idx_str, 10) catch |err| { const idx = std.fmt.parseInt(usize, idx_str, 10) catch |err| {
log.debug("ignoring file {s}", .{@errorName(err)});
break :blk {}; break :blk {};
}; };
@ -167,7 +177,7 @@ pub const Runner = struct {
} }
} }
const out_path = try std.fmt.allocPrint(self.allocator, "{}/{}{}{}", .{ const out_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}{d}{s}", .{
dirname, dirname,
starts_with, starts_with,
max + 1, max + 1,
@ -180,36 +190,38 @@ pub const Runner = struct {
fn quicksaveCmd(self: *Runner) !void { fn quicksaveCmd(self: *Runner) !void {
var image = try self.getImage(); var image = try self.getImage();
const out_path = try self.makeGlitchedPath(); const out_path = try self.makeGlitchedPath();
defer self.allocator.free(out_path);
try image.saveTo(out_path); try image.saveTo(out_path);
} }
fn runQSCmd(self: *Runner, cmd: lang.Command) !void { fn runQSCmd(self: *Runner, cmd: *lang.Command) !void {
const runqs = cmd.cast(lang.Command.RunQS).?; const runqs = cmd.cast(lang.Command.RunQS).?;
var image = try self.getImage(); var image = try self.getImage();
const out_path = try self.makeGlitchedPath(); const out_path = try self.makeGlitchedPath();
defer self.allocator.free(out_path);
try image.saveTo(out_path); try image.saveTo(out_path);
var proc = try std.ChildProcess.init( var proc = std.ChildProcess.init(
&[_][]const u8{ runqs.program, out_path }, &[_][]const u8{ runqs.program, out_path },
self.allocator, self.allocator,
); );
defer proc.deinit(); //defer proc.deinit();
std.debug.warn("running '{} {}'\n", .{ runqs.program, out_path }); log.debug("running '{s} {s}'", .{ runqs.program, out_path });
_ = try proc.spawnAndWait(); _ = try proc.spawnAndWait();
} }
fn rotateCmd(self: *Runner, cmd: lang.Command) !void { fn rotateCmd(self: *Runner, cmd: *lang.Command) !void {
const rotate_cmd = cmd.cast(lang.Command.Rotate).?; const rotate_cmd = cmd.cast(lang.Command.Rotate).?;
var image = try self.getImage(); const image = try self.getImage();
var c_bgfill = try std.cstr.addNullByte(self.allocator, rotate_cmd.bgfill); const c_bgfill = try self.allocator.dupeZ(u8, rotate_cmd.bgfill);
defer self.allocator.free(c_bgfill); defer self.allocator.free(c_bgfill);
try magick.runRotate(image, rotate_cmd.deg, c_bgfill); try magick.runRotate(image, rotate_cmd.deg, c_bgfill);
} }
fn executeLV2Command(self: *@This(), command: var) !void { fn executeLV2Command(self: *@This(), command: anytype) !void {
const pos = plugin.Position{ const pos = plugin.Position{
.split = command.split, .split = command.split,
.index = command.index, .index = command.index,
@ -231,7 +243,7 @@ pub const Runner = struct {
try image.runPlugin(typ.lv2_url, pos, params); try image.runPlugin(typ.lv2_url, pos, params);
} }
fn executeCustomCommand(self: *@This(), command: var) !void { fn executeCustomCommand(self: *@This(), command: anytype) !void {
const pos = plugin.Position{ const pos = plugin.Position{
.split = command.split, .split = command.split,
.index = command.index, .index = command.index,
@ -243,20 +255,19 @@ pub const Runner = struct {
fn runSingleCommand( fn runSingleCommand(
self: *@This(), self: *@This(),
cmd: lang.Command, cmd: *lang.Command,
comptime tag: lang.Command.Tag, comptime tag: lang.Command.Tag,
) !void { ) !void {
comptime const typ = lang.Command.tagToType(tag); const typ = lang.Command.tagToType(tag);
const command = cmd.cast(typ).?; const command = cmd.cast(typ).?;
const ctype = typ.command_type; const ctype = typ.command_type;
switch (ctype) { switch (ctype) {
.lv2_command => try self.executeLV2Command(command.*), .lv2_command => try self.executeLV2Command(command.*),
.custom_command => try self.executeCustomCommand(command.*), .custom_command => try self.executeCustomCommand(command.*),
else => @panic("TODO support command type"),
} }
} }
fn runCommand(self: *@This(), cmd: lang.Command) !void { fn runCommand(self: *@This(), cmd: *lang.Command) !void {
switch (cmd.tag) { switch (cmd.tag) {
.noop => {}, .noop => {},
.load => { .load => {
@ -308,26 +319,26 @@ pub const Runner = struct {
cmds: lang.CommandList, cmds: lang.CommandList,
debug_flag: bool, debug_flag: bool,
) !void { ) !void {
for (cmds.items) |cmd| { _ = debug_flag;
for (cmds.list.items) |cmd| {
cmd.print(); cmd.print();
try self.runCommand(cmd.*); try self.runCommand(cmd);
} }
} }
}; };
test "running noop" { test "running noop" {
const allocator = std.heap.direct_allocator; const allocator = std.testing.allocator;
var cmds = lang.CommandList.init(allocator); var cmds = lang.CommandList.init(allocator);
defer cmds.deinit(); defer cmds.deinit();
var cmd_ptr = try allocator.create(lang.Command); const command = lang.Command{ .tag = .noop };
cmd_ptr.* = lang.Command{
.command = .Noop,
.args = lang.ArgList.init(allocator),
};
try cmds.append(cmd_ptr);
var runner = Runner.init(allocator); var noop = try allocator.create(lang.Command.Noop);
noop.* = lang.Command.Noop{ .base = command };
try cmds.append(&noop.base);
var runner = Runner.init(allocator, false);
defer runner.deinit(); defer runner.deinit();
try runner.runCommands(cmds, false); try runner.runCommands(cmds, false);