#[derive(Params)] reference
Every attribute, range type, smoothing mode, and parameter type that the params derive accepts. For a narrative tour and recipes, see the parameters chapter in the guide.
##[param(...)] attribute keys
| Key | Example | Notes |
|---|---|---|
id |
id = 0 |
Optional. Auto-assigned by field order if omitted. Stable across releases — never change. Required when nesting structs to avoid 0/1/2/… collisions. |
name |
name = "Gain" |
Required. Display name in host and GUI. |
short_name |
short_name = "Gn" |
Abbreviated name for narrow strips. Defaults to name. |
range |
range = "linear(-60, 6)" |
Value mapping. Inferred for BoolParam and EnumParam<T>. |
default |
default = 0.0 |
Default value in plain units. Defaults to range min. |
unit |
unit = "dB" |
Display unit. Shapes the default formatter. Valid: dB, Hz, ms, s, %, pan, st. |
smooth |
smooth = "exp(5)" |
Smoothing style + time in ms. See Smoothing. |
group |
group = "Filter" |
Parameter group surfaced by the host (CLAP module path / VST3 unit / AU group). |
flags |
flags = "automatable | bypass" |
Combination of: automatable, hidden, readonly, bypass. |
format |
format = "format_cutoff" |
Method on the params struct that converts a f64 value to a String. |
parse |
parse = "parse_cutoff" |
Inverse of format. Method that parses a host text-input &str back to f64. |
The derive generates MyParams::new(), a Default impl, the full Params trait impl, and a typed MyParamsParamId enum (#[repr(u32)]) with one variant per parameter.
#Parameter types
| Field type | Default widget | Notes |
|---|---|---|
FloatParam |
knob | Continuous. Supports smoothing and custom formatting. |
BoolParam |
toggle | On / off. Range is implicit 0..1 — don't pass range. |
IntParam |
knob | Integer steps within a range. |
EnumParam<T> |
selector | Click-to-cycle. T is a #[derive(ParamEnum)] enum; range is inferred from the variant count. |
MeterSlot |
meter | Read-only, written from process(), drawn by the GUI. Declared with #[meter], not #[param(...)]. |
#Enum parameters
#[derive(ParamEnum)]
pub enum Waveform { Sine, Saw, Square, Triangle }
#[param(name = "Waveform")]
pub waveform: EnumParam<Waveform>,
Use #[name = "..."] on a variant to override its display text — the Rust name stays the same:
#[derive(ParamEnum)]
pub enum Direction {
#[name = "Up/Down"]
UpDown,
Down,
}
#Meters
Meters are not parameters — they flow audio-thread → UI-thread instead of host → plugin. Declare them with #[meter]:
#[derive(Params)]
pub struct MyParams {
#[param(name = "Gain", range = "linear(-60, 6)", unit = "dB", smooth = "exp(5)")]
pub gain: FloatParam,
#[meter] pub meter_l: MeterSlot,
#[meter] pub meter_r: MeterSlot,
}
Parameters and meters share the generated ParamId enum. Write from process(), read in layout():
// process():
context.set_meter(P::MeterL, buffer.output_peak(0));
context.set_meter(P::MeterR, buffer.output_peak(1));
// layout():
meter(&[P::MeterL, P::MeterR], "Level").rows(3)
The write is realtime-safe (atomic); the GUI reads the latest value every frame.
#Range types
range = "linear(-60, 6)" # linear between min and max
range = "log(20, 20000)" # logarithmic — frequency, time constants
range = "discrete(1, 16)" # integer steps
range = "enum(4)" # N discrete cases (rarely written by hand;
# EnumParam<T> infers this from the variant count)
BoolParam ranges are implicit 0..1. EnumParam<T> ranges are inferred from T's variant count.
#Smoothing
Host automation usually arrives block-rate. Smoothing interpolates between successive target values so there's no zipper noise on continuous parameters.
smooth = "none" # instant jump. Right for toggles, enums, voice counts.
smooth = "linear(20)" # linear ramp over 20 ms. Right for pan and mix.
smooth = "exp(5)" # exponential one-pole, 5 ms. Right for gain and filter cutoff.
In reset(), prime the smoothers:
fn reset(&mut self, sample_rate: f64, _: usize) {
self.params.set_sample_rate(sample_rate);
self.params.snap_smoothers();
}
In process(), pull a smoothed value per sample with .read():
let g = self.params.gain.read();
.read() returns f32 or f64 depending on the prelude in scope —
see Precision (preludes).
The method takes &self (the smoother state is atomic), so it
works through Arc<Params> without &mut.
#Flags
flags is a |-separated combination of bit flags:
| Flag | Meaning |
|---|---|
automatable |
Surfaces in the host's automation lane (default for non-bypass params). |
hidden |
Excluded from the host parameter list. Use sparingly — most hosts surface every exposed param. |
readonly |
Host can't write; GUI can't write either. For internal state you want serialized. |
bypass |
Marks the bypass parameter. Hosts treat this specially (per-track bypass UI). One per plugin. |
#Custom formatting
Most plugins get by with the default formatter chosen from unit. When you need conditional display — Hz vs. kHz, semitones, dotted-note durations — point format at a method on your params struct:
#[derive(Params)]
pub struct SynthParams {
#[param(name = "Cutoff", range = "log(20, 20000)", unit = "Hz",
format = "format_cutoff", parse = "parse_cutoff")]
pub cutoff: FloatParam,
}
impl SynthParams {
fn format_cutoff(&self, value: f64) -> String {
if value >= 1000.0 {
format!("{:.1} kHz", value / 1000.0)
} else {
format!("{:.0} Hz", value)
}
}
fn parse_cutoff(&self, text: &str) -> Option<f64> {
let t = text.trim().to_lowercase();
if let Some(n) = t.strip_suffix("khz") {
n.trim().parse::<f64>().ok().map(|v| v * 1000.0)
} else {
t.trim_end_matches("hz").trim().parse().ok()
}
}
}
parse is the inverse, used when the host accepts text input (Logic's "Type Value", REAPER's modify-value dialog, etc.).
#Nested structs
Split a wide parameter set into sub-structs with #[nested]:
#[derive(Params)]
pub struct PluginParams {
#[nested] pub filter: FilterParams,
#[nested] pub envelope: EnvelopeParams,
}
#[derive(Params)]
pub struct FilterParams {
#[param(id = 10, name = "Cutoff", group = "Filter",
range = "log(20, 20000)", unit = "Hz")]
pub cutoff: FloatParam,
}
Assign explicit id values when nesting. Auto-IDs are per-struct, so without id = the nested structs collide at 0, 1, 2, ... Pick a non-overlapping ID block per struct (10–19 for filter, 20–29 for envelope, etc.). Nested params are flattened for the host — it sees one list.
#Generated ParamId enum
The derive emits a #[repr(u32)] enum named <StructName>ParamId with one variant per parameter (and per meter). Use it everywhere you'd otherwise pass a raw u32:
use MyParamsParamId as P;
knob(P::Gain, "Gain");
slider(P::Pan, "Pan");
context.set_meter(P::MeterL, peak);
Typos become compile errors; rename-refactor stays safe.
#Shared ownership
The shell owns the Arc<MyParams> and passes a clone to YourPlugin::new(). GUI closures can also clone the Arc. Host automation writes atomically; every reader sees the latest value without locking.
pub struct MyPlugin {
params: Arc<MyParams>,
}
impl MyPlugin {
pub fn new(params: Arc<MyParams>) -> Self {
Self { params }
}
}