diff --git a/src/custom.zig b/src/custom.zig index 0420bd3..f346fa0 100644 --- a/src/custom.zig +++ b/src/custom.zig @@ -16,12 +16,15 @@ pub const RandomNoise = struct { pub fn init( allocator: *std.mem.Allocator, - params: var, + params: *plugins.ParamMap, ) ?RandomNoise { - var r = std.rand.DefaultPrng.init(params.seed); + const seed = @floatToInt(u64, params.get("seed").?.value); + const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value); - if (params.fill_bytes > 0) { - var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; + var r = std.rand.DefaultPrng.init(seed); + + if (fillbytes > 0) { + var rand_buf = allocator.alloc(f32, fillbytes) catch return null; for (rand_buf) |_, idx| { rand_buf[idx] = r.random.float(f32); @@ -64,12 +67,15 @@ pub const WildNoise = struct { pub fn init( allocator: *std.mem.Allocator, - params: var, + params: *plugins.ParamMap, ) ?WildNoise { - var r = std.rand.DefaultPrng.init(params.seed); + const seed = @floatToInt(u64, params.get("seed").?.value); + const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value); - if (params.fill_bytes > 0) { - var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null; + var r = std.rand.DefaultPrng.init(seed); + + if (fillbytes > 0) { + var rand_buf = allocator.alloc(f32, fillbytes) catch return null; for (rand_buf) |_, idx| { rand_buf[idx] = @intToFloat(f32, r.random.int(u1)); @@ -112,10 +118,11 @@ pub const Write = struct { pub fn init( allocator: *std.mem.Allocator, - params: var, + params: *plugins.ParamMap, ) Write { + const data = params.get("data").?; return Write{ - .data = params.data, + .data = data.value, }; } @@ -133,10 +140,10 @@ pub const Embed = struct { sndfile: *c.SNDFILE = undefined, buf: []f32 = undefined, - pub fn init(allocator: *std.mem.Allocator, params: var) @This() { + pub fn init(allocator: *std.mem.Allocator, filepath: []const u8) @This() { return Embed{ .allocator = allocator, - .filepath = params.path, + .filepath = filepath, }; } diff --git a/src/image.zig b/src/image.zig index e0fea67..7f8deb4 100644 --- a/src/image.zig +++ b/src/image.zig @@ -75,17 +75,18 @@ 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_XXXXXXXXXXXXXXXXXXXXX"; + const template = "/tmp/temp_XXXXXXXXXXX"; var nam = try allocator.alloc(u8, template.len); std.mem.copy(u8, nam, template); - const seed = @truncate(u64, @bitCast(u128, std.time.nanoTimestamp())); + const seed = @bitCast(u64, std.time.timestamp()); 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)); @@ -94,16 +95,12 @@ pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 { } // 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( - 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(); + _ = std.fs.cwd().openFile(nam, .{ .read = true, .write = false }) catch |err| { + if (err == error.FileNotFound) { + return nam; + } + }; } return error.TempGenFail; @@ -425,7 +422,8 @@ pub const Image = struct { self: *Image, comptime Plugin: type, position: plugins.Position, - extra: var, + comptime ExtraType: type, + extra: ExtraType, ) !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 305eb4a..34c5004 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -1,486 +1,166 @@ const std = @import("std"); const plugin = @import("plugin.zig"); -const custom = @import("custom.zig"); -pub const ParseError = error{ParseFail}; - -pub const CommandType = enum { - /// "LV2 Commands" are commands that receive split, index, and then receive - /// any f64 arguments. - lv2_command, - - custom_command, +pub const ParseError = error{ + OutOfMemory, + ArgRequired, + ParseFail, }; -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; +pub const CommandType = enum { + Noop, + Load, + Quicksave, + RunQS, - base: Command, - split: usize, - index: usize, - parameters: LV2Parameters, - }; -} + 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, -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; + Noise, + WildNoise, + Write, + Embed, - base: Command, - split: usize, - index: usize, - parameters: PluginParameters, - }; -} + Rotate, +}; pub const Command = struct { - tag: Tag, + command: CommandType, + args: ArgList, + cur_idx: usize = 0, - pub const Tag = enum { - noop, - load, - quicksave, - runqs, + pub fn print(self: Command) void { + std.debug.warn("cmd:{}\n", .{self.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, + pub fn argAt(self: Command, idx: usize) ![]const u8 { + std.debug.warn("{} {}", .{ idx, self.args.items.len }); - noise, - wildnoise, - write, - embed, + if (idx > (self.args.items.len - 1)) { + std.debug.warn("Expected argument at index {}\n", .{idx}); + return ParseError.ArgRequired; + } - rotate, - }; + return self.args.items[idx]; + } - pub fn tagToType(tag: Tag) type { - return switch (tag) { - .noop => Noop, - .load => Load, - .quicksave => Quicksave, - .runqs => RunQS, + pub fn usizeArgAt(self: Command, idx: usize) !usize { + var arg = try self.argAt(idx); + return try std.fmt.parseInt(usize, arg, 10); + } - .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 consumePosition(self: *Command) !plugin.Position { + self.cur_idx = 2; + return plugin.Position{ + .split = try self.usizeArgAt(0), + .index = try self.usizeArgAt(1), }; } - 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 intArgAt(self: Command, idx: usize) !i32 { + var arg = try self.argAt(idx); + return try std.fmt.parseInt(i32, arg, 10); } - pub fn print(base: *const @This()) void { - std.debug.warn("tag: {}\n", .{base.tag}); + pub fn floatArgAt(self: Command, idx: usize) !f32 { + var arg = try self.argAt(idx); + return try std.fmt.parseFloat(f32, arg); } - pub const Noop = struct { - pub const base_tag = Tag.noop; - base: Command, - }; + 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 Load = struct { - pub const base_tag = Tag.load; - base: Command, - path: []u8, - }; + 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 Quicksave = struct { - pub const base_tag = Tag.quicksave; - base: Command, - }; + try arr.append(value); + } - pub const RunQS = struct { - pub const base_tag = Tag.runqs; - base: Command, - program: []const u8, - }; + return arr.items; + } - pub const Noise = CustomCommand(Tag.noise, custom.RandomNoise, struct { - seed: u64, - fill_bytes: usize, - }); + 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 Wildnoise = CustomCommand(Tag.wildnoise, custom.WildNoise, struct { - seed: u64, - fill_bytes: usize, - }); + try params.append(plugin.Param{ + .sym = symbol, + .value = val, + }); + } - pub const Write = CustomCommand(Tag.write, custom.Write, struct { - data: f32, - }); + 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 Embed = CustomCommand(Tag.write, custom.Embed, struct { - path: []const u8, - }); + 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 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, - }); + return cmd; + } }; -pub const CommandList = std.ArrayList(*Command); +pub const CommandList = std.ArrayList(Command); +pub const ArgList = std.ArrayList([]const u8); + +pub const KeywordMap = std.StringHashMap(CommandType); /// A parser. pub const Lang = struct { allocator: *std.mem.Allocator, + keywords: KeywordMap, has_error: bool = false, line: usize = 0, @@ -488,108 +168,137 @@ 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 {} + pub fn deinit(self: *Lang) void { + self.keywords.deinit(); + } 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; } - 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 { + pub fn parse(self: *Lang, data: []const u8) ParseError!CommandList { var splitted_it = std.mem.split(data, ";"); + try self.fillKeywords(); var cmds = CommandList.init(self.allocator); while (splitted_it.next()) |stmt_orig| { @@ -600,7 +309,7 @@ pub const Lang = struct { if (stmt.len == 0) continue; if (std.mem.startsWith(u8, stmt, "#")) continue; - // TODO better tokenizer instead of just tokenize(" ")...maybe???? + // TODO better tokenizer instead of just tokenize(" "); var tok_it = std.mem.tokenize(stmt, " "); var cmd_opt = tok_it.next(); @@ -608,45 +317,32 @@ pub const Lang = struct { self.doError("No command given", .{}); continue; } - const command_string = cmd_opt.?; + var command = cmd_opt.?; - 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); - } - } - - if (!found) { - self.doError("Unknown command '{}' ({})", .{ command_string, command_string.len }); + 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 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); + continue; + }; + + try cmds.append(cmd); } if (self.has_error) return ParseError.ParseFail; diff --git a/src/main.zig b/src/main.zig index be1b672..04bcf2d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,26 +16,12 @@ 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(); @@ -71,16 +57,13 @@ 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"); - // 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); + try cmds.append(langs.Command{ + .command = .Load, + .args = loadargs, + }); } if (file_read_opt) |file_read| { @@ -118,12 +101,11 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { // run the load command try runner.runCommands(cmds, true); - const wanted_runner: []const u8 = std.os.getenv("SCRITCHER_RUNNER") orelse "ristretto"; + var runqs_args = langs.ArgList.init(allocator); + defer runqs_args.deinit(); - var runqs_cmd = langs.Command.RunQS{ - .base = langs.Command{ .tag = langs.Command.Tag.runqs }, - .program = wanted_runner, - }; + const wanted_runner: []const u8 = std.os.getenv("SCRITCHER_RUNNER") orelse "ristretto"; + try runqs_args.append(wanted_runner); while (true) { lang.reset(); @@ -139,51 +121,11 @@ 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")) { - 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); + try cmds.append(current); // run the current added command to main cmds list // with the main parent runner - var cmds_wrapped = try wrapInCmdList(allocator, heap_cmd); + var cmds_wrapped = try wrapInCmdList(allocator, current); defer cmds_wrapped.deinit(); try runner.runCommands(cmds_wrapped, true); @@ -208,11 +150,7 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { std.debug.warn("repl: error while parsing: {}\n", .{err}); continue; }; - - // no command? ignore! - if (cmds_parsed.items.len == 0) continue; - - current = cmds_parsed.items[0].*; + current = cmds_parsed.items[0]; // by cloning the parent runner, we can iteratively write // whatever command we want and only commit the good results @@ -220,9 +158,10 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void { var runner_clone = try runner.clone(); defer runner_clone.deinit(); - // taking address is fine, because runqs_cmd lives in the lifetime - // of this function. - try cmds_parsed.append(&runqs_cmd.base); + try cmds_parsed.append(langs.Command{ + .command = .RunQS, + .args = runqs_args, + }); try runner_clone.runCommands(cmds_parsed, true); _ = try stdout.write("\n"); diff --git a/src/printer.zig b/src/printer.zig index bf32f76..ea91ec2 100644 --- a/src/printer.zig +++ b/src/printer.zig @@ -1,89 +1,54 @@ -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| { - const command = @tagName(cmd.tag); + 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", + }; + try stream.print("{}", .{command}); - 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), + for (cmd.args.items) |arg| { + try stream.print(" {}", .{arg}); } _ = try stream.write(";\n"); diff --git a/src/runner.zig b/src/runner.zig index a089a67..6276954 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -24,15 +24,12 @@ pub const Runner = struct { image: ?*Image = null, /// If the runner is in REPL mode - repl: bool, - - args: [][]u8, + repl: bool = false, pub fn init(allocator: *std.mem.Allocator, repl: bool) Runner { return Runner{ .allocator = allocator, .repl = repl, - .args = std.process.argsAlloc(allocator) catch unreachable, }; } @@ -44,12 +41,7 @@ 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, - .repl = self.repl, - .args = self.args, - }; + return Runner{ .allocator = self.allocator, .image = cloned_image }; } fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 { @@ -57,20 +49,20 @@ pub const Runner = struct { // parse the index from 1 to end var index = try std.fmt.parseInt(usize, load_path[1..], 10); - // 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' + // don't care about the 'repl' being prepended when we're in repl + if (self.repl) index += 1; - // ':0' should ALWAYS point to the image. - if (self.repl) index += 3 else index += 2; + var args_it = std.process.args(); + _ = args_it.skip(); - std.debug.warn("ARGS!! {} \n", .{self.args.len}); - for (self.args) |arg, idx| { - std.debug.warn("arg{} = {}\n", .{ idx, arg }); + var i: usize = 0; + while (i <= index) : (i += 1) { + _ = args_it.skip(); } - std.debug.warn("fetch arg idx={}, val={}\n", .{ index, self.args[index] }); - return self.args[index]; + + const arg = try (args_it.next(self.allocator) orelse @panic("expected argument")); + + return arg; } else { return load_path; } @@ -98,7 +90,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. Got path '{}'\n", .{load_path}); + std.debug.warn("Only BMP files are allowed to be loaded.\n", .{}); return RunError.NoBMP; } @@ -183,134 +175,614 @@ pub const Runner = struct { try image.saveTo(out_path); } - fn runQSCmd(self: *Runner, cmd: lang.Command) !void { - const runqs = cmd.cast(lang.Command.RunQS).?; + fn runQSCmd(self: *Runner, program: []const u8) !void { var image = try self.getImage(); const out_path = try self.makeGlitchedPath(); try image.saveTo(out_path); var proc = try std.ChildProcess.init( - &[_][]const u8{ runqs.program, out_path }, + &[_][]const u8{ program, out_path }, self.allocator, ); defer proc.deinit(); - std.debug.warn("running '{} {}'\n", .{ runqs.program, out_path }); + std.debug.warn("running '{} {}'\n", .{ program, out_path }); _ = try proc.spawnAndWait(); } - fn rotateCmd(self: *Runner, cmd: lang.Command) !void { - const rotate_cmd = cmd.cast(lang.Command.Rotate).?; - + /// Run the http://lv2plug.in/plugins/eg-amp plugin over the file. + fn ampCmd(self: *Runner, pos: Position, params: ParamList) !void { var image = try self.getImage(); - 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); + try image.runPlugin("http://lv2plug.in/plugins/eg-amp", pos, params); } - fn executeLV2Command(self: *@This(), command: var) !void { - const pos = plugin.Position{ - .split = command.split, - .index = command.index, - }; + 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 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(); - const typ = @TypeOf(command); - - inline for (@typeInfo(@TypeOf(command.parameters)).Struct.fields) |cmd_field| { + for (bands) |band_value, idx| { + var sym = try std.fmt.allocPrint(self.allocator, "band_{}", .{idx + 1}); try params.append(plugin.Param{ - .sym = cmd_field.name, - .value = @field(command.parameters, cmd_field.name), + .sym = sym, + .value = band_value, }); } - var image = try self.getImage(); - try image.runPlugin(typ.lv2_url, pos, params); + try image.runPlugin("http://plugin.org.uk/swh-plugins/mbeq", position, params); } - fn executeCustomCommand(self: *@This(), command: var) !void { - const pos = plugin.Position{ - .split = command.split, - .index = command.index, - }; - + fn chorusCmd(self: *Runner, pos: Position, params: ParamList) !void { var image = try self.getImage(); - try image.runCustomPlugin(@TypeOf(command).plugin_type, pos, command.parameters); + try image.runPlugin("http://plugin.org.uk/swh-plugins/multivoiceChorus", pos, params); } - fn runSingleCommand( - self: *@This(), - cmd: lang.Command, - comptime tag: lang.Command.Tag, + 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 { - 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"), - } + 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 runCommand(self: *@This(), cmd: lang.Command) !void { - switch (cmd.tag) { - .noop => {}, - .load => { - const command = cmd.cast(lang.Command.Load).?; - try self.loadCmd(command.path); + 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(), - .rotate => try self.rotateCmd(cmd), - .runqs => try self.runQSCmd(cmd), + .Quicksave => try self.quicksaveCmd(), + .RunQS => try self.runQSCmd(cmd.args.items[0]), - .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), + .Amp => blk: { + const pos = try cmd.consumePosition(); + try cmd.appendParam(¶ms, "gain"); + try self.ampCmd(pos, params); + }, - .noise => try self.runSingleCommand(cmd, .noise), - .wildnoise => try self.runSingleCommand(cmd, .wildnoise), - .write => try self.runSingleCommand(cmd, .write), - .embed => try self.runSingleCommand(cmd, .embed), - } + .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; + }, + }; } + /// Run a list of commands. pub fn runCommands( self: *Runner, cmds: lang.CommandList, debug_flag: bool, ) !void { - for (cmds.items) |cmd| { - cmd.print(); - try self.runCommand(cmd.*); + 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); } } };