WaveformSounds
Audition a whole sample pack from one list.
A searchable, filterable list of sound previews — a mini waveform per row, type, key and BPM filters, and arrow-key auditioning. Every row plays through one shared player, so a pack of hundreds costs the same as one.
$ npm i @arraypress/waveform-sounds Play a sound below, then press ↓.
Live demo
33 previews, one player.
A real pack of 7 types — plucks, leads, pads, keys, bass and loops. Search it, filter by type, key, tempo or loops and one-shots, sort it, and audition it from the keyboard. The filters write themselves into the address bar, so a filtered view is a link you can share.
- Paris Bass Loop 01LoopBass0:06
- Paris Bass Loop 02LoopBass0:06
- Paris Bass Note A1Bass0.4s
- Paris Bass Note A2Bass0:02
- Drum LoopLoopDrum loops1280:03
- Paris Drums 01LoopDrum loops0:06
- Paris Drums 02LoopDrum loops0:06
- Electric PianoKeys1260:07
- House PianoKeys126C#m0:07
- Red WoodsLeadsG#m0:13
- Surprise KissLeads0:07
- Too YoungLeads1210:07
- Paris Loop 01LoopMelodic loopsAm0:06
- Paris Loop 02LoopMelodic loopsAm0:03
- Paris Loop 03LoopMelodic loopsAm0:03
- ColorizePads0:13
- ReachPads0:07
- SeacrestPads0:07
- Too YoungPads0:07
- Bus 605Plucks0:07
- DesirePlucks0:11
- Got Me ThinkingPlucksG#m0:11
- IcarusPlucks0:12
- LovelyPlucks0:11
- LuminousPlucksDm0:12
- NocturnePlucksF#0:12
- Pieces Of YouPlucks0:12
- PlankPlucks121Eb0:07
- SinePlucksCm0:11
- Small MomentsPlucks0:11
- Spaces In BetweenPlucks121Bb0:07
- Too YoungPlucks121Gm0:07
- WingsPlucksG0:11
No sounds match.
Every preview is a clip from this site's own demos. BPM and key are written into the file names only where two separate analyses of the audio agreed, and left out where they didn't — so some rows have no tempo or key, as in any real pack.
Built for auditioning
A sound browser, not a stack of players.
The way producers actually shop for sounds: narrow the list, then run down it with the arrow keys, hearing each one the moment it's selected.
One engine, any number of rows
Rows are pictures — low-res peaks, drawn only when on screen. A single WaveformPlayer plays whichever sound you pick, so a 300-sound pack is still one player.
Forgiving search
Every word, in any order, typo-tolerant. A number matches the BPM. Press / in the list to jump to the search box.
Type, key and BPM filters
Type chips (a searchable menu once there are more than ten), a key menu and a BPM range — each appears only when the data has something to filter.
Keyboard auditioning
↑ / ↓ move through the list — and while a sound plays, play the next one. ← / → seek, Space plays and pauses.
Loops & one-shots
Mark a sound loop: true and it gets a loop icon, the list gets a Loops / One-shots filter, and the Loop toggle repeats only loops — a kick never loops.
Sort & paging
Sort by title, BPM, key or length — pick which orders the menu offers. Long lists page with “Show more” (50 rows by default).
Filters in the URL
urlState keeps the search, type, key, BPM range and sort in the address, so a filtered list can be shared and survives a refresh.
Server-rendered
renderSounds() writes the toolbar and every row as HTML; the runtime adopts that markup instead of rebuilding it.
Optional downloads
Give a sound a download URL — a free sample, the .mid of a MIDI preview — and its row gets a download button. Rows without one carry nothing.
Colour-agnostic
Everything derives from the text colour and the "on" states invert, so it fits light and dark pages as is. --ws-accent opts into a brand colour.
Mobile layout
On a narrow screen each row becomes a title, a meta line and a full-width waveform — still one tap to play, one tap to seek.
Play stats for free
The list plays through a regular WaveformPlayer, so WaveformTracker counts which sounds get played with no extra code.
The manifest
From a folder of previews to a list in one command.
WaveformGen walks the folder and writes one
sounds.json for the whole pack: the type is the sub-folder, the title, BPM and key come from the file name — and are left out rather than guessed — and a "loop" in the folder or file name marks a loop. Each sound carries 64 bars of peaks, so every row draws its waveform without decoding any audio. This is the command that built the demo above.
$ npx @arraypress/waveform-gen ./public/previews/ --recursive \
--manifest ./public/previews/sounds.json --base-url /previews/
📋 ./public/previews/sounds.json: 33 sounds, 7 with BPM, 13 with key public/previews/
├── Drum loops/
│ └── Drum_Loop_128.mp3 → Drum Loop · 128 BPM · loop
├── Keys/
│ └── House_Piano_126_C#min.mp3 → House Piano · 126 BPM · C#m
├── Plucks/
│ ├── Plank_121_Ebmaj.mp3 → Plank · 121 BPM · Eb
│ └── Wings_Gmaj.mp3 → Wings · G
└── Bass/
└── Paris_Bass_Loop_01.mp3 → Paris Bass Loop 01 {
"version": 1,
"sounds": [
{
"url": "/previews/Keys/House_Piano_126_C%23min.mp3",
"title": "House Piano",
"type": "Keys",
"bpm": 126,
"key": "C#m",
"duration": 7.63,
"peaks": "9285748e926ba6a4a1cbd8b8…"
},
{
"url": "/previews/Drum%20loops/Drum_Loop_128.mp3",
"title": "Drum Loop",
"type": "Drum loops",
"bpm": 128,
"loop": true,
"duration": 3.75,
"peaks": "a585883a912a1508ff8c883f…"
},
…
]
} --manifest needs @arraypress/waveform-gen 2.1.0 or later; marking loops, 2.2.0.
Two layouts
Plain rows, one player docked below.
player="strip" drops the per-row waveforms and docks a full player under the list. And every control is optional — here, just the 14 plucks, a key filter and no tempo anywhere. Both lists share one player class, so starting one pauses the other.
- Bus 6050:07
- Desire0:11
- Got Me ThinkingG#m0:11
- Icarus0:12
- Lovely0:11
- LuminousDm0:12
- NocturneF#0:12
- Pieces Of You0:12
- PlankEb0:07
- SineCm0:11
- Small Moments0:11
- Spaces In BetweenBb0:07
- Too YoungGm0:07
- WingsG0:11
No sounds match.
<WaveformSounds
sounds={plucks}
player="strip"
filters={['key']}
sorts={['default', 'title', 'key']}
columns={['key', 'duration']}
loopToggle={false}
/> Getting started
One element and a manifest.
Point data-waveform-sounds at a manifest and load the player and the list — it initialises on page load. Or use the typed wrappers for Astro, React, Vue and Svelte, which server-render the list when you pass sounds.
<!-- The player is the audio engine — load it first -->
<link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.css">
<link rel="stylesheet" href="https://unpkg.com/@arraypress/waveform-sounds/dist/waveform-sounds.css">
<script src="https://unpkg.com/@arraypress/waveform-player/dist/waveform-player.min.js"></script>
<script src="https://unpkg.com/@arraypress/waveform-sounds/dist/waveform-sounds.min.js"></script>
<!-- One element: the runtime fetches the manifest and builds the list -->
<div data-waveform-sounds
data-manifest="/previews/sounds.json"
data-url-state="true"></div>