Skip to main content

@waveform-playlist/ui-components

The UI components package provides low-level React components for waveform visualization, track controls, and playlist UI elements. These components are used by the browser package but can also be used independently for custom implementations.

Installation

npm install @waveform-playlist/ui-components react react-dom styled-components

Peer Dependencies

react
string
required
React 17.0.0 or later
react-dom
string
required
React DOM 17.0.0 or later
styled-components
string
required
Styled Components 5.0.0 or later

Optional Peer Dependencies

@dnd-kit/core
string
DnD Kit 6.0.0+ for drag-and-drop (only needed if using Clip/Track components)
@dnd-kit/modifiers
string
DnD Kit modifiers 9.0.0+
@dnd-kit/utilities
string
DnD Kit utilities 3.0.0+

Main Exports

Components

Waveform Visualization

Channel
component
Canvas-based waveform channel renderer with virtual scrolling
SmartChannel
component
Intelligent channel that switches between canvas rendering and loading states
SpectrogramChannel
component
FFT-based spectrogram visualization with Web Worker processing
TimeScale
component
Horizontal time ruler with configurable format (seconds, hh:mm:ss, samples)
SmartScale
component
Adaptive time scale that adjusts tick density based on zoom level
SpectrogramLabels
component
Vertical frequency labels for spectrogram (Hz, kHz)

Track & Clip Components

Track
component
Complete track UI with controls, waveform, and interaction handlers
Clip
component
Audio clip with waveform, drag handles, and fade overlays
ClipHeader
component
Clip name and color indicator
ClipBoundary
component
Draggable boundary handles for clip trimming
FadeOverlay
component
Visual fade curve overlay (linear, logarithmic, sCurve, exponential)

Playlist Layout

Playlist
component
Container with horizontal virtual scrolling and viewport management
Playhead
component
Animated playback position indicator
Selection
component
Visual selection region overlay
LoopRegion
component
Loop region indicator with drag handles

Controls

TrackControls
namespace
Collection of track control components (Mute, Solo, Volume, Pan, Remove, etc.)
TrackMenu
component
Dropdown menu for track actions
MasterVolumeControl
component
Master volume slider (0.0-1.0)
TimeFormatSelect
component
Time format selector dropdown
AudioPosition
component
Current playback time display
SelectionTimeInputs
component
Editable selection start/end time inputs
TimeInput
component
Generic time input with validation
AutomaticScrollCheckbox
component
Toggle automatic scrolling during playback

Utility

ErrorBoundary
component
React error boundary for graceful error handling

Contexts

ScrollViewportProvider
component
Provides viewport information for horizontal virtual scrolling
useScrollViewport()
hook
Access scroll position and visible range: { scrollLeft, containerWidth, visibleStart, visibleEnd }
ClipViewportOriginProvider
component
Provides clip’s pixel offset for coordinate space conversion
DevicePixelRatioProvider
component
Provides device pixel ratio for high-DPI rendering
PlaylistInfoProvider
component
Provides playlist metadata (sample rate, samples per pixel)
PlayoutProvider
component
Provides playout adapter reference
ThemeProvider
component
Styled-components theme provider with waveform-specific theme
TrackControlsProvider
component
Provides track control callbacks

Theming

WaveformPlaylistTheme
interface
Complete theme interface with all color and styling properties
defaultTheme
object
Default light theme
darkTheme
object
Dark theme variant

Utilities

Time Formatting

formatTime(seconds, format)
function
Format seconds as string (hh:mm:ss, mm:ss.ms, samples, etc.)
parseTimeToSeconds(input, format, sampleRate)
function
Parse time string to seconds
TimeFormat
type
Union type: 'seconds' | 'hh:mm:ss' | 'hh:mm:ss.ms' | 'samples'

Conversions

samplesToSeconds(samples, sampleRate)
function
Convert sample count to seconds
secondsToSamples(seconds, sampleRate)
function
Convert seconds to sample count
samplesToPixels(samples, samplesPerPixel)
function
Convert samples to pixels
pixelsToSamples(pixels, samplesPerPixel)
function
Convert pixels to samples

Virtual Scrolling

useVisibleChunkIndices(totalWidth, chunkWidth, originX?)
hook
Get array of visible canvas chunk indices for viewport-aware rendering

Usage Example

Custom Waveform Visualization

import {
  Channel,
  TimeScale,
  ScrollViewportProvider,
  PlaylistInfoProvider,
  DevicePixelRatioProvider,
  defaultTheme,
} from '@waveform-playlist/ui-components';
import { ThemeProvider } from 'styled-components';

function CustomWaveform({ audioBuffer, samplesPerPixel }) {
  return (
    <ThemeProvider theme={defaultTheme}>
      <DevicePixelRatioProvider>
        <PlaylistInfoProvider
          sampleRate={audioBuffer.sampleRate}
          samplesPerPixel={samplesPerPixel}
        >
          <ScrollViewportProvider>
            <TimeScale
              duration={audioBuffer.duration}
              samplesPerPixel={samplesPerPixel}
              sampleRate={audioBuffer.sampleRate}
            />
            <Channel
              audioBuffer={audioBuffer}
              samplesPerPixel={samplesPerPixel}
              height={128}
            />
          </ScrollViewportProvider>
        </PlaylistInfoProvider>
      </DevicePixelRatioProvider>
    </ThemeProvider>
  );
}

Custom Theme

import {
  defaultTheme,
  type WaveformPlaylistTheme,
} from '@waveform-playlist/ui-components';
import { ThemeProvider } from 'styled-components';

const customTheme: Partial<WaveformPlaylistTheme> = {
  ...defaultTheme,
  waveOutlineColor: '#3b82f6',
  waveFillColor: '#60a5fa',
  waveProgressColor: '#1d4ed8',
  backgroundColor: '#0f172a',
  playheadColor: '#f59e0b',
};

function ThemedPlaylist() {
  return (
    <ThemeProvider theme={customTheme}>
      {/* Your components */}
    </ThemeProvider>
  );
}

Using Spectrogram

import {
  SpectrogramChannel,
  SpectrogramLabels,
} from '@waveform-playlist/ui-components';
import type { SpectrogramConfig } from '@waveform-playlist/core';

const spectrogramConfig: SpectrogramConfig = {
  fftSize: 2048,
  hopSize: 512,
  windowFunction: 'hann',
  frequencyScale: 'linear',
  minFrequency: 0,
  maxFrequency: 22050,
};

function Spectrogram({ audioBuffer }) {
  return (
    <div style={{ display: 'flex' }}>
      <SpectrogramLabels
        config={spectrogramConfig}
        height={256}
      />
      <SpectrogramChannel
        audioBuffer={audioBuffer}
        config={spectrogramConfig}
        colorMap="viridis"
        height={256}
      />
    </div>
  );
}

Key Features

Horizontal Virtual Scrolling

For performance with long audio files, waveforms are rendered in 1000px canvas chunks. Only visible chunks (plus buffer) are mounted:
  • ScrollViewportProvider tracks scroll position
  • useVisibleChunkIndices() calculates which chunks to render
  • Components use absolute positioning for chunks
  • Massive memory savings for spectrogram visualization

High-DPI Canvas Rendering

All canvas components automatically scale for Retina/high-DPI displays:
  • DevicePixelRatioProvider provides window.devicePixelRatio
  • Canvas internal resolution scaled by DPR
  • CSS size remains unchanged
  • Crisp waveforms on all displays

Web Worker Spectrogram

Spectrogram FFT processing runs in a Web Worker to keep UI responsive:
  • SpectrogramChannel transfers canvas control to worker via transferControlToOffscreen()
  • Worker processes audio and draws directly to canvas
  • No main thread blocking
  • Multi-channel fairness ensures all visible chunks render before background batches

Theme System

All styling properties live in theme object, not component props:
  • Colors, backgrounds, borders in WaveformPlaylistTheme
  • Components access via props.theme
  • Single source of truth for visual consistency
  • Easy dark mode support

Architectural Patterns

Stable React Keys

Always use track.id / clip.clipId as React keys, never array indices. Index-based keys cause DOM reuse which breaks transferControlToOffscreen() (can only be called once per canvas).

Canvas Chunk Cleanup

useChunkedCanvasRefs() runs cleanup on every render because the virtualizer can unmount canvases between any render. This prevents stale canvas references in workers.

Virtual Scrolling Chunk Offsets

Canvas registries may contain non-consecutive chunks (e.g., chunks 50-55). Always use extractChunkNumber(canvasId) to get the real chunk index - never compute offsets from array index 0.

Multi-Channel Rendering Fairness

Render visible chunks for ALL channels before starting background batches. Sequential per-channel rendering causes channel starvation when generation aborts interrupt background work.
  • Browser - High-level React hooks and integrated components
  • Core - Type definitions used by these components
  • Playout - Audio playback integration

Build docs developers (and LLMs) love