pnpm add @audio-ui/core
import { barCount, barLayout, barRect, barRects, bitmapLength, /* … */ } from "@audio-ui/core/waveform";| Adapters | @audio-ui/react/waveform, @audio-ui/elements/waveform |
|---|
| Export | Kind | What it is |
|---|---|---|
BarLayout | interface | Where the bars go on the canvas bitmap. |
BarLayoutOptions | interface | What barLayout lays bars out for: the canvas's size and pixel ratio, the bars' width, gap and shortest height, and the variant. |
BarRect | interface | A bar's rectangle on the bitmap, in whole device pixels. |
barCount | function | How many bars of barWidth separated by barGap fit in width. |
barLayout | function | Lay the bars out on the device pixel grid. |
barRect | function | Where bar index goes for a level of 0…1, in whole device pixels. |
barRects | function | Where each level goes, left to right. |
bitmapLength | function | How many device pixels a CSS length covers on the canvas bitmap. |
envelopeRects | function | Where each bar of an envelope goes, in whole device pixels: from its high to its low around the middle, never thinner than the shortest bar. |
mirroredScrollRects | function | Where each level of a history goes when it scrolls outwards from the middle, left to right: the newest stays in the center, growing out of the one before it, while older ones slide away on both sides. |
scrollShift | function | How far to shift a row of count bars so the last one sits flush with the right edge once it has fully slid in. |
WaveformVariant | type | bars stands the bars on the baseline, mirror centers them on the middle line, and line joins the levels into one continuous, centered shape. |
createLevelHistory | function | Records a level every intervalMs into a window of size entries that scrolls: each new entry pushes the oldest one out. |
LevelHistory | interface | What createLevelHistory returns: feed it levels, step it every frame, and read the window it scrolls and how far the newest entry has slid in. |
isAudible | function | Whether levels carry a signal, rather than silence. |
loudest | function | The loudest magnitude in levels. |
SILENCE_THRESHOLD | const | Loudest level under which a signal counts as silence. |
createLevelsEngine | function | Turns an input of any length into the level of each of count bars: fitted to the bar count, rising at once and falling smoothly, optionally mirrored, with an idle animation while silent and a sweep while loading. |
LevelsEngine | interface | What createLevelsEngine returns: step it every frame with the input, and read the levels and whether they have settled. |
LevelsEngineOptions | interface | What a levels engine fits its input to: the bar count, the gain, the shortest level, whether the bars mirror, and what they do while idle, loading or with reduced motion. |
WaveformIdle | type | What the bars do while there is no signal: none rests them, pulse makes them breathe together and wave runs a ripple along them. |
attachWaveformBars | function | Draws a waveform as count elements, bar(index) each: writes each one's level, 0…1, to its --waveform-level CSS variable, rising at once and falling smoothly, and nothing while they rest or root is off screen. |
WaveformBarsOptions | interface | The options of attachWaveformBars: the bar count, the peaks or a live signal, the stretch shown and the played share, and how the bars move. |
WaveformPlayed | interface | The played share of the audio, 0…1, as it changes. |
attachWaveformCanvas | function | Draws a waveform on canvas, laid out on the device pixel grid: as many bars as fit its width, every edge on a physical pixel, so it stays sharp at any pixel density and zoom level. |
WaveformCanvasOptions | interface | The options of attachWaveformCanvas: the peaks or a live signal, the stretch shown and the played share, the variant, the bars' size, and how the drawing moves. |
fitPeaks | function | Fits the stretch of peaks from from to to (shares of the whole, 0…1) to one high and one low per bar of envelope. |
PeakEnvelope | interface | Where each bar reaches above and below the middle: high 0…1 up, low 0…-1 down. |
peakCount | function | How many entries peaks holds. |
peakMagnitudes | function | A level per entry of the stretch from from to to: how far each slice reaches either way. |
rangeShares | function | The shares of the whole audio that the stretch range covers, for fitPeaks: [start, end] in duration units becomes [from, to] in 0…1. |
WaveformPeaks | type | The audio's shape, one entry per slice of it: either a level per slice (0…1, drawn the same above and below the middle) or the lowest and highest sample of each slice (-1…1), which draws the waveform as it really is, lopsided where the audio is. |
createWaveformRegionController | function | A region's behavior with no view attached: its span on the timeline, held or controlled, every way of moving it by its body or its edges, and the keys and input that move an edge. |
WaveformRegionChangeEventDetails | type | What onValueChange receives with each change to a region: which edge moved, the reason ("drag", "keyboard" or "input-change"), the event, and cancel() to refuse the change. |
WaveformRegionCommitEventDetails | type | What onValueCommitted receives once a gesture on a region ends: which edge moved, the reason and the event behind it. |
WaveformRegionController | interface | What createWaveformRegionController returns: the snapshot to draw from, and the presses, keys and typed values that move its edges. |
WaveformRegionEdge | type | What a change to a region moves: its start, its end, or the whole region with "both". |
WaveformRegionOptions | interface | Keys move an edge by this share of the timeline, a large step with Shift or Page Up/Down. |
WaveformRegionSnapshot | interface | What a view draws a region from. |
WaveformRegionValue | type | A region as [start, end], in duration units. |
ScopeOptions | interface | The box scopePoints draws into, in px, its inset, and the gain applied to the samples. |
ScopePoint | interface | A point of the trace, in px. |
scopePoints | function | Where a line through samples (-1…1 each) goes: left to right across the width, around the middle of the height. |
createWaveformController | function | A seekable waveform's behavior with no view attached: its position, held, controlled or live, every way of moving it, and the stretch selected. |
WaveformSeekChangeEventDetails | type | What onValueChange receives with each seek: the reason ("press", "drag", "keyboard" or "input-change"), the event, and cancel() to refuse it. |
WaveformSeekCommitEventDetails | type | What onValueCommitted receives once a seek ends: the reason and the event behind it. |
WaveformSeekController | interface | What createWaveformController returns: the snapshot to draw from, the hover, and the presses, keys and typed values that seek. |
WaveformSeekOptions | interface | The options of createWaveformController: the position, held, controlled or live, the audio's length and the stretch shown, the steps, and the callbacks. |
WaveformSeekSnapshot | interface | What a view draws a seekable waveform from. |
WaveformSelectionChangeEventDetails | type | A stretch selected by dragging across the waveform with Shift held. |
WaveformSelectionCommitEventDetails | type | The end of a stretch selected with Shift held: the reason and the event behind it. |
WaveformSelectionHandlers | interface | Listens to the stretch a Shift + drag selects: set by the control that is dragged on. |
Usage
A waveform draws either a recording's shape, known upfront, or a live signal. The shape is its peaks: a level per slice from 0 to 1, or the lowest and highest sample of each slice, which draws the waveform as it really is, lopsided where the audio is. computePeaks makes them from samples.
Painting
attachWaveformCanvas(canvas, options) draws on a canvas, as many bars as fit its width with every edge on a device pixel, so it stays sharp at any density and zoom. It paints only when something changed, and not at all while it is off screen, and returns what stops it:
import { attachWaveformCanvas } from "@audio-ui/core/waveform";
const stop = attachWaveformCanvas(canvas, {
peaks,
duration: buffer.duration,
range: [0, buffer.duration],
played: { get: () => audio.currentTime / buffer.duration, subscribe: onTimeUpdate },
variant: "mirror",
});variant is "mirror" (centred on the middle line), "bars" (standing on the baseline), "line" or "scope" for raw samples. range is the stretch shown, for a zoomed view; the played share is drawn in the --waveform-progress-color CSS color and the rest in --waveform-color. With levels, a live source, it follows a signal instead: across the width, or in mode: "scrolling" as a record of the loudest that moves at scrollSpeed. idle makes the bars rest, breathe or ripple while there is no signal, loading sweeps them while the audio is on its way, and reducedMotion repaints a moving waveform four times a second at most.
attachWaveformBars draws the same as one element per bar, writing each bar's level to a CSS variable, for bars styled in CSS. The layout and the fitting are functions of their own: barLayout and barRects place bars on the pixel grid, fitPeaks fits a stretch of peaks to one high and one low per bar, createLevelsEngine turns a live input into a level per bar, createLevelHistory keeps the scrolling record, and scopePoints is the trace through raw samples.
Seeking
createWaveformController makes a waveform seekable, and follows the contract every controller shares. Its position is in the units of duration, held, controlled with value, or live with source, which it follows without a render:
import { createWaveformController } from "@audio-ui/core/waveform";
const seek = createWaveformController({
duration: buffer.duration,
range: [0, buffer.duration],
step: 5,
largeStep: 30,
onValueCommitted: (seconds) => {
audio.currentTime = seconds;
},
});
surface.addEventListener("pointerdown", (event) => seek.pressSeek(event, surface));pressSeek follows a drag, keyDown takes the arrows (step), Page Up, Page Down and Shift (largeStep), typeAt sets the position from an input, and hoverAt with leave keeps the position under the pointer in hover, a store. A drag with Shift held selects a stretch instead, reported to the handlers given to setSelection. toShare and atShare convert between a position and a share of the shown stretch.
createWaveformRegionController drives a region on the waveform, [start, end]: press(edge, event, handle, track, shownRange) drags one edge, or the whole region with "both", keeping it at least minLength long; keyDown(edge, event) moves an edge with the arrows and Page keys and sends it to its ends with Home and End, and typeAt sets it from an input.
The Waveform draws these in React.