This page documents the observed .vdjstems sidecar file format used by
VirtualDJ's prepared stems. It is about the container/file contract, not
VDJScript stem controls.
Source labels:
Official: VirtualDJ public stems help describes the five stem components and prepared-stems workflow.Local observation: files generated by VirtualDJ and inspected locally.Local script: local helper scripts and generated files that VirtualDJ accepts.Local test: tool behavior (ffmpeg/MP4Box/AtomicParsley) verified locally.Inference: reasoned from evidence but not directly verified.External: Matroska container references and stem-file drafts outside VirtualDJ.
Local observation: VirtualDJ-prepared .vdjstems sidecars are open Matroska
audio files, not encrypted/private blobs. The observed VirtualDJ sidecar contract
is:
| Stream index | Matroska track ID | Audio role title |
|---|---|---|
0 |
1 |
vocal |
1 |
2 |
hihat |
2 |
3 |
bass |
3 |
4 |
instruments |
4 |
5 |
kick |
All observed VirtualDJ-generated sidecars use five stereo AAC streams. The
stream title tags are part of the role contract; do not treat the file as just
"any five audio streams in Matroska."
Local test: two further contract fields are load-bearing for externally
generated sidecars: the streams' sample rate must equal the original
file's (see Sample Rate Contract), and the Matroska Writing application
field must carry VirtualDJ's <version>.stems2 signature (see Recreating A
VirtualDJ-Like Sidecar) — VirtualDJ silently ignores a sidecar without it.
Local observation: On 2026-06-04, the local folder
~/Library/Mobile Documents/com~apple~CloudDocs/Music/Tracks contained 1,505
files ending in .vdjstems.
The fast signature pass found:
| Count | Container/codec shape |
|---|---|
| 1,476 | Matroska, five stereo AAC streams at 44.1 kHz, titled vocal, hihat, bass, instruments, kick |
| 23 | Matroska, five stereo AAC streams at 48 kHz, same titles/order |
| 5 | Matroska, five stereo AAC streams at 96 kHz, same titles/order |
| 1 | MPEG-4, six stereo ALAC streams, mixed track first, not VirtualDJ-generated |
The 1,504 Matroska files had a Writing application value in the form
VirtualDJ ... .stems2, with examples including:
VirtualDJ 2026.9336.stems2VirtualDJ 2026.9004.stems2VirtualDJ 2026.8978.stems2VirtualDJ 2025.8800.stems2VirtualDJ i.stems2
The one MPEG-4/ALAC outlier had Writing application: Lavf62.3.100 and should
be treated as a custom/experimental stem container, not as the VirtualDJ sidecar
format described here.
Local observation: A 2026-07-18 recount of the same folder found 1,521
.vdjstems files, consistent with the corpus continuing to grow through normal
VirtualDJ prepared-stems use. Additional VirtualDJ-generated sidecars with the
same Matroska shape exist under
~/Library/Application Support/VirtualDJ/Sampler/ (including
*.vdjsample.vdjstems files), so the format also covers sampler content.
Local observation: A representative VirtualDJ-generated sidecar reports:
| Field | Observed value |
|---|---|
| Extension | .vdjstems |
| File type | Matroska audio (audio/x-matroska) |
| EBML doc type | matroska |
| Matroska version | Version 4 |
| Timecode scale | 1 ms |
| Writing application | VirtualDJ <version>.stems2 |
| Muxing/writing library | Lavf... |
| Audio codec | AAC LC (A_AAC / A_AAC-2) |
| Channels | 2, stereo |
| Default audio track | none |
| Forced audio track | none |
ffprobe exposes the file as format_name=matroska,webm; generic tools may
warn that .vdjstems is not a normal Matroska extension. That warning is
expected.
Local observation: Detailed samples had no chapters and no attachments exposed
by ffprobe. Stream DURATION tags can vary by a final AAC frame; for example,
kick was sometimes a few milliseconds longer than the other streams.
Local observation and Local script: VirtualDJ's role detection depends on
metadata and order. The important stream tags are the lowercase titles:
0 title=vocal
1 title=hihat
2 title=bass
3 title=instruments
4 title=kick
Practical rules:
- Use exactly five sidecar streams.
- Do not include a
mixed trackstream in the sidecar. - Use the exact role order
vocal,hihat,bass,instruments,kick. - Write the role names as stream titles, not only filenames.
- Clear default dispositions on every audio stream.
- Keep every stream stereo and start-aligned at timestamp 0.
- Match the original file's sample rate (see next section) — this is a playback contract, not a preference.
- AAC is what VirtualDJ itself writes, but it is not required:
Local test(2026-07-18, tone probes) confirmed sidecars with FLAC (16- and 24-bit), ALAC, and PCM streams all load and play correctly in VirtualDJ 2026. This enables lossless prepared stems and does not appear to be documented anywhere else. FLAC is the natural choice (Matroska-native, smaller); the decoder is evidently plain libavformat.
Local observation (2026-07-18): The sidecar's sample rate must equal the
original file's sample rate. Pairing every non-44.1 kHz sidecar in the corpus
with its original shows an exact match in all cases:
| Sidecar rate | Original rate | Count observed |
|---|---|---|
| 48000 | 48000 | all 48 kHz sidecars checked (20+) |
| 96000 | 96000 | all 96 kHz sidecars checked (5) |
| 44100 | 44100 | spot-checked sample of 40 |
| 44100 | 192000 | 1 (VirtualDJ capped it — AAC-LC tops out at 96 kHz) |
Local test (2026-07-18, VirtualDJ 2026): an externally generated sidecar
whose streams were 44.1 kHz against a higher-rate original loaded and was
used by VirtualDJ, but muting stems played the sidecar audio pitch-shifted
upward and stuttering — consistent with VirtualDJ reading sidecar frames on
the original file's clock without resampling. The same failure is expected in
the opposite direction (rate too high → slow/low). Externally generated
packers must probe the original's rate and encode the sidecar to match,
capping above 96 kHz at 44.1 kHz per the observed VirtualDJ behavior.
This also resolves part of a prior unknown: VirtualDJ 2026 does accept and play externally generated Matroska sidecars (the failure above was rate mismatch, not rejection).
Inference: The stream titles are safer to treat as required even if a
particular build also appears to infer roles by order. The local scripts that
work write both the order and the titles, and VirtualDJ's own sidecars include
both.
Local observation: Prepared-stem sidecars are named by appending .vdjstems to
the source track path:
/path/to/original.ext.vdjstems
Examples:
Track Name.flac.vdjstems
Track Name.m4a.vdjstems
Track Name.mp3.vdjstems
Track Name.dj2.flac.vdjstems
VirtualDJ settings can also affect where prepared stems are stored; see
stemsSavedFolder in Configuration Options.
Use ffprobe for a quick compatibility check:
ffprobe -v error -select_streams a \
-show_entries stream=index,codec_name,sample_rate,channels:stream_tags=title \
-of compact=p=0:nk=1 \
"/path/to/track.ext.vdjstems"Expected VirtualDJ sidecar shape:
0|aac|44100|2|vocal
1|aac|44100|2|hihat
2|aac|44100|2|bass
3|aac|44100|2|instruments
4|aac|44100|2|kick
The sample rate may be 44100, 48000, or 96000 in the local corpus, and
always equals the original file's rate (see Sample Rate Contract above).
Use mediainfo when you need Matroska-level fields:
mediainfo "/path/to/track.ext.vdjstems"Look for:
Format : Matroska
Writing application : VirtualDJ 2026.9336.stems2
Writing library : Lavf62.13.102
Title : vocal
Title : hihat
Title : bass
Title : instruments
Title : kick
Local script: The minimal packer is ordinary ffmpeg: map five prepared WAV
stems, assign stream titles, encode AAC, and force a Matroska output even though
the extension is .vdjstems.
#!/usr/bin/env zsh
set -euo pipefail
dir="${1:?usage: pack-vdjstems-sidecar <stem-dir> [output.vdjstems]}"
out="${2:-$dir/$(basename "$dir").vdjstems}"
for name in vocal hihat bass instruments kick; do
[[ -f "$dir/$name.wav" ]] || {
print -u2 "missing $dir/$name.wav"
exit 2
}
done
ffmpeg -y \
-i "$dir/vocal.wav" \
-i "$dir/hihat.wav" \
-i "$dir/bass.wav" \
-i "$dir/instruments.wav" \
-i "$dir/kick.wav" \
-map 0:a -map 1:a -map 2:a -map 3:a -map 4:a \
-metadata:s:a:0 title="vocal" \
-metadata:s:a:1 title="hihat" \
-metadata:s:a:2 title="bass" \
-metadata:s:a:3 title="instruments" \
-metadata:s:a:4 title="kick" \
-disposition:a:0 0 \
-disposition:a:1 0 \
-disposition:a:2 0 \
-disposition:a:3 0 \
-disposition:a:4 0 \
-c:a aac -b:a 320k -ar "$orig_rate" \
-f matroska \
"$out"$orig_rate must be the original track's sample rate (probe it with
ffprobe), capped at 44100 above 96 kHz — see Sample Rate Contract.
The Writing application field must then be stamped — this is required.
Local test (2026-07-18, VirtualDJ 2026, tone-probe A/B): two sidecars that
were byte-identical except for the Matroska Writing application field were
placed next to identical originals. The one stamped
VirtualDJ 2026.9336.stems2 was used by VirtualDJ (stem pads played the
sidecar streams); the one left as Lavf… was silently ignored (VirtualDJ
treated the track as having no prepared stems). ffmpeg hardcodes Lavf in
this field, so stamp it afterwards:
mkvpropedit "$out" --edit info \
--set "writing-application=VirtualDJ 2026.9336.stems2"An earlier revision of this page claimed the field did not need to be faked;
that claim was wrong and is superseded by the A/B above. The likely purpose
of the <version>.stems2 marker is engine-version gating — VirtualDJ can
tell which stems engine produced a cache and regenerate or ignore stale ones.
Local observation: On 2026-07-18, a six-stream MPEG-4 file named with the
.vdjstems extension was not accepted by VirtualDJ as a prepared-stems file
(it was either rejected or misinterpreted as a sidecar). The corpus outlier
above (MPEG-4/ALAC named .vdjstems) matches the same mistake: it was produced
by an earlier local script, not by VirtualDJ.
Practical rule: .vdjstems means "Matroska sidecar, five stems" to VirtualDJ.
MPEG-4 stem containers must be named .m4a and used as standalone files (next
section). Never emit an MP4 with a .vdjstems extension.
Local test (2026-07-18, VirtualDJ 2026): VirtualDJ detects a multi-track
MPEG-4/M4A audio file in the library as a stems-capable track. This is a
community-reverse-engineered format (VirtualDJ 2023-era output) and it
still works in VirtualDJ 2026 — an acceptance matrix built from one stem
set confirmed the recipe below loads with working stem pads. The working
recipe:
| Field | Working value |
|---|---|
| Extension | .m4a (see extension rule above) |
| Container | MPEG-4, brand isom (minor 512), compatible mp42 + mp41 |
| Storage | flat (MP4Box -flat; non-interleaved) |
| Streams | exactly 6, order: mixed track, vocal, hihat, bass, instruments, kick |
| Track names | per-track udta name atoms (MP4Box -udta N:type=name:str=...) |
| Disposition | stream 0 (mixed track) default; all stems non-default |
| Codec | AAC-LC 320k, ALAC, or FLAC, 44.1 kHz stereo. ALAC must be 16-bit (s16p) on every stream — Local test 2026: an s32p master plays stuttery with pitch artifacts; the identical all-s16p file plays perfectly. FLAC requires the plain mp4 muxer (ffmpeg -f mp4 -strict experimental; the ipod/.m4a muxer refuses it) |
| iTunes tags | tool=VirtualDJ <version> (itags) |
Inference: The per-track udta name atoms are the safer role signal in
this container: known-working scripts wrote roles as udta names (with stream
title/handler metadata absent or inconsistent), unlike the Matroska sidecar
where the stream title tags carry the roles.
The mixed track stream is the full mix, so the file plays as an ordinary
library track in any player. When packing from separated stems without an
original mix, sum the unique stems (do not count a drums stem twice if kick and
hihat were duplicated from it — see the −6 dB rule below).
Local test (2026-07-18, VirtualDJ 2026): the master must be the plain
unity-gain sum of the five stem streams. When stems are active, VirtualDJ
sums the stem tracks at unity gain; a master attenuated relative to that sum
(e.g. ffmpeg amix default 1/n scaling) makes stem playback jump ~12 dB
louder than master playback the moment any stem is muted. The legacy packer's
amix=inputs=5,volume=5 idiom exists precisely to undo amix's scaling;
amix=inputs=N:normalize=0 is the modern equivalent.
Local test (VirtualDJ 2026, macOS): five candidate containers built from
the same stem set and loaded in VirtualDJ:
| Candidate | Result |
|---|---|
Legacy recipe .m4a (AAC, itags incl. tempo/key, -brand isom on the ffmpeg mux) |
Works — loads, stem pads function |
Same family, minor deltas (no ffmpeg-level isom brand, different itags) |
Loads from library only; drag-and-drop onto a deck fails; stems work |
MP4-era .vdjstems (six-stream ALAC MP4, -inter 500, mp42 brands) |
Rejected — unloads the current deck track and loads nothing |
Matroska 6-track .mka (mixed + 5 titled stems) |
Works via drag-and-drop; invisible in the browser (.mka not in VirtualDJ's extension list); renamed .mkv it appears but is not detected as a stems file |
Matroska 5-track .mka/.mkv (exact sidecar shape as a file) |
Not treated as stems — plays the first stream (vocal) as a normal track |
Local observation: There are two stem role vocabularies in local artifacts.
Besides the five-stem set on this page, an October 2025 local experiment
(~/Music/Stems/test-stems/TestTone.vdjstems, five-stream ALAC MP4) used the
NI-style 4-stem naming as udta track names: All, Vocal, Instrument,
Bass, Drums — the same set local NI-conversion scripts target. Whether
the 4-stem M4A family is still recognized by VirtualDJ 2026 is under test
(tests/Stems/make-diagnostic-stems.zsh builds a DIAG-4stem.m4a probe).
Notes:
- VirtualDJ's accepted-extensions option (observed default) does not include
.mka:mp3,wav,cda,wma,asf,ogg,m4a,aac,aif,aiff,flac,mpc,ape,weba,opus, vdj,vdjcache,vdjedit,vdjsample,mp4,ogm,ogv,avi,mpg,mpeg,wmv,vob,mov,divx, m4v,mkv,flv,webm,vdjcachev,apng. - The
.mka-drag-drop vs.mkv-browser asymmetry for the 6-track Matroska candidate is unexplained; treat Matroska standalones as unreliable and use the M4A recipe. - The browser-only vs drag-and-drop difference between the two M4A variants
is not yet isolated (candidate deltas: ffmpeg-level
-brand isom, presence ofINITIALKEY/tempoitags); packers should replicate the fully working variant exactly.
Local test (2026-07-18, tone-probe round — master = white noise, each stem
a distinct sine, so routing is audible; harness:
tests/Stems/make-diagnostic-stems.zsh):
| Probe | Result |
|---|---|
| 5-stem standalone M4A (working recipe, noise master) | Confirmed — master plays the mixed track stream; engaging stems switches to the stem streams; each pad maps to its titled stream |
4-stem M4A (All, Vocal, Instrument, Bass, Drums udta names) |
Dead in VirtualDJ 2026 — loads into the deck but plays nothing and detects no stems (an October 2025 local artifact shows this family was previously in local use) |
Matroska sidecar, writing-application=VirtualDJ 2026.9336.stems2 |
Works — stem pads play the sidecar's tone streams; master plays the original (noise) |
Same sidecar, writing-application=Lavf… |
Ignored — original plays, no stems detected; proves the stamp is the acceptance gate |
| Sidecar with FLAC streams (stamped) | Works — lossless sidecars are possible; previously undocumented |
| Sidecar with ALAC streams (stamped) | Works |
| Standalone M4A in ALAC (stems s16p, master s32p) | Broken — loads but playback stutters with pitch artifacts, the same audible signature as a sample-rate mismatch |
| Standalone M4A in ALAC, all streams s16p | Works — isolates the failure above to the 32-bit master: VirtualDJ's standalone ALAC playback is 16-bit only |
Sidecar with PCM (pcm_s16le) streams |
Works |
| Sidecar with 24-bit FLAC streams | Works — the 16-bit limit applies only to standalone MP4 ALAC, not to the Matroska sidecar |
Sidecar stamped VirtualDJ 2025.8800.stems2 (old version) |
Works, and VirtualDJ did not regenerate or modify the file — the stamp check is version-tolerant |
Sidecar with capitalized titles (Vocal, Hihat, …) |
Works — role matching is case-insensitive (stream order was also canonical, so order-vs-title precedence remains unseparated) |
| Standalone MP4 with six FLAC streams | Works — lossless FLAC standalones are possible. Note the mux: ffmpeg's .m4a/ipod muxer refuses FLAC; force -f mp4 (-strict experimental), then MP4Box processes it normally |
Local test (2026-07-18, MP4Box 2.x / ffmpeg 8.1):
tempo=<bpm>via MP4Box-itagsis written as the iTunestmpoatom and reads back correctly.INITIALKEY=<key>via MP4Box-itagsis silently dropped (MP4Box printsUnrecognize tag).- A key survives as an iTunes freeform atom
----:com.apple.iTunes:initialkey, e.g. written withAtomicParsley file --rDNSatom "Am" name=initialkey domain=com.apple.iTunes -W;ffprobethen reportsTAG:initialkey=Am. This is the atom family other DJ taggers (Mixed In Key, Serato) use. - Arbitrary
-metadata KEY=valueon ffmpeg's MP4 muxer is dropped for unknown keys; it cannot writeINITIALKEYeither.
Whether VirtualDJ itself reads the freeform initialkey atom from a standalone
stems file is untested; VirtualDJ analyzes key on import regardless.
Local script: 4-stem separators (Demucs-style vocals/drums/bass/other)
have no kick/hihat split. The working convention when duplicating the drums
stem into both roles is to attenuate both copies by −6.0206 dB (a factor of
exactly ½), so kick + hihat sums back to the original drums level when both
stems play. When synthesizing a standalone mixed track from such stems, sum
the drums source once, not both duplicates.
Local script: The maintained toolchain lives in
/Users/nom/config/config/zsh/bin (July 2026 overhaul):
vdjstems make song.flac— separate + pack in one command. Default output is the Matroska sidecarsong.flac.vdjstems;--standalonebuilds the six-track M4A instead.vdjstems-split— MLX-accelerated separation (BS-RoFormer vocals + htdemucs_ft bands + MDX23C-DrumSep kick/hihat;--swruns the BS-Roformer-SW six-stem model in a single pass).vdjstems-pack --sidecar ORIGINAL <stemdir>/vdjstems-pack --standalone— the packer implementing both formats on this page.vdjstems-info,vdjstems-extract,vdjstems-from-nistem— inspection, stream extraction, and NI.stem.mp4conversion.
Superseded experiments are parked in /Users/nom/config/config/zsh/_bin_quarantine.
The July 2026 tone-probe rounds closed every practical question (see the acceptance matrix). What remains is minor:
- Role order vs title precedence in sidecars: every probe used canonical order AND correct titles, so which one wins on a mismatch is unobserved.
- Whether some future engine version bump makes VirtualDJ regenerate old-stamped sidecars during library maintenance (the 2025-stamp probe was not modified during normal load/play).
- Behavior on other platforms (all tests: macOS, VirtualDJ 2026).
- Whether VirtualDJ reads the freeform
initialkeyatom from standalone files. - What exactly distinguishes the fully-working M4A variant from the
library-only variant (see the acceptance matrix) —
-brand isomat the ffmpeg layer and itag differences are the candidates. - Why the 6-track Matroska engaged stems when drag-and-dropped as
.mkabut not when browsed as.mkv.
- VirtualDJ stems help -
Official - Matroska stem files Internet-Draft -
External - Local
.vdjstemsfiles under~/Library/Mobile Documents/com~apple~CloudDocs/Music/Tracksand~/Library/Application Support/VirtualDJ/Sampler-Local observation - Local
vdjstems-*toolchain under/Users/nom/config/config/zsh/bin(superseded experiments inconfig/zsh/_bin_quarantine) -Local script - MP4Box itags / AtomicParsley freeform-atom behavior verified locally
2026-07-18 -
Local test