Compare commits

..

8 commits

2 changed files with 199 additions and 160 deletions

View file

@ -51,12 +51,29 @@ pub const CommandType = enum {
Rotate, Rotate,
}; };
pub const Type = enum { pub const NewCommandType = enum {
/// "LV2 Commands" are commands that receive split, index, and then receive /// "LV2 Commands" are commands that receive split, index, and then receive
/// any f64 arguments. /// any f64 arguments.
lv2_command, 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 { pub const NewCommand = struct {
tag: Tag, tag: Tag,
@ -112,6 +129,33 @@ pub const NewCommand = struct {
.amp => Amp, .amp => Amp,
.rflanger => RFlanger, .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"), else => @panic("TODO"),
}; };
} }
@ -149,22 +193,37 @@ pub const NewCommand = struct {
base: NewCommand, base: NewCommand,
}; };
pub const Amp = struct { pub const Amp = LV2Command(
pub const base_tag = Tag.amp; .amp,
base: NewCommand, "http://lv2plug.in/plugins/eg-amp",
split: usize, struct {
index: usize, gain: f32
gain: f32 },
}; );
pub const RFlanger = struct { pub const RFlanger = LV2Command(
pub const base_tag = Tag.rflanger; .rflanger,
base: NewCommand, "http://plugin.org.uk/swh-plugins/retroFlange",
split: usize, struct {
index: usize, delay_depth_avg: f32, law_freq: f32
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 { pub const Command = struct {
@ -276,7 +335,6 @@ pub const KeywordMap = std.StringHashMap(CommandType);
/// A parser. /// A parser.
pub const Lang = struct { pub const Lang = struct {
allocator: *std.mem.Allocator, allocator: *std.mem.Allocator,
keywords: KeywordMap,
has_error: bool = false, has_error: bool = false,
line: usize = 0, line: usize = 0,
@ -284,105 +342,15 @@ pub const Lang = struct {
pub fn init(allocator: *std.mem.Allocator) Lang { pub fn init(allocator: *std.mem.Allocator) Lang {
return Lang{ return Lang{
.allocator = allocator, .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 { pub fn reset(self: *Lang) void {
self.has_error = false; self.has_error = false;
self.line = 0; 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 { 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(fmt, args);
@ -398,28 +366,57 @@ pub const Lang = struct {
) !void { ) !void {
// Based on the command struct fields, we can parse the arguments. // Based on the command struct fields, we can parse the arguments.
var cmd = try self.allocator.create(command_struct); var cmd = try self.allocator.create(command_struct);
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| { // TODO: crash when no arguments are left but we still need
comptime { // arguments...
if (std.mem.eql(u8, cmd_field.name, "base")) {
continue; 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 std.debug.warn("parsing {}, arg of type {} => {}\n", .{
// arguments... @typeName(command_struct),
const arg = tok_it.next().?; @typeName(@TypeOf(argument_value)),
const argument_value = switch (cmd_field.field_type) { argument_value,
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;
}
@field(cmd, cmd_field.name) = argument_value;
} }
cmd.base.tag = command_struct.base_tag; cmd.base.tag = command_struct.base_tag;

View file

@ -374,6 +374,28 @@ pub const Runner = struct {
try image.runPlugin("http://calf.sourceforge.net/plugins/VintageDelay", pos, params); 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( fn newRunCommandSingle(
self: *@This(), self: *@This(),
cmd: lang.NewCommand, cmd: lang.NewCommand,
@ -381,15 +403,65 @@ pub const Runner = struct {
) !void { ) !void {
comptime const typ = lang.NewCommand.tagToType(tag); comptime const typ = lang.NewCommand.tagToType(tag);
const command = cmd.cast(typ).?; 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 { fn newRunCommand(self: *@This(), cmd: lang.NewCommand) !void {
// .load => try self.newRunCommandSingle(cmd, .load),
switch (cmd.tag) { switch (cmd.tag) {
.load => try self.newRunCommandSingle(cmd, .load), .load => {
else => @panic("TODO"), 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(), .Quicksave => try self.quicksaveCmd(),
.RunQS => try self.runQSCmd(cmd.args.items[0]), .RunQS => try self.runQSCmd(cmd.args.items[0]),
.Amp => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "gain");
try self.ampCmd(pos, params);
},
.RFlanger => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "delay_depth_avg");
try cmd.appendParam(&params, "law_freq");
try self.rFlangerCmd(pos, params);
},
.Eq => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "lo");
try cmd.appendParam(&params, "mid");
try cmd.appendParam(&params, "hi");
try self.eqCmd(pos, params);
},
.Phaser => blk: { .Phaser => blk: {
const pos = try cmd.consumePosition(); const pos = try cmd.consumePosition();
@ -796,14 +846,6 @@ pub const Runner = struct {
) !void { ) !void {
for (cmds.items) |cmd| { for (cmds.items) |cmd| {
cmd.print(); 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.*); try self.newRunCommand(cmd.*);
} }
} }