WaveLens Docs
User Guide

wavscan

A command-line tool that answers one question: at this exact moment in this WAV file, what frequencies are present, and how loud is each one? Point it at a timestamp or a sample index and it reports the dominant frequency plus the next loudest distinct peaks nearby — a measurement, not a pass/fail verdict.

wavscan 0.7.0-beta · single self-contained executable · no external runtime dependencies

Overview

wavscan windows a signal around one instant, computes its magnitude spectrum, and reports what's actually there — closer in spirit to ffprobe than to wavelens or wavsummary. There's no reference file, no tolerance, no --config, and no exit-code-as-pass/fail semantics: a query either succeeds (prints what it found, exit 0) or fails on a genuine usage error (bad file, out-of-range query point, exit 1). Finding zero peaks above the noise floor is a valid, non-error result.

It's the tool to reach for when you already know something is wrong at a specific point in a file (from a wavelens analyze report's worst-sample or worst-bin fields, or just from listening) and want to see the actual frequency content there, or when you're spot-checking a generated fixture's content without running a full comparison.

Requirements

  • A valid, active WaveLens license file (see License below)
  • A 16-bit or 24-bit PCM WAV file — mono, stereo, or multichannel

License

wavscan requires an active WaveLens license to run, checked before the file is read. It ships as part of the WaveLens toolset, not as a separately licensed product — the same license file that activates wavelens activates wavscan. There are three ways to point it at your license file, checked in this order — the first one that's set wins.

1. Explicit path with --license

# explicit --license overrides both the env var and the next-to-executable default
wavscan --license /path/to/wavelens.lic --input take.wav --at 1.5

2. WAVELENS_LICENSE_FILE environment variable

# bash / zsh
export WAVELENS_LICENSE_FILE=/path/to/wavelens.lic
wavscan --input take.wav --at 1.5

3. Zero configuration — no flag, no env var

Place a file named exactly wavelens.lic in the same folder as the wavscan executable.

i

Don't have a license file, or need a renewal? Contact your WaveLens distributor or administrator.

Quick start

Query a WAV file at a specific timestamp:

wavscan --input take.wav --at 2.5
# output:
query time     2.500s (sample 120,000 @ 48000Hz)
window         4096 samples (85.3ms, resolution 11.7Hz/bin)

channel 0:
  dominant peak  1002.0 Hz  @  -6.6 dB

  top 5 peaks:
    1.  1002.0 Hz    -6.6 dB
    2.  2004.0 Hz   -24.1 dB
    3.  3006.0 Hz   -31.8 dB

Every channel in the file is analyzed and reported by default. Add --channel N to restrict the query to one channel, or --json/--csv for machine-readable output — see Output formats.

Command syntax

wavscan --input <in.wav> (--at <SECONDS> | --at-sample <N>)
        [--license L]
        [--window-samples 1024|2048|4096|8192|16384|32768]
        [--top N] [--min-separation-hz HZ] [--noise-floor-db DB]
        [--channel N] [--json] [--csv] [--html FILE]
OptionDefaultDescription
--input FILErequiredWAV file to query (16/24-bit PCM only).
--at SECONDSone ofQuery point as a timestamp. Mutually exclusive with --at-sample — exactly one of the two is required.
--at-sample None ofQuery point as an exact sample index.
--window-samples N4096Must be one of 1024, 2048, 4096, 8192, 16384, 32768.
--top N5May return fewer if fewer peaks survive the separation/noise-floor gates.
--min-separation-hz HZ4 × bin widthBin width is sample_rate / window_samples, so the default is resolved per query.
--noise-floor-db DB-80.0Candidates below this level are never picked.
--channel Nall channelsRestrict to one channel, 0-indexed. Omitted means every channel in the file is analyzed and reported.
--jsonoffPrint JSON instead of text. Mutually exclusive with --csv.
--csvoffPrint flat CSV instead of text. Mutually exclusive with --json.
--html FILEoffAlso write a self-contained HTML report — see --html report. Independent of --json/--csv.

Query point: --at vs --at-sample

--at SECONDS is a timestamp, rounded to the nearest sample against the file's own sample rate. --at-sample N is an exact sample index, useful when you already have one from another tool's output — e.g. a wavelens analyze report's worst_sample or worst_event_start_frame field. Exactly one of the two is required; passing both, or neither, is a usage error.

The query point must land strictly inside the file — at or past the file's own duration is rejected, not clamped, since a silently-clamped query would report content from a different point than the one asked for.

How peaks are picked

A window of --window-samples samples is centered on the query point (zero-padded at either edge if the query sits near the start or end of the file), Hann-windowed, and converted to a magnitude-dB spectrum — the same window function and dB scaling wavelens's own Spectral Fidelity primitive uses, so numbers from the two tools agree on the same content.

From that spectrum, up to --top peaks are kept, subject to two independent gates:

  • Noise floor — a candidate below --noise-floor-db is never picked, regardless of rank.
  • Minimum separation — two candidates closer together than --min-separation-hz are treated as one peak (the louder one wins), so a single tone's spectral leakage across adjacent bins doesn't get reported as several distinct peaks.

Peaks are local maxima only, ranked by level, most prominent first. A query may legitimately return fewer than --top peaks — a quiet or narrowband signal simply doesn't have that many distinct peaks above the floor. Returning 0 peaks is a valid result, not an error.

Output formats

Exactly one of text (default), --json, or --csv is printed to stdout — never mixed together, so a script piping the output doesn't have to filter out anything extra.

Text (default)

query time     0.500s (sample 24,000 @ 48000Hz)
window         4096 samples (85.3ms, resolution 11.7Hz/bin)

channel 0:
  dominant peak  1002.0 Hz  @  -6.6 dB

  top 1 peak:
    1.  1002.0 Hz    -6.6 dB

--json

{
    "input_file": "take.wav",
    "sample_rate": 48000,
    "channels": 2,
    "query_time_s": 0.500000,
    "query_sample": 24000,
    "window_samples": 4096,
    "window_duration_ms": 85.333333,
    "resolution_hz": 11.718750,
    "min_separation_hz": 46.875000,
    "noise_floor_db": -80.000000,
    "results": [
        {
            "channel": 0,
            "dominant_peak": { "frequency_hz": 1001.9531, "level_db": -6.6288 },
            "top_peaks": [
                { "rank": 1, "frequency_hz": 1001.9531, "level_db": -6.6288 }
            ]
        }
    ]
}

The window/resolution fields (window_samples through noise_floor_db) are top-level, once per query — they depend only on the query parameters, not on any one channel's content. dominant_peak is null, and top_peaks an empty array, when nothing survived the gates for that channel.

--csv

channel,rank,frequency_hz,level_db
0,1,1001.9531,-6.6288
1,1,1001.9531,-6.6288

One row per peak, flat across all channels — no header block, no window/resolution metadata. Use --json instead when that metadata matters.

--html report

--html FILE writes a self-contained HTML page alongside whatever stdout format was chosen — one spectral chart per channel, with the picked peaks marked and labeled directly on the curve. It's independent of --json/--csv; combine --html report.html --json to get both a page to look at and data to script against from the same query.

wavscan --input take.wav --at 2.5 --html scan.html --json > scan.json

The confirmation line (wrote scan.html) is printed to stderr, not stdout — so a pipeline reading stdout for --json/--csv never has to filter out a trailing non-data line.

Exit codes

CodeMeaning
0The query ran successfully — including a query that found 0 peaks above the noise floor, which is a valid result, not a failure.
1A usage error — bad arguments, a missing/unreadable input file, an out-of-range query point or channel, or an invalid license.
i

wavscan has no pass/fail concept, so its exit code is purely "did the query run," never "did the content look right." That question is wavelens analyze's job.

Common recipes

Inspect the exact point a wavelens report flagged

# worst_sample from a level_fidelity_channel_0_worst_sample field, say
wavscan --input dut.wav --at-sample 96044

Check one channel only, with a tighter noise floor

wavscan --input take.wav --at 3.2 --channel 0 --noise-floor-db -100

Higher-resolution query on a low-frequency signal

# bigger window = finer resolution_hz, at the cost of time precision
wavscan --input take.wav --at 1.0 --window-samples 16384

Script against the numbers directly

wavscan --input take.wav --at 2.5 --json \
  | python3 -c "import json,sys; d = json.load(sys.stdin); print(d['results'][0]['dominant_peak'])"

Errors & troubleshooting

MessageMeaning & fix
error: license INVALID -- no license found at '...'Check whichever of the three lookup mechanisms actually resolved — see License.
error: license EXPIRED -- license expired on ...Contact your administrator for a renewal.
error: --input is required--input is mandatory on every invocation.
error: exactly one of --at or --at-sample is requiredPass one, not both, not neither.
error: failed to read '...'The file doesn't exist, isn't valid WAV, or isn't 16/24-bit PCM.
error: --channel N is out of range ('...' has M channels)Check the file's actual channel count — N is 0-indexed.
error: query point (sample N) is at or past '...'s own durationThe timestamp/sample index falls at or beyond the end of the file. Not clamped — pick a point strictly inside it.
error: --window-samples must be one of 1024, 2048, 4096, 8192, 16384, 32768Only those six discrete sizes are accepted.
error: --json and --csv are mutually exclusivePick one.
error: unrecognized argument '...'Check the flag name against Command syntax.

FAQ

Does wavscan compare two files, like wavelens does?

No. wavscan takes exactly one input file and reports what's in it at one instant — there's no ref/DUT concept, no tolerance, and no pass/fail. Use wavelens analyze for comparisons.

Why did my query return fewer peaks than --top asked for?

Not an error — a window simply doesn't always have that many distinct peaks above --noise-floor-db once the minimum-separation gate merges nearby candidates. See How peaks are picked.

Do the frequency numbers match what wavelens analyze reports for the same content?

Yes — wavscan reimplements the identical Hann-window magnitude-dB spectrum wavelens's Spectral Fidelity primitive uses, so a peak found here at a given point should agree with what a wavelens analyze report shows for that same window.

Can I query multiple points in one invocation?

No — one query point per run. Script a loop over --at/--at-sample values if you need several, ideally with --json so each result is easy to collect.