Typed Astro component for @arraypress/waveform-sounds.
A searchable, filterable list of sound previews — server-rendered, then brought to life — with a mini waveform per row and one shared audio engine.
npm install @arraypress/waveform-sounds-astro @arraypress/waveform-sounds @arraypress/waveform-playerImport the two stylesheets once, in your layout. The component does not inject CSS (none of the family's wrappers do):
---
import '@arraypress/waveform-player/styles.css';
import '@arraypress/waveform-sounds/styles.css';
---The JavaScript is handled for you: the component ships a bundled client script that loads the sounds runtime, and the player too unless the page already has one (see below).
---
import WaveformSounds from '@arraypress/waveform-sounds-astro';
---
<WaveformSounds
sounds={[
{ url: '/previews/kick-01.mp3', title: 'Kick 01', type: 'Drums', bpm: 128, key: 'F minor', duration: 2.4 },
{ url: '/previews/bass-04.mp3', title: 'Bass Loop 04', type: 'Bass', bpm: 128, key: 'Fmin', peaks: 'a1b2c3…' },
]}
pageSize={100}
columns={['type', 'bpm', 'key']}
/>The whole list — toolbar, filter chips, every row — is in the HTML. The
browser runtime adopts that markup instead of rebuilding it, so the list is
readable and crawlable before any script runs. Give rows peaks (what
waveform-gen --manifest writes) and the row waveforms draw without decoding
any audio. A sound with download: '/files/kick-01.wav' gets a download
button on its row (a plain <a download> — gating is up to your site).
<WaveformSounds manifest="/sounds.json" player="strip" />With only manifest (and no sounds), the component renders an empty
container with data-manifest, and the runtime fetches and renders the list.
sounds={[]} counts as "given" — it renders an empty list, as the core does.
Generate a manifest from a folder of previews (--base-url is the folder's public URL;
without it every URL is relative to the site root, /kick.mp3):
npx @arraypress/waveform-gen ./public/previews/*.mp3 --manifest ./public/sounds.json --base-url /previews/To server-render a manifest instead, read it at build time and pass its
sounds array.
Every prop is optional. An omitted prop emits nothing, so the core's default
applies. Types come straight from the core's WaveformSoundsOptions.
| Prop | Type | Default | Notes |
|---|---|---|---|
sounds |
SoundInput[] |
— | Rendered on the server. |
manifest |
string |
— | URL, fetched in the browser when there are no sounds. |
player |
'inline' | 'strip' |
'inline' |
Mini waveform per row, or one docked player. |
search |
boolean |
true |
Search box. |
filters |
('type' | 'key' | 'bpm')[] |
all three | [] = no filter controls. Drop one from filters, columns and sorts to remove it everywhere. |
sorts |
SoundsSort[] |
all five | Orders the Sort menu offers; the first is the start order. [] = no menu. |
showCount |
boolean |
true |
The "12 of 300 sounds" line. |
menuSearch |
number |
8 |
Type / key / sort dropdowns with more options than this get a search field. |
urlState |
boolean | string |
false |
Keep filters + sort in the address (?q=…&type=…), shareable and refresh-proof. A string prefixes the parameter names, for several lists on a page. |
idPrefix |
string |
the id, else a hash of the sounds |
Prefix for the dropdowns' element ids. Two lists of the same sounds on one page need an id or idPrefix each. |
loopToggle |
boolean |
true |
Loop button. |
pageSize |
number |
50 |
Rows before "Show more"; 0 = all. |
maxTypeChips |
number |
10 |
More types than this become a menu. |
columns |
('type' | 'bpm' | 'key' | 'duration')[] |
all four | Order is kept. |
waveformStyle |
'mirror' | 'bars' |
'mirror' |
Row waveform. |
waveformColor |
string |
CSS --ws-wave-color |
Default derives from currentColor. |
progressColor |
string |
CSS --ws-progress-color |
Default derives from currentColor. |
barWidth |
number |
2 |
Row bar width, CSS px. |
barGap |
number |
1 |
Gap between row bars, CSS px. |
loop |
boolean |
false |
Start with Loop on. |
autoAdvance |
boolean |
false |
Play the next visible sound at the end. |
arrowAudition |
boolean |
true |
↑/↓ play the next row while playing. |
strings |
Partial<WaveformSoundsStrings> |
English | UI words; emitted as JSON. |
playerOptions |
object |
— | Options for the engine WaveformPlayer; emitted as JSON (functions are dropped). |
id |
string |
— | Container id. |
class |
string |
— | Added to waveform-sounds waveform-sounds--<player>. |
Not available as props: the on* callbacks (see Events) and playerClass
(the page's window.WaveformPlayer is used — see below).
No accent is needed: like the rest of the family the list is colour-agnostic.
Everything derives from currentColor, so it fits light and dark pages as
is, and the "on" states (selected chip, playing row) invert. Opt into a
brand colour with --ws-accent / --ws-on-accent if you want one.
Set --ws-surface to your page background. The runtime detects it, but a
server-rendered list paints before any script runs, so the first paint
(dropdown and button fills) uses the CSS value:
.waveform-sounds { --ws-surface: var(--color-bg); }Other properties: --ws-wave-color, --ws-progress-color, --ws-border,
--ws-radius, --ws-control-radius, … (see the core's README).
Callbacks can't cross server rendering. Listen for the core's bubbling DOM
events instead — on the element, or on document:
<WaveformSounds id="pack" manifest="/sounds.json" />
<script>
document.addEventListener('waveformsounds:play', (e) => {
console.log('playing', e.detail.sound.title);
});
</script>| Event | detail |
|---|---|
waveformsounds:ready |
{ sounds, instance } |
waveformsounds:play / :pause / :end |
{ sound, index, instance } |
waveformsounds:filter |
{ visible, total, filter, sort, instance } |
waveformsounds:error |
{ error, sound?, index?, instance } |
For the imperative API, window.WaveformSounds.getInstance('#pack') returns
the instance (play(), setFilter(), setSort(), …). It is created by the
client script, so call it from an event handler or after
waveformsounds:ready.
The client script is a processed Astro <script>: one bundled module,
emitted as an external file (/_astro/…) and identical on every page — no
is:inline, no define:vars. Everything per-instance travels in data-*
attributes. A strict hash-based CSP needs no extra hashes for this component.
Works with and without Astro's router. The script initialises on load, again
on every astro:page-load, and on astro:after-swap destroys the instances
whose element left the page (stopping their audio). A list inside a
transition:persist element is carried over and keeps playing.
The list plays through a WaveformPlayer, and singlePlay (one sound at a
time across the page) only works between players of the same class. So the
client script never replaces a player the page already has: it waits for the
page's own scripts to run (DOMContentLoaded) and uses
window.WaveformPlayer if it's there — a persistent WaveformBar's, or one
loaded by a <script> tag. Only when there is none does it load the bundled
@arraypress/waveform-player (a separate chunk, by dynamic import).
A player your code loads LATER than that (from its own dynamic import) can't
be detected; load it from a static <script>/import instead.
MIT © ArrayPress