Waveform
Waveform bars for an audio or video asset, drawn in currentColor.
<Waveform> draws mirrored bars for a range of an asset's audio, filling its CSS box. useWaveformPeaks(asset) computes the peaks once per asset and shares them between every component that asks.
Example
An audio clip on the timeline, drawn as its waveform:
import type { AudioClip } from "@miraiclip/core";
import { Timeline, useProjectState, useTimelineClip, useWaveformPeaks, Waveform } from "@miraiclip/react";
/** An audio clip on the timeline, drawn as its waveform. Use it in a <Timeline.Lane>. */
export function AudioClipBlock({ clip }: { clip: AudioClip }) {
return (
<Timeline.Clip
clip={clip}
className="top-1.5 bottom-1.5 overflow-hidden rounded-md border border-emerald-400/40 bg-emerald-400/15 data-[selected]:ring-2 data-[selected]:ring-emerald-400"
>
<ClipWaveform clip={clip} />
</Timeline.Clip>
);
}
function ClipWaveform({ clip }: { clip: AudioClip }) {
const asset = useProjectState((s) => s.doc.assets[clip.assetId]);
const peaks = useWaveformPeaks(asset); // null while computing, or when the file has no audio
// Live while the clip is trimmed, so the bars follow the handle.
const { trimStartUs, durationUs } = useTimelineClip();
if (!peaks) return null;
return (
<Waveform
peaks={peaks}
fromUs={trimStartUs}
toUs={trimStartUs + durationUs}
gain={clip.volume}
// Bars are drawn in currentColor.
className="pointer-events-none absolute inset-0 h-full w-full text-emerald-300/80"
/>
);
}useTimelineClip() gives the clip's live in-point and length, so the bars follow a trim handle while it is dragged.
Props
| Prop | Default | Description |
|---|---|---|
peaks | required | Peaks for the whole asset, from useWaveformPeaks or the renderer's computeWaveformPeaks |
fromUs, toUs | required | Media range to draw. For a clip: trimStartUs to trimStartUs + durationUs |
gain | 1 | Linear gain before drawing, so volume changes show |
barWidth, barGap | 2, 1 | Bar width and gap, CSS px |
floorDb | −60 | Quietest level drawn. Heights use a decibel scale, so quiet passages stay visible |
maxCanvasPx | 4096 | Longest side of the canvas's backing store, device px |
Other props go to the <canvas>. Size it with CSS (the canvas fills its box and redraws when the box resizes), and color it with color: the bars use currentColor. It renders a <canvas data-waveform>.
Very wide clips at high zoom would need a canvas wider than mobile browsers allow. The backing store is capped at maxCanvasPx, so the bars get softer instead of failing to draw.
<Waveform> doesn't read the project or playback, so it works outside the providers, for example in a media library list.
useWaveformPeaks
const peaks = useWaveformPeaks(asset); // WaveformPeaks | null| Behaviour | Detail |
|---|---|
| Assets | Audio and video assets with a durationUs. Others return null |
| While computing | Returns null; the component re-renders when the peaks are ready |
| Cache | One computation per asset id and src, shared by every caller and kept for the page's lifetime |
| Queue | Assets are decoded one at a time with a streamed decode, so a long project doesn't start dozens of decoders |
| Failures | A source that can't be fetched, has no audio track, can't be decoded, or takes more than 30 s returns null |
Pass undefined to skip, for example in a compact list view: useWaveformPeaks(compact ? undefined : asset).
Without the hook
Compute peaks yourself with the renderer's openMediabunnyAudio and computeWaveformPeaks, for example on a server or in a worker, and pass them in. The result is plain data ({ peaks: Float32Array, bucketUs, durationUs }). See Build an editor › Waveforms.