diff --git a/src/custom.zig b/src/custom.zig index f346fa0..0420bd3 100644 --- a/src/custom.zig +++ b/src/custom.zig @@ -16,15 +16,12 @@ pub const RandomNoise = struct { pub fn init( allocator: *std.mem.Allocator, - params: *plugins.ParamMap, + params: var, ) ?RandomNoise { - const seed = @floatToInt(u64, params.get("seed").?.value); - const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value); + var r = std.rand.DefaultPrng.init(params.seed); - var r = std.rand.DefaultPrng.init(seed); - - if (fillbytes > 0) { - var rand_buf = allocator.alloc(f32, fillbytes) catch return null; + if (params.fill_bytes > 0) { + var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; for (rand_buf) |_, idx| { rand_buf[idx] = r.random.float(f32); @@ -67,15 +64,12 @@ pub const WildNoise = struct { pub fn init( allocator: *std.mem.Allocator, - params: *plugins.ParamMap, + params: var, ) ?WildNoise { - const seed = @floatToInt(u64, params.get("seed").?.value); - const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value); + var r = std.rand.DefaultPrng.init(params.seed); - var r = std.rand.DefaultPrng.init(seed); - - if (fillbytes > 0) { - var rand_buf = allocator.alloc(f32, fillbytes) catch return null; + if (params.fill_bytes > 0) { + var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; for (rand_buf) |_, idx| { rand_buf[idx] = @intToFloat(f32, r.random.int(u1)); @@ -118,11 +112,10 @@ pub const Write = struct { pub fn init( allocator: *std.mem.Allocator, - params: *plugins.ParamMap, + params: var, ) Write { - const data = params.get("data").?; return Write{ - .data = data.value, + .data = params.data, }; } @@ -140,10 +133,10 @@ pub const Embed = struct { sndfile: *c.SNDFILE = undefined, buf: []f32 = undefined, - pub fn init(allocator: *std.mem.Allocator, filepath: []const u8) @This() { + pub fn init(allocator: *std.mem.Allocator, params: var) @This() { return Embed{ .allocator = allocator, - .filepath = filepath, + .filepath = params.path, }; } diff --git a/src/image.zig b/src/image.zig index 7f8deb4..e0fea67 100644 --- a/src/image.zig +++ b/src/image.zig @@ -75,18 +75,17 @@ pub fn sseek(file: *c.SNDFILE, offset: usize) void { /// Caller owns the returned memory. pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 { const template_start = "/temp/temp_"; - const template = "/tmp/temp_XXXXXXXXXXX"; + const template = "/tmp/temp_XXXXXXXXXXXXXXXXXXXXX"; var nam = try allocator.alloc(u8, template.len); std.mem.copy(u8, nam, template); - const seed = @bitCast(u64, std.time.timestamp()); + const seed = @truncate(u64, @bitCast(u128, std.time.nanoTimestamp())); var r = std.rand.DefaultPrng.init(seed); 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)); @@ -95,12 +94,16 @@ pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 { } // if we fail to access it, we assume it doesn't exist and return it. - - _ = std.fs.cwd().openFile(nam, .{ .read = true, .write = false }) catch |err| { - if (err == error.FileNotFound) { - return nam; - } + var tmp_file: std.fs.File = std.fs.cwd().openFile( + nam, + .{ .read = true, .write = false }, + ) catch |err| blk: { + if (err == error.FileNotFound) return nam else continue; }; + + // if we actually found someone, close the handle so that we don't + // get EMFILE later on. + tmp_file.close(); } return error.TempGenFail; @@ -422,8 +425,7 @@ pub const Image = struct { self: *Image, comptime Plugin: type, position: plugins.Position, - comptime ExtraType: type, - extra: ExtraType, + extra: var, ) !void { var plugin_opt: ?Plugin = Plugin.init(self.allocator, extra); if (plugin_opt == null) { diff --git a/src/lang.zig b/src/lang.zig index 34c5004..305eb4a 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -1,166 +1,486 @@ const std = @import("std"); const plugin = @import("plugin.zig"); +const custom = @import("custom.zig"); -pub const ParseError = error{ - OutOfMemory, - ArgRequired, - ParseFail, -}; +pub const ParseError = error{ParseFail}; pub const CommandType = enum { - Noop, - Load, - Quicksave, - RunQS, + /// "LV2 Commands" are commands that receive split, index, and then receive + /// any f64 arguments. + lv2_command, - Amp, - RFlanger, - Eq, - Phaser, - Mbeq, - Chorus, - PitchScaler, - Reverb, - Highpass, - Delay, - Vinyl, - RevDelay, - Gate, - Detune, - Overdrive, - Degrade, - RePsycho, - TalkBox, - DynComp, - ThruZero, - Foverdrive, - Gverb, - Invert, - TapeDelay, - ModDelay, - MultiChorus, - Saturator, - VintageDelay, - - Noise, - WildNoise, - Write, - Embed, - - Rotate, + custom_command, }; +fn LV2Command( + comptime tag: Command.Tag, + comptime plugin_url: []const u8, + comptime LV2Parameters: type, +) type { + return struct { + pub const base_tag = tag; + pub const command_type = CommandType.lv2_command; + pub const lv2_url = plugin_url; + + base: Command, + split: usize, + index: usize, + parameters: LV2Parameters, + }; +} + +fn CustomCommand( + comptime tag: Command.Tag, + comptime Plugin: type, + comptime PluginParameters: type, +) type { + return struct { + pub const base_tag = tag; + pub const command_type = CommandType.custom_command; + pub const plugin_type = Plugin; + + base: Command, + split: usize, + index: usize, + parameters: PluginParameters, + }; +} + pub const Command = struct { - command: CommandType, - args: ArgList, - cur_idx: usize = 0, + tag: Tag, - pub fn print(self: Command) void { - std.debug.warn("cmd:{}\n", .{self.command}); - } + pub const Tag = enum { + noop, + load, + quicksave, + runqs, - pub fn argAt(self: Command, idx: usize) ![]const u8 { - std.debug.warn("{} {}", .{ idx, self.args.items.len }); + amp, + rflanger, + eq, + phaser, + mbeq, + chorus, + pitchscaler, + reverb, + highpass, + delay, + vinyl, + revdelay, + gate, + detune, + overdrive, + degrade, + repsycho, + talkbox, + dyncomp, + thruzero, + foverdrive, + gverb, + invert, + tapedelay, + moddelay, + multichorus, + saturator, + vintagedelay, - if (idx > (self.args.items.len - 1)) { - std.debug.warn("Expected argument at index {}\n", .{idx}); - return ParseError.ArgRequired; - } + noise, + wildnoise, + write, + embed, - return self.args.items[idx]; - } + rotate, + }; - pub fn usizeArgAt(self: Command, idx: usize) !usize { - var arg = try self.argAt(idx); - return try std.fmt.parseInt(usize, arg, 10); - } + pub fn tagToType(tag: Tag) type { + return switch (tag) { + .noop => Noop, + .load => Load, + .quicksave => Quicksave, + .runqs => RunQS, - pub fn consumePosition(self: *Command) !plugin.Position { - self.cur_idx = 2; - return plugin.Position{ - .split = try self.usizeArgAt(0), - .index = try self.usizeArgAt(1), + .amp => Amp, + .rflanger => RFlanger, + .eq => Eq, + .phaser => Phaser, + .mbeq => Mbeq, + .chorus => Chorus, + .pitchscaler => Pitchscaler, + .reverb => Reverb, + .highpass => Highpass, + .delay => Delay, + .vinyl => Vinyl, + .revdelay => Revdelay, + .gate => Gate, + .detune => Detune, + .overdrive => Overdrive, + .degrade => Degrade, + .repsycho => Repsycho, + .talkbox => Talkbox, + .dyncomp => Dyncomp, + .thruzero => Thruzero, + .foverdrive => Foverdrive, + .gverb => Gverb, + .invert => Invert, + .tapedelay => Tapedelay, + .moddelay => Moddelay, + .multichorus => Multichorus, + .saturator => Saturator, + .vintagedelay => Vintagedelay, + + .noise => Noise, + .wildnoise => Wildnoise, + .write => Write, + .embed => Embed, + + .rotate => Rotate, + + else => @panic("TODO"), }; } - pub fn intArgAt(self: Command, idx: usize) !i32 { - var arg = try self.argAt(idx); - return try std.fmt.parseInt(i32, arg, 10); + pub fn cast(base: *const @This(), comptime T: type) ?*const T { + if (base.tag != T.base_tag) + return null; + + return @fieldParentPtr(T, "base", base); } - pub fn floatArgAt(self: Command, idx: usize) !f32 { - var arg = try self.argAt(idx); - return try std.fmt.parseFloat(f32, arg); + pub fn print(base: *const @This()) void { + std.debug.warn("tag: {}\n", .{base.tag}); } - pub fn floatArgMany( - self: Command, - allocator: *std.mem.Allocator, - start_index: usize, - elements: usize, - default: f32, - ) ![]const f32 { - var i: usize = start_index; - var arr = std.ArrayList(f32).init(allocator); + pub const Noop = struct { + pub const base_tag = Tag.noop; + base: Command, + }; - while (i < elements) : (i += 1) { - var value: f32 = self.floatArgAt(i) catch |err| blk: { - std.debug.warn("\tdoing default on arg {}\n", .{i}); - break :blk default; - }; + pub const Load = struct { + pub const base_tag = Tag.load; + base: Command, + path: []u8, + }; - try arr.append(value); - } + pub const Quicksave = struct { + pub const base_tag = Tag.quicksave; + base: Command, + }; - return arr.items; - } + pub const RunQS = struct { + pub const base_tag = Tag.runqs; + base: Command, + program: []const u8, + }; - pub fn appendParam( - self: *Command, - params: *plugin.ParamList, - symbol: []const u8, - ) !void { - var val = try self.floatArgAt(self.cur_idx); - self.cur_idx += 1; + pub const Noise = CustomCommand(Tag.noise, custom.RandomNoise, struct { + seed: u64, + fill_bytes: usize, + }); - try params.append(plugin.Param{ - .sym = symbol, - .value = val, - }); - } + pub const Wildnoise = CustomCommand(Tag.wildnoise, custom.WildNoise, struct { + seed: u64, + fill_bytes: usize, + }); - pub fn appendParamMap( - self: *Command, - map: *plugin.ParamMap, - symbol: []const u8, - ) !void { - var val = try self.floatArgAt(self.cur_idx); - self.cur_idx += 1; - _ = try map.put(symbol, val); - } + pub const Write = CustomCommand(Tag.write, custom.Write, struct { + data: f32, + }); - pub fn copy(self: Command, allocator: *std.mem.Allocator) !*Command { - var cmd = try allocator.create(Command); - cmd.* = Command{ - .command = self.command, - .args = self.args, - .cur_idx = self.cur_idx, - }; + pub const Embed = CustomCommand(Tag.write, custom.Embed, struct { + path: []const u8, + }); - return cmd; - } + pub const Rotate = struct { + pub const base_tag = Tag.rotate; + base: Command, + deg: f32, + bgfill: []const u8, + }; + + pub const Amp = LV2Command( + .amp, + "http://lv2plug.in/plugins/eg-amp", + struct { + gain: f32 + }, + ); + + pub const RFlanger = LV2Command( + .rflanger, + "http://plugin.org.uk/swh-plugins/retroFlange", + struct { + delay_depth_avg: f32, law_freq: f32 + }, + ); + + pub const Eq = LV2Command( + .rflanger, + "http://plugin.org.uk/swh-plugins/dj_eq_mono", + struct { + lo: f32, mid: f32, hi: f32 + }, + ); + + pub const Phaser = LV2Command( + .phaser, + "http://plugin.org.uk/swh-plugins/lfoPhaser", + struct { + lfo_rate: f32, lfo_depth: f32, fb: f32, spread: f32 + }, + ); + + pub const Mbeq = LV2Command( + .mbeq, + "http://plugin.org.uk/swh-plugins/mbeq", + struct { + band_1: f32, + band_2: f32, + band_3: f32, + band_4: f32, + band_5: f32, + band_6: f32, + band_7: f32, + band_8: f32, + band_9: f32, + band_10: f32, + band_11: f32, + band_12: f32, + band_13: f32, + band_14: f32, + band_15: f32, + }, + ); + + pub const Chorus = LV2Command( + .chorus, + "http://plugin.org.uk/swh-plugins/multivoiceChorus", + struct { + voices: f32, + delay_base: f32, + voice_spread: f32, + detune: f32, + law_freq: f32, + attendb: f32, + }, + ); + + pub const Pitchscaler = LV2Command( + .pitchscaler, + "http://plugin.org.uk/swh-plugins/pitchScaleHQ", + struct { mult: f32 }, + ); + + pub const Reverb = LV2Command( + .reverb, + "http://invadarecords.com/plugins/lv2/erreverb/mono", + struct { + roomLength: f32, + roomWidth: f32, + roomHeight: f32, + sourceLR: f32, + sourceFB: f32, + listLR: f32, + listFB: f32, + hpf: f32, + warmth: f32, + diffusion: f32, + }, + ); + + pub const Highpass = LV2Command(.highpass, "http://invadarecords.com/plugins/lv2/filter/hpf/mono", struct { + freq: f32, + gain: f32, + noClip: f32, + }); + + pub const Delay = LV2Command(.delay, "http://plugin.org.uk/swh-plugins/delayorama", struct { + seed: f32, + gain: f32, + feedback_pc: f32, + tap_count: f32, + first_delay: f32, + delay_range: f32, + delay_scale: f32, + delay_rand_pc: f32, + gain_scale: f32, + wet: f32, + }); + + pub const Vinyl = LV2Command(.vinyl, "http://plugin.org.uk/swh-plugins/vynil", struct { + year: f32, + rpm: f32, + warp: f32, + click: f32, + wear: f32, + }); + + pub const Revdelay = LV2Command(.revdelay, "http://plugin.org.uk/swh-plugins/revdelay", struct { + delay_time: f32, + dry_level: f32, + wet_level: f32, + feedback: f32, + xfade_samp: f32, + }); + // pub const Noise= LV2Command(.,,struct{}); + pub const Gate = LV2Command(.gate, "http://hippie.lt/lv2/gate", struct { + @"switch": f32, + threshold: f32, + attack: f32, + hold: f32, + decay: f32, + gaterange: f32, + }); + pub const Detune = LV2Command(.detune, "http://drobilla.net/plugins/mda/Detune", struct { + detune: f32, + mix: f32, + output: f32, + latency: f32, + }); + pub const Overdrive = LV2Command(.overdrive, "http://drobilla.net/plugins/mda/Overdrive", struct { + drive: f32, + muffle: f32, + output: f32, + }); + pub const Degrade = LV2Command(.degrade, "http://drobilla.net/plugins/mda/Degrade", struct { + headroom: f32, + quant: f32, + rate: f32, + post_filt: f32, + non_lin: f32, + output: f32, + }); + pub const Repsycho = LV2Command(.repsycho, "http://drobilla.net/plugins/mda/RePsycho", struct { + tune: f32, + fine: f32, + decay: f32, + thresh: f32, + hold: f32, + mix: f32, + quality: f32, + }); + pub const Talkbox = LV2Command(.talkbox, "http://drobilla.net/plugins/mda/TalkBox", struct { + wet: f32, + dry: f32, + carrier: f32, + quality: f32, + }); + pub const Dyncomp = LV2Command(.dyncomp, "http://gareus.org/oss/lv2/darc#mono", struct { + enable: f32, + hold: f32, + inputgain: f32, + threshold: f32, + Ratio: f32, + attack: f32, + release: f32, + gain_min: f32, + gain_max: f32, + rms: f32, + }); + pub const Foverdrive = LV2Command(.foverdrive, "http://plugin.org.uk/swh-plugins/foverdrive", struct { + drive: f32, + }); + pub const Thruzero = LV2Command(.thruzero, "http://drobilla.net/plugins/mda/ThruZero", struct { + rate: f32, mix: f32, feedback: f32, depth_mod: f32 + }); + + pub const Gverb = LV2Command(.gverb, "http://plugin.org.uk/swh-plugins/gverb", struct { + roomsize: f32, + revtime: f32, + damping: f32, + inputbandwidth: f32, + drylevel: f32, + earlylevel: f32, + taillevel: f32, + }); + pub const Invert = LV2Command(.invert, "http://plugin.org.uk/swh-plugins/inv", struct {}); + pub const Tapedelay = LV2Command(.tapedelay, "http://plugin.org.uk/swh-plugins/tapeDelay", struct { + speed: f32, + da_db: f32, + + t1d: f32, + t1a_db: f32, + + t2d: f32, + t2a_db: f32, + + t3d: f32, + t3a_db: f32, + + t4d: f32, + t4a_db: f32, + }); + + pub const Moddelay = LV2Command( + .moddelay, + "http://plugin.org.uk/swh-plugins/modDelay", + struct { + base: f32, + }, + ); + + pub const Multichorus = LV2Command(.multichorus, "http://calf.sourceforge.net/plugins/MultiChorus", struct { + min_delay: f32, + mod_depth: f32, + mod_rate: f32, + stereo: f32, + voices: f32, + vphase: f32, + amount: f32, + dry: f32, + freq: f32, + freq2: f32, + q: f32, + overlap: f32, + level_in: f32, + level_out: f32, + lfo: f32, + }); + pub const Saturator = LV2Command(.saturator, "http://calf.sourceforge.net/plugins/Saturator", struct { + bypass: f32, + level_in: f32, + level_out: f32, + mix: f32, + drive: f32, + blend: f32, + lp_pre_freq: f32, + hp_pre_freq: f32, + lp_post_freq: f32, + hp_post_freq: f32, + p_freq: f32, + p_level: f32, + p_q: f32, + pre: f32, + post: f32, + }); + pub const Vintagedelay = LV2Command(.vintagedelay, "http://calf.sourceforge.net/plugins/VintageDelay", struct { + level_in: f32, + level_out: f32, + subdiv: f32, + time_l: f32, + time_r: f32, + feedback: f32, + amount: f32, + mix_mode: f32, + medium: f32, + dry: f32, + width: f32, + fragmentation: f32, + pbeats: f32, + pfrag: f32, + timing: f32, + bpm: f32, + ms: f32, + hz: f32, + bpm_host: f32, + }); }; -pub const CommandList = std.ArrayList(Command); -pub const ArgList = std.ArrayList([]const u8); - -pub const KeywordMap = std.StringHashMap(CommandType); +pub const CommandList = std.ArrayList(*Command); /// A parser. pub const Lang = struct { allocator: *std.mem.Allocator, - keywords: KeywordMap, has_error: bool = false, line: usize = 0, @@ -168,137 +488,108 @@ pub const Lang = struct { pub fn init(allocator: *std.mem.Allocator) Lang { return Lang{ .allocator = allocator, - .keywords = KeywordMap.init(allocator), }; } - pub fn deinit(self: *Lang) void { - self.keywords.deinit(); - } + pub fn deinit(self: *Lang) void {} pub fn reset(self: *Lang) void { self.has_error = false; self.line = 0; } - - fn fillKeywords(self: *Lang) !void { - _ = try self.keywords.put("noop", .Noop); - _ = try self.keywords.put("load", .Load); - _ = try self.keywords.put("quicksave", .Quicksave); - _ = try self.keywords.put("runqs", .RunQS); - - _ = try self.keywords.put("amp", .Amp); - _ = try self.keywords.put("rflanger", .RFlanger); - _ = try self.keywords.put("eq", .Eq); - _ = try self.keywords.put("mbeq", .Mbeq); - _ = try self.keywords.put("phaser", .Phaser); - _ = try self.keywords.put("chorus", .Chorus); - _ = try self.keywords.put("pitchscaler", .PitchScaler); - _ = try self.keywords.put("reverb", .Reverb); - _ = try self.keywords.put("highpass", .Highpass); - _ = try self.keywords.put("delay", .Delay); - _ = try self.keywords.put("vinyl", .Vinyl); - _ = try self.keywords.put("revdelay", .RevDelay); - _ = try self.keywords.put("gate", .Gate); - _ = try self.keywords.put("detune", .Detune); - _ = try self.keywords.put("overdrive", .Overdrive); - _ = try self.keywords.put("talkbox", .TalkBox); - _ = try self.keywords.put("thruzero", .ThruZero); - _ = try self.keywords.put("foverdrive", .Foverdrive); - _ = try self.keywords.put("gverb", .Gverb); - _ = try self.keywords.put("invert", .Invert); - _ = try self.keywords.put("tapedelay", .TapeDelay); - _ = try self.keywords.put("moddelay", .ModDelay); - _ = try self.keywords.put("multichorus", .MultiChorus); - _ = try self.keywords.put("saturator", .Saturator); - _ = try self.keywords.put("vintagedelay", .VintageDelay); - - // custom implementations (not lv2) - _ = try self.keywords.put("noise", .Noise); - _ = try self.keywords.put("wildnoise", .WildNoise); - _ = try self.keywords.put("write", .Write); - _ = try self.keywords.put("embed", .Embed); - _ = try self.keywords.put("degrade", .Degrade); - _ = try self.keywords.put("repsycho", .RePsycho); - _ = try self.keywords.put("dyncomp", .RePsycho); - - // even more custom - _ = try self.keywords.put("rotate", .Rotate); - } - - pub fn getCommand(self: *Lang, stmt: []const u8) ?CommandType { - var kv_opt = self.keywords.get(stmt); - - if (kv_opt) |kv| { - return kv.value; - } else { - return null; - } - } - - fn expectAny(self: *Lang, count: usize, args: ArgList) !void { - if (args.items.len != count) { - self.doError("expected {} arguments, found {}", .{ count, args.items.len }); - return error.ArgRequired; - } - } - - fn expectSingle(self: *Lang, args: ArgList) !void { - return try self.expectAny(1, args); - } - - fn expectFloat(self: *Lang, count: usize, args: ArgList) !void { - var i: usize = 0; - - if (args.items.len != count) { - self.doError("expected {} arguments, found {}", .{ count, args.items.len }); - return error.ArgRequired; - } - - while (i < count) : (i += 1) { - var arg = args.items[i]; - _ = std.fmt.parseFloat(f32, arg) catch |err| { - std.debug.warn("failed to parse f32: {}\n", .{err}); - return error.FloatParseFail; - }; - } - } - - fn validateCommand(self: *Lang, cmd: Command) !void { - switch (cmd.command) { - .Quicksave, .Noop => {}, - .Load, .RunQS => try self.expectSingle(cmd.args), - .Amp => try self.expectFloat(3, cmd.args), - .RFlanger => try self.expectFloat(4, cmd.args), - .Eq => try self.expectFloat(5, cmd.args), - .Phaser => try self.expectFloat(6, cmd.args), - .Mbeq => try self.expectFloat(17, cmd.args), - .Chorus => try self.expectFloat(8, cmd.args), - .PitchScaler => try self.expectFloat(3, cmd.args), - .Reverb => try self.expectFloat(12, cmd.args), - .Highpass => try self.expectFloat(5, cmd.args), - .Delay => try self.expectFloat(12, cmd.args), - .Vinyl => try self.expectFloat(7, cmd.args), - .RevDelay => try self.expectFloat(7, cmd.args), - .Noise => try self.expectFloat(4, cmd.args), - .WildNoise => try self.expectFloat(4, cmd.args), - .Write => try self.expectFloat(3, cmd.args), - .Rotate => try self.expectAny(2, cmd.args), - .Embed => try self.expectAny(3, cmd.args), - else => std.debug.warn("WARN unchecked command {}\n", .{cmd.command}), - } - } - fn doError(self: *Lang, comptime fmt: []const u8, args: var) void { - std.debug.warn("error at line {}: ", .{self.line}); + std.debug.warn("ERROR! at line {}: ", .{self.line}); std.debug.warn(fmt, args); std.debug.warn("\n", .{}); self.has_error = true; } - pub fn parse(self: *Lang, data: []const u8) ParseError!CommandList { + fn parseCommandArguments( + self: *@This(), + comptime command_struct: type, + tok_it: *std.mem.TokenIterator, + commands: *CommandList, + ) !void { + // Based on the command struct fields, we can parse the arguments. + var cmd = try self.allocator.create(command_struct); + const is_lv2_command = switch (command_struct.base_tag) { + .noop, .load, .quicksave, .runqs, .rotate => false, + else => true, + }; + + // TODO: crash when no arguments are left but we still need + // arguments... + + if (is_lv2_command) { + const split = tok_it.next(); + if (split == null) { + self.doError("Expected split parameter, got EOL", .{}); + return; + } + + const index = tok_it.next(); + if (index == null) { + self.doError("Expected index parameter, got EOL", .{}); + return; + } + + cmd.split = try std.fmt.parseInt(usize, split.?, 10); + cmd.index = try std.fmt.parseInt(usize, index.?, 10); + + inline for (@typeInfo(@TypeOf(cmd.parameters)).Struct.fields) |cmd_field| { + const maybe_arg = tok_it.next(); + if (maybe_arg == null) { + self.doError("Expected parameter for {}, got nothing", .{cmd_field.name}); + return; + } + + const arg = maybe_arg.?; + const arg_value = switch (cmd_field.field_type) { + f32 => try std.fmt.parseFloat(f32, arg), + u64 => try std.fmt.parseInt(u64, arg, 10), + usize => try std.fmt.parseInt(usize, arg, 10), + []const u8 => try self.allocator.dupe(u8, arg), + else => @compileError("parameter struct has unsupported type " ++ @typeName(cmd_field.field_type)), + }; + + @field(cmd.parameters, cmd_field.name) = arg_value; + } + } else { + inline for (@typeInfo(command_struct).Struct.fields) |cmd_field| { + comptime { + if (std.mem.eql(u8, cmd_field.name, "base")) { + continue; + } + } + const arg = tok_it.next().?; + const argument_value = switch (cmd_field.field_type) { + usize => try std.fmt.parseInt(usize, arg, 10), + i32 => try std.fmt.parseInt(i32, arg, 10), + f32 => try std.fmt.parseFloat(f32, arg), + []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) ++ "."), + }; + + std.debug.warn("parsing {}, arg of type {} => {}\n", .{ + @typeName(command_struct), + @typeName(@TypeOf(argument_value)), + argument_value, + }); + + @field(cmd, cmd_field.name) = argument_value; + } + } + + cmd.base.tag = command_struct.base_tag; + const command = cmd.base.cast(command_struct).?; + std.debug.warn("cmd: {}\n", .{command}); + + try commands.append(&cmd.base); + } + + pub fn parse(self: *Lang, data: []const u8) !CommandList { var splitted_it = std.mem.split(data, ";"); - try self.fillKeywords(); var cmds = CommandList.init(self.allocator); while (splitted_it.next()) |stmt_orig| { @@ -309,7 +600,7 @@ pub const Lang = struct { if (stmt.len == 0) continue; if (std.mem.startsWith(u8, stmt, "#")) continue; - // TODO better tokenizer instead of just tokenize(" "); + // TODO better tokenizer instead of just tokenize(" ")...maybe???? var tok_it = std.mem.tokenize(stmt, " "); var cmd_opt = tok_it.next(); @@ -317,32 +608,45 @@ pub const Lang = struct { self.doError("No command given", .{}); continue; } - var command = cmd_opt.?; + const command_string = cmd_opt.?; - var ctype_opt = self.getCommand(command); - var ctype: CommandType = undefined; - if (ctype_opt) |ctype_val| { - ctype = ctype_val; - } else { - self.doError("Unknown command '{}' ({})", .{ command, command.len }); - continue; + var found: bool = false; + + inline for (@typeInfo(Command).Struct.decls) |cmd_struct_decl| { + switch (cmd_struct_decl.data) { + .Type => |typ| switch (@typeInfo(typ)) { + .Struct => {}, + else => continue, + }, + else => continue, + } + + const struct_name = cmd_struct_decl.name; + comptime var lowered_command_name = [_]u8{0} ** struct_name.len; + comptime { + for (struct_name) |c, i| { + lowered_command_name[i] = std.ascii.toLower(c); + } + } + + // if we have a match, we know the proper struct type + // to use. this actually works compared to storing command_struct + // in a variable because then that variable must be comptime. + + // 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. + + if (std.mem.eql(u8, &lowered_command_name, command_string)) { + found = true; + const cmd_struct_type = cmd_struct_decl.data.Type; + try self.parseCommandArguments(cmd_struct_type, &tok_it, &cmds); + } } - var args = ArgList.init(self.allocator); - errdefer args.deinit(); - - while (tok_it.next()) |arg| { - try args.append(arg); - } - - // construct final Command based on command - var cmd = Command{ .command = ctype, .args = args }; - self.validateCommand(cmd) catch |err| { - //self.doError("error validating command '{}': {}", command, err); + if (!found) { + self.doError("Unknown command '{}' ({})", .{ command_string, command_string.len }); continue; - }; - - try cmds.append(cmd); + } } if (self.has_error) return ParseError.ParseFail; diff --git a/src/main.zig b/src/main.zig index 04bcf2d..be1b672 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,12 +16,26 @@ const readline = @cImport({ @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); try cmds.append(cmd); return cmds; } +fn copyCommandToHeap(allocator: *std.mem.Allocator, command: langs.Command, comptime tag: langs.Command.Tag) !*langs.Command { + const CommandStruct = langs.Command.tagToType(tag); + const casted = command.cast(CommandStruct).?; + var heap_cmd = try allocator.create(CommandStruct); + + @memcpy( + @ptrCast([*]u8, &heap_cmd), + @ptrCast([*]const u8, &casted), + @sizeOf(CommandStruct), + ); + + return &heap_cmd.base; +} + pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { var stdout_file = std.io.getStdOut(); const stdout = &stdout_file.outStream(); @@ -57,13 +71,16 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { } else { // if there isn't any commands on the file, we load our default // 'load :0' command - var loadargs = langs.ArgList.init(allocator); - try loadargs.append(":0"); - try cmds.append(langs.Command{ - .command = .Load, - .args = loadargs, - }); + // TODO: deliberate memleak here. we only allocate this + // command once, for the start of the file, so. + var load_cmd = try allocator.create(langs.Command.Load); + std.mem.copy(u8, load_cmd.path, ":0"); + load_cmd.base.tag = langs.Command.Tag.load; + + // taking address is fine, because load_cmd lives in the lifetime + // of the allocator. + try cmds.append(&load_cmd.base); } if (file_read_opt) |file_read| { @@ -101,11 +118,12 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { // run the load command try runner.runCommands(cmds, true); - var runqs_args = langs.ArgList.init(allocator); - defer runqs_args.deinit(); - const wanted_runner: []const u8 = std.os.getenv("SCRITCHER_RUNNER") orelse "ristretto"; - try runqs_args.append(wanted_runner); + + var runqs_cmd = langs.Command.RunQS{ + .base = langs.Command{ .tag = langs.Command.Tag.runqs }, + .program = wanted_runner, + }; while (true) { lang.reset(); @@ -121,11 +139,51 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { var line = rd_line[0..std.mem.len(rd_line)]; if (std.mem.eql(u8, line, "push")) { - try cmds.append(current); + const heap_cmd = switch (current.tag) { + .noop => try copyCommandToHeap(allocator, current, .noop), + .load => try copyCommandToHeap(allocator, current, .load), + .quicksave => try copyCommandToHeap(allocator, current, .quicksave), + .runqs => try copyCommandToHeap(allocator, current, .runqs), + .amp => try copyCommandToHeap(allocator, current, .amp), + .rflanger => try copyCommandToHeap(allocator, current, .rflanger), + .eq => try copyCommandToHeap(allocator, current, .eq), + .phaser => try copyCommandToHeap(allocator, current, .phaser), + .mbeq => try copyCommandToHeap(allocator, current, .mbeq), + .chorus => try copyCommandToHeap(allocator, current, .chorus), + .pitchscaler => try copyCommandToHeap(allocator, current, .pitchscaler), + .reverb => try copyCommandToHeap(allocator, current, .reverb), + .highpass => try copyCommandToHeap(allocator, current, .highpass), + .delay => try copyCommandToHeap(allocator, current, .delay), + .vinyl => try copyCommandToHeap(allocator, current, .vinyl), + .revdelay => try copyCommandToHeap(allocator, current, .revdelay), + .gate => try copyCommandToHeap(allocator, current, .gate), + .detune => try copyCommandToHeap(allocator, current, .detune), + .overdrive => try copyCommandToHeap(allocator, current, .overdrive), + .degrade => try copyCommandToHeap(allocator, current, .degrade), + .repsycho => try copyCommandToHeap(allocator, current, .repsycho), + .talkbox => try copyCommandToHeap(allocator, current, .talkbox), + .dyncomp => try copyCommandToHeap(allocator, current, .dyncomp), + .thruzero => try copyCommandToHeap(allocator, current, .thruzero), + .foverdrive => try copyCommandToHeap(allocator, current, .foverdrive), + .gverb => try copyCommandToHeap(allocator, current, .gverb), + .invert => try copyCommandToHeap(allocator, current, .invert), + .tapedelay => try copyCommandToHeap(allocator, current, .tapedelay), + .moddelay => try copyCommandToHeap(allocator, current, .moddelay), + .multichorus => try copyCommandToHeap(allocator, current, .multichorus), + .saturator => try copyCommandToHeap(allocator, current, .saturator), + .vintagedelay => try copyCommandToHeap(allocator, current, .vintagedelay), + .noise => try copyCommandToHeap(allocator, current, .noise), + .wildnoise => try copyCommandToHeap(allocator, current, .wildnoise), + .write => try copyCommandToHeap(allocator, current, .write), + .embed => try copyCommandToHeap(allocator, current, .embed), + .rotate => try copyCommandToHeap(allocator, current, .rotate), + }; + + try cmds.append(heap_cmd); // run the current added command to main cmds list // with the main parent runner - var cmds_wrapped = try wrapInCmdList(allocator, current); + var cmds_wrapped = try wrapInCmdList(allocator, heap_cmd); defer cmds_wrapped.deinit(); try runner.runCommands(cmds_wrapped, true); @@ -150,7 +208,11 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { std.debug.warn("repl: error while parsing: {}\n", .{err}); continue; }; - current = cmds_parsed.items[0]; + + // no command? ignore! + if (cmds_parsed.items.len == 0) continue; + + current = cmds_parsed.items[0].*; // by cloning the parent runner, we can iteratively write // whatever command we want and only commit the good results @@ -158,10 +220,9 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { var runner_clone = try runner.clone(); defer runner_clone.deinit(); - try cmds_parsed.append(langs.Command{ - .command = .RunQS, - .args = runqs_args, - }); + // taking address is fine, because runqs_cmd lives in the lifetime + // of this function. + try cmds_parsed.append(&runqs_cmd.base); try runner_clone.runCommands(cmds_parsed, true); _ = try stdout.write("\n"); diff --git a/src/printer.zig b/src/printer.zig index ea91ec2..bf32f76 100644 --- a/src/printer.zig +++ b/src/printer.zig @@ -1,54 +1,89 @@ +const std = @import("std"); const langs = @import("lang.zig"); +fn printCommandWithParams(stream: var, command: var) !void { + const Parameters = @TypeOf(command.parameters); + try stream.print(" {} {}", .{ command.split, command.index }); + inline for (@typeInfo(Parameters).Struct.fields) |field| { + if (field.field_type == f32 or field.field_type == f64) { + try stream.print(" {d}", .{@field(command.parameters, field.name)}); + } else { + try stream.print(" {}", .{@field(command.parameters, field.name)}); + } + } +} + +fn printCommand(stream: var, cmd: *langs.Command, comptime tag: langs.Command.Tag) !void { + const CommandStruct = langs.Command.tagToType(tag); + 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; + switch (ctype) { + .lv2_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 { for (list.items) |cmd| { - var command = switch (cmd.command) { - .Noop => "noop", - .Load => "load", - .Quicksave => "quicksave", - .RunQS => "runqs", - - .Amp => "amp", - .RFlanger => "rflanger", - .Eq => "eq", - .Phaser => "phaser", - .Mbeq => "mbeq", - .Chorus => "chorus", - .PitchScaler => "pitchscaler", - .Reverb => "reverb", - .Highpass => "highpass", - .Delay => "delay", - .Vinyl => "vinyl", - .RevDelay => "revdelay", - .Gate => "gate", - .Detune => "detune", - .Overdrive => "overdrive", - .Degrade => "Degrade", - .RePsycho => "repsycho", - .TalkBox => "talkbox", - .DynComp => "dyncomp", - .ThruZero => "thruzero", - .Foverdrive => "foverdrive", - .Gverb => "gverb", - .Invert => "invert", - .TapeDelay => "tapedelay", - .ModDelay => "moddelay", - .MultiChorus => "multichorus", - .Saturator => "saturator", - .VintageDelay => "vintagedelay", - - .Noise => "noise", - .WildNoise => "wildnoise", - .Write => "write", - .Embed => "embed", - - .Rotate => "rotate", - }; - + const command = @tagName(cmd.tag); try stream.print("{}", .{command}); - for (cmd.args.items) |arg| { - try stream.print(" {}", .{arg}); + switch (cmd.tag) { + .load => { + const load = cmd.cast(langs.Command.Load).?; + try stream.print(" {}", .{load.path}); + }, + .runqs => { + const runqs = cmd.cast(langs.Command.RunQS).?; + try stream.print(" {}", .{runqs.program}); + }, + .noop, .quicksave => {}, + .rotate => { + const rotate = cmd.cast(langs.Command.Rotate).?; + try stream.print(" {d} {}", .{ rotate.deg, rotate.bgfill }); + }, + + .amp => try printCommand(stream, cmd, .amp), + .rflanger => try printCommand(stream, cmd, .rflanger), + .eq => try printCommand(stream, cmd, .eq), + .phaser => try printCommand(stream, cmd, .phaser), + .mbeq => try printCommand(stream, cmd, .mbeq), + .chorus => try printCommand(stream, cmd, .chorus), + .pitchscaler => try printCommand(stream, cmd, .pitchscaler), + .reverb => try printCommand(stream, cmd, .reverb), + .highpass => try printCommand(stream, cmd, .highpass), + .delay => try printCommand(stream, cmd, .delay), + .vinyl => try printCommand(stream, cmd, .vinyl), + .revdelay => try printCommand(stream, cmd, .revdelay), + .gate => try printCommand(stream, cmd, .gate), + .detune => try printCommand(stream, cmd, .detune), + .overdrive => try printCommand(stream, cmd, .overdrive), + .degrade => try printCommand(stream, cmd, .degrade), + .repsycho => try printCommand(stream, cmd, .repsycho), + .talkbox => try printCommand(stream, cmd, .talkbox), + .dyncomp => try printCommand(stream, cmd, .dyncomp), + .thruzero => try printCommand(stream, cmd, .thruzero), + .foverdrive => try printCommand(stream, cmd, .foverdrive), + .gverb => try printCommand(stream, cmd, .gverb), + .invert => try printCommand(stream, cmd, .invert), + .tapedelay => try printCommand(stream, cmd, .tapedelay), + .moddelay => try printCommand(stream, cmd, .moddelay), + .multichorus => try printCommand(stream, cmd, .multichorus), + .saturator => try printCommand(stream, cmd, .saturator), + .vintagedelay => try printCommand(stream, cmd, .vintagedelay), + + .noise => try printCommand(stream, cmd, .noise), + .wildnoise => try printCommand(stream, cmd, .wildnoise), + .write => try printCommand(stream, cmd, .write), + .embed => try printCommand(stream, cmd, .embed), } _ = try stream.write(";\n"); diff --git a/src/runner.zig b/src/runner.zig index 6276954..a089a67 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -24,12 +24,15 @@ pub const Runner = struct { image: ?*Image = null, /// If the runner is in REPL mode - repl: bool = false, + repl: bool, + + args: [][]u8, pub fn init(allocator: *std.mem.Allocator, repl: bool) Runner { return Runner{ .allocator = allocator, .repl = repl, + .args = std.process.argsAlloc(allocator) catch unreachable, }; } @@ -41,7 +44,12 @@ pub const Runner = struct { pub fn clone(self: *Runner) !Runner { var cloned_image = if (self.image) |image| try image.clone() else null; - return Runner{ .allocator = self.allocator, .image = cloned_image }; + return Runner{ + .allocator = self.allocator, + .image = cloned_image, + .repl = self.repl, + .args = self.args, + }; } fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 { @@ -49,20 +57,20 @@ pub const Runner = struct { // parse the index from 1 to end var index = try std.fmt.parseInt(usize, load_path[1..], 10); - // don't care about the 'repl' being prepended when we're in repl - if (self.repl) index += 1; + // if it isn't in the repl, args look like this: + // 'scritcher ./script ./image' + // if it is, it looks like this + // 'scritcher repl ./script ./image' - var args_it = std.process.args(); - _ = args_it.skip(); + // ':0' should ALWAYS point to the image. + if (self.repl) index += 3 else index += 2; - var i: usize = 0; - while (i <= index) : (i += 1) { - _ = args_it.skip(); + std.debug.warn("ARGS!! {} \n", .{self.args.len}); + for (self.args) |arg, idx| { + std.debug.warn("arg{} = {}\n", .{ idx, arg }); } - - const arg = try (args_it.next(self.allocator) orelse @panic("expected argument")); - - return arg; + std.debug.warn("fetch arg idx={}, val={}\n", .{ index, self.args[index] }); + return self.args[index]; } else { return load_path; } @@ -90,7 +98,7 @@ pub const Runner = struct { // krita/gimp and make it export a bmp and while in the program you can // apply filters, etc. 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.\n", .{}); + std.debug.warn("Only BMP files are allowed to be loaded. Got path '{}'\n", .{load_path}); return RunError.NoBMP; } @@ -175,614 +183,134 @@ pub const Runner = struct { try image.saveTo(out_path); } - fn runQSCmd(self: *Runner, program: []const u8) !void { + fn runQSCmd(self: *Runner, cmd: lang.Command) !void { + const runqs = cmd.cast(lang.Command.RunQS).?; var image = try self.getImage(); const out_path = try self.makeGlitchedPath(); try image.saveTo(out_path); var proc = try std.ChildProcess.init( - &[_][]const u8{ program, out_path }, + &[_][]const u8{ runqs.program, out_path }, self.allocator, ); defer proc.deinit(); - std.debug.warn("running '{} {}'\n", .{ program, out_path }); + std.debug.warn("running '{} {}'\n", .{ runqs.program, out_path }); _ = try proc.spawnAndWait(); } - /// Run the http://lv2plug.in/plugins/eg-amp plugin over the file. - fn ampCmd(self: *Runner, pos: Position, params: ParamList) !void { + fn rotateCmd(self: *Runner, cmd: lang.Command) !void { + const rotate_cmd = cmd.cast(lang.Command.Rotate).?; + var image = try self.getImage(); - try image.runPlugin("http://lv2plug.in/plugins/eg-amp", pos, params); + var c_bgfill = try std.cstr.addNullByte(self.allocator, rotate_cmd.bgfill); + defer self.allocator.free(c_bgfill); + + try magick.runRotate(image, rotate_cmd.deg, c_bgfill); } - fn rFlangerCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/retroFlange", pos, params); - } + fn executeLV2Command(self: *@This(), command: var) !void { + const pos = plugin.Position{ + .split = command.split, + .index = command.index, + }; - fn eqCmd(self: *Runner, position: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/dj_eq_mono", position, params); - } - - fn phaserCmd(self: *Runner, position: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/lfoPhaser", position, params); - } - - fn mbeqCmd(self: *Runner, position: Position, bands: []const f32) !void { - var image = try self.getImage(); var params = ParamList.init(self.allocator); defer params.deinit(); - for (bands) |band_value, idx| { - var sym = try std.fmt.allocPrint(self.allocator, "band_{}", .{idx + 1}); + const typ = @TypeOf(command); + + inline for (@typeInfo(@TypeOf(command.parameters)).Struct.fields) |cmd_field| { try params.append(plugin.Param{ - .sym = sym, - .value = band_value, + .sym = cmd_field.name, + .value = @field(command.parameters, cmd_field.name), }); } - try image.runPlugin("http://plugin.org.uk/swh-plugins/mbeq", position, params); - } - - fn chorusCmd(self: *Runner, pos: Position, params: ParamList) !void { var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/multivoiceChorus", pos, params); + try image.runPlugin(typ.lv2_url, pos, params); } - fn pitchScalerCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/pitchScaleHQ", pos, params); - } - - fn reverbCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://invadarecords.com/plugins/lv2/erreverb/mono", pos, params); - } - - fn highpassCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://invadarecords.com/plugins/lv2/filter/hpf/mono", pos, params); - } - - fn delayCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/delayorama", pos, params); - } - - fn vinylCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/vynil", pos, params); - } - - fn revDelayCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/revdelay", pos, params); - } - - fn noiseCmd(self: *Runner, pos: Position, map: *ParamMap) !void { - var image = try self.getImage(); - try image.runCustomPlugin(custom.RandomNoise, pos, *ParamMap, map); - } - - fn wildNoiseCmd(self: *Runner, pos: Position, map: *ParamMap) !void { - var image = try self.getImage(); - try image.runCustomPlugin(custom.WildNoise, pos, *ParamMap, map); - } - - fn writeCmd(self: *Runner, pos: Position, map: *ParamMap) !void { - var image = try self.getImage(); - try image.runCustomPlugin(custom.Write, pos, *ParamMap, map); - } - - fn embedCmd(self: *Runner, pos: Position, path: []const u8) !void { - var image = try self.getImage(); - try image.runCustomPlugin(custom.Embed, pos, []const u8, path); - } - - fn rotateCmd( - self: *Runner, - deg: f32, - bgfill: []const u8, - ) !void { - var image = try self.getImage(); - var c_bgfill = try std.cstr.addNullByte(self.allocator, bgfill); - defer self.allocator.free(c_bgfill); - - try magick.runRotate(image, deg, c_bgfill); - } - - fn gateCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://hippie.lt/lv2/gate", pos, params); - } - - fn detuneCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/Detune", pos, params); - } - - fn overdriveCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/Overdrive", pos, params); - } - - fn degradeCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/Degrade", pos, params); - } - - fn repsychoCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/RePsycho", pos, params); - } - - fn talkboxCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/TalkBox", pos, params); - } - - fn dynCompCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://gareus.org/oss/lv2/darc#mono", pos, params); - } - - fn foverdriveCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/foverdrive", pos, params); - } - - fn thruZeroCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://drobilla.net/plugins/mda/ThruZero", pos, params); - } - - fn gverbCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/gverb", pos, params); - } - - fn invertCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/inv", pos, params); - } - - fn tapedelayCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/tapeDelay", pos, params); - } - - fn moddelayCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://plugin.org.uk/swh-plugins/modDelay", pos, params); - } - - fn multichorusCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://calf.sourceforge.net/plugins/MultiChorus", pos, params); - } - - fn saturatorCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://calf.sourceforge.net/plugins/Saturator", pos, params); - } - - fn vintagedelayCmd(self: *Runner, pos: Position, params: ParamList) !void { - var image = try self.getImage(); - try image.runPlugin("http://calf.sourceforge.net/plugins/VintageDelay", pos, params); - } - - fn runCommand(self: *Runner, cmd: *lang.Command) !void { - var params = ParamList.init(self.allocator); - defer params.deinit(); - - var map = ParamMap.init(self.allocator); - defer map.deinit(); - - return switch (cmd.command) { - .Noop => {}, - .Load => blk: { - var path = cmd.args.items[0]; - try self.loadCmd(path); - - // TODO is this needed? - break :blk; - }, - .Quicksave => try self.quicksaveCmd(), - .RunQS => try self.runQSCmd(cmd.args.items[0]), - - .Amp => blk: { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "gain"); - try self.ampCmd(pos, params); - }, - - .RFlanger => blk: { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "delay_depth_avg"); - try cmd.appendParam(¶ms, "law_freq"); - try self.rFlangerCmd(pos, params); - }, - - .Eq => blk: { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "lo"); - try cmd.appendParam(¶ms, "mid"); - try cmd.appendParam(¶ms, "hi"); - - try self.eqCmd(pos, params); - }, - - .Phaser => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "lfo_rate"); - try cmd.appendParam(¶ms, "lfo_depth"); - try cmd.appendParam(¶ms, "fb"); - try cmd.appendParam(¶ms, "spread"); - - try self.phaserCmd(pos, params); - }, - - .Mbeq => blk: { - const pos = try cmd.consumePosition(); - const bands = try cmd.floatArgMany(self.allocator, 2, 15, @as(f32, 0)); - defer self.allocator.free(bands); - - try self.mbeqCmd(pos, bands); - }, - - .Chorus => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "voices"); - try cmd.appendParam(¶ms, "delay_base"); - try cmd.appendParam(¶ms, "voice_spread"); - try cmd.appendParam(¶ms, "detune"); - try cmd.appendParam(¶ms, "law_freq"); - try cmd.appendParam(¶ms, "attendb"); - - try self.chorusCmd(pos, params); - }, - - .PitchScaler => blk: { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "mult"); - try self.pitchScalerCmd(pos, params); - }, - - .Reverb => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "roomLength"); - try cmd.appendParam(¶ms, "roomWidth"); - try cmd.appendParam(¶ms, "roomHeight"); - try cmd.appendParam(¶ms, "sourceLR"); - try cmd.appendParam(¶ms, "sourceFB"); - try cmd.appendParam(¶ms, "listLR"); - try cmd.appendParam(¶ms, "listFB"); - try cmd.appendParam(¶ms, "hpf"); - try cmd.appendParam(¶ms, "warmth"); - try cmd.appendParam(¶ms, "diffusion"); - - try self.reverbCmd(pos, params); - }, - - .Highpass => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "freq"); - try cmd.appendParam(¶ms, "gain"); - try cmd.appendParam(¶ms, "noClip"); - - try self.highpassCmd(pos, params); - }, - - .Delay => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "seed"); - try cmd.appendParam(¶ms, "gain"); - try cmd.appendParam(¶ms, "feedback_pc"); - try cmd.appendParam(¶ms, "tap_count"); - try cmd.appendParam(¶ms, "first_delay"); - try cmd.appendParam(¶ms, "delay_range"); - try cmd.appendParam(¶ms, "delay_scale"); - try cmd.appendParam(¶ms, "delay_rand_pc"); - try cmd.appendParam(¶ms, "gain_scale"); - try cmd.appendParam(¶ms, "wet"); - - try self.delayCmd(pos, params); - }, - - .Vinyl => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "year"); - try cmd.appendParam(¶ms, "rpm"); - try cmd.appendParam(¶ms, "warp"); - try cmd.appendParam(¶ms, "click"); - try cmd.appendParam(¶ms, "wear"); - - try self.vinylCmd(pos, params); - }, - - .RevDelay => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "delay_time"); - try cmd.appendParam(¶ms, "dry_level"); - try cmd.appendParam(¶ms, "wet_level"); - try cmd.appendParam(¶ms, "feedback"); - try cmd.appendParam(¶ms, "xfade_samp"); - - try self.revDelayCmd(pos, params); - }, - - .Noise => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParamMap(&map, "seed"); - try cmd.appendParamMap(&map, "fill_bytes"); - - try self.noiseCmd(pos, &map); - }, - - .WildNoise => blk: { - const pos = try cmd.consumePosition(); - - try cmd.appendParamMap(&map, "seed"); - try cmd.appendParamMap(&map, "fill_bytes"); - - try self.wildNoiseCmd(pos, &map); - }, - - .Write => blk: { - const pos = try cmd.consumePosition(); - try cmd.appendParamMap(&map, "data"); - try self.writeCmd(pos, &map); - }, - - .Embed => blk: { - const pos = try cmd.consumePosition(); - const path = cmd.args.items[2]; - try self.embedCmd(pos, path); - }, - - .Rotate => blk: { - const deg = try cmd.floatArgAt(0); - const bgfill = try cmd.argAt(1); - try self.rotateCmd(deg, bgfill); - }, - - .Gate => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "switch"); - try cmd.appendParam(¶ms, "threshold"); - try cmd.appendParam(¶ms, "attack"); - try cmd.appendParam(¶ms, "hold"); - try cmd.appendParam(¶ms, "decay"); - try cmd.appendParam(¶ms, "gaterange"); - try self.gateCmd(pos, params); - }, - - .Detune => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "detune"); - try cmd.appendParam(¶ms, "mix"); - try cmd.appendParam(¶ms, "output"); - try cmd.appendParam(¶ms, "latency"); - try self.detuneCmd(pos, params); - }, - - .Overdrive => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "drive"); - try cmd.appendParam(¶ms, "muffle"); - try cmd.appendParam(¶ms, "output"); - try self.overdriveCmd(pos, params); - }, - - .Degrade => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "headroom"); - try cmd.appendParam(¶ms, "quant"); - try cmd.appendParam(¶ms, "rate"); - try cmd.appendParam(¶ms, "post_filt"); - try cmd.appendParam(¶ms, "non_lin"); - try cmd.appendParam(¶ms, "output"); - try self.degradeCmd(pos, params); - }, - - .RePsycho => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "tune"); - try cmd.appendParam(¶ms, "fine"); - try cmd.appendParam(¶ms, "decay"); - try cmd.appendParam(¶ms, "thresh"); - try cmd.appendParam(¶ms, "hold"); - try cmd.appendParam(¶ms, "mix"); - try cmd.appendParam(¶ms, "quality"); - try self.repsychoCmd(pos, params); - }, - - .TalkBox => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "wet"); - try cmd.appendParam(¶ms, "dry"); - try cmd.appendParam(¶ms, "carrier"); - try cmd.appendParam(¶ms, "quality"); - try self.talkboxCmd(pos, params); - }, - - .DynComp => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "enable"); - try cmd.appendParam(¶ms, "hold"); - try cmd.appendParam(¶ms, "inputgain"); - try cmd.appendParam(¶ms, "threshold"); - try cmd.appendParam(¶ms, "ratio"); - try cmd.appendParam(¶ms, "attack"); - try cmd.appendParam(¶ms, "release"); - try cmd.appendParam(¶ms, "gain_min"); - try cmd.appendParam(¶ms, "gain_max"); - try cmd.appendParam(¶ms, "rms"); - try self.dynCompCmd(pos, params); - }, - - .ThruZero => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "rate"); - try cmd.appendParam(¶ms, "mix"); - try cmd.appendParam(¶ms, "feedback"); - try cmd.appendParam(¶ms, "depth_mod"); - try self.thruZeroCmd(pos, params); - }, - - .Foverdrive => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "drive"); - try self.foverdriveCmd(pos, params); - }, - - .Gverb => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "roomsize"); - try cmd.appendParam(¶ms, "revtime"); - try cmd.appendParam(¶ms, "damping"); - try cmd.appendParam(¶ms, "inputbandwidth"); - try cmd.appendParam(¶ms, "drylevel"); - try cmd.appendParam(¶ms, "earlylevel"); - try cmd.appendParam(¶ms, "taillevel"); - try self.gverbCmd(pos, params); - }, - - .Invert => { - const pos = try cmd.consumePosition(); - try self.gverbCmd(pos, params); - }, - - .TapeDelay => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "speed"); - try cmd.appendParam(¶ms, "da_db"); - - try cmd.appendParam(¶ms, "t1d"); - try cmd.appendParam(¶ms, "t1a_db"); - - try cmd.appendParam(¶ms, "t2d"); - try cmd.appendParam(¶ms, "t2a_db"); - - try cmd.appendParam(¶ms, "t3d"); - try cmd.appendParam(¶ms, "t3a_db"); - - try cmd.appendParam(¶ms, "t4d"); - try cmd.appendParam(¶ms, "t4a_db"); - - try self.tapedelayCmd(pos, params); - }, - - .ModDelay => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "base"); - try self.moddelayCmd(pos, params); - }, - - .MultiChorus => { - const pos = try cmd.consumePosition(); - try cmd.appendParam(¶ms, "min_delay"); - try cmd.appendParam(¶ms, "mod_depth"); - try cmd.appendParam(¶ms, "mod_rate"); - try cmd.appendParam(¶ms, "stereo"); - try cmd.appendParam(¶ms, "voices"); - try cmd.appendParam(¶ms, "vphase"); - try cmd.appendParam(¶ms, "amount"); - try cmd.appendParam(¶ms, "dry"); - try cmd.appendParam(¶ms, "freq"); - try cmd.appendParam(¶ms, "freq2"); - try cmd.appendParam(¶ms, "q"); - try cmd.appendParam(¶ms, "overlap"); - try cmd.appendParam(¶ms, "level_in"); - try cmd.appendParam(¶ms, "level_out"); - try cmd.appendParam(¶ms, "lfo"); - try self.multichorusCmd(pos, params); - }, - - .Saturator => { - const pos = try cmd.consumePosition(); - - try cmd.appendParam(¶ms, "bypass"); - try cmd.appendParam(¶ms, "level_in"); - try cmd.appendParam(¶ms, "level_out"); - try cmd.appendParam(¶ms, "mix"); - try cmd.appendParam(¶ms, "drive"); - try cmd.appendParam(¶ms, "blend"); - try cmd.appendParam(¶ms, "lp_pre_freq"); - try cmd.appendParam(¶ms, "hp_pre_freq"); - try cmd.appendParam(¶ms, "lp_post_freq"); - try cmd.appendParam(¶ms, "hp_post_freq"); - try cmd.appendParam(¶ms, "p_freq"); - try cmd.appendParam(¶ms, "p_level"); - try cmd.appendParam(¶ms, "p_q"); - try cmd.appendParam(¶ms, "pre"); - try cmd.appendParam(¶ms, "post"); - try self.saturatorCmd(pos, params); - }, - - .VintageDelay => { - const pos = try cmd.consumePosition(); - const PARAMS = [_][]const u8{ - "level_in", - "level_out", - "subdiv", - "time_l", - "time_r", - "feedback", - "amount", - "mix_mode", - "medium", - "dry", - "width", - "fragmentation", - "pbeats", - "pfrag", - "timing", - "bpm", - "ms", - "hz", - "bpm_host", - }; - - inline for (PARAMS) |param| { - try cmd.appendParam(¶ms, param); - } - - try self.vintagedelayCmd(pos, params); - }, - - else => blk: { - std.debug.warn("Unsupported command: {}\n", .{cmd.command}); - break :blk RunError.UnknownCommand; - }, + fn executeCustomCommand(self: *@This(), command: var) !void { + const pos = plugin.Position{ + .split = command.split, + .index = command.index, }; + + var image = try self.getImage(); + try image.runCustomPlugin(@TypeOf(command).plugin_type, pos, command.parameters); } + fn runSingleCommand( + self: *@This(), + cmd: lang.Command, + comptime tag: lang.Command.Tag, + ) !void { + comptime const typ = lang.Command.tagToType(tag); + const command = cmd.cast(typ).?; + const ctype = typ.command_type; + switch (ctype) { + .lv2_command => try self.executeLV2Command(command.*), + .custom_command => try self.executeCustomCommand(command.*), + else => @panic("TODO support command type"), + } + } + + fn runCommand(self: *@This(), cmd: lang.Command) !void { + switch (cmd.tag) { + .noop => {}, + .load => { + const command = cmd.cast(lang.Command.Load).?; + try self.loadCmd(command.path); + }, + .quicksave => try self.quicksaveCmd(), + .rotate => try self.rotateCmd(cmd), + .runqs => try self.runQSCmd(cmd), + + .amp => try self.runSingleCommand(cmd, .amp), + .rflanger => try self.runSingleCommand(cmd, .rflanger), + .eq => try self.runSingleCommand(cmd, .eq), + .phaser => try self.runSingleCommand(cmd, .phaser), + .mbeq => try self.runSingleCommand(cmd, .mbeq), + .chorus => try self.runSingleCommand(cmd, .chorus), + .pitchscaler => try self.runSingleCommand(cmd, .pitchscaler), + .reverb => try self.runSingleCommand(cmd, .reverb), + .highpass => try self.runSingleCommand(cmd, .highpass), + .delay => try self.runSingleCommand(cmd, .delay), + .vinyl => try self.runSingleCommand(cmd, .vinyl), + .revdelay => try self.runSingleCommand(cmd, .revdelay), + .gate => try self.runSingleCommand(cmd, .gate), + .detune => try self.runSingleCommand(cmd, .detune), + .overdrive => try self.runSingleCommand(cmd, .overdrive), + .degrade => try self.runSingleCommand(cmd, .degrade), + .repsycho => try self.runSingleCommand(cmd, .repsycho), + .talkbox => try self.runSingleCommand(cmd, .talkbox), + .dyncomp => try self.runSingleCommand(cmd, .dyncomp), + .thruzero => try self.runSingleCommand(cmd, .thruzero), + .foverdrive => try self.runSingleCommand(cmd, .foverdrive), + .gverb => try self.runSingleCommand(cmd, .gverb), + .invert => try self.runSingleCommand(cmd, .invert), + .tapedelay => try self.runSingleCommand(cmd, .tapedelay), + .moddelay => try self.runSingleCommand(cmd, .moddelay), + .multichorus => try self.runSingleCommand(cmd, .multichorus), + .saturator => try self.runSingleCommand(cmd, .saturator), + .vintagedelay => try self.runSingleCommand(cmd, .vintagedelay), + + .noise => try self.runSingleCommand(cmd, .noise), + .wildnoise => try self.runSingleCommand(cmd, .wildnoise), + .write => try self.runSingleCommand(cmd, .write), + .embed => try self.runSingleCommand(cmd, .embed), + } + } /// Run a list of commands. pub fn runCommands( self: *Runner, cmds: lang.CommandList, debug_flag: bool, ) !void { - for (cmds.items) |const_cmd| { - if (debug_flag) const_cmd.print(); - - // copy the command so we own its memory - var cmd = try const_cmd.copy(self.allocator); - defer self.allocator.destroy(cmd); - - try self.runCommand(cmd); + for (cmds.items) |cmd| { + cmd.print(); + try self.runCommand(cmd.*); } } };