diff --git a/src/lang.zig b/src/lang.zig index 44566e5..c45aff8 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -51,29 +51,12 @@ pub const CommandType = enum { Rotate, }; -pub const NewCommandType = enum { +pub const Type = 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, @@ -129,33 +112,6 @@ 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"), }; } @@ -193,37 +149,22 @@ pub const NewCommand = struct { base: NewCommand, }; - pub const Amp = LV2Command( - .amp, - "http://lv2plug.in/plugins/eg-amp", - struct { - gain: f32 - }, - ); + pub const Amp = struct { + pub const base_tag = Tag.amp; + base: NewCommand, + split: usize, + index: usize, + 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( - .rflanger, - "http://plugin.org.uk/swh-plugins/lfoPhaser", - struct { - lfo_rate: f32, lfo_depth: f32, fb: f32, spread: 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 Command = struct { @@ -335,6 +276,7 @@ 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, @@ -342,15 +284,105 @@ 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 { + 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); @@ -366,57 +398,28 @@ 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, - }; - // 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; - } + 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) ++ "."), - }; - - std.debug.warn("parsing {}, arg of type {} => {}\n", .{ - @typeName(command_struct), - @typeName(@TypeOf(argument_value)), - argument_value, - }); - - @field(cmd, cmd_field.name) = argument_value; } + + // 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 }); + + @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 438ce0b..a27c945 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -374,28 +374,6 @@ 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, @@ -403,65 +381,15 @@ pub const Runner = struct { ) !void { comptime const typ = lang.NewCommand.tagToType(tag); const command = cmd.cast(typ).?; - inline for (@typeInfo(typ).Struct.decls) |decl| { - comptime { - if (!std.mem.eql(u8, decl.name, "command_type")) { - continue; - } - } + std.debug.warn("{} {}\n", .{ command.path.ptr, command.path.len }); - const ctype = typ.command_type; - switch (ctype) { - .lv2_command => try self.executeLV2Command(command.*), - else => @panic("TODO support command type"), - } - } + std.debug.warn("{}\n", .{command}); } fn newRunCommand(self: *@This(), cmd: lang.NewCommand) !void { - // .load => try self.newRunCommandSingle(cmd, .load), switch (cmd.tag) { - .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"); - }, + .load => try self.newRunCommandSingle(cmd, .load), + else => @panic("TODO"), } } @@ -484,6 +412,28 @@ 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(); @@ -846,6 +796,14 @@ 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.*); } }