Waveform
The loudness of a piece of audio over time. It colors what has played up to the playhead, and a press or a drag anywhere on it seeks there.
import { Waveform } from '@adecore/ui';import { useState } from 'react';
import { SegmentBar, Waveform } from '@adecore/ui';
const DURATION = 225;
const SECTIONS = [
{ name: 'Intro', length: 22 },
{ name: 'Verse', length: 58 },
{ name: 'Chorus', length: 64 },
{ name: 'Bridge', length: 36 },
{ name: 'Outro', length: 45 }
];
const STARTS = SECTIONS.map((_, i) => SECTIONS.slice(0, i).reduce((sum, section) => sum + section.length, 0));
// One sample per 100 ms, as a loudness analysis gives them.
const LEVELS = Array.from({ length: DURATION * 10 }, (_, i) => {
const swell = 0.55 + 0.35 * Math.sin((i / (DURATION * 10)) * Math.PI * 3);
const grain = 0.3 * Math.abs(Math.sin(i * 1.7) * Math.cos(i * 0.9));
return Math.min(1, swell * 0.75 + grain);
});
export default function WaveformDemo() {
const [time, setTime] = useState(83);
const [scrubbing, setScrubbing] = useState(false);
const at = STARTS.findLastIndex((start) => start <= time);
return (
<div className="flex w-full max-w-xl flex-col gap-2">
<SegmentBar
label="Sections"
parts={SECTIONS.map((section, i) => ({ value: section.length, label: section.name, current: i === at }))}
onSelect={(index) => setTime(STARTS[index] ?? 0)}
/>
<Waveform
label="Record"
levels={LEVELS}
duration={DURATION}
value={time}
onValueChange={(next) => {
setScrubbing(true);
setTime(next);
}}
onValueCommitted={() => setScrubbing(false)}
marks={STARTS.slice(1)}
className="h-32"
/>
<span className="text-xs text-text-faint">{scrubbing ? 'Scrubbing' : 'Playing'}</span>
<span className="mt-4 text-xs text-text-muted">Zoomed in on the chorus</span>
<Waveform label="Score" levels={LEVELS} duration={DURATION} range={[70, 150]} value={time} marks={STARTS.slice(1)} className="h-8" />
</div>
);
}levels holds loudness samples from 0 (silence) to 1, spread evenly over duration, at any rate. The waveform fits them to its width.
It fills the height className gives it, so it works as a player and as a timeline row.
<Waveform label="Record" levels={levels} duration={225} value={time} onValueChange={seek} className="h-32" />A part of the audio
range shows only a stretch of the audio, such as the visible part of a zoomable timeline. Bars, marks, playhead and seeking all follow it. A SegmentBar takes the same range.
<Waveform label="Score" levels={levels} duration={225} range={[70, 150]} className="h-8" />Seeking
With onValueChange the waveform is a slider. It takes focus, and a press or drag calls onValueChange with the time under the pointer. onValueCommitted follows when the drag ends.
| Key | Moves the playhead |
|---|---|
| Arrow keys | step seconds |
| Page Up, Page Down | Ten steps |
| Home, End | To either end |
Each key commits at once and stops there, so a transport higher up does not move the playhead twice. A screen reader hears the position as "01:23 of 03:45".
onKeyDown hears every key first. The waveform leaves a key you call preventDefault on alone and lets it bubble, so a player can bind Up and Down to something else:
<Waveform
label="Record"
levels={levels}
duration={225}
value={time}
onValueChange={seek}
onKeyDown={(e) => {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
e.preventDefault();
}
}}
/>Without onValueChange it is an image named by its label. Without value it draws no playhead.
marks draws a line through the waveform at each time, such as a cut.
Props
| Prop | Type | Default | |
|---|---|---|---|
levels | readonly number[] | Required. Loudness samples from 0 to 1, spread evenly over duration. | |
duration | number | Required. In seconds. | |
label | string | Required. The name a screen reader reads. | |
range | readonly [from: number, to: number] | [0, duration] | The stretch it shows, in seconds. |
value | number | How far it has played, in seconds. Left out, it draws no playhead. | |
onValueChange | (value: number) => void | Seeks to a time in seconds. Left out, the waveform only shows. | |
onValueCommitted | (value: number) => void | The time a drag lets go at, or a key moved to. | |
step | number | 1 | How far an arrow key moves the playhead, in seconds. |
onKeyDown | (e: KeyboardEvent<HTMLDivElement>) => void | Hears a key first; after preventDefault the waveform lets it bubble. | |
marks | readonly number[] | [] | Times in seconds drawn as lines. |
className | string | Its height, which it fills. | |
ref | Ref<HTMLDivElement> |
WaveformProps is an exported type.