Compare commits
147 commits
Author | SHA1 | Date | |
---|---|---|---|
3af4dc575a | |||
c0da5e68b7 | |||
c73b98a356 | |||
7cc93c2976 | |||
ed67a52b15 | |||
0f6ac055a4 | |||
43dad638f2 | |||
ef6c68705d | |||
6b2ce7e425 | |||
2796b654e5 | |||
1987c4d497 | |||
5d1bcf33ee | |||
c817170a04 | |||
268be1074c | |||
d7d4385242 | |||
b195577dee | |||
ea3850b99d | |||
b28f208e65 | |||
2a801a129b | |||
78cc7fab4b | |||
a9e4c5823a | |||
79d7e137d2 | |||
73c5214146 | |||
32b01976d8 | |||
9937365433 | |||
667e6cbdac | |||
2acb45fdca | |||
ebf716de17 | |||
5e8093b26f | |||
7097334201 | |||
c2834f8254 | |||
77cceab288 | |||
9527426d48 | |||
0cd47be7ac | |||
708ef45600 | |||
f61e9b013f | |||
4adf80e51a | |||
353d8d6947 | |||
8aa65958d0 | |||
fa7e993ec2 | |||
973d6e63c5 | |||
8d312cd987 | |||
c80a6d0df7 | |||
0ba2c5ffcc | |||
18924adac4 | |||
6d8614e678 | |||
af0ea574e1 | |||
1b59705eae | |||
a2ea8fb53e | |||
c78ca9dd5b | |||
dc98c7a22f | |||
aeb76fe6c0 | |||
542ba75b01 | |||
c7eb70a06f | |||
0240b10a3c | |||
d6c92c0231 | |||
5235482ab4 | |||
ee7ffd1be7 | |||
0453f37305 | |||
7f008db540 | |||
b06bab9ec5 | |||
f973d6807d | |||
128f58c502 | |||
e71eba583e | |||
9cb82e3180 | |||
b00ab8e839 | |||
0b816a512e | |||
b0525f2386 | |||
54919110a5 | |||
76b353e593 | |||
b238517b33 | |||
36937a5fde | |||
72379e63ee | |||
690ab89cfd | |||
30da41293a | |||
89afa8af10 | |||
9801e303c0 | |||
2b4f428890 | |||
83996b889f | |||
10b2c69605 | |||
303a40758d | |||
1c1e525b1d | |||
1fac8c7312 | |||
e8808c501b | |||
e669b74ffb | |||
ca751e58f7 | |||
82dc99d7d5 | |||
d9358ed794 | |||
8ce844ceed | |||
7543ecafaa | |||
325e7b1102 | |||
0de2d05fa3 | |||
3a7009f9bf | |||
c6c31f5a60 | |||
8e0ebbe5e0 | |||
d09603c005 | |||
5412934f27 | |||
b5512c45fb | |||
dd1b493da2 | |||
dca5d7b644 | |||
b21960d372 | |||
94c9695110 | |||
f9c1851734 | |||
9c6387973f | |||
d518369314 | |||
7d519b73b6 | |||
6497fc1dd8 | |||
eb18d01cdd | |||
47a97636cd | |||
c3b83c3e4b | |||
f20079b807 | |||
154032f9b1 | |||
27f499dfbb | |||
d86e9efe43 | |||
e462b4e9d6 | |||
c8f949fe96 | |||
3de84b5a23 | |||
0c2cd5638e | |||
043556e798 | |||
9614b96c71 | |||
8bb09f1573 | |||
8261d202bd | |||
fe7e347762 | |||
2c0716e11d | |||
08d9923e75 | |||
bc8ab98689 | |||
127ea389fd | |||
a0a75579dd | |||
8c3c5a3ac2 | |||
299a39fc27 | |||
c73b7440a6 | |||
fa8740e0db | |||
046e43a68c | |||
519eb51206 | |||
5a0e6934e7 | |||
7dae50b030 | |||
7c4dead5cf | |||
9a6caa2453 | |||
182d368363 | |||
a10416e56f | |||
09471e564a | |||
46d576fc13 | |||
c57c40db61 | |||
a411659d6b | |||
fb0516a1db | |||
91de05d3b1 | |||
1c17b714a7 |
34 changed files with 1610 additions and 5110 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
||||||
zig-cache/
|
zig-cache/
|
||||||
|
zig-out/
|
||||||
*.mp3
|
*.mp3
|
||||||
*.wav
|
*.wav
|
||||||
|
build_runner.zig
|
||||||
|
|
16
README.md
16
README.md
|
@ -20,6 +20,9 @@ glitch art "framework", ???????? language??? something?
|
||||||
will likely be there by default)
|
will likely be there by default)
|
||||||
- the SWH plugins ( https://github.com/swh/lv2 )
|
- the SWH plugins ( https://github.com/swh/lv2 )
|
||||||
- the Invada Studio plugins ( https://launchpad.net/invada-studio/ )
|
- the Invada Studio plugins ( https://launchpad.net/invada-studio/ )
|
||||||
|
- abGate plugin
|
||||||
|
- MDA plugins
|
||||||
|
- Calf plugins
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# build and install
|
# build and install
|
||||||
|
@ -48,10 +51,17 @@ $your_image_viewer blah_g1.bmp
|
||||||
using repl works via `scritcher repl scri_file.scri input_image.bmp`
|
using repl works via `scritcher repl scri_file.scri input_image.bmp`
|
||||||
|
|
||||||
you type commands as you'd write the specific scritcher commands
|
you type commands as you'd write the specific scritcher commands
|
||||||
(`doc/README.md`), with three repl-specific ones:
|
(`doc/README.md`), with four repl-specific ones (semicolons do not apply):
|
||||||
- `push`, to push the last written command to the queue
|
- `push`, to push the last written command to the queue
|
||||||
- `save`, to write the queue to the given `scri_file.scri` file
|
- `save`, to write the queue to the given `scri_file.scri` file
|
||||||
- `list`, to print the current contents of the queue
|
- `list`, to print the current contents of the queue
|
||||||
|
- `quit`, to exit
|
||||||
|
|
||||||
this allows for quicker iteration of commands, as you can type a command, tweak
|
After a non-REPL command, such as an effect, the program pointed by
|
||||||
its arguments, and when satisfied, `push` it, and work on the next one, etc.
|
`SCRITCHER_RUNNER` will run as argument to the `runqs` command. By default,
|
||||||
|
the program run will be `ristretto` (as it is my preffered image viewer,
|
||||||
|
considering it was able to handle when some images went broke)
|
||||||
|
|
||||||
|
this allows for quicker iteration of commands, as you can type a command, see
|
||||||
|
the image changes as fast as possible, tweak its arguments,
|
||||||
|
and when satisfied, `push` it, and work on the next command, etc.
|
||||||
|
|
68
build.zig
68
build.zig
|
@ -1,7 +1,6 @@
|
||||||
const builds = @import("std").build;
|
const std = @import("std");
|
||||||
const Builder = @import("std").build.Builder;
|
|
||||||
|
|
||||||
fn setupLinks(step: *builds.LibExeObjStep) void {
|
fn setupLinks(step: *std.Build.Step.Compile) void {
|
||||||
step.linkSystemLibrary("c");
|
step.linkSystemLibrary("c");
|
||||||
|
|
||||||
step.linkSystemLibrary("lilv-0");
|
step.linkSystemLibrary("lilv-0");
|
||||||
|
@ -11,27 +10,64 @@ fn setupLinks(step: *builds.LibExeObjStep) void {
|
||||||
step.linkSystemLibrary("GraphicsMagickWand");
|
step.linkSystemLibrary("GraphicsMagickWand");
|
||||||
step.linkSystemLibrary("GraphicsMagick");
|
step.linkSystemLibrary("GraphicsMagick");
|
||||||
|
|
||||||
step.addIncludeDir("/usr/include/lilv-0");
|
step.addIncludePath(.{ .path = "/usr/include/GraphicsMagick" });
|
||||||
step.addIncludeDir("/usr/include/GraphicsMagick");
|
step.addIncludePath(.{ .path = "/usr/include" });
|
||||||
|
|
||||||
|
const possible_lilv_include_dirs = [_][]const u8{
|
||||||
|
"/usr/include/lilv-0/lilv",
|
||||||
|
"/usr/include/lilv-0",
|
||||||
|
};
|
||||||
|
|
||||||
|
var found_any_lilv = false;
|
||||||
|
|
||||||
|
for (possible_lilv_include_dirs) |possible_lilv_dir| {
|
||||||
|
var possible_dir = std.fs.cwd().openDir(possible_lilv_dir, .{}) catch |err| {
|
||||||
|
std.debug.print("possible lilv {s} fail: {s}\n", .{ possible_lilv_dir, @errorName(err) });
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
possible_dir.close();
|
||||||
|
found_any_lilv = true;
|
||||||
|
|
||||||
|
std.debug.print("found lilv at '{s}'\n", .{possible_lilv_dir});
|
||||||
|
step.addIncludePath(.{ .path = possible_lilv_dir });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found_any_lilv) {
|
||||||
|
std.debug.print("No LILV library was found :(\n", .{});
|
||||||
|
@panic("no lilv found");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(b: *Builder) void {
|
pub fn build(b: *std.Build) void {
|
||||||
const mode = b.standardReleaseOptions();
|
const target = b.standardTargetOptions(.{});
|
||||||
const exe = b.addExecutable("scritcher", "src/main.zig");
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
exe.setBuildMode(mode);
|
|
||||||
exe.install();
|
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "scritcher",
|
||||||
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
setupLinks(exe);
|
setupLinks(exe);
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
const test_obj_step = b.addTest("src/main.zig");
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
setupLinks(test_obj_step);
|
|
||||||
|
|
||||||
const run_cmd = exe.run();
|
if (b.args) |args| {
|
||||||
run_cmd.step.dependOn(b.getInstallStep());
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
const run_step = b.step("run", "Run the app");
|
const run_step = b.step("run", "Run the app");
|
||||||
run_step.dependOn(&run_cmd.step);
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
const test_step = b.step("test", "Run tests");
|
const test_step = b.addTest(.{
|
||||||
test_step.dependOn(&test_obj_step.step);
|
.root_source_file = b.path("src/main.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
setupLinks(test_step);
|
||||||
|
const run_unit_tests = b.addRunArtifact(test_step);
|
||||||
|
const test_cmd = b.step("test", "run unit tests");
|
||||||
|
test_cmd.dependOn(&run_unit_tests.step);
|
||||||
}
|
}
|
||||||
|
|
206
doc/README.md
206
doc/README.md
|
@ -23,7 +23,8 @@ where in the file you want the plugin to be ran.
|
||||||
so, if you did `plugin 3 1...`, it would split the file in 3, then get the
|
so, if you did `plugin 3 1...`, it would split the file in 3, then get the
|
||||||
part that is of index 1 (starting at 0).
|
part that is of index 1 (starting at 0).
|
||||||
|
|
||||||
**Keep in mind parts start from the bottom of the file.**
|
**Keep in mind parts can start from either top or bottom of the image,
|
||||||
|
it depends of the file format**
|
||||||
|
|
||||||
## `load path_or_arg`
|
## `load path_or_arg`
|
||||||
|
|
||||||
|
@ -166,6 +167,11 @@ Rotate the image by `deg` degrees, filling the resulting triangles with `bgfill`
|
||||||
|
|
||||||
`bgfill` is a hex string, e.g `#000000`.
|
`bgfill` is a hex string, e.g `#000000`.
|
||||||
|
|
||||||
|
## `embed split index path_to_file`
|
||||||
|
|
||||||
|
embed an audio file in the given image. the file is opened with libsndfile,
|
||||||
|
which means some formats, like mp3, will not be opened.
|
||||||
|
|
||||||
## `quicksave`
|
## `quicksave`
|
||||||
|
|
||||||
Save the file on the same directory of the file specified by `load`, but
|
Save the file on the same directory of the file specified by `load`, but
|
||||||
|
@ -173,3 +179,201 @@ with a suffix on the filename (before extension).
|
||||||
|
|
||||||
Doing consecutive `quicksave`s will not overwrite any files, the suffixes will
|
Doing consecutive `quicksave`s will not overwrite any files, the suffixes will
|
||||||
just be different.
|
just be different.
|
||||||
|
|
||||||
|
## `gate split index switch threshold attack hold decay gaterange`
|
||||||
|
|
||||||
|
**TODO:** find good parameters
|
||||||
|
|
||||||
|
- switch (bool): 0..1, default 0
|
||||||
|
- threshold (dB): -70..12, default -70
|
||||||
|
- attack (ms): 0.1..500, default 30
|
||||||
|
- hold (ms): 5..3000, default 500
|
||||||
|
- decay (ms): 5..4000, default 1000
|
||||||
|
- gaterange (dB): -90..-20, default -90
|
||||||
|
|
||||||
|
## `detune split index detune mix output latency`
|
||||||
|
|
||||||
|
> A low-quality stereo pitch shifter for the sort of chorus and detune effects found on multi-effects hardware.
|
||||||
|
|
||||||
|
- detune (cents, left channel is lowered in pitch, right channel is raised): 0..1, default 0.2
|
||||||
|
- mix (wet/dry mix): 0..1, default 0.9
|
||||||
|
- output (level trim): 0..1, default 0.5
|
||||||
|
- latency (trade-off between latency and low-freq response): 0..1, default 0.5
|
||||||
|
|
||||||
|
other presets:
|
||||||
|
- stereo detune: 0.2 0.9 0.5 0.5
|
||||||
|
- out of tune: 0.8 0.7 0.5 0.5
|
||||||
|
|
||||||
|
|
||||||
|
## `overdrive split index drive muffle output`
|
||||||
|
|
||||||
|
> Possible uses include adding body to drum loops, fuzz guitar, and that 'standing outside a nightclub' sound. This plug does not simulate valve distortion, and any attempt to process organ sounds through it will be extremely unrewarding!
|
||||||
|
|
||||||
|
- drive (amount of distortion): 0..1, default 0
|
||||||
|
- muffle (gentle low-pass filter): 0..1, default 0
|
||||||
|
- output (level trim): 0..1, default 0.5
|
||||||
|
|
||||||
|
## `degrade split index headroom quant rate post_filt non_lin output`
|
||||||
|
|
||||||
|
> Sample quality reduction
|
||||||
|
|
||||||
|
**NOTE:** finding the right parameters is HARD for this plugin.
|
||||||
|
|
||||||
|
- headroom (peak clipping threshold): 0..1, default 0.8
|
||||||
|
- quant (bit depth, typically 8 or below for "telephone" quality): 0..1, default 0.5
|
||||||
|
- rate (sample rate): 0..1, default 0.65
|
||||||
|
- post_filt (low-pass filter to muffle the distortion): 0..1, default 0.9
|
||||||
|
- non_lin (additional harmonic distortion "thickening"): 0..1, default 0.58
|
||||||
|
- output: 0..1, default 0.5
|
||||||
|
|
||||||
|
## `repsycho split index tune fine decay thresh hold mix quality`
|
||||||
|
|
||||||
|
**NOTE:** HARD to find good parameters
|
||||||
|
|
||||||
|
- tune (coarse tune, semitones): 0..1, default 1
|
||||||
|
- fine (fine tune, cents): 0..1, default 1
|
||||||
|
- decay (adjust envelope of each trunk, a fast decay can be useful while setting up): 0..1, default 0.5
|
||||||
|
- thresh (trigger level to divide the input into chunks): 0..1, default 0.6
|
||||||
|
- hold (minimum chunk length): 0..1, default 0.45
|
||||||
|
- mix (mix original signal with output): 0..1, default 1
|
||||||
|
- quality (quality, bool. the high=1 setting uses smoother pitch-shifting and allows stereo): 0..1, default 0
|
||||||
|
|
||||||
|
## `talkbox split index wet dry carrier quality`
|
||||||
|
|
||||||
|
> High resolution vocoder
|
||||||
|
|
||||||
|
- wet: 0..1, default 0.5
|
||||||
|
- dry: 0..1, default 0
|
||||||
|
- carrier: 0..1, default 0
|
||||||
|
- quality: 0..1, default 1
|
||||||
|
|
||||||
|
## `dyncomp split index enable hold inputgain threshold ratio attack release gain_min gain_max rms`
|
||||||
|
|
||||||
|
- enable (bool): 0..1, default 1
|
||||||
|
- hold (bool): 0..1, default 0
|
||||||
|
- inputgain (dB): -10..30, default 0
|
||||||
|
- threshold (dB): -50..-10, default -30
|
||||||
|
- ratio (???): 0..1, default 0
|
||||||
|
- attack (seconds): 0.001..0.1, default 0.01
|
||||||
|
- release (seconds): 0.03..3.0, default 0.3
|
||||||
|
- gain\_min (dB): -20..40
|
||||||
|
- gain\_max (dB): -20..40
|
||||||
|
- rms (signal level, dB): -80..10
|
||||||
|
|
||||||
|
## `thruzero split index rate mix feedback depth_mod`
|
||||||
|
|
||||||
|
> Tape flanger and ADT
|
||||||
|
|
||||||
|
> This plug simulates tape-flanging, where two copies of a signal cancel out completely as the tapes pass each other. It can also be used for other "modulated delay" effects such as phasing and simple chorusing.
|
||||||
|
|
||||||
|
- rate (modulation rate, set to minimum for static comb filtering): 0..1, default 0.3
|
||||||
|
- mix (wet/dry mix, set to 50% for complete cancelling): 0..1, default 0.47
|
||||||
|
- feedback (add positive or negative feedback for harsher or "ringing" sound): 0..1, default 0.3
|
||||||
|
- depth_mod (modulation depth, set to less than 100% to limit build up of low frequencies with feedback): 0..1, default 1
|
||||||
|
|
||||||
|
## `foverdrive split index drive`
|
||||||
|
|
||||||
|
Fast Overdrive from SWH plugins.
|
||||||
|
|
||||||
|
- drive: 1..3, default 1
|
||||||
|
|
||||||
|
## `gverb split index roomsize revtime damping drylevel earlylevel taillevel`
|
||||||
|
|
||||||
|
GVerb algorithm from SWH plugins.
|
||||||
|
|
||||||
|
- roomsize (meters): 1..300, default 75.75
|
||||||
|
- revtime (reverb time, seconds): 0.1..30, default 7.575
|
||||||
|
- damping: 0..1, default 0.5
|
||||||
|
- inputbandwidth: 0..1, default 0.75
|
||||||
|
- drylevel (dB): -70..0, default 0
|
||||||
|
- earlylevel (dB): -70..0, default 0
|
||||||
|
- taillevel (dB): -70..0, default -17.5
|
||||||
|
|
||||||
|
## `invert split index`
|
||||||
|
|
||||||
|
> A utility plugin that inverts the signal, also (wrongly) known as a 180 degree phase shift.
|
||||||
|
|
||||||
|
## `tapedelay split index speed da_db t1d t1a_db...`
|
||||||
|
|
||||||
|
**TODO:** gives 0 output
|
||||||
|
|
||||||
|
> Correctly models the tape motion and some of the smear effect, there is no simulation fo the head saturation yet, as I don't have a good model of it. When I get one I will add it.
|
||||||
|
|
||||||
|
> The way the tape accelerates and decelerates gives a nicer delay effect for many purposes.
|
||||||
|
|
||||||
|
- speed (inches/sec, 1=normal): 0..10, default 1
|
||||||
|
- da\_db (dry level, dB): -90..0, default -90
|
||||||
|
- t1d (tap 1 distance, inches): 0..4, default 0
|
||||||
|
- t1a\_db (tap 1 level, dB): -90..0, default 0
|
||||||
|
- t2d (tap 2 distance, inches): 0..4, default 1
|
||||||
|
- t2a\_db (tap 2 level, dB): -90..0, default -90
|
||||||
|
- t3d (tap 3 distance, inches): 0..4, default 2
|
||||||
|
- t3a\_db (tap 3 level, dB): -90..0, default -90
|
||||||
|
- t4d (tap 4 distance, inches): 0..4, default 3
|
||||||
|
- t4a\_db (tap 4 level, dB): -90..0, default -90
|
||||||
|
|
||||||
|
## `moddelay split index base`
|
||||||
|
|
||||||
|
> A delay whose tap can be modulated at audio rate.
|
||||||
|
|
||||||
|
- base (base delay, seconds): 0..1, default 1
|
||||||
|
|
||||||
|
## `multichorus split index min_delay mod_depth mod_rate stereo voices vphase amount dry freq freq2 q overlap level_in level_out lfo`
|
||||||
|
|
||||||
|
Calf Multi Chorus
|
||||||
|
|
||||||
|
- `min_delay` (ms): 0.1..10, default 5
|
||||||
|
- `mod_depth` (ms): 0.1..10, default 6
|
||||||
|
- `mod_rate` (hz): 0.1..20, default 0.1
|
||||||
|
- `stereo` (degrees): 0..360, default 180
|
||||||
|
- `voices`: 1..8, default 4
|
||||||
|
- `vphase` (inter-voice phase, degrees): 0..360, default 64
|
||||||
|
- `amount`: 0..4, default 0.5
|
||||||
|
- `dry`: 0..4, default 0.5
|
||||||
|
- `freq` (center frq 1, hz): 10..20000, default 100
|
||||||
|
- `freq2` (center frq 2, hz): 10..20000, default 5000
|
||||||
|
- `q` (???): 0.125..8, default 0.125
|
||||||
|
- `overlap`: 0..1, default 0.75
|
||||||
|
- `level_in` (Input Gain): 0.0156250..64, default 1
|
||||||
|
- `level_out` (Output Gain): 0.0156250..64, default 1
|
||||||
|
- `lfo` (toggle): 0..1, default 1
|
||||||
|
|
||||||
|
## `saturator split index bypass level_in level_out mix drive blend lp_pre_freq hp_pre_fre lp_post_freq hp_post_freq p_freq p_level p_q pre post`
|
||||||
|
|
||||||
|
- `bypass` (toggle): 0..1, default 0
|
||||||
|
- `level_in` (Input Gain): 0.0156250..64, default 1
|
||||||
|
- `level_out` (Output Gain): 0.0156250..64, default 1
|
||||||
|
- `mix`: 0..1, default 1
|
||||||
|
- `drive` (saturation, coef): 0.1..10, default 5
|
||||||
|
- `blend` (coef): -10..10, default 10
|
||||||
|
- `lp_pre_freq` (lowpass, hz): 10..20000, default 20000
|
||||||
|
- `hp_pre_freq` (highpass, hz): 10..20000, default 10
|
||||||
|
- `lp_post_freq` (lowpass, hz): 10..20000, default 20000
|
||||||
|
- `hp_post_freq` (highpass, hz): 10..20000, default 10
|
||||||
|
- `p_freq` (Tone, hz): 80..8000, default 2000
|
||||||
|
- `p_level` (Amount): 0.0625..16, default 1
|
||||||
|
- `p_q` (???, coef): 0.1..10, default 1
|
||||||
|
- `pre` (Activate Pre, toggle): 0..1, default 0
|
||||||
|
- `post` (Activate Post, toggle): 0..1, default 0
|
||||||
|
|
||||||
|
## `vintagedelay split index ...`
|
||||||
|
|
||||||
|
- `level_in` (Input Gain): 0.0156250..64, default 1
|
||||||
|
- `level_out` (Output Gain): 0.0156250..64, default 1
|
||||||
|
- `subdiv` (int): 1..16, default 4
|
||||||
|
- `time_l` (int): 1..16, default 3
|
||||||
|
- `time_r` (int): 1..16, default 5
|
||||||
|
- `feedback`: 0..1, default 0.5
|
||||||
|
- `amount` (Wet): 0..4, default 0.25
|
||||||
|
- `mix_mode` (enum): Stereo=0, Ping-Pong=1, L then R=2, R then L=3, default 1
|
||||||
|
- `medium` (enum): Plain=0, Tape=1, Old Tape=2, default 1
|
||||||
|
- `dry` (dry): 0..4, default 1
|
||||||
|
- `width` (stereo width, strict): -1..1, default 1
|
||||||
|
- `fragmentation` (enum): Repeating=0, Pattern=1, default 0
|
||||||
|
- `pbeats` (Pattern Beats, int): 1..8, default 4
|
||||||
|
- `pfrag` (Pattern Fragmentation, int): 1..8, default 4
|
||||||
|
- `timing` (enum): BPM=0, ms=1, Hz=2, Sync=3, default 0
|
||||||
|
- `bpm`: 30..300, default 120
|
||||||
|
- `ms` (int): 10..2000, default 500
|
||||||
|
- `hz`: 0.01..100, default 2
|
||||||
|
- `bpm_host` (strict): 1..300, default 120
|
||||||
|
|
8
examples/degrade.scri
Normal file
8
examples/degrade.scri
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
load :0;
|
||||||
|
degrade 8 1 0.8 0.5 0.65 0.9 0.58 0.5;
|
||||||
|
degrade 8 2 0.1 1 0.65 0.5 0.5 0.4;
|
||||||
|
degrade 8 3 0.1 1 0.65 0.9 0.58 0.5;
|
||||||
|
degrade 8 4 0 1 1 0 0 1;
|
||||||
|
degrade 8 5 0 1 1 0 0 0;
|
||||||
|
degrade 8 6 0 0 0 0 0 0;
|
||||||
|
quicksave;
|
3
examples/detune.scri
Normal file
3
examples/detune.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
detune 3 1 0.2 0.9 0.5 0.5;
|
||||||
|
quicksave;
|
3
examples/dyncomp.scri
Normal file
3
examples/dyncomp.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
dyncomp 3 1 1 0 0 -30 0 0.01 0.3 0 0 0;
|
||||||
|
quicksave;
|
3
examples/foverdrive.scri
Normal file
3
examples/foverdrive.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
foverdrive 3 1 100;
|
||||||
|
quicksave;
|
3
examples/gate.scri
Normal file
3
examples/gate.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
gate 3 1 0 -70 30 500 1000 -90;
|
||||||
|
quicksave;
|
3
examples/gverb.scri
Normal file
3
examples/gverb.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
gverb 3 1 75.75 7.575 0.5 0.75 0 0 -17.5;
|
||||||
|
quicksave;
|
3
examples/invert.scri
Normal file
3
examples/invert.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
invert 3 1;
|
||||||
|
quicksave;
|
|
@ -1,3 +1,3 @@
|
||||||
load :0;
|
load :0;
|
||||||
mbeq 3 1 1 4 0.2 2.1;
|
mbeq 3 1 0 0 0 0.3 0 0 7 0 0 0 0 0 0.1 0 0;
|
||||||
quicksave;
|
quicksave;
|
||||||
|
|
3
examples/moddelay.scri
Normal file
3
examples/moddelay.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
moddelay 3 1 1;
|
||||||
|
quicksave;
|
3
examples/multichorus.scri
Normal file
3
examples/multichorus.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
multichorus 3 1 5 6 0.1 180 4 64 0.5 0.5 100 5000 0.125 0.75 1 1 1;
|
||||||
|
quicksave;
|
5
examples/overdrive.scri
Normal file
5
examples/overdrive.scri
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
load :0;
|
||||||
|
overdrive 5 1 0.5 0 0.4;
|
||||||
|
overdrive 5 2 0.6 0.2 0.3;
|
||||||
|
overdrive 5 3 0.7 0.3 0.2;
|
||||||
|
quicksave;
|
8
examples/repsycho.scri
Normal file
8
examples/repsycho.scri
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
load :0;
|
||||||
|
repsycho 8 1 1 1 0.5 0.6 0.45 1 0;
|
||||||
|
repsycho 8 2 1 1 1 0.6 0.45 1 0;
|
||||||
|
repsycho 8 3 1 1 0.5 0.8 0.45 1 0;
|
||||||
|
repsycho 8 4 1 1 0.5 0.6 0.1 1 0;
|
||||||
|
repsycho 8 5 1 1 0.5 0.6 0.45 0.5 0;
|
||||||
|
repsycho 8 6 1 1 0.5 0.6 0.45 1 1;
|
||||||
|
quicksave;
|
3
examples/rflanger.scri
Normal file
3
examples/rflanger.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
rflanger 3 1 2.5 1;
|
||||||
|
quicksave;
|
3
examples/saturator.scri
Normal file
3
examples/saturator.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
saturator 3 1 0 1 1 1 5 10 20000 10 20000 10 2000 1 1 0 0;
|
||||||
|
quicksave;
|
3
examples/talkbox.scri
Normal file
3
examples/talkbox.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
talkbox 3 1 0.5 0 0 1;
|
||||||
|
quicksave;
|
3
examples/tapedelay.scri
Normal file
3
examples/tapedelay.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
tapedelay 3 1 1 -90 0 0 1 -90 2 -90 3 -90;
|
||||||
|
quicksave;
|
3
examples/thruzero.scri
Normal file
3
examples/thruzero.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
thruzero 3 1 0.3 0.47 0.3 1;
|
||||||
|
quicksave;
|
3
examples/vintagedelay.scri
Normal file
3
examples/vintagedelay.scri
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
load :0;
|
||||||
|
vintagedelay 3 1 1 1 4 3 5 0.5 0.25 1 1 1 1 0 4 4 0 120 500 2 120;
|
||||||
|
quicksave;
|
|
@ -1,11 +1,10 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const log = std.log.scoped(.scritcher_bmp);
|
||||||
pub const BMPValidError = error{InvalidMagic};
|
pub const BMPValidError = error{InvalidMagic};
|
||||||
|
|
||||||
const VALID_MAGICS = [_][]const u8{
|
const VALID_MAGICS = [_][]const u8{
|
||||||
"BM",
|
"BM",
|
||||||
"BA",
|
"BA",
|
||||||
|
|
||||||
"CI",
|
"CI",
|
||||||
"CP",
|
"CP",
|
||||||
"IC",
|
"IC",
|
||||||
|
@ -20,7 +19,7 @@ pub fn magicValid(magic: []const u8) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
std.debug.warn("\tINVALID HEADER: '{}'\n", magic);
|
log.debug("\tINVALID HEADER: '{s}'", .{magic});
|
||||||
return BMPValidError.InvalidMagic;
|
return BMPValidError.InvalidMagic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ const lv2 = @import("lv2_helpers.zig");
|
||||||
const plugins = @import("plugin.zig");
|
const plugins = @import("plugin.zig");
|
||||||
const image = @import("image.zig");
|
const image = @import("image.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_custom);
|
||||||
const c = lv2.c;
|
const c = lv2.c;
|
||||||
|
|
||||||
const RunBuffers = plugins.RunBuffers;
|
const RunBuffers = plugins.RunBuffers;
|
||||||
|
@ -11,23 +12,20 @@ const RunBuffers = plugins.RunBuffers;
|
||||||
pub const RandomNoise = struct {
|
pub const RandomNoise = struct {
|
||||||
r: std.rand.DefaultPrng,
|
r: std.rand.DefaultPrng,
|
||||||
rand_buf: ?[]f32 = null,
|
rand_buf: ?[]f32 = null,
|
||||||
allocator: ?*std.mem.Allocator = null,
|
allocator: ?std.mem.Allocator = null,
|
||||||
cnt: usize = 0,
|
cnt: usize = 0,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
params: *plugins.ParamMap,
|
params: anytype,
|
||||||
) ?RandomNoise {
|
) ?RandomNoise {
|
||||||
const seed = @floatToInt(u64, params.get("seed").?.value);
|
var r = std.rand.DefaultPrng.init(params.seed);
|
||||||
const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value);
|
|
||||||
|
|
||||||
var r = std.rand.DefaultPrng.init(seed);
|
if (params.fill_bytes > 0) {
|
||||||
|
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
|
||||||
|
|
||||||
if (fillbytes > 0) {
|
for (rand_buf, 0..) |_, idx| {
|
||||||
var rand_buf = allocator.alloc(f32, fillbytes) catch return null;
|
rand_buf[idx] = r.random().float(f32);
|
||||||
|
|
||||||
for (rand_buf) |_, idx| {
|
|
||||||
rand_buf[idx] = r.random.float(f32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return RandomNoise{
|
return RandomNoise{
|
||||||
|
@ -54,7 +52,7 @@ pub const RandomNoise = struct {
|
||||||
bufs.out[0] = rand_buf[self.cnt];
|
bufs.out[0] = rand_buf[self.cnt];
|
||||||
self.cnt += 1;
|
self.cnt += 1;
|
||||||
} else {
|
} else {
|
||||||
bufs.out[0] = self.r.random.float(f32);
|
bufs.out[0] = self.r.random().float(f32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -62,23 +60,20 @@ pub const RandomNoise = struct {
|
||||||
pub const WildNoise = struct {
|
pub const WildNoise = struct {
|
||||||
r: std.rand.DefaultPrng,
|
r: std.rand.DefaultPrng,
|
||||||
rand_buf: ?[]f32 = null,
|
rand_buf: ?[]f32 = null,
|
||||||
allocator: ?*std.mem.Allocator = null,
|
allocator: ?std.mem.Allocator = null,
|
||||||
cnt: usize = 0,
|
cnt: usize = 0,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
params: *plugins.ParamMap,
|
params: anytype,
|
||||||
) ?WildNoise {
|
) ?WildNoise {
|
||||||
const seed = @floatToInt(u64, params.get("seed").?.value);
|
var r = std.rand.DefaultPrng.init(params.seed);
|
||||||
const fillbytes = @floatToInt(usize, params.get("fill_bytes").?.value);
|
|
||||||
|
|
||||||
var r = std.rand.DefaultPrng.init(seed);
|
if (params.fill_bytes > 0) {
|
||||||
|
var rand_buf = allocator.alloc(f32, params.fill_bytes) catch return null;
|
||||||
|
|
||||||
if (fillbytes > 0) {
|
for (rand_buf, 0..) |_, idx| {
|
||||||
var rand_buf = allocator.alloc(f32, fillbytes) catch return null;
|
rand_buf[idx] = @as(f32, @floatFromInt(r.random().int(u1)));
|
||||||
|
|
||||||
for (rand_buf) |_, idx| {
|
|
||||||
rand_buf[idx] = @intToFloat(f32, r.random.int(u1));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return WildNoise{
|
return WildNoise{
|
||||||
|
@ -104,7 +99,7 @@ pub const WildNoise = struct {
|
||||||
bufs.out[0] = rand_buf[self.cnt];
|
bufs.out[0] = rand_buf[self.cnt];
|
||||||
self.cnt += 1;
|
self.cnt += 1;
|
||||||
} else {
|
} else {
|
||||||
bufs.out[0] = @intToFloat(f32, self.r.random.int(u1));
|
bufs.out[0] = @as(f32, @floatFromInt(self.r.random().int(u1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -117,16 +112,18 @@ pub const Write = struct {
|
||||||
data: f32,
|
data: f32,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
params: *plugins.ParamMap,
|
params: anytype,
|
||||||
) Write {
|
) Write {
|
||||||
const data = params.get("data").?;
|
_ = allocator;
|
||||||
return Write{
|
return Write{
|
||||||
.data = data.value,
|
.data = params.data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Write) void {}
|
pub fn deinit(self: *Write) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(self: *Write, bufs: *RunBuffers) void {
|
pub fn run(self: *Write, bufs: *RunBuffers) void {
|
||||||
bufs.out[0] = self.data;
|
bufs.out[0] = self.data;
|
||||||
|
@ -134,27 +131,27 @@ pub const Write = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Embed = struct {
|
pub const Embed = struct {
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
filepath: []const u8,
|
filepath: []const u8,
|
||||||
|
|
||||||
sndfile: *c.SNDFILE = undefined,
|
sndfile: *c.SNDFILE = undefined,
|
||||||
buf: []f32 = undefined,
|
buf: []f32 = undefined,
|
||||||
|
|
||||||
pub fn init(allocator: *std.mem.Allocator, filepath: []const u8) @This() {
|
pub fn init(allocator: std.mem.Allocator, params: anytype) @This() {
|
||||||
return Embed{
|
return Embed{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.filepath = filepath,
|
.filepath = params.path,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup(self: *@This()) !void {
|
pub fn setup(self: *@This()) !void {
|
||||||
var in_fmt = c.SF_INFO{
|
var in_fmt = c.SF_INFO{
|
||||||
.frames = c_int(0),
|
.frames = @as(c_int, 0),
|
||||||
.samplerate = c_int(0),
|
.samplerate = @as(c_int, 0),
|
||||||
.channels = c_int(0),
|
.channels = @as(c_int, 0),
|
||||||
.format = c_int(0),
|
.format = @as(c_int, 0),
|
||||||
.sections = c_int(0),
|
.sections = @as(c_int, 0),
|
||||||
.seekable = c_int(0),
|
.seekable = @as(c_int, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sndfile = try image.sopen(
|
self.sndfile = try image.sopen(
|
||||||
|
@ -166,10 +163,12 @@ pub const Embed = struct {
|
||||||
|
|
||||||
image.sseek(self.sndfile, 0);
|
image.sseek(self.sndfile, 0);
|
||||||
|
|
||||||
self.buf = try self.allocator.alloc(f32, @intCast(usize, in_fmt.channels));
|
self.buf = try self.allocator.alloc(f32, @as(usize, @intCast(in_fmt.channels)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *@This()) void {}
|
pub fn deinit(self: *@This()) void {
|
||||||
|
_ = self;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run(self: *@This(), bufs: *RunBuffers) void {
|
pub fn run(self: *@This(), bufs: *RunBuffers) void {
|
||||||
const read_bytes = c.sf_readf_float(self.sndfile, self.buf.ptr, 1);
|
const read_bytes = c.sf_readf_float(self.sndfile, self.buf.ptr, 1);
|
||||||
|
@ -181,11 +180,10 @@ pub const Embed = struct {
|
||||||
|
|
||||||
if (read_bytes < 0) {
|
if (read_bytes < 0) {
|
||||||
const st: i32 = c.sf_error(self.sndfile);
|
const st: i32 = c.sf_error(self.sndfile);
|
||||||
std.debug.warn(
|
log.debug("Failed to read {s} ({s})", .{
|
||||||
"Failed to read {} ({})\n",
|
|
||||||
self.filepath,
|
self.filepath,
|
||||||
c.sf_error_number(st),
|
c.sf_error_number(st),
|
||||||
);
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
233
src/image.zig
233
src/image.zig
|
@ -3,11 +3,9 @@ const lv2 = @import("lv2_helpers.zig");
|
||||||
const c = lv2.c;
|
const c = lv2.c;
|
||||||
const bmp = @import("bmp_valid.zig");
|
const bmp = @import("bmp_valid.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_image);
|
||||||
const plugins = @import("plugin.zig");
|
const plugins = @import("plugin.zig");
|
||||||
|
|
||||||
/// Approximate size of the BMP header, in bytes.
|
|
||||||
pub const BMPHeaderSize: usize = 82000;
|
|
||||||
|
|
||||||
/// Buffer size for main image copying.
|
/// Buffer size for main image copying.
|
||||||
pub const BufferSize: usize = 300000;
|
pub const BufferSize: usize = 300000;
|
||||||
|
|
||||||
|
@ -23,35 +21,34 @@ pub const ImageError = error{
|
||||||
|
|
||||||
/// Low level integration function with libsndfile.
|
/// Low level integration function with libsndfile.
|
||||||
pub fn sopen(
|
pub fn sopen(
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
path: []const u8,
|
path: []const u8,
|
||||||
mode: i32,
|
mode: i32,
|
||||||
fmt: *c.SF_INFO,
|
fmt: *c.SF_INFO,
|
||||||
) !*c.SNDFILE {
|
) !*c.SNDFILE {
|
||||||
var cstr_path = try std.cstr.addNullByte(allocator, path);
|
const cstr_path = try allocator.dupeZ(u8, path);
|
||||||
defer allocator.free(cstr_path);
|
defer allocator.free(cstr_path);
|
||||||
|
|
||||||
var file = c.sf_open(cstr_path.ptr, mode, fmt);
|
const file = c.sf_open(cstr_path.ptr, mode, fmt);
|
||||||
const st: i32 = c.sf_error(file);
|
const st: i32 = c.sf_error(file);
|
||||||
|
|
||||||
if (st != 0) {
|
if (st != 0) {
|
||||||
std.debug.warn(
|
log.debug("Failed to open {s} ({s})", .{
|
||||||
"Failed to open {} ({})\n",
|
|
||||||
path,
|
path,
|
||||||
c.sf_error_number(st),
|
c.sf_error_number(st),
|
||||||
);
|
});
|
||||||
|
|
||||||
return ImageError.OpenFail;
|
return ImageError.OpenFail;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frames_on_end = c.sf_seek(file, 0, c.SEEK_END);
|
const frames_on_end = c.sf_seek(file, 0, c.SEEK_END);
|
||||||
_ = c.sf_seek(file, 0, c.SEEK_SET);
|
_ = c.sf_seek(file, 0, c.SEEK_SET);
|
||||||
std.testing.expectEqual(fmt.frames, frames_on_end);
|
try std.testing.expectEqual(fmt.frames, frames_on_end);
|
||||||
|
|
||||||
const frames_on_end_by_end = c.sf_seek(file, frames_on_end, c.SEEK_SET);
|
const frames_on_end_by_end = c.sf_seek(file, frames_on_end, c.SEEK_SET);
|
||||||
std.testing.expectEqual(frames_on_end, frames_on_end_by_end);
|
try std.testing.expectEqual(frames_on_end, frames_on_end_by_end);
|
||||||
|
|
||||||
std.debug.warn("frames on end: {}, frame on end (2): {}\n", frames_on_end, frames_on_end_by_end);
|
log.debug("frames on end: {}, frame on end (2): {}", .{ frames_on_end, frames_on_end_by_end });
|
||||||
|
|
||||||
return file.?;
|
return file.?;
|
||||||
}
|
}
|
||||||
|
@ -60,49 +57,54 @@ pub fn swrite(file: *c.SNDFILE, buf: [*]f32, frames: i64) !void {
|
||||||
const count = c.sf_writef_float(file, buf, frames);
|
const count = c.sf_writef_float(file, buf, frames);
|
||||||
|
|
||||||
if (count != frames) {
|
if (count != frames) {
|
||||||
std.debug.warn("Wanted to write {}, got {}\n", frames, count);
|
log.debug("Wanted to write {}, got {}", .{ frames, count });
|
||||||
return ImageError.WriteFail;
|
return ImageError.WriteFail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sseek(file: *c.SNDFILE, offset: usize) void {
|
pub fn sseek(file: *c.SNDFILE, offset: usize) void {
|
||||||
const offset_i64 = @intCast(i64, offset);
|
const offset_i64 = @as(i64, @intCast(offset));
|
||||||
const frames = c.sf_seek(file, offset_i64, c.SEEK_SET);
|
const frames = c.sf_seek(file, offset_i64, c.SEEK_SET);
|
||||||
const frames_current = c.sf_seek(file, 0, c.SEEK_CUR);
|
const frames_current = c.sf_seek(file, 0, c.SEEK_CUR);
|
||||||
std.testing.expectEqual(frames, frames_current);
|
std.debug.assert(frames == frames_current);
|
||||||
|
|
||||||
if (frames != offset_i64) {
|
if (frames != offset_i64) {
|
||||||
std.debug.warn("failed to seek to {} (seeked {} frames, offset_i64={})\n", offset, frames, offset_i64);
|
log.debug("failed to seek to {} (seeked {} frames, offset_i64={})", .{ offset, frames, offset_i64 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caller owns the returned memory.
|
/// Caller owns the returned memory.
|
||||||
pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 {
|
pub fn temporaryName(allocator: std.mem.Allocator) ![]u8 {
|
||||||
const template_start = "/temp/temp_";
|
const template_start = "/temp/temp_";
|
||||||
const template = "/tmp/temp_XXXXXXXXXXX";
|
const template = "/tmp/temp_XXXXXXXXXXXXXXXXXXXXX";
|
||||||
var nam = try allocator.alloc(u8, template.len);
|
var nam = try allocator.alloc(u8, template.len);
|
||||||
std.mem.copy(u8, nam, template);
|
std.mem.copyForwards(u8, nam, template);
|
||||||
|
|
||||||
var r = std.rand.DefaultPrng.init(std.time.timestamp());
|
const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
|
||||||
|
var r = std.rand.DefaultPrng.init(seed);
|
||||||
|
|
||||||
var fill = nam[template_start.len..nam.len];
|
var fill = nam[template_start.len..nam.len];
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < 100) : (i += 1) {
|
while (i < 100) : (i += 1) {
|
||||||
|
|
||||||
// generate a random uppercase letter, that is, 65 + random number.
|
// generate a random uppercase letter, that is, 65 + random number.
|
||||||
for (fill) |_, f_idx| {
|
for (fill, 0..) |_, f_idx| {
|
||||||
var idx = @intCast(u8, r.random.uintLessThan(u5, 24));
|
const idx = @as(u8, @intCast(r.random().uintLessThan(u5, 24)));
|
||||||
var letter = u8(65) + idx;
|
const letter = @as(u8, 65) + idx;
|
||||||
fill[f_idx] = letter;
|
fill[f_idx] = letter;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we fail to access it, we assume it doesn't exist and return it.
|
// if we fail to access it, we assume it doesn't exist and return it.
|
||||||
std.fs.File.access(nam) catch |err| {
|
var tmp_file: std.fs.File = std.fs.cwd().openFile(
|
||||||
if (err == error.FileNotFound) {
|
nam,
|
||||||
return nam;
|
.{ .mode = .read_only },
|
||||||
}
|
) catch |err| {
|
||||||
|
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;
|
return error.TempGenFail;
|
||||||
|
@ -110,17 +112,17 @@ pub fn temporaryName(allocator: *std.mem.Allocator) ![]u8 {
|
||||||
|
|
||||||
pub fn mkSfInfo() c.SF_INFO {
|
pub fn mkSfInfo() c.SF_INFO {
|
||||||
return c.SF_INFO{
|
return c.SF_INFO{
|
||||||
.frames = c_int(0),
|
.frames = @as(c_int, 0),
|
||||||
.samplerate = c_int(44100),
|
.samplerate = @as(c_int, 44100),
|
||||||
.channels = c_int(1),
|
.channels = @as(c_int, 1),
|
||||||
.format = c.SF_FORMAT_ULAW | c.SF_FORMAT_RAW | c.SF_ENDIAN_BIG,
|
.format = c.SF_FORMAT_ULAW | c.SF_FORMAT_RAW | c.SF_ENDIAN_BIG,
|
||||||
.sections = c_int(0),
|
.sections = @as(c_int, 0),
|
||||||
.seekable = c_int(0),
|
.seekable = @as(c_int, 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const Image = struct {
|
pub const Image = struct {
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
/// Pointer to the underlying libsndfile's SNDFILE struct.
|
/// Pointer to the underlying libsndfile's SNDFILE struct.
|
||||||
sndfile: *c.SNDFILE,
|
sndfile: *c.SNDFILE,
|
||||||
|
@ -135,21 +137,21 @@ pub const Image = struct {
|
||||||
curpath: []const u8,
|
curpath: []const u8,
|
||||||
|
|
||||||
/// Open a BMP image for later.
|
/// Open a BMP image for later.
|
||||||
pub fn open(allocator: *std.mem.Allocator, path: []const u8) !*Image {
|
pub fn open(allocator: std.mem.Allocator, path: []const u8) !*Image {
|
||||||
var in_fmt = mkSfInfo();
|
var in_fmt = mkSfInfo();
|
||||||
var sndfile = try sopen(allocator, path, c.SFM_READ, &in_fmt);
|
const sndfile = try sopen(allocator, path, c.SFM_READ, &in_fmt);
|
||||||
|
|
||||||
var image = try allocator.create(Image);
|
const image = try allocator.create(Image);
|
||||||
|
|
||||||
std.debug.assert(in_fmt.frames > i64(0));
|
std.debug.assert(in_fmt.frames > @as(i64, 0));
|
||||||
std.debug.assert(in_fmt.seekable == i32(1));
|
std.debug.assert(in_fmt.seekable == @as(i32, 1));
|
||||||
|
|
||||||
image.* = Image{
|
image.* = Image{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.sndfile = sndfile,
|
.sndfile = sndfile,
|
||||||
.path = path,
|
.path = path,
|
||||||
.curpath = path,
|
.curpath = path,
|
||||||
.frames = @intCast(usize, in_fmt.frames),
|
.frames = @as(usize, @intCast(in_fmt.frames)),
|
||||||
};
|
};
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
|
@ -158,49 +160,50 @@ pub const Image = struct {
|
||||||
pub fn clone(self: *Image) !*Image {
|
pub fn clone(self: *Image) !*Image {
|
||||||
var in_fmt = mkSfInfo();
|
var in_fmt = mkSfInfo();
|
||||||
// clone sndfile
|
// clone sndfile
|
||||||
var sndfile = try sopen(self.allocator, self.curpath, c.SFM_READ, &in_fmt);
|
const sndfile = try sopen(self.allocator, self.curpath, c.SFM_READ, &in_fmt);
|
||||||
std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames));
|
std.debug.assert(self.frames == @as(usize, @intCast(in_fmt.frames)));
|
||||||
|
|
||||||
var image = try self.allocator.create(Image);
|
const image = try self.allocator.create(Image);
|
||||||
|
|
||||||
std.debug.assert(in_fmt.frames > i64(0));
|
std.debug.assert(in_fmt.frames > @as(i64, 0));
|
||||||
std.debug.assert(in_fmt.seekable == i32(1));
|
std.debug.assert(in_fmt.seekable == @as(i32, 1));
|
||||||
|
|
||||||
image.* = Image{
|
image.* = Image{
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
.sndfile = sndfile,
|
.sndfile = sndfile,
|
||||||
.path = self.path,
|
.path = self.path,
|
||||||
.curpath = self.curpath,
|
.curpath = self.curpath,
|
||||||
.frames = @intCast(usize, in_fmt.frames),
|
.frames = @as(usize, @intCast(in_fmt.frames)),
|
||||||
};
|
};
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(self: *Image) void {
|
pub fn close(self: *Image) void {
|
||||||
//self.allocator.free(self.path);
|
const st: i32 = c.sf_close(self.sndfile);
|
||||||
//self.allocator.free(self.curpath);
|
|
||||||
var st: i32 = c.sf_close(self.sndfile);
|
|
||||||
|
|
||||||
if (st != 0) {
|
if (st != 0) {
|
||||||
std.debug.warn(
|
log.debug("Failed to close {s} ({s})", .{
|
||||||
"Failed to close {} ({})\n",
|
|
||||||
self.path,
|
self.path,
|
||||||
c.sf_error_number(st),
|
c.sf_error_number(st),
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.allocator.free(self.path);
|
||||||
|
self.allocator.free(self.curpath);
|
||||||
|
|
||||||
|
var allocator = self.allocator;
|
||||||
|
self.* = undefined;
|
||||||
|
allocator.destroy(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(self: *Image, file_chans: c_int, buf: []f32) bool {
|
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(self.sndfile, buf.ptr, 1);
|
||||||
|
const buf_chans = @as(c_int, @intCast(buf.len));
|
||||||
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;
|
var i = file_chans - 1;
|
||||||
while (i < buf_chans) : (i += 1) {
|
while (i < buf_chans) : (i += 1) {
|
||||||
//buf[@intCast(usize, i)] = buf[i % file_chans];
|
//buf[@intCast(usize, i)] = buf[i % file_chans];
|
||||||
buf[@intCast(usize, i)] = buf[@intCast(usize, @mod(i, file_chans))];
|
buf[@as(usize, @intCast(i))] = buf[@as(usize, @intCast(@mod(i, file_chans)))];
|
||||||
}
|
}
|
||||||
|
|
||||||
return n_read == 1;
|
return n_read == 1;
|
||||||
|
@ -224,7 +227,7 @@ pub const Image = struct {
|
||||||
sseek(out_file, start);
|
sseek(out_file, start);
|
||||||
|
|
||||||
while (i <= end) : (i += buf.len) {
|
while (i <= end) : (i += buf.len) {
|
||||||
std.debug.warn("\t\ti={}, buf.len={}, end={}\n", i, buf.len, end);
|
log.debug("\t\ti={d}, buf.len={d}, end={d}", .{ i, buf.len, end });
|
||||||
sseek(self.sndfile, i);
|
sseek(self.sndfile, i);
|
||||||
sseek(out_file, i);
|
sseek(out_file, i);
|
||||||
|
|
||||||
|
@ -234,13 +237,13 @@ pub const Image = struct {
|
||||||
var view: []f32 = buf[0..buf.len];
|
var view: []f32 = buf[0..buf.len];
|
||||||
|
|
||||||
if (bytes_until_end < buf.len) {
|
if (bytes_until_end < buf.len) {
|
||||||
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, bytes_until_end));
|
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @as(i64, @intCast(bytes_until_end)));
|
||||||
view = buf[0..bytes_until_end];
|
view = buf[0..bytes_until_end];
|
||||||
} else {
|
} else {
|
||||||
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @intCast(i64, buf.len));
|
read_bytes = c.sf_readf_float(self.sndfile, buf.ptr, @as(i64, @intCast(buf.len)));
|
||||||
}
|
}
|
||||||
|
|
||||||
try swrite(out_file, view.ptr, @intCast(i64, view.len));
|
try swrite(out_file, view.ptr, @as(i64, @intCast(view.len)));
|
||||||
}
|
}
|
||||||
|
|
||||||
sseek(self.sndfile, end);
|
sseek(self.sndfile, end);
|
||||||
|
@ -249,8 +252,8 @@ pub const Image = struct {
|
||||||
|
|
||||||
fn getSeekPos(self: *Image, position: plugins.Position) plugins.SeekPos {
|
fn getSeekPos(self: *Image, position: plugins.Position) plugins.SeekPos {
|
||||||
const file_end = self.frames;
|
const file_end = self.frames;
|
||||||
var seek_pos = position.seekPos(file_end);
|
const seek_pos = position.seekPos(file_end);
|
||||||
std.debug.warn("\tstart {} end {}\n", seek_pos.start, seek_pos.end);
|
log.debug("\tstart {d} end {d}", .{ seek_pos.start, seek_pos.end });
|
||||||
return seek_pos;
|
return seek_pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,21 +261,20 @@ pub const Image = struct {
|
||||||
var in_fmt = mkSfInfo();
|
var in_fmt = mkSfInfo();
|
||||||
|
|
||||||
self.sndfile = try sopen(self.allocator, path, c.SFM_READ, &in_fmt);
|
self.sndfile = try sopen(self.allocator, path, c.SFM_READ, &in_fmt);
|
||||||
std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames));
|
// std.testing.expectEqual(self.frames, @intCast(usize, in_fmt.frames));
|
||||||
|
|
||||||
self.curpath = path;
|
self.curpath = path;
|
||||||
self.frames = @intCast(usize, in_fmt.frames);
|
self.frames = @as(usize, @intCast(in_fmt.frames));
|
||||||
|
|
||||||
std.debug.warn(
|
log.debug("\timage: reopened on '{s}' (frames={d}, fmt.frames={d})", .{
|
||||||
"\timage: reopened on '{}' (frames={}, fmt.frames={})\n",
|
|
||||||
self.curpath,
|
self.curpath,
|
||||||
self.frames,
|
self.frames,
|
||||||
in_fmt.frames,
|
in_fmt.frames,
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkValid(self: *Image) !void {
|
pub fn checkValid(self: *Image) !void {
|
||||||
var file = try std.fs.File.openRead(self.path);
|
var file = try std.fs.cwd().openFile(self.path, .{ .mode = .read_only });
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
// main bmp header:
|
// main bmp header:
|
||||||
|
@ -284,7 +286,8 @@ pub const Image = struct {
|
||||||
var magic = [2]u8{ 0, 0 };
|
var magic = [2]u8{ 0, 0 };
|
||||||
_ = try file.read(&magic);
|
_ = try file.read(&magic);
|
||||||
|
|
||||||
try bmp.magicValid(&magic);
|
if (std.mem.endsWith(u8, self.path, ".bmp"))
|
||||||
|
try bmp.magicValid(&magic);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a plugin over the image.
|
/// Run a plugin over the image.
|
||||||
|
@ -303,50 +306,48 @@ pub const Image = struct {
|
||||||
defer ctx.deinit();
|
defer ctx.deinit();
|
||||||
|
|
||||||
var ports = try lv2.setupPorts(&ctx);
|
var ports = try lv2.setupPorts(&ctx);
|
||||||
|
defer ctx.allocator.free(ports);
|
||||||
|
|
||||||
if (ctx.n_audio_in > 2) {
|
if (ctx.n_audio_in > 2) {
|
||||||
std.debug.warn("plugin <{}> has more than two inputs.\n", plugin_uri);
|
log.debug("plugin <{s}> has more than two inputs.", .{plugin_uri});
|
||||||
return ImageError.InvalidPlugin;
|
return ImageError.InvalidPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.n_audio_out > 2) {
|
if (ctx.n_audio_out > 2) {
|
||||||
std.debug.warn("plugin <{}> has more than two outputs.\n", plugin_uri);
|
log.debug("plugin <{s}> has more than two outputs.", .{plugin_uri});
|
||||||
return ImageError.InvalidPlugin;
|
return ImageError.InvalidPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now, for each param for the plugin, we find its port, and set
|
// now, for each param for the plugin, we find its port, and set
|
||||||
// the value for the port there.
|
// the value for the port there.
|
||||||
var it = params.iterator();
|
for (params.items) |param| {
|
||||||
|
const sym_cstr = try self.allocator.dupeZ(u8, param.sym);
|
||||||
while (it.next()) |param| {
|
|
||||||
var sym_cstr = try std.cstr.addNullByte(self.allocator, param.sym);
|
|
||||||
defer self.allocator.free(sym_cstr);
|
defer self.allocator.free(sym_cstr);
|
||||||
|
|
||||||
var sym = c.lilv_new_string(ctx.world, sym_cstr.ptr);
|
const sym = c.lilv_new_string(ctx.world, sym_cstr.ptr);
|
||||||
const port = c.lilv_plugin_get_port_by_symbol(ctx.plugin, sym) orelse blk: {
|
const port = c.lilv_plugin_get_port_by_symbol(ctx.plugin, sym) orelse {
|
||||||
std.debug.warn("assert fail: symbol {} not found on port\n", param.sym);
|
log.debug("assert fail: symbol {s} not found on port", .{param.sym});
|
||||||
return ImageError.InvalidSymbol;
|
return ImageError.InvalidSymbol;
|
||||||
};
|
};
|
||||||
|
|
||||||
c.lilv_node_free(sym);
|
c.lilv_node_free(sym);
|
||||||
|
|
||||||
var idx = c.lilv_port_get_index(ctx.plugin, port);
|
const idx = c.lilv_port_get_index(ctx.plugin, port);
|
||||||
std.debug.warn(
|
log.debug("\tset sym={s}, idx={d} to val={}", .{
|
||||||
"\tset sym={}, idx={} to val={}\n",
|
|
||||||
param.sym,
|
param.sym,
|
||||||
idx,
|
idx,
|
||||||
param.value,
|
param.value,
|
||||||
);
|
});
|
||||||
(&ports[idx]).value = param.value;
|
(&ports[idx]).value = param.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we need to generate a temporary file and put the output of
|
// now we need to generate a temporary file and put the output of
|
||||||
// running the plugin on that file
|
// running the plugin on that file
|
||||||
var tmpnam = try temporaryName(self.allocator);
|
const tmpnam = try temporaryName(self.allocator);
|
||||||
std.debug.warn("\trunning plugin from '{}' to '{}'\n", self.curpath, tmpnam);
|
log.debug("\trunning plugin from '{s}' to '{s}'", .{ self.curpath, tmpnam });
|
||||||
|
|
||||||
var out_fmt = mkSfInfo();
|
var out_fmt = mkSfInfo();
|
||||||
var out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
|
const out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
|
||||||
|
|
||||||
var rctx = try plugins.RunContext.init(self.allocator, ctx.plugin);
|
var rctx = try plugins.RunContext.init(self.allocator, ctx.plugin);
|
||||||
defer rctx.deinit();
|
defer rctx.deinit();
|
||||||
|
@ -369,27 +370,34 @@ pub const Image = struct {
|
||||||
// pre-plugin copy, merged with bmp header copy
|
// pre-plugin copy, merged with bmp header copy
|
||||||
try self.copyBytes(
|
try self.copyBytes(
|
||||||
out_file,
|
out_file,
|
||||||
usize(0),
|
@as(usize, 0),
|
||||||
seek_pos.start,
|
seek_pos.start,
|
||||||
);
|
);
|
||||||
|
|
||||||
sseek(self.sndfile, seek_pos.start);
|
sseek(self.sndfile, seek_pos.start);
|
||||||
|
|
||||||
var i: usize = seek_pos.start;
|
var i: usize = seek_pos.start;
|
||||||
std.debug.warn("\tseek pos start: {} end: {}\n", seek_pos.start, seek_pos.end);
|
log.debug("\tseek pos start: {d} end: {d}", .{ seek_pos.start, seek_pos.end });
|
||||||
|
|
||||||
var inbuf = rctx.buffers.in;
|
var inbuf = &rctx.buffers.in;
|
||||||
var outbuf = rctx.buffers.out;
|
const outbuf = &rctx.buffers.out;
|
||||||
|
|
||||||
while (i <= seek_pos.end) : (i += 1) {
|
while (i <= seek_pos.end) : (i += 1) {
|
||||||
const read_bytes = c.sf_readf_float(self.sndfile, inbuf.ptr, 1);
|
inbuf[0] = 0;
|
||||||
|
inbuf[1] = 0;
|
||||||
|
|
||||||
|
const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1);
|
||||||
if (read_bytes == 0) {
|
if (read_bytes == 0) {
|
||||||
std.debug.warn("WARN! reached EOF at idx={}\n", i);
|
log.debug("WARN! reached EOF at idx={d}", .{i});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// trick plugins into having correct stereo signal from
|
||||||
|
// my mono input
|
||||||
|
inbuf[1] = inbuf[0];
|
||||||
|
|
||||||
lv2.lilv_instance_run(rctx.instance, 1);
|
lv2.lilv_instance_run(rctx.instance, 1);
|
||||||
try swrite(out_file, outbuf.ptr, 1);
|
try swrite(out_file, outbuf, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
sseek(self.sndfile, seek_pos.end);
|
sseek(self.sndfile, seek_pos.end);
|
||||||
|
@ -408,23 +416,22 @@ pub const Image = struct {
|
||||||
try self.reopen(tmpnam);
|
try self.reopen(tmpnam);
|
||||||
try self.checkValid();
|
try self.checkValid();
|
||||||
|
|
||||||
var time_taken = timer.read();
|
const time_taken = timer.read();
|
||||||
std.debug.warn("\ttook {d:.2}ms running plugin\n", time_taken / std.time.millisecond);
|
log.debug("\ttook {d:.2}ms running plugin", .{time_taken / std.time.us_per_ms});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn saveTo(self: *Image, out_path: []const u8) !void {
|
pub fn saveTo(self: *Image, out_path: []const u8) !void {
|
||||||
std.debug.warn("\timg: copy from '{}' to '{}'\n", self.curpath, out_path);
|
log.debug("\timg: copy from '{s}' to '{s}'", .{ self.curpath, out_path });
|
||||||
try std.fs.copyFile(self.curpath, out_path);
|
try std.fs.copyFileAbsolute(self.curpath, out_path, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runCustomPlugin(
|
pub fn runCustomPlugin(
|
||||||
self: *Image,
|
self: *Image,
|
||||||
comptime Plugin: type,
|
comptime Plugin: type,
|
||||||
position: plugins.Position,
|
position: plugins.Position,
|
||||||
comptime ExtraType: type,
|
extra: anytype,
|
||||||
extra: ExtraType,
|
|
||||||
) !void {
|
) !void {
|
||||||
var plugin_opt: ?Plugin = Plugin.init(self.allocator, extra);
|
const plugin_opt: ?Plugin = Plugin.init(self.allocator, extra);
|
||||||
if (plugin_opt == null) {
|
if (plugin_opt == null) {
|
||||||
return ImageError.PluginLoadFail;
|
return ImageError.PluginLoadFail;
|
||||||
}
|
}
|
||||||
|
@ -441,15 +448,13 @@ pub const Image = struct {
|
||||||
|
|
||||||
// the code here is a copypaste of runPlugin() without the specific
|
// the code here is a copypaste of runPlugin() without the specific
|
||||||
// lilv things.
|
// lilv things.
|
||||||
var tmpnam = try temporaryName(self.allocator);
|
const tmpnam = try temporaryName(self.allocator);
|
||||||
std.debug.warn("\trunning CUSTOM plugin from '{}' to '{}'\n", self.curpath, tmpnam);
|
log.debug("\trunning CUSTOM plugin from '{s}' to '{s}'", .{ self.curpath, tmpnam });
|
||||||
|
|
||||||
var out_fmt = mkSfInfo();
|
var out_fmt = mkSfInfo();
|
||||||
var out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
|
const out_file = try sopen(self.allocator, tmpnam, c.SFM_WRITE, &out_fmt);
|
||||||
|
|
||||||
var bufs = try plugins.RunBuffers.init(self.allocator);
|
|
||||||
defer bufs.deinit();
|
|
||||||
|
|
||||||
|
var bufs = plugins.RunBuffers{};
|
||||||
const seek_pos = self.getSeekPos(position);
|
const seek_pos = self.getSeekPos(position);
|
||||||
|
|
||||||
// make sure we start from 0
|
// make sure we start from 0
|
||||||
|
@ -464,27 +469,27 @@ pub const Image = struct {
|
||||||
// pre-plugin copy, merged with bmp header copy
|
// pre-plugin copy, merged with bmp header copy
|
||||||
try self.copyBytes(
|
try self.copyBytes(
|
||||||
out_file,
|
out_file,
|
||||||
usize(0),
|
@as(usize, 0),
|
||||||
seek_pos.start,
|
seek_pos.start,
|
||||||
);
|
);
|
||||||
|
|
||||||
sseek(self.sndfile, seek_pos.start);
|
sseek(self.sndfile, seek_pos.start);
|
||||||
|
|
||||||
var i: usize = seek_pos.start;
|
var i: usize = seek_pos.start;
|
||||||
std.debug.warn("\tseek pos start: {} end: {}\n", seek_pos.start, seek_pos.end);
|
log.debug("\tseek pos start: {d} end: {d}", .{ seek_pos.start, seek_pos.end });
|
||||||
|
|
||||||
var inbuf = bufs.in;
|
const inbuf = &bufs.in;
|
||||||
var outbuf = bufs.out;
|
const outbuf = &bufs.out;
|
||||||
|
|
||||||
while (i <= seek_pos.end) : (i += 1) {
|
while (i <= seek_pos.end) : (i += 1) {
|
||||||
const read_bytes = c.sf_readf_float(self.sndfile, bufs.in.ptr, 1);
|
const read_bytes = c.sf_readf_float(self.sndfile, inbuf, 1);
|
||||||
if (read_bytes == 0) {
|
if (read_bytes == 0) {
|
||||||
std.debug.warn("WARN! reached EOF at idx={}\n", i);
|
log.debug("WARN! reached EOF at idx={d}", .{i});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.run(&bufs);
|
plugin.run(&bufs);
|
||||||
try swrite(out_file, bufs.out.ptr, 1);
|
try swrite(out_file, outbuf, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
sseek(self.sndfile, seek_pos.end);
|
sseek(self.sndfile, seek_pos.end);
|
||||||
|
|
966
src/lang.zig
966
src/lang.zig
File diff suppressed because it is too large
Load diff
|
@ -1,10 +1,12 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const plugin = @import("plugin.zig");
|
const plugin = @import("plugin.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_lv2);
|
||||||
|
|
||||||
pub const c = @cImport({
|
pub const c = @cImport({
|
||||||
@cInclude("sndfile.h");
|
@cInclude("sndfile.h");
|
||||||
@cInclude("lilv/lilv.h");
|
@cInclude("lilv/lilv.h");
|
||||||
@cInclude("lv2/core/lv2.h");
|
@cInclude("lv2.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn Lv2Core(comptime ns: []const u8) []const u8 {
|
pub fn Lv2Core(comptime ns: []const u8) []const u8 {
|
||||||
|
@ -21,7 +23,7 @@ const LV2_CORE__connectionOptional = Lv2Core("#connectionOptional");
|
||||||
pub fn lilv_instance_connect_port(
|
pub fn lilv_instance_connect_port(
|
||||||
instance: [*c]c.LilvInstance,
|
instance: [*c]c.LilvInstance,
|
||||||
port_index: u32,
|
port_index: u32,
|
||||||
data_location: ?*c_void,
|
data_location: ?*anyopaque,
|
||||||
) void {
|
) void {
|
||||||
instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location);
|
instance.?.*.lv2_descriptor.?.*.connect_port.?(instance.?.*.lv2_handle, port_index, data_location);
|
||||||
}
|
}
|
||||||
|
@ -59,42 +61,44 @@ pub const Port = struct {
|
||||||
/// Setup ports for a given plugin. Gives an array to pointers of Port structs.
|
/// 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
|
/// This setup is required so we link the plugin to the ports later on, and
|
||||||
/// also link our buffers, and control values.
|
/// also link our buffers, and control values.
|
||||||
|
///
|
||||||
|
/// Caller owns returned memory.
|
||||||
pub fn setupPorts(ctx: *plugin.Context) ![]Port {
|
pub fn setupPorts(ctx: *plugin.Context) ![]Port {
|
||||||
var world = ctx.world;
|
const world = ctx.world;
|
||||||
const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin);
|
const n_ports: u32 = c.lilv_plugin_get_num_ports(ctx.plugin);
|
||||||
|
|
||||||
var ports = try ctx.allocator.alloc(Port, n_ports);
|
var ports = try ctx.allocator.alloc(Port, n_ports);
|
||||||
|
|
||||||
for (ports) |_, idx| {
|
for (ports, 0..) |_, idx| {
|
||||||
var port: *Port = &ports[idx];
|
const port: *Port = &ports[idx];
|
||||||
port.* = Port{
|
port.* = Port{
|
||||||
.lilv_port = null,
|
.lilv_port = null,
|
||||||
.ptype = .Control,
|
.ptype = .Control,
|
||||||
.index = f32(0),
|
.index = @as(f32, 0),
|
||||||
.value = f32(0),
|
.value = @as(f32, 0),
|
||||||
.is_input = false,
|
.is_input = false,
|
||||||
.optional = false,
|
.optional = false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var values: []f32 = try ctx.allocator.alloc(f32, n_ports);
|
const values: []f32 = try ctx.allocator.alloc(f32, n_ports);
|
||||||
defer ctx.allocator.free(values);
|
defer ctx.allocator.free(values);
|
||||||
|
|
||||||
c.lilv_plugin_get_port_ranges_float(ctx.plugin, null, null, values.ptr);
|
c.lilv_plugin_get_port_ranges_float(ctx.plugin, null, null, values.ptr);
|
||||||
var lv2_InputPort = c.lilv_new_uri(world, LV2_CORE__InputPort.ptr);
|
const lv2_InputPort = c.lilv_new_uri(world, LV2_CORE__InputPort.ptr).?;
|
||||||
defer std.heap.c_allocator.destroy(lv2_InputPort);
|
//defer std.heap.c_allocator.destroy(lv2_InputPort);
|
||||||
|
|
||||||
var lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr);
|
const lv2_OutputPort = c.lilv_new_uri(world, LV2_CORE__OutputPort.ptr).?;
|
||||||
defer std.heap.c_allocator.destroy(lv2_OutputPort);
|
//defer std.heap.c_allocator.destroy(lv2_OutputPort);
|
||||||
|
|
||||||
var lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr);
|
const lv2_AudioPort = c.lilv_new_uri(world, LV2_CORE__AudioPort.ptr).?;
|
||||||
defer std.heap.c_allocator.destroy(lv2_AudioPort);
|
//defer std.heap.c_allocator.destroy(lv2_AudioPort);
|
||||||
|
|
||||||
var lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr);
|
const lv2_ControlPort = c.lilv_new_uri(world, LV2_CORE__ControlPort.ptr).?;
|
||||||
defer std.heap.c_allocator.destroy(lv2_ControlPort);
|
//defer std.heap.c_allocator.destroy(lv2_ControlPort);
|
||||||
|
|
||||||
var lv2_connectionOptional = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr);
|
const lv2_connection_string = c.lilv_new_uri(world, LV2_CORE__connectionOptional.ptr).?;
|
||||||
defer std.heap.c_allocator.destroy(lv2_connectionOptional);
|
//defer std.heap.c_allocator.destroy(lv2_connection_string);
|
||||||
|
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
while (i < n_ports) : (i += 1) {
|
while (i < n_ports) : (i += 1) {
|
||||||
|
@ -106,17 +110,17 @@ pub fn setupPorts(ctx: *plugin.Context) ![]Port {
|
||||||
port.index = i;
|
port.index = i;
|
||||||
|
|
||||||
if (std.math.isNan(values[i])) {
|
if (std.math.isNan(values[i])) {
|
||||||
port.value = f32(0);
|
port.value = @as(f32, 0);
|
||||||
} else {
|
} else {
|
||||||
port.value = values[i];
|
port.value = values[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connectionOptional);
|
port.optional = c.lilv_port_has_property(ctx.plugin, lport, lv2_connection_string);
|
||||||
|
|
||||||
if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) {
|
if (c.lilv_port_is_a(ctx.plugin, lport, lv2_InputPort)) {
|
||||||
port.is_input = true;
|
port.is_input = true;
|
||||||
} else if (!c.lilv_port_is_a(ctx.plugin, lport, lv2_OutputPort) and !port.optional) {
|
} 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);
|
log.debug("Port {d} is neither input or output", .{i});
|
||||||
return error.UnassignedIOPort;
|
return error.UnassignedIOPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +136,7 @@ pub fn setupPorts(ctx: *plugin.Context) ![]Port {
|
||||||
ctx.n_audio_out += 1;
|
ctx.n_audio_out += 1;
|
||||||
}
|
}
|
||||||
} else if (!port.optional) {
|
} else if (!port.optional) {
|
||||||
std.debug.warn("Port {} has unsupported type\n", i);
|
log.debug("Port {d} has unsupported type", .{i});
|
||||||
return error.UnsupportedPortType;
|
return error.UnsupportedPortType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,12 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const images = @import("image.zig");
|
const images = @import("image.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_magick);
|
||||||
const Image = images.Image;
|
const Image = images.Image;
|
||||||
|
|
||||||
const mc = @import("magick_wand.zig");
|
const mc = @cImport({
|
||||||
|
@cInclude("wand/magick_wand.h");
|
||||||
|
});
|
||||||
|
|
||||||
pub const MagickContext = struct {
|
pub const MagickContext = struct {
|
||||||
wand: *mc.MagickWand,
|
wand: *mc.MagickWand,
|
||||||
|
@ -12,7 +15,7 @@ pub const MagickContext = struct {
|
||||||
pub fn init() !MagickContext {
|
pub fn init() !MagickContext {
|
||||||
mc.InitializeMagick(null);
|
mc.InitializeMagick(null);
|
||||||
|
|
||||||
var wand = mc.NewMagickWand();
|
const wand = mc.NewMagickWand();
|
||||||
if (wand == null) return error.WandCreateFail;
|
if (wand == null) return error.WandCreateFail;
|
||||||
|
|
||||||
return MagickContext{
|
return MagickContext{
|
||||||
|
@ -26,6 +29,7 @@ pub const MagickContext = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn doErr(self: *MagickContext) !void {
|
pub fn doErr(self: *MagickContext) !void {
|
||||||
|
_ = self;
|
||||||
return error.WandError;
|
return error.WandError;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -34,10 +38,10 @@ fn magickLoad(image: *Image) !MagickContext {
|
||||||
var mctx = try MagickContext.init();
|
var mctx = try MagickContext.init();
|
||||||
errdefer mctx.deinit();
|
errdefer mctx.deinit();
|
||||||
|
|
||||||
var curpath = try std.cstr.addNullByte(image.allocator, image.curpath);
|
const curpath = try image.allocator.dupeZ(u8, image.curpath);
|
||||||
defer image.allocator.free(curpath);
|
defer image.allocator.free(curpath);
|
||||||
|
|
||||||
std.debug.warn("loading '{}'\n", curpath);
|
log.debug("loading '{s}'", .{curpath});
|
||||||
|
|
||||||
if (mc.MagickReadImage(mctx.wand, curpath.ptr) != 1)
|
if (mc.MagickReadImage(mctx.wand, curpath.ptr) != 1)
|
||||||
return error.MagickReadFail;
|
return error.MagickReadFail;
|
||||||
|
@ -48,16 +52,16 @@ fn magickLoad(image: *Image) !MagickContext {
|
||||||
fn magickSave(image: *Image, wand: *mc.MagickWand) !void {
|
fn magickSave(image: *Image, wand: *mc.MagickWand) !void {
|
||||||
const allocator = image.allocator;
|
const allocator = image.allocator;
|
||||||
|
|
||||||
var tmpnam = try images.temporaryName(allocator);
|
const tmpnam = try images.temporaryName(allocator);
|
||||||
var c_tmpnam = try std.cstr.addNullByte(allocator, tmpnam);
|
const c_tmpnam = try allocator.dupeZ(u8, tmpnam);
|
||||||
defer allocator.free(c_tmpnam);
|
defer allocator.free(c_tmpnam);
|
||||||
|
|
||||||
std.debug.warn("\tmagick: saving to '{}'..", c_tmpnam);
|
log.debug("\tmagick: saving to '{s}'..", .{c_tmpnam});
|
||||||
|
|
||||||
if (mc.MagickWriteImage(wand, c_tmpnam.ptr) != 1)
|
if (mc.MagickWriteImage(wand, c_tmpnam.ptr) != 1)
|
||||||
return error.MagickWriteFail;
|
return error.MagickWriteFail;
|
||||||
|
|
||||||
std.debug.warn("OK\n");
|
log.debug("OK", .{});
|
||||||
|
|
||||||
try image.reopen(tmpnam);
|
try image.reopen(tmpnam);
|
||||||
}
|
}
|
||||||
|
@ -68,7 +72,7 @@ pub fn runRotate(image: *Image, deg: f32, bgfill: []const u8) !void {
|
||||||
var mctx = try magickLoad(image);
|
var mctx = try magickLoad(image);
|
||||||
defer mctx.deinit();
|
defer mctx.deinit();
|
||||||
|
|
||||||
var bg = mc.NewPixelWand();
|
const bg = mc.NewPixelWand();
|
||||||
defer mc.DestroyPixelWand(bg);
|
defer mc.DestroyPixelWand(bg);
|
||||||
|
|
||||||
if (mc.PixelSetColor(bg, bgfill.ptr) != 1)
|
if (mc.PixelSetColor(bg, bgfill.ptr) != 1)
|
||||||
|
|
4124
src/magick_wand.zig
4124
src/magick_wand.zig
File diff suppressed because it is too large
Load diff
198
src/main.zig
198
src/main.zig
|
@ -3,6 +3,8 @@ const langs = @import("lang.zig");
|
||||||
const runners = @import("runner.zig");
|
const runners = @import("runner.zig");
|
||||||
const printer = @import("printer.zig");
|
const printer = @import("printer.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher);
|
||||||
|
|
||||||
test "scritcher" {
|
test "scritcher" {
|
||||||
_ = @import("lang.zig");
|
_ = @import("lang.zig");
|
||||||
_ = @import("runner.zig");
|
_ = @import("runner.zig");
|
||||||
|
@ -16,18 +18,32 @@ const readline = @cImport({
|
||||||
@cInclude("readline/history.h");
|
@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);
|
var cmds = langs.CommandList.init(allocator);
|
||||||
try cmds.append(cmd);
|
try cmds.append(cmd);
|
||||||
return cmds;
|
return cmds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
|
fn copyCommandToHeap(allocator: std.mem.Allocator, command: langs.Command, comptime tag: langs.Command.Tag) !*langs.Command {
|
||||||
var stdout_file = try std.io.getStdOut();
|
const CommandStruct = langs.Command.tagToType(tag);
|
||||||
const stdout = &stdout_file.outStream().stream;
|
const casted = command.cast(CommandStruct).?;
|
||||||
const scri_path = try (args_it.next(allocator) orelse @panic("expected scri path"));
|
var heap_cmd = try allocator.create(CommandStruct);
|
||||||
|
|
||||||
var file_read_opt: ?std.fs.File = std.fs.File.openRead(scri_path) catch |err| blk: {
|
heap_cmd.* = casted.*;
|
||||||
|
log.debug("casted: {}", .{casted});
|
||||||
|
log.debug("heap_cmd: {}", .{heap_cmd});
|
||||||
|
|
||||||
|
return &heap_cmd.base;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doRepl(allocator: std.mem.Allocator, args_it: anytype) !void {
|
||||||
|
var stdout_file = std.io.getStdOut();
|
||||||
|
const stdout = &stdout_file.writer();
|
||||||
|
const scri_path = (args_it.next() orelse @panic("expected scri path"));
|
||||||
|
errdefer allocator.free(scri_path);
|
||||||
|
defer allocator.free(scri_path);
|
||||||
|
|
||||||
|
var file_read_opt: ?std.fs.File = std.fs.cwd().openFile(scri_path, .{}) catch |err| blk: {
|
||||||
if (err == error.FileNotFound) break :blk null;
|
if (err == error.FileNotFound) break :blk null;
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
|
@ -40,42 +56,47 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
|
||||||
defer lang.deinit();
|
defer lang.deinit();
|
||||||
|
|
||||||
if (total_bytes > 0) {
|
if (total_bytes > 0) {
|
||||||
// this MUST BE long lived (a reference to it is kept inside
|
const scri_existing = try allocator.alloc(u8, total_bytes);
|
||||||
// existing_cmds, and then passed along to cmds),
|
|
||||||
// we can't defer them here
|
|
||||||
var scri_existing = try allocator.alloc(u8, total_bytes);
|
|
||||||
_ = try file_read_opt.?.read(scri_existing);
|
_ = try file_read_opt.?.read(scri_existing);
|
||||||
|
defer allocator.free(scri_existing);
|
||||||
|
|
||||||
// we can defer this because we copy the Command structs back to cmds
|
// we can't defer this directly because we copy the
|
||||||
|
// Command pointers to the cmds list. running deinit() directly
|
||||||
|
// would cause those pointers to be freed.
|
||||||
var existing_cmds = try lang.parse(scri_existing);
|
var existing_cmds = try lang.parse(scri_existing);
|
||||||
defer existing_cmds.deinit();
|
defer existing_cmds.list.deinit();
|
||||||
|
|
||||||
// copy the existing command list into the repl's command list
|
// copy the existing command list into the repl's command list
|
||||||
var it = existing_cmds.iterator();
|
for (existing_cmds.list.items) |existing_cmd| {
|
||||||
while (it.next()) |existing_cmd| {
|
|
||||||
try cmds.append(existing_cmd);
|
try cmds.append(existing_cmd);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// if there isn't any commands on the file, we load our default
|
// if there isn't any commands on the file, we load our default
|
||||||
// 'load :0' command
|
// 'load :0' command
|
||||||
var loadargs = langs.ArgList.init(allocator);
|
|
||||||
try loadargs.append(":0");
|
|
||||||
|
|
||||||
try cmds.append(langs.Command{
|
// TODO: deliberate memleak here. we only allocate this
|
||||||
.command = .Load,
|
// command once, for the start of the file, so.
|
||||||
.args = loadargs,
|
var load_cmd = try allocator.create(langs.Command.Load);
|
||||||
});
|
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| {
|
if (file_read_opt) |file_read| {
|
||||||
file_read.close();
|
file_read.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
var file = try std.fs.File.openWrite(scri_path);
|
var file = try std.fs.cwd().createFile(scri_path, .{
|
||||||
|
.read = false,
|
||||||
|
.truncate = true,
|
||||||
|
});
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
var out = file.outStream();
|
var out = file.writer();
|
||||||
var stream = &out.stream;
|
const stream = &out;
|
||||||
|
|
||||||
// since we opened the file for writing, it becomes empty, so, to ensure
|
// since we opened the file for writing, it becomes empty, so, to ensure
|
||||||
// we don't fuck up later on, we print cmds before starting the repl
|
// we don't fuck up later on, we print cmds before starting the repl
|
||||||
|
@ -99,31 +120,72 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
|
||||||
// run the load command
|
// run the load command
|
||||||
try runner.runCommands(cmds, true);
|
try runner.runCommands(cmds, true);
|
||||||
|
|
||||||
var runqs_args = langs.ArgList.init(allocator);
|
const wanted_runner: []const u8 = std.posix.getenv("SCRITCHER_RUNNER") orelse "ristretto";
|
||||||
defer runqs_args.deinit();
|
|
||||||
|
|
||||||
// TODO change the runqs command to something given in an env var
|
var runqs_cmd = langs.Command.RunQS{
|
||||||
try runqs_args.append("ristretto");
|
.base = langs.Command{ .tag = langs.Command.Tag.runqs },
|
||||||
|
.program = wanted_runner,
|
||||||
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
lang.reset();
|
lang.reset();
|
||||||
|
|
||||||
var rd_line = readline.readline(c"> ");
|
var rd_line = readline.readline("> ");
|
||||||
if (rd_line == null) {
|
if (rd_line == null) {
|
||||||
std.debug.warn("leaving from eof\n");
|
log.debug("leaving from eof", .{});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
readline.add_history(rd_line);
|
readline.add_history(rd_line);
|
||||||
//defer std.heap.c_allocator.destroy(rd_line);
|
//defer std.heap.c_allocator.destroy(rd_line);
|
||||||
|
|
||||||
var line = rd_line[0..std.mem.len(u8, rd_line)];
|
const line = rd_line[0..std.mem.len(rd_line)];
|
||||||
|
|
||||||
if (std.mem.eql(u8, line, "push")) {
|
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
|
// run the current added command to main cmds list
|
||||||
// with the main parent runner
|
// with the main parent runner
|
||||||
var cmds_wrapped = try wrapInCmdList(allocator, current);
|
var cmds_wrapped = try wrapInCmdList(allocator, heap_cmd);
|
||||||
defer cmds_wrapped.deinit();
|
defer cmds_wrapped.deinit();
|
||||||
try runner.runCommands(cmds_wrapped, true);
|
try runner.runCommands(cmds_wrapped, true);
|
||||||
|
|
||||||
|
@ -138,17 +200,20 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
|
||||||
try printer.printList(cmds, stream);
|
try printer.printList(cmds, stream);
|
||||||
continue;
|
continue;
|
||||||
} else if (std.mem.eql(u8, line, "quit") or std.mem.eql(u8, line, "q")) {
|
} else if (std.mem.eql(u8, line, "quit") or std.mem.eql(u8, line, "q")) {
|
||||||
std.debug.warn("leaving\n");
|
log.debug("leaving", .{});
|
||||||
break;
|
break;
|
||||||
} else if (std.mem.startsWith(u8, line, "#")) {
|
} else if (std.mem.startsWith(u8, line, "#")) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmds_parsed = lang.parse(line) catch |err| {
|
var cmds_parsed = lang.parse(line) catch |err| {
|
||||||
std.debug.warn("repl: error while parsing: {}\n", err);
|
log.debug("repl: error while parsing: {}", .{err});
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
current = cmds_parsed.at(0);
|
|
||||||
|
// no command? ignore!
|
||||||
|
if (cmds_parsed.list.items.len == 0) continue;
|
||||||
|
current = cmds_parsed.list.items[0].*;
|
||||||
|
|
||||||
// by cloning the parent runner, we can iteratively write
|
// by cloning the parent runner, we can iteratively write
|
||||||
// whatever command we want and only commit the good results
|
// whatever command we want and only commit the good results
|
||||||
|
@ -156,43 +221,38 @@ pub fn doRepl(allocator: *std.mem.Allocator, args_it: var) !void {
|
||||||
var runner_clone = try runner.clone();
|
var runner_clone = try runner.clone();
|
||||||
defer runner_clone.deinit();
|
defer runner_clone.deinit();
|
||||||
|
|
||||||
try cmds_parsed.append(langs.Command{
|
// taking address is fine, because runqs_cmd lives in the lifetime
|
||||||
.command = .RunQS,
|
// of this function.
|
||||||
.args = runqs_args,
|
try cmds_parsed.append(&runqs_cmd.base);
|
||||||
});
|
|
||||||
|
|
||||||
try runner_clone.runCommands(cmds_parsed, true);
|
try runner_clone.runCommands(cmds_parsed, true);
|
||||||
try stdout.write("\n");
|
_ = try stdout.write("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
fn doHelp() void {
|
||||||
const allocator = std.heap.direct_allocator;
|
log.debug("scritcher!", .{});
|
||||||
|
log.debug("usage: scritcher [run|help|repl]", .{});
|
||||||
|
log.debug("\tscritcher run path_to_script.scri path_to_input_file.bmp", .{});
|
||||||
|
log.debug("\tscritcher repl path_to_script.scri path_to_input_file.bmp", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doRun(allocator: std.mem.Allocator, args_it: anytype) !void {
|
||||||
var lang = langs.Lang.init(allocator);
|
var lang = langs.Lang.init(allocator);
|
||||||
defer lang.deinit();
|
defer lang.deinit();
|
||||||
|
|
||||||
var runner = runners.Runner.init(allocator, false);
|
var runner = runners.Runner.init(allocator, false);
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
var args_it = std.process.args();
|
const scri_path = (args_it.next() orelse @panic("run: expected scri path"));
|
||||||
|
|
||||||
// TODO print help
|
var file = try std.fs.cwd().openFile(scri_path, .{});
|
||||||
|
|
||||||
_ = try (args_it.next(allocator) orelse @panic("expected exe name"));
|
|
||||||
const scri_path = try (args_it.next(allocator) orelse @panic("expected scri path"));
|
|
||||||
|
|
||||||
if (std.mem.eql(u8, scri_path, "repl")) {
|
|
||||||
return try doRepl(allocator, &args_it);
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = try std.fs.File.openRead(scri_path);
|
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
// sadly, we read it all into memory. such is life
|
// sadly, we read it all into memory. such is life
|
||||||
const total_bytes = try file.getEndPos();
|
const total_bytes = try file.getEndPos();
|
||||||
|
|
||||||
var data = try allocator.alloc(u8, total_bytes);
|
const data = try allocator.alloc(u8, total_bytes);
|
||||||
defer allocator.free(data);
|
defer allocator.free(data);
|
||||||
_ = try file.read(data);
|
_ = try file.read(data);
|
||||||
|
|
||||||
|
@ -201,3 +261,33 @@ pub fn main() !void {
|
||||||
|
|
||||||
try runner.runCommands(cmds, true);
|
try runner.runCommands(cmds, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
var allocator_instance = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer {
|
||||||
|
_ = allocator_instance.deinit();
|
||||||
|
}
|
||||||
|
const allocator = allocator_instance.allocator();
|
||||||
|
|
||||||
|
var args_it = try std.process.argsWithAllocator(allocator);
|
||||||
|
defer args_it.deinit();
|
||||||
|
|
||||||
|
_ = args_it.skip();
|
||||||
|
const cli_command_opt = args_it.next();
|
||||||
|
if (cli_command_opt == null) {
|
||||||
|
return doHelp();
|
||||||
|
}
|
||||||
|
|
||||||
|
const cli_command = cli_command_opt.?;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, cli_command, "help")) {
|
||||||
|
return doHelp();
|
||||||
|
} else if (std.mem.eql(u8, cli_command, "repl")) {
|
||||||
|
return try doRepl(allocator, &args_it);
|
||||||
|
} else if (std.mem.eql(u8, cli_command, "run")) {
|
||||||
|
return try doRun(allocator, &args_it);
|
||||||
|
} else {
|
||||||
|
log.debug("unknown command: '{s}'", .{cli_command});
|
||||||
|
return error.UnknownCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ const std = @import("std");
|
||||||
const lv2 = @import("lv2_helpers.zig");
|
const lv2 = @import("lv2_helpers.zig");
|
||||||
const c = lv2.c;
|
const c = lv2.c;
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_plugin);
|
||||||
const ImageError = @import("image.zig").ImageError;
|
const ImageError = @import("image.zig").ImageError;
|
||||||
|
|
||||||
/// Control port
|
/// Control port
|
||||||
|
@ -35,7 +36,7 @@ pub const Position = struct {
|
||||||
|
|
||||||
pub fn seekPos(self: Position, total_size: usize) SeekPos {
|
pub fn seekPos(self: Position, total_size: usize) SeekPos {
|
||||||
std.debug.assert(self.index <= self.split);
|
std.debug.assert(self.index <= self.split);
|
||||||
var tot = total_size / self.split;
|
const tot = total_size / self.split;
|
||||||
|
|
||||||
return SeekPos{
|
return SeekPos{
|
||||||
.start = self.index * tot,
|
.start = self.index * tot,
|
||||||
|
@ -46,7 +47,7 @@ pub const Position = struct {
|
||||||
|
|
||||||
/// Represents the starting context for a single plugin run.
|
/// Represents the starting context for a single plugin run.
|
||||||
pub const Context = struct {
|
pub const Context = struct {
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
world: *c.LilvWorld,
|
world: *c.LilvWorld,
|
||||||
plugin: *const c.LilvPlugin,
|
plugin: *const c.LilvPlugin,
|
||||||
|
|
||||||
|
@ -61,29 +62,11 @@ pub const Context = struct {
|
||||||
|
|
||||||
/// Specific run context for non-plugins.
|
/// Specific run context for non-plugins.
|
||||||
pub const RunBuffers = struct {
|
pub const RunBuffers = struct {
|
||||||
allocator: *std.mem.Allocator,
|
// we use [2]f32 to account for stereo plugins, however
|
||||||
|
// we only use in_buf[0] and out_buf[0], and don't use the
|
||||||
in: []f32,
|
// (supposedly) right side of neither input or output.
|
||||||
out: []f32,
|
in: [2]f32 = [_]f32{0} ** 2,
|
||||||
|
out: [2]f32 = [_]f32{0} ** 2,
|
||||||
pub fn init(allocator: *std.mem.Allocator) !RunBuffers {
|
|
||||||
// we allocate []f32 with size 2 to account for stereo plugins, however
|
|
||||||
// we only use &in_buf[0] and &out_buf[0], and don't use the
|
|
||||||
// (supposedly) right side of neither input or output.
|
|
||||||
var in_buf = try allocator.alloc(f32, 2);
|
|
||||||
std.mem.secureZero(f32, in_buf);
|
|
||||||
|
|
||||||
return RunBuffers{
|
|
||||||
.allocator = allocator,
|
|
||||||
.in = in_buf,
|
|
||||||
.out = try allocator.alloc(f32, 2),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *RunBuffers) void {
|
|
||||||
self.allocator.free(self.in);
|
|
||||||
self.allocator.free(self.out);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents the specific run context of plugin instantation.
|
/// Represents the specific run context of plugin instantation.
|
||||||
|
@ -92,10 +75,12 @@ pub const RunContext = struct {
|
||||||
instance: *c.LilvInstance,
|
instance: *c.LilvInstance,
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
plugin: *const c.LilvPlugin,
|
plugin: *const c.LilvPlugin,
|
||||||
) !RunContext {
|
) !RunContext {
|
||||||
var instance = c.lilv_plugin_instantiate(plugin, f64(44100), null);
|
_ = allocator; // TODO batch RunBuffers?
|
||||||
|
|
||||||
|
const instance = c.lilv_plugin_instantiate(plugin, @as(f64, 44100), null);
|
||||||
errdefer c.lilv_instance_free(instance);
|
errdefer c.lilv_instance_free(instance);
|
||||||
|
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
|
@ -103,63 +88,67 @@ pub const RunContext = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
return RunContext{
|
return RunContext{
|
||||||
.buffers = try RunBuffers.init(allocator),
|
.buffers = RunBuffers{},
|
||||||
.instance = instance.?,
|
.instance = instance.?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *RunContext) void {
|
pub fn deinit(self: *RunContext) void {
|
||||||
c.lilv_instance_free(self.instance);
|
c.lilv_instance_free(self.instance);
|
||||||
self.buffers.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn connectPorts(self: *RunContext, ports: []lv2.Port) void {
|
pub fn connectPorts(self: *RunContext, ports: []lv2.Port) void {
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
var o: usize = 0;
|
var o: usize = 0;
|
||||||
|
|
||||||
var in_buf = self.buffers.in;
|
for (ports, 0..) |_, p_idx| {
|
||||||
var out_buf = self.buffers.out;
|
const p = @as(u32, @intCast(p_idx));
|
||||||
|
|
||||||
for (ports) |_, p_idx| {
|
|
||||||
var p = @intCast(u32, p_idx);
|
|
||||||
var port: *lv2.Port = &ports[p_idx];
|
var port: *lv2.Port = &ports[p_idx];
|
||||||
|
|
||||||
switch (port.ptype) {
|
switch (port.ptype) {
|
||||||
.Control => lv2.lilv_instance_connect_port(self.instance, p, &port.value),
|
.Control => lv2.lilv_instance_connect_port(self.instance, p, &port.value),
|
||||||
.Audio => blk: {
|
.Audio => {
|
||||||
if (port.is_input) {
|
if (port.is_input) {
|
||||||
lv2.lilv_instance_connect_port(self.instance, p, &in_buf[i]);
|
lv2.lilv_instance_connect_port(
|
||||||
|
self.instance,
|
||||||
|
p,
|
||||||
|
&self.buffers.in[i],
|
||||||
|
);
|
||||||
i += 1;
|
i += 1;
|
||||||
} else {
|
} else {
|
||||||
lv2.lilv_instance_connect_port(self.instance, p, &out_buf[o]);
|
lv2.lilv_instance_connect_port(
|
||||||
|
self.instance,
|
||||||
|
p,
|
||||||
|
&self.buffers.out[o],
|
||||||
|
);
|
||||||
o += 1;
|
o += 1;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => lv2.lilv_instance_connect_port(self.instance, p, null),
|
// else => lv2.lilv_instance_connect_port(self.instance, p, null),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn makeContext(allocator: *std.mem.Allocator, plugin_uri: []const u8) !Context {
|
pub fn makeContext(allocator: std.mem.Allocator, plugin_uri: []const u8) !Context {
|
||||||
const cstr_plugin_uri = try std.cstr.addNullByte(allocator, plugin_uri);
|
const cstr_plugin_uri = try allocator.dupeZ(u8, plugin_uri);
|
||||||
defer allocator.free(cstr_plugin_uri);
|
defer allocator.free(cstr_plugin_uri);
|
||||||
|
|
||||||
var world: *c.LilvWorld = c.lilv_world_new().?;
|
const world: *c.LilvWorld = c.lilv_world_new().?;
|
||||||
errdefer c.lilv_world_free(world);
|
errdefer c.lilv_world_free(world);
|
||||||
|
|
||||||
c.lilv_world_load_all(world);
|
c.lilv_world_load_all(world);
|
||||||
|
|
||||||
var uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse blk: {
|
const uri: *c.LilvNode = c.lilv_new_uri(world, cstr_plugin_uri.ptr) orelse {
|
||||||
std.debug.warn("Invalid plugin URI <{}>\n", plugin_uri);
|
log.debug("Invalid plugin URI <{s}>", .{plugin_uri});
|
||||||
return ImageError.InvalidPlugin;
|
return ImageError.InvalidPlugin;
|
||||||
};
|
};
|
||||||
defer c.lilv_node_free(uri);
|
defer c.lilv_node_free(uri);
|
||||||
|
|
||||||
const plugins: *const c.LilvPlugins = c.lilv_world_get_all_plugins(world);
|
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: {
|
const plugin: *const c.LilvPlugin = c.lilv_plugins_get_by_uri(plugins, uri) orelse {
|
||||||
std.debug.warn("Plugin <{}> not found\n", plugin_uri);
|
log.debug("Plugin <{s}> not found", .{plugin_uri});
|
||||||
return ImageError.UnknownPlugin;
|
return ImageError.UnknownPlugin;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
116
src/printer.zig
116
src/printer.zig
|
@ -1,40 +1,88 @@
|
||||||
|
const std = @import("std");
|
||||||
const langs = @import("lang.zig");
|
const langs = @import("lang.zig");
|
||||||
|
|
||||||
pub fn printList(list: langs.CommandList, stream: var) !void {
|
const log = std.log.scoped(.scritcher_printer);
|
||||||
for (list.toSlice()) |cmd| {
|
|
||||||
var command = switch (cmd.command) {
|
|
||||||
.Noop => "noop",
|
|
||||||
.Load => "load",
|
|
||||||
.Quicksave => "quicksave",
|
|
||||||
.RunQS => "runqs",
|
|
||||||
|
|
||||||
.Amp => "amp",
|
fn printCommandWithParams(stream: anytype, command: anytype) !void {
|
||||||
.RFlanger => "rflanger",
|
const Parameters = @TypeOf(command.parameters);
|
||||||
.Eq => "eq",
|
try stream.print(" {d} {d}", .{ command.split, command.index });
|
||||||
.Phaser => "phaser",
|
inline for (@typeInfo(Parameters).Struct.fields) |field| {
|
||||||
.Mbeq => "mbeq",
|
if (field.type == f32 or field.type == f64) {
|
||||||
.Chorus => "chorus",
|
try stream.print(" {}", .{@field(command.parameters, field.name)});
|
||||||
.PitchScaler => "pitchscaler",
|
} else if (field.type == usize or field.type == u64) {
|
||||||
.Reverb => "reverb",
|
try stream.print(" {d}", .{@field(command.parameters, field.name)});
|
||||||
.Highpass => "highpass",
|
} else {
|
||||||
.Delay => "delay",
|
try stream.print(" {s}", .{@field(command.parameters, field.name)});
|
||||||
.Vinyl => "vinyl",
|
|
||||||
.RevDelay => "revdelay",
|
|
||||||
|
|
||||||
.Noise => "noise",
|
|
||||||
.WildNoise => "wildnoise",
|
|
||||||
.Write => "write",
|
|
||||||
.Embed => "embed",
|
|
||||||
|
|
||||||
.Rotate => "rotate",
|
|
||||||
};
|
|
||||||
|
|
||||||
try stream.print("{}", command);
|
|
||||||
|
|
||||||
for (cmd.args.toSlice()) |arg| {
|
|
||||||
try stream.print(" {}", arg);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
try stream.write(";\n");
|
}
|
||||||
|
|
||||||
|
fn printCommand(stream: anytype, cmd: *langs.Command, comptime tag: langs.Command.Tag) !void {
|
||||||
|
const CommandStruct = langs.Command.tagToType(tag);
|
||||||
|
const casted = cmd.cast(CommandStruct).?;
|
||||||
|
|
||||||
|
const ctype = CommandStruct.command_type;
|
||||||
|
switch (ctype) {
|
||||||
|
.lv2_command => try printCommandWithParams(stream, casted),
|
||||||
|
.custom_command => try printCommandWithParams(stream, casted),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printList(list: langs.CommandList, stream: anytype) !void {
|
||||||
|
for (list.list.items) |cmd| {
|
||||||
|
const command = @tagName(cmd.tag);
|
||||||
|
try stream.print("{s}", .{command});
|
||||||
|
|
||||||
|
switch (cmd.tag) {
|
||||||
|
.load => {
|
||||||
|
const load = cmd.cast(langs.Command.Load).?;
|
||||||
|
try stream.print(" {s}", .{load.path});
|
||||||
|
},
|
||||||
|
.runqs => {
|
||||||
|
const runqs = cmd.cast(langs.Command.RunQS).?;
|
||||||
|
try stream.print(" {s}", .{runqs.program});
|
||||||
|
},
|
||||||
|
.noop, .quicksave => {},
|
||||||
|
.rotate => {
|
||||||
|
const rotate = cmd.cast(langs.Command.Rotate).?;
|
||||||
|
try stream.print(" {d} {s}", .{ 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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
486
src/runner.zig
486
src/runner.zig
|
@ -5,6 +5,8 @@ const plugin = @import("plugin.zig");
|
||||||
const custom = @import("custom.zig");
|
const custom = @import("custom.zig");
|
||||||
const magick = @import("magick.zig");
|
const magick = @import("magick.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.scritcher_runner);
|
||||||
|
|
||||||
const Position = plugin.Position;
|
const Position = plugin.Position;
|
||||||
const ParamList = plugin.ParamList;
|
const ParamList = plugin.ParamList;
|
||||||
const ParamMap = plugin.ParamMap;
|
const ParamMap = plugin.ParamMap;
|
||||||
|
@ -18,18 +20,21 @@ pub const RunError = error{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Runner = struct {
|
pub const Runner = struct {
|
||||||
allocator: *std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
/// The currently opened image in the runner
|
/// The currently opened image in the runner
|
||||||
image: ?*Image = null,
|
image: ?*Image = null,
|
||||||
|
|
||||||
/// If the runner is in REPL mode
|
/// If the runner is in REPL mode
|
||||||
repl: bool = false,
|
repl: bool,
|
||||||
|
|
||||||
pub fn init(allocator: *std.mem.Allocator, repl: bool) Runner {
|
args: []const [:0]u8,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator, repl: bool) Runner {
|
||||||
return Runner{
|
return Runner{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.repl = repl,
|
.repl = repl,
|
||||||
|
.args = std.process.argsAlloc(allocator) catch unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,50 +42,59 @@ pub const Runner = struct {
|
||||||
if (self.image) |image| {
|
if (self.image) |image| {
|
||||||
image.close();
|
image.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std.process.argsFree(self.allocator, self.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone(self: *Runner) !Runner {
|
pub fn clone(self: *Runner) !Runner {
|
||||||
var cloned_image = if (self.image) |image| try image.clone() else null;
|
const 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 {
|
fn resolveArg(self: *Runner, load_path: []const u8) ![]const u8 {
|
||||||
|
std.debug.assert(load_path.len > 0);
|
||||||
if (load_path[0] == ':') {
|
if (load_path[0] == ':') {
|
||||||
// parse the index from 1 to end
|
// parse the index from 1 to end
|
||||||
var index = try std.fmt.parseInt(usize, load_path[1..], 10);
|
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 it isn't in the repl, args look like this:
|
||||||
if (self.repl) index += 1;
|
// 'scritcher ./script ./image'
|
||||||
|
// if it is, it looks like this
|
||||||
|
// 'scritcher repl ./script ./image'
|
||||||
|
|
||||||
var args_it = std.process.args();
|
// ':0' should ALWAYS point to the image.
|
||||||
_ = args_it.skip();
|
if (self.repl) index += 3 else index += 3;
|
||||||
|
|
||||||
var i: usize = 0;
|
for (self.args, 0..) |arg, idx| {
|
||||||
while (i <= index) : (i += 1) {
|
log.debug("arg{d} = {s}", .{ idx, arg });
|
||||||
_ = args_it.skip();
|
|
||||||
}
|
}
|
||||||
|
log.debug("fetch arg idx={d}", .{index});
|
||||||
const arg = try (args_it.next(self.allocator) orelse @panic("expected argument"));
|
log.debug("fetch arg val={s}", .{self.args[index]});
|
||||||
|
return self.args[index];
|
||||||
return arg;
|
|
||||||
} else {
|
} else {
|
||||||
return load_path;
|
return load_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Caller owns returned memory.
|
||||||
fn resolveArgPath(self: *Runner, path_or_argidx: []const u8) ![]const u8 {
|
fn resolveArgPath(self: *Runner, path_or_argidx: []const u8) ![]const u8 {
|
||||||
const path = try self.resolveArg(path_or_argidx);
|
const path = try self.resolveArg(path_or_argidx);
|
||||||
const resolved_path = try std.fs.path.resolve(
|
const resolved_path = try std.fs.path.resolve(
|
||||||
self.allocator,
|
self.allocator,
|
||||||
[_][]const u8{path},
|
&[_][]const u8{path},
|
||||||
);
|
);
|
||||||
|
|
||||||
return resolved_path;
|
return resolved_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCmd(self: *Runner, path_or_argidx: []const u8) !void {
|
fn loadCmd(self: *Runner, path_or_argidx: []const u8) !void {
|
||||||
var load_path = try self.resolveArgPath(path_or_argidx);
|
const load_path = try self.resolveArgPath(path_or_argidx);
|
||||||
std.debug.warn("\tload path: {}\n", load_path);
|
log.debug("\tload path: {s}", .{load_path});
|
||||||
|
|
||||||
// we could use ImageMagick to convert from X to BMP
|
// we could use ImageMagick to convert from X to BMP
|
||||||
// but i can't find an easy way to do things in memory.
|
// but i can't find an easy way to do things in memory.
|
||||||
|
@ -89,14 +103,15 @@ pub const Runner = struct {
|
||||||
// before loading the file into scritcher. for example, you can start
|
// before loading the file into scritcher. for example, you can start
|
||||||
// krita/gimp and make it export a bmp and while in the program you can
|
// krita/gimp and make it export a bmp and while in the program you can
|
||||||
// apply filters, etc.
|
// apply filters, etc.
|
||||||
if (!std.mem.endsWith(u8, load_path, ".bmp")) {
|
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");
|
log.debug("Only BMP files are allowed to be loaded. Got path '{s}'", .{load_path});
|
||||||
return RunError.NoBMP;
|
return RunError.NoBMP;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't copy load_path into a temporary file because we're already
|
// we don't copy load_path into a temporary file because we're already
|
||||||
// loading it under the SFM_READ mode, which won't cause any destructive
|
// loading it under the SFM_READ mode, which won't cause any destructive
|
||||||
// operations on the file.
|
// operations on the file.
|
||||||
|
if (self.image) |image| image.close();
|
||||||
self.image = try Image.open(self.allocator, load_path);
|
self.image = try Image.open(self.allocator, load_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,20 +119,21 @@ pub const Runner = struct {
|
||||||
if (self.image) |image| {
|
if (self.image) |image| {
|
||||||
return image;
|
return image;
|
||||||
} else {
|
} else {
|
||||||
std.debug.warn("image is required!\n");
|
log.debug("image is required!", .{});
|
||||||
return RunError.ImageRequired;
|
return RunError.ImageRequired;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Caller owns returned memory.
|
||||||
fn makeGlitchedPath(self: *Runner) ![]const u8 {
|
fn makeGlitchedPath(self: *Runner) ![]const u8 {
|
||||||
// we want to transform basename, if it is "x.bmp" to "x_gN.bmp", where
|
// we want to transform basename, if it is "x.bmp" to "x_gN.bmp", where
|
||||||
// N is the maximum non-used integer.
|
// N is the maximum non-used integer.
|
||||||
var image = try self.getImage();
|
const image = try self.getImage();
|
||||||
|
|
||||||
const basename = std.fs.path.basename(image.path);
|
const basename = std.fs.path.basename(image.path);
|
||||||
const dirname = std.fs.path.dirname(image.path).?;
|
const dirname = std.fs.path.dirname(image.path).?;
|
||||||
|
|
||||||
var dir = try std.fs.Dir.open(self.allocator, dirname);
|
var dir = try std.fs.cwd().openDir(dirname, .{ .iterate = true });
|
||||||
defer dir.close();
|
defer dir.close();
|
||||||
|
|
||||||
const period_idx = std.mem.lastIndexOf(u8, basename, ".").?;
|
const period_idx = std.mem.lastIndexOf(u8, basename, ".").?;
|
||||||
|
@ -125,18 +141,18 @@ pub const Runner = struct {
|
||||||
|
|
||||||
// starts_with would be "x_g", we want to find all files in the directory
|
// starts_with would be "x_g", we want to find all files in the directory
|
||||||
// that start with that name.
|
// that start with that name.
|
||||||
const starts_with = try std.fmt.allocPrint(
|
const starts_with = try std.fmt.allocPrint(self.allocator, "{s}_g", .{
|
||||||
self.allocator,
|
|
||||||
"{}_g",
|
|
||||||
basename[0..period_idx],
|
basename[0..period_idx],
|
||||||
);
|
});
|
||||||
defer self.allocator.free(starts_with);
|
defer self.allocator.free(starts_with);
|
||||||
|
|
||||||
var max: usize = 0;
|
var max: usize = 0;
|
||||||
|
|
||||||
while (try dir.next()) |entry| {
|
var it = dir.iterate();
|
||||||
|
|
||||||
|
while (try it.next()) |entry| {
|
||||||
switch (entry.kind) {
|
switch (entry.kind) {
|
||||||
.File => blk: {
|
.file => blk: {
|
||||||
if (!std.mem.startsWith(u8, entry.name, starts_with)) break :blk {};
|
if (!std.mem.startsWith(u8, entry.name, starts_with)) break :blk {};
|
||||||
|
|
||||||
// we want to get the N in x_gN.ext
|
// we want to get the N in x_gN.ext
|
||||||
|
@ -150,6 +166,8 @@ pub const Runner = struct {
|
||||||
// if N isn't a number, we just ignore that file
|
// if N isn't a number, we just ignore that file
|
||||||
const idx_str = entry.name[entry_gidx + 2 .. entry_pidx];
|
const idx_str = entry.name[entry_gidx + 2 .. entry_pidx];
|
||||||
const idx = std.fmt.parseInt(usize, idx_str, 10) catch |err| {
|
const idx = std.fmt.parseInt(usize, idx_str, 10) catch |err| {
|
||||||
|
log.debug("ignoring file {s}", .{@errorName(err)});
|
||||||
|
|
||||||
break :blk {};
|
break :blk {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,14 +177,12 @@ pub const Runner = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const out_path = try std.fmt.allocPrint(
|
const out_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}{d}{s}", .{
|
||||||
self.allocator,
|
|
||||||
"{}/{}{}{}",
|
|
||||||
dirname,
|
dirname,
|
||||||
starts_with,
|
starts_with,
|
||||||
max + 1,
|
max + 1,
|
||||||
extension,
|
extension,
|
||||||
);
|
});
|
||||||
|
|
||||||
return out_path;
|
return out_path;
|
||||||
}
|
}
|
||||||
|
@ -174,349 +190,155 @@ pub const Runner = struct {
|
||||||
fn quicksaveCmd(self: *Runner) !void {
|
fn quicksaveCmd(self: *Runner) !void {
|
||||||
var image = try self.getImage();
|
var image = try self.getImage();
|
||||||
const out_path = try self.makeGlitchedPath();
|
const out_path = try self.makeGlitchedPath();
|
||||||
|
defer self.allocator.free(out_path);
|
||||||
try image.saveTo(out_path);
|
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();
|
var image = try self.getImage();
|
||||||
const out_path = try self.makeGlitchedPath();
|
const out_path = try self.makeGlitchedPath();
|
||||||
|
defer self.allocator.free(out_path);
|
||||||
try image.saveTo(out_path);
|
try image.saveTo(out_path);
|
||||||
|
|
||||||
var proc = try std.ChildProcess.init(
|
var proc = std.ChildProcess.init(
|
||||||
[_][]const u8{ program, out_path },
|
&[_][]const u8{ runqs.program, out_path },
|
||||||
self.allocator,
|
self.allocator,
|
||||||
);
|
);
|
||||||
defer proc.deinit();
|
//defer proc.deinit();
|
||||||
|
|
||||||
std.debug.warn("running '{} {}'\n", program, out_path);
|
log.debug("running '{s} {s}'", .{ runqs.program, out_path });
|
||||||
_ = try proc.spawnAndWait();
|
_ = try proc.spawnAndWait();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the http://lv2plug.in/plugins/eg-amp plugin over the file.
|
fn rotateCmd(self: *Runner, cmd: *lang.Command) !void {
|
||||||
fn ampCmd(self: *Runner, pos: Position, params: ParamList) !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);
|
const image = try self.getImage();
|
||||||
|
const c_bgfill = try self.allocator.dupeZ(u8, 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 {
|
fn executeLV2Command(self: *@This(), command: anytype) !void {
|
||||||
var image = try self.getImage();
|
const pos = plugin.Position{
|
||||||
try image.runPlugin("http://plugin.org.uk/swh-plugins/retroFlange", pos, params);
|
.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);
|
var params = ParamList.init(self.allocator);
|
||||||
defer params.deinit();
|
defer params.deinit();
|
||||||
|
|
||||||
for (bands) |band_value, idx| {
|
const typ = @TypeOf(command);
|
||||||
var sym = try std.fmt.allocPrint(self.allocator, "band_{}", idx + 1);
|
|
||||||
|
inline for (@typeInfo(@TypeOf(command.parameters)).Struct.fields) |cmd_field| {
|
||||||
try params.append(plugin.Param{
|
try params.append(plugin.Param{
|
||||||
.sym = sym,
|
.sym = cmd_field.name,
|
||||||
.value = band_value,
|
.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();
|
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 {
|
fn executeCustomCommand(self: *@This(), command: anytype) !void {
|
||||||
var image = try self.getImage();
|
const pos = plugin.Position{
|
||||||
try image.runPlugin("http://plugin.org.uk/swh-plugins/pitchScaleHQ", pos, params);
|
.split = command.split,
|
||||||
}
|
.index = command.index,
|
||||||
|
|
||||||
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 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.at(0);
|
|
||||||
try self.loadCmd(path);
|
|
||||||
|
|
||||||
// TODO is this needed?
|
|
||||||
break :blk;
|
|
||||||
},
|
|
||||||
.Quicksave => try self.quicksaveCmd(),
|
|
||||||
.RunQS => try self.runQSCmd(cmd.args.at(0)),
|
|
||||||
|
|
||||||
.Amp => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
try cmd.appendParam(¶ms, "gain");
|
|
||||||
try self.ampCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.RFlanger => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
try cmd.appendParam(¶ms, "delay_depth_avg");
|
|
||||||
try cmd.appendParam(¶ms, "law_freq");
|
|
||||||
try self.rFlangerCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Eq => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
try cmd.appendParam(¶ms, "lo");
|
|
||||||
try cmd.appendParam(¶ms, "mid");
|
|
||||||
try cmd.appendParam(¶ms, "hi");
|
|
||||||
|
|
||||||
try self.eqCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Phaser => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "lfo_rate");
|
|
||||||
try cmd.appendParam(¶ms, "lfo_depth");
|
|
||||||
try cmd.appendParam(¶ms, "fb");
|
|
||||||
try cmd.appendParam(¶ms, "spread");
|
|
||||||
|
|
||||||
try self.phaserCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Mbeq => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
const bands = try cmd.floatArgMany(self.allocator, 2, 15, f32(0));
|
|
||||||
defer self.allocator.free(bands);
|
|
||||||
|
|
||||||
try self.mbeqCmd(pos, bands);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Chorus => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "voices");
|
|
||||||
try cmd.appendParam(¶ms, "delay_base");
|
|
||||||
try cmd.appendParam(¶ms, "voice_spread");
|
|
||||||
try cmd.appendParam(¶ms, "detune");
|
|
||||||
try cmd.appendParam(¶ms, "law_freq");
|
|
||||||
try cmd.appendParam(¶ms, "attendb");
|
|
||||||
|
|
||||||
try self.chorusCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.PitchScaler => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
try cmd.appendParam(¶ms, "mult");
|
|
||||||
try self.pitchScalerCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Reverb => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "roomLength");
|
|
||||||
try cmd.appendParam(¶ms, "roomWidth");
|
|
||||||
try cmd.appendParam(¶ms, "roomHeight");
|
|
||||||
try cmd.appendParam(¶ms, "sourceLR");
|
|
||||||
try cmd.appendParam(¶ms, "sourceFB");
|
|
||||||
try cmd.appendParam(¶ms, "listLR");
|
|
||||||
try cmd.appendParam(¶ms, "listFB");
|
|
||||||
try cmd.appendParam(¶ms, "hpf");
|
|
||||||
try cmd.appendParam(¶ms, "warmth");
|
|
||||||
try cmd.appendParam(¶ms, "diffusion");
|
|
||||||
|
|
||||||
try self.reverbCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Highpass => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "freq");
|
|
||||||
try cmd.appendParam(¶ms, "gain");
|
|
||||||
try cmd.appendParam(¶ms, "noClip");
|
|
||||||
|
|
||||||
try self.highpassCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Delay => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "seed");
|
|
||||||
try cmd.appendParam(¶ms, "gain");
|
|
||||||
try cmd.appendParam(¶ms, "feedback_pc");
|
|
||||||
try cmd.appendParam(¶ms, "tap_count");
|
|
||||||
try cmd.appendParam(¶ms, "first_delay");
|
|
||||||
try cmd.appendParam(¶ms, "delay_range");
|
|
||||||
try cmd.appendParam(¶ms, "delay_scale");
|
|
||||||
try cmd.appendParam(¶ms, "delay_rand_pc");
|
|
||||||
try cmd.appendParam(¶ms, "gain_scale");
|
|
||||||
try cmd.appendParam(¶ms, "wet");
|
|
||||||
|
|
||||||
try self.delayCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Vinyl => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "year");
|
|
||||||
try cmd.appendParam(¶ms, "rpm");
|
|
||||||
try cmd.appendParam(¶ms, "warp");
|
|
||||||
try cmd.appendParam(¶ms, "click");
|
|
||||||
try cmd.appendParam(¶ms, "wear");
|
|
||||||
|
|
||||||
try self.vinylCmd(pos, params);
|
|
||||||
},
|
|
||||||
|
|
||||||
.RevDelay => blk: {
|
|
||||||
const pos = try cmd.consumePosition();
|
|
||||||
|
|
||||||
try cmd.appendParam(¶ms, "delay_time");
|
|
||||||
try cmd.appendParam(¶ms, "dry_level");
|
|
||||||
try cmd.appendParam(¶ms, "wet_level");
|
|
||||||
try cmd.appendParam(¶ms, "feedback");
|
|
||||||
try cmd.appendParam(¶ms, "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.at(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);
|
|
||||||
},
|
|
||||||
|
|
||||||
else => blk: {
|
|
||||||
std.debug.warn("Unsupported command: {}\n", cmd.command);
|
|
||||||
break :blk RunError.UnknownCommand;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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.*),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
/// Run a list of commands.
|
||||||
pub fn runCommands(
|
pub fn runCommands(
|
||||||
self: *Runner,
|
self: *Runner,
|
||||||
cmds: lang.CommandList,
|
cmds: lang.CommandList,
|
||||||
debug_flag: bool,
|
debug_flag: bool,
|
||||||
) !void {
|
) !void {
|
||||||
for (cmds.toSlice()) |const_cmd| {
|
_ = debug_flag;
|
||||||
if (debug_flag) const_cmd.print();
|
for (cmds.list.items) |cmd| {
|
||||||
|
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);
|
try self.runCommand(cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
test "running noop" {
|
test "running noop" {
|
||||||
const allocator = std.heap.direct_allocator;
|
const allocator = std.testing.allocator;
|
||||||
var cmds = lang.CommandList.init(allocator);
|
var cmds = lang.CommandList.init(allocator);
|
||||||
defer cmds.deinit();
|
defer cmds.deinit();
|
||||||
|
|
||||||
var cmd_ptr = try allocator.create(lang.Command);
|
const command = lang.Command{ .tag = .noop };
|
||||||
cmd_ptr.* = lang.Command{
|
|
||||||
.command = .Noop,
|
|
||||||
.args = lang.ArgList.init(allocator),
|
|
||||||
};
|
|
||||||
try cmds.append(cmd_ptr);
|
|
||||||
|
|
||||||
var runner = Runner.init(allocator);
|
var noop = try allocator.create(lang.Command.Noop);
|
||||||
|
noop.* = lang.Command.Noop{ .base = command };
|
||||||
|
try cmds.append(&noop.base);
|
||||||
|
|
||||||
|
var runner = Runner.init(allocator, false);
|
||||||
defer runner.deinit();
|
defer runner.deinit();
|
||||||
|
|
||||||
try runner.runCommands(cmds, false);
|
try runner.runCommands(cmds, false);
|
||||||
|
|
Loading…
Reference in a new issue