Skip to content
Sign in

/docs · General · Practical

Is my audio upsampled?

One call per file and one field to read: bandLimit.upsampled says yes, no or cannot-say, with the reason in plain words. What to do with each answer, how often each came out in our tests, and what it cannot see.

Updated Nov 10, 2026

View as Markdown


Who this is for

You build or buy speech for a dataset that should be wideband, and you want to know which files only look that way. The sample rate in a header says how a file is stored, not what is in it: a telephone call stored at 16 or 48 kHz still stops near 4 kHz.

This page gives you one call, one field to read, and what to do with each answer. You do not need to know how the engine decides: the limits are in the answer itself.

1. Call it

Add bandLimit to a request to the signal analysis. For one local file with curl:

curl -s "https://audiolab.tools/v1/signallab?bandLimit=true" \
  -H "Authorization: Bearer $AUDIOLAB_KEY" \
  -F file=@recording.wav

For a file at a public URL, send JSON instead:

curl -s https://audiolab.tools/v1/signallab \
  -H "Authorization: Bearer $AUDIOLAB_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/recording.wav", "bandLimit": true}'

With the Python client (pip install audiolabtools), one file, then a folder 20 files at a time into a CSV:

import csv
from pathlib import Path

from audiolabtools import AudioLab

lab = AudioLab()  # reads AUDIOLAB_KEY

band = lab.signal("recording.wav", bandLimit=True)["bandLimit"]
print(band["upsampled"], band["reason"])

files = sorted(str(p) for p in Path("recordings").glob("*.wav"))
with open("upsampled.csv", "w", newline="", encoding="utf-8") as f:
    out = csv.writer(f)
    out.writerow(["file", "upsampled", "ratio", "reason"])
    for i in range(0, len(files), 20):
        chunk = files[i : i + 20]
        answer = lab.batch(chunk, endpoint="signallab", bandLimit=True)
        for name, r in zip(chunk, answer["results"]):
            b = (r.get("data") or {}).get("bandLimit") or {}
            out.writerow([name, b.get("upsampled", "error"), b.get("ratio"),
                          b.get("reason", r.get("error"))])

In an AI assistant with the AudioLab MCP server (version 0.5.1 or later), index_signal takes bandLimit: true, and so does analyze_batch with endpoint: "signallab/index", for 20 files at a time. Ask it, for example: “Which of these recordings were upsampled?”

Every file is one call and is uploaded to the API, hosted in the EU; only numbers come back. For a large folder, the dataset audit has a full script that paces itself, can sample, and picks up where it stopped.

2. Read one field

Read bandLimit.upsampled. It is yes, no or cannot-say. Next to it, bandLimit.reason says the same in plain words, with the bandwidth and the sample rate: copy it into your report as it is. This is the answer for a recording we brought down to 8 kHz and back up to 16 kHz:

{
  "bandLimit": {
    "upsampled": "yes",
    "status": "validated",
    "reason": "The band stops at 4.3 kHz in a 16 kHz file: the audio was probably at 8 kHz or less, as in a telephone call, before it was stored at this rate. In study UP-1, 0.1 to 1.8% of untouched recordings read the same way.",
    "basis": [
      {
        "check": "telephone-origin (UP-1)",
        "status": "validated",
        "result": "fires"
      },
      {
        "check": "band-limit ratio (UP-2)",
        "status": "proxy",
        "result": "band-limited",
        "ratio": 0.537
      }
    ],
    "bandwidthHz": 4296.9,
    "sampleRate": 16000,
    "ratio": 0.537,
    "verdict": "band-limited",
    "reasonCode": "ratio-below-L"
  }
}

status is validated only for a yes. basis lists each condition the answer rests on, with its own status, so you can see why. You do not need it to act.

3. What to do with each answer

upsampledWhat it meansWhat to do
yesThe band stops under 5 kHz in a file of 16 kHz or more, for at least 2 seconds: the audio was probably at 8 kHz or less, as in a telephone call, before it was stored at this rate. Status validated (study UP-1).Do not use it as native wideband.
noThe band reaches half the sample rate: no sign of upsampling. Status proxy (study UP-2).Nothing to do.
cannot-sayEverything else: a band that stops early without the telephone pattern, a band between 80 and 90 percent of half the sample rate, a file under 2 seconds, a tonal spectrum, or silence. The reason says which.Set it aside. If your dataset may only hold native wideband, leave it out or listen to it. If you need a number to filter on, bandLimit.ratio is the bandwidth over half the sample rate.

Why a band that only stops early is not a yes: a narrow source looks the same. In study UP-2, a LibriSpeech speaker recorded at its own rate read as band-limited, and so would a microphone or a filter that cuts the top.

4. How often each answer came out in our tests

We read the recordings of both studies again with the decision, on 10 November 2026. The two rules were fixed before those studies ran, and how they combine into one answer was fixed before this reading, so this is a description of how the three answers fall, not a new test. The UP-1 recordings were read twice, at their own rate and decoded at 48 kHz, so the counts are readings, not files.

Kind of recordingReadingsyesnocannot-say
Through an 8 kHz telephone codec and back to 16 or 48 kHz (UP-1: G.711, AMR, GSM)4,6924,608 (98.2%)084 (1.8%)
Through a telephone codec (UP-2: AMR, G.711, GSM)9090 (100%)00
Upsampled from 4 kHz450443 (98.4%)07 (1.6%)
Upsampled from 8 kHz450436 (96.9%)014 (3.1%)
Upsampled from 11.025 kHz4501 (0.2%)0449 (99.8%)
Upsampled from 16 kHz to 44.1 or 48 kHz3,4285 (0.1%)03,423 (99.9%)
FLEURS speech at its own 16 kHz1,08214 (1.3%)1,032 (95.4%)36 (3.3%)
LibriSpeech speech at its own 16 kHz1500141 (94.0%)9 (6.0%)
Speech cut off at 6 kHz, at its own 16 kHz78217 (2.2%)1 (0.1%)764 (97.7%)
Speech through a lossy codec (MP3 32 to 128 kbit/s, AAC 48, Opus 16)3000130 (43.3%)170 (56.7%)

What that means for you:

  • No upsampled reading got a no: 0 of 9,560.
  • yes finds the telephone pattern and upsampling from 4 or 8 kHz. Upsampling from 11.025 kHz or more comes out cannot-say, almost never yes. So read cannot-say as “look at this”, not as “probably fine”.
  • On speech at its own rate, a yes was wrong for 14 of 1,082 FLEURS readings (1.3%) and none of the 150 LibriSpeech readings. Speech cut off at 6 kHz read yes in 17 of 782 (2.2%).

What it cannot see

  • Real telephone lines, VoIP codecs (Opus, G.722) and Bluetooth headsets. UP-1 used telephone codecs we ran ourselves.
  • Native 44.1 and 48 kHz recordings: how often they would read cannot-say is not measured. A 44.1 kHz source stored at 48 kHz reads no; the rule cannot see that step.
  • An upsampler that makes up high frequencies of its own, such as a model that restores the band, reads no.
  • It names no source rate, apart from the telephone origin.

The two studies, with their pre-registration and results, are on the studies page. What the band limit was tested on is also on its page.

Next in General docs

Read a response: what each field means and how far to trust it →

A real answer from the API, field by field: what it says, its status and margin, and what you may and may not conclude from it.

More in General docs