Ground truth · the limits of an instrument

Loud Compared to What

A sound level meter reports decibels of pressure in the air. A browser reports decibels relative to the full scale of your sound card, which is a number inside your hardware and not a pressure in your room. This page implements ITU-R BS.1770-5 gated loudness, shows it recovering the Recommendation's own published calibration value and three further fixed answers, and only then plays a tone probe through your speaker and listens to it come back. It hands you four measurements of your own hardware: how far the reading of one identical tone moved between the start and the end of a single capture, how far from flat your speaker-room-microphone loop is, what one calibration constant would get wrong between 250 Hz and 4 kHz, and how much room your gear has between its own noise floor and the Recommendation's calibration point. The first three could each come back at zero, and the page says so in those words when they do. On headphones, on a muted speaker, or with the microphone refused, it hands you none of them and names the test that stopped it.

There is a decibel your browser can give you and a decibel it cannot. The one it can give you is written dBFS, decibels relative to full scale: the largest number the audio path can carry. The one it cannot give you is dB SPL, decibels relative to twenty micropascals of pressure in the air. Between them sits a conversion constant that depends on the microphone element, its preamplifier, its housing, the phone case, and every gain stage in between, and no browser API exposes it. So a web page that answers "how loud is my room" in sound-pressure decibels is supplying that constant from somewhere it does not name, and what follows is about how badly one number would do that job on your own hardware.

That much is a fact about the interface, not a discovery, and this page marks it as one wherever it says it. What is not definitional, and what this page actually measures, is how badly a single conversion constant would fail on your own hardware even if somebody handed you one. That is four numbers, three of them capable of coming back at zero, none of them published anywhere, because nobody has measured your room.

Before it measures you, it measures four things whose answers are already fixed

You have no second instrument. You cannot check a page that tells you your room is at some number of decibels, and a page like that is one hard-coded constant away from being a lie nobody can catch. So the estimator below is made to prove itself first, on four recordings that ship with this page, in front of you, before it is allowed to touch your microphone. It runs whether or not you ever grant it.

The anchor is the Recommendation's own calibration signal. ITU-R BS.1770-5 says, of the algorithm it specifies:

If a 0 dB FS, 1 kHz (997 Hz to be exact, see Notes 1 and 2) sine wave is applied to the left, centre, or right channel input, the indicated loudness will equal −3.01 LKFS.

NOTE 1 – The constant −0.691 in equation (2) cancels out the K-weighting gain for 997 Hz.

Recommendation ITU-R BS.1770-5 (11/2023), Annex 1

So: generate that exact signal from a closed form, hand it to the estimator, and the estimator must say -3.01. Three more recordings follow it, each with a different known answer, and each chosen to kill a different way of being wrong. The four answers span 30 decibels, so no single constant satisfies them.

The slate

running the four specimens…
specimenexpectedwhere the expected value comes from
997 Hz at 0 dBFS-3.01 LKFS printed in BS.1770-5, quoted above
997 Hz at -30 dBFS-33.01 LKFS the same printed value, moved by the Recommendation's own statement that 1 dB of level is 1 LKFS of reading
40 Hz at 0 dBFS-9.2682 LKFS closed form over the printed Table 1 and Table 2 coefficients
997 Hz, quiet / loud / quiet-13.3243 LKFS closed form over the Recommendation's own two-stage gating rule

The estimator is one function. It is handed a block of samples and a sampling rate and nothing else: not which specimen it is looking at, not what the answer is meant to be, not whether the signal came off this server or out of your microphone. There is no branch in it that could tell. That is the whole basis on which anything below is worth reading.

The calibration value, re-derived

Quoting a standard is not checking it. The Recommendation also prints the coefficients of the two filters that make up its K-weighting, and from those the calibration value follows in closed form, with no measurement anywhere in it:

computing…

Computed in your browser, on load, from the coefficients in the table below. The Recommendation prints -3.01; the closed form lands on -3.010286, and the difference is the two decimal places the Recommendation chose to print.

What a constant would do to this slate

A claim that a control could catch a fake is worth less than watching it catch one. Below is the same four-specimen slate, run through an estimator that ignores its input entirely and always returns the anchor's published value with a confident error bar.

An estimator that always answers -3.01

It passes the anchor. It has to: the anchor's answer is what it returns. It dies on all three controls, which is exactly what a control is for.

And what each control is actually protecting

A constant is the easy fake. The dangerous fakes are the plausible ones: an implementation that measures a real level but skips a stage of the Recommendation. Those still pass the anchor. Here is the same slate run through four of them, each with one stage of the Recommendation taken out and nothing else changed. The K-weighting comes out as a unit: both filters, and the constant that exists only to compensate for them.


    

Read the first row. An estimator that skips the K-weighting entirely and reports gated mean square straight in dBFS passes the anchor, passes the anchor moved 30 dB down, and passes the two-level specimen that exists to test the gating. At 997 Hz the K-weighting gain and the Recommendation's -0.691 offset cancel by design, to K(997) - 0.691 = 0.000014 dB, and all three of those signals are 997 Hz, so on them the two estimators are the same instrument. On the three shipped files they land within five millionths of a decibel of each other; the rest of that 0.000014 is the filter's start transient, and the gap grows towards it as the tone lengthens. The anchor cannot see that bug. Only the 40 Hz specimen can, and it misses by 6.26 dB. That is the whole reason a page like this needs more than one known answer.

Now read the last row, which is the one this page would rather you did not skip. An estimator that drops the Recommendation's -70 LKFS absolute gate and keeps only the relative one is not caught by this slate at all. It arms the instrument. The reason is a property of these four specimens rather than a property of the estimator: the quietest gating block anywhere on the slate is the two-level control's quiet section, at about -53 LKFS, so there is nothing under -70 for that gate to exclude and removing it moves no answer here by anything. A slate is only ever as good as the differences its specimens can make, and this one has that hole in it.

The one frequency where the anchor is blind

You can operate that yourself, right now, without granting anything. Pick a frequency. The page generates a one-second sine at digital full scale, hands it to the armed instrument (the same estimate() the four specimens went through, and the same one your microphone would go through), and hands the identical signal to the version with the K-weighting skipped. Then it prints both.

Third-octave centres from 20 Hz to 20 kHz, plus the Recommendation's own 997 Hz. Nothing is played and nothing is recorded: the tone exists only as numbers.

measuring…

The filters, regenerated from a prototype and checked against the printed table

The Recommendation prints its coefficients for one sampling rate, and says so:

These filter coefficients are for a sampling rate of 48 kHz. Implementations at other sampling rates will require different coefficient values, which should be chosen to provide the same frequency response that the specified filter provides at 48 kHz.

Recommendation ITU-R BS.1770-5 (11/2023), Annex 1, after Table 1 and after Table 2

Your browser's audio clock may well not be 48 kHz, so this page has to do what that paragraph asks. It does not print the analogue prototype the tables came from, so the prototype had to be reconstructed. A reconstruction taken on trust would be a hole straight through the middle of everything above, so the page does not take it on trust: it regenerates the printed tables from the reconstruction, in your browser, and shows you the residual.

At 48 kHz the page uses the printed values verbatim regardless. At 44.1 kHz, the rate most Apple hardware runs at, the regenerated filter's worst disagreement with the published 48 kHz curve anywhere between 20 Hz and 20 kHz is 0.0053 dB, and that number is carried into the error bar on every reading this page makes at that rate.

This is where the slate does its hardest work, and it is worth saying out loud. The four specimens ship as 48 kHz files, but the browser decodes them into the same AudioContext your microphone would arrive in, which resamples them to whatever rate your hardware actually runs at. So the slate at the top of this page was measured at your rate, through the regenerated filters, not at 48 kHz through the printed ones. That matters, because using the printed 48 kHz coefficients verbatim at some other rate is the commonest way to get this algorithm wrong, and it is a quiet way: nothing crashes, every number stays plausible. The anchor catches it. An implementation that did that would read the anchor at -2.8013 instead of -3.0103 at 44.1 kHz, -1.6777 at 32 kHz and -3.6597 at 96 kHz, all three outside the tolerance, so the slate would refuse to arm and this page would report nothing about you.

The redesign is not free of error either, and the error grows as the rate falls. Worst disagreement with the published 48 kHz curve: 0.0053 dB at 44.1 kHz, 0.032 dB at 32 kHz, 0.082 dB at 22.05 kHz, 0.152 dB at 16 kHz. That last one is wider than the 0.1 LU tolerance the specimens are judged against, and a specimen whose honest error bar is wider than its own tolerance is failed rather than passed. So on a device whose audio clock runs at 16 kHz, which a Bluetooth headset in call mode will do, this page arms nothing and measures nothing, and says which specimen stopped it. That is the correct direction to fail in, and it is still a reader for whom this page is dead.

Now your own hardware

The instrument is armed. What follows plays 11.8 seconds of tones out of your speaker and records your microphone at the same time, so what comes back has been through your speaker, your room, and your microphone in series. Nothing is uploaded. The recording is a block of numbers in this tab's memory for the length of the measurement, and it is gone when you close it.

This plays sound, including one deliberately loud sweep near the end. Take headphones off: on headphones the microphone hears nothing and the page will refuse rather than guess. Start at a moderate system volume. There is a stop control below and it works at any point.

The probe

One second of silence to measure your noise floor, a short chirp so the page can find where everything is in the recording, a 997 Hz reference tone, seven tones at the same digital amplitude from 125 Hz to 8 kHz, a loud sweep, and then the same 997 Hz reference tone again.



    

Any constant gain cancels out of the arithmetic: the first three results below are each a difference of two readings taken at the same digital amplitude, and the fourth never uses the probe at all. What the level changes is physical, and there are three of those. Too quiet and a band falls under the presence test; too loud and your speaker starts distorting; and a gain stage that only engages above some level will sit still when the probe is quiet and move when it is loud. The first two are reported by name rather than absorbed. The third is not separable at all, and it is why the first result is a measurement of this chain at this level rather than a constant of your device. This is the largest input to these numbers that the page cannot read. The acoustic level is this slider multiplied by your operating system's own volume, and no browser API reports the second one. Turn the system volume down far enough and a chain that compresses will report that it did not.

not measured

Grant the microphone when your browser asks. If you do not, nothing above changes and nothing below is invented.


    

Gain excursion, same tone, 9 s apart

Loop non-flatness, peak to peak

What one constant gets wrong, 250 Hz against 4 kHz

Usable window, your noise floor to the 997 Hz calibration point

Your loop, band by band

Each bar is one band's reading minus what a perfectly flat loop would have read for the same digital amplitude, with the K-weighting the Recommendation specifies already taken out. What is left is your own loop's gain at that frequency, in decibels, relative to its own mean. A flat loop is a flat row of zeros.

"Just turn the automatic gain control off"

This is the sophisticated objection and it deserves a real answer, not a caveat. It goes: the automatic gain control is a red herring. Ask getUserMedia for autoGainControl: false and it goes away. Then measure the device once against a real meter, store the offset, and you have your conversion constant. That is a serious objection, and it is more or less how a calibrated meter app would have to work.

This page asks for those flags. It asks for autoGainControl: false, echoCancellation: false and noiseSuppression: false on every capture, and it prints what your browser actually returned, up there in the grey block, because a browser is allowed to say no. Then it measures the gain anyway, twice, nine seconds apart, on the identical tone, with a loud sweep in between. That is the first result above and it is a difference of two readings from one recording, so it does not care what the flags said.

Twice, and not once across the whole recording, and the reason belongs on this page. The Recommendation's relative gate discards any 400 ms block sitting more than 10 LU below its own window's level. So a large gain change measured inside one window is partly hidden by the standard's own gate: put a -20 dB step halfway through a six-second tone and the gated answer is -3.2308 LKFS, against -5.9774 ungated, because 27 of the 57 blocks are the quiet half and the gate throws all 27 away. The quiet half is nowhere in the answer. Measure the two halves as separate windows and the step comes back exactly: -3.0103 then -23.0103, a difference of -20.0000 dB. The two-level specimen at the top of this page is that same gate doing that same thing on purpose, to 54 of its 97 blocks. An instrument built for programme loudness has a blind spot for exactly the effect this page is trying to measure, and the way round it is to hand it two windows instead of one.

Flag against measurement, from one capture. If your device reports autoGainControl=false and the two readings still differ, then something moved that the flag does not reach, and there are three places it could have happened. It could be in the capture chain, below anything the browser can turn off: the codec, the driver, the phone's audio DSP. It could equally be in the playback chain, and this page cannot tell those apart: a small speaker driven hard for several seconds heats up and its protection limiter pulls the level down, and the sweep in the middle of the probe is there precisely because it is loud. Or something in your room changed during those nine seconds. So the first result is how far the reading of one identical tone moved, given what this page played in between, and it is not an attribution to your microphone. One capture cannot separate the three and the page does not pretend to. You can narrow it, though: run it twice in a still room. A room effect will not repeat itself; a gain stage that responds to the loud sweep will give you the same sign and something close to the same size, every time. If the two readings agree instead, your chain held, and the page says that.

The second and third results answer the rest of the objection, and they are the ones that do not go away on good hardware. A conversion constant is one number. It converts every frequency by the same amount. Your loop does not: the seven tones went out at the same digital amplitude and came back at levels the chart shows. The second result is the spread of that, peak to peak. The third narrows it to two bands four octaves apart, 250 Hz and 4 kHz, picked before any measurement as the widest pair an ordinary laptop speaker can be expected to reach, and prints the difference. That is the error a single-constant calibration makes between a sound made mostly of low frequencies and a sound made mostly of high ones, measured on your hardware, not asserted.

And the fourth result is the scale of the question. Its bottom is yours, and it is measured, in the silent second before the probe starts. Its top is not measured, and it is worth being exact about what it is: it is the reading of the Recommendation's own calibration signal, a 997 Hz sine at digital full scale, computed here from the Recommendation's coefficients at -3.010286 LKFS. It is the one level on this scale that a standard prints, which is why the window is quoted up to it.

Part of that fourth number is the instrument, and the page has to say which part. The estimator keeps only the gating blocks sitting above the Recommendation's -70 LKFS absolute gate and averages those, so any floor it reports is above -70 LKFS by construction, and a room quieter than that comes back as a refusal rather than as a smaller number. The widest window this page can ever print is therefore about 67 dB, the distance from that gate up to the calibration point. If your gear is better than that, what you are reading is the estimator's limit and not your hardware's, and the same -70 that keeps this figure bounded away from zero is also a lid on it. The honest instrument for a noise floor is an ungated mean square, which is a different estimator, and this page has exactly one.

It is not the top of the scale, and the tempting sentence here is false. "Nothing that is not clipping can read above a full-scale sine" is the obvious thing to say, and this page's own tone bench refutes it four screens up. K-weighting is a deliberate high-frequency lift, reaching +4.04 dB asymptotically, so a clean unclipped sine at digital full scale reads above -3.01 LKFS at every frequency over 997 Hz. Drag the bench to 20 kHz: the armed instrument prints 0.342 LKFS, which is 3.35 dB above the ceiling, and refuses nothing, because nothing is clipping. The sentence is only true with at 997 Hz on the end of it, which is the qualifier the calibration value itself carries, and the window below is a window up to the calibration point rather than up to the rail.

What the loop is, and what it is not

The chart is not your microphone's frequency response. It is your speaker, multiplied by your room, multiplied by your microphone, and there is no way to separate the three from inside a browser. That is not a caveat to bury under the number, it is the same fact as the rest of the page, arriving one level up: with a single instrument and no reference, every property you measure is a property of the whole chain. Move the laptop and the chart changes. That is honest, and it is why the number the page quotes is a spread and not an absolute.

What this page will not print

It will not print a figure in dB SPL, and it does not offer a field where you could type a calibration constant and have it produce one.

Labelled as what it is: this part is definitional, not a result. The Web Audio API hands a page sample values on a scale whose maximum is called full scale. No method anywhere in the browser platform reports how many pascals of pressure one unit of full scale corresponds to on the device it is running on, and no measurement made inside the browser could ever recover it, because every measurement is made in those same units. That sentence could not be contradicted by anything measured above and it is not offered as though it could. It is what the unit's own name says: LKFS expands to loudness, K-weighted, relative to nominal full scale, and the last four words are the refusal.

What is a result is everything the four panels above report. Three of the four could have come back at zero: the gain excursion, the loop non-flatness, and the 250 Hz against 4 kHz figure. On a machine with a fixed-gain capture chain and a flat loop they very nearly will, and each of those three carries a sentence that says so in those words when it happens. The fourth cannot, and the page says that too rather than selling it as a null result: the usable window is a distance from your noise floor up to a fixed calibration point, and the estimator refuses a window whose every block sits under -70 LKFS, so the width is bounded away from zero by construction. Zero there would not mean the effect is absent. It would mean the hardware is useless.

The five ways this page refuses

There are three more, inside the estimator itself, and they apply equally to a specimen and to you: a window too short to hold three gating blocks, a window whose every block sits under the Recommendation's -70 LKFS absolute gate, and a window that is flat-topped at the rail, where the level that arrived is no longer the level that was recorded.

One thing the Recommendation says about its own algorithm

The probe is pure tones, and the standard this page is built on is explicit that pure tones are outside what it was validated for:

It should be noted that while this algorithm has been shown to be effective for use on audio programmes that are typical of broadcast content, the algorithm is not, in general, suitable for use to estimate the subjective loudness of pure tones.

Recommendation ITU-R BS.1770-5 (11/2023), Annex 1, after Table 3

That sentence is why this page never tells you how loud anything is. It uses LKFS as what it also is: a completely specified, reproducible, full-scale-referenced level scale with a published calibration point, which is precisely the property needed here. No claim is made anywhere on this page about how loud anything sounds to a person, and every result above is a difference of two readings on that one scale, where any subjective interpretation would cancel even if one were being attempted.

Not a hearing-safety instrument

Nothing here is an exposure measurement. There is no dose, no time-weighted average, no threshold, and no statement about whether any sound is safe. A page that cannot report absolute pressure cannot say anything at all about hearing risk, and this one does not try.

Every free choice this page made

choiceset towhy, and what it costs
specimen tolerance an allowance chosen here, not a published one, and the page does not borrow authority for it: it covers the 16-bit quantisation of the shipped files and your browser resampling them to its own rate. At 48 kHz the four recoveries land within 0.003 LKFS of their truths, so it decides nothing. The two-level specimen is wider because its own honest interval is wider, and a specimen whose error bar exceeds its tolerance is failed by the kit rather than passed
filter coefficients the Recommendation prints one sampling rate and asks for the same response at others; the reconstruction it needs, and its residual against the printed digits, are in the table above
gating block the Recommendation's own, not ours
gates the Recommendation's own two-stage gate
channels the capture node is mono, so whatever your device hands over is mixed to one channel before anything is measured; the multi-channel weights of Table 3 are not exercised and nothing here claims them. It is a choice with a size: the Recommendation sums weighted channels, so the same acoustic field measured as stereo would read 3.01 LKFS higher, and LKFS values are not comparable between readers unless the channel count is the same. Every result above is a difference of two readings taken the same way, where that cancels
minimum blocks below this a window is refused rather than reported; it also sets the shortest segment the probe can use
error bar a bootstrap over the surviving gating blocks, so an uneven window honestly reports a wider interval than a steady one
systematic floor the Recommendation prints its calibration value to two decimals, so nothing here is meaningful below half of the last one
tone presence above the local noise, or the band is dropped by name instead of estimated
distortion limit second harmonic relative to the tone; above it the speaker is distorting and the band is dropped
clipping test flat-topping, not loudness: a full-scale sine is not clipped and must not be refused as though it were. The tolerance on "equal" has to be tight, because near its own peak a slow sine moves less than one sixteen-bit code per sample without any clipping at all
probe amplitude quiet enough that most speakers stay linear; the distortion test above catches the ones that do not
bands 125 Hz is included and expected to fail on laptop speakers; that failure is a reported result, not a gap
segment window the trim discards the ramps and the room's tail from the band before. Each segment is then handed to the estimator as its own measurement interval, so its gating blocks start at the segment and no block straddles a boundary between two frequencies. One grid laid across the whole capture would mix each band with its neighbours and compress the spread. The trimmed 600 ms is exactly three gating blocks, which is the shortest window this page will report at all
alignment floor below it the page cannot place the segments and reports none of them
spread quorum fewer surviving bands than this and no peak-to-peak figure is quoted

What is checked, and how you can check it

The estimator, the filter design, the probe waveform and the whole second layer live in one file, /strata/no-absolute-loudness/lkfs.js, which this page loads and the verifier runs directly, so there is no second copy to drift. The verifier recomputes every fixed number on this page from the Recommendation's coefficients, regenerates all six WAV files and compares their SHA-256 hashes, runs the four broken estimators and requires each to be caught by the specimen that is supposed to catch it (or, for the fourth, requires the slate to be shown missing it), alters the third decimal place of one published coefficient and requires the slate to break, runs the whole slate again at 44.1, 32, 22.05 and 96 kHz and requires it to arm there and then builds the naive implementation that uses the printed 48 kHz coefficients verbatim at those rates and requires the anchor to catch it, and asserts each refusal fires on a signal built to trigger it. It also runs its own shareable-surface predicates over strings built to trip them, because a check on the honesty of a social card is worth nothing until it has been seen to fail. It prints 237/237 checks passed.

node research/no-absolute-loudness/verify-no-absolute-loudness.mjs
node scripts/check-live-sensor.mjs --only=no-absolute-loudness

The second command is the one that matters most, because everything else could be true of a page whose microphone code never runs. It launches a real browser, replaces the microphone with a WAV whose answer is known, and asks this page's own getUserMedia path what it measured. Then it does it again with a second WAV built exactly 12 dB quieter, and requires the answer to move by 12 dB. A page that quietly returned its anchor no matter what it heard would pass every other check on this site and fail that one.

The live-sensor gate injects a file rather than making a sound, so it proves the capture, the alignment, the segmentation and the estimator. It cannot prove that a real speaker was driven, because there is no speaker in a headless browser. What the gate injects is the same waveform this page plays, generated by the same function.

The slate above is stated in full by the instrument itself:


  

What is uncertain, and what could still be wrong

Source: Recommendation ITU-R BS.1770-5 (11/2023), Algorithms to measure audio programme loudness and true-peak audio level, Annex 1. Published free of charge by the ITU at itu.int. This page quotes five sentences, in three blockquotes, and the two coefficient tables it had to implement, and does not redistribute the document.