cargo xtask bundle fa76-plugin --releaseproduces both bundles under target/bundled/:
target/bundled/FA76.vst3/Contents/x86_64-win/FA76.vst3
target/bundled/FA76.clap
Copy FA76.vst3 to C:\Program Files\Common Files\VST3\ (or ~/.vst3 on
Linux, ~/Library/Audio/Plug-Ins/VST3 on macOS) and rescan in the host. The
CLAP bundle is built from the same code at no extra cost, so it is shipped too.
The plugin exists because the editor is shared, not duplicated:
| crate | what it is | dependencies |
|---|---|---|
fa76 |
the DSP core | none |
fa76-ui |
the faceplate: geometry, widgets, presets, fonts | fa76, egui |
fa76-playground |
standalone: file loading, transport, waveform | fa76-ui, eframe, cpal, symphonia |
fa76-plugin |
VST3/CLAP wiring | fa76-ui, nih_plug, nih_plug_egui |
xtask |
the bundler | nih_plug_xtask |
The core still has zero dependencies and is still tested on its own
(cargo test -p fa76). Nothing in fa76-ui knows whether it is running inside
eframe or inside a plugin window.
The two hosts store parameters completely differently - the standalone in
atomics, the plugin in nih-plug's FloatParam/EnumParam with begin/set/end
gestures so a DAW can record automation. The editor never touches either; it
talks to a trait:
pub trait EditorHost {
fn value(&self, control: Control) -> f32;
fn set(&self, control: Control, value: f32);
fn begin_gesture(&self, _control: Control) {}
fn end_gesture(&self, _control: Control) {}
fn gain_reduction_db(&self) -> f32;
fn take_input_peak(&self) -> f32;
fn take_output_peak(&self) -> f32;
}Control values are in the panel's own units - dial 0..10 for the attenuators,
1..7 for the pots, an index into the enum's ALL for the switches - so nothing
has to agree on a normalisation convention. The standalone implementation
ignores the gesture calls; the plugin turns them into
begin_set_parameter/end_set_parameter, so dragging a knob on the faceplate
writes one clean automation gesture rather than a burst of unrelated writes.
nih_plug_egui pins egui 0.31, so the whole workspace is pinned there too.
That is the reason the standalone uses eframe 0.31 rather than something
newer: sharing the editor is worth more than being on the latest egui, and the
alternative - two copies of the panel in two egui dialects - would drift apart
within a week.
Nine automatable parameters, named and scaled exactly as the panel reads them, so a host's generic UI shows the same numbers as the faceplate:
| id | type | range |
|---|---|---|
input |
float | 0..10 (dial) |
output |
float | 0..10 (dial) |
attack |
float | 1..7 (dial, 7 = fastest) |
release |
float | 1..7 (dial, 7 = fastest) |
ratio |
enum | 4:1, 8:1, 12:1, 20:1, All Buttons |
revision |
enum | Rev A, Rev D, Rev E, Rev F |
oversampling |
enum | 1x, 2x, 4x, 8x |
stereo |
enum | Linked, Dual Mono |
bypass |
bool |
SAMPLE_ACCURATE_AUTOMATION is off deliberately. The model is a closed feedback
loop with its own timing network; re-solving it at arbitrary sub-block
boundaries would not make the automation more faithful, it would just cost CPU.
Parameters are applied per block, and only when one has actually moved.
The core reports its oversampling group delay through
FetLimiterCore::latency_samples(). It is zero at 1x, and a few samples
otherwise; see docs/dsp/1176-circuit-model.md. There is no lookahead anywhere.
It is reported to the host from initialize, and again whenever the
oversampling parameter changes - switching 1x -> 4x mid-session moves the delay,
and a host that is not told will not recompensate it.
VST3_CLASS_ID is b"FutureboardFA76." - exactly 16 bytes. Changing it makes
hosts treat the plugin as a different one and drop saved state, so it must stay
put once anything has been saved with it.
The DSP suite runs against the core, independent of any host:
cargo test -p fa76 --release # 69 tests + doctest
cargo test -p fa76-ui # preset round-trips
cargo clippy --workspace --all-targetsThere is no automated test of the plugin wrapper itself. Validating it against
a real host - pluginval, or simply loading it in a DAW - has not been done and
is the obvious next step.