loudness-worklet 1.5.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -16
- package/package.json +14 -30
- package/packages/lib/dist/index.d.ts +36 -0
- package/packages/lib/dist/index.js +477 -0
- package/dist/index.d.ts +0 -15
- package/dist/index.js +0 -442
package/README.md
CHANGED
|
@@ -5,9 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
A loudness meter for the `Web Audio API`, based on the [ITU-R BS.1770-5](https://www.itu.int/rec/R-REC-BS.1770) standard and implemented as an AudioWorkletProcessor.
|
|
7
7
|
|
|
8
|
-
[](https://lcweden.github.io/loudness-worklet/)
|
|
9
|
-
|
|
10
|
-
<p align="center"><a href="https://lcweden.github.io/loudness-worklet/">Demo</a></p>
|
|
8
|
+
[](https://lcweden.github.io/loudness-worklet/)
|
|
11
9
|
|
|
12
10
|
## Features
|
|
13
11
|
|
|
@@ -244,20 +242,20 @@ const worklet = new AudioWorkletNode(context, "loudness-processor", {
|
|
|
244
242
|
Measurement results are sent back to the main thread via `port.onmessage` with the following format:
|
|
245
243
|
|
|
246
244
|
```typescript
|
|
247
|
-
type
|
|
245
|
+
type LoudnessMeasurements = {
|
|
246
|
+
momentaryLoudness: number;
|
|
247
|
+
shortTermLoudness: number;
|
|
248
|
+
integratedLoudness: number;
|
|
249
|
+
maximumMomentaryLoudness: number;
|
|
250
|
+
maximumShortTermLoudness: number;
|
|
251
|
+
maximumTruePeakLevel: number;
|
|
252
|
+
loudnessRange: number;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
type LoudnessSnapshot = {
|
|
248
256
|
currentFrame: number;
|
|
249
257
|
currentTime: number;
|
|
250
|
-
|
|
251
|
-
{
|
|
252
|
-
momentaryLoudness: number;
|
|
253
|
-
shortTermLoudness: number;
|
|
254
|
-
integratedLoudness: number;
|
|
255
|
-
maximumMomentaryLoudness: number;
|
|
256
|
-
maximumShortTermLoudness: number;
|
|
257
|
-
maximumTruePeakLevel: number;
|
|
258
|
-
loudnessRange: number;
|
|
259
|
-
}
|
|
260
|
-
];
|
|
258
|
+
currentMeasurements: LoudnessMeasurements[];
|
|
261
259
|
};
|
|
262
260
|
```
|
|
263
261
|
|
|
@@ -271,7 +269,7 @@ type AudioLoudnessSnapshot = {
|
|
|
271
269
|
| `maximumMomentaryLoudness` | `LUFS`/`LKFS` |
|
|
272
270
|
| `maximumShortTermLoudness` | `LUFS`/`LKFS` |
|
|
273
271
|
| `maximumTruePeakLevel` | `dBTP` |
|
|
274
|
-
| `loudnessRange` | `
|
|
272
|
+
| `loudnessRange` | `LU` |
|
|
275
273
|
|
|
276
274
|
### Supported Channels
|
|
277
275
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loudness-worklet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "A lightweight and efficient AudioWorklet for real-time loudness measurement in the browser, compliant with the ITU-R BS.1770-5 standard.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web-audio",
|
|
@@ -18,20 +18,22 @@
|
|
|
18
18
|
"lufs",
|
|
19
19
|
"lra",
|
|
20
20
|
"javascript",
|
|
21
|
-
"typescript"
|
|
22
|
-
"lightweight"
|
|
21
|
+
"typescript"
|
|
23
22
|
],
|
|
24
23
|
"type": "module",
|
|
25
|
-
"
|
|
26
|
-
"types": "./dist/index.d.ts",
|
|
24
|
+
"main": "./packages/lib/dist/index.js",
|
|
25
|
+
"types": "./packages/lib/dist/index.d.ts",
|
|
27
26
|
"exports": {
|
|
28
27
|
".": {
|
|
29
|
-
"
|
|
30
|
-
"
|
|
28
|
+
"types": "./packages/lib/dist/index.d.ts",
|
|
29
|
+
"default": "./packages/lib/dist/index.js"
|
|
31
30
|
}
|
|
32
31
|
},
|
|
33
32
|
"files": [
|
|
34
|
-
"dist"
|
|
33
|
+
"packages/lib/dist"
|
|
34
|
+
],
|
|
35
|
+
"workspaces": [
|
|
36
|
+
"packages/*"
|
|
35
37
|
],
|
|
36
38
|
"license": "MIT",
|
|
37
39
|
"author": "lcweden",
|
|
@@ -44,29 +46,11 @@
|
|
|
44
46
|
"url": "https://github.com/lcweden/loudness-worklet/issues"
|
|
45
47
|
},
|
|
46
48
|
"scripts": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"test": "vitest",
|
|
50
|
-
"build": "vite build --mode static && vite build --mode demo",
|
|
51
|
-
"lib": "vite build --mode static && vite build --mode lib && tsc -p tsconfig.lib.json",
|
|
52
|
-
"prepare": "npm run lib",
|
|
53
|
-
"preview": "vite preview --mode demo",
|
|
54
|
-
"format": "prettier --write ."
|
|
49
|
+
"format": "biome format --write",
|
|
50
|
+
"lint": "biome lint --write"
|
|
55
51
|
},
|
|
56
52
|
"devDependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"@
|
|
59
|
-
"@types/audioworklet": "^0.0.85",
|
|
60
|
-
"@types/node": "^22.14.1",
|
|
61
|
-
"daisyui": "^5.0.28",
|
|
62
|
-
"echarts": "^6.0.0",
|
|
63
|
-
"prettier": "^3.5.3",
|
|
64
|
-
"prettier-plugin-tailwindcss": "^0.6.11",
|
|
65
|
-
"solid-js": "^1.9.5",
|
|
66
|
-
"tailwindcss": "^4.1.4",
|
|
67
|
-
"typescript": "~5.7.2",
|
|
68
|
-
"vite": "^6.3.1",
|
|
69
|
-
"vite-plugin-solid": "^2.11.6",
|
|
70
|
-
"vitest": "^3.2.2"
|
|
53
|
+
"@biomejs/biome": "2.3.8",
|
|
54
|
+
"@types/node": "^24.10.1"
|
|
71
55
|
}
|
|
72
56
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare function createLoudnessWorklet(context: BaseAudioContext, options?: LoudnessWorkletProcessorOptions): Promise<AudioWorkletNode>;
|
|
2
|
+
|
|
3
|
+
export declare type LoudnessMeasurements = {
|
|
4
|
+
momentaryLoudness: number;
|
|
5
|
+
shortTermLoudness: number;
|
|
6
|
+
integratedLoudness: number;
|
|
7
|
+
maximumMomentaryLoudness: number;
|
|
8
|
+
maximumShortTermLoudness: number;
|
|
9
|
+
maximumTruePeakLevel: number;
|
|
10
|
+
loudnessRange: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export declare type LoudnessProcessorOptions = {
|
|
14
|
+
capacity?: number;
|
|
15
|
+
interval?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export declare type LoudnessSnapshot = {
|
|
19
|
+
currentFrame: number;
|
|
20
|
+
currentTime: number;
|
|
21
|
+
currentMeasurements: LoudnessMeasurements[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export declare class LoudnessWorkletNode extends AudioWorkletNode {
|
|
25
|
+
constructor(context: BaseAudioContext, options?: LoudnessWorkletProcessorOptions);
|
|
26
|
+
static loadModule(context: BaseAudioContext): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export declare interface LoudnessWorkletProcessorOptions {
|
|
30
|
+
numberOfInputs?: AudioWorkletNodeOptions["numberOfInputs"];
|
|
31
|
+
numberOfOutputs?: AudioWorkletNodeOptions["numberOfOutputs"];
|
|
32
|
+
outputChannelCount?: AudioWorkletNodeOptions["outputChannelCount"];
|
|
33
|
+
processorOptions?: LoudnessProcessorOptions;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { }
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
const i = `const x = {
|
|
2
|
+
highshelf: {
|
|
3
|
+
a: [-1.69065929318241, 0.73248077421585],
|
|
4
|
+
b: [1.53512485958697, -2.69169618940638, 1.19839281085285]
|
|
5
|
+
},
|
|
6
|
+
highpass: { a: [-1.99004745483398, 0.99007225036621], b: [1, -2, 1] }
|
|
7
|
+
}, C = {
|
|
8
|
+
lowpass: {
|
|
9
|
+
phase0: [
|
|
10
|
+
0.001708984375,
|
|
11
|
+
0.010986328125,
|
|
12
|
+
-0.0196533203125,
|
|
13
|
+
0.033203125,
|
|
14
|
+
-0.0594482421875,
|
|
15
|
+
0.1373291015625,
|
|
16
|
+
0.97216796875,
|
|
17
|
+
-0.102294921875,
|
|
18
|
+
0.047607421875,
|
|
19
|
+
-0.026611328125,
|
|
20
|
+
0.014892578125,
|
|
21
|
+
-0.00830078125
|
|
22
|
+
],
|
|
23
|
+
phase1: [
|
|
24
|
+
-0.0291748046875,
|
|
25
|
+
0.029296875,
|
|
26
|
+
-0.0517578125,
|
|
27
|
+
0.089111328125,
|
|
28
|
+
-0.16650390625,
|
|
29
|
+
0.465087890625,
|
|
30
|
+
0.77978515625,
|
|
31
|
+
-0.2003173828125,
|
|
32
|
+
0.1015625,
|
|
33
|
+
-0.0582275390625,
|
|
34
|
+
0.0330810546875,
|
|
35
|
+
-0.0189208984375
|
|
36
|
+
],
|
|
37
|
+
phase2: [
|
|
38
|
+
-0.0189208984375,
|
|
39
|
+
0.0330810546875,
|
|
40
|
+
-0.0582275390625,
|
|
41
|
+
0.1015625,
|
|
42
|
+
-0.2003173828125,
|
|
43
|
+
0.77978515625,
|
|
44
|
+
0.465087890625,
|
|
45
|
+
-0.16650390625,
|
|
46
|
+
0.089111328125,
|
|
47
|
+
-0.0517578125,
|
|
48
|
+
0.029296875,
|
|
49
|
+
-0.0291748046875
|
|
50
|
+
],
|
|
51
|
+
phase3: [
|
|
52
|
+
-0.00830078125,
|
|
53
|
+
0.014892578125,
|
|
54
|
+
-0.026611328125,
|
|
55
|
+
0.047607421875,
|
|
56
|
+
-0.102294921875,
|
|
57
|
+
0.97216796875,
|
|
58
|
+
0.1373291015625,
|
|
59
|
+
-0.0594482421875,
|
|
60
|
+
0.033203125,
|
|
61
|
+
-0.0196533203125,
|
|
62
|
+
0.010986328125,
|
|
63
|
+
0.001708984375
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
}, B = {
|
|
67
|
+
1: [1],
|
|
68
|
+
2: [1, 1],
|
|
69
|
+
6: [1, 1, 1, 0, 1.41, 1.41],
|
|
70
|
+
8: [1, 1, 1, 0, 1.41, 1.41, 1, 1],
|
|
71
|
+
10: [1, 1, 1, 0, 1.41, 1.41, 1, 1, 1, 1],
|
|
72
|
+
12: [1, 1, 1, 0, 1.41, 1.41, 1, 1, 1, 1, 1],
|
|
73
|
+
24: [
|
|
74
|
+
1.41,
|
|
75
|
+
1.41,
|
|
76
|
+
1,
|
|
77
|
+
0,
|
|
78
|
+
1,
|
|
79
|
+
1,
|
|
80
|
+
1,
|
|
81
|
+
1,
|
|
82
|
+
1,
|
|
83
|
+
0,
|
|
84
|
+
1.41,
|
|
85
|
+
1.41,
|
|
86
|
+
1,
|
|
87
|
+
1,
|
|
88
|
+
1,
|
|
89
|
+
1,
|
|
90
|
+
1,
|
|
91
|
+
1,
|
|
92
|
+
1,
|
|
93
|
+
1,
|
|
94
|
+
1,
|
|
95
|
+
1,
|
|
96
|
+
1,
|
|
97
|
+
1
|
|
98
|
+
]
|
|
99
|
+
}, P = 0.4, M = 0.1, H = 3, O = 0.1, W = 0.1, k = 0.95, V = 12.04, U = -70, Y = -10, z = -70, K = -20;
|
|
100
|
+
class D {
|
|
101
|
+
#t = new Float32Array(2);
|
|
102
|
+
#e = new Float32Array(3);
|
|
103
|
+
#s = new Float32Array(2);
|
|
104
|
+
#r = new Float32Array(2);
|
|
105
|
+
/**
|
|
106
|
+
* Creates a new BiquadraticFilter with given coefficients.
|
|
107
|
+
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
108
|
+
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
109
|
+
*/
|
|
110
|
+
constructor(e, i) {
|
|
111
|
+
this.reset(), this.set(e, i);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Processes a single input sample and returns the filtered output.
|
|
115
|
+
* @param { number } input - The input sample.
|
|
116
|
+
* @returns { number } - The filtered output sample.
|
|
117
|
+
*/
|
|
118
|
+
process(e) {
|
|
119
|
+
const i = this.#e[0] * e + this.#e[1] * this.#s[0] + this.#e[2] * this.#s[1] - this.#t[0] * this.#r[0] - this.#t[1] * this.#r[1];
|
|
120
|
+
return this.#s[1] = this.#s[0], this.#s[0] = e, this.#r[1] = this.#r[0], this.#r[0] = i, i;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sets new filter coefficients.
|
|
124
|
+
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
125
|
+
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
126
|
+
* @returns { void }
|
|
127
|
+
*/
|
|
128
|
+
set(e, i) {
|
|
129
|
+
e.length = 2, this.#t.set(e), i.length = 3, this.#e.set(i);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resets the filter state.
|
|
133
|
+
* @returns { void }
|
|
134
|
+
*/
|
|
135
|
+
reset() {
|
|
136
|
+
this.#s.fill(0), this.#r.fill(0);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
class N {
|
|
140
|
+
#t;
|
|
141
|
+
#e;
|
|
142
|
+
#s;
|
|
143
|
+
/**
|
|
144
|
+
* Creates an instance of the filter.
|
|
145
|
+
* @param coefficients - The filter coefficients.
|
|
146
|
+
*/
|
|
147
|
+
constructor(e) {
|
|
148
|
+
this.#t = e, this.#e = Array(e.length).fill(0), this.#s = 0;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Processes a single input sample.
|
|
152
|
+
* @param {number} input - The input sample.
|
|
153
|
+
* @returns {number} - The filtered output sample.
|
|
154
|
+
*/
|
|
155
|
+
process(e) {
|
|
156
|
+
this.#e[this.#s] = e, this.#s = (this.#s + 1) % this.#e.length;
|
|
157
|
+
let i = 0;
|
|
158
|
+
for (let g = 0; g < this.#t.length; g++) {
|
|
159
|
+
const s = (this.#s - 1 - g + this.#e.length) % this.#e.length;
|
|
160
|
+
i += this.#t[g] * this.#e[s];
|
|
161
|
+
}
|
|
162
|
+
return i;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resets the filter state.
|
|
166
|
+
* @returns { void }
|
|
167
|
+
*/
|
|
168
|
+
reset() {
|
|
169
|
+
this.#e.fill(0), this.#s = 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
class I {
|
|
173
|
+
#t;
|
|
174
|
+
#e;
|
|
175
|
+
#s;
|
|
176
|
+
#r;
|
|
177
|
+
#i;
|
|
178
|
+
/**
|
|
179
|
+
* Creates a new CircularBuffer with given capacity.
|
|
180
|
+
* @param { number } capacity - The maximum number of items the buffer can hold.
|
|
181
|
+
*/
|
|
182
|
+
constructor(e) {
|
|
183
|
+
this.#e = e || 0, this.#t = new Array(e), this.#s = 0, this.#r = 0, this.#i = 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Adds an item to the buffer.
|
|
187
|
+
* @param { T } item - The item to add to the buffer.
|
|
188
|
+
* @returns { void }
|
|
189
|
+
*/
|
|
190
|
+
push(e) {
|
|
191
|
+
this.#t[this.#r] = e, this.isFull() ? this.#s = (this.#s + 1) % this.#e : this.#i++, this.#r = (this.#r + 1) % this.#e;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Removes and returns the oldest item from the buffer.
|
|
195
|
+
* @returns { T | undefined }
|
|
196
|
+
*/
|
|
197
|
+
pop() {
|
|
198
|
+
if (this.isEmpty())
|
|
199
|
+
return;
|
|
200
|
+
const e = this.#t[this.#s];
|
|
201
|
+
return this.#t[this.#s] = void 0, this.#s = (this.#s + 1) % this.#e, this.#i--, e;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Returns the oldest item from the buffer without removing it.
|
|
205
|
+
* @returns { T | undefined }
|
|
206
|
+
*/
|
|
207
|
+
peek() {
|
|
208
|
+
if (!this.isEmpty())
|
|
209
|
+
return this.#t[this.#s];
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Returns a slice of the buffer contents.
|
|
213
|
+
* @param { number } start - The starting index of the slice (inclusive).
|
|
214
|
+
* @param { number } end - The ending index of the slice (exclusive).
|
|
215
|
+
* @returns { T[] }
|
|
216
|
+
*/
|
|
217
|
+
slice(e, i) {
|
|
218
|
+
if (e >= i)
|
|
219
|
+
return [];
|
|
220
|
+
const g = [];
|
|
221
|
+
for (let s = Math.max(0, e); s < Math.min(this.#i, i); s++) {
|
|
222
|
+
const n = (this.#s + s) % this.#e;
|
|
223
|
+
g.push(this.#t[n]);
|
|
224
|
+
}
|
|
225
|
+
return g;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Adds an item to the buffer and
|
|
229
|
+
* returns undefined if the buffer is not full,
|
|
230
|
+
* otherwise returns the oldest item from the buffer without removing it.
|
|
231
|
+
*
|
|
232
|
+
* @param item
|
|
233
|
+
*/
|
|
234
|
+
evict(e) {
|
|
235
|
+
const i = this.isFull() ? this.peek() : void 0;
|
|
236
|
+
return this.push(e), i;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Checks if the buffer is empty.
|
|
240
|
+
* @returns { boolean }
|
|
241
|
+
*/
|
|
242
|
+
isEmpty() {
|
|
243
|
+
return this.#i === 0;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Checks if the buffer is full.
|
|
247
|
+
* @returns { boolean }
|
|
248
|
+
*/
|
|
249
|
+
isFull() {
|
|
250
|
+
return this.#i === this.#e;
|
|
251
|
+
}
|
|
252
|
+
/** @type { number } */
|
|
253
|
+
get length() {
|
|
254
|
+
return this.#i;
|
|
255
|
+
}
|
|
256
|
+
/** @type { number } */
|
|
257
|
+
get capacity() {
|
|
258
|
+
return this.#e;
|
|
259
|
+
}
|
|
260
|
+
/** @type { IterableIterator<T> } */
|
|
261
|
+
*[Symbol.iterator]() {
|
|
262
|
+
for (let e = 0; e < this.#i; e++) {
|
|
263
|
+
const i = (this.#s + e) % this.#e;
|
|
264
|
+
yield this.#t[i];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
class j extends AudioWorkletProcessor {
|
|
269
|
+
capacity;
|
|
270
|
+
interval;
|
|
271
|
+
previousTime = 0;
|
|
272
|
+
attenuation = 10 ** (-V / 20);
|
|
273
|
+
measurements;
|
|
274
|
+
kWeightingFilters = [];
|
|
275
|
+
overSamplingFilters = [];
|
|
276
|
+
overSampledValues = [];
|
|
277
|
+
overSampledValueDirtyFlags = [];
|
|
278
|
+
mEnergyBuffers = [];
|
|
279
|
+
mEnergySums = [];
|
|
280
|
+
mSampleAccumulators = [];
|
|
281
|
+
mTraces = [];
|
|
282
|
+
mTraceDirtyFlags = [];
|
|
283
|
+
sEnergyBuffers = [];
|
|
284
|
+
sEnergySums = [];
|
|
285
|
+
sSampleAccumulators = [];
|
|
286
|
+
sTraces = [];
|
|
287
|
+
sTraceDirtyFlags = [];
|
|
288
|
+
constructor(e) {
|
|
289
|
+
super();
|
|
290
|
+
const { numberOfInputs: i = 1, processorOptions: g } = e, { capacity: s, interval: n } = g;
|
|
291
|
+
this.capacity = s || 0, this.interval = n || 0, this.measurements = [];
|
|
292
|
+
for (let h = 0; h < i; h++) {
|
|
293
|
+
const F = Math.round(sampleRate * P), T = Math.round(sampleRate * H), L = Math.ceil(this.capacity / M), _ = Math.ceil(this.capacity / O);
|
|
294
|
+
this.mEnergySums[h] = 0, this.mSampleAccumulators[h] = 0, this.mEnergyBuffers[h] = new I(F), this.mTraces[h] = this.capacity ? new I(L) : [], this.sEnergySums[h] = 0, this.sSampleAccumulators[h] = 0, this.sEnergyBuffers[h] = new I(T), this.sTraces[h] = this.capacity ? new I(_) : [], this.measurements[h] = {
|
|
295
|
+
momentaryLoudness: Number.NEGATIVE_INFINITY,
|
|
296
|
+
shortTermLoudness: Number.NEGATIVE_INFINITY,
|
|
297
|
+
integratedLoudness: Number.NEGATIVE_INFINITY,
|
|
298
|
+
maximumMomentaryLoudness: Number.NEGATIVE_INFINITY,
|
|
299
|
+
maximumShortTermLoudness: Number.NEGATIVE_INFINITY,
|
|
300
|
+
maximumTruePeakLevel: Number.NEGATIVE_INFINITY,
|
|
301
|
+
loudnessRange: Number.NEGATIVE_INFINITY
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
process(e, i, g) {
|
|
306
|
+
for (let s = 0; s < e.length; s++) {
|
|
307
|
+
if (!e[s].length)
|
|
308
|
+
continue;
|
|
309
|
+
const n = e[s].length, h = e[s][0].length, F = sampleRate >= 96e3 ? 2 : 4, T = B[n], L = this.mEnergyBuffers[s].capacity, _ = this.sEnergyBuffers[s].capacity;
|
|
310
|
+
if (!this.kWeightingFilters[s] || this.kWeightingFilters[s].length !== n) {
|
|
311
|
+
const { highshelf: o, highpass: t } = x;
|
|
312
|
+
this.kWeightingFilters[s] = this.kWeightingFilters[s] || [];
|
|
313
|
+
for (let a = 0; a < n; a++)
|
|
314
|
+
this.kWeightingFilters[s][a] = [
|
|
315
|
+
new D(o.a, o.b),
|
|
316
|
+
new D(t.a, t.b)
|
|
317
|
+
];
|
|
318
|
+
}
|
|
319
|
+
if (!this.overSamplingFilters[s] || this.overSamplingFilters[s].length !== n) {
|
|
320
|
+
const { lowpass: o } = C, { phase0: t, phase1: a, phase2: f, phase3: u } = o;
|
|
321
|
+
this.overSamplingFilters[s] = this.overSamplingFilters[s] || [];
|
|
322
|
+
for (let l = 0; l < n; l++)
|
|
323
|
+
this.overSamplingFilters[s][l] = [
|
|
324
|
+
new N(t),
|
|
325
|
+
new N(a),
|
|
326
|
+
new N(f),
|
|
327
|
+
new N(u)
|
|
328
|
+
];
|
|
329
|
+
}
|
|
330
|
+
for (let o = 0; o < h; o++) {
|
|
331
|
+
let t = 0;
|
|
332
|
+
for (let u = 0; u < n; u++) {
|
|
333
|
+
const l = e[s][u][o], [m, r] = this.kWeightingFilters[s][u], E = m.process(l), y = r.process(E), S = y * y, c = T[u] ?? 1;
|
|
334
|
+
t += S * c;
|
|
335
|
+
const d = l * this.attenuation;
|
|
336
|
+
let p = 0;
|
|
337
|
+
for (let v = 0; v < F; v++) {
|
|
338
|
+
const w = this.overSamplingFilters[s][u][v], G = Math.abs(w.process(d));
|
|
339
|
+
p < G && (p = G);
|
|
340
|
+
}
|
|
341
|
+
this.overSampledValues[s] !== void 0 ? p > this.overSampledValues[s] && (this.overSampledValues[s] = p, this.overSampledValueDirtyFlags[s] = !0) : (this.overSampledValues[s] = p, this.overSampledValueDirtyFlags[s] = !0);
|
|
342
|
+
}
|
|
343
|
+
const a = this.mEnergyBuffers[s].evict(t) ?? 0;
|
|
344
|
+
this.mEnergySums[s] += t - a;
|
|
345
|
+
const f = this.sEnergyBuffers[s].evict(t) ?? 0;
|
|
346
|
+
if (this.sEnergySums[s] += t - f, this.mEnergyBuffers[s].isFull()) {
|
|
347
|
+
const u = this.mEnergySums[s] / L, l = this.energyToLoudness(u), m = this.measurements[s].maximumMomentaryLoudness, r = Math.max(l, m);
|
|
348
|
+
this.measurements[s].momentaryLoudness = l, this.measurements[s].maximumMomentaryLoudness = r;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.mSampleAccumulators[s] += h, this.sSampleAccumulators[s] += h;
|
|
352
|
+
const R = Math.round(sampleRate * M), b = Math.round(sampleRate * O);
|
|
353
|
+
for (; this.mSampleAccumulators[s] >= R; ) {
|
|
354
|
+
if (this.mEnergyBuffers[s].isFull()) {
|
|
355
|
+
const o = this.mEnergySums[s] / L, t = this.energyToLoudness(o);
|
|
356
|
+
this.mTraces[s].push(t), this.mTraceDirtyFlags[s] = !0;
|
|
357
|
+
}
|
|
358
|
+
this.mSampleAccumulators[s] -= R;
|
|
359
|
+
}
|
|
360
|
+
for (; this.sSampleAccumulators[s] >= b; ) {
|
|
361
|
+
if (this.sEnergyBuffers[s].isFull()) {
|
|
362
|
+
const o = this.sEnergySums[s] / _, t = this.energyToLoudness(o), a = this.measurements[s].maximumShortTermLoudness, f = Math.max(t, a);
|
|
363
|
+
this.measurements[s].shortTermLoudness = t, this.measurements[s].maximumShortTermLoudness = f, this.sTraces[s].push(t), this.sTraceDirtyFlags[s] = !0;
|
|
364
|
+
}
|
|
365
|
+
this.sSampleAccumulators[s] -= b;
|
|
366
|
+
}
|
|
367
|
+
if (this.mTraces[s].length > 2 && this.mTraceDirtyFlags[s]) {
|
|
368
|
+
const o = [];
|
|
369
|
+
for (const t of this.mTraces[s])
|
|
370
|
+
t > U && o.push(t);
|
|
371
|
+
if (o.length > 2) {
|
|
372
|
+
const t = [];
|
|
373
|
+
for (const r of o)
|
|
374
|
+
t.push(this.loudnessToEnergy(r));
|
|
375
|
+
let a = 0;
|
|
376
|
+
for (const r of t)
|
|
377
|
+
a += r;
|
|
378
|
+
const f = a / t.length, l = this.energyToLoudness(f) + Y, m = [];
|
|
379
|
+
for (const r of o)
|
|
380
|
+
r > l && m.push(r);
|
|
381
|
+
if (m.length > 2) {
|
|
382
|
+
const r = [];
|
|
383
|
+
for (const c of m)
|
|
384
|
+
r.push(this.loudnessToEnergy(c));
|
|
385
|
+
let E = 0;
|
|
386
|
+
for (const c of r)
|
|
387
|
+
E += c;
|
|
388
|
+
const y = E / r.length, S = this.energyToLoudness(y);
|
|
389
|
+
this.measurements[s].integratedLoudness = S;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
this.mTraceDirtyFlags[s] = !1;
|
|
393
|
+
}
|
|
394
|
+
if (this.sTraces[s].length > 2 && this.sTraceDirtyFlags[s]) {
|
|
395
|
+
const o = [];
|
|
396
|
+
for (const t of this.sTraces[s])
|
|
397
|
+
t > z && o.push(t);
|
|
398
|
+
if (o.length > 2) {
|
|
399
|
+
const t = [];
|
|
400
|
+
for (const r of o)
|
|
401
|
+
t.push(this.loudnessToEnergy(r));
|
|
402
|
+
let a = 0;
|
|
403
|
+
for (const r of t)
|
|
404
|
+
a += r;
|
|
405
|
+
const f = a / t.length, l = this.energyToLoudness(f) + K, m = [];
|
|
406
|
+
for (const r of o)
|
|
407
|
+
r > l && m.push(r);
|
|
408
|
+
if (m.length > 2) {
|
|
409
|
+
const r = m.sort((c, d) => c - d), [E, y] = [
|
|
410
|
+
W,
|
|
411
|
+
k
|
|
412
|
+
].map((c) => {
|
|
413
|
+
const d = Math.floor(
|
|
414
|
+
c * (r.length - 1)
|
|
415
|
+
), p = Math.ceil(
|
|
416
|
+
c * (r.length - 1)
|
|
417
|
+
);
|
|
418
|
+
return p === d ? r[d] : r[d] + (r[p] - r[d]) * (c * (r.length - 1) - d);
|
|
419
|
+
}), S = y - E;
|
|
420
|
+
this.measurements[s].loudnessRange = S;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
this.sTraceDirtyFlags[s] = !1;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (currentTime - this.previousTime >= Number(this.interval)) {
|
|
427
|
+
for (let n = 0; n < this.measurements.length; n++) {
|
|
428
|
+
const h = this.overSampledValues[n];
|
|
429
|
+
if (this.overSampledValueDirtyFlags[n]) {
|
|
430
|
+
const T = 20 * Math.log10(h) + V;
|
|
431
|
+
this.measurements[n].maximumTruePeakLevel = T, this.overSampledValueDirtyFlags[n] = !1;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const s = {
|
|
435
|
+
currentFrame,
|
|
436
|
+
currentTime,
|
|
437
|
+
currentMeasurements: this.measurements
|
|
438
|
+
};
|
|
439
|
+
this.port.postMessage(s), this.previousTime = currentTime;
|
|
440
|
+
}
|
|
441
|
+
for (let s = 0; s < Math.min(e.length, i.length); s++)
|
|
442
|
+
for (let n = 0; n < Math.min(e[s].length, i[s].length); n++)
|
|
443
|
+
i[s][n].set(e[s][n]);
|
|
444
|
+
return !0;
|
|
445
|
+
}
|
|
446
|
+
energyToLoudness(e) {
|
|
447
|
+
return -0.691 + 10 * Math.log10(Math.max(e, Number.EPSILON));
|
|
448
|
+
}
|
|
449
|
+
loudnessToEnergy(e) {
|
|
450
|
+
return 10 ** ((e + 0.691) / 10);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
registerProcessor("loudness-processor", j);
|
|
454
|
+
`, t = "loudness-processor";
|
|
455
|
+
class a extends AudioWorkletNode {
|
|
456
|
+
constructor(n, s) {
|
|
457
|
+
super(n, t, s);
|
|
458
|
+
}
|
|
459
|
+
static async loadModule(n) {
|
|
460
|
+
return r(n);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async function h(e, n) {
|
|
464
|
+
return await r(e), new AudioWorkletNode(e, t, n);
|
|
465
|
+
}
|
|
466
|
+
async function r(e) {
|
|
467
|
+
const n = new Blob([i], { type: "application/javascript" }), s = URL.createObjectURL(n);
|
|
468
|
+
try {
|
|
469
|
+
await e.audioWorklet.addModule(s);
|
|
470
|
+
} finally {
|
|
471
|
+
URL.revokeObjectURL(s);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
export {
|
|
475
|
+
a as LoudnessWorkletNode,
|
|
476
|
+
h as createLoudnessWorklet
|
|
477
|
+
};
|
package/dist/index.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
interface LoudnessWorkletProcessorOptions {
|
|
2
|
-
numberOfInputs?: AudioWorkletNodeOptions["numberOfInputs"];
|
|
3
|
-
numberOfOutputs?: AudioWorkletNodeOptions["numberOfOutputs"];
|
|
4
|
-
outputChannelCount?: AudioWorkletNodeOptions["outputChannelCount"];
|
|
5
|
-
processorOptions?: {
|
|
6
|
-
interval?: number;
|
|
7
|
-
capacity?: number;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
declare class LoudnessWorkletNode extends AudioWorkletNode {
|
|
11
|
-
constructor(context: BaseAudioContext, options?: LoudnessWorkletProcessorOptions);
|
|
12
|
-
static loadModule(context: BaseAudioContext): Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
declare function createLoudnessWorklet(context: BaseAudioContext, options?: LoudnessWorkletProcessorOptions): Promise<AudioWorkletNode>;
|
|
15
|
-
export { createLoudnessWorklet, LoudnessWorkletNode };
|
package/dist/index.js
DELETED
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
const i = `/**
|
|
2
|
-
* A lightweight and efficient AudioWorklet for real-time loudness measurement in the browser, compliant with the ITU-R BS.1770-5 standard.
|
|
3
|
-
*
|
|
4
|
-
* @file loudness.worklet.js
|
|
5
|
-
* @version 1.5.1
|
|
6
|
-
* @author lcweden
|
|
7
|
-
* @license MIT
|
|
8
|
-
* @see https://github.com/lcweden/loudness-worklet.git
|
|
9
|
-
* @date 2025-11-18T12:38:50.840Z
|
|
10
|
-
*/\r
|
|
11
|
-
\r
|
|
12
|
-
class O {
|
|
13
|
-
#t = new Float32Array(2);
|
|
14
|
-
#e = new Float32Array(3);
|
|
15
|
-
#s = new Float32Array(2);
|
|
16
|
-
#r = new Float32Array(2);
|
|
17
|
-
/**
|
|
18
|
-
* Creates a new BiquadraticFilter with given coefficients.
|
|
19
|
-
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
20
|
-
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
21
|
-
*/
|
|
22
|
-
constructor(e, r) {
|
|
23
|
-
this.reset(), this.set(e, r);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Processes a single input sample and returns the filtered output.
|
|
27
|
-
* @param { number } input - The input sample.
|
|
28
|
-
* @returns { number } - The filtered output sample.
|
|
29
|
-
*/
|
|
30
|
-
process(e) {
|
|
31
|
-
const r = this.#e[0] * e + this.#e[1] * this.#s[0] + this.#e[2] * this.#s[1] - this.#t[0] * this.#r[0] - this.#t[1] * this.#r[1];
|
|
32
|
-
return this.#s[1] = this.#s[0], this.#s[0] = e, this.#r[1] = this.#r[0], this.#r[0] = r, r;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Sets new filter coefficients.
|
|
36
|
-
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
37
|
-
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
38
|
-
* @returns { void }
|
|
39
|
-
*/
|
|
40
|
-
set(e, r) {
|
|
41
|
-
this.#t.set((e.length = 2, e)), this.#e.set((r.length = 3, r));
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Resets the filter state.
|
|
45
|
-
* @returns { void }
|
|
46
|
-
*/
|
|
47
|
-
reset() {
|
|
48
|
-
this.#s.fill(0), this.#r.fill(0);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
class d {
|
|
52
|
-
#t;
|
|
53
|
-
#e;
|
|
54
|
-
#s = 0;
|
|
55
|
-
#r = 0;
|
|
56
|
-
#n = 0;
|
|
57
|
-
/**
|
|
58
|
-
* Creates a new CircularBuffer with given capacity.
|
|
59
|
-
* @param { number } capacity - The maximum number of items the buffer can hold.
|
|
60
|
-
*/
|
|
61
|
-
constructor(e) {
|
|
62
|
-
this.#e = e || 0, this.#t = new Array(e);
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Adds an item to the buffer.
|
|
66
|
-
* @param item - The item to add to the buffer.
|
|
67
|
-
* @returns { void }
|
|
68
|
-
*/
|
|
69
|
-
push(e) {
|
|
70
|
-
this.#t[this.#r] = e, this.isFull() ? this.#s = (this.#s + 1) % this.#e : this.#n++, this.#r = (this.#r + 1) % this.#e;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Removes and returns the oldest item from the buffer.
|
|
74
|
-
* @returns { T | undefined }
|
|
75
|
-
*/
|
|
76
|
-
pop() {
|
|
77
|
-
if (this.isEmpty())
|
|
78
|
-
return;
|
|
79
|
-
const e = this.#t[this.#s];
|
|
80
|
-
return this.#s = (this.#s + 1) % this.#e, this.#n--, e;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Returns the oldest item from the buffer without removing it.
|
|
84
|
-
* @returns { T | undefined }
|
|
85
|
-
*/
|
|
86
|
-
peek() {
|
|
87
|
-
if (!this.isEmpty())
|
|
88
|
-
return this.#t[this.#s];
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Returns a slice of the buffer contents.
|
|
92
|
-
* @param { number } start - The starting index of the slice (inclusive).
|
|
93
|
-
* @param { number } end - The ending index of the slice (exclusive).
|
|
94
|
-
* @returns { T[] }
|
|
95
|
-
*/
|
|
96
|
-
slice(e = 0, r = this.#n) {
|
|
97
|
-
if (e < 0 && (e = 0), r > this.#n && (r = this.#n), e >= r)
|
|
98
|
-
return [];
|
|
99
|
-
const n = [];
|
|
100
|
-
for (let s = e; s < r; s++) {
|
|
101
|
-
const u = (this.#s + s) % this.#e;
|
|
102
|
-
n.push(this.#t[u]);
|
|
103
|
-
}
|
|
104
|
-
return n;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Checks if the buffer is empty.
|
|
108
|
-
* @returns { boolean }
|
|
109
|
-
*/
|
|
110
|
-
isEmpty() {
|
|
111
|
-
return this.#n === 0;
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Checks if the buffer is full.
|
|
115
|
-
* @returns { boolean }
|
|
116
|
-
*/
|
|
117
|
-
isFull() {
|
|
118
|
-
return this.#n === this.#e;
|
|
119
|
-
}
|
|
120
|
-
/** @type { number } */
|
|
121
|
-
get length() {
|
|
122
|
-
return this.#n;
|
|
123
|
-
}
|
|
124
|
-
/** @type { number } */
|
|
125
|
-
get capacity() {
|
|
126
|
-
return this.#e;
|
|
127
|
-
}
|
|
128
|
-
/** @type { IterableIterator<T> } */
|
|
129
|
-
*[Symbol.iterator]() {
|
|
130
|
-
for (let e = 0; e < this.#n; e++) {
|
|
131
|
-
const r = (this.#s + e) % this.#e;
|
|
132
|
-
yield this.#t[r];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const L = {
|
|
137
|
-
highshelf: { a: [-1.69065929318241, 0.73248077421585], b: [1.53512485958697, -2.69169618940638, 1.19839281085285] },
|
|
138
|
-
highpass: { a: [-1.99004745483398, 0.99007225036621], b: [1, -2, 1] }
|
|
139
|
-
}, R = {
|
|
140
|
-
lowpass: {
|
|
141
|
-
phase0: [
|
|
142
|
-
0.001708984375,
|
|
143
|
-
0.010986328125,
|
|
144
|
-
-0.0196533203125,
|
|
145
|
-
0.033203125,
|
|
146
|
-
-0.0594482421875,
|
|
147
|
-
0.1373291015625,
|
|
148
|
-
0.97216796875,
|
|
149
|
-
-0.102294921875,
|
|
150
|
-
0.047607421875,
|
|
151
|
-
-0.026611328125,
|
|
152
|
-
0.014892578125,
|
|
153
|
-
-0.00830078125
|
|
154
|
-
],
|
|
155
|
-
phase1: [
|
|
156
|
-
-0.0291748046875,
|
|
157
|
-
0.029296875,
|
|
158
|
-
-0.0517578125,
|
|
159
|
-
0.089111328125,
|
|
160
|
-
-0.16650390625,
|
|
161
|
-
0.465087890625,
|
|
162
|
-
0.77978515625,
|
|
163
|
-
-0.2003173828125,
|
|
164
|
-
0.1015625,
|
|
165
|
-
-0.0582275390625,
|
|
166
|
-
0.0330810546875,
|
|
167
|
-
-0.0189208984375
|
|
168
|
-
],
|
|
169
|
-
phase2: [
|
|
170
|
-
-0.0189208984375,
|
|
171
|
-
0.0330810546875,
|
|
172
|
-
-0.0582275390625,
|
|
173
|
-
0.1015625,
|
|
174
|
-
-0.2003173828125,
|
|
175
|
-
0.77978515625,
|
|
176
|
-
0.465087890625,
|
|
177
|
-
-0.16650390625,
|
|
178
|
-
0.089111328125,
|
|
179
|
-
-0.0517578125,
|
|
180
|
-
0.029296875,
|
|
181
|
-
-0.0291748046875
|
|
182
|
-
],
|
|
183
|
-
phase3: [
|
|
184
|
-
-0.00830078125,
|
|
185
|
-
0.014892578125,
|
|
186
|
-
-0.026611328125,
|
|
187
|
-
0.047607421875,
|
|
188
|
-
-0.102294921875,
|
|
189
|
-
0.97216796875,
|
|
190
|
-
0.1373291015625,
|
|
191
|
-
-0.0594482421875,
|
|
192
|
-
0.033203125,
|
|
193
|
-
-0.0196533203125,
|
|
194
|
-
0.010986328125,
|
|
195
|
-
0.001708984375
|
|
196
|
-
]
|
|
197
|
-
}
|
|
198
|
-
}, G = {
|
|
199
|
-
1: { mono: 1 },
|
|
200
|
-
2: { L: 1, R: 1 },
|
|
201
|
-
6: { L: 1, R: 1, C: 1, LFE: 0, Ls: 1.41, Rs: 1.41 },
|
|
202
|
-
8: { L: 1, R: 1, C: 1, LFE: 0, Lss: 1.41, Rss: 1.41, Lrs: 1, Rrs: 1 },
|
|
203
|
-
10: { L: 1, R: 1, C: 1, LFE: 0, Ls: 1.41, Rs: 1.41, Tfl: 1, Tfr: 1, Tbl: 1, Tbr: 1 },
|
|
204
|
-
12: {
|
|
205
|
-
L: 1,
|
|
206
|
-
R: 1,
|
|
207
|
-
C: 1,
|
|
208
|
-
LFE: 0,
|
|
209
|
-
Lss: 1.41,
|
|
210
|
-
Rss: 1.41,
|
|
211
|
-
Lrs: 1,
|
|
212
|
-
Rrs: 1,
|
|
213
|
-
Tfl: 1,
|
|
214
|
-
Tfr: 1,
|
|
215
|
-
Tbl: 1,
|
|
216
|
-
Tbr: 1
|
|
217
|
-
},
|
|
218
|
-
24: {
|
|
219
|
-
FL: 1.41,
|
|
220
|
-
FR: 1.41,
|
|
221
|
-
FC: 1,
|
|
222
|
-
LFE1: 0,
|
|
223
|
-
BL: 1,
|
|
224
|
-
BR: 1,
|
|
225
|
-
FLc: 1,
|
|
226
|
-
FRc: 1,
|
|
227
|
-
BC: 1,
|
|
228
|
-
LFE2: 0,
|
|
229
|
-
SiL: 1.41,
|
|
230
|
-
SiR: 1.41,
|
|
231
|
-
TpFL: 1,
|
|
232
|
-
TpFR: 1,
|
|
233
|
-
TpFC: 1,
|
|
234
|
-
TpC: 1,
|
|
235
|
-
TpBL: 1,
|
|
236
|
-
TpBR: 1,
|
|
237
|
-
TpSiL: 1,
|
|
238
|
-
TpSiR: 1,
|
|
239
|
-
TpBC: 1,
|
|
240
|
-
BtFC: 1,
|
|
241
|
-
BtFL: 1,
|
|
242
|
-
BtFR: 1
|
|
243
|
-
}
|
|
244
|
-
}, U = 0.4, B = 0.1, Y = 3, C = 0.1, j = 0.1, q = 0.95, w = 12.04, z = -70, K = -10, J = -70, Q = -20;
|
|
245
|
-
class F {
|
|
246
|
-
#t;
|
|
247
|
-
#e;
|
|
248
|
-
/**
|
|
249
|
-
* Creates an instance of the filter.
|
|
250
|
-
* @param coefficients - The filter coefficients.
|
|
251
|
-
*/
|
|
252
|
-
constructor(e) {
|
|
253
|
-
this.#t = e, this.#e = Array(e.length).fill(0);
|
|
254
|
-
}
|
|
255
|
-
process(e) {
|
|
256
|
-
if (Array.isArray(e))
|
|
257
|
-
return e.map((n) => this.process(n));
|
|
258
|
-
{
|
|
259
|
-
const r = e;
|
|
260
|
-
this.#e.pop(), this.#e.unshift(r);
|
|
261
|
-
let n = 0;
|
|
262
|
-
for (let s = 0; s < this.#t.length; s++)
|
|
263
|
-
n += this.#t[s] * this.#e[s];
|
|
264
|
-
return n;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Resets the filter state.
|
|
269
|
-
* @returns { void }
|
|
270
|
-
*/
|
|
271
|
-
reset() {
|
|
272
|
-
this.#e.fill(0);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
class X extends AudioWorkletProcessor {
|
|
276
|
-
capacity = null;
|
|
277
|
-
interval = null;
|
|
278
|
-
lastTime = 0;
|
|
279
|
-
metrics = [];
|
|
280
|
-
kWeightingFilters = [];
|
|
281
|
-
truePeakFilters = [];
|
|
282
|
-
momentaryEnergyBuffers = [];
|
|
283
|
-
momentaryEnergyRunningSums = [];
|
|
284
|
-
momentarySampleAccumulators = [];
|
|
285
|
-
momentaryLoudnessHistories = [];
|
|
286
|
-
shortTermEnergyBuffers = [];
|
|
287
|
-
shortTermEnergyRunningSums = [];
|
|
288
|
-
shortTermLoudnessHistories = [];
|
|
289
|
-
shortTermSampleAccumulators = [];
|
|
290
|
-
constructor(e) {
|
|
291
|
-
super();
|
|
292
|
-
const { numberOfInputs: r = 1, processorOptions: n } = e;
|
|
293
|
-
if (n) {
|
|
294
|
-
const { capacity: s, interval: u } = n;
|
|
295
|
-
this.capacity = s ?? null, this.interval = u ?? null;
|
|
296
|
-
}
|
|
297
|
-
for (let s = 0; s < r; s++)
|
|
298
|
-
this.momentaryEnergyRunningSums[s] = 0, this.momentarySampleAccumulators[s] = 0, this.momentaryEnergyBuffers[s] = new d(Math.round(sampleRate * U)), this.momentaryLoudnessHistories[s] = this.capacity ? new d(Math.ceil(this.capacity / B)) : new Array(), this.shortTermEnergyRunningSums[s] = 0, this.shortTermSampleAccumulators[s] = 0, this.shortTermEnergyBuffers[s] = new d(Math.round(sampleRate * Y)), this.shortTermLoudnessHistories[s] = this.capacity ? new d(Math.ceil(this.capacity / C)) : new Array(), this.metrics[s] = {
|
|
299
|
-
momentaryLoudness: Number.NEGATIVE_INFINITY,
|
|
300
|
-
shortTermLoudness: Number.NEGATIVE_INFINITY,
|
|
301
|
-
integratedLoudness: Number.NEGATIVE_INFINITY,
|
|
302
|
-
maximumMomentaryLoudness: Number.NEGATIVE_INFINITY,
|
|
303
|
-
maximumShortTermLoudness: Number.NEGATIVE_INFINITY,
|
|
304
|
-
maximumTruePeakLevel: Number.NEGATIVE_INFINITY,
|
|
305
|
-
loudnessRange: Number.NEGATIVE_INFINITY
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
process(e, r) {
|
|
309
|
-
const n = e.length;
|
|
310
|
-
for (let s = 0; s < n; s++) {
|
|
311
|
-
if (!e[s].length) continue;
|
|
312
|
-
const u = e[s], c = u.length, A = u[0].length, H = Object.values(G[c] || G[1]), x = Math.pow(10, -w / 20);
|
|
313
|
-
(!this.kWeightingFilters[s] || this.kWeightingFilters[s].length !== c) && (this.kWeightingFilters[s] = Array.from({ length: c }, () => [
|
|
314
|
-
new O(L.highshelf.a, L.highshelf.b),
|
|
315
|
-
new O(L.highpass.a, L.highpass.b)
|
|
316
|
-
])), (!this.truePeakFilters[s] || this.truePeakFilters[s].length !== c) && (this.truePeakFilters[s] = Array.from({ length: c }, () => [
|
|
317
|
-
new F(R.lowpass.phase0),
|
|
318
|
-
new F(R.lowpass.phase1),
|
|
319
|
-
new F(R.lowpass.phase2),
|
|
320
|
-
new F(R.lowpass.phase3)
|
|
321
|
-
]));
|
|
322
|
-
for (let o = 0; o < A; o++) {
|
|
323
|
-
let i = 0;
|
|
324
|
-
for (let t = 0; t < c; t++) {
|
|
325
|
-
const h = u[t][o], [y, T] = this.kWeightingFilters[s][t], m = y.process(h), f = T.process(m) ** 2, v = H[t] ?? 1;
|
|
326
|
-
i += f * v;
|
|
327
|
-
const P = u[t][o] * x, k = sampleRate >= 96e3 ? 2 : 4, b = [];
|
|
328
|
-
for (let N = 0; N < k; N++) {
|
|
329
|
-
const D = this.truePeakFilters[s][t][N];
|
|
330
|
-
b.push(Math.abs(D.process(P)));
|
|
331
|
-
}
|
|
332
|
-
const W = 20 * Math.log10(Math.max(...b)) + w, V = this.metrics[s].maximumTruePeakLevel;
|
|
333
|
-
this.metrics[s].maximumTruePeakLevel = Math.max(V, W);
|
|
334
|
-
}
|
|
335
|
-
const g = i, p = this.momentaryEnergyBuffers[s].peek() ?? 0, I = this.momentaryEnergyBuffers[s].isFull() ? p : 0;
|
|
336
|
-
this.momentaryEnergyRunningSums[s] += g - I, this.momentaryEnergyBuffers[s].push(g);
|
|
337
|
-
const E = this.shortTermEnergyBuffers[s].peek() ?? 0, l = this.shortTermEnergyBuffers[s].isFull() ? E : 0;
|
|
338
|
-
if (this.shortTermEnergyRunningSums[s] += g - l, this.shortTermEnergyBuffers[s].push(g), this.momentaryEnergyBuffers[s].isFull()) {
|
|
339
|
-
const t = this.momentaryEnergyRunningSums[s] / this.momentaryEnergyBuffers[s].capacity, h = this.#s(t);
|
|
340
|
-
this.metrics[s].momentaryLoudness = h, this.metrics[s].maximumMomentaryLoudness = Math.max(
|
|
341
|
-
this.metrics[s].maximumMomentaryLoudness,
|
|
342
|
-
h
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
this.momentarySampleAccumulators[s] += A;
|
|
347
|
-
const _ = Math.round(sampleRate * B);
|
|
348
|
-
for (; this.momentarySampleAccumulators[s] >= _; ) {
|
|
349
|
-
if (this.momentaryEnergyBuffers[s].isFull()) {
|
|
350
|
-
const o = this.momentaryEnergyRunningSums[s] / this.momentaryEnergyBuffers[s].capacity, i = this.#s(o);
|
|
351
|
-
this.momentaryLoudnessHistories[s].push(i);
|
|
352
|
-
}
|
|
353
|
-
this.momentarySampleAccumulators[s] -= _;
|
|
354
|
-
}
|
|
355
|
-
this.shortTermSampleAccumulators[s] += A;
|
|
356
|
-
const M = Math.round(sampleRate * C);
|
|
357
|
-
for (; this.shortTermSampleAccumulators[s] >= M; ) {
|
|
358
|
-
if (this.shortTermEnergyBuffers[s].isFull()) {
|
|
359
|
-
const o = this.shortTermEnergyRunningSums[s] / this.shortTermEnergyBuffers[s].capacity, i = this.#s(o);
|
|
360
|
-
this.metrics[s].shortTermLoudness = i, this.metrics[s].maximumShortTermLoudness = Math.max(
|
|
361
|
-
this.metrics[s].maximumShortTermLoudness,
|
|
362
|
-
i
|
|
363
|
-
), this.shortTermLoudnessHistories[s].push(i);
|
|
364
|
-
}
|
|
365
|
-
this.shortTermSampleAccumulators[s] -= M;
|
|
366
|
-
}
|
|
367
|
-
if (this.momentaryLoudnessHistories[s].length > 2) {
|
|
368
|
-
const o = Array.from(this.momentaryLoudnessHistories[s]).filter(
|
|
369
|
-
(i) => i > z
|
|
370
|
-
);
|
|
371
|
-
if (o.length > 2) {
|
|
372
|
-
const i = o.map(this.#r), p = i.reduce((t, h) => t + h, 0) / i.length, E = this.#s(p) + K, l = o.filter((t) => t > E);
|
|
373
|
-
if (l.length > 2) {
|
|
374
|
-
const t = l.map(this.#r), y = t.reduce((m, a) => m + a, 0) / t.length, T = this.#s(y);
|
|
375
|
-
this.metrics[s].integratedLoudness = T;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (this.shortTermLoudnessHistories[s].length > 2) {
|
|
380
|
-
const o = Array.from(this.shortTermLoudnessHistories[s]).filter(
|
|
381
|
-
(i) => i > J
|
|
382
|
-
);
|
|
383
|
-
if (o.length > 2) {
|
|
384
|
-
const i = o.map(this.#r), p = i.reduce((t, h) => t + h, 0) / i.length, E = this.#s(p) + Q, l = o.filter((t) => t > E);
|
|
385
|
-
if (l.length > 2) {
|
|
386
|
-
const t = l.toSorted((m, a) => m - a), [h, y] = [
|
|
387
|
-
j,
|
|
388
|
-
q
|
|
389
|
-
].map((m) => {
|
|
390
|
-
const a = Math.floor(m * (t.length - 1)), f = Math.ceil(m * (t.length - 1));
|
|
391
|
-
return f === a ? t[a] : t[a] + (t[f] - t[a]) * (m * (t.length - 1) - a);
|
|
392
|
-
}), T = y - h;
|
|
393
|
-
this.metrics[s].loudnessRange = T;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
return this.#e(e, r), this.#t(), !0;
|
|
399
|
-
}
|
|
400
|
-
#t() {
|
|
401
|
-
if (currentTime - this.lastTime >= Number(this.interval)) {
|
|
402
|
-
const e = { currentFrame, currentTime, currentMetrics: this.metrics };
|
|
403
|
-
this.port.postMessage(e), this.lastTime = currentTime;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
#e(e, r) {
|
|
407
|
-
for (let n = 0; n < Math.min(e.length, r.length); n++)
|
|
408
|
-
for (let s = 0; s < Math.min(e[n].length, r[n].length); s++)
|
|
409
|
-
r[n][s].set(e[n][s]);
|
|
410
|
-
}
|
|
411
|
-
#s(e) {
|
|
412
|
-
return -0.691 + 10 * Math.log10(Math.max(e, Number.EPSILON));
|
|
413
|
-
}
|
|
414
|
-
#r(e) {
|
|
415
|
-
return Math.pow(10, (e + 0.691) / 10);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
registerProcessor("loudness-processor", X);
|
|
419
|
-
`, t = "loudness-processor";
|
|
420
|
-
class h extends AudioWorkletNode {
|
|
421
|
-
constructor(n, s) {
|
|
422
|
-
super(n, t, s);
|
|
423
|
-
}
|
|
424
|
-
static async loadModule(n) {
|
|
425
|
-
return r(n);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
async function o(e, n) {
|
|
429
|
-
return await r(e), new AudioWorkletNode(e, t, n);
|
|
430
|
-
}
|
|
431
|
-
async function r(e) {
|
|
432
|
-
const n = new Blob([i], { type: "application/javascript" }), s = URL.createObjectURL(n);
|
|
433
|
-
try {
|
|
434
|
-
await e.audioWorklet.addModule(s);
|
|
435
|
-
} finally {
|
|
436
|
-
URL.revokeObjectURL(s);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
export {
|
|
440
|
-
h as LoudnessWorkletNode,
|
|
441
|
-
o as createLoudnessWorklet
|
|
442
|
-
};
|