Command line
The binary is sound-to-patch. This page shows the help of each command.
The help texts run in the test suite, so they match the built binary.
cargo xtask dist also renders them as man pages.
sound-to-patch
$ sound-to-patch --help
Turn an audio stem or a written sound description into a starting point in Vital or Serum 2.
Usage: sound-to-patch <COMMAND>
Commands:
analyze Analyse a stem and write layers.json, spectrogram.png and notes.mid
notes Transcribe a stem and write notes.mid
preset Read, inspect and write synth presets
render Render a preset and a MIDI file to a WAV file through the synth
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
analyze
$ sound-to-patch analyze --help
Analyse a stem and write layers.json, spectrogram.png and notes.mid.
The tempo is refined from the stem near --bpm, and the stem is transcribed unless --notes gives its notes. Each step prints what it found.
Example: sound-to-patch analyze bass.wav --bpm 140
Usage: sound-to-patch analyze [OPTIONS] --bpm <BPM> <STEM>
Arguments:
<STEM>
The audio stem: WAV, AIFF, FLAC, MP3, AAC or M4A
Options:
--bpm <BPM>
The tempo of the stem in beats per minute, 4 to 1000. It is refined from the stem, 2.91 % below to 3.09 % above it, unless --bpm-exact is given
--bpm-exact
Use --bpm as it is, without refining it from the stem
--notes <FILE>
A MIDI file with the stem's notes, instead of transcribing the stem; the f0 track still reads the stem unless the notes play chords. Its tempo must match --bpm or the refined tempo
--profile <PROFILE>
Decode with the settings of this kind of stem instead of the detected ones
Possible values:
- bass: B0 to F#4, with the minimum length from the BPM
- lead: C2 to B6, with the minimum length from the BPM. Plucks use it too
- pad: C2 to B6, with the decoder's default minimum length. Chords use it too
-o, --out <DIR>
The output folder [default: a folder named after the stem, next to it]
--force
Replace files in the output folder that this stem's report does not own: a report or notes of another stem, a report of the same name with other content, a file this tool did not write, or a spectrogram.png without a report
--synth <SYNTH>
The target synth that the report names [default: vital]
Possible values:
- vital: Vital, with `.vital` preset files
- serum2: Serum 2, with `.SerumPreset` preset files
-h, --help
Print help (see a summary with '-h')
analyze decodes the stem at its native rate, runs the stem estimators
and writes three files into the output folder, which it creates:
layers.json: the report. The layer report page lists every field.spectrogram.png: a 1500 by 512 pixel image of the busiest 10 s, in two panels. The pipeline page gives its layout.notes.mid: the final note list, the notes rewritten with the layers, as the analyze guide explains. It replaces the first file ofnotes. A stem without notes writes none.
The files are replaced on the next run of the same stem file; nothing else
in the folder changes. A stem of the same name with other bytes, such as a
new export, needs --force, below. The same stem and flags give the same
bytes. The stem is read once, so a pipe such as /dev/stdin works as a
stem. Each file
is written to a hidden temporary sibling and renamed into place,
notes.mid first and layers.json last; the old layers.json moves
aside before any file changes. So no run leaves a cut-short file, and a
layers.json that exists belongs to the files next to it. A write that
fails, such as on a full disk, puts the old files back, leaves no
temporary file and removes a folder the run created. A run killed during
the renames, such as by a power loss, can leave the old files as hidden
backups named .<file>.<random>.old, such as
.layers.json.a1B2c3.old, with no layers.json. The next run stops and
names every backup: rename each backup back to its file name to keep the
old report, or pass --force to write new files and remove the backups
once they are in place. The
command prints one wrote <path> line per file to standard output, and a
warning line for each corrupt packet that became silence to standard
error.
layers.json records the stem’s file name in source and the SHA-256 of
its bytes in source_sha256. The command stops before it decodes, names
the file and asks for --out or --force when the folder holds:
- the report of another stem, such as
bass.aiffnext tobass.wavsharingbass/; - a report of the same name with other content: another stem of that
name, such as the
bass.wavof another song in a shared--outfolder, or this stem changed since that run, such as a new export; a report from release 0.31 or earlier has no checksum and is matched by its name; - a
layers.jsonthat is not a report of this tool: it cannot be read, is not JSON or names nosource; - a
spectrogram.pngand nolayers.json, a file no report owns; - a
notes.midof another stem by its track name, or one with no track name, as innotes.
--out picks another folder, and --force replaces the files. An
output folder that is a file, by default or by --out, such as a file
bass next to bass.wav, stops the command with a request for --out.
Without --out, the folder is named after the stem file and sits next to
it: stems/bass.wav writes stems/bass/. A stem whose name without the
extension is . or .., such as ..wav, would name its own folder or the
one above, and a stem without an extension, such as bass or .wav,
would name the stem file itself, so each needs --out.
- Tempo:
--bpmis refined from the stem’s onsets, from 2.91 % below to 3.09 % above it, as the tempo section describes, and the first line of standard output says which tempo every later step uses and why.--bpm-exactuses--bpmas it is. The report records the given and the refined tempo. - Notes: without
--notes, the stem runs through the notes step ofnotesonce, and the next lines of standard output are its steps;--profileoverrides the detected settings as there. Thetranscriptiongroup of the report records the model and the settings. - With
--notes, the MIDI file gives the note list and the model never runs;--profileis then a usage error, andtranscriptionisnull. The note times follow the file’s tempo map, and every tempo in the file must lie within 0.01 BPM of--bpmor of the refined tempo; a file without a tempo event plays at the MIDI default of 120 BPM. Notes on channel 10, the percussion channel, are left out. - Standard output ends with
wrote <path>for each file. A warning line for each corrupt packet that became silence goes to standard error.
| Exit code | Meaning |
|---|---|
| 0 | The files were written. |
| 1 | The stem or the note file cannot be read or is invalid, the notes step fails, the selected synth has no adapter yet, the output folder is a file, has no name of its own or holds a file the run does not own, an output cannot be written, or the analysis fails internally. A refusal and a failure share this code. The message names the cause and the fix, and the file when one is involved. |
| 2 | A usage error, such as a missing --bpm, a tempo outside 4 to 1000, or --profile with --notes. |
notes
$ sound-to-patch notes --help
Transcribe a stem and write notes.mid.
The pitch range, the voicing and the minimum note length come from the stem unless --profile sets them. Each step prints what it found.
Example: sound-to-patch notes bass.wav --bpm 140
Usage: sound-to-patch notes [OPTIONS] --bpm <BPM> <STEM>
Arguments:
<STEM>
The audio stem: WAV, AIFF, FLAC, MP3, AAC or M4A
Options:
--bpm <BPM>
The tempo of the stem in beats per minute, 4 to 1000. It is refined from the stem, 2.91 % below to 3.09 % above it, unless --bpm-exact is given
--bpm-exact
Use --bpm as it is, without refining it from the stem
--profile <PROFILE>
Decode with the settings of this kind of stem instead of the detected ones
Possible values:
- bass: B0 to F#4, with the minimum length from the BPM
- lead: C2 to B6, with the minimum length from the BPM. Plucks use it too
- pad: C2 to B6, with the decoder's default minimum length. Chords use it too
-o, --out <DIR>
The output folder [default: a folder named after the stem, next to it]
--force
Write next to a report that is not this stem's, replace a notes.mid of another stem or with no track name, or replace the final notes and remove the report that describes them
-h, --help
Print help (see a summary with '-h')
notes decodes the stem at its native rate, runs the transcription model
once and the f0 track once, and writes notes.mid into the output folder,
which it creates. The pipeline page
gives each step.
- Settings: without
--profile, the pitch range, the voicing and the minimum note length come from the stem.--profile bass,leadorpadsets them instead; detection still runs and prints its findings. - Tempo:
--bpmis refined from the stem as foranalyze, and every later step uses the printed tempo;--bpm-exactuses--bpmas it is. - Output: standard output gets one line per step, in order: the tempo,
the detected range, the detected voicing, the register window, the
voicing the cleanup used and where it came from, the settings used, the
decoded count, then the note count after the octave check, the ghost
merge, the attack split, the split join and the quiet-note guard with
its drop count, and last
wrote <path>. A warning line for each corrupt packet that became silence goes to standard error. - The file: a format 0 MIDI file with 480 ticks per beat, the stem’s file
name as the track name, the printed tempo and 4/4 at tick 0, and
every note on channel 1 at velocity 100.
It is written to a hidden temporary sibling and renamed into place. A
failed write leaves the old files and no temporary file. Only
notes.midis replaced; nothing else in the folder changes, except the report that--forceremoves, below. - The folder: as for
analyze,stems/bass.wavwrites intostems/bass/without--out, and a stem such as..wav,.wavorbassneeds--out. When the folder holds anotes.midwhose track name is another stem, or none, the command stops before it decodes and names the file;--forcereplaces that file. When it holds alayers.jsonof another stem, of the same name with other content, or one that is not a report, it stops the same way;--forcewritesnotes.midnext to that file and leaves it. When it holds alayers.jsonwith anotesgroup, the report ofanalyzethat describes the finalnotes.mid, it stops whatever the stem, because the first list would replace the file the report describes;--forcereplacesnotes.midand removes that report once the new file is in place, so no report describes a file that is gone. A backup that a killed run left, as foranalyze, stops it too;--forcewritesnotes.midand then removes the backups.--outpicks another folder in every case.
| Exit code | Meaning |
|---|---|
| 0 | notes.mid was written. |
| 1 | The stem cannot be read, gives no note (digital silence, for one), or cannot be transcribed; the output folder is a file, has no name of its own or holds a file the run does not own; or the file cannot be written. Nothing is written. A refusal and a failure share this code. The message names the cause and the fix. |
| 2 | A usage error: a missing --bpm, a tempo outside 4 to 1000 or not a number, or a profile other than bass, lead and pad. |
preset
$ sound-to-patch preset --help
Read, inspect and write synth presets
Usage: sound-to-patch preset <COMMAND>
Commands:
inspect Print the header fields and the decoded tree of a preset file
apply Apply an edit list to a template and write a preset
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
preset inspect
$ sound-to-patch preset inspect --help
Print the header fields and the decoded tree of a preset file.
Example: sound-to-patch preset inspect bass.vital
Usage: sound-to-patch preset inspect [OPTIONS] <FILE>
Arguments:
<FILE>
The preset file, such as `bass.vital`
Options:
--synth <SYNTH>
The target synth [default: the extension of the preset file, else vital]
Possible values:
- vital: Vital, with `.vital` preset files
- serum2: Serum 2, with `.SerumPreset` preset files
--full
Print every list element and every text in full
--shown <N>
List elements printed before the rest is counted
[default: 3]
-h, --help
Print help (see a summary with '-h')
preset inspect refuses a file without a stamp, settings or sample, and
a file with a known control that holds a text, a list, an object or null in
place of a number. Vital fails to load a text in a control and loads a
flag, so a flag reads. The message names the key.
preset apply
$ sound-to-patch preset apply --help
Apply an edit list to a template and write a preset.
Values are written with their unit, such as "1.2 kHz", "800 ms", "-6 dB", "+12 st" or "25%".
Example: sound-to-patch preset apply edits.json -o bass.vital
Usage: sound-to-patch preset apply [OPTIONS] --out <FILE> <EDITS>
Arguments:
<EDITS>
The edit list, a JSON file
Options:
-o, --out <FILE>
The preset to write, such as `bass.vital`
--template <FILE>
A preset to start from instead of the synth's built-in template
--name <NAME>
The preset name. Without it, the name of the output file
--force
Replace the output file if it exists
--synth <SYNTH>
The target synth [default: the extension of the preset file, else vital]
Possible values:
- vital: Vital, with `.vital` preset files
- serum2: Serum 2, with `.SerumPreset` preset files
-h, --help
Print help (see a summary with '-h')
The output file:
- must carry the synth’s extension, such as
.vital; - must not exist, unless
--forceis given; - must not be the edit list or the template;
- must not lie inside Vital’s own
Factoryfolder.
The output keeps the template’s stamp, and the summary prints it. A stamp
with a newer first or second number than the stable build 1.0.7, such as
1.1.0, gives a warning: that build refuses it. The load gate ignores the
third number, so 1.0.8 gives none. A run that ends with an error leaves no
file. A run killed during the write leaves at most a temporary file whose name
ends in .partial next to the output. A folder
without hard links, such as one on an exFAT or FAT32 drive, gets the file
too, and an existing file there is still replaced only with --force.
There, without --force, the file is written in place: it is visible while
it is written, an error removes it, and a run killed during the write
leaves a partial output file. An
empty --name, such as an unset shell variable, is a usage error.
The edit list
preset apply reads an edit list: a JSON object with the target synth and
a list of edits, in a UTF-8 file of at most 1 MiB. A leading byte order
mark is skipped. Each edit is one of four records, and each carries a
reason.
| Record | Fields | Example |
|---|---|---|
| A control | param, value | {"param": "filter_1_cutoff", "value": "400 Hz", "reason": "dark"} |
| A menu choice | menu, choice | {"menu": "filter_1_model", "choice": "Ladder", "reason": "warm"} |
| A route | route: source, dest, amount, optional polarity | {"route": {"source": "env_2", "dest": "filter_1_cutoff", "amount": "+24 st"}, "reason": "pluck"} |
| A raw key | raw: key, value | {"raw": {"key": "custom_warps_count", "value": 2}, "reason": "a newer build"} |
- A control value carries its unit, as the units page lists:
"1.2 kHz","800 ms","-6 dB","+12 st"or"25%". A switch takestrueorfalse, and a count takes a number. - A route amount is in semitones when the destination is stored in semitones, such as a cutoff or a pitch, and in percent otherwise. A depth in semitones is divided by the destination’s stored span, as Vital shows it.
- A route may target a number, a count, a menu such as
lfo_1_tempo, or the amount or power of another slot. A switch and the bipolar, stereo and bypass flags of a slot take no route. - A route amount lies within -100% to 100% of the destination’s range.
- A route that joins the same source and destination as an existing one edits it in place and keeps its polarity, curve and flags unless the edit sets them.
- A raw key is written as it is, outside the registry, and the output lists it as raw. A raw edit never replaces a key of the template that holds a list or an object.
- A list edits each control once and each source and destination once. A second edit of either is refused with the number of the first.
- Every problem of a list is reported at once, up to 50, each with the file, the edit, the cause and the fix.
The guide walks through one edit list.
render
$ sound-to-patch render --help
Render a preset and a MIDI file to a WAV file through the synth.
The synth's plug-in loads headless in the plug-in host process. A render counts only after the load check: the clip sounds, and the state the plug-in saves back holds the preset. A clip whose octave bands match the plug-in's init patch gives a warning. Each step prints what it found.
Example: sound-to-patch render bass.vital --midi notes.mid --bpm 140 -o bass.wav
Usage: sound-to-patch render [OPTIONS] --midi <FILE> --bpm <BPM> --out <WAV> <PRESET>
Arguments:
<PRESET>
The preset to render
Options:
--midi <FILE>
The MIDI file with the notes, read as `analyze --notes` reads it
--bpm <BPM>
The tempo of the MIDI file in beats per minute, 4 to 1000
-o, --out <WAV>
The WAV file to write; the render record goes next to it with .json
--plugin <BUNDLE>
The synth's .vst3 bundle [default: the bundle in /Library/Audio/Plug-Ins/VST3, else in the user's VST3 folder]
--synth <SYNTH>
The target synth [default: the extension of the preset file, else vital]
Possible values:
- vital: Vital, with `.vital` preset files
- serum2: Serum 2, with `.SerumPreset` preset files
--force
Replace an existing WAV or record file
-h, --help
Print help (see a summary with '-h')
render writes two files: the WAV file named by --out, 32-bit float
stereo at 44.1 kHz, and the render record next to it, with .json in
place of .wav. The record follows schemas/render-record.schema.json;
the render record page lists its fields.
Both go to temporary files in the same folder first, and replace the
outputs only once both writes succeeded. Without --force, a failed
write leaves the folder as it was. With --force, a failure while placing
the files can leave the new WAV file beside the old record. The error then
names both files and says that they no longer match. Make the record
replaceable and render again with --force.
The plug-in loads in sound-to-patch-host, a second program that ships
next to sound-to-patch. SOUND_TO_PATCH_HOST names another copy. The
guide walks through one render and the load check.