Compare commits
No commits in common. "fdbed55b80be762a4c026a46ad5febadd1cbc24d" and "3b822e4b9a79ca399c653b3773ffd33ec26d7f55" have entirely different histories.
fdbed55b80
...
3b822e4b9a
10 changed files with 285 additions and 522 deletions
125
.gitignore
vendored
125
.gitignore
vendored
|
@ -1 +1,124 @@
|
|||
zig-cache/
|
||||
# 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/
|
||||
|
|
20
build.zig
20
build.zig
|
@ -1,20 +0,0 @@
|
|||
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);
|
||||
}
|
2
scritcher/__init__.py
Normal file
2
scritcher/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .main import main
|
||||
__all__ = ['main']
|
2
scritcher/error.py
Normal file
2
scritcher/error.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class InterpreterError(Exception):
|
||||
pass
|
78
scritcher/executer.py
Normal file
78
scritcher/executer.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
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:])
|
17
scritcher/image.py
Normal file
17
scritcher/image.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
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')
|
32
scritcher/main.py
Normal file
32
scritcher/main.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
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}')
|
16
scritcher/utils.py
Normal file
16
scritcher/utils.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
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
Normal file
14
setup.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
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
501
src/main.zig
|
@ -1,501 +0,0 @@
|
|||
// 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