Compare commits
7 commits
3b822e4b9a
...
fdbed55b80
Author | SHA1 | Date | |
---|---|---|---|
fdbed55b80 | |||
f222c5e34f | |||
bd00f61977 | |||
846faafc92 | |||
f7a16447f4 | |||
58aea2755e | |||
24e4f01a3e |
10 changed files with 522 additions and 285 deletions
125
.gitignore
vendored
125
.gitignore
vendored
|
@ -1,124 +1 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
pip-wheel-metadata/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
zig-cache/
|
||||
|
|
20
build.zig
Normal file
20
build.zig
Normal file
|
@ -0,0 +1,20 @@
|
|||
const Builder = @import("std").build.Builder;
|
||||
|
||||
pub fn build(b: *Builder) void {
|
||||
const mode = b.standardReleaseOptions();
|
||||
const exe = b.addExecutable("scritcher", "src/main.zig");
|
||||
exe.setBuildMode(mode);
|
||||
exe.install();
|
||||
|
||||
exe.linkSystemLibrary("lilv-0");
|
||||
exe.linkSystemLibrary("sndfile");
|
||||
exe.linkSystemLibrary("c");
|
||||
|
||||
exe.addIncludeDir("/usr/include/lilv-0");
|
||||
|
||||
const run_cmd = exe.run();
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
from .main import main
|
||||
__all__ = ['main']
|
|
@ -1,2 +0,0 @@
|
|||
class InterpreterError(Exception):
|
||||
pass
|
|
@ -1,78 +0,0 @@
|
|||
import os
|
||||
import shlex
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
|
||||
from .utils import load_file_path
|
||||
from .error import InterpreterError
|
||||
from .image import GlitchImage
|
||||
|
||||
|
||||
class Interpreter:
|
||||
"""Interpreter for scritcher instructions/statements."""
|
||||
def __init__(self):
|
||||
self.orig_path = None
|
||||
self.img = None
|
||||
|
||||
def _cmd_noop(self):
|
||||
pass
|
||||
|
||||
def _cmd_load(self, loadpath: str):
|
||||
source_path = load_file_path(loadpath)
|
||||
self.orig_path = source_path
|
||||
|
||||
# create a temporary file to hold bmp data
|
||||
handle, bmp_path = tempfile.mkstemp('.bmp')
|
||||
os.close(handle)
|
||||
|
||||
self.img = GlitchImage(source_path, Path(bmp_path))
|
||||
self.img.load()
|
||||
|
||||
def _cmd_quicksave(self):
|
||||
suffix = self.img.original.suffix
|
||||
name = self.img.original.name.replace(suffix, '')
|
||||
parent_folder = self.orig_path.parents[0]
|
||||
|
||||
# we need to search all files that match the pattern NAME_g*
|
||||
# then we save on the one _g suffix above it.
|
||||
|
||||
# e.g if the original path is "miya.png", we want to search for
|
||||
# files named "miya_g1.raw", "miya_g2.raw", and then we want to write
|
||||
# on "miya_g3.raw".
|
||||
index = 0
|
||||
for glitched_out in parent_folder.glob(f'{name}_g*'):
|
||||
try:
|
||||
idx = int(glitched_out.name.strip(name + '_g')[0])
|
||||
|
||||
# glob() doesnt seem to show a stable order. anyways, we can
|
||||
# just only update our index when its the maximum found
|
||||
if idx > index:
|
||||
index = idx
|
||||
except (IndexError, ValueError):
|
||||
continue
|
||||
|
||||
# create our next glitched path
|
||||
out_path = shutil.copyfile(
|
||||
self.img.path, parent_folder / f'{name}_g{index + 1}.raw')
|
||||
print('saved to', out_path)
|
||||
|
||||
|
||||
def run(self, line: str):
|
||||
"""Run a single line."""
|
||||
print(f'running {line!r}')
|
||||
|
||||
args = shlex.split(line)
|
||||
command = args[0]
|
||||
|
||||
if command != 'load' and self.img is None:
|
||||
print('warn: no file loaded.')
|
||||
|
||||
try:
|
||||
method = getattr(self, f'_cmd_{command}')
|
||||
except AttributeError:
|
||||
raise InterpreterError(f'Command {command!r} not found')
|
||||
|
||||
method(*args[1:])
|
|
@ -1,17 +0,0 @@
|
|||
import logging
|
||||
from PIL import Image
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class GlitchImage:
|
||||
"""A wrapper class around PIL.Image"""
|
||||
def __init__(self, orig_path, target_path):
|
||||
self.original = orig_path
|
||||
self.path = target_path
|
||||
|
||||
def load(self):
|
||||
"""Load the given image, convert it to BMP, and write it on the
|
||||
given target path."""
|
||||
log.info('opening %r into %r', self.original, self.path)
|
||||
img = Image.open(self.original)
|
||||
img.save(self.path, 'BMP')
|
|
@ -1,32 +0,0 @@
|
|||
import sys
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from .executer import Interpreter
|
||||
from .error import InterpreterError
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
def main():
|
||||
try:
|
||||
scri_path = Path(sys.argv[1]).resolve()
|
||||
except IndexError:
|
||||
print(f'usage: {sys.argv[0]} path/to/file.scri')
|
||||
return
|
||||
|
||||
full_text = scri_path.read_text()
|
||||
full_text = full_text.replace('\n', '')
|
||||
stmts = full_text.split(';')
|
||||
|
||||
interp = Interpreter()
|
||||
|
||||
try:
|
||||
for stmt in stmts:
|
||||
if not stmt:
|
||||
continue
|
||||
|
||||
interp.run(stmt)
|
||||
|
||||
print('OK')
|
||||
except InterpreterError as err:
|
||||
print(f'Interpreter error. {err.args[0]!r}')
|
|
@ -1,16 +0,0 @@
|
|||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .error import InterpreterError
|
||||
|
||||
def load_file_path(arg: str) -> Path:
|
||||
"""load given argument."""
|
||||
|
||||
try:
|
||||
index = int(arg.split(':')[1])
|
||||
except ValueError:
|
||||
raise InterpreterError('Invalid argument index')
|
||||
except IndexError:
|
||||
return Path(arg).resolve()
|
||||
|
||||
return Path(sys.argv[2 + index])
|
14
setup.py
14
setup.py
|
@ -1,14 +0,0 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='scritcher',
|
||||
version='0.1',
|
||||
py_modules=['scritcher'],
|
||||
install_requires=[
|
||||
'Pillow==6.0.0',
|
||||
],
|
||||
entry_points='''
|
||||
[console_scripts]
|
||||
scritcher=scritcher:main
|
||||
''',
|
||||
)
|
501
src/main.zig
Normal file
501
src/main.zig
Normal file
|
@ -0,0 +1,501 @@
|
|||
// Copyright 2007-2016 David Robillard <http://drobilla.net>
|
||||
// Copyright 2019 Luna Mendes <https://l4.pm>
|
||||
//
|
||||
// the main script is a fork/port of lv2apply, licensed under the ISC license.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const c = @cImport({
|
||||
@cInclude("assert.h");
|
||||
@cInclude("math.h");
|
||||
@cInclude("sndfile.h");
|
||||
@cInclude("stdarg.h");
|
||||
@cInclude("stdio.h");
|
||||
@cInclude("stdlib.h");
|
||||
@cInclude("string.h");
|
||||
|
||||
@cInclude("lilv/lilv.h");
|
||||
@cInclude("lv2/core/lv2.h");
|
||||
|
||||
@cDefine("LILV_VERSION", "0.24.4");
|
||||
});
|
||||
|
||||
const LV2_CORE_URI = "http://lv2plug.in/ns/lv2core";
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
|
||||
fn makeCStr(data: []const u8) ![]u8 {
|
||||
var allocator = std.heap.direct_allocator;
|
||||
return std.cstr.addNullByte(allocator, data);
|
||||
}
|
||||
|
||||
/// Control port value set from the command line
|
||||
const Param = struct {
|
||||
/// Port symbol
|
||||
sym: []const u8,
|
||||
|
||||
/// Control value
|
||||
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 {
|
||||
Control,
|
||||
Audio,
|
||||
};
|
||||
|
||||
/// Runtime port information.
|
||||
const Port = struct {
|
||||
lilv_port: ?*const c.LilvPort,
|
||||
ptype: PortType,
|
||||
index: u32,
|
||||
value: f32,
|
||||
is_input: bool,
|
||||
optional: bool,
|
||||
};
|
||||
|
||||
/// Application state
|
||||
const LV2Apply = struct {
|
||||
allocator: *std.mem.Allocator,
|
||||
|
||||
world: ?*c.LilvWorld = null,
|
||||
plugin: ?*const c.LilvPlugin = null,
|
||||
instance: ?*c.LilvInstance = null,
|
||||
|
||||
in_path: ?[]u8 = undefined,
|
||||
out_path: ?[]u8 = undefined,
|
||||
|
||||
in_file: ?*c.SNDFILE = null,
|
||||
out_file: ?*c.SNDFILE = null,
|
||||
|
||||
n_params: u32,
|
||||
|
||||
params: ParamList,
|
||||
|
||||
n_audio_in: u32,
|
||||
n_audio_out: u32,
|
||||
|
||||
ports: []*Port = undefined,
|
||||
|
||||
pub fn deinit(self: *LV2Apply) void {
|
||||
sclose(self.in_path, self.in_file);
|
||||
sclose(self.out_path, self.out_file);
|
||||
|
||||
if (self.instance) |instance| {
|
||||
c.lilv_instance_free(self.instance);
|
||||
}
|
||||
if (self.world) |world| {
|
||||
c.lilv_world_free(self.world);
|
||||
}
|
||||
|
||||
self.allocator.free(self.ports);
|
||||
self.params.deinit();
|
||||
}
|
||||
|
||||
pub fn makeCStr(self: *LV2Apply, data: []const u8) ![]u8 {
|
||||
return std.cstr.addNullByte(self.allocator, data);
|
||||
}
|
||||
|
||||
pub fn createPorts(self: *LV2Apply) !i32 {
|
||||
var world = self.world;
|
||||
const n_ports: u32 = c.lilv_plugin_get_num_ports(self.plugin);
|
||||
|
||||
self.ports = try self.allocator.realloc(self.ports, n_ports);
|
||||
|
||||
for (self.ports) |port_ptr, idx| {
|
||||
var port = try self.allocator.create(Port);
|
||||
port.* = Port{
|
||||
.lilv_port = null,
|
||||
.ptype = .Control,
|
||||
.index = f32(0),
|
||||
.value = f32(0),
|
||||
.is_input = false,
|
||||
.optional = false,
|
||||
};
|
||||
|
||||
self.ports[idx] = port;
|
||||
}
|
||||
|
||||
var values: []f32 = try self.allocator.alloc(f32, n_ports);
|
||||
defer self.allocator.free(values);
|
||||
|
||||
c.lilv_plugin_get_port_ranges_float(self.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 = self.ports[i];
|
||||
|
||||
const lport = c.lilv_plugin_get_port_by_index(self.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(self.plugin, lport, lv2_connectionOptional);
|
||||
|
||||
if (c.lilv_port_is_a(self.plugin, lport, lv2_InputPort)) {
|
||||
port.is_input = true;
|
||||
} else if (!c.lilv_port_is_a(self.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(self.plugin, lport, lv2_ControlPort)) {
|
||||
port.ptype = .Control;
|
||||
} else if (c.lilv_port_is_a(self.plugin, lport, lv2_AudioPort)) {
|
||||
port.ptype = .Audio;
|
||||
|
||||
if (port.is_input) {
|
||||
self.n_audio_in += 1;
|
||||
} else {
|
||||
self.n_audio_out += 1;
|
||||
}
|
||||
} else if (!port.optional) {
|
||||
std.debug.warn("Port {} has unsupported type\n", i);
|
||||
return error.UnsupportedPortType;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
fn sopen(path_opt: ?[]const u8, mode: i32, fmt: *c.SF_INFO) ?*c.SNDFILE {
|
||||
if (path_opt == null) return null;
|
||||
|
||||
var path = path_opt.?;
|
||||
var file = c.sf_open(path.ptr, mode, fmt);
|
||||
const st: i32 = c.sf_error(file);
|
||||
|
||||
if (st != 0) {
|
||||
std.debug.warn("Failed to open {} ({})\n", path, c.sf_error_number(st));
|
||||
return null;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
fn sclose(path_opt: ?[]const u8, file_opt: ?*c.SNDFILE) void {
|
||||
if (path_opt == null) return;
|
||||
if (file_opt == null) return;
|
||||
var path = path_opt.?;
|
||||
var file = file_opt.?;
|
||||
|
||||
var st: i32 = c.sf_close(file);
|
||||
if (st != 0) {
|
||||
std.debug.warn(
|
||||
"Failed to close {} ({})\n",
|
||||
path,
|
||||
c.sf_error_number(st),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///Read a single frame from a file into an interleaved buffer.
|
||||
///If more channels are required than are available in the file, the remaining
|
||||
///channels are distributed in a round-robin fashion (LRLRL).
|
||||
fn sread(file_opt: ?*c.SNDFILE, 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;
|
||||
}
|
||||
|
||||
fn print_version() !void {
|
||||
const stdout_file = try std.io.getStdOut();
|
||||
try stdout_file.write("lv2apply (lilv) 0.24.4\n");
|
||||
try stdout_file.write("Copyright 2007-2016 David Robillard <http://drobilla.net>\n");
|
||||
try stdout_file.write("Copyright 2019 Luna Mendes <http://l4.pm>\n");
|
||||
}
|
||||
|
||||
fn print_usage() !void {
|
||||
const stdout_file = try std.io.getStdOut();
|
||||
try stdout_file.write("Usage: lv2apply [OPTION]... PLUGIN_URI\n");
|
||||
try stdout_file.write("\t-i IN_FILE Input file\n");
|
||||
try stdout_file.write("\t-o OUT_FILE Output file\n");
|
||||
try stdout_file.write("\t-c SYM VAL Control value\n");
|
||||
try stdout_file.write("\t--help print help\n");
|
||||
try stdout_file.write("\t--version display version\n");
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var arena = std.heap.ArenaAllocator.init(std.heap.c_allocator);
|
||||
const allocator = &arena.allocator;
|
||||
|
||||
var in_path: ?[]u8 = null;
|
||||
var out_path: ?[]u8 = null;
|
||||
|
||||
var self = LV2Apply{
|
||||
.allocator = allocator,
|
||||
.n_params = 0,
|
||||
.params = ParamList.init(allocator),
|
||||
.n_audio_in = 0,
|
||||
.n_audio_out = 0,
|
||||
.ports = try allocator.alloc(*Port, 0),
|
||||
};
|
||||
|
||||
defer self.deinit();
|
||||
|
||||
var args_it = std.process.args();
|
||||
_ = args_it.skip();
|
||||
|
||||
var plugin_uri_opt: ?[]u8 = null;
|
||||
|
||||
while (args_it.next(allocator)) |arg_err| {
|
||||
var arg = try arg_err;
|
||||
if (std.mem.eql(u8, arg, "--version")) {
|
||||
try print_version();
|
||||
return;
|
||||
} else if (std.mem.eql(u8, arg, "--help")) {
|
||||
try print_usage();
|
||||
return;
|
||||
} else if (std.mem.eql(u8, arg, "-i")) {
|
||||
in_path = try args_it.next(allocator).?;
|
||||
} else if (std.mem.eql(u8, arg, "-o")) {
|
||||
out_path = try args_it.next(allocator).?;
|
||||
} else if (std.mem.eql(u8, arg, "-c")) {
|
||||
var param_name = try args_it.next(allocator).?;
|
||||
var value_arg = try args_it.next(allocator).?;
|
||||
|
||||
var param_value = try std.fmt.parseFloat(f32, value_arg);
|
||||
try self.params.append(Param{ .sym = param_name, .value = param_value });
|
||||
} else if (arg[0] == '-') {
|
||||
try print_usage();
|
||||
return;
|
||||
} else {
|
||||
plugin_uri_opt = arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_path == null or out_path == null or plugin_uri_opt == null) {
|
||||
try print_usage();
|
||||
return;
|
||||
}
|
||||
|
||||
self.in_path = try self.makeCStr(in_path.?);
|
||||
self.out_path = try self.makeCStr(out_path.?);
|
||||
var plugin_uri = try self.makeCStr(plugin_uri_opt.?);
|
||||
|
||||
self.world = c.lilv_world_new().?;
|
||||
var uri: *c.LilvNode = c.lilv_new_uri(self.world, plugin_uri.ptr) orelse blk: {
|
||||
std.debug.warn("Invalid plugin URI <{}>\n", plugin_uri);
|
||||
return;
|
||||
};
|
||||
|
||||
c.lilv_world_load_all(self.world);
|
||||
const plugins: *const c.LilvPlugins = c.lilv_world_get_all_plugins(self.world);
|
||||
|
||||
const plugin_opt = c.lilv_plugins_get_by_uri(plugins, uri);
|
||||
c.lilv_node_free(uri);
|
||||
|
||||
if (plugin_opt) |plugin| {
|
||||
self.plugin = plugin;
|
||||
} else {
|
||||
std.debug.warn("Plugin <{}> not found\n", plugin_uri);
|
||||
return;
|
||||
}
|
||||
|
||||
var in_fmt = c.SF_INFO{
|
||||
.frames = c_int(0),
|
||||
.samplerate = c_int(44100),
|
||||
.channels = c_int(1),
|
||||
.format = c.SF_FORMAT_ULAW | c.SF_FORMAT_RAW | c.SF_ENDIAN_BIG,
|
||||
.sections = c_int(0),
|
||||
.seekable = c_int(0),
|
||||
};
|
||||
|
||||
var in_file_opt = sopen(self.in_path, c.SFM_READ, &in_fmt);
|
||||
if (in_file_opt) |in_file| {
|
||||
self.in_file = in_file;
|
||||
} else {
|
||||
std.debug.warn("Failed to call sopen().\n");
|
||||
return;
|
||||
}
|
||||
|
||||
_ = try self.createPorts();
|
||||
|
||||
if (self.n_audio_in == 0 or
|
||||
(in_fmt.channels != @intCast(c_int, self.n_audio_in) and in_fmt.channels != 1))
|
||||
{
|
||||
std.debug.warn("unable to map {} inputs to {} ports\n", in_fmt.channels, self.n_audio_in);
|
||||
}
|
||||
|
||||
var it = self.params.iterator();
|
||||
|
||||
while (it.next()) |param| {
|
||||
var param_sym = try self.makeCStr(param.sym);
|
||||
var sym = c.lilv_new_string(self.world, param_sym.ptr);
|
||||
const port = c.lilv_plugin_get_port_by_symbol(self.plugin, sym).?;
|
||||
c.lilv_node_free(sym);
|
||||
|
||||
var idx = c.lilv_port_get_index(self.plugin, port);
|
||||
self.ports[idx].value = param.value;
|
||||
}
|
||||
|
||||
var out_fmt = c.SF_INFO{
|
||||
.frames = c_int(0),
|
||||
.samplerate = c_int(44100),
|
||||
.channels = @intCast(c_int, self.n_audio_out),
|
||||
.format = c.SF_FORMAT_ULAW | c.SF_FORMAT_RAW,
|
||||
.sections = c_int(0),
|
||||
.seekable = c_int(0),
|
||||
};
|
||||
|
||||
var out_file_opt = sopen(self.out_path, c.SFM_WRITE, &out_fmt);
|
||||
if (out_file_opt) |out_file| {
|
||||
self.out_file = out_file;
|
||||
} else {
|
||||
std.debug.warn("Failed to call sopen() for outfile.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const n_ports: u32 = c.lilv_plugin_get_num_ports(self.plugin);
|
||||
|
||||
var in_buf = try self.allocator.alloc(f32, self.n_audio_in);
|
||||
var out_buf = try self.allocator.alloc(f32, self.n_audio_out);
|
||||
|
||||
var instance_opt = c.lilv_plugin_instantiate(
|
||||
self.plugin,
|
||||
@intToFloat(f64, in_fmt.samplerate),
|
||||
null,
|
||||
);
|
||||
|
||||
if (instance_opt) |instance| {
|
||||
self.instance = instance;
|
||||
} else {
|
||||
std.debug.warn("failed to instantiate\n");
|
||||
return;
|
||||
}
|
||||
|
||||
var i: u32 = 0;
|
||||
var o: u32 = 0;
|
||||
|
||||
for (self.ports) |port, p_idx| {
|
||||
var ptype = port.ptype;
|
||||
var p = @intCast(u32, p_idx);
|
||||
|
||||
if (ptype == .Control) {
|
||||
lilv_instance_connect_port(self.instance, p, &port.value);
|
||||
} else if (ptype == .Audio) {
|
||||
if (port.is_input) {
|
||||
lilv_instance_connect_port(self.instance, p, &in_buf[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
lilv_instance_connect_port(self.instance, p, &out_buf[o]);
|
||||
o += 1;
|
||||
}
|
||||
} else {
|
||||
lilv_instance_connect_port(self.instance, p, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Ports are now connected to buffers in interleaved format, so we can run
|
||||
// a single frame at a time and avoid having to interleave buffers to
|
||||
// read/write from/to sndfile.
|
||||
lilv_instance_activate(self.instance);
|
||||
|
||||
const START = 10 * 44100;
|
||||
// for the first START frames, copy from in to out
|
||||
|
||||
i = 0;
|
||||
while (i < START) : (i += 1) {
|
||||
_ = sread(self.in_file, in_fmt.channels, in_buf);
|
||||
_ = c.sf_writef_float(self.out_file, in_buf.ptr, 1);
|
||||
}
|
||||
|
||||
// seek to START then run plugin over the rest
|
||||
var seeked = c.sf_seek(self.in_file, START, c.SEEK_SET);
|
||||
std.debug.warn("{} seeked frames\n", seeked);
|
||||
|
||||
while (sread(self.in_file, in_fmt.channels, in_buf)) {
|
||||
lilv_instance_run(self.instance, 1);
|
||||
|
||||
if (c.sf_writef_float(self.out_file, out_buf.ptr, 1) != 1) {
|
||||
std.debug.warn("failed to write to output file\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lilv_instance_deactivate(self.instance);
|
||||
return;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue