Audio Parameters Model

Learn how AudioUI's parameter model works, from value systems to scale functions and MIDI integration.

AudioUI uses a sophisticated parameter model that bridges the gap between user interface controls and audio processing. This model handles value conversion, quantization, scaling, and MIDI integration automatically.

Why Parameters Matter

Audio applications need to handle three different value domains:

  1. Real values: What your audio engine uses (e.g., 1000 Hz, -6 dB)
  2. Normalized values: What the UI uses internally (0.0 to 1.0)
  3. MIDI values: What external controllers send (0 to 127, or higher resolution)

The parameter model automatically converts between these domains, ensuring your UI always reflects the correct quantized state and handles MIDI input correctly.

The Tripartite Value System

AudioUI uses a three-domain value system with MIDI as the pivot point:

MIDI Value (The Source of Truth)

  • Type: Integer
  • Range: 0 to (2^Resolution - 1)
  • Characteristics: Linear, discrete, quantized
  • Role: Central pivot point for all conversions

Normalized Value (UI Reality)

  • Type: Float
  • Range: 0.0 to 1.0
  • Characteristics: Linear with respect to control travel and MIDI values
  • Usage: Used by visual components and host automation

Real Value (Audio Reality)

  • Type: number | boolean | string
  • Range: Defined by parameter type
  • Characteristics: Can be logarithmic, exponential, discrete, or boolean
  • Usage: What your application logic actually uses

Parameter Types

AudioUI supports three parameter types:

Continuous Parameters

Used for variable controls like volume, frequency, pan, etc.

ContinuousParameter.tsx
import { createContinuousParameter } from "@cutoff/audio-ui-react";
import { Knob } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const cutoffParam = createContinuousParameter({
  paramId: "cutoff",
  label: "Cutoff",
  min: 20,
  max: 20000,
  unit: "Hz",
  scale: "log",
  defaultValue: 1000,
});
 
export default function ContinuousParameterExample() {
  const [value, setValue] = useState(1000);
 
  return (
    <Knob
      parameter={cutoffParam}
      value={value}
      onChange={(e) => setValue(e.value)}
    />
  );
}

Boolean Parameters

Used for on/off controls like mute, solo, bypass.

BooleanParameter.tsx
import { createBooleanParameter } from "@cutoff/audio-ui-react";
import { Button } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const powerParam = createBooleanParameter({
  paramId: "power",
  label: "Power",
  latch: true,
  defaultValue: false,
});
 
export default function BooleanParameterExample() {
  const [value, setValue] = useState(false);
 
  return (
    <Button
      parameter={powerParam}
      value={value}
      onChange={(e) => setValue(e.value)}
    />
  );
}

Discrete Parameters

Used for selecting from a set of options like waveforms, filter types. The option definitions (value, label, optional midiValue) define the parameter structure; for how this relates to visual content (OptionView) in components, see Options vs OptionView.

DiscreteParameter.tsx
import { createDiscreteParameter } from "@cutoff/audio-ui-react";
import { CycleButton } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const waveformParam = createDiscreteParameter({
  paramId: "waveform",
  label: "Waveform",
  options: [
    { value: "sine", label: "Sine" },
    { value: "square", label: "Square" },
    { value: "sawtooth", label: "Saw" },
    { value: "triangle", label: "Tri" },
  ],
  defaultValue: "sine",
});
 
export default function DiscreteParameterExample() {
  const [value, setValue] = useState("sine");
 
  return (
    <CycleButton
      parameter={waveformParam}
      value={value}
      onChange={(e) => setValue(e.value)}
    />
  );
}

Creating Parameters

Continuous Parameters

CreateContinuous.tsx
import { createContinuousParameter } from "@cutoff/audio-ui-react";
 
// Basic linear parameter
const volumeParam = createContinuousParameter({
  paramId: "volume",
  label: "Volume",
  min: 0,
  max: 100,
  unit: "%",
  defaultValue: 50,
});
 
// Logarithmic parameter (for frequency)
const frequencyParam = createContinuousParameter({
  paramId: "frequency",
  label: "Frequency",
  min: 20,
  max: 20000,
  unit: "Hz",
  scale: "log",
  defaultValue: 1000,
});
 
// Bipolar parameter (for pan)
const panParam = createContinuousParameter({
  paramId: "pan",
  label: "Pan",
  min: -100,
  max: 100,
  unit: "%",
  bipolar: true,
  defaultValue: 0,
});
 
// With step quantization
const attackParam = createContinuousParameter({
  paramId: "attack",
  label: "Attack",
  min: 0,
  max: 1000,
  unit: "ms",
  step: 1, // 1ms increments
  defaultValue: 0,
});

Boolean Parameters

CreateBoolean.tsx
import { createBooleanParameter } from "@cutoff/audio-ui-react";
 
// Toggle (latch) parameter
const powerParam = createBooleanParameter({
  paramId: "power",
  label: "Power",
  latch: true,
  defaultValue: false,
});
 
// Momentary parameter
const recordParam = createBooleanParameter({
  paramId: "record",
  label: "Record",
  latch: false,
  defaultValue: false,
});

Discrete Parameters

CreateDiscrete.tsx
import { createDiscreteParameter } from "@cutoff/audio-ui-react";
 
// Basic discrete parameter
const filterTypeParam = createDiscreteParameter({
  paramId: "filterType",
  label: "Filter Type",
  options: [
    { value: "lowpass", label: "Low Pass" },
    { value: "bandpass", label: "Band Pass" },
    { value: "highpass", label: "High Pass" },
  ],
  defaultValue: "lowpass",
});
 
// With MIDI values (structure is compatible with MIDI 2.0 Property Exchange valueSelect typeHint)
const waveformParam = createDiscreteParameter({
  paramId: "waveform",
  label: "Waveform",
  options: [
    { value: "sine", label: "Sine", midiValue: 0 },
    { value: "square", label: "Square", midiValue: 42 },
    { value: "sawtooth", label: "Saw", midiValue: 84 },
    { value: "triangle", label: "Tri", midiValue: 127 },
  ],
  defaultValue: "sine",
});

Scale Functions

Scale functions transform how values are interpreted. They affect the control feel, not the step grid.

Linear Scale (Default)

Standard linear mapping. Used for pan, modulation depth, etc.

LinearScale.tsx
const panParam = createContinuousParameter({
  paramId: "pan",
  label: "Pan",
  min: -100,
  max: 100,
  scale: "linear", // or omit (default)
  defaultValue: 0,
});

Logarithmic Scale

Used for volume/dB, frequency (musical intervals).

LogScale.tsx
const volumeParam = createContinuousParameter({
  paramId: "volume",
  label: "Volume",
  min: -60,
  max: 6,
  unit: "dB",
  scale: "log",
  defaultValue: 0,
});
 
const frequencyParam = createContinuousParameter({
  paramId: "frequency",
  label: "Frequency",
  min: 20,
  max: 20000,
  unit: "Hz",
  scale: "log",
  defaultValue: 1000,
});

Exponential Scale

Used for envelope curves, some filter parameters.

ExpScale.tsx
const attackParam = createContinuousParameter({
  paramId: "attack",
  label: "Attack",
  min: 0,
  max: 1000,
  unit: "ms",
  scale: "exp",
  defaultValue: 0,
});

Bipolar Mode

Bipolar parameters are centered around zero. This affects both default values and visual rendering.

Bipolar.tsx
const panParam = createContinuousParameter({
  paramId: "pan",
  label: "Pan",
  min: -100,
  max: 100,
  bipolar: true, // Centers at zero
  defaultValue: 0, // Defaults to center (0.5 normalized)
});
 
// Visual rendering:
// - ValueRing starts at 12 o'clock (center)
// - ValueStrip fills from center
// - Controls show centered indicator

Important: The bipolar flag is independent of the numeric range. A parameter can be bipolar even if min >= 0.

Step Quantization

The step parameter defines a linear grid in the real value domain, regardless of scale type.

When Step Makes Sense

  • Linear scales: Always works well
  • Log scales with linear units: Works when the unit is linear (e.g., dB)
  • Exp scales with linear units: Works when the unit is linear (e.g., milliseconds)
StepQuantization.tsx
// Volume with log scale: step makes sense (linear dB increments)
const volumeParam = createContinuousParameter({
  paramId: "volume",
  label: "Volume",
  min: -60,
  max: 6,
  step: 0.5, // 0.5 dB increments
  unit: "dB",
  scale: "log",
});
 
// Attack time with exp scale: step makes sense (linear ms increments)
const attackParam = createContinuousParameter({
  paramId: "attack",
  label: "Attack",
  min: 0,
  max: 1000,
  step: 1, // 1 ms increments
  unit: "ms",
  scale: "exp",
});

When Step May Not Make Sense

For frequency (Hz) with log scale, linear Hz steps don't align with musical perception. Consider omitting step or using a very small value:

NoStep.tsx
// Frequency with log scale: step may not make sense
const freqParam = createContinuousParameter({
  paramId: "frequency",
  label: "Frequency",
  min: 20,
  max: 20000,
  // No step - allow smooth control across logarithmic range
  unit: "Hz",
  scale: "log",
});

MIDI Resolution

MIDI resolution determines the quantization grid. Higher resolution provides finer control.

MidiResolution.tsx
// Standard 7-bit MIDI (0-127)
const standardParam = createContinuousParameter({
  paramId: "mod",
  label: "Mod Wheel",
  min: 0,
  max: 127,
  midiResolution: 7, // 7-bit (0-127)
  defaultValue: 0,
});
 
// High resolution (internal precision)
const preciseParam = createContinuousParameter({
  paramId: "cutoff",
  label: "Cutoff",
  min: 20,
  max: 20000,
  midiResolution: 32, // High resolution for internal precision
  defaultValue: 1000,
});

Available resolutions: 7, 8, 14, 16, 32, 64.

Integration Examples

Frequency Control

FrequencyControl.tsx
import { createContinuousParameter } from "@cutoff/audio-ui-react";
import { Knob, frequencyFormatter } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const cutoffParam = createContinuousParameter({
  paramId: "cutoff",
  label: "Cutoff",
  min: 20,
  max: 20000,
  unit: "Hz",
  scale: "log",
  defaultValue: 1000,
});
 
export default function FrequencyControlExample() {
  const [value, setValue] = useState(1000);
 
  return (
    <Knob
      parameter={cutoffParam}
      value={value}
      onChange={(e) => setValue(e.value)}
      valueFormatter={frequencyFormatter}
    />
  );
}

Volume Control

VolumeControl.tsx
import { createContinuousParameter } from "@cutoff/audio-ui-react";
import { Slider } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const volumeParam = createContinuousParameter({
  paramId: "volume",
  label: "Volume",
  min: -60,
  max: 6,
  unit: "dB",
  scale: "log",
  step: 0.5,
  defaultValue: 0,
});
 
export default function VolumeControlExample() {
  const [value, setValue] = useState(0);
 
  return (
    <Slider
      parameter={volumeParam}
      value={value}
      onChange={(e) => setValue(e.value)}
      orientation="vertical"
    />
  );
}

Waveform Selection

WaveformSelection.tsx
import { createDiscreteParameter } from "@cutoff/audio-ui-react";
import { CycleButton, OptionView } from "@cutoff/audio-ui-react";
import { useState } from "react";
 
const waveformParam = createDiscreteParameter({
  paramId: "waveform",
  label: "Waveform",
  options: [
    { value: "sine", label: "Sine" },
    { value: "square", label: "Square" },
    { value: "sawtooth", label: "Saw" },
    { value: "triangle", label: "Tri" },
  ],
  defaultValue: "sine",
});
 
export default function WaveformSelectionExample() {
  const [value, setValue] = useState("sine");
 
  return (
    <CycleButton
      parameter={waveformParam}
      value={value}
      onChange={(e) => setValue(e.value)}
    >
      <OptionView value="sine">Sine</OptionView>
      <OptionView value="square">Square</OptionView>
      <OptionView value="sawtooth">Saw</OptionView>
      <OptionView value="triangle">Tri</OptionView>
    </CycleButton>
  );
}

Ad-Hoc vs Parameter Model

Components support two modes:

Ad-Hoc Mode

Simple props for quick prototyping:

AdHocMode.tsx
<Knob
  value={value}
  onChange={(e) => setValue(e.value)}
  min={20}
  max={20000}
  label="Cutoff"
/>

Parameter Model Mode

Full parameter definition for production use:

ParameterMode.tsx
const cutoffParam = createContinuousParameter({
  paramId: "cutoff",
  label: "Cutoff",
  min: 20,
  max: 20000,
  unit: "Hz",
  scale: "log",
  defaultValue: 1000,
});
 
<Knob
  parameter={cutoffParam}
  value={value}
  onChange={(e) => setValue(e.value)}
/>

Benefits of Parameter Model:

  • Consistent value handling
  • Automatic MIDI integration
  • Proper quantization
  • Scale function support
  • Better integration with audio engines

Component Compatibility

Not all components support all parameter types:

ComponentSupported TypesNotes
KnobcontinuousVariable control
SlidercontinuousLinear fader
ButtonbooleanToggle or momentary
CycleButtondiscreteOption selection

Components validate parameter types and will error if given an incompatible parameter.

Best Practices

  1. Use parameter model for production: Provides better integration and consistency
  2. Choose appropriate scales: Log for frequency/volume, linear for pan/modulation
  3. Set step when it makes sense: Use for integer values or meaningful increments
  4. Use bipolar for centered controls: Pan, balance, etc.
  5. Set MIDI resolution appropriately: Higher for internal precision, standard (7-bit) for MIDI CC
  6. Provide units: Helps with formatting and user understanding

Next Steps