diff --git a/src/lang.zig b/src/lang.zig index c45aff8..44566e5 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -51,12 +51,29 @@ pub const CommandType = enum { Rotate, }; -pub const Type = enum { +pub const NewCommandType = enum { /// "LV2 Commands" are commands that receive split, index, and then receive /// any f64 arguments. lv2_command, }; +fn LV2Command( + comptime tag: NewCommand.Tag, + comptime plugin_url: []const u8, + comptime LV2Parameters: type, +) type { + return struct { + pub const base_tag = tag; + pub const command_type = NewCommandType.lv2_command; + pub const lv2_url = plugin_url; + + base: NewCommand, + split: usize, + index: usize, + parameters: LV2Parameters, + }; +} + pub const NewCommand = struct { tag: Tag, @@ -112,6 +129,33 @@ pub const NewCommand = struct { .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, + else => @panic("TODO"), }; } @@ -149,22 +193,37 @@ pub const NewCommand = struct { base: NewCommand, }; - pub const Amp = struct { - pub const base_tag = Tag.amp; - base: NewCommand, - split: usize, - index: usize, - gain: f32 - }; + pub const Amp = LV2Command( + .amp, + "http://lv2plug.in/plugins/eg-amp", + struct { + gain: f32 + }, + ); - pub const RFlanger = struct { - pub const base_tag = Tag.rflanger; - base: NewCommand, - split: usize, - index: usize, - delay_depth_avg: f32, - law_freq: 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( + .rflanger, + "http://plugin.org.uk/swh-plugins/lfoPhaser", + struct { + lfo_rate: f32, lfo_depth: f32, fb: f32, spread: f32 + }, + ); }; pub const Command = struct { @@ -276,7 +335,6 @@ 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, @@ -284,105 +342,15 @@ 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 { - inline for (@typeInfo(NewCommand).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); - } - } - - _ = try self.keywords.put(&lowered_command_name, @field(CommandType, struct_name)); - } - } - - 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(fmt, args); @@ -398,28 +366,57 @@ pub const Lang = struct { ) !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 => false, + else => command_struct.command_type == .lv2_command, + }; - inline for (@typeInfo(command_struct).Struct.fields) |cmd_field| { - comptime { - if (std.mem.eql(u8, cmd_field.name, "base")) { - continue; - } + // TODO: crash when no arguments are left but we still need + // arguments... + + if (is_lv2_command) { + cmd.split = try std.fmt.parseInt(usize, tok_it.next().?, 10); + cmd.index = try std.fmt.parseInt(usize, tok_it.next().?, 10); + + inline for (@typeInfo(@TypeOf(cmd.parameters)).Struct.fields) |cmd_field| { + const arg = tok_it.next().?; + const argument_value = switch (cmd_field.field_type) { + f32 => try std.fmt.parseFloat(f32, arg), + else => @compileError("LV2 parameter struct can only have f32 fields"), + }; + + std.debug.warn("parsing {}, arg of type {} => {}\n", .{ + @typeName(command_struct), + @typeName(@TypeOf(argument_value)), + argument_value, + }); + + @field(cmd.parameters, cmd_field.name) = argument_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), + []const u8 => try self.allocator.dupe(u8, arg), + else => @panic("Invalid parameter type (" ++ @typeName(cmd_field.field_type) ++ ") left on command struct " ++ @typeName(command_struct) ++ "."), + }; - // TODO: crash when no arguments are left but we still need - // arguments... - 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), - []const u8 => try self.allocator.dupe(u8, arg), - else => @panic("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, + }); - std.debug.warn("parsing {}, arg of type {} => {}\n", .{ @typeName(command_struct), @typeName(@TypeOf(argument_value)), argument_value }); - - @field(cmd, cmd_field.name) = argument_value; + @field(cmd, cmd_field.name) = argument_value; + } } cmd.base.tag = command_struct.base_tag; diff --git a/src/runner.zig b/src/runner.zig index a27c945..438ce0b 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -374,6 +374,28 @@ pub const Runner = struct { try image.runPlugin("http://calf.sourceforge.net/plugins/VintageDelay", pos, params); } + fn executeLV2Command(self: *@This(), command: var) !void { + const pos = plugin.Position{ + .split = command.split, + .index = command.index, + }; + + var params = ParamList.init(self.allocator); + defer params.deinit(); + + const typ = @TypeOf(command); + + inline for (@typeInfo(@TypeOf(command.parameters)).Struct.fields) |cmd_field| { + try params.append(plugin.Param{ + .sym = cmd_field.name, + .value = @field(command.parameters, cmd_field.name), + }); + } + + var image = try self.getImage(); + try image.runPlugin(typ.lv2_url, pos, params); + } + fn newRunCommandSingle( self: *@This(), cmd: lang.NewCommand, @@ -381,15 +403,65 @@ pub const Runner = struct { ) !void { comptime const typ = lang.NewCommand.tagToType(tag); const command = cmd.cast(typ).?; - std.debug.warn("{} {}\n", .{ command.path.ptr, command.path.len }); + inline for (@typeInfo(typ).Struct.decls) |decl| { + comptime { + if (!std.mem.eql(u8, decl.name, "command_type")) { + continue; + } + } - std.debug.warn("{}\n", .{command}); + const ctype = typ.command_type; + switch (ctype) { + .lv2_command => try self.executeLV2Command(command.*), + else => @panic("TODO support command type"), + } + } } fn newRunCommand(self: *@This(), cmd: lang.NewCommand) !void { + // .load => try self.newRunCommandSingle(cmd, .load), switch (cmd.tag) { - .load => try self.newRunCommandSingle(cmd, .load), - else => @panic("TODO"), + .load => { + const command = cmd.cast(lang.NewCommand.Load).?; + try self.loadCmd(command.path); + }, + .quicksave => { + try self.quicksaveCmd(); + }, + .amp => try self.newRunCommandSingle(cmd, .amp), + + .rflanger => try self.newRunCommandSingle(cmd, .rflanger), + .eq => try self.newRunCommandSingle(cmd, .eq), + .phaser => try self.newRunCommandSingle(cmd, .phaser), + // .mbeq => try self.newRunCommandSingle(cmd, .mbeq), + // .chorus => try self.newRunCommandSingle(cmd, .chorus), + // .pitchscaler => try self.newRunCommandSingle(cmd, .pitchscaler), + // .reverb => try self.newRunCommandSingle(cmd, .reverb), + // .highpass => try self.newRunCommandSingle(cmd, .highpass), + // .delay => try self.newRunCommandSingle(cmd, .delay), + // .vinyl => try self.newRunCommandSingle(cmd, .vinyl), + // .revdelay => try self.newRunCommandSingle(cmd, .revdelay), + // .gate => try self.newRunCommandSingle(cmd, .gate), + // .detune => try self.newRunCommandSingle(cmd, .detune), + // .overdrive => try self.newRunCommandSingle(cmd, .overdrive), + // .degrade => try self.newRunCommandSingle(cmd, .degrade), + // .repsycho => try self.newRunCommandSingle(cmd, .repsycho), + // .talkbox => try self.newRunCommandSingle(cmd, .talkbox), + // .dyncomp => try self.newRunCommandSingle(cmd, .dyncomp), + // .thruzero => try self.newRunCommandSingle(cmd, .thruzero), + // .foverdrive => try self.newRunCommandSingle(cmd, .foverdrive), + // .gverb => try self.newRunCommandSingle(cmd, .gverb), + // .invert => try self.newRunCommandSingle(cmd, .invert), + // .tapedelay => try self.newRunCommandSingle(cmd, .tapedelay), + // .moddelay => try self.newRunCommandSingle(cmd, .moddelay), + // .multichorus => try self.newRunCommandSingle(cmd, .multichorus), + // .saturator => try self.newRunCommandSingle(cmd, .saturator), + // .vintagedelay => try self.newRunCommandSingle(cmd, .vintagedelay), + + else => { + std.debug.warn("TODO support {}\n", .{@tagName(cmd.tag)}); + @panic("TODO support tag"); + }, } } @@ -412,28 +484,6 @@ pub const Runner = struct { .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(); @@ -796,14 +846,6 @@ pub const Runner = struct { ) !void { for (cmds.items) |cmd| { cmd.print(); - - switch (cmd.tag) { - .load => { - const proper_cmd = cmd.cast(lang.NewCommand.Load).?; - std.debug.warn("got load! {}\n", .{proper_cmd}); - }, - else => @panic("TODO"), - } try self.newRunCommand(cmd.*); } }