From 176bbd446add2fb305050b20b43cf998ebee6e99 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 8 Jul 2019 22:40:52 -0300 Subject: [PATCH 1/4] add draft impl for ampCmd --- examples/middle_echo.scri | 3 ++- src/lang.zig | 10 ++++++++++ src/plugin.zig | 17 +++++++++++++++++ src/runner.zig | 30 +++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/plugin.zig diff --git a/examples/middle_echo.scri b/examples/middle_echo.scri index 18e29f6..627c63a 100644 --- a/examples/middle_echo.scri +++ b/examples/middle_echo.scri @@ -1,3 +1,4 @@ load :0; -echo 3 0 1 0.2; +amp 10 1; +# echo 3 0 1 0.2; quicksave; diff --git a/src/lang.zig b/src/lang.zig index 8b91e8f..8ee0d9e 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -19,6 +19,16 @@ pub const Command = struct { pub fn print(self: *const Command) void { std.debug.warn("cmd:{}\n", self.command); } + + pub fn intArgAt(self: *const Command, idx: usize) !i32 { + var arg = self.args.at(idx); + return try std.fmt.parseInt(i32, arg, 10); + } + + pub fn floatArgAt(self: *const Command, idx: usize) !f32 { + var arg = self.args.at(idx); + return try std.fmt.parseFloat(f32, arg); + } }; pub const CommandList = std.ArrayList(*Command); diff --git a/src/plugin.zig b/src/plugin.zig new file mode 100644 index 0000000..4241df4 --- /dev/null +++ b/src/plugin.zig @@ -0,0 +1,17 @@ +const std = @import("std"); + +/// Control port +pub const Param = struct { + /// Port symbol + sym: []const u8, + + /// Control value + value: f32, +}; + +pub const ParamList = std.ArrayList(Param); + +pub const Position = struct { + split: usize, + index: usize, +}; diff --git a/src/runner.zig b/src/runner.zig index 90ccd08..740394e 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -1,6 +1,7 @@ const std = @import("std"); const lang = @import("lang.zig"); const images = @import("image.zig"); +const plugin = @import("plugin.zig"); const Image = images.Image; @@ -70,6 +71,8 @@ pub const Runner = struct { return RunError.NoBMP; } + // TODO, copy load_path into a temporary file, then use that + // file to work on things. self.image = try Image.open(self.allocator, load_path); } @@ -135,6 +138,24 @@ pub const Runner = struct { std.debug.warn("out path: {}\n", out_path); } + /// Run the http://lv2plug.in/plugins/eg-amp plugin over the file. + fn ampCmd(self: *Runner, split: i32, index: i32, gain: f32) !void { + var param = try self.allocator.create(plugin.Param); + defer self.allocator.destroy(param); + + param.* = Param{ .sym = "gain", .value = gain }; + + var params = plugin.ParamList.init(self.allocator); + defer params.deinit(); + + // TODO impl this + try image.runPlugin( + "http://lv2plug.in/plugins/eg-amp", + plugin.Position{ .split = split, .index = index }, + params, + ); + } + fn runCommand(self: *Runner, cmd: *lang.Command) !void { return switch (cmd.command) { .Noop => {}, @@ -143,8 +164,15 @@ pub const Runner = struct { try self.loadCmd(path); break :blk; }, - .Quicksave => try self.quicksaveCmd(), + + // .Amp => blk: { + // const split = try cmd.intArgAt(0); + // const index = try cmd.intArgAt(1); + // const gain = try cmd.floatArgAt(2); + // try self.ampCmd(split, index, gain); + // } + else => blk: { std.debug.warn("Unknown command: {}\n", cmd.command); break :blk RunError.UnknownCommand; From eea39cdd24d0bfe31782de2fc7faf5c6958b0ca7 Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 8 Jul 2019 22:41:38 -0300 Subject: [PATCH 2/4] add comments --- src/lang.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lang.zig b/src/lang.zig index 8ee0d9e..c558e22 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -65,6 +65,7 @@ pub const Lang = struct { stmt = std.mem.trimLeft(u8, stmt, "\n"); if (stmt.len == 0) continue; + if (stmt[0] == '#') continue; // TODO better tokenizer instead of just tokenize(" "); var tok_it = std.mem.tokenize(stmt, " "); From 8e453298bc489b1dc79b6a58db111fa4f9f2ef3e Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 9 Jul 2019 00:04:01 -0300 Subject: [PATCH 3/4] add basic machinery around plugin --- examples/middle_echo.scri | 2 +- src/image.zig | 37 ++++++++++++++++++++++++++++++++++++- src/lang.zig | 21 +++++++++++++++++++-- src/lv2_helpers.zig | 39 +++++++++++++++++++++++++++++++++++++++ src/main.zig | 24 ------------------------ src/plugin.zig | 37 +++++++++++++++++++++++++++++++++++++ src/runner.zig | 22 ++++++++++++---------- 7 files changed, 144 insertions(+), 38 deletions(-) create mode 100644 src/lv2_helpers.zig diff --git a/examples/middle_echo.scri b/examples/middle_echo.scri index 627c63a..45bc8d1 100644 --- a/examples/middle_echo.scri +++ b/examples/middle_echo.scri @@ -1,4 +1,4 @@ load :0; -amp 10 1; +amp 3 1 10; # echo 3 0 1 0.2; quicksave; diff --git a/src/image.zig b/src/image.zig index 0def9ba..d91a4a3 100644 --- a/src/image.zig +++ b/src/image.zig @@ -1,9 +1,18 @@ const std = @import("std"); const c = @cImport({ @cInclude("sndfile.h"); + + @cInclude("lilv/lilv.h"); + @cInclude("lv2/core/lv2.h"); }); -pub const ImageError = error{OpenFail}; +const plugins = @import("plugin.zig"); + +pub const ImageError = error{ + OpenFail, + InvalidPlugin, + UnknownPlugin, +}; /// Low level integration function with libsndfile. fn sopen( @@ -70,4 +79,30 @@ pub const Image = struct { ); } } + + pub fn read(self: *Image, file_chans: c_int, buf: []f32) bool { + var file = file_opt.?; + + const n_read: c.sf_count_t = c.sf_readf_float(file, buf.ptr, 1); + const buf_chans = @intCast(c_int, buf.len); + + var i = file_chans - 1; + while (i < buf_chans) : (i += 1) { + //buf[@intCast(usize, i)] = buf[i % file_chans]; + buf[@intCast(usize, i)] = buf[@intCast(usize, @mod(i, file_chans))]; + } + + return n_read == 1; + } + + pub fn runPlugin( + self: *Image, + plugin_uri: []const u8, + pos: plugins.Position, + params: plugins.ParamList, + ) !void { + const context = try plugins.makeContext(self.allocator, plugin_uri); + std.debug.warn("world: {}\n", context.world); + std.debug.warn("plugin: {}\n", context.plugin); + } }; diff --git a/src/lang.zig b/src/lang.zig index c558e22..ed5b402 100644 --- a/src/lang.zig +++ b/src/lang.zig @@ -3,6 +3,7 @@ const std = @import("std"); pub const ParseError = error{ NoCommandGiven, UnknownCommand, + ArgRequired, }; pub const CommandType = enum { @@ -20,13 +21,29 @@ pub const Command = struct { std.debug.warn("cmd:{}\n", self.command); } + pub fn argAt(self: *const Command, idx: usize) ![]const u8 { + const args = self.args.toSliceConst(); + + if (idx > (args.len - 1)) { + std.debug.warn("Expected argument at index {}\n", idx); + return ParseError.ArgRequired; + } + + return args[idx]; + } + + pub fn usizeArgAt(self: *const Command, idx: usize) !usize { + var arg = try self.argAt(idx); + return try std.fmt.parseInt(usize, arg, 10); + } + pub fn intArgAt(self: *const Command, idx: usize) !i32 { - var arg = self.args.at(idx); + var arg = try self.argAt(idx); return try std.fmt.parseInt(i32, arg, 10); } pub fn floatArgAt(self: *const Command, idx: usize) !f32 { - var arg = self.args.at(idx); + var arg = try self.argAt(idx); return try std.fmt.parseFloat(f32, arg); } }; diff --git a/src/lv2_helpers.zig b/src/lv2_helpers.zig new file mode 100644 index 0000000..be9d915 --- /dev/null +++ b/src/lv2_helpers.zig @@ -0,0 +1,39 @@ +const c = @cImport({ + @cInclude("lilv/lilv.h"); + @cInclude("lv2/core/lv2.h"); +}); + +const LV2_CORE_URI = "http://lv2plug.in/ns/lv2core"; + +pub fn Lv2Core(ns: []const u8) ![]const u8 { + var allocator = std.heap.direct_allocator; + + return try std.cstr.addNullByte( + allocator, + try std.fmt.allocPrint(allocator, "{}{}", LV2_CORE_URI, ns), + ); +} + +pub fn lilv_instance_connect_port( + instance: [*c]c.LilvInstance, + port_index: u32, + data_location: ?*c_void, +) void { + instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location); +} + +pub fn lilv_instance_activate(instance: [*c]c.LilvInstance) void { + if (instance.?.*.lv2_descriptor.?.*.activate != null) { + instance.?.*.lv2_descriptor.?.*.activate.?(instance.?.*.lv2_handle); + } +} + +pub fn lilv_instance_run(instance: [*c]c.LilvInstance, sample_count: u32) void { + instance.?.*.lv2_descriptor.?.*.run.?(instance.?.*.lv2_handle, sample_count); +} + +pub fn lilv_instance_deactivate(instance: [*c]c.LilvInstance) void { + if (instance.?.*.lv2_descriptor.?.*.deactivate != null) { + instance.?.*.lv2_descriptor.?.*.deactivate.?(instance.?.*.lv2_handle); + } +} diff --git a/src/main.zig b/src/main.zig index ddfeea5..2b2fcf6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -60,30 +60,6 @@ const Param = struct { value: f32, }; -pub fn lilv_instance_connect_port( - instance: [*c]c.LilvInstance, - port_index: u32, - data_location: ?*c_void, -) void { - instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location); -} - -pub fn lilv_instance_activate(instance: [*c]c.LilvInstance) void { - if (instance.?.*.lv2_descriptor.?.*.activate != null) { - instance.?.*.lv2_descriptor.?.*.activate.?(instance.?.*.lv2_handle); - } -} - -pub fn lilv_instance_run(instance: [*c]c.LilvInstance, sample_count: u32) void { - instance.?.*.lv2_descriptor.?.*.run.?(instance.?.*.lv2_handle, sample_count); -} - -pub fn lilv_instance_deactivate(instance: [*c]c.LilvInstance) void { - if (instance.?.*.lv2_descriptor.?.*.deactivate != null) { - instance.?.*.lv2_descriptor.?.*.deactivate.?(instance.?.*.lv2_handle); - } -} - const ParamList = std.ArrayList(Param); const PortType = enum { diff --git a/src/plugin.zig b/src/plugin.zig index 4241df4..bfcc420 100644 --- a/src/plugin.zig +++ b/src/plugin.zig @@ -1,5 +1,12 @@ const std = @import("std"); +const c = @cImport({ + @cInclude("lilv/lilv.h"); + @cInclude("lv2/core/lv2.h"); +}); + +const ImageError = @import("image.zig").ImageError; + /// Control port pub const Param = struct { /// Port symbol @@ -9,9 +16,39 @@ pub const Param = struct { value: f32, }; +/// List of parameters to be set to control ports. pub const ParamList = std.ArrayList(Param); +/// Represents a relative position in the image pub const Position = struct { split: usize, index: usize, }; + +/// Represents the starting context for a single plugin run. +pub const Context = struct { + world: *c.LilvWorld, + plugin: *const c.LilvPlugin, +}; + +pub fn makeContext(allocator: *std.mem.Allocator, plugin_uri: []const u8) !Context { + const cstr_plugin_uri = try std.cstr.addNullByte(allocator, plugin_uri); + var world = c.lilv_world_new().?; + + c.lilv_world_load_all(world); + var uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse blk: { + std.debug.warn("Invalid plugin URI <{}>\n", plugin_uri); + return ImageError.InvalidPlugin; + }; + + const plugins: *const c.LilvPlugins = c.lilv_world_get_all_plugins(world); + + var plugin: *const c.LilvPlugin = c.lilv_plugins_get_by_uri(plugins, uri) orelse blk: { + std.debug.warn("Plugin <{}> not found\n", plugin_uri); + return ImageError.UnknownPlugin; + }; + + c.lilv_node_free(uri); + + return Context{ .world = world, .plugin = plugin }; +} diff --git a/src/runner.zig b/src/runner.zig index 740394e..0be0451 100644 --- a/src/runner.zig +++ b/src/runner.zig @@ -16,7 +16,9 @@ pub const Runner = struct { image: ?*Image = null, pub fn init(allocator: *std.mem.Allocator) Runner { - return Runner{ .allocator = allocator }; + return Runner{ + .allocator = allocator, + }; } pub fn deinit(self: *Runner) void { @@ -139,16 +141,16 @@ pub const Runner = struct { } /// Run the http://lv2plug.in/plugins/eg-amp plugin over the file. - fn ampCmd(self: *Runner, split: i32, index: i32, gain: f32) !void { + fn ampCmd(self: *Runner, split: usize, index: usize, gain: f32) !void { + var image = try self.getImage(); var param = try self.allocator.create(plugin.Param); defer self.allocator.destroy(param); - param.* = Param{ .sym = "gain", .value = gain }; + param.* = plugin.Param{ .sym = "gain", .value = gain }; var params = plugin.ParamList.init(self.allocator); defer params.deinit(); - // TODO impl this try image.runPlugin( "http://lv2plug.in/plugins/eg-amp", plugin.Position{ .split = split, .index = index }, @@ -166,12 +168,12 @@ pub const Runner = struct { }, .Quicksave => try self.quicksaveCmd(), - // .Amp => blk: { - // const split = try cmd.intArgAt(0); - // const index = try cmd.intArgAt(1); - // const gain = try cmd.floatArgAt(2); - // try self.ampCmd(split, index, gain); - // } + .Amp => blk: { + const split = try cmd.usizeArgAt(0); + const index = try cmd.usizeArgAt(1); + const gain = try cmd.floatArgAt(2); + try self.ampCmd(split, index, gain); + }, else => blk: { std.debug.warn("Unknown command: {}\n", cmd.command); From bbf34f1133a36bbab71349b87cdbddf94fdee742 Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 9 Jul 2019 13:21:07 -0300 Subject: [PATCH 4/4] add port setup code --- src/image.zig | 9 +++- src/lv2_helpers.zig | 113 +++++++++++++++++++++++++++++++++++++++++++- src/plugin.zig | 14 +++--- 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/src/image.zig b/src/image.zig index d91a4a3..f165896 100644 --- a/src/image.zig +++ b/src/image.zig @@ -1,4 +1,6 @@ const std = @import("std"); +const lv2 = @import("lv2_helpers.zig"); + const c = @cImport({ @cInclude("sndfile.h"); @@ -101,8 +103,13 @@ pub const Image = struct { pos: plugins.Position, params: plugins.ParamList, ) !void { - const context = try plugins.makeContext(self.allocator, plugin_uri); + var context = try plugins.makeContext(self.allocator, plugin_uri); std.debug.warn("world: {}\n", context.world); std.debug.warn("plugin: {}\n", context.plugin); + + var ports = try lv2.setupPorts(&context); + for (ports) |port| { + std.debug.warn("port: {}\n", port.*); + } } }; diff --git a/src/lv2_helpers.zig b/src/lv2_helpers.zig index be9d915..8a6dc57 100644 --- a/src/lv2_helpers.zig +++ b/src/lv2_helpers.zig @@ -1,4 +1,7 @@ -const c = @cImport({ +const std = @import("std"); +const plugin = @import("plugin.zig"); + +pub const c = @cImport({ @cInclude("lilv/lilv.h"); @cInclude("lv2/core/lv2.h"); }); @@ -37,3 +40,111 @@ pub fn lilv_instance_deactivate(instance: [*c]c.LilvInstance) void { instance.?.*.lv2_descriptor.?.*.deactivate.?(instance.?.*.lv2_handle); } } + +pub const PortType = enum { + Control, + Audio, +}; + +pub const Port = struct { + lilv_port: ?*const c.LilvPort, + ptype: PortType, + index: u32, + value: f32, + is_input: bool, + optional: bool, +}; + +/// Setup ports for a given plugin. Gives an array to pointers of Port structs. +/// This setup is required so we link the plugin to the ports later on, and +/// also link our buffers, and control values. +pub fn setupPorts(ctx: *plugin.Context) ![]*Port { + var world = ctx.world; + const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin); + + var ports = try ctx.allocator.alloc(*Port, n_ports); + + for (ports) |port_ptr, idx| { + var port = try ctx.allocator.create(Port); + port.* = Port{ + .lilv_port = null, + .ptype = .Control, + .index = f32(0), + .value = f32(0), + .is_input = false, + .optional = false, + }; + + ports[idx] = port; + } + + var values: []f32 = try ctx.allocator.alloc(f32, n_ports); + defer ctx.allocator.free(values); + + c.lilv_plugin_get_port_ranges_float(ctx.plugin, null, null, values.ptr); + + // bad solution, but it really do be like that + const LV2_CORE__InputPort = try Lv2Core("#InputPort"); + const LV2_CORE__OutputPort = try Lv2Core("#OutputPort"); + const LV2_CORE__AudioPort = try Lv2Core("#AudioPort"); + const LV2_CORE__ControlPort = try Lv2Core("#ControlPort"); + const LV2_CORE__connectionOptional = try Lv2Core("#connectionOptional"); + + var lv2_InputPort = c.lilv_new_uri(world, LV2_CORE__InputPort.ptr); + defer std.heap.c_allocator.destroy(lv2_InputPort); + + var lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr); + defer std.heap.c_allocator.destroy(lv2_OutputPort); + + var lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr); + defer std.heap.c_allocator.destroy(lv2_AudioPort); + + var lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr); + defer std.heap.c_allocator.destroy(lv2_ControlPort); + + var lv2_connectionOptional = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr); + defer std.heap.c_allocator.destroy(lv2_connectionOptional); + + var i: u32 = 0; + while (i < n_ports) : (i += 1) { + var port: *Port = ports[i]; + + const lport = c.lilv_plugin_get_port_by_index(ctx.plugin, i).?; + + port.lilv_port = lport; + port.index = i; + + if (std.math.isNan(values[i])) { + port.value = f32(0); + } else { + port.value = values[i]; + } + + port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connectionOptional); + + if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) { + port.is_input = true; + } else if (!c.lilv_port_is_a(ctx.plugin, lport, lv2_OutputPort) and !port.optional) { + std.debug.warn("Port {} is neither input or output\n", i); + return error.UnassignedIOPort; + } + + // check if port is an audio or control port + if (c.lilv_port_is_a(ctx.plugin, lport, lv2_ControlPort)) { + port.ptype = .Control; + } else if (c.lilv_port_is_a(ctx.plugin, lport, lv2_AudioPort)) { + port.ptype = .Audio; + + if (port.is_input) { + ctx.n_audio_in += 1; + } else { + ctx.n_audio_out += 1; + } + } else if (!port.optional) { + std.debug.warn("Port {} has unsupported type\n", i); + return error.UnsupportedPortType; + } + } + + return ports; +} diff --git a/src/plugin.zig b/src/plugin.zig index bfcc420..a555b11 100644 --- a/src/plugin.zig +++ b/src/plugin.zig @@ -1,9 +1,6 @@ const std = @import("std"); -const c = @cImport({ - @cInclude("lilv/lilv.h"); - @cInclude("lv2/core/lv2.h"); -}); +const c = @import("lv2_helpers.zig").c; const ImageError = @import("image.zig").ImageError; @@ -27,8 +24,13 @@ pub const Position = struct { /// Represents the starting context for a single plugin run. pub const Context = struct { + allocator: *std.mem.Allocator, world: *c.LilvWorld, - plugin: *const c.LilvPlugin, + plugin: ?*const c.LilvPlugin, + + // they should both be 1. + n_audio_in: usize = 0, + n_audio_out: usize = 0, }; pub fn makeContext(allocator: *std.mem.Allocator, plugin_uri: []const u8) !Context { @@ -50,5 +52,5 @@ pub fn makeContext(allocator: *std.mem.Allocator, plugin_uri: []const u8) !Conte c.lilv_node_free(uri); - return Context{ .world = world, .plugin = plugin }; + return Context{ .allocator = allocator, .world = world, .plugin = plugin }; }