How to approach it
When processing large numbers of files, it helps to build up in this order.
- Dial in one file in the GUI and confirm the result by eye and by ear
- Write the same settings out as
--oparguments toa9a_cli renderand confirm they reproduce on that one file (exporting a job file from the GUI’s bounce dialog skips this step; see “Exporting a job file from the GUI” below) - Widen to many files with
--out-dir - Once the procedure is settled, move it into a job file and pass only the inputs and outputs on the command line
The GUI and the CLI share the same processing engine, so what you confirmed in the GUI reproduces exactly in the CLI.
Processing many files at once
Pass multiple inputs and specify the destination with --out-dir. --out cannot be used with multiple inputs.
a9a_cli render assets/*.wav \
--out-dir build/mastered \
--same-format \
--force \
--op loudness target=-16LUFS max-true-peak=-1dBTP limiter=on
--op arguments are applied in the order given. When the order changes the result — in particular when trim is involved — see the notes in Editing Waveforms.
Add --no-indicator to disable the progress display, or -q / --quiet to suppress warnings and progress output as well. For automated runs that keep logs, --no-indicator makes the output far easier to read.
Moving the procedure into a job file
Collect the processing settings in a TOML file and load it with render --job <JOB>. Keeping one job file per output spec puts the procedure itself under version control.
input = ["../audio/a.wav", "../audio/b.wav"]
out_dir = "../out"
same_format = true
force = true
comment = ["ARTIST=a9a", "ALBUM=demo"]
[[op]]
type = "gain"
db = -3
[[op]]
type = "fade_in"
duration = "100ms"
curve = "equal_power"
[[op]]
type = "loop"
start = 12000
end = 96000
a9a_cli render --job jobs/render.toml
The full key list is in the CLI Reference.
Exporting a job file from the GUI
Besides writing job files by hand, you can export them from the GUI’s bounce dialog. The settings and edits you confirmed by eye and by ear in the GUI become a job file that runs as-is with render --job (see Exporting for the export steps).
- A job file exported with
Export job file as a presetenabled contains noinput/output/out_dir. Pass the inputs and destination on the command line to reuse it across different sets of files (a9a_cli render --job preset.toml --out-dir out/ *.wav) - Because a GUI bounce always replaces the input’s loop data with the GUI’s own settings, the exported job file includes
clear_loop = true - GUI loop positions are already finalized, so
loopops are exported withsnap = false(no zero-cross snapping) - Files with different edits are exported as separate job files
Combining a job file with command-line arguments
Keep the shared processing in the job file and pass the per-job details on the command line.
a9a_cli render \
--job jobs/master.toml \
--out-dir build/mastered \
--comment REVISION=2026-03-20
When both are given, they resolve as follows.
| Item | Resolution |
|---|---|
| Operations | CLI --op entries are appended after the job file’s [[op]] entries |
input | The CLI value wins |
output / out_dir | The CLI value wins |
Output overrides such as sample_rate | The CLI value wins, field by field |
force / same_format / clear_loop | true when either side is true |
comment / remove_comment | CLI --comment / --remove-comment values are appended to the job file arrays |
Checking a set of files
Adding --json to probe prints the measurements to stdout in a machine-readable form, suitable for bulk checks and CI asset quality gates.
a9a_cli probe assets/*.wav --json | jq '.[0].integrated_lufs'
The top level is always an array, even for a single input, with entries in the order the inputs were given.
[
{
"file": "input.wav",
"format": "wav",
"sample_rate_hz": 48000,
"bit_depth": 16,
"channels": 2,
"duration_seconds": 1.0,
"integrated_lufs": -14.0,
"true_peak_dbtp": -1.0,
"channel_true_peaks_dbtp": [-1.0, -1.0],
"loudness_range_lu": null,
"crest_factor_db": 3.0,
"channel_crest_factor_db": [3.0, 3.0],
"aac": null,
"loop": {"start": 4, "end": 12, "length": 8},
"tags": [{"key": "ARTIST", "value": "a9a"}],
"warnings": [],
"silence": {
"threshold_dbfs": -60.0,
"min_duration_ms": 100.0,
"leading_frames": 24000,
"leading_seconds": 0.5,
"trailing_frames": 0,
"trailing_seconds": 0.0,
"total_frames": 24000,
"total_seconds": 0.5,
"regions": [
{"start_frame": 0, "end_frame": 24000, "start_seconds": 0.0, "end_seconds": 0.5}
]
}
}
]
Values that cannot be measured (±inf / NaN, for example on silence) are null. The silence detection conditions can be changed with --silence-threshold (default -60 dBFS) and --silence-min-duration (default 100 ms). The field definitions are in the CLI Reference.
How failures are reported
If some files fail to decode or measure, the remaining inputs are still processed. Failed files appear in the same array in this form, and the exit code is non-zero when at least one input failed.
{"file": "broken.wav", "error": "Failed to decode ..."}
Successful entries never include an error key, so its presence tells you which inputs failed.
Quality gate examples
Confirm that every file decoded and that its integrated loudness is within ±1 LU of the target. When the condition fails, jq -e exits non-zero, so this works as a CI step as is.
set -o pipefail
a9a_cli probe assets/*.wav --json \
| jq -e 'all(.[]; .error == null and .integrated_lufs >= -17 and .integrated_lufs <= -15)'
Detect mixed sample rates or channel counts across a set:
set -o pipefail
a9a_cli probe assets/*.wav --json \
| jq -e 'all(.[]; .error == null)
and ([.[] | {sample_rate_hz, channels}] | unique | length == 1)'
Confirm that no file has more than 10 ms of silence left at its head or tail:
set -o pipefail
a9a_cli probe assets/*.wav --json \
| jq -e 'all(.[]; .error == null
and .silence.leading_seconds <= 0.01
and .silence.trailing_seconds <= 0.01)'
This page was written with generative AI.