Miraiclip SDK
React components

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.

Loading demo…
Change the gain and the length of media drawn. This demo draws generated peaks, so it loads no audio file.

Example

An audio clip on the timeline, drawn as its waveform:

AudioClipBlock.tsx
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

PropDefaultDescription
peaksrequiredPeaks for the whole asset, from useWaveformPeaks or the renderer's computeWaveformPeaks
fromUs, toUsrequiredMedia range to draw. For a clip: trimStartUs to trimStartUs + durationUs
gain1Linear gain before drawing, so volume changes show
barWidth, barGap2, 1Bar width and gap, CSS px
floorDb−60Quietest level drawn. Heights use a decibel scale, so quiet passages stay visible
maxCanvasPx4096Longest 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
BehaviourDetail
AssetsAudio and video assets with a durationUs. Others return null
While computingReturns null; the component re-renders when the peaks are ready
CacheOne computation per asset id and src, shared by every caller and kept for the page's lifetime
QueueAssets are decoded one at a time with a streamed decode, so a long project doesn't start dozens of decoders
FailuresA 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.

On this page