Architecture
Waveform Playlist is built as a modular monorepo designed for flexibility, tree-shaking, and framework-agnostic reuse.Monorepo Structure
The project uses pnpm workspaces to organize packages by responsibility:Core Package Architecture
Package Dependencies
The dependency graph ensures clean separation of concerns:Package Descriptions
@waveform-playlist/core
Purpose: Pure TypeScript types and interfaces with zero dependencies
Key Exports:
AudioClip,ClipTrack,TimelineinterfacescreateClip(),createClipFromSeconds()factory functions- Fade types:
Fade,FadeType - Peak data types:
Peaks,PeakData,Bits
@waveform-playlist/engine
Purpose: Framework-agnostic stateful timeline engine
Key Exports:
PlaylistEngineclass (event emitter + state management)PlayoutAdapterinterface (pluggable audio backend)- Pure operations functions (clip drag, trim, split, zoom)
- Pure operations (
operations/) - Stateless constraint functions - Stateful engine - Composes operations with events
@waveform-playlist/playout
Purpose: Tone.js audio playback implementation
Key Exports:
TonePlayoutclass (Tone.js wrapper)createToneAdapter()factory (bridges engine to Tone.js)- Global AudioContext management
- Professional scheduling with Transport API
- Built-in effects system
- Sample-accurate timing
@waveform-playlist/browser
Purpose: React integration layer
Key Exports:
WaveformPlaylistProvider(React Context provider)- Custom hooks (
usePlaylistControls,usePlaylistData, etc.) - Primitive components (
PlayButton,ZoomInButton, etc.)
@waveform-playlist/ui-components
Purpose: Reusable styled React components
Key Exports:
Playlist,Track,ClipcomponentsTimeScale,Playhead,Selectionoverlays- Theme system (
WaveformPlaylistTheme,defaultTheme)
Data Flow Architecture
Waveform Playlist uses a unidirectional data flow with React Context and an event-driven engine:Engine State Ownership
ThePlaylistEngine owns these state slices:
- Selection:
selectionStart,selectionEnd - Loop region:
isLoopEnabled,loopStart,loopEnd - Zoom:
samplesPerPixel,canZoomIn,canZoomOut - Master volume:
masterVolume - Selected track:
selectedTrackId - Tracks:
tracks[](for clip mutations: move, trim, split)
React State Ownership
React owns these state slices:- Playback timing:
isPlaying,currentTime(60fps animation loop) - UI-only state:
continuousPlay,annotationsEditable,isAutomaticScroll - Annotations:
annotations[],activeAnnotationId
Split Context Pattern
The provider uses 4 separate contexts to optimize performance:1. PlaybackAnimationContext (60fps)
High-frequency updates, only animation subscribers:2. PlaylistStateContext (User Interactions)
State that changes on user actions:3. PlaylistControlsContext (Stable Functions)
Doesn’t cause re-renders when accessed:4. PlaylistDataContext (Static/Infrequent)
Changes rarely after initialization:Why Split? Prevents unnecessary re-renders. Checkboxes don’t re-render during 60fps animation because they only subscribe to
PlaylistControlsContext and PlaylistStateContext.Sample-Based Architecture
This architectural decision eliminates floating-point precision errors:Benefits
✅ Perfect pixel alignment - No 1-pixel gaps between clips✅ Mathematically exact - All calculations use integers
✅ No precision loss - Converting between time/samples/pixels
✅ Predictable rendering - Sample boundaries align with pixels
User-Facing API
Users can still work with seconds:Provider Pattern
The flexible provider pattern enables custom layouts:Maximum Flexibility: Place controls anywhere in your layout while maintaining full type safety.
Build System
Package Builds
Each package builds independently with tsup:dist/index.js(CommonJS)dist/index.mjs(ES Modules)dist/index.d.ts(TypeScript types)
Tree-Shaking Support
All packages export ES Modules for optimal tree-shaking:Next Steps
Tracks & Clips
Learn about the clip-based editing model
Audio Engine
Understand the Tone.js integration
Theming
Customize the visual appearance
API Reference
Explore the complete API