/docs · VoiceLab · Practical
Read the audio before you transcribe
One call before the model: what the scene fields tell you about a file, and what they do not, with the numbers from our own transcription test.
A transcript can be wrong for reasons that were in the audio before the model saw it: a clipped upload, a second voice talking over the message, a speaker only a few dB above a loud room. A measurement before the model can show those. It cannot tell you whether the transcript will be right, and this page says where that line is, with numbers.
What the fields do not predict
We tested it, on 10 October 2026. Sixteen invented Dutch citizen reports (street and number, town, surname, phone number, email address), read by three synthetic voices in 13 conditions: clean, telephone band, street noise and babble at 10 and 5 dB, a television, four measured reverberation times, and combinations. That is 624 recordings and 1,248 transcriptions by Whisper small and Whisper large-v3-turbo, 48 per condition and model, so a margin of about 13 points.
- The SNR measures did not predict errors. For “street, number or town wrong”, the AUC of
worstPhraseSnrDb,segmentalSnrDbandsignalToNoiseDbwas 0.52 to 0.55 for both models. 0.50 is a coin flip. sceneIntelligibilityraised false alarms. It read not clear (borderline or buried) on 100 percent of the recordings with street noise at 10 and 5 dB and on 94 to 100 percent with babble, while Whisper small still got the location right on 58 to 67 percent of them (clean: 69) and large-v3-turbo on 71 to 85 percent (clean: 81). Over all recordings it flagged 58 percent; of the flags, 47 percent (small) or 25 percent (large) had a real location error.- What explained the errors was the words. On clean recordings the street, number and town came back right 69 percent (small) or 81 percent (large) of the time, an email address spoken as words 21 or 35 percent, a phone number 83 or 88, a surname 73 or 81. The choice of model weighed more than the audio: reverberation of 1.0 s with babble gave 17 percent for small against 54 percent for large.
The limits of the test: synthetic voices, Whisper only, a simulated telephone band, Dutch text, 48 recordings per condition and model. Real speakers, other recognisers and other languages are untested. So do not refuse a file on sceneIntelligibility or on an SNR alone. They describe the signal.
The call
POST https://audiolab.tools/v1/voicelab/qa with a Bearer key. Send the file as multipart/form-data (file), or as JSON with a public url. The response is the full VoiceLab analysis; the part that matters here, abridged, for a lecture with a second speaker 18 dB behind the message:
{
"sceneBackground": "program",
"sceneProgramScore": 0.92,
"sceneGapHarmonicShare": 0.33,
"speechToBackgroundDb": 15.2,
"sceneIntelligibility": "clear",
"sceneIntelligibilityScore": 0.81
}
Read it as a sentence: there is a second voice in the pauses (program, a third of the pause energy is moving pitch), the message sits 15 dB above it, and the foreground voice reads as followable by a listener. That says something about the signal, not about what a recogniser will do with it.
| Field | What it says | Values |
|---|---|---|
sceneIntelligibility | A proxy for whether a listener could follow the foreground voice (not for speech recognition) | clear, borderline, buried, or null under two measurable phrases |
sceneIntelligibilityScore | The same as a number | 0 to 1; clear from 0.65, borderline from 0.35 |
speechToBackgroundDb | Mean speech level minus the mean pause floor | dB; 3 reads as 0, 18 reads as 1 in the score |
sceneBackground | What fills the pauses | quiet, stationary (a machine), program (a second voice or music), unsteady, or null under a second of pause |
clipping.severity | Digital damage, easier to fix at the source than later | None, Low, Medium, High |
speechRatio | Share of the file that is speech at all | 0 to 1 |
Using it without refusing files
const KEY = process.env.AUDIOLAB_API_KEY;
async function check(url) {
const v = await fetch('https://audiolab.tools/v1/voicelab/qa', {
method: 'POST',
headers: { Authorization: `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ url }),
}).then((r) => r.json());
const notes = [];
if (v.speechRatio < 0.2) return { route: 'no-speech', notes }; // nothing to transcribe, do not bill minutes
if (v.clipping.severity === 'High') notes.push('clipped'); // damage at the source
if (v.sceneBackground === 'program') notes.push('second-voice-or-music'); // something is talking behind the message
if (v.sceneIntelligibility === 'buried') notes.push('voice-close-to-background');
return { route: 'transcribe', notes }; // attach the notes to the transcript, let a person decide
}
Two outcomes are safe to act on: a file with almost no speech, and a transcript that carries what the audio showed. Everything else is information to attach to the transcript, not a reason to refuse the file. The null case matters: a file with one phrase is not a bad file, it is an unmeasured one.
Example readings
These are example readings, not a calibration. The labels were set by hand on this material and have not been validated against listeners or against speech recognition. A lecture recorded in a hall with an audience, then mixed with the things that sit behind a message:
| Scene | Background | Speech over background | Intelligibility |
|---|---|---|---|
| Lecture, clean | unsteady (the room and the audience) | 17.1 dB | clear 0.94 |
| plus air handling at −20 dB | stationary, tone at 120.8 Hz | 15.4 dB | clear 0.85 |
| plus a second speaker at −18 dB | program | 15.2 dB | clear 0.81 |
| plus a club track at −18 dB | unsteady | 15.0 dB | clear 0.80 |
| plus a second speaker at −6 dB | program | 10.0 dB | borderline 0.45 |
And a room recording made through a phone, where the playback speakers only reached 5 to 7 dB above the room’s own floor: every take read buried, scores 0.12 to 0.25. Whether a recogniser would have coped with it was not tested.
The score is the weaker of two legs: the worst tenth of the per-phrase signal-to-noise ratio, and the speech over the pause floor, both mapped so that 3 dB reads 0 and 18 dB reads 1, then reduced for a reverberant room and for clipping. It is deliberately conservative: one bad stretch of phrases pulls the whole file down.
What the number is not
- Not a predictor of transcription accuracy. See the test above.
- Not an STI. It is a proxy built from levels, phrase by phrase. It tells you whether the voice sits far enough above what is behind it; it does not model a listener’s ear, and it has not been tested against listeners.
- Not a speech detector for music. The phrase detector reads sung vocals as speech, so a music file with a vocal can come back
buried. Read music onsceneBackgroundandspeechRatioelsewhere, or do not send it here at all. - Not measured on short files. Under two measurable phrases every scene field is
null. Treatnullas “not measured”, never as “fine” or “bad”. - Read after the first 0.4 s of each pause, so the tail of the last word does not count as background. Pauses shorter than one second do not count at all.
Backlogs and agents
For a backlog, POST /v1/batch runs the same route over up to 20 URLs in one call and returns one result per URL, in order. For an agent, the MCP tool analyze_voice returns the same fields for a url (or a local path with the npm package, which reads the file on your machine and uploads it to the API for measuring). The Free tier includes 1,000 calls a month, which is enough to see what share of your intake carries each note before you decide anything.
Want to see the same numbers by hand first? Drop a file on the workbench and open the Scene group in the Voice tab, or read the definitions and the status of every scene.* lens on /lenses.