Merge pull request 'Use comptime for fully declarative LV2 and Custom commands' (#14) from declarative-commands into master

Reviewed-on: #14
This commit is contained in:
luna 2020-06-02 21:37:46 +00:00
commit 18924adac4
6 changed files with 867 additions and 944 deletions

View file

@ -16,15 +16,12 @@ pub const RandomNoise = struct {
pub fn init(
allocator: *std.mem.Allocator,
params: *plugins.ParamMap,
params: var,
) ?RandomNoise {
const seed = @floatToInt(u64, params.get("seed").?.value);
const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value);
var r = std.rand.DefaultPrng.init(params.seed);
var r = std.rand.DefaultPrng.init(seed);
if (fillbytes > 0) {
var rand_buf = allocator.alloc(f32, fillbytes) catch return null;
if (params.fill_bytes > 0) {
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
for (rand_buf) |_, idx| {
rand_buf[idx] = r.random.float(f32);
@ -67,15 +64,12 @@ pub const WildNoise = struct {
pub fn init(
allocator: *std.mem.Allocator,
params: *plugins.ParamMap,
params: var,
) ?WildNoise {
const seed = @floatToInt(u64, params.get("seed").?.value);
const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value);
var r = std.rand.DefaultPrng.init(params.seed);
var r = std.rand.DefaultPrng.init(seed);
if (fillbytes > 0) {
var rand_buf = allocator.alloc(f32, fillbytes) catch return null;
if (params.fill_bytes > 0) {
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
for (rand_buf) |_, idx| {
rand_buf[idx] = @intToFloat(f32, r.random.int(u1));
@ -118,11 +112,10 @@ pub const Write = struct {
pub fn init(
allocator: *std.mem.Allocator,
params: *plugins.ParamMap,
params: var,
) Write {
const data = params.get("data").?;
return Write{
.data = data.value,
.data = params.data,
};
}
@ -140,10 +133,10 @@ pub const Embed = struct {
sndfile: *c.SNDFILE = undefined,
buf: []f32 = undefined,
pub fn init(allocator: *std.mem.Allocator, filepath: []const u8) @This() {
pub fn init(allocator: *std.mem.Allocator, params: var) @This() {
return Embed{
.allocator = allocator,
.filepath = filepath,
.filepath = params.path,
};
}

View file

@ -75,18 +75,17 @@ pub fn sseek(file: *c.SNDFILE, offset: usize) void {
/// Caller owns the returned memory.
pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 {
const template_start = "/temp/temp_";
const template = "/tmp/temp_XXXXXXXXXXX";
const template = "/tmp/temp_XXXXXXXXXXXXXXXXXXXXX";
var nam = try allocator.alloc(u8, template.len);
std.mem.copy(u8, nam, template);
const seed = @bitCast(u64, std.time.timestamp());
const seed = @truncate(u64, @bitCast(u128, std.time.nanoTimestamp()));
var r = std.rand.DefaultPrng.init(seed);
var fill = nam[template_start.len..nam.len];
var i: usize = 0;
while (i < 100) : (i += 1) {
// generate a random uppercase letter, that is, 65 + random number.
for (fill) |_, f_idx| {
var idx = @intCast(u8, r.random.uintLessThan(u5, 24));
@ -95,12 +94,16 @@ pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 {
}
// if we fail to access it, we assume it doesn't exist and return it.
_ = std.fs.cwd().openFile(nam, .{ .read = true, .write = false }) catch |err| {
if (err == error.FileNotFound) {
return nam;
}
var tmp_file: std.fs.File = std.fs.cwd().openFile(
nam,
.{ .read = true, .write = false },
) catch |err| blk: {
if (err == error.FileNotFound) return nam else continue;
};
// if we actually found someone, close the handle so that we don't
// get EMFILE later on.
tmp_file.close();
}
return error.TempGenFail;
@ -422,8 +425,7 @@ pub const Image = struct {
self: *Image,
comptime Plugin: type,
position: plugins.Position,
comptime ExtraType: type,
extra: ExtraType,
extra: var,
) !void {
var plugin_opt: ?Plugin = Plugin.init(self.allocator, extra);
if (plugin_opt == null) {

View file

@ -1,166 +1,486 @@
const std = @import("std");
const plugin = @import("plugin.zig");
const custom = @import("custom.zig");
pub const ParseError = error{
OutOfMemory,
ArgRequired,
ParseFail,
};
pub const ParseError = error{ParseFail};
pub const CommandType = enum {
Noop,
Load,
Quicksave,
RunQS,
/// "LV2 Commands" are commands that receive split, index, and then receive
/// any f64 arguments.
lv2_command,
Amp,
RFlanger,
Eq,
Phaser,
Mbeq,
Chorus,
PitchScaler,
Reverb,
Highpass,
Delay,
Vinyl,
RevDelay,
Gate,
Detune,
Overdrive,
Degrade,
RePsycho,
TalkBox,
DynComp,
ThruZero,
Foverdrive,
Gverb,
Invert,
TapeDelay,
ModDelay,
MultiChorus,
Saturator,
VintageDelay,
Noise,
WildNoise,
Write,
Embed,
Rotate,
custom_command,
};
fn LV2Command(
comptime tag: Command.Tag,
comptime plugin_url: []const u8,
comptime LV2Parameters: type,
) type {
return struct {
pub const base_tag = tag;
pub const command_type = CommandType.lv2_command;
pub const lv2_url = plugin_url;
base: Command,
split: usize,
index: usize,
parameters: LV2Parameters,
};
}
fn CustomCommand(
comptime tag: Command.Tag,
comptime Plugin: type,
comptime PluginParameters: type,
) type {
return struct {
pub const base_tag = tag;
pub const command_type = CommandType.custom_command;
pub const plugin_type = Plugin;
base: Command,
split: usize,
index: usize,
parameters: PluginParameters,
};
}
pub const Command = struct {
command: CommandType,
args: ArgList,
cur_idx: usize = 0,
tag: Tag,
pub fn print(self: Command) void {
std.debug.warn("cmd:{}\n", .{self.command});
}
pub const Tag = enum {
noop,
load,
quicksave,
runqs,
pub fn argAt(self: Command, idx: usize) ![]const u8 {
std.debug.warn("{} {}", .{ idx, self.args.items.len });
amp,
rflanger,
eq,
phaser,
mbeq,
chorus,
pitchscaler,
reverb,
highpass,
delay,
vinyl,
revdelay,
gate,
detune,
overdrive,
degrade,
repsycho,
talkbox,
dyncomp,
thruzero,
foverdrive,
gverb,
invert,
tapedelay,
moddelay,
multichorus,
saturator,
vintagedelay,
if (idx > (self.args.items.len - 1)) {
std.debug.warn("Expected argument at index {}\n", .{idx});
return ParseError.ArgRequired;
}
noise,
wildnoise,
write,
embed,
return self.args.items[idx];
}
rotate,
};
pub fn usizeArgAt(self: Command, idx: usize) !usize {
var arg = try self.argAt(idx);
return try std.fmt.parseInt(usize, arg, 10);
}
pub fn tagToType(tag: Tag) type {
return switch (tag) {
.noop => Noop,
.load => Load,
.quicksave => Quicksave,
.runqs => RunQS,
pub fn consumePosition(self: *Command) !plugin.Position {
self.cur_idx = 2;
return plugin.Position{
.split = try self.usizeArgAt(0),
.index = try self.usizeArgAt(1),
.amp => Amp,
.rflanger => RFlanger,
.eq => Eq,
.phaser => Phaser,
.mbeq => Mbeq,
.chorus => Chorus,
.pitchscaler => Pitchscaler,
.reverb => Reverb,
.highpass => Highpass,
.delay => Delay,
.vinyl => Vinyl,
.revdelay => Revdelay,
.gate => Gate,
.detune => Detune,
.overdrive => Overdrive,
.degrade => Degrade,
.repsycho => Repsycho,
.talkbox => Talkbox,
.dyncomp => Dyncomp,
.thruzero => Thruzero,
.foverdrive => Foverdrive,
.gverb => Gverb,
.invert => Invert,
.tapedelay => Tapedelay,
.moddelay => Moddelay,
.multichorus => Multichorus,
.saturator => Saturator,
.vintagedelay => Vintagedelay,
.noise => Noise,
.wildnoise => Wildnoise,
.write => Write,
.embed => Embed,
.rotate => Rotate,
else => @panic("TODO"),
};
}
pub fn intArgAt(self: Command, idx: usize) !i32 {
var arg = try self.argAt(idx);
return try std.fmt.parseInt(i32, arg, 10);
pub fn cast(base: *const @This(), comptime T: type) ?*const T {
if (base.tag != T.base_tag)
return null;
return @fieldParentPtr(T, "base", base);
}
pub fn floatArgAt(self: Command, idx: usize) !f32 {
var arg = try self.argAt(idx);
return try std.fmt.parseFloat(f32, arg);
pub fn print(base: *const @This()) void {
std.debug.warn("tag: {}\n", .{base.tag});
}
pub fn floatArgMany(
self: Command,
allocator: *std.mem.Allocator,
start_index: usize,
elements: usize,
default: f32,
) ![]const f32 {
var i: usize = start_index;
var arr = std.ArrayList(f32).init(allocator);
pub const Noop = struct {
pub const base_tag = Tag.noop;
base: Command,
};
while (i < elements) : (i += 1) {
var value: f32 = self.floatArgAt(i) catch |err| blk: {
std.debug.warn("\tdoing default on arg {}\n", .{i});
break :blk default;
};
pub const Load = struct {
pub const base_tag = Tag.load;
base: Command,
path: []u8,
};
try arr.append(value);
}
pub const Quicksave = struct {
pub const base_tag = Tag.quicksave;
base: Command,
};
return arr.items;
}
pub const RunQS = struct {
pub const base_tag = Tag.runqs;
base: Command,
program: []const u8,
};
pub fn appendParam(
self: *Command,
params: *plugin.ParamList,
symbol: []const u8,
) !void {
var val = try self.floatArgAt(self.cur_idx);
self.cur_idx += 1;
pub const Noise = CustomCommand(Tag.noise, custom.RandomNoise, struct {
seed: u64,
fill_bytes: usize,
});
try params.append(plugin.Param{
.sym = symbol,
.value = val,
});
}
pub const Wildnoise = CustomCommand(Tag.wildnoise, custom.WildNoise, struct {
seed: u64,
fill_bytes: usize,
});
pub fn appendParamMap(
self: *Command,
map: *plugin.ParamMap,
symbol: []const u8,
) !void {
var val = try self.floatArgAt(self.cur_idx);
self.cur_idx += 1;
_ = try map.put(symbol, val);
}
pub const Write = CustomCommand(Tag.write, custom.Write, struct {
data: f32,
});
pub fn copy(self: Command, allocator: *std.mem.Allocator) !*Command {
var cmd = try allocator.create(Command);
cmd.* = Command{
.command = self.command,
.args = self.args,
.cur_idx = self.cur_idx,
};
pub const Embed = CustomCommand(Tag.write, custom.Embed, struct {
path: []const u8,
});
return cmd;
}
pub const Rotate = struct {
pub const base_tag = Tag.rotate;
base: Command,
deg: f32,
bgfill: []const u8,
};
pub const Amp = LV2Command(
.amp,
"http://lv2plug.in/plugins/eg-amp",
struct {
gain: f32
},
);
pub const RFlanger = LV2Command(
.rflanger,
"http://plugin.org.uk/swh-plugins/retroFlange",
struct {
delay_depth_avg: f32, law_freq: f32
},
);
pub const Eq = LV2Command(
.rflanger,
"http://plugin.org.uk/swh-plugins/dj_eq_mono",
struct {
lo: f32, mid: f32, hi: f32
},
);
pub const Phaser = LV2Command(
.phaser,
"http://plugin.org.uk/swh-plugins/lfoPhaser",
struct {
lfo_rate: f32, lfo_depth: f32, fb: f32, spread: f32
},
);
pub const Mbeq = LV2Command(
.mbeq,
"http://plugin.org.uk/swh-plugins/mbeq",
struct {
band_1: f32,
band_2: f32,
band_3: f32,
band_4: f32,
band_5: f32,
band_6: f32,
band_7: f32,
band_8: f32,
band_9: f32,
band_10: f32,
band_11: f32,
band_12: f32,
band_13: f32,
band_14: f32,
band_15: f32,
},
);
pub const Chorus = LV2Command(
.chorus,
"http://plugin.org.uk/swh-plugins/multivoiceChorus",
struct {
voices: f32,
delay_base: f32,
voice_spread: f32,
detune: f32,
law_freq: f32,
attendb: f32,
},
);
pub const Pitchscaler = LV2Command(
.pitchscaler,
"http://plugin.org.uk/swh-plugins/pitchScaleHQ",
struct { mult: f32 },
);
pub const Reverb = LV2Command(
.reverb,
"http://invadarecords.com/plugins/lv2/erreverb/mono",
struct {
roomLength: f32,
roomWidth: f32,
roomHeight: f32,
sourceLR: f32,
sourceFB: f32,
listLR: f32,
listFB: f32,
hpf: f32,
warmth: f32,
diffusion: f32,
},
);
pub const Highpass = LV2Command(.highpass, "http://invadarecords.com/plugins/lv2/filter/hpf/mono", struct {
freq: f32,
gain: f32,
noClip: f32,
});
pub const Delay = LV2Command(.delay, "http://plugin.org.uk/swh-plugins/delayorama", struct {
seed: f32,
gain: f32,
feedback_pc: f32,
tap_count: f32,
first_delay: f32,
delay_range: f32,
delay_scale: f32,
delay_rand_pc: f32,
gain_scale: f32,
wet: f32,
});
pub const Vinyl = LV2Command(.vinyl, "http://plugin.org.uk/swh-plugins/vynil", struct {
year: f32,
rpm: f32,
warp: f32,
click: f32,
wear: f32,
});
pub const Revdelay = LV2Command(.revdelay, "http://plugin.org.uk/swh-plugins/revdelay", struct {
delay_time: f32,
dry_level: f32,
wet_level: f32,
feedback: f32,
xfade_samp: f32,
});
// pub const Noise= LV2Command(.,,struct{});
pub const Gate = LV2Command(.gate, "http://hippie.lt/lv2/gate", struct {
@"switch": f32,
threshold: f32,
attack: f32,
hold: f32,
decay: f32,
gaterange: f32,
});
pub const Detune = LV2Command(.detune, "http://drobilla.net/plugins/mda/Detune", struct {
detune: f32,
mix: f32,
output: f32,
latency: f32,
});
pub const Overdrive = LV2Command(.overdrive, "http://drobilla.net/plugins/mda/Overdrive", struct {
drive: f32,
muffle: f32,
output: f32,
});
pub const Degrade = LV2Command(.degrade, "http://drobilla.net/plugins/mda/Degrade", struct {
headroom: f32,
quant: f32,
rate: f32,
post_filt: f32,
non_lin: f32,
output: f32,
});
pub const Repsycho = LV2Command(.repsycho, "http://drobilla.net/plugins/mda/RePsycho", struct {
tune: f32,
fine: f32,
decay: f32,
thresh: f32,
hold: f32,
mix: f32,
quality: f32,
});
pub const Talkbox = LV2Command(.talkbox, "http://drobilla.net/plugins/mda/TalkBox", struct {
wet: f32,
dry: f32,
carrier: f32,
quality: f32,
});
pub const Dyncomp = LV2Command(.dyncomp, "http://gareus.org/oss/lv2/darc#mono", struct {
enable: f32,
hold: f32,
inputgain: f32,
threshold: f32,
Ratio: f32,
attack: f32,
release: f32,
gain_min: f32,
gain_max: f32,
rms: f32,
});
pub const Foverdrive = LV2Command(.foverdrive, "http://plugin.org.uk/swh-plugins/foverdrive", struct {
drive: f32,
});
pub const Thruzero = LV2Command(.thruzero, "http://drobilla.net/plugins/mda/ThruZero", struct {
rate: f32, mix: f32, feedback: f32, depth_mod: f32
});
pub const Gverb = LV2Command(.gverb, "http://plugin.org.uk/swh-plugins/gverb", struct {
roomsize: f32,
revtime: f32,
damping: f32,
inputbandwidth: f32,
drylevel: f32,
earlylevel: f32,
taillevel: f32,
});
pub const Invert = LV2Command(.invert, "http://plugin.org.uk/swh-plugins/inv", struct {});
pub const Tapedelay = LV2Command(.tapedelay, "http://plugin.org.uk/swh-plugins/tapeDelay", struct {
speed: f32,
da_db: f32,
t1d: f32,
t1a_db: f32,
t2d: f32,
t2a_db: f32,
t3d: f32,
t3a_db: f32,
t4d: f32,
t4a_db: f32,
});
pub const Moddelay = LV2Command(
.moddelay,
"http://plugin.org.uk/swh-plugins/modDelay",
struct {
base: f32,
},
);
pub const Multichorus = LV2Command(.multichorus, "http://calf.sourceforge.net/plugins/MultiChorus", struct {
min_delay: f32,
mod_depth: f32,
mod_rate: f32,
stereo: f32,
voices: f32,
vphase: f32,
amount: f32,
dry: f32,
freq: f32,
freq2: f32,
q: f32,
overlap: f32,
level_in: f32,
level_out: f32,
lfo: f32,
});
pub const Saturator = LV2Command(.saturator, "http://calf.sourceforge.net/plugins/Saturator", struct {
bypass: f32,
level_in: f32,
level_out: f32,
mix: f32,
drive: f32,
blend: f32,
lp_pre_freq: f32,
hp_pre_freq: f32,
lp_post_freq: f32,
hp_post_freq: f32,
p_freq: f32,
p_level: f32,
p_q: f32,
pre: f32,
post: f32,
});
pub const Vintagedelay = LV2Command(.vintagedelay, "http://calf.sourceforge.net/plugins/VintageDelay", struct {
level_in: f32,
level_out: f32,
subdiv: f32,
time_l: f32,
time_r: f32,
feedback: f32,
amount: f32,
mix_mode: f32,
medium: f32,
dry: f32,
width: f32,
fragmentation: f32,
pbeats: f32,
pfrag: f32,
timing: f32,
bpm: f32,
ms: f32,
hz: f32,
bpm_host: f32,
});
};
pub const CommandList = std.ArrayList(Command);
pub const ArgList = std.ArrayList([]const u8);
pub const KeywordMap = std.StringHashMap(CommandType);
pub const CommandList = std.ArrayList(*Command);
/// A parser.
pub const Lang = struct {
allocator: *std.mem.Allocator,
keywords: KeywordMap,
has_error: bool = false,
line: usize = 0,
@ -168,137 +488,108 @@ pub const Lang = struct {
pub fn init(allocator: *std.mem.Allocator) Lang {
return Lang{
.allocator = allocator,
.keywords = KeywordMap.init(allocator),
};
}
pub fn deinit(self: *Lang) void {
self.keywords.deinit();
}
pub fn deinit(self: *Lang) void {}
pub fn reset(self: *Lang) void {
self.has_error = false;
self.line = 0;
}
fn fillKeywords(self: *Lang) !void {
_ = try self.keywords.put("noop", .Noop);
_ = try self.keywords.put("load", .Load);
_ = try self.keywords.put("quicksave", .Quicksave);
_ = try self.keywords.put("runqs", .RunQS);
_ = try self.keywords.put("amp", .Amp);
_ = try self.keywords.put("rflanger", .RFlanger);
_ = try self.keywords.put("eq", .Eq);
_ = try self.keywords.put("mbeq", .Mbeq);
_ = try self.keywords.put("phaser", .Phaser);
_ = try self.keywords.put("chorus", .Chorus);
_ = try self.keywords.put("pitchscaler", .PitchScaler);
_ = try self.keywords.put("reverb", .Reverb);
_ = try self.keywords.put("highpass", .Highpass);
_ = try self.keywords.put("delay", .Delay);
_ = try self.keywords.put("vinyl", .Vinyl);
_ = try self.keywords.put("revdelay", .RevDelay);
_ = try self.keywords.put("gate", .Gate);
_ = try self.keywords.put("detune", .Detune);
_ = try self.keywords.put("overdrive", .Overdrive);
_ = try self.keywords.put("talkbox", .TalkBox);
_ = try self.keywords.put("thruzero", .ThruZero);
_ = try self.keywords.put("foverdrive", .Foverdrive);
_ = try self.keywords.put("gverb", .Gverb);
_ = try self.keywords.put("invert", .Invert);
_ = try self.keywords.put("tapedelay", .TapeDelay);
_ = try self.keywords.put("moddelay", .ModDelay);
_ = try self.keywords.put("multichorus", .MultiChorus);
_ = try self.keywords.put("saturator", .Saturator);
_ = try self.keywords.put("vintagedelay", .VintageDelay);
// custom implementations (not lv2)
_ = try self.keywords.put("noise", .Noise);
_ = try self.keywords.put("wildnoise", .WildNoise);
_ = try self.keywords.put("write", .Write);
_ = try self.keywords.put("embed", .Embed);
_ = try self.keywords.put("degrade", .Degrade);
_ = try self.keywords.put("repsycho", .RePsycho);
_ = try self.keywords.put("dyncomp", .RePsycho);
// even more custom
_ = try self.keywords.put("rotate", .Rotate);
}
pub fn getCommand(self: *Lang, stmt: []const u8) ?CommandType {
var kv_opt = self.keywords.get(stmt);
if (kv_opt) |kv| {
return kv.value;
} else {
return null;
}
}
fn expectAny(self: *Lang, count: usize, args: ArgList) !void {
if (args.items.len != count) {
self.doError("expected {} arguments, found {}", .{ count, args.items.len });
return error.ArgRequired;
}
}
fn expectSingle(self: *Lang, args: ArgList) !void {
return try self.expectAny(1, args);
}
fn expectFloat(self: *Lang, count: usize, args: ArgList) !void {
var i: usize = 0;
if (args.items.len != count) {
self.doError("expected {} arguments, found {}", .{ count, args.items.len });
return error.ArgRequired;
}
while (i < count) : (i += 1) {
var arg = args.items[i];
_ = std.fmt.parseFloat(f32, arg) catch |err| {
std.debug.warn("failed to parse f32: {}\n", .{err});
return error.FloatParseFail;
};
}
}
fn validateCommand(self: *Lang, cmd: Command) !void {
switch (cmd.command) {
.Quicksave, .Noop => {},
.Load, .RunQS => try self.expectSingle(cmd.args),
.Amp => try self.expectFloat(3, cmd.args),
.RFlanger => try self.expectFloat(4, cmd.args),
.Eq => try self.expectFloat(5, cmd.args),
.Phaser => try self.expectFloat(6, cmd.args),
.Mbeq => try self.expectFloat(17, cmd.args),
.Chorus => try self.expectFloat(8, cmd.args),
.PitchScaler => try self.expectFloat(3, cmd.args),
.Reverb => try self.expectFloat(12, cmd.args),
.Highpass => try self.expectFloat(5, cmd.args),
.Delay => try self.expectFloat(12, cmd.args),
.Vinyl => try self.expectFloat(7, cmd.args),
.RevDelay => try self.expectFloat(7, cmd.args),
.Noise => try self.expectFloat(4, cmd.args),
.WildNoise => try self.expectFloat(4, cmd.args),
.Write => try self.expectFloat(3, cmd.args),
.Rotate => try self.expectAny(2, cmd.args),
.Embed => try self.expectAny(3, cmd.args),
else => std.debug.warn("WARN unchecked command {}\n", .{cmd.command}),
}
}
fn doError(self: *Lang, comptime fmt: []const u8, args: var) void {
std.debug.warn("error at line {}: ", .{self.line});
std.debug.warn("ERROR! at line {}: ", .{self.line});
std.debug.warn(fmt, args);
std.debug.warn("\n", .{});
self.has_error = true;
}
pub fn parse(self: *Lang, data: []const u8) ParseError!CommandList {
fn parseCommandArguments(
self: *@This(),
comptime command_struct: type,
tok_it: *std.mem.TokenIterator,
commands: *CommandList,
) !void {
// Based on the command struct fields, we can parse the arguments.
var cmd = try self.allocator.create(command_struct);
const is_lv2_command = switch (command_struct.base_tag) {
.noop, .load, .quicksave, .runqs, .rotate => false,
else => true,
};
// TODO: crash when no arguments are left but we still need
// arguments...
if (is_lv2_command) {
const split = tok_it.next();
if (split == null) {
self.doError("Expected split parameter, got EOL", .{});
return;
}
const index = tok_it.next();
if (index == null) {
self.doError("Expected index parameter, got EOL", .{});
return;
}
cmd.split = try std.fmt.parseInt(usize, split.?, 10);
cmd.index = try std.fmt.parseInt(usize, index.?, 10);
inline for (@typeInfo(@TypeOf(cmd.parameters)).Struct.fields) |cmd_field| {
const maybe_arg = tok_it.next();
if (maybe_arg == null) {
self.doError("Expected parameter for {}, got nothing", .{cmd_field.name});
return;
}
const arg = maybe_arg.?;
const arg_value = switch (cmd_field.field_type) {
f32 => try std.fmt.parseFloat(f32, arg),
u64 => try std.fmt.parseInt(u64, arg, 10),
usize => try std.fmt.parseInt(usize, arg, 10),
[]const u8 => try self.allocator.dupe(u8, arg),
else => @compileError("parameter struct has unsupported type " ++ @typeName(cmd_field.field_type)),
};
@field(cmd.parameters, cmd_field.name) = arg_value;
}
} else {
inline for (@typeInfo(command_struct).Struct.fields) |cmd_field| {
comptime {
if (std.mem.eql(u8, cmd_field.name, "base")) {
continue;
}
}
const arg = tok_it.next().?;
const argument_value = switch (cmd_field.field_type) {
usize => try std.fmt.parseInt(usize, arg, 10),
i32 => try std.fmt.parseInt(i32, arg, 10),
f32 => try std.fmt.parseFloat(f32, arg),
[]u8 => try self.allocator.dupe(u8, arg),
[]const u8 => try self.allocator.dupe(u8, arg),
else => @compileError("Invalid parameter type (" ++ @typeName(cmd_field.field_type) ++ ") left on command struct " ++ @typeName(command_struct) ++ "."),
};
std.debug.warn("parsing {}, arg of type {} => {}\n", .{
@typeName(command_struct),
@typeName(@TypeOf(argument_value)),
argument_value,
});
@field(cmd, cmd_field.name) = argument_value;
}
}
cmd.base.tag = command_struct.base_tag;
const command = cmd.base.cast(command_struct).?;
std.debug.warn("cmd: {}\n", .{command});
try commands.append(&cmd.base);
}
pub fn parse(self: *Lang, data: []const u8) !CommandList {
var splitted_it = std.mem.split(data, ";");
try self.fillKeywords();
var cmds = CommandList.init(self.allocator);
while (splitted_it.next()) |stmt_orig| {
@ -309,7 +600,7 @@ pub const Lang = struct {
if (stmt.len == 0) continue;
if (std.mem.startsWith(u8, stmt, "#")) continue;
// TODO better tokenizer instead of just tokenize(" ");
// TODO better tokenizer instead of just tokenize(" ")...maybe????
var tok_it = std.mem.tokenize(stmt, " ");
var cmd_opt = tok_it.next();
@ -317,32 +608,45 @@ pub const Lang = struct {
self.doError("No command given", .{});
continue;
}
var command = cmd_opt.?;
const command_string = cmd_opt.?;
var ctype_opt = self.getCommand(command);
var ctype: CommandType = undefined;
if (ctype_opt) |ctype_val| {
ctype = ctype_val;
} else {
self.doError("Unknown command '{}' ({})", .{ command, command.len });
continue;
var found: bool = false;
inline for (@typeInfo(Command).Struct.decls) |cmd_struct_decl| {
switch (cmd_struct_decl.data) {
.Type => |typ| switch (@typeInfo(typ)) {
.Struct => {},
else => continue,
},
else => continue,
}
const struct_name = cmd_struct_decl.name;
comptime var lowered_command_name = [_]u8{0} ** struct_name.len;
comptime {
for (struct_name) |c, i| {
lowered_command_name[i] = std.ascii.toLower(c);
}
}
// if we have a match, we know the proper struct type
// to use. this actually works compared to storing command_struct
// in a variable because then that variable must be comptime.
// the drawback of this approach is that our emitted code is basically linear
// because we don't use the hashmap anymore. maybe #5359 can help.
if (std.mem.eql(u8, &lowered_command_name, command_string)) {
found = true;
const cmd_struct_type = cmd_struct_decl.data.Type;
try self.parseCommandArguments(cmd_struct_type, &tok_it, &cmds);
}
}
var args = ArgList.init(self.allocator);
errdefer args.deinit();
while (tok_it.next()) |arg| {
try args.append(arg);
}
// construct final Command based on command
var cmd = Command{ .command = ctype, .args = args };
self.validateCommand(cmd) catch |err| {
//self.doError("error validating command '{}': {}", command, err);
if (!found) {
self.doError("Unknown command '{}' ({})", .{ command_string, command_string.len });
continue;
};
try cmds.append(cmd);
}
}
if (self.has_error) return ParseError.ParseFail;

View file

@ -16,12 +16,26 @@ const readline = @cImport({
@cInclude("readline/history.h");
});
fn wrapInCmdList(allocator: *std.mem.Allocator, cmd: langs.Command) !langs.CommandList {
fn wrapInCmdList(allocator: *std.mem.Allocator, cmd: *langs.Command) !langs.CommandList {
var cmds = langs.CommandList.init(allocator);
try cmds.append(cmd);
return cmds;
}
fn copyCommandToHeap(allocator: *std.mem.Allocator, command: langs.Command, comptime tag: langs.Command.Tag) !*langs.Command {
const CommandStruct = langs.Command.tagToType(tag);
const casted = command.cast(CommandStruct).?;
var heap_cmd = try allocator.create(CommandStruct);
@memcpy(
@ptrCast([*]u8, &heap_cmd),
@ptrCast([*]const u8, &casted),
@sizeOf(CommandStruct),
);
return &heap_cmd.base;
}
pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
var stdout_file = std.io.getStdOut();
const stdout = &stdout_file.outStream();
@ -57,13 +71,16 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
} else {
// if there isn't any commands on the file, we load our default
// 'load :0' command
var loadargs = langs.ArgList.init(allocator);
try loadargs.append(":0");
try cmds.append(langs.Command{
.command = .Load,
.args = loadargs,
});
// TODO: deliberate memleak here. we only allocate this
// command once, for the start of the file, so.
var load_cmd = try allocator.create(langs.Command.Load);
std.mem.copy(u8, load_cmd.path, ":0");
load_cmd.base.tag = langs.Command.Tag.load;
// taking address is fine, because load_cmd lives in the lifetime
// of the allocator.
try cmds.append(&load_cmd.base);
}
if (file_read_opt) |file_read| {
@ -101,11 +118,12 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
// run the load command
try runner.runCommands(cmds, true);
var runqs_args = langs.ArgList.init(allocator);
defer runqs_args.deinit();
const wanted_runner: []const u8 = std.os.getenv("SCRITCHER_RUNNER") orelse "ristretto";
try runqs_args.append(wanted_runner);
var runqs_cmd = langs.Command.RunQS{
.base = langs.Command{ .tag = langs.Command.Tag.runqs },
.program = wanted_runner,
};
while (true) {
lang.reset();
@ -121,11 +139,51 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
var line = rd_line[0..std.mem.len(rd_line)];
if (std.mem.eql(u8, line, "push")) {
try cmds.append(current);
const heap_cmd = switch (current.tag) {
.noop => try copyCommandToHeap(allocator, current, .noop),
.load => try copyCommandToHeap(allocator, current, .load),
.quicksave => try copyCommandToHeap(allocator, current, .quicksave),
.runqs => try copyCommandToHeap(allocator, current, .runqs),
.amp => try copyCommandToHeap(allocator, current, .amp),
.rflanger => try copyCommandToHeap(allocator, current, .rflanger),
.eq => try copyCommandToHeap(allocator, current, .eq),
.phaser => try copyCommandToHeap(allocator, current, .phaser),
.mbeq => try copyCommandToHeap(allocator, current, .mbeq),
.chorus => try copyCommandToHeap(allocator, current, .chorus),
.pitchscaler => try copyCommandToHeap(allocator, current, .pitchscaler),
.reverb => try copyCommandToHeap(allocator, current, .reverb),
.highpass => try copyCommandToHeap(allocator, current, .highpass),
.delay => try copyCommandToHeap(allocator, current, .delay),
.vinyl => try copyCommandToHeap(allocator, current, .vinyl),
.revdelay => try copyCommandToHeap(allocator, current, .revdelay),
.gate => try copyCommandToHeap(allocator, current, .gate),
.detune => try copyCommandToHeap(allocator, current, .detune),
.overdrive => try copyCommandToHeap(allocator, current, .overdrive),
.degrade => try copyCommandToHeap(allocator, current, .degrade),
.repsycho => try copyCommandToHeap(allocator, current, .repsycho),
.talkbox => try copyCommandToHeap(allocator, current, .talkbox),
.dyncomp => try copyCommandToHeap(allocator, current, .dyncomp),
.thruzero => try copyCommandToHeap(allocator, current, .thruzero),
.foverdrive => try copyCommandToHeap(allocator, current, .foverdrive),
.gverb => try copyCommandToHeap(allocator, current, .gverb),
.invert => try copyCommandToHeap(allocator, current, .invert),
.tapedelay => try copyCommandToHeap(allocator, current, .tapedelay),
.moddelay => try copyCommandToHeap(allocator, current, .moddelay),
.multichorus => try copyCommandToHeap(allocator, current, .multichorus),
.saturator => try copyCommandToHeap(allocator, current, .saturator),
.vintagedelay => try copyCommandToHeap(allocator, current, .vintagedelay),
.noise => try copyCommandToHeap(allocator, current, .noise),
.wildnoise => try copyCommandToHeap(allocator, current, .wildnoise),
.write => try copyCommandToHeap(allocator, current, .write),
.embed => try copyCommandToHeap(allocator, current, .embed),
.rotate => try copyCommandToHeap(allocator, current, .rotate),
};
try cmds.append(heap_cmd);
// run the current added command to main cmds list
// with the main parent runner
var cmds_wrapped = try wrapInCmdList(allocator, current);
var cmds_wrapped = try wrapInCmdList(allocator, heap_cmd);
defer cmds_wrapped.deinit();
try runner.runCommands(cmds_wrapped, true);
@ -150,7 +208,11 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
std.debug.warn("repl: error while parsing: {}\n", .{err});
continue;
};
current = cmds_parsed.items[0];
// no command? ignore!
if (cmds_parsed.items.len == 0) continue;
current = cmds_parsed.items[0].*;
// by cloning the parent runner, we can iteratively write
// whatever command we want and only commit the good results
@ -158,10 +220,9 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
var runner_clone = try runner.clone();
defer runner_clone.deinit();
try cmds_parsed.append(langs.Command{
.command = .RunQS,
.args = runqs_args,
});
// taking address is fine, because runqs_cmd lives in the lifetime
// of this function.
try cmds_parsed.append(&runqs_cmd.base);
try runner_clone.runCommands(cmds_parsed, true);
_ = try stdout.write("\n");

View file

@ -1,54 +1,89 @@
const std = @import("std");
const langs = @import("lang.zig");
fn printCommandWithParams(stream: var, command: var) !void {
const Parameters = @TypeOf(command.parameters);
try stream.print(" {} {}", .{ command.split, command.index });
inline for (@typeInfo(Parameters).Struct.fields) |field| {
if (field.field_type == f32 or field.field_type == f64) {
try stream.print(" {d}", .{@field(command.parameters, field.name)});
} else {
try stream.print(" {}", .{@field(command.parameters, field.name)});
}
}
}
fn printCommand(stream: var, cmd: *langs.Command, comptime tag: langs.Command.Tag) !void {
const CommandStruct = langs.Command.tagToType(tag);
const casted = cmd.cast(CommandStruct).?;
// TODO move this to Tag method?
const is_typed = switch (tag) {
.noop, .load, .quicksave, .runqs, .rotate => false,
else => true,
};
const ctype = CommandStruct.command_type;
switch (ctype) {
.lv2_command => try printCommandWithParams(stream, casted),
.custom_command => try printCommandWithParams(stream, casted),
else => @panic("TODO support command type"),
}
}
pub fn printList(list: langs.CommandList, stream: var) !void {
for (list.items) |cmd| {
var command = switch (cmd.command) {
.Noop => "noop",
.Load => "load",
.Quicksave => "quicksave",
.RunQS => "runqs",
.Amp => "amp",
.RFlanger => "rflanger",
.Eq => "eq",
.Phaser => "phaser",
.Mbeq => "mbeq",
.Chorus => "chorus",
.PitchScaler => "pitchscaler",
.Reverb => "reverb",
.Highpass => "highpass",
.Delay => "delay",
.Vinyl => "vinyl",
.RevDelay => "revdelay",
.Gate => "gate",
.Detune => "detune",
.Overdrive => "overdrive",
.Degrade => "Degrade",
.RePsycho => "repsycho",
.TalkBox => "talkbox",
.DynComp => "dyncomp",
.ThruZero => "thruzero",
.Foverdrive => "foverdrive",
.Gverb => "gverb",
.Invert => "invert",
.TapeDelay => "tapedelay",
.ModDelay => "moddelay",
.MultiChorus => "multichorus",
.Saturator => "saturator",
.VintageDelay => "vintagedelay",
.Noise => "noise",
.WildNoise => "wildnoise",
.Write => "write",
.Embed => "embed",
.Rotate => "rotate",
};
const command = @tagName(cmd.tag);
try stream.print("{}", .{command});
for (cmd.args.items) |arg| {
try stream.print(" {}", .{arg});
switch (cmd.tag) {
.load => {
const load = cmd.cast(langs.Command.Load).?;
try stream.print(" {}", .{load.path});
},
.runqs => {
const runqs = cmd.cast(langs.Command.RunQS).?;
try stream.print(" {}", .{runqs.program});
},
.noop, .quicksave => {},
.rotate => {
const rotate = cmd.cast(langs.Command.Rotate).?;
try stream.print(" {d} {}", .{ rotate.deg, rotate.bgfill });
},
.amp => try printCommand(stream, cmd, .amp),
.rflanger => try printCommand(stream, cmd, .rflanger),
.eq => try printCommand(stream, cmd, .eq),
.phaser => try printCommand(stream, cmd, .phaser),
.mbeq => try printCommand(stream, cmd, .mbeq),
.chorus => try printCommand(stream, cmd, .chorus),
.pitchscaler => try printCommand(stream, cmd, .pitchscaler),
.reverb => try printCommand(stream, cmd, .reverb),
.highpass => try printCommand(stream, cmd, .highpass),
.delay => try printCommand(stream, cmd, .delay),
.vinyl => try printCommand(stream, cmd, .vinyl),
.revdelay => try printCommand(stream, cmd, .revdelay),
.gate => try printCommand(stream, cmd, .gate),
.detune => try printCommand(stream, cmd, .detune),
.overdrive => try printCommand(stream, cmd, .overdrive),
.degrade => try printCommand(stream, cmd, .degrade),
.repsycho => try printCommand(stream, cmd, .repsycho),
.talkbox => try printCommand(stream, cmd, .talkbox),
.dyncomp => try printCommand(stream, cmd, .dyncomp),
.thruzero => try printCommand(stream, cmd, .thruzero),
.foverdrive => try printCommand(stream, cmd, .foverdrive),
.gverb => try printCommand(stream, cmd, .gverb),
.invert => try printCommand(stream, cmd, .invert),
.tapedelay => try printCommand(stream, cmd, .tapedelay),
.moddelay => try printCommand(stream, cmd, .moddelay),
.multichorus => try printCommand(stream, cmd, .multichorus),
.saturator => try printCommand(stream, cmd, .saturator),
.vintagedelay => try printCommand(stream, cmd, .vintagedelay),
.noise => try printCommand(stream, cmd, .noise),
.wildnoise => try printCommand(stream, cmd, .wildnoise),
.write => try printCommand(stream, cmd, .write),
.embed => try printCommand(stream, cmd, .embed),
}
_ = try stream.write(";\n");

View file

@ -24,12 +24,15 @@ pub const Runner = struct {
image: ?*Image = null,
/// If the runner is in REPL mode
repl: bool = false,
repl: bool,
args: [][]u8,
pub fn init(allocator: *std.mem.Allocator, repl: bool) Runner {
return Runner{
.allocator = allocator,
.repl = repl,
.args = std.process.argsAlloc(allocator) catch unreachable,
};
}
@ -41,7 +44,12 @@ pub const Runner = struct {
pub fn clone(self: *Runner) !Runner {
var cloned_image = if (self.image) |image| try image.clone() else null;
return Runner{ .allocator = self.allocator, .image = cloned_image };
return Runner{
.allocator = self.allocator,
.image = cloned_image,
.repl = self.repl,
.args = self.args,
};
}
fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 {
@ -49,20 +57,20 @@ pub const Runner = struct {
// parse the index from 1 to end
var index = try std.fmt.parseInt(usize, load_path[1..], 10);
// don't care about the 'repl' being prepended when we're in repl
if (self.repl) index += 1;
// if it isn't in the repl, args look like this:
// 'scritcher ./script ./image'
// if it is, it looks like this
// 'scritcher repl ./script ./image'
var args_it = std.process.args();
_ = args_it.skip();
// ':0' should ALWAYS point to the image.
if (self.repl) index += 3 else index += 2;
var i: usize = 0;
while (i <= index) : (i += 1) {
_ = args_it.skip();
std.debug.warn("ARGS!! {} \n", .{self.args.len});
for (self.args) |arg, idx| {
std.debug.warn("arg{} = {}\n", .{ idx, arg });
}
const arg = try (args_it.next(self.allocator) orelse @panic("expected argument"));
return arg;
std.debug.warn("fetch arg idx={}, val={}\n", .{ index, self.args[index] });
return self.args[index];
} else {
return load_path;
}
@ -90,7 +98,7 @@ pub const Runner = struct {
// krita/gimp and make it export a bmp and while in the program you can
// apply filters, etc.
if (!std.mem.endsWith(u8, load_path, ".bmp") and !std.mem.endsWith(u8, load_path, ".ppm")) {
std.debug.warn("Only BMP files are allowed to be loaded.\n", .{});
std.debug.warn("Only BMP files are allowed to be loaded. Got path '{}'\n", .{load_path});
return RunError.NoBMP;
}
@ -175,614 +183,134 @@ pub const Runner = struct {
try image.saveTo(out_path);
}
fn runQSCmd(self: *Runner, program: []const u8) !void {
fn runQSCmd(self: *Runner, cmd: lang.Command) !void {
const runqs = cmd.cast(lang.Command.RunQS).?;
var image = try self.getImage();
const out_path = try self.makeGlitchedPath();
try image.saveTo(out_path);
var proc = try std.ChildProcess.init(
&[_][]const u8{ program, out_path },
&[_][]const u8{ runqs.program, out_path },
self.allocator,
);
defer proc.deinit();
std.debug.warn("running '{} {}'\n", .{ program, out_path });
std.debug.warn("running '{} {}'\n", .{ runqs.program, out_path });
_ = try proc.spawnAndWait();
}
/// Run the http://lv2plug.in/plugins/eg-amp plugin over the file.
fn ampCmd(self: *Runner, pos: Position, params: ParamList) !void {
fn rotateCmd(self: *Runner, cmd: lang.Command) !void {
const rotate_cmd = cmd.cast(lang.Command.Rotate).?;
var image = try self.getImage();
try image.runPlugin("http://lv2plug.in/plugins/eg-amp", pos, params);
var c_bgfill = try std.cstr.addNullByte(self.allocator, rotate_cmd.bgfill);
defer self.allocator.free(c_bgfill);
try magick.runRotate(image, rotate_cmd.deg, c_bgfill);
}
fn rFlangerCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/retroFlange", pos, params);
}
fn executeLV2Command(self: *@This(), command: var) !void {
const pos = plugin.Position{
.split = command.split,
.index = command.index,
};
fn eqCmd(self: *Runner, position: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/dj_eq_mono", position, params);
}
fn phaserCmd(self: *Runner, position: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/lfoPhaser", position, params);
}
fn mbeqCmd(self: *Runner, position: Position, bands: []const f32) !void {
var image = try self.getImage();
var params = ParamList.init(self.allocator);
defer params.deinit();
for (bands) |band_value, idx| {
var sym = try std.fmt.allocPrint(self.allocator, "band_{}", .{idx + 1});
const typ = @TypeOf(command);
inline for (@typeInfo(@TypeOf(command.parameters)).Struct.fields) |cmd_field| {
try params.append(plugin.Param{
.sym = sym,
.value = band_value,
.sym = cmd_field.name,
.value = @field(command.parameters, cmd_field.name),
});
}
try image.runPlugin("http://plugin.org.uk/swh-plugins/mbeq", position, params);
}
fn chorusCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/multivoiceChorus", pos, params);
try image.runPlugin(typ.lv2_url, pos, params);
}
fn pitchScalerCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/pitchScaleHQ", pos, params);
}
fn reverbCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://invadarecords.com/plugins/lv2/erreverb/mono", pos, params);
}
fn highpassCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://invadarecords.com/plugins/lv2/filter/hpf/mono", pos, params);
}
fn delayCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/delayorama", pos, params);
}
fn vinylCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/vynil", pos, params);
}
fn revDelayCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/revdelay", pos, params);
}
fn noiseCmd(self: *Runner, pos: Position, map: *ParamMap) !void {
var image = try self.getImage();
try image.runCustomPlugin(custom.RandomNoise, pos, *ParamMap, map);
}
fn wildNoiseCmd(self: *Runner, pos: Position, map: *ParamMap) !void {
var image = try self.getImage();
try image.runCustomPlugin(custom.WildNoise, pos, *ParamMap, map);
}
fn writeCmd(self: *Runner, pos: Position, map: *ParamMap) !void {
var image = try self.getImage();
try image.runCustomPlugin(custom.Write, pos, *ParamMap, map);
}
fn embedCmd(self: *Runner, pos: Position, path: []const u8) !void {
var image = try self.getImage();
try image.runCustomPlugin(custom.Embed, pos, []const u8, path);
}
fn rotateCmd(
self: *Runner,
deg: f32,
bgfill: []const u8,
) !void {
var image = try self.getImage();
var c_bgfill = try std.cstr.addNullByte(self.allocator, bgfill);
defer self.allocator.free(c_bgfill);
try magick.runRotate(image, deg, c_bgfill);
}
fn gateCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://hippie.lt/lv2/gate", pos, params);
}
fn detuneCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/Detune", pos, params);
}
fn overdriveCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/Overdrive", pos, params);
}
fn degradeCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/Degrade", pos, params);
}
fn repsychoCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/RePsycho", pos, params);
}
fn talkboxCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/TalkBox", pos, params);
}
fn dynCompCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://gareus.org/oss/lv2/darc#mono", pos, params);
}
fn foverdriveCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/foverdrive", pos, params);
}
fn thruZeroCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://drobilla.net/plugins/mda/ThruZero", pos, params);
}
fn gverbCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/gverb", pos, params);
}
fn invertCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/inv", pos, params);
}
fn tapedelayCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/tapeDelay", pos, params);
}
fn moddelayCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://plugin.org.uk/swh-plugins/modDelay", pos, params);
}
fn multichorusCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://calf.sourceforge.net/plugins/MultiChorus", pos, params);
}
fn saturatorCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://calf.sourceforge.net/plugins/Saturator", pos, params);
}
fn vintagedelayCmd(self: *Runner, pos: Position, params: ParamList) !void {
var image = try self.getImage();
try image.runPlugin("http://calf.sourceforge.net/plugins/VintageDelay", pos, params);
}
fn runCommand(self: *Runner, cmd: *lang.Command) !void {
var params = ParamList.init(self.allocator);
defer params.deinit();
var map = ParamMap.init(self.allocator);
defer map.deinit();
return switch (cmd.command) {
.Noop => {},
.Load => blk: {
var path = cmd.args.items[0];
try self.loadCmd(path);
// TODO is this needed?
break :blk;
},
.Quicksave => try self.quicksaveCmd(),
.RunQS => try self.runQSCmd(cmd.args.items[0]),
.Amp => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&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: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "lfo_rate");
try cmd.appendParam(&params, "lfo_depth");
try cmd.appendParam(&params, "fb");
try cmd.appendParam(&params, "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(&params, "voices");
try cmd.appendParam(&params, "delay_base");
try cmd.appendParam(&params, "voice_spread");
try cmd.appendParam(&params, "detune");
try cmd.appendParam(&params, "law_freq");
try cmd.appendParam(&params, "attendb");
try self.chorusCmd(pos, params);
},
.PitchScaler => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "mult");
try self.pitchScalerCmd(pos, params);
},
.Reverb => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "roomLength");
try cmd.appendParam(&params, "roomWidth");
try cmd.appendParam(&params, "roomHeight");
try cmd.appendParam(&params, "sourceLR");
try cmd.appendParam(&params, "sourceFB");
try cmd.appendParam(&params, "listLR");
try cmd.appendParam(&params, "listFB");
try cmd.appendParam(&params, "hpf");
try cmd.appendParam(&params, "warmth");
try cmd.appendParam(&params, "diffusion");
try self.reverbCmd(pos, params);
},
.Highpass => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "freq");
try cmd.appendParam(&params, "gain");
try cmd.appendParam(&params, "noClip");
try self.highpassCmd(pos, params);
},
.Delay => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "seed");
try cmd.appendParam(&params, "gain");
try cmd.appendParam(&params, "feedback_pc");
try cmd.appendParam(&params, "tap_count");
try cmd.appendParam(&params, "first_delay");
try cmd.appendParam(&params, "delay_range");
try cmd.appendParam(&params, "delay_scale");
try cmd.appendParam(&params, "delay_rand_pc");
try cmd.appendParam(&params, "gain_scale");
try cmd.appendParam(&params, "wet");
try self.delayCmd(pos, params);
},
.Vinyl => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "year");
try cmd.appendParam(&params, "rpm");
try cmd.appendParam(&params, "warp");
try cmd.appendParam(&params, "click");
try cmd.appendParam(&params, "wear");
try self.vinylCmd(pos, params);
},
.RevDelay => blk: {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "delay_time");
try cmd.appendParam(&params, "dry_level");
try cmd.appendParam(&params, "wet_level");
try cmd.appendParam(&params, "feedback");
try cmd.appendParam(&params, "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(&params, "switch");
try cmd.appendParam(&params, "threshold");
try cmd.appendParam(&params, "attack");
try cmd.appendParam(&params, "hold");
try cmd.appendParam(&params, "decay");
try cmd.appendParam(&params, "gaterange");
try self.gateCmd(pos, params);
},
.Detune => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "detune");
try cmd.appendParam(&params, "mix");
try cmd.appendParam(&params, "output");
try cmd.appendParam(&params, "latency");
try self.detuneCmd(pos, params);
},
.Overdrive => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "drive");
try cmd.appendParam(&params, "muffle");
try cmd.appendParam(&params, "output");
try self.overdriveCmd(pos, params);
},
.Degrade => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "headroom");
try cmd.appendParam(&params, "quant");
try cmd.appendParam(&params, "rate");
try cmd.appendParam(&params, "post_filt");
try cmd.appendParam(&params, "non_lin");
try cmd.appendParam(&params, "output");
try self.degradeCmd(pos, params);
},
.RePsycho => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "tune");
try cmd.appendParam(&params, "fine");
try cmd.appendParam(&params, "decay");
try cmd.appendParam(&params, "thresh");
try cmd.appendParam(&params, "hold");
try cmd.appendParam(&params, "mix");
try cmd.appendParam(&params, "quality");
try self.repsychoCmd(pos, params);
},
.TalkBox => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "wet");
try cmd.appendParam(&params, "dry");
try cmd.appendParam(&params, "carrier");
try cmd.appendParam(&params, "quality");
try self.talkboxCmd(pos, params);
},
.DynComp => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "enable");
try cmd.appendParam(&params, "hold");
try cmd.appendParam(&params, "inputgain");
try cmd.appendParam(&params, "threshold");
try cmd.appendParam(&params, "ratio");
try cmd.appendParam(&params, "attack");
try cmd.appendParam(&params, "release");
try cmd.appendParam(&params, "gain_min");
try cmd.appendParam(&params, "gain_max");
try cmd.appendParam(&params, "rms");
try self.dynCompCmd(pos, params);
},
.ThruZero => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "rate");
try cmd.appendParam(&params, "mix");
try cmd.appendParam(&params, "feedback");
try cmd.appendParam(&params, "depth_mod");
try self.thruZeroCmd(pos, params);
},
.Foverdrive => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "drive");
try self.foverdriveCmd(pos, params);
},
.Gverb => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "roomsize");
try cmd.appendParam(&params, "revtime");
try cmd.appendParam(&params, "damping");
try cmd.appendParam(&params, "inputbandwidth");
try cmd.appendParam(&params, "drylevel");
try cmd.appendParam(&params, "earlylevel");
try cmd.appendParam(&params, "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(&params, "speed");
try cmd.appendParam(&params, "da_db");
try cmd.appendParam(&params, "t1d");
try cmd.appendParam(&params, "t1a_db");
try cmd.appendParam(&params, "t2d");
try cmd.appendParam(&params, "t2a_db");
try cmd.appendParam(&params, "t3d");
try cmd.appendParam(&params, "t3a_db");
try cmd.appendParam(&params, "t4d");
try cmd.appendParam(&params, "t4a_db");
try self.tapedelayCmd(pos, params);
},
.ModDelay => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "base");
try self.moddelayCmd(pos, params);
},
.MultiChorus => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "min_delay");
try cmd.appendParam(&params, "mod_depth");
try cmd.appendParam(&params, "mod_rate");
try cmd.appendParam(&params, "stereo");
try cmd.appendParam(&params, "voices");
try cmd.appendParam(&params, "vphase");
try cmd.appendParam(&params, "amount");
try cmd.appendParam(&params, "dry");
try cmd.appendParam(&params, "freq");
try cmd.appendParam(&params, "freq2");
try cmd.appendParam(&params, "q");
try cmd.appendParam(&params, "overlap");
try cmd.appendParam(&params, "level_in");
try cmd.appendParam(&params, "level_out");
try cmd.appendParam(&params, "lfo");
try self.multichorusCmd(pos, params);
},
.Saturator => {
const pos = try cmd.consumePosition();
try cmd.appendParam(&params, "bypass");
try cmd.appendParam(&params, "level_in");
try cmd.appendParam(&params, "level_out");
try cmd.appendParam(&params, "mix");
try cmd.appendParam(&params, "drive");
try cmd.appendParam(&params, "blend");
try cmd.appendParam(&params, "lp_pre_freq");
try cmd.appendParam(&params, "hp_pre_freq");
try cmd.appendParam(&params, "lp_post_freq");
try cmd.appendParam(&params, "hp_post_freq");
try cmd.appendParam(&params, "p_freq");
try cmd.appendParam(&params, "p_level");
try cmd.appendParam(&params, "p_q");
try cmd.appendParam(&params, "pre");
try cmd.appendParam(&params, "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(&params, param);
}
try self.vintagedelayCmd(pos, params);
},
else => blk: {
std.debug.warn("Unsupported command: {}\n", .{cmd.command});
break :blk RunError.UnknownCommand;
},
fn executeCustomCommand(self: *@This(), command: var) !void {
const pos = plugin.Position{
.split = command.split,
.index = command.index,
};
var image = try self.getImage();
try image.runCustomPlugin(@TypeOf(command).plugin_type, pos, command.parameters);
}
fn runSingleCommand(
self: *@This(),
cmd: lang.Command,
comptime tag: lang.Command.Tag,
) !void {
comptime const typ = lang.Command.tagToType(tag);
const command = cmd.cast(typ).?;
const ctype = typ.command_type;
switch (ctype) {
.lv2_command => try self.executeLV2Command(command.*),
.custom_command => try self.executeCustomCommand(command.*),
else => @panic("TODO support command type"),
}
}
fn runCommand(self: *@This(), cmd: lang.Command) !void {
switch (cmd.tag) {
.noop => {},
.load => {
const command = cmd.cast(lang.Command.Load).?;
try self.loadCmd(command.path);
},
.quicksave => try self.quicksaveCmd(),
.rotate => try self.rotateCmd(cmd),
.runqs => try self.runQSCmd(cmd),
.amp => try self.runSingleCommand(cmd, .amp),
.rflanger => try self.runSingleCommand(cmd, .rflanger),
.eq => try self.runSingleCommand(cmd, .eq),
.phaser => try self.runSingleCommand(cmd, .phaser),
.mbeq => try self.runSingleCommand(cmd, .mbeq),
.chorus => try self.runSingleCommand(cmd, .chorus),
.pitchscaler => try self.runSingleCommand(cmd, .pitchscaler),
.reverb => try self.runSingleCommand(cmd, .reverb),
.highpass => try self.runSingleCommand(cmd, .highpass),
.delay => try self.runSingleCommand(cmd, .delay),
.vinyl => try self.runSingleCommand(cmd, .vinyl),
.revdelay => try self.runSingleCommand(cmd, .revdelay),
.gate => try self.runSingleCommand(cmd, .gate),
.detune => try self.runSingleCommand(cmd, .detune),
.overdrive => try self.runSingleCommand(cmd, .overdrive),
.degrade => try self.runSingleCommand(cmd, .degrade),
.repsycho => try self.runSingleCommand(cmd, .repsycho),
.talkbox => try self.runSingleCommand(cmd, .talkbox),
.dyncomp => try self.runSingleCommand(cmd, .dyncomp),
.thruzero => try self.runSingleCommand(cmd, .thruzero),
.foverdrive => try self.runSingleCommand(cmd, .foverdrive),
.gverb => try self.runSingleCommand(cmd, .gverb),
.invert => try self.runSingleCommand(cmd, .invert),
.tapedelay => try self.runSingleCommand(cmd, .tapedelay),
.moddelay => try self.runSingleCommand(cmd, .moddelay),
.multichorus => try self.runSingleCommand(cmd, .multichorus),
.saturator => try self.runSingleCommand(cmd, .saturator),
.vintagedelay => try self.runSingleCommand(cmd, .vintagedelay),
.noise => try self.runSingleCommand(cmd, .noise),
.wildnoise => try self.runSingleCommand(cmd, .wildnoise),
.write => try self.runSingleCommand(cmd, .write),
.embed => try self.runSingleCommand(cmd, .embed),
}
}
/// Run a list of commands.
pub fn runCommands(
self: *Runner,
cmds: lang.CommandList,
debug_flag: bool,
) !void {
for (cmds.items) |const_cmd| {
if (debug_flag) const_cmd.print();
// copy the command so we own its memory
var cmd = try const_cmd.copy(self.allocator);
defer self.allocator.destroy(cmd);
try self.runCommand(cmd);
for (cmds.items) |cmd| {
cmd.print();
try self.runCommand(cmd.*);
}
}
};