Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cocreating/4StemPlayer/llms.txt

Use this file to discover all available pages before exploring further.

4Stem Band Player is a browser-only static web application built with SvelteKit and Svelte 5. There is no server-side API, no database, no authentication layer, and no persistent client storage — every asset the player needs is a static file served from the public/ build output. Audio decoding and playback happen entirely inside Web Audio API nodes that are created after the page mounts in the browser.

Full Dependency Table

Every runtime and development dependency with its pinned version:
AreaPackageVersion
App framework@sveltejs/kit2.59.1
UI runtimesvelte5.55.7
Build toolvite8.0.11
Languagetypescript6.0.3
Static adapter@sveltejs/adapter-static3.0.10
Waveform renderingwavesurfer.js7.12.6
Pitch/tempo processing@soundtouchjs/audio-worklet2.0.3
Test runnervitest4.1.5
Test DOMjsdom29.1.1
Script runnertsx4.21.0
Peak generationffmpegexternal CLI
ffmpeg is only required when running npm run songs:peaks or npm run songs:prepare on a development machine. It is never called at runtime in the browser.

Runtime Shape

The SvelteKit route is fully prerendered and statically exported:
  • src/routes/+layout.ts exports prerender = true and ssr = true.
  • svelte.config.js uses @sveltejs/adapter-static and writes the finished site to the public/ directory.
  • SSR renders only the application shell — no audio work, no AudioContext construction, and no window access happens server-side.
  • After the page mounts in the browser, Svelte lifecycle code in AppShell.svelte creates the AudioEngine and begins fetching the song manifest.
Svelte 5 runes used throughout the codebase:
RunePurpose
$propsComponent input declarations
$stateOwned, reactive UI state
$derivedComputed display values from reactive state
$effectSide effects that run when reactive dependencies change

Boot Sequence

The following sequence runs every time the app loads in a browser tab:
1

AppShell.svelte mounts

The SvelteKit page hydrates and onMount fires. Theme and low-memory preferences are read from localStorage, and the Space bar keydown listener is registered on window.
2

loadSongManifest() fetches /songs/manifest.json

songs.ts sends a fetch request for the static manifest file. A loading indicator is displayed while the request is in flight.
3

First song is auto-selected

When the manifest resolves and contains at least one entry, the first song (or the last-played song stored in localStorage) is selected automatically.
4

loadSongBundle(entry) fetches song.json and lyrics.md

Two parallel requests retrieve the selected song’s full metadata object and its Markdown lyrics text. The resolved SongBundle is stored in AppShell’s $state.
5

AudioEngine.loadSong() fetches and decodes all available stems

The engine fetches every stem MP3 concurrently, decodes each with AudioContext.decodeAudioData(), and builds the gain graph. Per-stem loading state is reported through snapshot updates.
6

Components render from AudioEngineSnapshot updates

AppShell subscribes to the engine and stores each snapshot in $state. TransportBar, StemMixer, WaveformView, and related components re-render reactively whenever the snapshot changes.

State Ownership

Clear state ownership prevents duplicate sources of truth and keeps components presentational:
StateOwnerNotes
Song list and selected songAppShell.svelteLoaded from /songs/manifest.json and stored in $state
Loaded metadata and lyricsAppShell.svelteStored as a $state SongBundle
Audio buffers, gain nodes, source nodesAudioEngineHidden inside the engine; never exposed directly
Transport position and playing stateAudioEngineExposed to the UI through immutable snapshots
Per-stem mute, solo, volume, load/error stateAudioEngineExposed as snapshot.stems
Waveform visual stateWaveformView.svelte + WaveSurferRecreated via $effect when stem URL, peaks URL, or duration changes
Keyboard shortcut decisionssrc/lib/keyboard.tsFilters Space bar events; ignores editable targets

UI Component Tree

AppShell.svelte is the sole orchestrator. It owns the engine lifecycle, manifest loading, song selection, and error state. Every child component is presentational and communicates upward through callback props.
AppShell.svelte  (orchestrator — owns engine, manifest, song selection)
├── SongSelector          (dropdown — calls onSelect)
├── ThemeToggle           (dark/light mode toggle)
├── LowMemoryToggle       (decode-profile toggle)
├── TransportBar          (play/pause/stop/seek/transpose — calls engine methods)
│   ├── SectionsPopover   (jump-to-section buttons — calls onSeek)
│   ├── MixerPopover      (per-stem mute/solo/volume — calls engine methods)
│   └── LyricsViewer      (preserved-whitespace lyrics display)
├── StemMixer             (renders one StemRow per stem)
│   └── StemRow (×N)      (mute/solo/volume + WaveformView per stem)
│       └── WaveformView  (WaveSurfer waveform; seek on click)
└── SongInfoPanel         (metadata, chords, section markers)
Because all audio mutations go through AppShell to AudioEngine, components like StemRow and TransportBar never hold audio state themselves. They receive the latest AudioEngineSnapshot as a prop and fire callbacks upward.

What This Stack Does Not Include

No CSS framework

Styling is plain global CSS in src/app.css. There is no Tailwind, no CSS-in-JS, and no component library.

No backend or API

The player reads only static files. There is no server route, no API endpoint, and no database query at any point.

No auth or accounts

There are no user accounts, no login flow, and no session tokens of any kind.

No persistent mix state

Mute, solo, and volume settings reset when a new song is selected. Only the last-played song ID and theme preference are stored in localStorage.

Testing

Tests run with Vitest in a jsdom environment. The configuration in vite.config.ts:
test: {
  environment: 'jsdom',
  include: ['src/**/*.test.ts', 'scripts/**/*.test.ts']
}
Current test coverage areas:
  • Audio engine stem loading and synchronized source starts
  • Seeking across all loaded stems
  • Gain behavior for volume, mute, and solo
  • Audio resource cleanup when switching songs
  • Song manifest creation and URL-safe path encoding
  • Song folder validation errors
  • Refresh workflow orchestration and CLI flag parsing
  • Peak skipping when peak files are newer than stems
  • Structured chord formatting and duration metadata formatting
  • Keyboard shortcut guard behavior

Deployment

The finished site is a folder of static files written to public/ by npm run build. Static hosting must serve:
  • index.html (the SvelteKit shell)
  • SvelteKit client assets under _app/
  • MP3 stems under songs/
  • JSON metadata and peak files under songs/
  • Markdown lyrics files under songs/
No server process, no Node runtime, and no API server is required after the build completes.
Run npm run songs:release before deploying to verify that song validation, type checks, unit tests, and the static build all pass together.

Build docs developers (and LLMs) love