Parameters and host automation
Typed parameter values, subscriptions, gestures, and the audio boundary.
Parameters are the values the plugin exposes to its host: a gain, a bypass
switch, a mode selector. Declare them in soundor.config.ts, use the generated
soundor:parameters module from the UI, and connect them to audio processing in
each runtime.
Declare parameters
The config accepts four parameter types:
parameters: [
{ type: 'float', id: 'gain', label: 'Gain', min: 0, max: 1, default: 0.5 },
{ type: 'int', id: 'voices', label: 'Voices', min: 1, max: 16, default: 4 },
{ type: 'bool', id: 'bypass', label: 'Bypass', default: false },
{
type: 'enum',
id: 'mode',
label: 'Mode',
values: ['clean', 'warm'],
default: 'clean',
},
],Float and integer parameters may also have a display unit. IDs are unique
within the plugin. Generated declarations expose the corresponding value types:
numbers, booleans, and enum string unions.
Read, write, and subscribe
import { parameters } from 'soundor:parameters';
const gain = parameters.gain.get();
parameters.gain.set(0.75);
const unsubscribe = parameters.gain.subscribe((value) => {
console.log('Gain changed:', value);
});
// Call when this subscription is no longer needed.
unsubscribe();info exposes the declaration, including the label, range where applicable,
and default. Numeric writes are clamped to their range; integer values are
rounded. Wrong value types fail, and enum writes must be declared choices.
Subscribers hear changes rather than repeated writes of the same value.
In React, use useParameter(parameters.gain) to read and subscribe with the
component's lifecycle. Keep the parameter as the value source so host automation,
preset restoration, and other views can update the displayed value.
One parameter of each type, in React:
import { Pressable, Text, View, useParameter } from '@soundor/react';
import type { ReactNode } from 'react';
import { parameters } from 'soundor:parameters';
function Button({
label,
selected = false,
onPress,
}: {
label: string;
selected?: boolean;
onPress: () => void;
}) {
return (
<Pressable
onPress={onPress}
accessibilityState={{ selected }}
style={({ pressed }) => ({
minWidth: 32,
alignItems: 'center',
paddingVertical: 4,
paddingHorizontal: 10,
borderRadius: 6,
backgroundColor: selected || pressed ? '#3a7bfd' : '#2a2c33',
})}
>
<Text style={{ color: 'white' }}>{label}</Text>
</Pressable>
);
}
function Row({ label, children }: { label: string; children: ReactNode }) {
return (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
<Text style={{ width: 64, color: '#8b8f98' }}>{label}</Text>
{children}
</View>
);
}
// One parameter of each type. Values are plain: a number in range (an int
// is rounded), a boolean, or one of an enum's values.
export default function Parameters() {
const gain = useParameter(parameters.gain);
const voices = useParameter(parameters.voices);
const bypass = useParameter(parameters.bypass);
const mode = useParameter(parameters.mode);
return (
<View style={{ gap: 10 }}>
<Row label="Gain">
<Button label="−" onPress={() => parameters.gain.set(gain - 0.1)} />
<Text style={{ width: 40, color: '#e6e7ea' }}>{gain.toFixed(2)}</Text>
<Button label="+" onPress={() => parameters.gain.set(gain + 0.1)} />
</Row>
<Row label="Voices">
<Button label="−" onPress={() => parameters.voices.set(voices - 1)} />
<Text style={{ width: 40, color: '#e6e7ea' }}>{voices}</Text>
<Button label="+" onPress={() => parameters.voices.set(voices + 1)} />
</Row>
<Row label="Bypass">
<Button
label={bypass ? 'On' : 'Off'}
selected={bypass}
onPress={() => parameters.bypass.set(!bypass)}
/>
</Row>
<Row label="Mode">
{parameters.mode.info.values.map((value) => (
<Button
key={value}
label={value}
selected={value === mode}
onPress={() => parameters.mode.set(value)}
/>
))}
</Row>
</View>
);
}parameters.tsxSourceGroup a user edit into a gesture
A gesture tells the host when a user starts and finishes changing a parameter. For a discrete action, bracket the write:
const gain = parameters.gain;
gain.beginGesture();
try {
gain.set(gain.info.default);
} finally {
gain.endGesture();
}For a drag, begin on pointer down, set values as the pointer moves, and end when the interaction finishes or is cancelled. Balance gestures across every exit path. Gestures can nest; only the outermost boundaries reach the host. In the JUCE runtime these map to JUCE's parameter change gestures, allowing a DAW to record the interaction as automation.
Connect parameters to audio
In a JUCE plugin the parameter API is backed by the processor's
AudioProcessorValueTreeState. Host automation, preset restoration, and UI
changes use that same state. Parameter listeners only set lock-free flags;
JavaScript subscribers run later on the message thread.
Your processor reads the parameter state and applies it to audio buffers. JavaScript does not process the audio or run on the audio thread. The basic example shows the C++ gain implementation alongside its browser implementation.
In the browser the UI, audio setup, and host controls share one parameter
store. Subscribe from runtimes/web/src/audio.ts to update a Web Audio node,
and return a cleanup function that unsubscribes and disconnects the graph.
See Web Audio setup.
Test parameter behavior
The test runtime implements parameter validation, changes, and nested gestures. Use the testing helpers to simulate host-side values and inspect UI behavior. A component test can verify that a fader displays an automated change or that Reset restores the declared default. Audio processing and real DAW automation require their own integration checks.