ReverseVerb
v1.0.0One-click reverse reverb swell for Ableton Live 12 (Extensions SDK). Offline DSP, right-click an audio clip.
ReverseVerb
An Ableton Live 12 Extension that automates the reverse reverb swell into a single right-click on an audio clip.
The reverb tail, flipped in time, crescendos into the original transient — a swell that builds up to the hit. Great on vocals, snares, downbeats, and transitions. The whole signal path is owned in JavaScript and runs offline, so the result is deterministic and the source clip is never touched.
renderPreFxAudio(track, clipStart, clipEnd) → dry sample buffer
→ reverse the buffer
→ convolve with a synthesized reverb impulse response
→ reverse the result back
→ (optional) lay the original dry hit back at the transient
→ write a NEW audio clip on a NEW track
Requirements
- Ableton Live 12 Suite Beta, v12.4.5+ with Developer Mode enabled (Preferences → Extensions). Extensions do not exist in Standard/Intro/Lite or the stable release.
- Node.js ≥ 24.14.1.
- The Ableton Extensions SDK + CLI tarballs, placed in
./sdk/. They are not included in this repo — Ableton's SDK license forbids redistributing the SDK itself. If you have access to the Extensions beta, download them and drop them in (see below).
Install & run (development)
You must supply the SDK yourself. From your Extensions beta download, place these
into a ./sdk/ folder at the repo root:
sdk/ableton-extensions-sdk-1.0.0-beta.0.tgz
sdk/ableton-extensions-cli-1.0.0-beta.0.tgz
(These exact filenames are what package.json references via file:./sdk/....)
Then:
npm install # resolves the SDK/CLI from ./sdk/*.tgz
cp .env.example .env # then edit EXTENSION_HOST_PATH for your machine
npm start # builds and loads the extension into Live
npm start requires Live's beta build with Developer Mode on; without it the
CLI can't connect. EXTENSION_HOST_PATH points at Live's Extension Host module
on your machine.
Other scripts:
| Command | What it does |
|---|---|
npm test | Runs the offline DSP unit tests (no Live needed). |
npm run typecheck | tsc --noEmit. |
npm run build | Type-check + production bundle to dist/extension.js. |
npm run package | Builds and packages a distributable .ablx. |
Usage
Right-click an audio clip in the Arrangement → you get two actions:
- Reverse Reverb Swell — one click, processes with musical defaults.
- Reverse Reverb Swell… — opens a settings dialog first.
The result is written to a new audio track placed so the original transient lands where the source clip began. The source clip is left untouched (undoable).
Whatever you set in Reverse Reverb Swell… is saved as your defaults
(settings.json in the extension's storage directory) and is reused by both
actions next time.
Parameters
| Param | Type | Default | Notes |
|---|---|---|---|
decaySeconds | float | 2.0 | Reverb length / swell length. |
preDelayMs | float | 0 | Front-pads the IR → a gap immediately before the hit. |
dampingHz | float | 6000 | One-pole low-pass tone on the tail. |
wetMix | float 0–1 | 1.0 | Wet swell level over the dry in the hit region (1.0 = pure swell on top of the dry). |
appendDry | bool | true | true = swell + original dry hit (drop-in); false = swell only. |
outputGainDb | float | 0.0 | Final trim, applied after normalisation to −1 dBFS. |
reverseBack | bool | true | true = reverse-reverb swell; false = plain forward reverb tail (for testing). |
Reverb model
The impulse response is synthesized procedurally — no IR file, no external binaries. It is exponentially-decaying, gently low-passed, decorrelated stereo noise:
- Length =
decaySecondsat the clip's sample rate (+preDelayMsof front padding). - Amplitude envelope
exp(-t·k),k = 6.908 / decaySeconds(~−60 dB at the tail). - One-pole low-pass at
dampingHzfor tone. - Two decorrelated noise channels (deterministic, seeded) for stereo width; mono input is fed to both.
Convolution is FFT-based (a dependency-free radix-2 FFT), so multi-second
decays don't stall the UI. The wet result is normalised to −1 dBFS before
outputGainDb.
Output placement
The processed buffer is longer than the source — the swell precedes the hit. The
dry transient lands swellLength (= IR length) into the buffer, so the new clip
is placed swellLength earlier on the timeline and the hit realigns to the
source clip's original start.
Caveats:
- Always a new track. The processed buffer spans the source's own timeline region, so reusing the source track would overwrite it. A dedicated new audio track is the simplest provably non-destructive choice. (Smart same-track placement is future work — see below.)
- Clip near bar 1. If the source starts less than
swellLengthfrom the timeline start, the new clip is clamped to bar 1 and the transient may not perfectly realign (a warning is logged).
Verified SDK API notes
These were confirmed against the bundled types
(sdk/.../dist/index.d.mts), docs, and examples before writing the core logic:
- Raw render —
context.resources.renderPreFxAudio(track, startBeat, endBeat)returns a path to a WAV in the extension's temp dir. It renders a track over a beat range (not a clip object), pre-effects. We resolve the clip's owningAudioTrack(walkingparent), render over the clip'sstartTime/endTime, then read + decode the WAV withaudio-decode. - Write back — write our processed WAV into
environment.tempDirectory(extensions are sandboxed to the temp/storage dirs), thenresources.importIntoProject(path)→track.createAudioClip({ filePath, … }). Inserting our own generated buffer as a new clip is fully supported. - Undo — clip creation is wrapped in
context.withinTransaction(…)(a synchronous callback returning the mutation's promise). - Parameters / UI — there is no rich parameter system in the beta. UI is
limited to
showModalDialog(a webview that posts a result string) andwithinProgressDialog. We expose all parameters through a small HTML form loaded from a self-containeddata:URL, plus a one-click defaults action. - Context-menu scoping —
ui.registerContextMenuAction("AudioClip", …)scopes the action to audio clips and passes the clip'sHandleto the command, resolved viagetObjectFromHandle(handle, AudioClip). - Native reverse — not present; reversing is done in JS.
Project layout
ReverseVerb/
├── manifest.json Extension manifest (name, entry, API version)
├── build.ts esbuild bundler (→ dist/extension.js)
├── src/
│ ├── extension.ts Host glue: context menu, render → process → new clip
│ ├── dsp.ts Pure DSP: synthIR, reverse, normalize, gain, processor
│ ├── fft.ts Dependency-free radix-2 FFT + FFT convolution
│ ├── wav.ts 32-bit float WAV encoder
│ └── params.ts Defaults, sanitisation, settings-modal HTML
├── test/
│ └── dsp.test.ts Offline DSP unit tests (Node test runner, no Live)
└── sdk/ SDK/CLI tarballs — you supply these (gitignored)
Scope
v1: a single selected arrangement audio clip.
Out of scope (future work): multi-clip batch, smart same-track placement when there's room, IR-file loading, gated/ducked variants, automation lanes, MIDI, and Session-view clips.
License
This project's own source code is released under the MIT License.
The Ableton Extensions SDK is not covered by that license and is not
included here. It is Ableton's, under their own SDK license, which forbids
redistributing the SDK kit. You may freely share a packaged .ablx you build
(the SDK code compiled into it is permitted "inside your application"), but not
the sdk/ tarballs, docs, or examples.
Rate this extension
One rating per account; you can change it any time.
Comments
No comments yet.