wavchan
A command-line tool for splitting multichannel/stereo WAV files apart, and combining files back together three different ways — across channels, across time, and across amplitude — the channel-layout counterpart to wavgen.
Overview
wavchan has five commands. split takes any plain-stereo or WAVEX multichannel file and breaks it into per-channel (or stereo-pair) output files. pack does the reverse, combining one or more mono/stereo files into a single multichannel or stereo output file, each input file tagged with which named channel(s) it supplies — combination across channels. join concatenates 2 or more files end-to-end in time. mix sums 2 or more same-shape files together sample-by-sample — combination across amplitude. info is read-only: it prints a file's channel layout without writing anything.
pack, join, and mix all take multiple files in and produce one file out, but along three different axes — see the FAQ for a side-by-side comparison.
It's the tool to reach for whenever you need genuinely independent left/right (or beyond-stereo) content — something wavgen deliberately doesn't do in one step (its --channels 2 only duplicates a single signal onto both channels). Generate each channel's content separately with wavgen, then combine them with wavchan pack.
It does not generate signal content itself, and it does not analyze, compare, or score audio — for signal generation see wavgen, for analysis see the main wavelens tool.
Requirements
- A valid, active WaveLens license file (see License below)
- Input and output files must be 16-bit or 24-bit PCM, standard or WAVEX/extensible format — no 8-bit, 32-bit integer, or floating-point WAV files
License
wavchan requires an active WaveLens license to run, checked the same way and in the same order as wavgen — the first of these that's set wins.
1. Explicit path with --license
# explicit --license overrides both the env var and the next-to-executable default wavchan pack --license /path/to/wavelens.lic \ --segment L:left.wav --segment R:right.wav -o stereo.wav
2. WAVELENS_LICENSE_FILE environment variable
# bash / zsh export WAVELENS_LICENSE_FILE=/path/to/wavelens.lic wavchan pack --segment L:left.wav --segment R:right.wav -o stereo.wav
3. Zero configuration — no flag, no env var
Place a file named exactly wavelens.lic in the same folder as the wavchan executable:
# wavelens.lic sits next to wavchan(.exe) -- no flag or env var needed wavchan pack --segment L:left.wav --segment R:right.wav -o stereo.wav
If none of the three resolve to a valid license, or the license is invalid or expired, wavchan refuses to run and prints the reason to the terminal.
Don't have a license file, or need a renewal? Contact your WaveLens distributor or administrator.
Quick start
Combine two mono files into one stereo file. This assumes a wavelens.lic is already sitting next to the wavchan executable, so no --license flag is needed:
wavchan pack --segment L:left.wav --segment R:right.wav -o stereo.wav
# output:
<<Generating...>> stereo.wav
Now split it back apart into its two mono channels:
wavchan split --input stereo.wav
# output -- written next to stereo.wav by default:
[Save as] stereo_L.wav
[Save as] stereo_R.wav
Command syntax
Every invocation names a command first (split, pack, info, join, or mix), then that command's own options. There are no options shared across all five commands besides --license.
wavchan split [--license <file.lic>] --input <in.wav> [--as mono|stereo] [-o <output_dir>] wavchan pack [--license <file.lic>] --segment <CHAN[,CHAN]>:<path> [--segment ...] -o <out.wav> wavchan info [--license <file.lic>] --input <file.wav> wavchan join [--license <file.lic>] --input <a.wav> --input <b.wav> [--input ...] -o <out.wav> wavchan mix [--license <file.lic>] --input <a.wav> --input <b.wav> [--input ...] -o <out.wav>
Options can appear in any order after the command name. --license is bracketed above because it's optional — see License.
Commands
wavchan splitone file → per-channel output filesGeneralizes wavGen-r07's stereo2mono and splitWavex into one command. What happens depends on the input file's own format:
- A plain (non-WAVEX) 2-channel file always splits into
_L/_Rmono files —--asis ignored, since there's only one way to split a plain stereo file. - A WAVEX multichannel file decodes its channel mask into named channels (see Channel names):
--as mono(the default) writes one file per channel;--as stereogroups them into standard stereo pairs, with digital silence filling any unpaired channel's second slot.
| Option | Required | Description |
|---|---|---|
| --input <file> | required | The WAV file to split. |
| --as mono|stereo | optional | Default mono. stereo only applies to WAVEX input — see above. |
| -o <dir> | optional | Output directory, created if it doesn't exist. Default: the same directory --input is in. |
Output filenames are always <input-basename>_<CHANNEL>.wav (mono) or <input-basename>_<CHAN0>_<CHAN1-or-"0">.wav (stereo pairs — a trailing _0 marks a channel that had no pairing partner, filled with silence).
Example — split a 3-channel WAVEX file (L, R, C) into three mono files, explicit --license:
wavchan split --license /path/to/wavelens.lic --input lrc.wav -o split_out
# output:
[Save as] split_out/lrc_L.wav
[Save as] split_out/lrc_R.wav
[Save as] split_out/lrc_C.wav
Example — the same file, but --as stereo instead:
wavchan split --input lrc.wav --as stereo -o split_out
# output -- L/R paired together, C has no partner so it's paired with silence:
[Save as] split_out/lrc_L_R.wav
[Save as] split_out/lrc_C_0.wav
Splitting a mono (1-channel) file is an error — there's nothing to split. Splitting a non-WAVEX file with channels other than 2 is also an error: without a channel mask to decode, wavchan has no way to know what each channel represents.
wavchan packone or more files → one multichannel/stereo fileGeneralizes wavGen-r07's mono2stereo and genWavex into one command. Each --segment names which channel(s) an input file supplies:
| Option | Required | Description |
|---|---|---|
| --segment CHAN[,CHAN]:path | required, repeatable | One channel name for a mono input file, or two (comma-separated) for a stereo input file's two channels in order. Repeat once per input file. |
| -o <file> | required | Output WAV file path. |
All input files must share the same sample rate, bit depth, and length — wavchan checks this and reports exactly which files disagree, rather than silently using whichever one it happened to read first (a real gap in the legacy tool this generalizes).
If the assembled channel set is exactly {L, R} and nothing else, the output is a plain (non-WAVEX) stereo file — maximally compatible with tools that don't understand WAVEX. Any other channel set writes a WAVEX file with the computed channel mask, in standard ascending-bit-position channel order regardless of the order --segment flags were given in.
Example — two mono files into stereo:
wavchan pack --license /path/to/wavelens.lic \ --segment L:left.wav --segment R:right.wav -o stereo.wav
Example — add a center channel to an existing stereo pair, producing a 3-channel WAVEX file (note the two comma-separated channel names for the stereo input):
wavchan pack --segment L,R:stereo.wav --segment C:center.wav -o lrc.wav
Example — assemble an 8-channel WAVEX file from individually generated mono channels:
wavchan pack \
--segment L:L.wav --segment R:R.wav --segment C:C.wav --segment LFE1:lfe.wav \
--segment Ls:ls.wav --segment Rs:rs.wav --segment Lc:lc.wav --segment Rc:rc.wav \
-o surround_71.wav
wavchan infoprint a file's channel layout — read-only, writes nothingReports sample rate, bit depth, channel count, and duration for any supported WAV file; for a WAVEX file, also the raw channel mask and its decoded channel names. Useful for answering "what channels does this file actually have" before deciding how to split it, without writing any output files to find out.
| Option | Required | Description |
|---|---|---|
| --input <file> | required | The WAV file to inspect. |
Example — an 8-channel WAVEX file:
wavchan info --input surround_71.wav
# output:
file: surround_71.wav
format: WAVEX (WAVE_FORMAT_EXTENSIBLE)
sample rate: 48000 Hz
bit depth: 24-bit
channels: 8
duration: 0.200s (9600 frames)
channel mask: 0x63f
channel map: L, R, C, LFE1, Lsr, Rsr, Lc, Rc
Example — a plain stereo file (no channel mask to decode, so channel identity is inferred rather than read):
wavchan info --input stereo.wav
# output:
file: stereo.wav
format: PCM (standard)
sample rate: 48000 Hz
bit depth: 24-bit
channels: 2
duration: 0.100s (4800 frames)
channel map: L, R (inferred -- no channel mask)
wavchan joinconcatenate files end-to-end in time — not channel assemblyPlays each input file's full duration one after another, in the order given, all in the same channel(s) — the output's duration is the sum of the inputs' durations, and its channel count is unchanged. This is the opposite of pack, which combines files across channels at a single point in time; don't confuse either with mix, which sums files together across amplitude — see below.
| Option | Required | Description |
|---|---|---|
| --input <file> | required, repeatable (2+) | One file to join, in the order it should appear in the output. Repeat for each file. |
| -o <file> | required | Output WAV file path. |
All inputs must share the same sample rate, bit depth, and channel count. If any input is WAVEX, every input must be WAVEX with the identical channel mask — joining a WAVEX file to a plain file, or two WAVEX files with different masks, is rejected. Output format matches the shared input format (plain PCM stays plain, WAVEX keeps its mask).
Example — the main use case: an amplitude-step file built from two independently generated wavgen levels (a quiet segment followed by a loud one, useful for compressor/limiter attack-time-style testing):
wavgen --mode tone --tone 1000:-40 --samplerate 48000 --bitdepth 24 --duration 0.5 -o low.wav wavgen --mode tone --tone 1000:0 --samplerate 48000 --bitdepth 24 --duration 1.0 -o high.wav wavchan join --input low.wav --input high.wav -o step.wav
Example — more than two files join too (a quiet/loud/quiet sequence):
wavchan join --input low.wav --input high.wav --input low.wav -o three_step.wav
wavchan mixsum files together sample-by-sample — additive superposition, not channel assembly or concatenationSums 2 or more files' samples together, at the same channel and time position — layering content, not assembling it across channels (pack) or laying it end-to-end in time (join). Output duration and channel count match the shared input shape. The sum is unity-gain — no per-input weighting; set the level you want at generation time in wavgen if a file needs to sit quieter or louder in the mix.
| Option | Required | Description |
|---|---|---|
| --input <file> | required, repeatable (2+) | One file to sum into the mix. Repeat for each file. |
| -o <file> | required | Output WAV file path. |
All inputs must share the same sample rate, bit depth, channel count, and frame count — stricter than join, which doesn't care about individual lengths. Mixing needs equal-length inputs, since there's no well-defined answer for what happens after a shorter file runs out. If any input is WAVEX, every input must be WAVEX with the identical channel mask, same rule as join. Output format matches the shared input format.
After summing, mix prints a warning (not a hard error) if the worst-case combined peak — the sum of each input's own measured peak, a cheap upper bound rather than the actual output peak — exceeds 0dBFS. The file is still written; a mix intentionally built to sit near or over full scale (e.g. stress-test content) is a legitimate use, so mix flags it rather than blocking it.
Example — sum two independently generated tones into one file:
wavgen --mode tone --tone 1000:-14 --samplerate 48000 --bitdepth 24 --duration 1.0 -o tone1.wav wavgen --mode tone --tone 2000:-14 --samplerate 48000 --bitdepth 24 --duration 1.0 -o tone2.wav wavchan mix --input tone1.wav --input tone2.wav -o mixed.wav
Example — more than two files mix too (a 5-tone stack, e.g. for multiband compressor stimulus):
wavchan mix --input tone0.wav --input tone1.wav --input tone2.wav --input tone3.wav --input tone4.wav -o five_tone_mix.wav
Channel names
wavchan understands 18 named speaker positions, matching the same dwChannelMask convention wavGen-r07 used. Channel names are case-insensitive.
| Name | Position |
|---|---|
| L / R | Front left / right |
| C | Front center |
| LFE1 | Low-frequency effects (subwoofer) |
| Ls / Rs | Side surround left / right |
| Lsr / Rsr | Rear surround left / right — same mask bit as Ls/Rs; see below |
| Lc / Rc | Front left-of-center / right-of-center |
| Cs | Rear center surround |
| Lss / Rss | Side surround left / right (wide layouts) |
| Lh / Rh | Height left / right |
| Lhr / Rhr | Rear height left / right |
| Ch / Chr | Center height / rear center height |
| Oh | Overhead / top center |
Ls/Rs vs. Lsr/Rsr are the same physical mask bit — the two names exist only to disambiguate side vs. rear surround by convention, not by a separate channel-mask bit. When wavchan split decodes a file with 7 or more channels, it automatically relabels Ls/Rs as Lsr/Rsr in the output filenames, matching how those speaker positions are actually used at that channel count. When packing, you can use either name — they resolve to the same channel.
Every channel name here works correctly with wavchan — not just the 3 that happen to be a single character (L/R/C). An earlier legacy tool this generalizes had a real bug where only single-character channel names packed correctly; see wavchan-spec.md §2 if you're curious about the history.
Output format
- Standard, uncompressed PCM WAV (RIFF/WAVE) or WAVE_FORMAT_EXTENSIBLE (WAVEX), 16-bit or 24-bit signed integer samples
packwrites plain PCM only when the result is exactly {L, R}; every other channel combination gets a WAVEX header with an explicit channel masksplitreads both plain PCM and WAVEX input, and always writes plain PCM output (a single channel, or a silence-filled stereo pair, never needs a channel mask of its own)
Common recipes
Combine two wavgen mono outputs into stereo
The most common wavchan workflow — generate independent left/right content with wavgen, then combine it, since wavgen --channels 2 only duplicates a single signal:
# wavgen generates each channel separately... wavgen --mode tone --tone 1000:-6 --samplerate 48000 --bitdepth 24 --duration 5 -o left.wav wavgen --mode tone --tone 2000:-6 --samplerate 48000 --bitdepth 24 --duration 5 -o right.wav # ...wavchan combines them wavchan pack --segment L:left.wav --segment R:right.wav -o stereo.wav
Stereo-link compressor test (asymmetric channel content)
Testing whether a compressor's stereo-link gain reduction leaks from one channel to the other requires genuinely different content per channel — e.g. a burst on the left, silence on the right:
wavgen --mode step-seq --freq 1000 --segment -40:0.5 --segment 0:1.0 --samplerate 48000 --bitdepth 24 -o burst_L.wav wavgen --mode silence --samplerate 48000 --bitdepth 24 --duration 1.5 -o silence_R.wav wavchan pack --segment L:burst_L.wav --segment R:silence_R.wav -o stereo_link_test.wav
Split a delivered surround file into individual channels for inspection
wavchan split --input surround_71.wav --as mono -o channels
Re-pair a surround file into stereo submixes
Useful for auditioning a multichannel file on stereo monitoring — pairs L/R, C/LFE1, Ls/Rs, etc. into separate stereo files:
wavchan split --input surround_71.wav --as stereo -o submixes
Full example reference
--license is omitted throughout for readability — add it, or set WAVELENS_LICENSE_FILE, per License if you're not relying on the next-to-executable default.
pack — mono files into stereo
# two mono files -> plain stereo (exactly {L,R})
wavchan pack --segment L:left.wav --segment R:right.wav -o stereo.wav
pack — extending an existing stereo pair
# stereo file's two channels (comma-separated names) + a mono center -> 3-channel WAVEX
wavchan pack --segment L,R:stereo.wav --segment C:center.wav -o lrc.wav
pack — full surround assembly
# eight independently generated mono files -> 7.1-style WAVEX file
wavchan pack --segment L:L.wav --segment R:R.wav --segment C:C.wav --segment LFE1:lfe.wav \
--segment Ls:ls.wav --segment Rs:rs.wav --segment Lc:lc.wav --segment Rc:rc.wav -o surround.wav
split — plain stereo into mono
# always _L/_R for a plain (non-WAVEX) 2-channel file
wavchan split --input stereo.wav -o out
split — WAVEX into mono (default)
wavchan split --input surround.wav --as mono -o out
split — WAVEX into stereo pairs
# standard pairing table -- unpaired channels get a silence-filled "_0" partner
wavchan split --input surround.wav --as stereo -o out
info — inspect a file's channel layout
# read-only -- writes nothing, works on plain PCM or WAVEX input
wavchan info --input surround.wav
wavchan info --input stereo.wav
join — concatenate files end-to-end in time
# two-level amplitude step wavchan join --input low.wav --input high.wav -o step.wav # three or more files join too, in the order given wavchan join --input low.wav --input high.wav --input low.wav -o three_step.wav
mix — sum files together sample-by-sample
# two tones summed into one file wavchan mix --input tone1.wav --input tone2.wav -o mixed.wav # three or more files mix too wavchan mix --input tone0.wav --input tone1.wav --input tone2.wav -o three_tone_mix.wav
Errors & troubleshooting
| Message | Meaning & fix |
|---|---|
| error: license INVALID -- no license found at '...' | Same three-mechanism lookup as wavgen — check the resolved path shown isn't missing or misspelled. |
| error: failed to read '...' (not a supported 16/24-bit PCM or WAVEX WAV file) | The input isn't a RIFF/WAVE file, is missing a fmt or data chunk, or uses an unsupported bit depth/format (8-bit, 32-bit int, float). |
| error: '...' is a plain (non-WAVEX) N-channel file -- wavchan can only infer channel identity for a plain 2-channel (L/R) file | A split input has channels ≠ 2 with no WAVEX channel mask to decode. Only plain-stereo and WAVEX input are supported. |
| error: '...' is already a plain stereo file -- nothing to pair | --as stereo only applies to WAVEX input; a plain stereo file has nothing to re-pair. |
| error: '...' has only 1 channel -- nothing to split | Mono input has no channels to separate. |
| error: '...' is not a recognized channel name | Check spelling against the table in Channel names — names are case-insensitive but must match exactly otherwise. |
| error: channel '...' assigned more than once | Two --segment flags named the same channel. Each channel can only be supplied by one input file. |
| error: '...' has N channel(s) but M channel name(s) were given for it | A --segment's channel-name count didn't match that file's actual channel count (1 name for mono, 2 for stereo). |
| error: '...' is X Hz / Y-bit, but '...' was Z Hz / W-bit -- all pack inputs must share the same sample rate and bit depth | Every file passed to pack must match. Re-generate the mismatched file at the right format. |
| error: '...' has N frames, but '...' has M -- all pack inputs must have the same length | Every file passed to pack must be the same duration (in samples, not just seconds). |
| error: malformed --segment '...', expected CHAN[,CHAN]:path | Check the format: one or two comma-separated channel names, a colon, then the file path. |
| error: join requires at least two --input files | join needs 2 or more --input flags — joining a single file is a no-op. |
| error: '...' is X Hz / Y-bit / N ch, but '...' was ... -- all join inputs must share the same sample rate, bit depth, and channel count | Every file passed to join must match on all three. Re-generate the mismatched file at the right format. |
| error: '...' has a different channel layout than '...' -- all join inputs must either all be plain PCM or all share the same WAVEX channel mask | Can't join a plain file to a WAVEX file, or two WAVEX files with different channel masks — there'd be no single consistent layout for the result. |
| error: mix requires at least 2 --input files | mix needs 2 or more --input flags — summing a single file is a no-op. |
| error: '...' is X Hz / Y-bit / N ch / M frames, but '...' was ... -- all mix inputs must share the same sample rate, bit depth, channel count, and frame count | Every file passed to mix must match on all four — including length, unlike join. Re-generate the mismatched file at the right format/duration. |
| error: '...' has a different channel layout than '...' -- all mix inputs must either all be plain PCM or all share the same WAVEX channel mask | Can't mix a plain file with a WAVEX file, or two WAVEX files with different channel masks. |
warning: combined peak may exceed 0dBFS by N.NNdB -- ... is not an error — mix still writes the output file. It's a heads-up that the worst-case summed peak may clip; check the result and reduce input levels first if that's not intended.
FAQ
Do I have to pass --license every time?
No — same as wavgen. Set WAVELENS_LICENSE_FILE once, or keep a wavelens.lic next to the wavchan executable.
Can I pack more than 2 channels into a plain (non-WAVEX) file?
No — a standard WAV header has no way to declare per-channel speaker identity beyond a bare channel count, so anything past a plain L/R pair needs the WAVEX channel-mask extension. pack makes this decision automatically: exactly {L, R} gets plain PCM, everything else gets WAVEX.
What happens if I pack a channel set that has no defined stereo pair (e.g. just Cs alone) and then split --as stereo?
It's written with a silence-filled second slot — the output filename gets a trailing _0 (e.g. _Cs_0.wav) so it's clear the second channel is silence, not a real pairing.
Does wavchan support directories of files, or only single files?
Single files only, on every command, today. wavGen-r07's original tools could walk a directory of WAV files; that's not carried over yet since nothing currently needs it — see wavchan-spec.md §7 if that becomes a real requirement.
What's the difference between pack, join, and mix?
All three take multiple files in and produce one file out, but along three different axes. pack puts each file in its own channel, all playing at once — output duration stays the same, channel count grows. join plays each file's full duration one after another in time — output duration grows, channel count stays the same. mix sums every file's samples together at the same channel and time position — amplitude layering — output duration and channel count both stay the same, but the content is additively combined. mix is unity-gain (no per-input weighting); a weighted downmix (e.g. 5.1-to-stereo with per-channel coefficients) isn't something any wavchan command does today.
What sample formats can I feed into the WaveLens analysis tool afterward?
See the main WaveLens documentation for input requirements — this guide covers channel splitting/packing only.