
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
loudness-worklet
Advanced tools
A lightweight and efficient AudioWorklet for real-time loudness measurement in the browser, compliant with the ITU-R BS.1770-5 standard.
A loudness meter for the Web Audio API, based on the ITU-R BS.1770-5 standard and implemented as an AudioWorkletProcessor.
Install via npm:
npm install loudness-worklet
Use helper functions to create and load the worklet:
import { createLoudnessWorklet } from "loudness-worklet";
const worklet = await createLoudnessWorklet(audioContext);
or
import { LoudnessWorkletNode } from "loudness-worklet";
await LoudnessWorkletNode.loadModule(audioContext);
const worklet = new LoudnessWorkletNode(audioContext);
Import directly in your browser using jsDelivr or unpkg:
import { createLoudnessWorklet, LoudnessWorkletNode } from "https://cdn.jsdelivr.net/npm/loudness-worklet/+esm";
For strict Content Security Policy (CSP) environments restricting blob: or eval() via script-src and worker-src, or for offline integration, download the pre-built loudness.worklet.js payload.
loudness.worklet.js in your project directory (e.g., /public/).await audioContext.audioWorklet.addModule("loudness.worklet.js");
const worklet = new AudioWorkletNode(audioContext, "loudness-processor");
This example shows the easiest way to get started with the loudness-worklet by measuring the loudness of a screen share stream with audio (e.g., a YouTube tab).
<!DOCTYPE html>
<html>
<body>
<button>Share Screen</button>
<pre></pre>
<script type="module">
import { createLoudnessWorklet } from "https://cdn.jsdelivr.net/npm/loudness-worklet/+esm";
const button = document.querySelector("button");
const pre = document.querySelector("pre");
button.onclick = async () => {
// Get the display media stream with audio
const mediaStream = await navigator.mediaDevices.getDisplayMedia({ audio: true });
const context = new AudioContext();
// Create the loudness worklet node automatically
const worklet = await createLoudnessWorklet(context, {
processorOptions: {
interval: 0.02, // every 0.02s a message will be sent
capacity: 600 // 1 minute of history can be stored
}
});
// Create the audio node from the stream
const source = new MediaStreamAudioSourceNode(context, { mediaStream });
worklet.port.onmessage = (event) => {
pre.textContent = JSON.stringify(event.data, null, 2);
};
// Connect the nodes
source.connect(worklet);
};
</script>
</body>
</html>
You can measure the loudness of audio files using OfflineAudioContext.
import { LoudnessWorkletNode } from "loudness-worklet";
const input = document.querySelector("input");
input.addEventListener("change", async (event) => {
const file = event.target.files[0];
const arrayBuffer = await file.arrayBuffer();
const audioBuffer = await new AudioContext().decodeAudioData(arrayBuffer);
const { numberOfChannels, length, sampleRate } = audioBuffer;
const context = new OfflineAudioContext(numberOfChannels, length, sampleRate);
await LoudnessWorkletNode.loadModule(context);
const source = new AudioBufferSourceNode(context, { buffer: audioBuffer });
const worklet = new LoudnessWorkletNode(context);
worklet.port.onmessage = (event) => console.log("Loudness Data:", event.data);
source.connect(worklet).connect(context.destination);
source.start();
await context.startRendering();
});
Supports all kinds of audio input.
import { createLoudnessWorklet } from "loudness-worklet";
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const context = new AudioContext({ sampleRate: 48000 });
const source = context.createMediaStreamSource(mediaStream);
const worklet = await createLoudnessWorklet(context, {
processorOptions: { interval: 1, capacity: 600 }
});
worklet.port.onmessage = (event) => console.log("Loudness Data:", event.data);
source.connect(worklet);
// worklet.connect(context.destination);
// Optionally connect to destination for monitoring (echo)
Provide the execution environment for audio processing.
AudioContext is used for real-time audio processing, such as live audio input from a microphone or media stream.
OfflineAudioContext is used for processing audio data offline, allowing for rendering and analysis without requiring real-time playback.
Nodes are the building blocks of an audio graph, representing audio sources, processing modules, and destinations. The following nodes are commonly used as a source input:
AudioBufferSourceNode is used to play audio data stored in an AudioBuffer, typically for pre-recorded audio files.
const arrayBuffer = await file.arrayBuffer();
const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
const bufferSource = new AudioBufferSourceNode(audioContext, { buffer: audioBuffer });
MediaStreamAudioSourceNode is used to play audio from a MediaStream, such as a live microphone input or a video element.
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
const mediaStreamSource = new MediaStreamAudioSourceNode(audioContext, { mediaStream });
MediaElementAudioSourceNode is used to play audio from an HTML <audio> or <video> element.
const mediaElement = document.querySelector("audio");
const elementSource = new MediaElementAudioSourceNode(audioContext, { mediaElement });
The AudioWorkletNode constructor accepts the following options:
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
| processorOptions.interval | number | No | 0.02 | Message interval in seconds. |
| processorOptions.capacity | number | No | 0 | Maximum seconds of history to keep. If set to 0, the processor will not limit the history size. |
Most of the time, you only need to set processorOptions or leave it empty.
const { numberOfChannels, length, sampleRate } = audioBuffer;
const worklet = new AudioWorkletNode(context, "loudness-processor", {
processorOptions: {
capacity: length / sampleRate,
interval: 0.02
}
});
Measurement results are sent back to the main thread via port.onmessage as a LoudnessSnapshot object:
type LoudnessMeasurements = {
momentaryLoudness: number;
shortTermLoudness: number;
integratedLoudness: number;
maximumMomentaryLoudness: number;
maximumShortTermLoudness: number;
maximumTruePeakLevel: number;
loudnessRange: number;
};
type LoudnessSnapshot = {
currentFrame: number;
currentTime: number;
currentMeasurements: LoudnessMeasurements[];
};
| Metric | Unit |
|---|---|
momentaryLoudness | LUFS/LKFS |
shortTermLoudness | LUFS/LKFS |
integratedLoudness | LUFS/LKFS |
maximumMomentaryLoudness | LUFS/LKFS |
maximumShortTermLoudness | LUFS/LKFS |
maximumTruePeakLevel | dBTP |
loudnessRange | LU |
Supported channel counts: 1, 2, 5, 6, 8, 10, 12, 24
[!NOTE] Channel counts not listed above are weighted at
1.0.
The following coefficients are used for the K-weighting filter:
| highshelf | highpass | |
|---|---|---|
| a1 | -1.69065929318241 | -1.99004745483398 |
| a2 | 0.73248077421585 | 0.99007225036621 |
| b0 | 1.53512485958697 | 1.0 |
| b1 | -2.69169618940638 | -2.0 |
| b2 | 1.19839281085285 | 1.0 |
The following FIR filter coefficients are used for true-peak measurement:
| Phase 0 | Phase 1 | Phase 2 | Phase 3 |
|---|---|---|---|
| 0.0017089843750 | -0.0291748046875 | -0.0189208984375 | -0.0083007812500 |
| 0.0109863281250 | 0.0292968750000 | 0.0330810546875 | 0.0148925781250 |
| -0.0196533203125 | -0.0517578125000 | -0.0582275390625 | -0.0266113281250 |
| 0.0332031250000 | 0.0891113281250 | 0.1015625000000 | 0.0476074218750 |
| -0.0594482421875 | -0.1665039062500 | -0.2003173828125 | -0.1022949218750 |
| 0.1373291015625 | 0.4650878906250 | 0.7797851562500 | 0.9721679687500 |
| 0.9721679687500 | 0.7797851562500 | 0.4650878906250 | 0.1373291015625 |
| -0.1022949218750 | -0.2003173828125 | -0.1665039062500 | -0.0594482421875 |
| 0.0476074218750 | 0.1015625000000 | 0.0891113281250 | 0.0332031250000 |
| -0.0266113281250 | -0.0582275390625 | -0.0517578125000 | -0.0196533203125 |
| 0.0148925781250 | 0.0330810546875 | 0.0292968750000 | 0.0109863281250 |
| -0.0083007812500 | -0.0189208984375 | -0.0291748046875 | 0.0017089843750 |
Code correctness is verified against the official ITU-R BS.2217 compliance test suite, ensuring strict adherence to the ITU-R BS.1770 specification.
| file | measurement | channels | |
|---|---|---|---|
| 1770Comp_2_RelGateTest | -10.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_AbsGateTest | -69.5 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_25Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_100Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_500Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_1000Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_2000Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_24LKFS_10000Hz_2ch | -24.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_25Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_100Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_500Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_1000Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_2000Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_23LKFS_10000Hz_2ch | -23.0 LKFS | 2 | :white_check_mark: |
| 1770Comp_2_18LKFS_FrequencySweep | -18.0 LKFS | 1 | :white_check_mark: |
| 1770Comp_2_24LKFS_SummingTest | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_SummingTest | -23.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckLeft | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckRight | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckCentre | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckLFE | -inf LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckLs | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_24LKFS_ChannelCheckRs | -24.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_ChannelCheckLeft | -23.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_ChannelCheckRight | -23.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LFKS_ChannelCheckCentre | -23.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_ChannelCheckLFE | -inf LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_ChannelCheckLs | -23.0 LKFS | 6 | :white_check_mark: |
| 1770Comp_2_23LKFS_ChannelCheckRs | -23.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf 6ch VinCntr-24LKFS | -24.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf 6ch VinL+R-24LKFS | -24.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf 6ch VinL-R-C-24LKFS | -24.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf Stereo VinL+R-24LKFS | -24.0 LKFS | 2 | :white_check_mark: |
| 1770-2 Conf Mono Voice+Music-24LKFS | -24.0 LKFS | 1 | :white_check_mark: |
| 1770-2 Conf 6ch VinCntr-23LKFS | -23.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf 6ch VinL+R-23LKFS | -23.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf 6ch VinL-R-C-23LKFS | -23.0 LKFS | 6 | :white_check_mark: |
| 1770-2 Conf Stereo VinL+R-23LKFS | -23.0 LKFS | 2 | :white_check_mark: |
| 1770-2 Conf Mono Voice+Music-23LKFS | -23.0 LKFS | 1 | :white_check_mark: |
| 1770Conf-8channels_24LKFS | -24.0 LKFS | 8 | :white_check_mark: |
| 1770Conf-8channels_23LKFS | -23.0 LKFS | 8 | :white_check_mark: |
| 1770Conf-10channels_24LKFS | -24.0 LKFS | 10 | :white_check_mark: |
| 1770Conf-10channels_23LKFS | -23.0 LKFS | 10 | :white_check_mark: |
| 1770Conf-12channels_24LKFS | -24.0 LKFS | 12 | :white_check_mark: |
| 1770Conf-12channels_23LKFS | -23.0 LKFS | 12 | :white_check_mark: |
| 1770Conf-24channels_24LKFS | -24.0 LKFS | 24 | :white_check_mark: |
| 1770Conf-24channels_23LKFS | -23.0 LKFS | 24 | :white_check_mark: |
[!TIP] If
decodeAudioDatafails, it's often due to the browser's specific support for the audio file's format (codec), container, or channel layout, rather than a general API incompatibility. Try a different browser or convert the audio file to a more widely supported format. For example, Chrome has limited support for certain codecs in audio files, while Safari offers broader support. (1770Conf-24channels_24LKFS)
Validated against EBU TECH 3341 minimum requirements for loudness metering, including gating behavior, time scales, and true-peak accuracy.
| file | expected response and accepted tolerances | |
|---|---|---|
| seq-3341-1-16bit | M, S, I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-2-16bit | M, S, I = -33.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-3-16bit-v02 | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-4-16bit-v02 | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-5-16bit-v02 | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-6-5channels-16bit | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-6-6channels-WAVEEX-16bit | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-7_seq-3342-5-24bit | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-2011-8_seq-3342-6-24bit-v02 | I = -23.0 ±0.1 LUFS | :white_check_mark: |
| seq-3341-9-24bit | S = -23.0 ±0.1 LUFS, constant after 3 s | :white_check_mark: |
| seq-3341-10-*-24bit | Max S = -23.0 ±0.1 LUFS, for each segment | :white_check_mark: |
| seq-3341-11-24bit | Max S = -38.0, -37.0, …, -19.0 ±0.1 LUFS, successive values | :white_check_mark: |
| seq-3341-12-24bit | M = -23.0 ±0.1 LUFS, constant after 1 s | :white_check_mark: |
| seq-3341-13-*-24bit | Max M = -23.0 ±0.1 LUFS, for each segment | :white_check_mark: |
| seq-3341-14-24bit | Max M = -38.0, …, -19.0 ±0.1 LUFS, successive values | :white_check_mark: |
| seq-3341-15-24bit | Max true-peak = -6.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-16-24bit | Max true-peak = -6.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-17-24bit | Max true-peak = -6.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-18-24bit | Max true-peak = -6.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-19-24bit | Max true-peak = +3.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-20-24bit | Max true-peak = 0.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-21-24bit | Max true-peak = 0.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-22-24bit | Max true-peak = 0.0 +0.2/-0.4 dBTP | :white_check_mark: |
| seq-3341-23-24bit | Max true-peak = 0.0 +0.2/-0.4 dBTP | :white_check_mark: |
EBU TECH 3342 focuses on the measurement of loudness range.
| file | expected response and accepted tolerances | |
|---|---|---|
| seq-3342-1-16bit | LRA = 10 ±1 LU | :white_check_mark: |
| seq-3342-2-16bit | LRA = 5 ±1 LU | :white_check_mark: |
| seq-3342-3-16bit | LRA = 20 ±1 LU | :white_check_mark: |
| seq-3342-4-16bit | LRA = 15 ±1 LU | :white_check_mark: |
| seq-3341-7_seq-3342-5-24bit | LRA = 5 ±1 LU | :white_check_mark: |
| seq-3341-2011-8_seq-3342-6-24bit-v02 | LRA = 15 ±1 LU | :white_check_mark: |
This project is a learning experiment aimed at exploring audio signal processing and ITU-R BS.1770 loudness measurement standards. I am not an expert in audio engineering or signal processing, and this project was developed as a way to better understand the concepts of audio loudness and implementation techniques. Thanks to the ITU-R BS.1770 standards for providing the theoretical basis for loudness measurement.
This project is licensed under the MIT License.
FAQs
A lightweight and efficient AudioWorklet for real-time loudness measurement in the browser, compliant with the ITU-R BS.1770-5 standard.
We found that loudness-worklet demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.