Sounds
The Astro, React, Svelte and Vue wrappers (@arraypress/waveform-sounds-astro, -react, -svelte, -vue) version separately — see Framework wrappers.
[Unreleased]
Section titled “[Unreleased]”[0.3.1] — 2026-10-08
Section titled “[0.3.1] — 2026-10-08”- The strip’s docked player no longer starts empty. It showed a play
button, no waveform and
0:00 / 0:00, and its button did nothing. The first sound the list shows is now cued in it — title, waveform, length — without playing (metadata only, the browser default for any<audio>), and its button plays it. Its row isn’t marked until it plays,currentstaysnull,next()starts with it, and a filter change re-cues the first sound still shown until something plays. - The docked player stays in place. It was
position: sticky, so it slid along the bottom of the screen over the rows as the page scrolled. It’s static now;--ws-strip-position: stickybrings the old behaviour back for a long list. - Rows and the player agree on lengths. Rows rounded to the nearest
second and the player’s clock rounds down, so a 7.6s sound read
0:08in its row and0:07in the player. Rows round down too now.
[0.3.0] — 2026-10-07
Section titled “[0.3.0] — 2026-10-07”Changed
Section titled “Changed”- BPM is a menu with a two-handle range instead of two number fields.
The button reads
Any BPM, or120–128 BPMonce set; its panel holds a range across the pack’s own tempos (two native range inputs on one track: arrow keys, screen readers and touch work as they do on any slider), the live readout and anAny BPMreset. The label follows the drag; the list re-filters once it settles. A handle left at an end is no limit on that side, so a range nobody narrowed never hides the sounds without a BPM.?bpm=in the address andsetFilter({bpmMin, bpmMax})work as before. New strings:anyBpm,bpmRange. - On a list that marks loops, Loop starts on. Only loops repeat (as in
0.2.0), so loops repeat out of the box and one-shots still play once. The
loopoption now defaults tonull(follow the data);true/falsestill force it. A list that marks nothing starts with it off, as before.
Removed
Section titled “Removed”.ws-bpm-rangeand its number inputs (data-ws-bpm-min/-maxare now the range handles).
[0.2.0] — 2026-10-07
Section titled “[0.2.0] — 2026-10-07”- Loops and one-shots. A sound can be marked
loop: true; anything else is a one-shot. Marking loops does three things:- a small loop icon beside the name (with “Loop” for screen readers);
- an All / Loops / One-shots filter, offered only when the list has
both kinds (
filtersentry'loop', on by default; in the address as?loop=loop/?loop=one-shotwithurlState); - the Loop toggle repeats only loops. A one-shot always plays once. A list that marks no loops behaves exactly as before.
isLoop()andLOOP_FILTERSin the data module;facets()reportsloopsandoneShots; four new strings (loopFilter,loops,oneShots,isLoop).
Changed
Section titled “Changed”- The title cell wraps its text in
.ws-title-text(so a loop’s icon can sit after an ellipsised name). Styling.ws-titlestill works.
[0.1.3] — 2026-10-07
Section titled “[0.1.3] — 2026-10-07”- Sounds under a second show their length.
formatDurationrounded to whole seconds, so a 0.38s one-shot read0:00— which looks like a missing length, and one-shots (kicks, hats, single notes) are routinely that short. Under a second it now shows tenths:0.4s. - The
stripplayer matches a dark page. Its background fell back to the systemCanvascolour, which is white on a dark page that doesn’t declarecolor-scheme: dark— a white box under a dark list. It now falls back to--ws-surface(the measured page surface), as the dropdowns already did. - No stray
·on a narrow screen. The mobile meta line put a separator before a cell whenever any cell came first, even an empty one, so a sound with no key read· 0:08. It now follows only a cell with content.
[0.1.2] — 2026-10-07
Section titled “[0.1.2] — 2026-10-07”- The
striplayout keeps its docked styling. The engine was mounted on the.ws-engineslot itself, andWaveformPlayerresets its container’sclassName, sows-engine--stripwas wiped on the first play: no sticky position, border, padding,--ws-strip-bgor--ws-radius. The engine now mounts into a child of the slot ([data-ws-engine-mount], made at runtime, so server-rendered markup from 0.1.0/0.1.1 works unchanged) anddestroy()removes it. render.d.tsdeclaresescapeHtml, which/renderhas always exported.
[0.1.1] — 2026-10-06
Section titled “[0.1.1] — 2026-10-06”- The playing row’s progress is smooth. It was painted a whole bar at a time (a bar switched colour once the playhead passed its middle), so a short clip advanced in visible steps — an 8-second loop across ~120 bars moved ~15 times a second however often it was redrawn. The bars are now drawn twice, the second pass clipped at the exact playhead pixel: the played part grows through the middle of a bar. Measured in Chrome: the edge moves every ~1px (≈37 times a second on a 12-second clip at 60fps).
- Bar geometry is a pure, tested function (
barRects), and each pass is a singlefill()of aPath2Dinstead of onefillRectper bar.
[0.1.0] — 2026-10-06
Section titled “[0.1.0] — 2026-10-06”- First release: a searchable, filterable list of sounds for showcasing the previews inside a sample pack, preset bank or sound library.
- One
WaveformPlayer(self mode) is the audio engine for the whole list, so playback gets the player’s seeking (including hosts that ignore byte ranges), error handling, Media Session card andsinglePlayhand-off. - Two layouts:
inline(a mini waveform per row; the playing row fills and is click-to-seek) andstrip(plain rows, the engine docked below). - Search (every word, any order; a number matches the BPM), type chips (a
menu past
maxTypeChips), key menu, BPM range, sort, Loop toggle, paging. - Keyboard: ↑/↓ move and, while a sound plays, audition the next; ←/→ seek;
/focuses the search; ↓ from the search enters the list. - Peaks as an 8-bit hex string (two characters per bar).
@arraypress/waveform-sounds/render: a DOM-free renderer for server rendering; the runtime adopts its markup instead of rebuilding it.@arraypress/waveform-sounds/no-autoinit.- Dropdowns for type (past
maxTypeChips), key and sort: a button and a popup listbox, with a search field pastmenuSearch(8) options; ↑/↓, Enter, Esc, type to narrow (WAI-ARIA combobox/listbox). - Every control is optional:
search,filters,sorts(which orders, in order; the first is the starting order),loopToggle,showCount,columns. - Optional per-sound
downloadlink (a download button on that row only). urlState: the filters and sort live in the address (shareable, survive a refresh), optionally prefixed for several lists on a page.- The engine is built when the list is ready (no audio loaded), so
waveform-tracker and other
waveformplayer:readyhooks see the first play. - Colour-agnostic by default: the “on” states are inverted (text colour as
fill, page surface as ink). The surface is detected (
--ws-surface) and re-read on theme flips; set it yourself for the first server paint.