FilmStripContinuousControl
A continuous control that displays frames from a filmstrip sprite sheet, supporting the industry-standard bitmap-based control representation.
FilmStripContinuousControl is a continuous control that displays frames from a filmstrip sprite sheet. It supports the widely-used industry standard for control representation: bitmap sprite sheets (filmstrips).
While bitmap-based visualization is more constrained than SVG (no dynamic theming, fixed visual appearance), this component shares the same layout, parameter model, interaction, and accessibility as other controls (vector, raster, and control primitives); see the Raster introduction.
The frame displayed is determined by the normalized value (0-1 maps to frame 0 to frameCount-1).
Note:
No Theming Support: This component does NOT support themable props (color, roundness, thickness) as visuals are entirely determined by the image content.
Props
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
value | number | — | Required | Current value of the control |
onChange | (event: AudioControlEvent<number>) => void | — | Optional | Handler for value changes |
min | number | — | Optional | Minimum value (ad-hoc mode) |
max | number | — | Optional | Maximum value (ad-hoc mode) |
step | number | — | Optional | Step size for value adjustments |
bipolar | boolean | false | Optional | Whether the component should operate in bipolar mode (centered at zero) |
label | string | — | Optional | Label displayed below the component |
frameWidth | number | — | Required | Width of a single frame in the filmstrip (pixels) |
frameHeight | number | — | Required | Height of a single frame in the filmstrip (pixels) |
frameCount | number | — | Required | Total number of frames in the filmstrip sprite sheet |
imageHref | string | — | Required | URL to the sprite sheet/filmstrip image |
orientation | "vertical" | "horizontal" | vertical | Optional | Orientation of the filmstrip (frames stacked vertically or horizontally) |
frameRotation | number | 0 | Optional | Optional frame rotation in degrees |
invertValue | boolean | false | Optional | If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0). Useful for filmstrips where frame 0 represents the maximum value |
valueAsLabel | "labelOnly" | "valueOnly" | "interactive" | labelOnly | Optional | Controls how the label and value are displayed |
adaptiveSize | boolean | false | Optional | Whether the component stretches to fill its container |
size | "xsmall" | "small" | "normal" | "large" | "xlarge" | normal | Optional | Size of the component |
parameter | ContinuousParameter | — | Optional | Audio Parameter definition (Model). If provided, overrides min/max/step/label/bipolar from ad-hoc props |
valueFormatter | (value: number, parameterDef: AudioParameter) => string | undefined | — | Optional | Custom renderer for the value display |
interactionMode | "drag" | "wheel" | "both" | both | Optional | Interaction mode |
interactionDirection | "vertical" | "horizontal" | "circular" | "both" | — | Optional | Direction of interaction |
interactionSensitivity | number | — | Optional | Sensitivity multiplier for interactions (default varies by control type) |
Basic Usage
FilmStripContinuousControl requires a filmstrip sprite sheet image and frame dimensions:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function BasicFilmStripExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
value={value}
min={0}
max={100}
label="VU Meter"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setValue(e.value)}
/>
);
}Filmstrip Configuration
Filmstrips are bitmap sprite sheets containing multiple frames stacked vertically (default) or horizontally. Each frame represents a different value state.
Frame Dimensions
Specify the dimensions of a single frame:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FrameDimensionsExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
value={value}
min={0}
max={100}
label="VU Meter"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setValue(e.value)}
/>
);
}Orientation
Control whether frames are stacked vertically or horizontally:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function OrientationExample() {
const [verticalVal, setVerticalVal] = useState(50);
const [horizontalVal, setHorizontalVal] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<FilmStripContinuousControl
value={verticalVal}
min={0}
max={100}
label="Vertical"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
orientation="vertical"
onChange={(e) => setVerticalVal(e.value)}
/>
<FilmStripContinuousControl
value={horizontalVal}
min={0}
max={100}
label="Horizontal"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
orientation="horizontal"
onChange={(e) => setHorizontalVal(e.value)}
/>
</div>
);
}Frame Rotation
Rotate the entire filmstrip container:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FrameRotationExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
value={value}
min={0}
max={100}
label="Rotated"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
frameRotation={45}
onChange={(e) => setValue(e.value)}
/>
);
}Value Inversion
Invert the value mapping for filmstrips where frame 0 represents the maximum value:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function ValueInversionExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
value={value}
min={0}
max={100}
label="Inverted"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
invertValue={true}
onChange={(e) => setValue(e.value)}
/>
);
}Value Mapping
The component maps continuous values (0-1 normalized) to frame indices (0 to frameCount-1):
- Value 0.0 (min) → Frame 0
- Value 1.0 (max) → Frame (frameCount - 1)
- Intermediate values are linearly interpolated
Parameter Model
Use a parameter model for integration with audio parameter systems. The following example uses ad-hoc props; with the full library you would use createContinuousParameter and the parameter prop:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FilmStripParameterModelExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
min={0}
max={100}
label="Volume"
value={value}
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setValue(e.value)}
/>
);
}Adaptive Sizing
The FilmStripContinuousControl component supports both fixed sizes and adaptive sizing. By default, the size of the component is driven by the Size System and their size attribute:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FilmStripSizingExample() {
const [v1, setV1] = useState(50);
const [v2, setV2] = useState(50);
const [v3, setV3] = useState(50);
const [v4, setV4] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<FilmStripContinuousControl
value={v1}
min={0}
max={100}
label="Small"
size="small"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setV1(e.value)}
/>
<FilmStripContinuousControl
value={v2}
min={0}
max={100}
label="Normal"
size="normal"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setV2(e.value)}
/>
<FilmStripContinuousControl
value={v3}
min={0}
max={100}
label="Large"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setV3(e.value)}
/>
<FilmStripContinuousControl
value={v4}
min={0}
max={100}
label="XLarge"
size="xlarge"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setV4(e.value)}
/>
</div>
);
}Adaptive sizing allows the component to adapt to the size of its parent container, adjusting its dimensions intelligently without causing distortion (scaleToFit display mode). The fill display mode makes the component fill the entire container, potentially distorting it.
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FilmStripContinuousAdaptiveSize() {
const [wideVal, setWideVal] = useState(50);
const [tallVal, setTallVal] = useState(50);
const [distortedVal, setDistortedVal] = useState(50);
return (
<div className="flex gap-6 items-center flex-wrap">
<div className="w-48 h-12 border rounded-lg p-2">
<FilmStripContinuousControl
value={wideVal}
min={0}
max={100}
onChange={(e) => setWideVal(e.value)}
adaptiveSize={true}
label="Wide"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
/>
</div>
<div className="w-20 h-32 border rounded-lg p-2">
<FilmStripContinuousControl
value={tallVal}
min={0}
max={100}
onChange={(e) => setTallVal(e.value)}
adaptiveSize={true}
label="Tall"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
/>
</div>
<div className="w-32 h-20 border rounded-lg p-2">
<FilmStripContinuousControl
value={distortedVal}
min={0}
max={100}
onChange={(e) => setDistortedVal(e.value)}
adaptiveSize={true}
displayMode="fill"
label="Distorted"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
/>
</div>
</div>
);
}Interaction Modes
Control how users interact with the filmstrip:
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function FilmStripInteractionExample() {
const [dragVal, setDragVal] = useState(50);
const [wheelVal, setWheelVal] = useState(50);
return (
<div className="flex gap-4 items-center flex-wrap">
<FilmStripContinuousControl
value={dragVal}
min={0}
max={100}
label="Drag Only"
interactionMode="drag"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setDragVal(e.value)}
/>
<FilmStripContinuousControl
value={wheelVal}
min={0}
max={100}
label="Wheel Only"
interactionMode="wheel"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
onChange={(e) => setWheelVal(e.value)}
/>
</div>
);
}Common Use Cases
VU Meter
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function VUMeterExample() {
const [value, setValue] = useState(50);
return (
<FilmStripContinuousControl
value={value}
min={0}
max={100}
label="VU Meter"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
valueAsLabel="interactive"
onChange={(e) => setValue(e.value)}
/>
);
}Rotary Knob
import { useState } from "react";
import { FilmStripContinuousControl } from "@cutoff/audio-ui-react";
export default function RotaryKnobExample() {
const [value, setValue] = useState(5);
return (
<FilmStripContinuousControl
value={value}
min={1}
max={10}
label="Tone"
size="large"
frameWidth={120}
frameHeight={80}
frameCount={48}
imageHref="/images/demo/vu3.png"
interactionDirection="circular"
valueAsLabel="interactive"
onChange={(e) => setValue(e.value)}
/>
);
}Filmstrip Image Format
Filmstrip images should follow these conventions:
- Vertical orientation (default): Frames stacked top to bottom
- Horizontal orientation: Frames stacked left to right
- Frame dimensions: All frames must have identical dimensions
- Frame count: Total number of frames in the sprite sheet
- Compatibility: Works with filmstrips exported from WebKnobMan and other VST development tools
Next Steps
- Explore FilmStripDiscreteControl for discrete parameter control
- Learn about FilmStripBooleanControl for boolean switches
- Check out the playground for interactive examples