loudness-worklet 1.5.1 → 1.6.0
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 +12 -12
- package/package.json +14 -30
- package/packages/lib/dist/index.d.ts +34 -0
- package/{dist → packages/lib/dist}/index.js +269 -185
- package/dist/index.d.ts +0 -15
package/README.md
CHANGED
|
@@ -244,20 +244,20 @@ const worklet = new AudioWorkletNode(context, "loudness-processor", {
|
|
|
244
244
|
Measurement results are sent back to the main thread via `port.onmessage` with the following format:
|
|
245
245
|
|
|
246
246
|
```typescript
|
|
247
|
-
type
|
|
247
|
+
type LoudnessMeasurements = {
|
|
248
|
+
momentaryLoudness: number;
|
|
249
|
+
shortTermLoudness: number;
|
|
250
|
+
integratedLoudness: number;
|
|
251
|
+
maximumMomentaryLoudness: number;
|
|
252
|
+
maximumShortTermLoudness: number;
|
|
253
|
+
maximumTruePeakLevel: number;
|
|
254
|
+
loudnessRange: number;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
type LoudnessSnapshot = {
|
|
248
258
|
currentFrame: number;
|
|
249
259
|
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
|
-
];
|
|
260
|
+
currentMeasurements: LoudnessMeasurements[];
|
|
261
261
|
};
|
|
262
262
|
```
|
|
263
263
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loudness-worklet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
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,34 @@
|
|
|
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 LoudnessSnapshot = {
|
|
14
|
+
currentFrame: number;
|
|
15
|
+
currentTime: number;
|
|
16
|
+
currentMeasurements: LoudnessMeasurements[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export declare class LoudnessWorkletNode extends AudioWorkletNode {
|
|
20
|
+
constructor(context: BaseAudioContext, options?: LoudnessWorkletProcessorOptions);
|
|
21
|
+
static loadModule(context: BaseAudioContext): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare interface LoudnessWorkletProcessorOptions {
|
|
25
|
+
numberOfInputs?: AudioWorkletNodeOptions["numberOfInputs"];
|
|
26
|
+
numberOfOutputs?: AudioWorkletNodeOptions["numberOfOutputs"];
|
|
27
|
+
outputChannelCount?: AudioWorkletNodeOptions["outputChannelCount"];
|
|
28
|
+
processorOptions?: {
|
|
29
|
+
interval?: number;
|
|
30
|
+
capacity?: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { }
|
|
@@ -1,142 +1,10 @@
|
|
|
1
|
-
const i =
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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] },
|
|
1
|
+
const i = `const d = {
|
|
2
|
+
highshelf: {
|
|
3
|
+
a: [-1.69065929318241, 0.73248077421585],
|
|
4
|
+
b: [1.53512485958697, -2.69169618940638, 1.19839281085285]
|
|
5
|
+
},
|
|
138
6
|
highpass: { a: [-1.99004745483398, 0.99007225036621], b: [1, -2, 1] }
|
|
139
|
-
},
|
|
7
|
+
}, L = {
|
|
140
8
|
lowpass: {
|
|
141
9
|
phase0: [
|
|
142
10
|
0.001708984375,
|
|
@@ -195,12 +63,32 @@ const L = {
|
|
|
195
63
|
0.001708984375
|
|
196
64
|
]
|
|
197
65
|
}
|
|
198
|
-
},
|
|
66
|
+
}, O = {
|
|
199
67
|
1: { mono: 1 },
|
|
200
68
|
2: { L: 1, R: 1 },
|
|
201
69
|
6: { L: 1, R: 1, C: 1, LFE: 0, Ls: 1.41, Rs: 1.41 },
|
|
202
|
-
8: {
|
|
203
|
-
|
|
70
|
+
8: {
|
|
71
|
+
L: 1,
|
|
72
|
+
R: 1,
|
|
73
|
+
C: 1,
|
|
74
|
+
LFE: 0,
|
|
75
|
+
Lss: 1.41,
|
|
76
|
+
Rss: 1.41,
|
|
77
|
+
Lrs: 1,
|
|
78
|
+
Rrs: 1
|
|
79
|
+
},
|
|
80
|
+
10: {
|
|
81
|
+
L: 1,
|
|
82
|
+
R: 1,
|
|
83
|
+
C: 1,
|
|
84
|
+
LFE: 0,
|
|
85
|
+
Ls: 1.41,
|
|
86
|
+
Rs: 1.41,
|
|
87
|
+
Tfl: 1,
|
|
88
|
+
Tfr: 1,
|
|
89
|
+
Tbl: 1,
|
|
90
|
+
Tbr: 1
|
|
91
|
+
},
|
|
204
92
|
12: {
|
|
205
93
|
L: 1,
|
|
206
94
|
R: 1,
|
|
@@ -241,8 +129,47 @@ const L = {
|
|
|
241
129
|
BtFL: 1,
|
|
242
130
|
BtFR: 1
|
|
243
131
|
}
|
|
244
|
-
}, U = 0.4,
|
|
245
|
-
class
|
|
132
|
+
}, U = 0.4, G = 0.1, Y = 3, B = 0.1, j = 0.1, q = 0.95, C = 12.04, z = -70, K = -10, J = -70, Q = -20;
|
|
133
|
+
class x {
|
|
134
|
+
#t = new Float32Array(2);
|
|
135
|
+
#e = new Float32Array(3);
|
|
136
|
+
#s = new Float32Array(2);
|
|
137
|
+
#r = new Float32Array(2);
|
|
138
|
+
/**
|
|
139
|
+
* Creates a new BiquadraticFilter with given coefficients.
|
|
140
|
+
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
141
|
+
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
142
|
+
*/
|
|
143
|
+
constructor(e, r) {
|
|
144
|
+
this.reset(), this.set(e, r);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Processes a single input sample and returns the filtered output.
|
|
148
|
+
* @param { number } input - The input sample.
|
|
149
|
+
* @returns { number } - The filtered output sample.
|
|
150
|
+
*/
|
|
151
|
+
process(e) {
|
|
152
|
+
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];
|
|
153
|
+
return this.#s[1] = this.#s[0], this.#s[0] = e, this.#r[1] = this.#r[0], this.#r[0] = r, r;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Sets new filter coefficients.
|
|
157
|
+
* @param { number[] } a - Feedback coefficients [a1, a2]
|
|
158
|
+
* @param { number[] } b - Feedforward coefficients [b0, b1, b2]
|
|
159
|
+
* @returns { void }
|
|
160
|
+
*/
|
|
161
|
+
set(e, r) {
|
|
162
|
+
e.length = 2, this.#t.set(e), r.length = 3, this.#e.set(r);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Resets the filter state.
|
|
166
|
+
* @returns { void }
|
|
167
|
+
*/
|
|
168
|
+
reset() {
|
|
169
|
+
this.#s.fill(0), this.#r.fill(0);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
class R {
|
|
246
173
|
#t;
|
|
247
174
|
#e;
|
|
248
175
|
/**
|
|
@@ -272,11 +199,96 @@ class F {
|
|
|
272
199
|
this.#e.fill(0);
|
|
273
200
|
}
|
|
274
201
|
}
|
|
202
|
+
class F {
|
|
203
|
+
#t;
|
|
204
|
+
#e;
|
|
205
|
+
#s;
|
|
206
|
+
#r;
|
|
207
|
+
#n;
|
|
208
|
+
/**
|
|
209
|
+
* Creates a new CircularBuffer with given capacity.
|
|
210
|
+
* @param { number } capacity - The maximum number of items the buffer can hold.
|
|
211
|
+
*/
|
|
212
|
+
constructor(e) {
|
|
213
|
+
this.#e = e || 0, this.#t = new Array(e), this.#s = 0, this.#r = 0, this.#n = 0;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Adds an item to the buffer.
|
|
217
|
+
* @param item - The item to add to the buffer.
|
|
218
|
+
* @returns { void }
|
|
219
|
+
*/
|
|
220
|
+
push(e) {
|
|
221
|
+
this.#t[this.#r] = e, this.isFull() ? this.#s = (this.#s + 1) % this.#e : this.#n++, this.#r = (this.#r + 1) % this.#e;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Removes and returns the oldest item from the buffer.
|
|
225
|
+
* @returns { T | undefined }
|
|
226
|
+
*/
|
|
227
|
+
pop() {
|
|
228
|
+
if (this.isEmpty())
|
|
229
|
+
return;
|
|
230
|
+
const e = this.#t[this.#s];
|
|
231
|
+
return this.#t[this.#s] = void 0, this.#s = (this.#s + 1) % this.#e, this.#n--, e;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns the oldest item from the buffer without removing it.
|
|
235
|
+
* @returns { T | undefined }
|
|
236
|
+
*/
|
|
237
|
+
peek() {
|
|
238
|
+
if (!this.isEmpty())
|
|
239
|
+
return this.#t[this.#s];
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Returns a slice of the buffer contents.
|
|
243
|
+
* @param { number } start - The starting index of the slice (inclusive).
|
|
244
|
+
* @param { number } end - The ending index of the slice (exclusive).
|
|
245
|
+
* @returns { T[] }
|
|
246
|
+
*/
|
|
247
|
+
slice(e, r) {
|
|
248
|
+
if (e >= r)
|
|
249
|
+
return [];
|
|
250
|
+
const n = [];
|
|
251
|
+
for (let s = Math.max(0, e); s < Math.min(this.#n, r); s++) {
|
|
252
|
+
const u = (this.#s + s) % this.#e;
|
|
253
|
+
n.push(this.#t[u]);
|
|
254
|
+
}
|
|
255
|
+
return n;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Checks if the buffer is empty.
|
|
259
|
+
* @returns { boolean }
|
|
260
|
+
*/
|
|
261
|
+
isEmpty() {
|
|
262
|
+
return this.#n === 0;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Checks if the buffer is full.
|
|
266
|
+
* @returns { boolean }
|
|
267
|
+
*/
|
|
268
|
+
isFull() {
|
|
269
|
+
return this.#n === this.#e;
|
|
270
|
+
}
|
|
271
|
+
/** @type { number } */
|
|
272
|
+
get length() {
|
|
273
|
+
return this.#n;
|
|
274
|
+
}
|
|
275
|
+
/** @type { number } */
|
|
276
|
+
get capacity() {
|
|
277
|
+
return this.#e;
|
|
278
|
+
}
|
|
279
|
+
/** @type { IterableIterator<T> } */
|
|
280
|
+
*[Symbol.iterator]() {
|
|
281
|
+
for (let e = 0; e < this.#n; e++) {
|
|
282
|
+
const r = (this.#s + e) % this.#e;
|
|
283
|
+
yield this.#t[r];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
275
287
|
class X extends AudioWorkletProcessor {
|
|
276
288
|
capacity = null;
|
|
277
289
|
interval = null;
|
|
278
290
|
lastTime = 0;
|
|
279
|
-
|
|
291
|
+
measurements = [];
|
|
280
292
|
kWeightingFilters = [];
|
|
281
293
|
truePeakFilters = [];
|
|
282
294
|
momentaryEnergyBuffers = [];
|
|
@@ -295,7 +307,15 @@ class X extends AudioWorkletProcessor {
|
|
|
295
307
|
this.capacity = s ?? null, this.interval = u ?? null;
|
|
296
308
|
}
|
|
297
309
|
for (let s = 0; s < r; s++)
|
|
298
|
-
this.momentaryEnergyRunningSums[s] = 0, this.momentarySampleAccumulators[s] = 0, this.momentaryEnergyBuffers[s] = new
|
|
310
|
+
this.momentaryEnergyRunningSums[s] = 0, this.momentarySampleAccumulators[s] = 0, this.momentaryEnergyBuffers[s] = new F(
|
|
311
|
+
Math.round(sampleRate * U)
|
|
312
|
+
), this.momentaryLoudnessHistories[s] = this.capacity ? new F(
|
|
313
|
+
Math.ceil(this.capacity / G)
|
|
314
|
+
) : [], this.shortTermEnergyRunningSums[s] = 0, this.shortTermSampleAccumulators[s] = 0, this.shortTermEnergyBuffers[s] = new F(
|
|
315
|
+
Math.round(sampleRate * Y)
|
|
316
|
+
), this.shortTermLoudnessHistories[s] = this.capacity ? new F(
|
|
317
|
+
Math.ceil(this.capacity / B)
|
|
318
|
+
) : [], this.measurements[s] = {
|
|
299
319
|
momentaryLoudness: Number.NEGATIVE_INFINITY,
|
|
300
320
|
shortTermLoudness: Number.NEGATIVE_INFINITY,
|
|
301
321
|
integratedLoudness: Number.NEGATIVE_INFINITY,
|
|
@@ -309,42 +329,69 @@ class X extends AudioWorkletProcessor {
|
|
|
309
329
|
const n = e.length;
|
|
310
330
|
for (let s = 0; s < n; s++) {
|
|
311
331
|
if (!e[s].length) continue;
|
|
312
|
-
const u = e[s], c = u.length, A = u[0].length, H = Object.values(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
332
|
+
const u = e[s], c = u.length, A = u[0].length, H = Object.values(
|
|
333
|
+
O[c] || O[1]
|
|
334
|
+
), v = 10 ** (-C / 20);
|
|
335
|
+
(!this.kWeightingFilters[s] || this.kWeightingFilters[s].length !== c) && (this.kWeightingFilters[s] = Array.from(
|
|
336
|
+
{ length: c },
|
|
337
|
+
() => [
|
|
338
|
+
new x(
|
|
339
|
+
d.highshelf.a,
|
|
340
|
+
d.highshelf.b
|
|
341
|
+
),
|
|
342
|
+
new x(
|
|
343
|
+
d.highpass.a,
|
|
344
|
+
d.highpass.b
|
|
345
|
+
)
|
|
346
|
+
]
|
|
347
|
+
)), (!this.truePeakFilters[s] || this.truePeakFilters[s].length !== c) && (this.truePeakFilters[s] = Array.from(
|
|
348
|
+
{ length: c },
|
|
349
|
+
() => [
|
|
350
|
+
new R(
|
|
351
|
+
L.lowpass.phase0
|
|
352
|
+
),
|
|
353
|
+
new R(
|
|
354
|
+
L.lowpass.phase1
|
|
355
|
+
),
|
|
356
|
+
new R(
|
|
357
|
+
L.lowpass.phase2
|
|
358
|
+
),
|
|
359
|
+
new R(
|
|
360
|
+
L.lowpass.phase3
|
|
361
|
+
)
|
|
362
|
+
]
|
|
363
|
+
));
|
|
322
364
|
for (let o = 0; o < A; o++) {
|
|
323
365
|
let i = 0;
|
|
324
366
|
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,
|
|
326
|
-
i += f *
|
|
327
|
-
const P = u[t][o] *
|
|
367
|
+
const h = u[t][o], [y, T] = this.kWeightingFilters[s][t], m = y.process(h), f = T.process(m) ** 2, w = H[t] ?? 1;
|
|
368
|
+
i += f * w;
|
|
369
|
+
const P = u[t][o] * v, k = sampleRate >= 96e3 ? 2 : 4, b = [];
|
|
328
370
|
for (let N = 0; N < k; N++) {
|
|
329
371
|
const D = this.truePeakFilters[s][t][N];
|
|
330
372
|
b.push(Math.abs(D.process(P)));
|
|
331
373
|
}
|
|
332
|
-
const W = 20 * Math.log10(Math.max(...b)) +
|
|
333
|
-
this.
|
|
374
|
+
const W = 20 * Math.log10(Math.max(...b)) + C, V = this.measurements[s].maximumTruePeakLevel;
|
|
375
|
+
this.measurements[s].maximumTruePeakLevel = Math.max(
|
|
376
|
+
V,
|
|
377
|
+
W
|
|
378
|
+
);
|
|
334
379
|
}
|
|
335
|
-
const g = i,
|
|
380
|
+
const g = i, E = this.momentaryEnergyBuffers[s].peek() ?? 0, I = this.momentaryEnergyBuffers[s].isFull() ? E : 0;
|
|
336
381
|
this.momentaryEnergyRunningSums[s] += g - I, this.momentaryEnergyBuffers[s].push(g);
|
|
337
|
-
const
|
|
382
|
+
const p = this.shortTermEnergyBuffers[s].peek() ?? 0, l = this.shortTermEnergyBuffers[s].isFull() ? p : 0;
|
|
338
383
|
if (this.shortTermEnergyRunningSums[s] += g - l, this.shortTermEnergyBuffers[s].push(g), this.momentaryEnergyBuffers[s].isFull()) {
|
|
339
384
|
const t = this.momentaryEnergyRunningSums[s] / this.momentaryEnergyBuffers[s].capacity, h = this.#s(t);
|
|
340
|
-
this.
|
|
341
|
-
this.
|
|
385
|
+
this.measurements[s].momentaryLoudness = h, this.measurements[s].maximumMomentaryLoudness = Math.max(
|
|
386
|
+
this.measurements[s].maximumMomentaryLoudness,
|
|
342
387
|
h
|
|
343
388
|
);
|
|
344
389
|
}
|
|
345
390
|
}
|
|
346
391
|
this.momentarySampleAccumulators[s] += A;
|
|
347
|
-
const _ = Math.round(
|
|
392
|
+
const _ = Math.round(
|
|
393
|
+
sampleRate * G
|
|
394
|
+
);
|
|
348
395
|
for (; this.momentarySampleAccumulators[s] >= _; ) {
|
|
349
396
|
if (this.momentaryEnergyBuffers[s].isFull()) {
|
|
350
397
|
const o = this.momentaryEnergyRunningSums[s] / this.momentaryEnergyBuffers[s].capacity, i = this.#s(o);
|
|
@@ -353,44 +400,77 @@ class X extends AudioWorkletProcessor {
|
|
|
353
400
|
this.momentarySampleAccumulators[s] -= _;
|
|
354
401
|
}
|
|
355
402
|
this.shortTermSampleAccumulators[s] += A;
|
|
356
|
-
const M = Math.round(
|
|
403
|
+
const M = Math.round(
|
|
404
|
+
sampleRate * B
|
|
405
|
+
);
|
|
357
406
|
for (; this.shortTermSampleAccumulators[s] >= M; ) {
|
|
358
407
|
if (this.shortTermEnergyBuffers[s].isFull()) {
|
|
359
408
|
const o = this.shortTermEnergyRunningSums[s] / this.shortTermEnergyBuffers[s].capacity, i = this.#s(o);
|
|
360
|
-
this.
|
|
361
|
-
this.
|
|
409
|
+
this.measurements[s].shortTermLoudness = i, this.measurements[s].maximumShortTermLoudness = Math.max(
|
|
410
|
+
this.measurements[s].maximumShortTermLoudness,
|
|
362
411
|
i
|
|
363
412
|
), this.shortTermLoudnessHistories[s].push(i);
|
|
364
413
|
}
|
|
365
414
|
this.shortTermSampleAccumulators[s] -= M;
|
|
366
415
|
}
|
|
367
416
|
if (this.momentaryLoudnessHistories[s].length > 2) {
|
|
368
|
-
const o = Array.from(
|
|
369
|
-
|
|
370
|
-
);
|
|
417
|
+
const o = Array.from(
|
|
418
|
+
this.momentaryLoudnessHistories[s]
|
|
419
|
+
).filter((i) => i > z);
|
|
371
420
|
if (o.length > 2) {
|
|
372
|
-
const i = o.map(
|
|
421
|
+
const i = o.map(
|
|
422
|
+
this.#r
|
|
423
|
+
), E = i.reduce(
|
|
424
|
+
(t, h) => t + h,
|
|
425
|
+
0
|
|
426
|
+
) / i.length, p = this.#s(
|
|
427
|
+
E
|
|
428
|
+
) + K, l = o.filter(
|
|
429
|
+
(t) => t > p
|
|
430
|
+
);
|
|
373
431
|
if (l.length > 2) {
|
|
374
|
-
const t = l.map(
|
|
375
|
-
|
|
432
|
+
const t = l.map(
|
|
433
|
+
this.#r
|
|
434
|
+
), y = t.reduce(
|
|
435
|
+
(m, a) => m + a,
|
|
436
|
+
0
|
|
437
|
+
) / t.length, T = this.#s(
|
|
438
|
+
y
|
|
439
|
+
);
|
|
440
|
+
this.measurements[s].integratedLoudness = T;
|
|
376
441
|
}
|
|
377
442
|
}
|
|
378
443
|
}
|
|
379
444
|
if (this.shortTermLoudnessHistories[s].length > 2) {
|
|
380
|
-
const o = Array.from(
|
|
381
|
-
|
|
382
|
-
);
|
|
445
|
+
const o = Array.from(
|
|
446
|
+
this.shortTermLoudnessHistories[s]
|
|
447
|
+
).filter((i) => i > J);
|
|
383
448
|
if (o.length > 2) {
|
|
384
|
-
const i = o.map(
|
|
449
|
+
const i = o.map(
|
|
450
|
+
this.#r
|
|
451
|
+
), E = i.reduce(
|
|
452
|
+
(t, h) => t + h,
|
|
453
|
+
0
|
|
454
|
+
) / i.length, p = this.#s(
|
|
455
|
+
E
|
|
456
|
+
) + Q, l = o.filter(
|
|
457
|
+
(t) => t > p
|
|
458
|
+
);
|
|
385
459
|
if (l.length > 2) {
|
|
386
|
-
const t = l.toSorted(
|
|
460
|
+
const t = l.toSorted(
|
|
461
|
+
(m, a) => m - a
|
|
462
|
+
), [h, y] = [
|
|
387
463
|
j,
|
|
388
464
|
q
|
|
389
465
|
].map((m) => {
|
|
390
|
-
const a = Math.floor(
|
|
466
|
+
const a = Math.floor(
|
|
467
|
+
m * (t.length - 1)
|
|
468
|
+
), f = Math.ceil(
|
|
469
|
+
m * (t.length - 1)
|
|
470
|
+
);
|
|
391
471
|
return f === a ? t[a] : t[a] + (t[f] - t[a]) * (m * (t.length - 1) - a);
|
|
392
472
|
}), T = y - h;
|
|
393
|
-
this.
|
|
473
|
+
this.measurements[s].loudnessRange = T;
|
|
394
474
|
}
|
|
395
475
|
}
|
|
396
476
|
}
|
|
@@ -399,7 +479,11 @@ class X extends AudioWorkletProcessor {
|
|
|
399
479
|
}
|
|
400
480
|
#t() {
|
|
401
481
|
if (currentTime - this.lastTime >= Number(this.interval)) {
|
|
402
|
-
const e = {
|
|
482
|
+
const e = {
|
|
483
|
+
currentFrame,
|
|
484
|
+
currentTime,
|
|
485
|
+
currentMeasurements: this.measurements
|
|
486
|
+
};
|
|
403
487
|
this.port.postMessage(e), this.lastTime = currentTime;
|
|
404
488
|
}
|
|
405
489
|
}
|
|
@@ -412,7 +496,7 @@ class X extends AudioWorkletProcessor {
|
|
|
412
496
|
return -0.691 + 10 * Math.log10(Math.max(e, Number.EPSILON));
|
|
413
497
|
}
|
|
414
498
|
#r(e) {
|
|
415
|
-
return
|
|
499
|
+
return 10 ** ((e + 0.691) / 10);
|
|
416
500
|
}
|
|
417
501
|
}
|
|
418
502
|
registerProcessor("loudness-processor", X);
|
|
@@ -425,7 +509,7 @@ class h extends AudioWorkletNode {
|
|
|
425
509
|
return r(n);
|
|
426
510
|
}
|
|
427
511
|
}
|
|
428
|
-
async function
|
|
512
|
+
async function a(e, n) {
|
|
429
513
|
return await r(e), new AudioWorkletNode(e, t, n);
|
|
430
514
|
}
|
|
431
515
|
async function r(e) {
|
|
@@ -438,5 +522,5 @@ async function r(e) {
|
|
|
438
522
|
}
|
|
439
523
|
export {
|
|
440
524
|
h as LoudnessWorkletNode,
|
|
441
|
-
|
|
525
|
+
a as createLoudnessWorklet
|
|
442
526
|
};
|
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 };
|