valis-shot measures a sound file. Use it when you cannot listen, or want a number to compare. It is a port of ideas
from earshot, written in C++ under src/shot/. It is a work in progress.
This page covers the command itself. Measuring a sound is the overview of every measurement Valis
offers, and where valis-shot sits among the tools.
Everything it reports is measured, not heard. A number or a model can say a sound is odd. It cannot say a sound is good.
| You ask | Command | MCP tool | Needs models |
|---|---|---|---|
| Is anything wrong with this file? How loud, how fast is the attack? | valis-shot measure FILE |
measure |
no |
| Did my change alter the sound? | valis-shot compare A B |
compare |
no |
| Is this "a trumpet"? | valis-shot is FILE "a trumpet" |
sounds_like |
yes |
| What is it? Which of my sentences fits best? | valis-shot listen FILE "a harp" "a piano" |
listen |
yes |
Start with measure. Use is for the "does it sound right" question. Use listen only when you want to
choose the competing sentences yourself.
Prints one JSON object. Read signal.notes first: plain words about silence, clipping, DC offset or values that are not
numbers. Then signal.pitch (the note and how many cents off), loudness and envelope.
| Field | Meaning | Warning sign |
|---|---|---|
loudness.integratedLufs |
overall loudness, LUFS | above about -9 is very loud; streaming sits near -14 |
loudness.truePeakDbtp |
highest level including between samples | above -1 may distort after MP3 |
envelope.attackMs |
how fast it starts: a drum a few ms, a bowed string hundreds | a pluck with a slow attack is not a pluck |
envelope.decayMs |
time to fall 30 dB; -1 if it never does |
a note that dies when you wanted a ring |
spectralDistanceDb is 0 for the same sound, a few dB for a small change, tens of dB for a different sound. A
sound 6 dB louder reads about 5.6. loudnessDifferenceLu is the loudness change, second file minus first.
When the first file is 20 s or longer, sections says where the difference lies. The first file is cut at its own section
boundaries (the sections of measure) and the same stretch of each file is compared: spectralDistanceDb,
levelDifferenceDb (second minus first, RMS) and, for two stereo files, widthDifferenceDb (see stereoImage in
measure). mostChangedSection is the index of the largest spectral distance. Both files are cut at the same times, so
renders that are not the same length or whose parts have moved compare unlike music; a section that starts after the
second file ends is left out.
Give it a file and one sentence. It scores the sentence against twenty common rivals (other instruments, a synthesizer preset, noise, a sine) and answers:
{"sentence":"a trumpet","verdict":"no","share":0.196,"rank":2,"of":20,
"closest":[{"sentence":"a synthesizer keyboard preset","share":0.600},{"sentence":"a trumpet","share":0.196}, ...]}
verdict is yes at a share of 0.5 or more, maybe from 0.2, else no. Those cut-offs are a judgement. The useful part
is closest: what the sound was mistaken for. A synthetic-sounding instrument shows up as a synthesizer preset.
The same models without the rivals: the eight most likely AudioSet labels, and a
cosine and share for each sentence you give. Read the share, not the cosine; a cosine alone means little.
Give two or three real alternatives.
fit_params (MCP) or OpDispatcher::fitParams plays one note, scores the render against a recording by spectral
distance, and moves the circuit's val:Param values to lower it. It reports the distance before and after and the value
found for each knob, and changes nothing. The search is a seeded evolution strategy, so the same call gives the same
answer; a render takes a fraction of a second, so 60 evaluations is a few seconds to a minute. In the test it takes an
SH-101 whose filter peak is wrong by 6500 Hz from 19.6 dB away to 0.2 dB and finds 1517 Hz where the answer is 1500.
Give it slots to search a few knobs; searching all of them needs more evaluations. A small distance means the spectra
agree, not that the sound is right.
With 24 evaluations or more it first tries a seeded spread of starting points over the whole range and climbs from the
best two or three, because one climb from a poor start can stop at the edge of a knob's range (tried on the dub bass, it
stopped at 1.8 dB where the true settings score 0; the spread reaches 0.45 dB in 120 renders). It releases the note at 80% of
a whole recording, and not at all inside the window when only the start of a longer recording is matched; hold names
the time in seconds if you know it. If the best match is still over 1 dB away the result says so in notes.
A song's mix aims at a loudness itself. Give the song val:loudness, or pass --lufs N to valis-song mix (or lufs to
the mix_song tool): the last gain brings the mix to that integrated loudness, held back so the true peak stays at or under
-1 dBTP, and the reply says when the ceiling, not the target, set the gain. It uses the same measureLoudness as
valis-shot measure. See songs. The Python helper that did this before is kept in scripts/archive/lufs.py.
is and listen)cmake -S . -B build -DVALIS_WITH_ONNX=ON # fetches ONNX Runtime 1.31.1, pinned and SHA-256 checked
cmake --build build
scripts/shot/fetch-models.sh # about 1.1 GB, once, into ~/.local/share/valis/models
Nothing downloads unless you run the script. A build without VALIS_WITH_ONNX, or without the models, answers is and
listen with that reason and nothing else breaks. The first call in a process loads the models (a few seconds, about a
gigabyte of memory); later calls are fast. Set VALIS_MODELS to keep the models elsewhere.
is on each rendered stem, with the sentence that should fit:
| Stem | Sentence | Verdict | What it was closest to |
|---|---|---|---|
| guitar | a plucked nylon string guitar | yes 92% | itself |
| vihuela | a plucked nylon string guitar | yes 88% | itself |
| congas | conga drums | yes 66% | itself |
| guitarron | a bass guitar | yes 55% | nylon guitar 44% |
| trumpets | a trumpet | no 20% | a synthesizer keyboard preset 60% |
| violins | a violin section | no 5% | an electric organ 47%, a flute 25% |
| harp | a harp | no 7% | a piano 53%, a bell 14% |
| shakers | maracas shaking | no 7% | conga drums 62% |
The plucked and struck parts pass. The trumpets, violins, harp and shakers do not, and the models say what they sound like
instead. That agreed with a listener who called the first trumpets a cheap keyboard. The next work is in TODO.md.
measure and compare can.Sine wave and hiss White noise, and CLAP must put a low tone nearer
"a low, deep rumble" and a high tone nearer "a high pitched whistle". That rules out a gross mistake, not a small shift.The work is in include/valis/ShotReport.h and src/shot/ShotReport.cpp. The command line tool, the operation surface
(OpDispatcher::measure, compare, soundsLike, listen) and the MCP server all call it, so there is one implementation.
| Part | What it does | From earshot |
|---|---|---|
Loudness |
integrated loudness, range and true peak after ITU-R BS.1770-4 and EBU R 128; coefficients derived for any sample rate | features.py |
EnvelopeTimes |
attack (10% to 90%), decay (to 30 dB down), noise floor, dynamic range | features.envelope_times |
SpectralDistance |
distance in dB between two renders at three time resolutions on a mel scale | fit/modal.mel_distance |
ClapFrontend, AstFrontend |
the log-mel features each model was trained on | the models' own extractors |
BpeTokenizer |
the byte-pair tokenizer for CLAP's text (reproduces the known RoBERTa ids) | the model's tokenizer |
NeuralEars |
the ONNX wrapper; the only file that includes ONNX Runtime | models/clap.py, models/ast.py |
Tests are in tests/shot/, each against a signal whose answer is known: a 997 Hz sine at -23 dBFS reads -23.0 LUFS,
and a tone at a quarter of the sample rate has a true peak 3 dB above its sample peak. On the v3 mix, loudness
is -12.3 LUFS with 7.5 LU range and a -0.97 dBTP true peak, louder than streaming services need.
The CLAP temperature used for shares, 38.6647, is exp(logit_scale_a) read from the published weights.
The weights are not in git and not downloaded by a build or a test. scripts/shot/fetch-models.sh fetches them from
pinned commits and checks each file's SHA-256; a bad download is discarded. The tests skip themselves when the weights
are absent.
| Model | Source (ONNX export of the original) | Licence | Size |
|---|---|---|---|
| CLAP audio encoder | Xenova/larger_clap_general @ d78c399, from laion/larger_clap_general | Apache-2.0 | 282 MB |
| CLAP text encoder | the same repository | Apache-2.0 | 502 MB |
| AST | Xenova/ast-finetuned-audioset-10-10-0.4593 @ 249a1fb, from MIT/ast-finetuned-audioset-10-10-0.4593 | BSD-3-Clause | 347 MB |
The original models carry the licences shown. The ONNX repositories state none of their own and are conversions of those weights.
| Idea | Why it is useful | Cost |
|---|---|---|
Modal fit (fit/modal.py): a struck recording becomes damped sinusoids, then a model that re-renders it |
Done in a small form: val:FreeModalBank plays the modes and fit_modes finds them (spectral peaks and a decay fit per mode, in src/shot/ModalFit.cpp, in place of ESPRIT, which needs an eigen-solver). Tested on known modes; real recordings are untried. |
|
Search by listening (forge/critic.py, search.py): vary a recipe's knobs and keep what scores nearest a target |
Done in a small form: fit_params (see below). Still missing: searching the structure of a circuit, and scoring by more than spectral distance. |
|
Sound bank as recipes (forge/recipes, 252 UI and film sounds) |
Short sounds (clicks, whooshes, risers) described as code; a useful example set. | Medium, mostly circuit writing. |
Realness (realness.py): real recording or code-synthesised |
Would score how acoustic a circuit sounds. | A classifier on CLAP features; could be ported now that CLAP runs. |
Calibrated match (match.py, vocab.py): a z-score among 322 reference descriptions |
A fairer verdict than the fixed cut-offs in is. |
Small: port vocab.json and its statistics. |
Timeline and sync (timeline.py) |
Only for scoring to video. | Not relevant yet. |
| Stripping a bed, gesture and spectral fitters | Fitting noisy sounds such as whooshes. | Large, narrow use. |
The two worth doing next, the free-mode modal bank and a parameter search, are in TODO.md.