a9a / Docs

Batch Processing and Automation

Processing many files at once, reusing a procedure with a TOML job file, and building post-export checks and CI quality gates on probe --json.

How to approach it

When processing large numbers of files, it helps to build up in this order.

  1. Dial in one file in the GUI and confirm the result by eye and by ear
  2. Write the same settings out as --op arguments to a9a_cli render and 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)
  3. Widen to many files with --out-dir
  4. 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 preset enabled contains no input / 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 loop ops are exported with snap = 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.

ItemResolution
OperationsCLI --op entries are appended after the job file’s [[op]] entries
inputThe CLI value wins
output / out_dirThe CLI value wins
Output overrides such as sample_rateThe CLI value wins, field by field
force / same_format / clear_looptrue when either side is true
comment / remove_commentCLI --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.