A browser port of Paulstretch by Nasca Octavian Paul. This version runs entirely in the browser, streams the stretched output in real time through an AudioWorklet, and exposes the full processing chain of the original (spectral filter, harmonics, octave mixer, pitch/frequency shift, tonal–noise separation, compressor, spread, free-form EQ curve, binaural beats).
Live build: https://olilarkin.github.io/paulstretch-for-web/
- Real-time streaming — stretched audio is generated in a worker, written into a
SharedArrayBufferring, and consumed by anAudioWorklet. No pre-render step; sliders respond within ~100 ms. - Stretch / HyperStretch / Shorten modes with adjustable FFT window size and type.
- Process tab — harmonics, pitch & frequency shift, octave mixer, band filter with high-frequency damping, tonal/noise emphasis, spectral compressor, spread, and an arbitrary-curve EQ. A live spectrum analyzer (log-spaced bargraph with peak-hold) sits behind the EQ curve.
- Stretch envelope — draw a per-position multiplier on the base stretch factor.
- Binaural beats — modulate left/right channels with a drawn frequency curve.
- Offline render — write the full stretched output to a WAV file via a separate render worker.
- Drag-and-drop WAV / FLAC / OGG / MP3 loading.
npm ci
npm run devOpen http://localhost:5173. Vite's dev server sets the COOP/COEP headers automatically.
To produce a production build:
npm run build
npm run previewThe DSP runs in a WebAssembly module published to GitHub Packages. The module is built from olilarkin/libpaulstretch, the C++ Paulstretch core compiled to WebAssembly with SIMD. .npmrc points @olilarkin/* at npm.pkg.github.com; npm ci needs a GITHUB_TOKEN (or NODE_AUTH_TOKEN) with read:packages scope in your environment to authenticate.
Paulstretch's streaming engine uses SharedArrayBuffer, which requires the page to be cross-origin isolated. That means the host must send:
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Vite's server and preview blocks in vite.config.ts set these for local dev. GitHub Pages can't, so public/coi-serviceworker.js re-serves responses with the right headers on first reload of the deployed site.
.github/workflows/deploy.yml builds the project on push to main and publishes dist/ to the gh-pages branch via peaceiris/actions-gh-pages. The workflow needs a GH_PACKAGES_TOKEN secret with read:packages scope to pull the WASM dependency.
src/
audio/
streaming/ # engine + worker + worklet + SAB ring buffer
render/ # offline WAV render worker
loadFile.ts # decode WAV/FLAC/OGG/MP3 via WebAudio
components/ # React UI (tabs, parameter panels, BPF editor)
state/store.ts # Zustand store
styles.css
- React + TypeScript + Vite
- Zustand for state
@olilarkin/paulstretch-wasm— the C++ Paulstretch core (olilarkin/libpaulstretch) compiled to WebAssembly with SIMD- AudioWorklet + SharedArrayBuffer for glitch-free streaming
npm testVitest covers the ring buffer, sync glue, envelope interpolation, and file loader.
- Nasca Octavian Paul — original Paulstretch algorithm and app.
- Oli Larkin — web port and UI.
GPL-2.0. See LICENSE.

