A plugin for Slopsmith that turns multi-stem .sloppak songs into a live mixing board. Toggle guitar, bass, drums, vocals, piano, or "other" on the fly during playback, tweak each stem's volume, and the plugin remembers your mix per song.
PSARC songs are untouched — the plugin only activates when a song's song_info payload contains a non-empty stems[] array.
- Per-stem mute toggles injected into the player control bar
- Inline volume controls — each stem button fills left-to-right to show its saved volume; drag across the button to set the level
- Per-song memory — muted stems and volumes are saved to
localStoragekeyed by filename, so each song reopens with your last mix - Mute on load — pick which stems start silenced when a song opens (e.g. always start with vocals off)
- Karaoke mode — one-click preset that always mutes vocals by default
- Sample-locked playback — every stem plays from a decoded
AudioBufferthrough one sharedAudioContext, so the stems and the note highway are sample-exact and cannot drift apart - Pitch-preserving speed control — slowing a song down (or speeding it up) changes tempo only, not pitch, just like PSARC playback
- Inert on PSARC — core audio works normally when there are no stems to mix
cd /path/to/slopsmith/plugins
git clone https://github.com/topkoa/slopsmith-plugin-stems.git stems
docker compose restart- Convert a PSARC to a
.sloppakwith the Sloppak Converter plugin (which runs Demucs to split the single mixed track into per-instrument stems), or hand-craft a sloppak directory with multiple stems listed inmanifest.yaml. - Play the song. The stem mixer bar appears in
#player-controlswith one labeled button per stem. - Click a stem to toggle it on/off. Drag left or right across the button to adjust its volume continuously.
- Your mute state and volumes are remembered the next time you open the same song.
Open Settings → Stems Toggle to configure:
- Karaoke mode — start every new song with vocals muted
- Mute on load — tick the stems that should default to off (e.g. vocals + piano for a guitar practice preset)
Per-song toggles always override defaults.
Playing six stems as six independent <audio> elements means six independent
HTMLMediaElement decoder clocks — and they do not stay in step. In practice
the guitar/core stem's element decoded ~7–8% fast, so the note highway (which
clocked off it) ran ahead of the music. The plugin removes HTMLMediaElement
decoder clocks from playback entirely.
When a song with stems loads, the plugin:
- Fetches every stem and
AudioContext.decodeAudioData()s each into anAudioBuffer(a "Decoding stems…" indicator shows while this runs) - Plays each stem through an
AudioBufferSourceNode→ per-stemGainNode→ masterGainNode→AudioContext.destination. Every source isstart()-ed at the sameAudioContexttime, so the stems are sample-locked forever - Derives the playhead from the
AudioContextclock and shims the core<audio id="audio">element'splay/pause/currentTime/duration/pausedto drive this transport, dispatching the matching media events so the rest of slopsmith is unaffected - Publishes
window.slopsmith.stems.setMasterVolumeso the core "Song" fader drives the masterGainNode, moving every stem together - Exposes
window.slopsmith.stems.getAnalyser()— anAnalyserNodeon the stem mix — for audio-reactive plugins
Transport (play / pause / seek / speed) operates on all stems atomically:
seeking stops and recreates every source node at the new offset so they
restart locked. Toggling a stem is a pure GainNode.gain.value change.
PSARC songs (no stems) and the JUCE desktop path are untouched — the <audio>
shims delegate straight to core whenever no sloppak is active.
When AudioWorklet is available, the per-stem AudioBufferSourceNodes are
replaced by a single stem-mixer AudioWorkletProcessor
(assets/stretch-worklet.js) that owns every stem's PCM. It mixes the stems
(applying live per-stem gains) and time-stretches the single mixed signal
with WSOLA, so the speed slider changes tempo without changing pitch — and
because there is one mix and one stretcher, the stems stay sample-locked. At
rate 1.0 it is an exact pass-through (no stretch, no added latency); off-unity
it reports its constant algorithmic latency so the highway stays aligned with
what is heard. The module is self-hosted and loaded from the plugin's
assets/ directory via the core /api/plugins/{id}/assets/{path} route.
If AudioWorklet is unavailable (e.g. an old WKWebView) the plugin falls back
to the AudioBufferSourceNode-per-stem path, where the speed slider couples
pitch to tempo as before; a one-time console warning is logged.
The DSP has a headless test: node tests/stretch-worklet.test.mjs.
Requires Slopsmith with .sloppak format support and a song_info payload that includes a stems[] array (available on the feature/sloppak-format branch and its merged descendants).
- Sloppak Converter — convert PSARCs into
.sloppakfiles in-app, with optional Demucs stem splitting
MIT