@buffered-audio/nodes 0.16.0 → 0.17.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 +23 -10
- package/dist/cli.js +100 -6
- package/dist/index.d.ts +90 -398
- package/dist/index.js +1218 -1176
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import path, { extname, join } from 'path';
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
3
|
import { createWriteStream, createReadStream } from 'fs';
|
|
4
|
-
import { open, stat,
|
|
4
|
+
import { open, stat, readFile, unlink, mkdtemp, writeFile, rm } from 'fs/promises';
|
|
5
5
|
import { tmpdir } from 'os';
|
|
6
|
+
import { Readable } from 'stream';
|
|
6
7
|
import { EventEmitter } from 'events';
|
|
7
8
|
import { spawn } from 'child_process';
|
|
8
9
|
import { createRequire } from 'module';
|
|
@@ -15575,6 +15576,66 @@ var __export2 = (target, all) => {
|
|
|
15575
15576
|
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
15576
15577
|
};
|
|
15577
15578
|
var HIGH_WATER_MARK = 10 * 1024 * 1024;
|
|
15579
|
+
var REVERSE_STRIPE_BYTES = 10 * 1024 * 1024;
|
|
15580
|
+
function deinterleave(buf, channels) {
|
|
15581
|
+
const bytesPerFrame = channels * 4;
|
|
15582
|
+
const frames = Math.floor(buf.length / bytesPerFrame);
|
|
15583
|
+
const interleaved = new Float32Array(buf.buffer, buf.byteOffset, frames * channels);
|
|
15584
|
+
const out = [];
|
|
15585
|
+
for (let ch = 0; ch < channels; ch++) out.push(new Float32Array(frames));
|
|
15586
|
+
for (let frame = 0; frame < frames; frame++) {
|
|
15587
|
+
const base = frame * channels;
|
|
15588
|
+
for (let ch = 0; ch < channels; ch++) {
|
|
15589
|
+
out[ch][frame] = interleaved[base + ch];
|
|
15590
|
+
}
|
|
15591
|
+
}
|
|
15592
|
+
return out;
|
|
15593
|
+
}
|
|
15594
|
+
function buildAudioChunk(samples, offset, sampleRate, bitDepth) {
|
|
15595
|
+
return { samples, offset, sampleRate: sampleRate ?? 0, bitDepth: bitDepth ?? 0 };
|
|
15596
|
+
}
|
|
15597
|
+
async function pullBytes(rs, isEnded, bytesNeeded) {
|
|
15598
|
+
const chunks = [];
|
|
15599
|
+
let collected = 0;
|
|
15600
|
+
while (collected < bytesNeeded) {
|
|
15601
|
+
const chunk = rs.read();
|
|
15602
|
+
if (chunk !== null) {
|
|
15603
|
+
const remaining = bytesNeeded - collected;
|
|
15604
|
+
if (chunk.length <= remaining) {
|
|
15605
|
+
chunks.push(chunk);
|
|
15606
|
+
collected += chunk.length;
|
|
15607
|
+
} else {
|
|
15608
|
+
chunks.push(chunk.subarray(0, remaining));
|
|
15609
|
+
collected += remaining;
|
|
15610
|
+
rs.unshift(chunk.subarray(remaining));
|
|
15611
|
+
}
|
|
15612
|
+
continue;
|
|
15613
|
+
}
|
|
15614
|
+
if (isEnded()) break;
|
|
15615
|
+
await new Promise((resolve) => {
|
|
15616
|
+
const wake = () => {
|
|
15617
|
+
rs.off("readable", wake);
|
|
15618
|
+
rs.off("end", wake);
|
|
15619
|
+
rs.off("error", wake);
|
|
15620
|
+
rs.off("close", wake);
|
|
15621
|
+
resolve();
|
|
15622
|
+
};
|
|
15623
|
+
rs.once("readable", wake);
|
|
15624
|
+
rs.once("end", wake);
|
|
15625
|
+
rs.once("error", wake);
|
|
15626
|
+
rs.once("close", wake);
|
|
15627
|
+
});
|
|
15628
|
+
}
|
|
15629
|
+
if (chunks.length === 0) return Buffer.alloc(0);
|
|
15630
|
+
if (chunks.length === 1) return chunks[0];
|
|
15631
|
+
return Buffer.concat(chunks);
|
|
15632
|
+
}
|
|
15633
|
+
async function awaitStreamClose(stream) {
|
|
15634
|
+
if (stream.closed) return;
|
|
15635
|
+
await new Promise((resolve) => {
|
|
15636
|
+
stream.once("close", () => resolve());
|
|
15637
|
+
});
|
|
15638
|
+
}
|
|
15578
15639
|
var ChunkBuffer = class {
|
|
15579
15640
|
constructor() {
|
|
15580
15641
|
this._frames = 0;
|
|
@@ -15583,6 +15644,7 @@ var ChunkBuffer = class {
|
|
|
15583
15644
|
this.writePositionByte = 0;
|
|
15584
15645
|
this.readStreamEnded = false;
|
|
15585
15646
|
this.framesReadInSession = 0;
|
|
15647
|
+
this.reverseReaders = /* @__PURE__ */ new Set();
|
|
15586
15648
|
}
|
|
15587
15649
|
get frames() {
|
|
15588
15650
|
return this._frames;
|
|
@@ -15628,28 +15690,47 @@ var ChunkBuffer = class {
|
|
|
15628
15690
|
async flushWrites() {
|
|
15629
15691
|
await this.endWriteStream();
|
|
15630
15692
|
}
|
|
15693
|
+
// Opens a read-only reverse view over this buffer's temp file. Flushes pending writes first, then
|
|
15694
|
+
// snapshots frames/channels/sampleRate/bitDepth so the reader is stable against later reads on the
|
|
15695
|
+
// source. The reader is registered here and closed by clear()/close() (Windows blocks unlink while a
|
|
15696
|
+
// handle is open — same EBUSY guard the forward read stream uses in endReadStream). See the
|
|
15697
|
+
// ReverseChunkReader class comment for the full borrow contract.
|
|
15698
|
+
async openReverseReader() {
|
|
15699
|
+
await this.flushWrites();
|
|
15700
|
+
const reader = new ReverseChunkReader(
|
|
15701
|
+
this.tempPath,
|
|
15702
|
+
{ frames: this._frames, channels: this._channels, sampleRate: this._sampleRate, bitDepth: this._bitDepth },
|
|
15703
|
+
REVERSE_STRIPE_BYTES,
|
|
15704
|
+
this
|
|
15705
|
+
);
|
|
15706
|
+
this.reverseReaders.add(reader);
|
|
15707
|
+
return reader;
|
|
15708
|
+
}
|
|
15709
|
+
// Called by a factory-created reader from its close() so it deregisters itself. Idempotent.
|
|
15710
|
+
deregisterReverseReader(reader) {
|
|
15711
|
+
this.reverseReaders.delete(reader);
|
|
15712
|
+
}
|
|
15713
|
+
async closeReverseReaders() {
|
|
15714
|
+
const readers = [...this.reverseReaders];
|
|
15715
|
+
this.reverseReaders.clear();
|
|
15716
|
+
for (const reader of readers) {
|
|
15717
|
+
await reader.close().catch(() => void 0);
|
|
15718
|
+
}
|
|
15719
|
+
}
|
|
15631
15720
|
async read(frames) {
|
|
15632
15721
|
const channels = this._channels;
|
|
15633
15722
|
const startFrame = this.framesReadInSession;
|
|
15634
15723
|
if (channels === 0 || frames <= 0 || this._frames === 0) {
|
|
15635
|
-
return
|
|
15724
|
+
return buildAudioChunk([], startFrame, this._sampleRate, this._bitDepth);
|
|
15636
15725
|
}
|
|
15637
15726
|
const bytesPerFrame = channels * 4;
|
|
15638
|
-
const
|
|
15639
|
-
const buf = await
|
|
15727
|
+
const rs = this.ensureReadStream();
|
|
15728
|
+
const buf = await pullBytes(rs, () => rs.destroyed || this.readStreamEnded, frames * bytesPerFrame);
|
|
15640
15729
|
const actualFrames = Math.floor(buf.length / bytesPerFrame);
|
|
15641
|
-
if (actualFrames <= 0) return
|
|
15730
|
+
if (actualFrames <= 0) return buildAudioChunk([], startFrame, this._sampleRate, this._bitDepth);
|
|
15642
15731
|
this.framesReadInSession += actualFrames;
|
|
15643
|
-
const
|
|
15644
|
-
|
|
15645
|
-
for (let ch = 0; ch < channels; ch++) out.push(new Float32Array(actualFrames));
|
|
15646
|
-
for (let frame = 0; frame < actualFrames; frame++) {
|
|
15647
|
-
const base = frame * channels;
|
|
15648
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
15649
|
-
out[ch][frame] = interleaved[base + ch];
|
|
15650
|
-
}
|
|
15651
|
-
}
|
|
15652
|
-
return this.buildAudioChunk(out, startFrame);
|
|
15732
|
+
const out = deinterleave(buf, channels);
|
|
15733
|
+
return buildAudioChunk(out, startFrame, this._sampleRate, this._bitDepth);
|
|
15653
15734
|
}
|
|
15654
15735
|
async reset() {
|
|
15655
15736
|
await this.endWriteStream();
|
|
@@ -15659,6 +15740,7 @@ var ChunkBuffer = class {
|
|
|
15659
15740
|
async clear() {
|
|
15660
15741
|
await this.endWriteStream();
|
|
15661
15742
|
await this.endReadStream();
|
|
15743
|
+
await this.closeReverseReaders();
|
|
15662
15744
|
if (this.tempPath) {
|
|
15663
15745
|
await unlink(this.tempPath).catch(() => void 0);
|
|
15664
15746
|
this.tempPath = void 0;
|
|
@@ -15670,6 +15752,7 @@ var ChunkBuffer = class {
|
|
|
15670
15752
|
async close() {
|
|
15671
15753
|
await this.endWriteStream();
|
|
15672
15754
|
await this.endReadStream();
|
|
15755
|
+
await this.closeReverseReaders();
|
|
15673
15756
|
if (this.tempPath) {
|
|
15674
15757
|
await unlink(this.tempPath).catch(() => void 0);
|
|
15675
15758
|
this.tempPath = void 0;
|
|
@@ -15679,9 +15762,6 @@ var ChunkBuffer = class {
|
|
|
15679
15762
|
this._frames = 0;
|
|
15680
15763
|
this._channels = 0;
|
|
15681
15764
|
}
|
|
15682
|
-
tempFilePath() {
|
|
15683
|
-
return this.tempPath;
|
|
15684
|
-
}
|
|
15685
15765
|
validateAndSetMetadata(sampleRate, bitDepth) {
|
|
15686
15766
|
if (sampleRate !== void 0) {
|
|
15687
15767
|
if (this._sampleRate === void 0) {
|
|
@@ -15705,9 +15785,6 @@ var ChunkBuffer = class {
|
|
|
15705
15785
|
throw new Error(`ChunkBuffer: channel count mismatch \u2014 buffer has ${String(this._channels)}, write supplied ${String(target)}`);
|
|
15706
15786
|
}
|
|
15707
15787
|
}
|
|
15708
|
-
buildAudioChunk(samples, offset) {
|
|
15709
|
-
return { samples, offset, sampleRate: this._sampleRate ?? 0, bitDepth: this._bitDepth ?? 0 };
|
|
15710
|
-
}
|
|
15711
15788
|
ensureTempPath() {
|
|
15712
15789
|
this.tempPath ?? (this.tempPath = join(tmpdir(), `chunk-buffer-${randomUUID()}.bin`));
|
|
15713
15790
|
return this.tempPath;
|
|
@@ -15760,47 +15837,140 @@ var ChunkBuffer = class {
|
|
|
15760
15837
|
this.readStreamEnded = false;
|
|
15761
15838
|
this.framesReadInSession = 0;
|
|
15762
15839
|
rs.destroy();
|
|
15763
|
-
|
|
15764
|
-
|
|
15765
|
-
|
|
15766
|
-
|
|
15840
|
+
await awaitStreamClose(rs);
|
|
15841
|
+
}
|
|
15842
|
+
};
|
|
15843
|
+
var ReverseChunkReader = class {
|
|
15844
|
+
constructor(path2, meta32, stripeBytes = REVERSE_STRIPE_BYTES, parent) {
|
|
15845
|
+
this.framesReturned = 0;
|
|
15846
|
+
this.closed = false;
|
|
15847
|
+
this.path = path2;
|
|
15848
|
+
this.frames = path2 === void 0 ? 0 : meta32.frames;
|
|
15849
|
+
this.channels = meta32.channels;
|
|
15850
|
+
this.sampleRate = meta32.sampleRate;
|
|
15851
|
+
this.bitDepth = meta32.bitDepth;
|
|
15852
|
+
this.bytesPerFrame = meta32.channels * 4;
|
|
15853
|
+
this.windowBytes = meta32.channels === 0 ? stripeBytes : Math.max(this.bytesPerFrame, Math.floor(stripeBytes / this.bytesPerFrame) * this.bytesPerFrame);
|
|
15854
|
+
this.parent = parent;
|
|
15855
|
+
}
|
|
15856
|
+
async read(frames) {
|
|
15857
|
+
if (this.closed) {
|
|
15858
|
+
throw new Error("ReverseChunkReader: read() after close()");
|
|
15767
15859
|
}
|
|
15860
|
+
const offset = this.framesReturned;
|
|
15861
|
+
const remaining = this.frames - this.framesReturned;
|
|
15862
|
+
if (this.path === void 0 || this.channels === 0 || frames <= 0 || remaining <= 0) {
|
|
15863
|
+
return buildAudioChunk([], offset, this.sampleRate, this.bitDepth);
|
|
15864
|
+
}
|
|
15865
|
+
const count = Math.min(frames, remaining);
|
|
15866
|
+
const rs = this.ensureStream();
|
|
15867
|
+
const buf = await pullBytes(rs, () => rs.destroyed || rs.readableEnded, count * this.bytesPerFrame);
|
|
15868
|
+
const actualFrames = Math.floor(buf.length / this.bytesPerFrame);
|
|
15869
|
+
if (actualFrames < count) {
|
|
15870
|
+
throw this.streamError ?? new Error("ReverseChunkReader: unexpected end of reverse stream");
|
|
15871
|
+
}
|
|
15872
|
+
this.framesReturned += actualFrames;
|
|
15873
|
+
const out = deinterleave(buf, this.channels);
|
|
15874
|
+
return buildAudioChunk(out, offset, this.sampleRate, this.bitDepth);
|
|
15768
15875
|
}
|
|
15769
|
-
async
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15876
|
+
async close() {
|
|
15877
|
+
if (this.closed) return;
|
|
15878
|
+
this.closed = true;
|
|
15879
|
+
const stream = this.stream;
|
|
15880
|
+
this.stream = void 0;
|
|
15881
|
+
if (stream) {
|
|
15882
|
+
stream.destroy();
|
|
15883
|
+
await awaitStreamClose(stream);
|
|
15884
|
+
}
|
|
15885
|
+
this.parent?.deregisterReverseReader(this);
|
|
15886
|
+
}
|
|
15887
|
+
ensureStream() {
|
|
15888
|
+
if (this.stream) return this.stream;
|
|
15889
|
+
if (this.path === void 0) {
|
|
15890
|
+
throw new Error("ReverseChunkReader: no source file");
|
|
15891
|
+
}
|
|
15892
|
+
const totalBytes = this.frames * this.bytesPerFrame;
|
|
15893
|
+
const stream = new ReverseReadable(this.path, totalBytes, this.bytesPerFrame, this.windowBytes);
|
|
15894
|
+
stream.once("error", (error482) => {
|
|
15895
|
+
this.streamError = error482;
|
|
15896
|
+
});
|
|
15897
|
+
this.stream = stream;
|
|
15898
|
+
return stream;
|
|
15899
|
+
}
|
|
15900
|
+
};
|
|
15901
|
+
var ReverseReadable = class extends Readable {
|
|
15902
|
+
constructor(path2, totalBytes, bytesPerFrame, windowBytes) {
|
|
15903
|
+
super({ highWaterMark: windowBytes });
|
|
15904
|
+
this.path = path2;
|
|
15905
|
+
this.bytesPerFrame = bytesPerFrame;
|
|
15906
|
+
this.windowBytes = windowBytes;
|
|
15907
|
+
this.pos = totalBytes;
|
|
15908
|
+
}
|
|
15909
|
+
// Emits the next backward window [max(0, pos - windowBytes), pos), with the frame order reversed in
|
|
15910
|
+
// place (channel order INSIDE each frame preserved — interleaved layout intact), then walks pos toward
|
|
15911
|
+
// 0 and pushes null at start-of-file. Node does not consume a rejected _read promise — an uncaught
|
|
15912
|
+
// throw becomes an unhandled rejection and the stream never errors — so any failure is routed to
|
|
15913
|
+
// destroy(err), which fires the 'error'/'close' events pullBytes waits on.
|
|
15914
|
+
_read() {
|
|
15915
|
+
void this.readWindow();
|
|
15916
|
+
}
|
|
15917
|
+
async readWindow() {
|
|
15918
|
+
try {
|
|
15919
|
+
if (this.pos <= 0) {
|
|
15920
|
+
this.push(null);
|
|
15921
|
+
return;
|
|
15786
15922
|
}
|
|
15787
|
-
|
|
15788
|
-
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15794
|
-
|
|
15795
|
-
|
|
15796
|
-
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
15923
|
+
const startByte = Math.max(0, this.pos - this.windowBytes);
|
|
15924
|
+
const length = this.pos - startByte;
|
|
15925
|
+
const buf = Buffer.alloc(length);
|
|
15926
|
+
await this.readFully(buf, startByte);
|
|
15927
|
+
this.reverseFramesInPlace(buf);
|
|
15928
|
+
this.pos = startByte;
|
|
15929
|
+
this.push(buf);
|
|
15930
|
+
} catch (error482) {
|
|
15931
|
+
this.destroy(error482);
|
|
15932
|
+
}
|
|
15933
|
+
}
|
|
15934
|
+
// Reverse the frame order within the window in place: frame i swaps with frame (frameCount - 1 - i),
|
|
15935
|
+
// each frame being one bytesPerFrame-wide slice. Channel order inside a frame is untouched, so the
|
|
15936
|
+
// interleaved layout survives — only the time order flips.
|
|
15937
|
+
reverseFramesInPlace(buf) {
|
|
15938
|
+
const frameCount = Math.floor(buf.length / this.bytesPerFrame);
|
|
15939
|
+
const scratch = Buffer.alloc(this.bytesPerFrame);
|
|
15940
|
+
for (let low = 0, high = frameCount - 1; low < high; low++, high--) {
|
|
15941
|
+
const lowByte = low * this.bytesPerFrame;
|
|
15942
|
+
const highByte = high * this.bytesPerFrame;
|
|
15943
|
+
buf.copy(scratch, 0, lowByte, lowByte + this.bytesPerFrame);
|
|
15944
|
+
buf.copy(buf, lowByte, highByte, highByte + this.bytesPerFrame);
|
|
15945
|
+
scratch.copy(buf, highByte, 0, this.bytesPerFrame);
|
|
15946
|
+
}
|
|
15947
|
+
}
|
|
15948
|
+
// The one centralized positioned read-fully helper. Loops on handle.read and throws on a zero-byte
|
|
15949
|
+
// read (unexpected EOF) — the shared guard against the former unchecked-bytesRead short-read bug.
|
|
15950
|
+
async readFully(target, position) {
|
|
15951
|
+
const handle = await this.ensureHandle();
|
|
15952
|
+
let filled = 0;
|
|
15953
|
+
while (filled < target.length) {
|
|
15954
|
+
const { bytesRead } = await handle.read(target, filled, target.length - filled, position + filled);
|
|
15955
|
+
if (bytesRead === 0) {
|
|
15956
|
+
throw new Error(`ReverseReadable: unexpected EOF at byte ${position + filled}`);
|
|
15957
|
+
}
|
|
15958
|
+
filled += bytesRead;
|
|
15959
|
+
}
|
|
15960
|
+
}
|
|
15961
|
+
async ensureHandle() {
|
|
15962
|
+
if (this.handle) return this.handle;
|
|
15963
|
+
this.handle = await open(this.path, "r");
|
|
15964
|
+
return this.handle;
|
|
15965
|
+
}
|
|
15966
|
+
_destroy(error482, callback) {
|
|
15967
|
+
const handle = this.handle;
|
|
15968
|
+
this.handle = void 0;
|
|
15969
|
+
if (!handle) {
|
|
15970
|
+
callback(error482);
|
|
15971
|
+
return;
|
|
15800
15972
|
}
|
|
15801
|
-
|
|
15802
|
-
if (chunks.length === 1) return chunks[0];
|
|
15803
|
-
return Buffer.concat(chunks);
|
|
15973
|
+
handle.close().then(() => callback(error482)).catch((closeError) => callback(error482 ?? closeError));
|
|
15804
15974
|
}
|
|
15805
15975
|
};
|
|
15806
15976
|
var external_exports2 = {};
|
|
@@ -29413,6 +29583,54 @@ function date42(params) {
|
|
|
29413
29583
|
return /* @__PURE__ */ _coercedDate2(ZodDate2, params);
|
|
29414
29584
|
}
|
|
29415
29585
|
config2(en_default2());
|
|
29586
|
+
var UNKNOWN_TOTAL_QUANTUM_FRAMES = 48e4;
|
|
29587
|
+
var DEFAULT_PROGRESS_QUANTUM = 0.1;
|
|
29588
|
+
var BufferedStream = class {
|
|
29589
|
+
constructor(properties) {
|
|
29590
|
+
this.events = new EventEmitter();
|
|
29591
|
+
this.quantumFraction = DEFAULT_PROGRESS_QUANTUM;
|
|
29592
|
+
this.lastBoundaryByPhase = /* @__PURE__ */ new Map();
|
|
29593
|
+
this.properties = properties;
|
|
29594
|
+
}
|
|
29595
|
+
emitProgress(phase2, framesDone, framesTotal, options) {
|
|
29596
|
+
const total = framesTotal !== void 0 && framesTotal > 0 ? framesTotal : void 0;
|
|
29597
|
+
if (options?.force) {
|
|
29598
|
+
this.lastBoundaryByPhase.set(phase2, framesDone);
|
|
29599
|
+
this.events.emit("progress", { phase: phase2, framesDone, framesTotal: total });
|
|
29600
|
+
return;
|
|
29601
|
+
}
|
|
29602
|
+
const quantum = total ? Math.max(1, Math.floor(total * this.quantumFraction)) : UNKNOWN_TOTAL_QUANTUM_FRAMES;
|
|
29603
|
+
const boundary = Math.floor(framesDone / quantum) * quantum;
|
|
29604
|
+
const last = this.lastBoundaryByPhase.get(phase2);
|
|
29605
|
+
if (last !== void 0 && boundary <= last) return;
|
|
29606
|
+
this.lastBoundaryByPhase.set(phase2, boundary);
|
|
29607
|
+
this.events.emit("progress", { phase: phase2, framesDone, framesTotal: total });
|
|
29608
|
+
}
|
|
29609
|
+
log(message, data, level = "info") {
|
|
29610
|
+
this.events.emit("log", { level, message, data });
|
|
29611
|
+
}
|
|
29612
|
+
progress(framesDone, framesTotal) {
|
|
29613
|
+
this.emitProgress("process", framesDone, framesTotal);
|
|
29614
|
+
}
|
|
29615
|
+
async teardown() {
|
|
29616
|
+
await this._teardown();
|
|
29617
|
+
}
|
|
29618
|
+
_teardown() {
|
|
29619
|
+
return;
|
|
29620
|
+
}
|
|
29621
|
+
};
|
|
29622
|
+
function wireStreamEvents(stream, identity, onEvent) {
|
|
29623
|
+
stream.events.on("started", () => onEvent(identity, { kind: "started" }));
|
|
29624
|
+
stream.events.on("finished", (payload) => onEvent(identity, { kind: "finished", ...payload }));
|
|
29625
|
+
stream.events.on("progress", (payload) => onEvent(identity, { kind: "progress", ...payload }));
|
|
29626
|
+
stream.events.on("log", (payload) => onEvent(identity, { kind: "log", ...payload }));
|
|
29627
|
+
}
|
|
29628
|
+
function wireStream(node, stream, context) {
|
|
29629
|
+
stream.quantumFraction = context.progressQuantum ?? DEFAULT_PROGRESS_QUANTUM;
|
|
29630
|
+
if (!context.onEvent) return;
|
|
29631
|
+
const identity = { nodeName: node.constructor.nodeName, id: node.id, type: node.type };
|
|
29632
|
+
wireStreamEvents(stream, identity, context.onEvent);
|
|
29633
|
+
}
|
|
29416
29634
|
var BufferedAudioNode = class {
|
|
29417
29635
|
constructor(properties) {
|
|
29418
29636
|
this.streams = [];
|
|
@@ -29462,20 +29680,8 @@ var BufferedAudioNode = class {
|
|
|
29462
29680
|
}
|
|
29463
29681
|
};
|
|
29464
29682
|
BufferedAudioNode.packageVersion = "0.0.0";
|
|
29465
|
-
BufferedAudioNode.
|
|
29683
|
+
BufferedAudioNode.nodeDescription = "";
|
|
29466
29684
|
BufferedAudioNode.schema = external_exports2.object({});
|
|
29467
|
-
var BufferedStream = class {
|
|
29468
|
-
constructor(properties) {
|
|
29469
|
-
this.events = new EventEmitter();
|
|
29470
|
-
this.properties = properties;
|
|
29471
|
-
}
|
|
29472
|
-
async teardown() {
|
|
29473
|
-
await this._teardown();
|
|
29474
|
-
}
|
|
29475
|
-
_teardown() {
|
|
29476
|
-
return;
|
|
29477
|
-
}
|
|
29478
|
-
};
|
|
29479
29685
|
var BufferedTargetStream = class extends BufferedStream {
|
|
29480
29686
|
constructor() {
|
|
29481
29687
|
super(...arguments);
|
|
@@ -29500,11 +29706,12 @@ var BufferedTargetStream = class extends BufferedStream {
|
|
|
29500
29706
|
}
|
|
29501
29707
|
await this._write(chunk);
|
|
29502
29708
|
this.framesWritten += chunk.samples[0]?.length ?? 0;
|
|
29503
|
-
this.
|
|
29709
|
+
this.emitProgress("write", this.framesWritten, this.sourceTotalFrames);
|
|
29504
29710
|
},
|
|
29505
29711
|
close: async () => {
|
|
29506
29712
|
await this._close();
|
|
29507
|
-
this.
|
|
29713
|
+
this.emitProgress("write", this.framesWritten, this.sourceTotalFrames, { force: true });
|
|
29714
|
+
this.events.emit("finished", { framesDone: this.framesWritten });
|
|
29508
29715
|
}
|
|
29509
29716
|
});
|
|
29510
29717
|
}
|
|
@@ -29516,6 +29723,7 @@ var TargetNode = class extends BufferedAudioNode {
|
|
|
29516
29723
|
setup(readable, context) {
|
|
29517
29724
|
const stream = this.createStream();
|
|
29518
29725
|
this.streams.push(stream);
|
|
29726
|
+
wireStream(this, stream, context);
|
|
29519
29727
|
return Promise.resolve([stream.setup(readable, context)]);
|
|
29520
29728
|
}
|
|
29521
29729
|
};
|
|
@@ -29539,6 +29747,8 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29539
29747
|
super(properties);
|
|
29540
29748
|
this.processingMs = 0;
|
|
29541
29749
|
this.framesProcessed = 0;
|
|
29750
|
+
this.framesBuffered = 0;
|
|
29751
|
+
this.framesEmitted = 0;
|
|
29542
29752
|
this.bufferOffset = 0;
|
|
29543
29753
|
this.hasStarted = false;
|
|
29544
29754
|
this.bufferSize = properties.bufferSize ?? 0;
|
|
@@ -29589,7 +29799,8 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29589
29799
|
}
|
|
29590
29800
|
this.processingMs += performance.now() - start;
|
|
29591
29801
|
this.framesProcessed += samplesIn;
|
|
29592
|
-
this.
|
|
29802
|
+
this.framesBuffered += samplesIn;
|
|
29803
|
+
this.emitProgress("buffer", this.framesBuffered, this.sourceTotalFrames);
|
|
29593
29804
|
return;
|
|
29594
29805
|
}
|
|
29595
29806
|
let offset = 0;
|
|
@@ -29610,16 +29821,22 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29610
29821
|
}
|
|
29611
29822
|
this.processingMs += performance.now() - start;
|
|
29612
29823
|
this.framesProcessed += samplesIn;
|
|
29613
|
-
this.
|
|
29824
|
+
this.framesBuffered += samplesIn;
|
|
29825
|
+
this.emitProgress("buffer", this.framesBuffered, this.sourceTotalFrames);
|
|
29614
29826
|
}
|
|
29615
29827
|
async handleFlush(controller) {
|
|
29828
|
+
this.emitProgress("buffer", this.framesBuffered, this.sourceTotalFrames, { force: true });
|
|
29616
29829
|
if (!this.chunkBuffer || this.chunkBuffer.frames === 0) {
|
|
29617
|
-
this.
|
|
29830
|
+
await this.emitFlushChunks(controller);
|
|
29831
|
+
this.emitProgress("emit", this.framesEmitted, this.sourceTotalFrames, { force: true });
|
|
29832
|
+
this.events.emit("finished", { framesDone: this.framesBuffered, processingMs: this.processingMs });
|
|
29618
29833
|
return;
|
|
29619
29834
|
}
|
|
29620
29835
|
if (this.bufferSize === 0) {
|
|
29621
29836
|
await this.chunkBuffer.close();
|
|
29622
|
-
this.
|
|
29837
|
+
await this.emitFlushChunks(controller);
|
|
29838
|
+
this.emitProgress("emit", this.framesEmitted, this.sourceTotalFrames, { force: true });
|
|
29839
|
+
this.events.emit("finished", { framesDone: this.framesBuffered, processingMs: this.processingMs });
|
|
29623
29840
|
return;
|
|
29624
29841
|
}
|
|
29625
29842
|
try {
|
|
@@ -29628,14 +29845,27 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29628
29845
|
await this.chunkBuffer.close();
|
|
29629
29846
|
this.chunkBuffer = void 0;
|
|
29630
29847
|
}
|
|
29631
|
-
this.
|
|
29848
|
+
await this.emitFlushChunks(controller);
|
|
29849
|
+
this.emitProgress("emit", this.framesEmitted, this.sourceTotalFrames, { force: true });
|
|
29850
|
+
this.events.emit("finished", { framesDone: this.framesBuffered, processingMs: this.processingMs });
|
|
29851
|
+
}
|
|
29852
|
+
async emitFlushChunks(controller) {
|
|
29853
|
+
const chunks = await this._flush();
|
|
29854
|
+
if (!chunks) return;
|
|
29855
|
+
for (const chunk of chunks) {
|
|
29856
|
+
controller.enqueue(chunk);
|
|
29857
|
+
this.framesEmitted += chunk.samples[0]?.length ?? 0;
|
|
29858
|
+
}
|
|
29632
29859
|
}
|
|
29633
29860
|
async processAndEmit(controller) {
|
|
29634
29861
|
if (!this.chunkBuffer) return;
|
|
29635
29862
|
const samplesBeforeProcess = this.chunkBuffer.frames;
|
|
29636
29863
|
const start = performance.now();
|
|
29864
|
+
const wholeFile = this.bufferSize === WHOLE_FILE;
|
|
29637
29865
|
await this.chunkBuffer.flushWrites();
|
|
29866
|
+
if (wholeFile) this.emitProgress("process", 0, void 0, { force: true });
|
|
29638
29867
|
await this._process(this.chunkBuffer);
|
|
29868
|
+
if (wholeFile) this.emitProgress("process", samplesBeforeProcess, samplesBeforeProcess, { force: true });
|
|
29639
29869
|
await this.emitBuffer(controller);
|
|
29640
29870
|
this.processingMs += performance.now() - start;
|
|
29641
29871
|
this.framesProcessed += samplesBeforeProcess;
|
|
@@ -29664,6 +29894,8 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29664
29894
|
};
|
|
29665
29895
|
const result = await this._unbuffer(adjusted);
|
|
29666
29896
|
if (result) controller.enqueue(result);
|
|
29897
|
+
this.framesEmitted += chunkFrames;
|
|
29898
|
+
this.emitProgress("emit", this.framesEmitted, this.sourceTotalFrames);
|
|
29667
29899
|
if (overlapScratch) {
|
|
29668
29900
|
if (chunkFrames >= overlap) {
|
|
29669
29901
|
for (let ch = 0; ch < channels; ch++) {
|
|
@@ -29714,6 +29946,9 @@ var BufferedTransformStream = class extends BufferedStream {
|
|
|
29714
29946
|
_unbuffer(chunk) {
|
|
29715
29947
|
return chunk;
|
|
29716
29948
|
}
|
|
29949
|
+
_flush() {
|
|
29950
|
+
return void 0;
|
|
29951
|
+
}
|
|
29717
29952
|
};
|
|
29718
29953
|
var TransformNode = class _TransformNode extends BufferedAudioNode {
|
|
29719
29954
|
static is(value) {
|
|
@@ -29725,6 +29960,7 @@ var TransformNode = class _TransformNode extends BufferedAudioNode {
|
|
|
29725
29960
|
async setup(readable, context) {
|
|
29726
29961
|
const stream = this.createStream();
|
|
29727
29962
|
this.streams.push(stream);
|
|
29963
|
+
wireStream(this, stream, context);
|
|
29728
29964
|
const output = await stream.setup(readable, context);
|
|
29729
29965
|
return this.setupChildren(output, context);
|
|
29730
29966
|
}
|
|
@@ -29746,6 +29982,7 @@ var BufferedSourceStream = class extends BufferedStream {
|
|
|
29746
29982
|
constructor() {
|
|
29747
29983
|
super(...arguments);
|
|
29748
29984
|
this.framesRead = 0;
|
|
29985
|
+
this.hasStarted = false;
|
|
29749
29986
|
}
|
|
29750
29987
|
setup(context) {
|
|
29751
29988
|
return this._setup(context);
|
|
@@ -29754,6 +29991,7 @@ var BufferedSourceStream = class extends BufferedStream {
|
|
|
29754
29991
|
async _setup(context) {
|
|
29755
29992
|
let done = false;
|
|
29756
29993
|
this.framesRead = 0;
|
|
29994
|
+
this.hasStarted = false;
|
|
29757
29995
|
const { signal, durationFrames: sourceTotalFrames, highWaterMark } = context;
|
|
29758
29996
|
return new ReadableStream(
|
|
29759
29997
|
{
|
|
@@ -29765,16 +30003,22 @@ var BufferedSourceStream = class extends BufferedStream {
|
|
|
29765
30003
|
return;
|
|
29766
30004
|
}
|
|
29767
30005
|
try {
|
|
30006
|
+
if (!this.hasStarted) {
|
|
30007
|
+
this.hasStarted = true;
|
|
30008
|
+
this.events.emit("started");
|
|
30009
|
+
}
|
|
29768
30010
|
const chunk = await this._read();
|
|
29769
30011
|
if (!chunk) {
|
|
29770
30012
|
done = true;
|
|
30013
|
+
this.emitProgress("read", this.framesRead, sourceTotalFrames, { force: true });
|
|
29771
30014
|
await this._flush();
|
|
30015
|
+
this.events.emit("finished", { framesDone: this.framesRead });
|
|
29772
30016
|
controller.close();
|
|
29773
30017
|
return;
|
|
29774
30018
|
}
|
|
29775
30019
|
this.framesRead += chunk.samples[0]?.length ?? 0;
|
|
29776
30020
|
controller.enqueue(chunk);
|
|
29777
|
-
this.
|
|
30021
|
+
this.emitProgress("read", this.framesRead, sourceTotalFrames);
|
|
29778
30022
|
} catch (error482) {
|
|
29779
30023
|
done = true;
|
|
29780
30024
|
controller.error(error482);
|
|
@@ -29805,6 +30049,7 @@ var SourceNode = class extends BufferedAudioNode {
|
|
|
29805
30049
|
async setup(context) {
|
|
29806
30050
|
const stream = this.createStream();
|
|
29807
30051
|
this.streams.push(stream);
|
|
30052
|
+
wireStream(this, stream, context);
|
|
29808
30053
|
const readable = await stream.setup(context);
|
|
29809
30054
|
const promises = await this.setupChildren(readable, context);
|
|
29810
30055
|
await Promise.all(promises);
|
|
@@ -29836,7 +30081,9 @@ var SourceNode = class extends BufferedAudioNode {
|
|
|
29836
30081
|
durationFrames: meta32.durationFrames,
|
|
29837
30082
|
highWaterMark: options?.highWaterMark ?? computedHighWaterMark,
|
|
29838
30083
|
signal: options?.signal,
|
|
29839
|
-
visited: /* @__PURE__ */ new Set()
|
|
30084
|
+
visited: /* @__PURE__ */ new Set(),
|
|
30085
|
+
onEvent: options?.onEvent,
|
|
30086
|
+
progressQuantum: options?.progressQuantum
|
|
29840
30087
|
};
|
|
29841
30088
|
const start = performance.now();
|
|
29842
30089
|
try {
|
|
@@ -29905,39 +30152,24 @@ external_exports2.object({
|
|
|
29905
30152
|
});
|
|
29906
30153
|
var STRIPE_BYTES = 10 * 1024 * 1024;
|
|
29907
30154
|
async function reverseBuffer(source, dest) {
|
|
29908
|
-
await source.flushWrites();
|
|
29909
30155
|
const out = dest ?? new ChunkBuffer();
|
|
29910
30156
|
const channels = source.channels;
|
|
29911
30157
|
const totalFrames = source.frames;
|
|
29912
30158
|
if (channels === 0 || totalFrames === 0) return out;
|
|
29913
|
-
const sourcePath = source.tempFilePath();
|
|
29914
|
-
if (!sourcePath) return out;
|
|
29915
30159
|
const sampleRate = source.sampleRate;
|
|
29916
30160
|
const bitDepth = source.bitDepth;
|
|
29917
30161
|
const bytesPerFrame = channels * 4;
|
|
29918
30162
|
const stripeFramesCap = Math.max(1, Math.floor(STRIPE_BYTES / bytesPerFrame));
|
|
29919
|
-
const
|
|
30163
|
+
const reader = await source.openReverseReader();
|
|
29920
30164
|
try {
|
|
29921
|
-
|
|
29922
|
-
|
|
29923
|
-
const
|
|
29924
|
-
|
|
29925
|
-
|
|
29926
|
-
await handle.read(buf, 0, buf.length, startFrame * bytesPerFrame);
|
|
29927
|
-
const interleaved = new Float32Array(buf.buffer, buf.byteOffset, stripeFrames * channels);
|
|
29928
|
-
const reversed = [];
|
|
29929
|
-
for (let ch = 0; ch < channels; ch++) reversed.push(new Float32Array(stripeFrames));
|
|
29930
|
-
for (let frame = 0; frame < stripeFrames; frame++) {
|
|
29931
|
-
const sourceBase = (stripeFrames - 1 - frame) * channels;
|
|
29932
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
29933
|
-
reversed[ch][frame] = interleaved[sourceBase + ch];
|
|
29934
|
-
}
|
|
29935
|
-
}
|
|
29936
|
-
await out.write(reversed, sampleRate, bitDepth);
|
|
29937
|
-
endFrame = startFrame;
|
|
30165
|
+
for (; ; ) {
|
|
30166
|
+
const chunk = await reader.read(stripeFramesCap);
|
|
30167
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
30168
|
+
if (frames === 0) break;
|
|
30169
|
+
await out.write(chunk.samples, sampleRate, bitDepth);
|
|
29938
30170
|
}
|
|
29939
30171
|
} finally {
|
|
29940
|
-
await
|
|
30172
|
+
await reader.close();
|
|
29941
30173
|
}
|
|
29942
30174
|
await out.flushWrites();
|
|
29943
30175
|
return out;
|
|
@@ -29946,7 +30178,7 @@ async function reverseBuffer(source, dest) {
|
|
|
29946
30178
|
// package.json
|
|
29947
30179
|
var package_default = {
|
|
29948
30180
|
name: "@buffered-audio/nodes",
|
|
29949
|
-
version: "0.
|
|
30181
|
+
version: "0.17.0"};
|
|
29950
30182
|
|
|
29951
30183
|
// src/package-metadata.ts
|
|
29952
30184
|
var PACKAGE_NAME = package_default.name;
|
|
@@ -29963,11 +30195,7 @@ var AmplitudeHistogramAccumulator = class {
|
|
|
29963
30195
|
this.bucketCount = bucketCount;
|
|
29964
30196
|
this.buckets = new Uint32Array(bucketCount);
|
|
29965
30197
|
}
|
|
29966
|
-
|
|
29967
|
-
* Consume `frames` samples from each channel. Throws if any channel
|
|
29968
|
-
* has fewer than `frames` samples or if {@link finalize} has already
|
|
29969
|
-
* been called.
|
|
29970
|
-
*/
|
|
30198
|
+
// Consumes `frames` samples per channel; throws if any channel has fewer than `frames` samples or if `finalize` was already called.
|
|
29971
30199
|
push(channels, frames) {
|
|
29972
30200
|
if (this.finalized) {
|
|
29973
30201
|
throw new Error("AmplitudeHistogramAccumulator: push() called after finalize()");
|
|
@@ -30008,13 +30236,6 @@ var AmplitudeHistogramAccumulator = class {
|
|
|
30008
30236
|
}
|
|
30009
30237
|
}
|
|
30010
30238
|
}
|
|
30011
|
-
/**
|
|
30012
|
-
* Returns the histogram with linearly-interpolated median. Idempotent
|
|
30013
|
-
* — subsequent calls return the same cached result object.
|
|
30014
|
-
*
|
|
30015
|
-
* Empty / all-zero input yields `bucketMax = 0`, `median = 0`, and an
|
|
30016
|
-
* all-zero `buckets` array, matching the one-shot utility's edge case.
|
|
30017
|
-
*/
|
|
30018
30239
|
finalize() {
|
|
30019
30240
|
if (this.cachedResult !== void 0) return this.cachedResult;
|
|
30020
30241
|
this.finalized = true;
|
|
@@ -30039,17 +30260,6 @@ var AmplitudeHistogramAccumulator = class {
|
|
|
30039
30260
|
this.cachedResult = { buckets: this.buckets, bucketMax: this.bucketMax, median };
|
|
30040
30261
|
return this.cachedResult;
|
|
30041
30262
|
}
|
|
30042
|
-
/**
|
|
30043
|
-
* Rebin existing counts into a wider range. Each old bucket's count is
|
|
30044
|
-
* deposited entirely into the new bucket whose interval contains the
|
|
30045
|
-
* old bucket's center. Faster than a proportional split and accuracy
|
|
30046
|
-
* difference is sub-bucket — with 1024 buckets the resolution is
|
|
30047
|
-
* already finer than any practical percentile precision requirement.
|
|
30048
|
-
*
|
|
30049
|
-
* Sample-count invariant: `sum(newBuckets) === sum(oldBuckets)`. This
|
|
30050
|
-
* follows trivially from each old bucket being deposited into exactly
|
|
30051
|
-
* one new bucket.
|
|
30052
|
-
*/
|
|
30053
30263
|
rebucket(newMax) {
|
|
30054
30264
|
if (this.bucketMax === 0) {
|
|
30055
30265
|
if (this.pendingZeros > 0) {
|
|
@@ -30182,11 +30392,6 @@ var BidirectionalIir = class {
|
|
|
30182
30392
|
const tauCausal = this.smoothingMs / 1e3;
|
|
30183
30393
|
this.alphaCausal = tauCausal > 0 ? 1 - Math.exp(-samplePeriod / tauCausal) : 1;
|
|
30184
30394
|
}
|
|
30185
|
-
/**
|
|
30186
|
-
* Bidirectional one-pole IIR with sqrt(2)*tau compensation.
|
|
30187
|
-
* Returns a fresh array; input is not mutated. Identity when
|
|
30188
|
-
* smoothingMs <= 0.
|
|
30189
|
-
*/
|
|
30190
30395
|
applyBidirectional(input) {
|
|
30191
30396
|
const output = Float32Array.from(input);
|
|
30192
30397
|
if (this.smoothingMs <= 0) return output;
|
|
@@ -30206,13 +30411,6 @@ var BidirectionalIir = class {
|
|
|
30206
30411
|
}
|
|
30207
30412
|
return output;
|
|
30208
30413
|
}
|
|
30209
|
-
/**
|
|
30210
|
-
* Forward-only one-pole IIR, single pass, no compensation. State is
|
|
30211
|
-
* carried by the caller via `{ value: number }` so chunked use can
|
|
30212
|
-
* be continuous. Identity when smoothingMs <= 0 (the input is
|
|
30213
|
-
* returned as a fresh copy so callers can rely on a fresh buffer
|
|
30214
|
-
* regardless).
|
|
30215
|
-
*/
|
|
30216
30414
|
applyCausal(input, state) {
|
|
30217
30415
|
const output = Float32Array.from(input);
|
|
30218
30416
|
if (this.smoothingMs <= 0) return output;
|
|
@@ -30227,22 +30425,6 @@ var BidirectionalIir = class {
|
|
|
30227
30425
|
state.value = y;
|
|
30228
30426
|
return output;
|
|
30229
30427
|
}
|
|
30230
|
-
/**
|
|
30231
|
-
* Forward HALF of the bidirectional cascade, chunked. Same loop body
|
|
30232
|
-
* as `applyCausal`, but uses `alphaBidirectional` (the sqrt(2)-
|
|
30233
|
-
* compensated alpha) so the output, after a subsequent
|
|
30234
|
-
* `applyBackwardPassInPlace` on the concatenated forward result,
|
|
30235
|
-
* matches `applyBidirectional`'s output byte-for-byte.
|
|
30236
|
-
*
|
|
30237
|
-
* State is carried by the caller via `{ value: number }`. For the
|
|
30238
|
-
* very first chunk the caller must seed `state.value = input[0]`
|
|
30239
|
-
* (matching `applyBidirectional`'s "init from first sample" rule);
|
|
30240
|
-
* for subsequent chunks `state.value` carries the previous chunk's
|
|
30241
|
-
* trailing forward state.
|
|
30242
|
-
*
|
|
30243
|
-
* Identity when smoothingMs <= 0 (returns a fresh copy of the
|
|
30244
|
-
* input).
|
|
30245
|
-
*/
|
|
30246
30428
|
applyForwardPass(input, state) {
|
|
30247
30429
|
const output = Float32Array.from(input);
|
|
30248
30430
|
if (this.smoothingMs <= 0) return output;
|
|
@@ -30257,21 +30439,6 @@ var BidirectionalIir = class {
|
|
|
30257
30439
|
state.value = y;
|
|
30258
30440
|
return output;
|
|
30259
30441
|
}
|
|
30260
|
-
/**
|
|
30261
|
-
* Backward HALF of the bidirectional cascade, run IN PLACE on a
|
|
30262
|
-
* whole-array buffer. The buffer is expected to hold the result of
|
|
30263
|
-
* the forward HALF (e.g. produced by chunked `applyForwardPass`
|
|
30264
|
-
* calls concatenated together). Overwrites `buffer` with the final
|
|
30265
|
-
* smoothed result.
|
|
30266
|
-
*
|
|
30267
|
-
* Init from `buffer[buffer.length - 1]`, matching the second pass of
|
|
30268
|
-
* `applyBidirectional`. Uses `alphaBidirectional`.
|
|
30269
|
-
*
|
|
30270
|
-
* Whole-array (not chunked) — backward IIR cannot stream forward
|
|
30271
|
-
* because it must walk end-to-start.
|
|
30272
|
-
*
|
|
30273
|
-
* Identity (no-op) when smoothingMs <= 0.
|
|
30274
|
-
*/
|
|
30275
30442
|
applyBackwardPassInPlace(buffer) {
|
|
30276
30443
|
if (this.smoothingMs <= 0) return;
|
|
30277
30444
|
if (buffer.length === 0) return;
|
|
@@ -30304,12 +30471,7 @@ var BlockSumAccumulator = class {
|
|
|
30304
30471
|
this.ringSize = computeRingSize(blockSize, blockStep);
|
|
30305
30472
|
this.activeBlockSums = new Float64Array(this.ringSize);
|
|
30306
30473
|
}
|
|
30307
|
-
|
|
30308
|
-
* Consume `frames` per-frame sums starting at `perFrameSums[0]`.
|
|
30309
|
-
* `perFrameSums.length` must be at least `frames`. Block boundary
|
|
30310
|
-
* accounting advances exactly as if these values were appended to a
|
|
30311
|
-
* single contiguous stream.
|
|
30312
|
-
*/
|
|
30474
|
+
// Consumes `frames` per-frame sums from `perFrameSums[0]` (needs length >= `frames`); block-boundary accounting advances as if appended to one contiguous stream.
|
|
30313
30475
|
push(perFrameSums, frames) {
|
|
30314
30476
|
if (this.finalized) {
|
|
30315
30477
|
throw new Error("BlockSumAccumulator: push() called after finalize()");
|
|
@@ -30322,34 +30484,35 @@ var BlockSumAccumulator = class {
|
|
|
30322
30484
|
const blockStep = this.blockStep;
|
|
30323
30485
|
const ringSize = this.ringSize;
|
|
30324
30486
|
const activeBlockSums = this.activeBlockSums;
|
|
30487
|
+
let samplesProcessed = this.samplesProcessed;
|
|
30488
|
+
let nextBlockToOpen = this.nextBlockToOpen;
|
|
30489
|
+
let nextBlockToClose = this.nextBlockToClose;
|
|
30490
|
+
let nextOpenAt = nextBlockToOpen * blockStep;
|
|
30491
|
+
let nextCloseAt = nextBlockToClose * blockStep + blockSize;
|
|
30325
30492
|
for (let frameIndex = 0; frameIndex < frames; frameIndex++) {
|
|
30326
|
-
const globalSampleIndex = this.samplesProcessed;
|
|
30327
30493
|
const sampleContribution = perFrameSums[frameIndex] ?? 0;
|
|
30328
|
-
|
|
30329
|
-
|
|
30330
|
-
|
|
30331
|
-
|
|
30332
|
-
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
for (let blockIndex = minBlock; blockIndex <= maxBlock; blockIndex++) {
|
|
30336
|
-
const slot = blockIndex % ringSize;
|
|
30494
|
+
while (samplesProcessed >= nextOpenAt) {
|
|
30495
|
+
activeBlockSums[nextBlockToOpen % ringSize] = 0;
|
|
30496
|
+
nextBlockToOpen++;
|
|
30497
|
+
nextOpenAt += blockStep;
|
|
30498
|
+
}
|
|
30499
|
+
let slot = nextBlockToClose % ringSize;
|
|
30500
|
+
for (let blockIndex = nextBlockToClose; blockIndex < nextBlockToOpen; blockIndex++) {
|
|
30337
30501
|
activeBlockSums[slot] = (activeBlockSums[slot] ?? 0) + sampleContribution;
|
|
30502
|
+
slot++;
|
|
30503
|
+
if (slot === ringSize) slot = 0;
|
|
30338
30504
|
}
|
|
30339
|
-
|
|
30340
|
-
while (
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30344
|
-
this.nextBlockToClose++;
|
|
30505
|
+
samplesProcessed++;
|
|
30506
|
+
while (samplesProcessed >= nextCloseAt) {
|
|
30507
|
+
this.closedBlockSums.push(activeBlockSums[nextBlockToClose % ringSize] ?? 0);
|
|
30508
|
+
nextBlockToClose++;
|
|
30509
|
+
nextCloseAt += blockStep;
|
|
30345
30510
|
}
|
|
30346
30511
|
}
|
|
30512
|
+
this.samplesProcessed = samplesProcessed;
|
|
30513
|
+
this.nextBlockToOpen = nextBlockToOpen;
|
|
30514
|
+
this.nextBlockToClose = nextBlockToClose;
|
|
30347
30515
|
}
|
|
30348
|
-
/**
|
|
30349
|
-
* Return the raw closed block sums in block-index order. Idempotent —
|
|
30350
|
-
* additional calls return the same array. Subsequent {@link push}
|
|
30351
|
-
* calls throw.
|
|
30352
|
-
*/
|
|
30353
30516
|
finalize() {
|
|
30354
30517
|
this.finalized = true;
|
|
30355
30518
|
return this.closedBlockSums;
|
|
@@ -30475,14 +30638,7 @@ var KWeightedSquaredSum = class {
|
|
|
30475
30638
|
this.weights[channelIndex] = weights[channelIndex] ?? 1;
|
|
30476
30639
|
}
|
|
30477
30640
|
}
|
|
30478
|
-
|
|
30479
|
-
* Consume `frames` of audio. `channels[c]` must have at least `frames`
|
|
30480
|
-
* valid samples starting at index 0; oversized buffers are fine.
|
|
30481
|
-
* `output` must have at least `frames` entries — `output[i]` receives
|
|
30482
|
-
* the K-weighted, channel-weighted, squared sum at frame `i`. Biquad
|
|
30483
|
-
* state advances exactly as if the samples were appended to a single
|
|
30484
|
-
* contiguous buffer.
|
|
30485
|
-
*/
|
|
30641
|
+
// `channels[c]` and `output` need >= `frames` entries from index 0 (oversized OK); `output[i]` gets the K-weighted channel-weighted squared sum at frame `i`; biquad state advances as if appended contiguously.
|
|
30486
30642
|
push(channels, frames, output) {
|
|
30487
30643
|
if (channels.length !== this.channelCount) {
|
|
30488
30644
|
throw new Error(`KWeightedSquaredSum: push got ${channels.length} channels, expected ${this.channelCount}`);
|
|
@@ -30509,40 +30665,56 @@ var KWeightedSquaredSum = class {
|
|
|
30509
30665
|
const rlbB2 = this.rlbB2;
|
|
30510
30666
|
const rlbA1 = this.rlbA1;
|
|
30511
30667
|
const rlbA2 = this.rlbA2;
|
|
30512
|
-
|
|
30513
|
-
|
|
30514
|
-
|
|
30515
|
-
|
|
30516
|
-
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
let
|
|
30522
|
-
|
|
30523
|
-
|
|
30524
|
-
|
|
30525
|
-
|
|
30526
|
-
|
|
30527
|
-
|
|
30528
|
-
|
|
30529
|
-
|
|
30530
|
-
|
|
30531
|
-
|
|
30532
|
-
|
|
30533
|
-
|
|
30534
|
-
|
|
30535
|
-
|
|
30536
|
-
|
|
30537
|
-
|
|
30538
|
-
|
|
30539
|
-
|
|
30540
|
-
|
|
30541
|
-
|
|
30542
|
-
|
|
30543
|
-
|
|
30544
|
-
|
|
30545
|
-
|
|
30668
|
+
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
|
|
30669
|
+
const channel = channels[channelIndex] ?? channels[0] ?? new Float32Array(0);
|
|
30670
|
+
const weight = weights[channelIndex] ?? 1;
|
|
30671
|
+
let px1 = this.preX1[channelIndex] ?? 0;
|
|
30672
|
+
let px2 = this.preX2[channelIndex] ?? 0;
|
|
30673
|
+
let py1 = this.preY1[channelIndex] ?? 0;
|
|
30674
|
+
let py2 = this.preY2[channelIndex] ?? 0;
|
|
30675
|
+
let rx1 = this.rlbX1[channelIndex] ?? 0;
|
|
30676
|
+
let rx2 = this.rlbX2[channelIndex] ?? 0;
|
|
30677
|
+
let ry1 = this.rlbY1[channelIndex] ?? 0;
|
|
30678
|
+
let ry2 = this.rlbY2[channelIndex] ?? 0;
|
|
30679
|
+
if (channelIndex === 0) {
|
|
30680
|
+
for (let frameIndex = 0; frameIndex < frames; frameIndex++) {
|
|
30681
|
+
const x0 = channel[frameIndex] ?? 0;
|
|
30682
|
+
const preY = preB0 * x0 + preB1 * px1 + preB2 * px2 - preA1 * py1 - preA2 * py2;
|
|
30683
|
+
px2 = px1;
|
|
30684
|
+
px1 = x0;
|
|
30685
|
+
py2 = py1;
|
|
30686
|
+
py1 = preY;
|
|
30687
|
+
const rlbY = rlbB0 * preY + rlbB1 * rx1 + rlbB2 * rx2 - rlbA1 * ry1 - rlbA2 * ry2;
|
|
30688
|
+
rx2 = rx1;
|
|
30689
|
+
rx1 = preY;
|
|
30690
|
+
ry2 = ry1;
|
|
30691
|
+
ry1 = rlbY;
|
|
30692
|
+
output[frameIndex] = weight * rlbY * rlbY;
|
|
30693
|
+
}
|
|
30694
|
+
} else {
|
|
30695
|
+
for (let frameIndex = 0; frameIndex < frames; frameIndex++) {
|
|
30696
|
+
const x0 = channel[frameIndex] ?? 0;
|
|
30697
|
+
const preY = preB0 * x0 + preB1 * px1 + preB2 * px2 - preA1 * py1 - preA2 * py2;
|
|
30698
|
+
px2 = px1;
|
|
30699
|
+
px1 = x0;
|
|
30700
|
+
py2 = py1;
|
|
30701
|
+
py1 = preY;
|
|
30702
|
+
const rlbY = rlbB0 * preY + rlbB1 * rx1 + rlbB2 * rx2 - rlbA1 * ry1 - rlbA2 * ry2;
|
|
30703
|
+
rx2 = rx1;
|
|
30704
|
+
rx1 = preY;
|
|
30705
|
+
ry2 = ry1;
|
|
30706
|
+
ry1 = rlbY;
|
|
30707
|
+
output[frameIndex] = (output[frameIndex] ?? 0) + weight * rlbY * rlbY;
|
|
30708
|
+
}
|
|
30709
|
+
}
|
|
30710
|
+
this.preX1[channelIndex] = px1;
|
|
30711
|
+
this.preX2[channelIndex] = px2;
|
|
30712
|
+
this.preY1[channelIndex] = py1;
|
|
30713
|
+
this.preY2[channelIndex] = py2;
|
|
30714
|
+
this.rlbX1[channelIndex] = rx1;
|
|
30715
|
+
this.rlbX2[channelIndex] = rx2;
|
|
30716
|
+
this.rlbY1[channelIndex] = ry1;
|
|
30717
|
+
this.rlbY2[channelIndex] = ry2;
|
|
30546
30718
|
}
|
|
30547
30719
|
}
|
|
30548
30720
|
};
|
|
@@ -30602,13 +30774,7 @@ var IntegratedLufsAccumulator = class {
|
|
|
30602
30774
|
this.kw = new KWeightedSquaredSum(sampleRate, channelCount, channelWeights);
|
|
30603
30775
|
this.blocks = new BlockSumAccumulator(this.blockSize, blockStep);
|
|
30604
30776
|
}
|
|
30605
|
-
|
|
30606
|
-
* Consume `frames` of audio. `channels[c]` must have at least
|
|
30607
|
-
* `frames` valid samples starting at index 0; oversized buffers are
|
|
30608
|
-
* fine and avoid the need for caller-side slicing. The accumulator
|
|
30609
|
-
* advances biquad state and block accounting exactly as if these
|
|
30610
|
-
* samples were appended to a single contiguous buffer.
|
|
30611
|
-
*/
|
|
30777
|
+
// `channels[c]` needs >= `frames` valid samples from index 0 (oversized OK); state advances as if appended to one contiguous buffer.
|
|
30612
30778
|
push(channels, frames) {
|
|
30613
30779
|
if (this.finalized) {
|
|
30614
30780
|
throw new Error("IntegratedLufsAccumulator: push() called after finalize()");
|
|
@@ -30620,12 +30786,6 @@ var IntegratedLufsAccumulator = class {
|
|
|
30620
30786
|
this.kw.push(channels, frames, this.outputBuffer);
|
|
30621
30787
|
this.blocks.push(this.outputBuffer, frames);
|
|
30622
30788
|
}
|
|
30623
|
-
/**
|
|
30624
|
-
* Apply the BS.1770 two-stage gating to all completed blocks and
|
|
30625
|
-
* return integrated LUFS. Returns -Infinity if no blocks completed
|
|
30626
|
-
* (signal shorter than one 400 ms block) or if every block fails the
|
|
30627
|
-
* absolute or relative gate.
|
|
30628
|
-
*/
|
|
30629
30789
|
finalize() {
|
|
30630
30790
|
this.finalized = true;
|
|
30631
30791
|
return applyBs1770Gating(this.blocks.finalize(), this.blockSize);
|
|
@@ -30709,11 +30869,6 @@ var LoudnessAccumulator = class {
|
|
|
30709
30869
|
this.blocks400 = new BlockSumAccumulator(this.blockSize400, blockStep);
|
|
30710
30870
|
this.blocks3s = new BlockSumAccumulator(this.blockSize3s, blockStep);
|
|
30711
30871
|
}
|
|
30712
|
-
/**
|
|
30713
|
-
* Consume `frames` of audio, advancing the K-weighting state and both
|
|
30714
|
-
* block-sum accumulators in lockstep. Identical chunk-boundary
|
|
30715
|
-
* semantics to {@link IntegratedLufsAccumulator.push}.
|
|
30716
|
-
*/
|
|
30717
30872
|
push(channels, frames) {
|
|
30718
30873
|
if (this.finalized) {
|
|
30719
30874
|
throw new Error("LoudnessAccumulator: push() called after finalize()");
|
|
@@ -30726,11 +30881,6 @@ var LoudnessAccumulator = class {
|
|
|
30726
30881
|
this.blocks400.push(this.outputBuffer, frames);
|
|
30727
30882
|
this.blocks3s.push(this.outputBuffer, frames);
|
|
30728
30883
|
}
|
|
30729
|
-
/**
|
|
30730
|
-
* Finalize and return all four metrics. Idempotent — additional calls
|
|
30731
|
-
* return the same cached result object reference. Subsequent
|
|
30732
|
-
* {@link push} calls throw.
|
|
30733
|
-
*/
|
|
30734
30884
|
finalize() {
|
|
30735
30885
|
if (this.cachedResult !== void 0) return this.cachedResult;
|
|
30736
30886
|
this.finalized = true;
|
|
@@ -31193,20 +31343,6 @@ var SlidingWindowMaxStream = class {
|
|
|
31193
31343
|
this.lookAhead = new Float32Array(ringCapacity);
|
|
31194
31344
|
this.deque = new Int32Array(ringCapacity);
|
|
31195
31345
|
}
|
|
31196
|
-
/**
|
|
31197
|
-
* Ingest the next chunk of input. Returns the next chunk of output
|
|
31198
|
-
* (deferred by `halfWidth` on the leading edge). When `isFinal` is
|
|
31199
|
-
* `true`, the trailing-edge outputs are flushed with the right-edge
|
|
31200
|
-
* clamped to the last ingested input index.
|
|
31201
|
-
*
|
|
31202
|
-
* The return value's length depends on the leading-edge fill state:
|
|
31203
|
-
* - Until total ingested ≥ `halfWidth + 1`, output length is 0
|
|
31204
|
-
* unless `isFinal` is true.
|
|
31205
|
-
* - Steady state: output length equals input chunk length.
|
|
31206
|
-
* - On the final chunk: output length equals input chunk length
|
|
31207
|
-
* plus any trailing-edge outputs not yet emitted (so total
|
|
31208
|
-
* across all calls equals total input length).
|
|
31209
|
-
*/
|
|
31210
31346
|
push(chunk, isFinal) {
|
|
31211
31347
|
const chunkLength = chunk.length;
|
|
31212
31348
|
const halfWidth = this.halfWidth;
|
|
@@ -31276,12 +31412,6 @@ var SlidingWindowMinStream = class {
|
|
|
31276
31412
|
this.lookAhead = new Float32Array(ringCapacity);
|
|
31277
31413
|
this.deque = new Int32Array(ringCapacity);
|
|
31278
31414
|
}
|
|
31279
|
-
/**
|
|
31280
|
-
* Ingest the next chunk of input. Returns the next chunk of output
|
|
31281
|
-
* (deferred by `halfWidth` on the leading edge). When `isFinal` is
|
|
31282
|
-
* `true`, the trailing-edge outputs are flushed with the right-edge
|
|
31283
|
-
* clamped to the last ingested input index.
|
|
31284
|
-
*/
|
|
31285
31415
|
push(chunk, isFinal) {
|
|
31286
31416
|
const chunkLength = chunk.length;
|
|
31287
31417
|
const halfWidth = this.halfWidth;
|
|
@@ -31578,119 +31708,144 @@ function butterflyStages(re, im, size) {
|
|
|
31578
31708
|
}
|
|
31579
31709
|
var TAPS_PER_PHASE_4X = 12;
|
|
31580
31710
|
var HISTORY_LENGTH_4X = TAPS_PER_PHASE_4X;
|
|
31581
|
-
var
|
|
31582
|
-
|
|
31583
|
-
|
|
31584
|
-
|
|
31585
|
-
|
|
31586
|
-
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
31592
|
-
|
|
31593
|
-
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
|
|
31597
|
-
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
31610
|
-
|
|
31611
|
-
|
|
31612
|
-
|
|
31613
|
-
|
|
31614
|
-
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
0.0330810546875,
|
|
31618
|
-
-0.0582275390625,
|
|
31619
|
-
0.1015625,
|
|
31620
|
-
-0.2003173828125,
|
|
31621
|
-
0.77978515625,
|
|
31622
|
-
0.465087890625,
|
|
31623
|
-
-0.16650390625,
|
|
31624
|
-
0.089111328125,
|
|
31625
|
-
-0.0517578125,
|
|
31626
|
-
0.029296875,
|
|
31627
|
-
-0.0291748046875
|
|
31628
|
-
]
|
|
31629
|
-
];
|
|
31711
|
+
var P1T0 = 0.001708984375;
|
|
31712
|
+
var P1T1 = 0.010986328125;
|
|
31713
|
+
var P1T2 = -0.0196533203125;
|
|
31714
|
+
var P1T3 = 0.033203125;
|
|
31715
|
+
var P1T4 = -0.0594482421875;
|
|
31716
|
+
var P1T5 = 0.1373291015625;
|
|
31717
|
+
var P1T6 = 0.97216796875;
|
|
31718
|
+
var P1T7 = -0.102294921875;
|
|
31719
|
+
var P1T8 = 0.047607421875;
|
|
31720
|
+
var P1T9 = -0.026611328125;
|
|
31721
|
+
var P1T10 = 0.014892578125;
|
|
31722
|
+
var P1T11 = -0.00830078125;
|
|
31723
|
+
var P2T0 = -0.0291748046875;
|
|
31724
|
+
var P2T1 = 0.029296875;
|
|
31725
|
+
var P2T2 = -0.0517578125;
|
|
31726
|
+
var P2T3 = 0.089111328125;
|
|
31727
|
+
var P2T4 = -0.16650390625;
|
|
31728
|
+
var P2T5 = 0.465087890625;
|
|
31729
|
+
var P2T6 = 0.77978515625;
|
|
31730
|
+
var P2T7 = -0.2003173828125;
|
|
31731
|
+
var P2T8 = 0.1015625;
|
|
31732
|
+
var P2T9 = -0.0582275390625;
|
|
31733
|
+
var P2T10 = 0.0330810546875;
|
|
31734
|
+
var P2T11 = -0.0189208984375;
|
|
31735
|
+
var P3T0 = -0.0189208984375;
|
|
31736
|
+
var P3T1 = 0.0330810546875;
|
|
31737
|
+
var P3T2 = -0.0582275390625;
|
|
31738
|
+
var P3T3 = 0.1015625;
|
|
31739
|
+
var P3T4 = -0.2003173828125;
|
|
31740
|
+
var P3T5 = 0.77978515625;
|
|
31741
|
+
var P3T6 = 0.465087890625;
|
|
31742
|
+
var P3T7 = -0.16650390625;
|
|
31743
|
+
var P3T8 = 0.089111328125;
|
|
31744
|
+
var P3T9 = -0.0517578125;
|
|
31745
|
+
var P3T10 = 0.029296875;
|
|
31746
|
+
var P3T11 = -0.0291748046875;
|
|
31630
31747
|
var TruePeakUpsampler = class {
|
|
31631
31748
|
constructor(factor = 4) {
|
|
31632
|
-
this.
|
|
31749
|
+
this.work = new Float64Array(0);
|
|
31633
31750
|
if (factor !== 4) {
|
|
31634
31751
|
throw new Error(`TruePeakUpsampler: factor ${factor} is not yet implemented; only 4\xD7 (BS.1770-4 Annex 1) is supported`);
|
|
31635
31752
|
}
|
|
31636
31753
|
this.factor = factor;
|
|
31637
31754
|
this.history = new Float64Array(HISTORY_LENGTH_4X);
|
|
31638
31755
|
}
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
* Output length is `input.length * factor`. State carries across
|
|
31642
|
-
* calls — feeding the same source in any chunk pattern produces
|
|
31643
|
-
* the same upsampled samples (modulo float ordering, which is
|
|
31644
|
-
* deterministic).
|
|
31645
|
-
*
|
|
31646
|
-
* The first 11 output frames after a {@link reset} (or after
|
|
31647
|
-
* construction) are coloured by the cold filter history (zeros).
|
|
31648
|
-
* For true-peak measurement this is harmless — the max-tracking
|
|
31649
|
-
* downstream sees only values ≤ the actual settled peak during
|
|
31650
|
-
* that ramp-up.
|
|
31651
|
-
*/
|
|
31652
|
-
upsample(input) {
|
|
31756
|
+
// `outputScratch` (>= input.length × factor) avoids the per-call allocation; the returned view aliases it.
|
|
31757
|
+
upsample(input, outputScratch) {
|
|
31653
31758
|
const factor = this.factor;
|
|
31654
31759
|
const inputLength = input.length;
|
|
31655
|
-
const
|
|
31760
|
+
const outputLength = inputLength * factor;
|
|
31761
|
+
const output = outputScratch !== void 0 && outputScratch.length >= outputLength ? outputScratch.subarray(0, outputLength) : new Float32Array(outputLength);
|
|
31656
31762
|
const history = this.history;
|
|
31657
31763
|
const historyLength = HISTORY_LENGTH_4X;
|
|
31658
|
-
|
|
31764
|
+
const workLength = historyLength + inputLength;
|
|
31765
|
+
if (this.work.length < workLength) {
|
|
31766
|
+
this.work = new Float64Array(workLength);
|
|
31767
|
+
}
|
|
31768
|
+
const work = this.work;
|
|
31769
|
+
work.set(history, 0);
|
|
31659
31770
|
for (let inIdx = 0; inIdx < inputLength; inIdx++) {
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
31771
|
+
work[historyLength + inIdx] = input[inIdx] ?? 0;
|
|
31772
|
+
}
|
|
31773
|
+
for (let inIdx = 0; inIdx < inputLength; inIdx++) {
|
|
31774
|
+
const currentIdx = historyLength + inIdx;
|
|
31663
31775
|
const outOffset = inIdx * factor;
|
|
31664
|
-
output[outOffset] =
|
|
31665
|
-
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
31671
|
-
|
|
31672
|
-
|
|
31673
|
-
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
31677
|
-
|
|
31678
|
-
|
|
31679
|
-
|
|
31776
|
+
output[outOffset] = input[inIdx] ?? 0;
|
|
31777
|
+
const v0 = work[currentIdx] ?? 0;
|
|
31778
|
+
const v1 = work[currentIdx - 1] ?? 0;
|
|
31779
|
+
const v2 = work[currentIdx - 2] ?? 0;
|
|
31780
|
+
const v3 = work[currentIdx - 3] ?? 0;
|
|
31781
|
+
const v4 = work[currentIdx - 4] ?? 0;
|
|
31782
|
+
const v5 = work[currentIdx - 5] ?? 0;
|
|
31783
|
+
const v6 = work[currentIdx - 6] ?? 0;
|
|
31784
|
+
const v7 = work[currentIdx - 7] ?? 0;
|
|
31785
|
+
const v8 = work[currentIdx - 8] ?? 0;
|
|
31786
|
+
const v9 = work[currentIdx - 9] ?? 0;
|
|
31787
|
+
const v10 = work[currentIdx - 10] ?? 0;
|
|
31788
|
+
const v11 = work[currentIdx - 11] ?? 0;
|
|
31789
|
+
let acc1 = 0;
|
|
31790
|
+
acc1 += P1T0 * v0;
|
|
31791
|
+
acc1 += P1T1 * v1;
|
|
31792
|
+
acc1 += P1T2 * v2;
|
|
31793
|
+
acc1 += P1T3 * v3;
|
|
31794
|
+
acc1 += P1T4 * v4;
|
|
31795
|
+
acc1 += P1T5 * v5;
|
|
31796
|
+
acc1 += P1T6 * v6;
|
|
31797
|
+
acc1 += P1T7 * v7;
|
|
31798
|
+
acc1 += P1T8 * v8;
|
|
31799
|
+
acc1 += P1T9 * v9;
|
|
31800
|
+
acc1 += P1T10 * v10;
|
|
31801
|
+
acc1 += P1T11 * v11;
|
|
31802
|
+
let acc2 = 0;
|
|
31803
|
+
acc2 += P2T0 * v0;
|
|
31804
|
+
acc2 += P2T1 * v1;
|
|
31805
|
+
acc2 += P2T2 * v2;
|
|
31806
|
+
acc2 += P2T3 * v3;
|
|
31807
|
+
acc2 += P2T4 * v4;
|
|
31808
|
+
acc2 += P2T5 * v5;
|
|
31809
|
+
acc2 += P2T6 * v6;
|
|
31810
|
+
acc2 += P2T7 * v7;
|
|
31811
|
+
acc2 += P2T8 * v8;
|
|
31812
|
+
acc2 += P2T9 * v9;
|
|
31813
|
+
acc2 += P2T10 * v10;
|
|
31814
|
+
acc2 += P2T11 * v11;
|
|
31815
|
+
let acc3 = 0;
|
|
31816
|
+
acc3 += P3T0 * v0;
|
|
31817
|
+
acc3 += P3T1 * v1;
|
|
31818
|
+
acc3 += P3T2 * v2;
|
|
31819
|
+
acc3 += P3T3 * v3;
|
|
31820
|
+
acc3 += P3T4 * v4;
|
|
31821
|
+
acc3 += P3T5 * v5;
|
|
31822
|
+
acc3 += P3T6 * v6;
|
|
31823
|
+
acc3 += P3T7 * v7;
|
|
31824
|
+
acc3 += P3T8 * v8;
|
|
31825
|
+
acc3 += P3T9 * v9;
|
|
31826
|
+
acc3 += P3T10 * v10;
|
|
31827
|
+
acc3 += P3T11 * v11;
|
|
31828
|
+
output[outOffset + 1] = acc1;
|
|
31829
|
+
output[outOffset + 2] = acc2;
|
|
31830
|
+
output[outOffset + 3] = acc3;
|
|
31831
|
+
}
|
|
31832
|
+
if (inputLength >= historyLength) {
|
|
31833
|
+
history.set(work.subarray(workLength - historyLength, workLength), 0);
|
|
31834
|
+
} else if (inputLength > 0) {
|
|
31835
|
+
history.copyWithin(0, inputLength);
|
|
31836
|
+
history.set(work.subarray(historyLength, workLength), historyLength - inputLength);
|
|
31837
|
+
}
|
|
31680
31838
|
return output;
|
|
31681
31839
|
}
|
|
31682
|
-
/**
|
|
31683
|
-
* Reset the 12-sample input history to zeros. Use when starting a
|
|
31684
|
-
* new render / stream / measurement on the same instance.
|
|
31685
|
-
*/
|
|
31686
31840
|
reset() {
|
|
31687
31841
|
this.history.fill(0);
|
|
31688
|
-
this.
|
|
31842
|
+
this.work = new Float64Array(0);
|
|
31689
31843
|
}
|
|
31690
31844
|
};
|
|
31691
31845
|
var DEFAULT_OVERSAMPLE_FACTOR = 4;
|
|
31692
31846
|
var TruePeakAccumulator = class {
|
|
31693
31847
|
constructor(_sampleRate, channelCount, oversampleFactor = DEFAULT_OVERSAMPLE_FACTOR) {
|
|
31848
|
+
this.upsampleScratch = new Float32Array(0);
|
|
31694
31849
|
this.runningMax = 0;
|
|
31695
31850
|
if (channelCount <= 0) {
|
|
31696
31851
|
throw new Error(`TruePeakAccumulator: channelCount must be positive, got ${channelCount}`);
|
|
@@ -31702,18 +31857,7 @@ var TruePeakAccumulator = class {
|
|
|
31702
31857
|
}
|
|
31703
31858
|
this.upsamplers = upsamplers;
|
|
31704
31859
|
}
|
|
31705
|
-
|
|
31706
|
-
* Consume `frames` of audio. `channels[c]` must have at least
|
|
31707
|
-
* `frames` valid samples starting at index 0; oversized buffers are
|
|
31708
|
-
* fine and avoid the need for caller-side slicing. The accumulator
|
|
31709
|
-
* advances per-channel upsampler state exactly as if the samples
|
|
31710
|
-
* were appended to a single contiguous buffer.
|
|
31711
|
-
*
|
|
31712
|
-
* The `frames` argument is accepted for symmetry with
|
|
31713
|
-
* {@link IntegratedLufsAccumulator} even though
|
|
31714
|
-
* {@link TruePeakUpsampler.upsample} derives length from the input
|
|
31715
|
-
* slice.
|
|
31716
|
-
*/
|
|
31860
|
+
// `channels[c]` needs >= `frames` valid samples from index 0 (oversized OK); advances per-channel upsampler state as if appended contiguously.
|
|
31717
31861
|
push(channels, frames) {
|
|
31718
31862
|
if (channels.length !== this.channelCount) {
|
|
31719
31863
|
throw new Error(`TruePeakAccumulator: push got ${channels.length} channels, expected ${this.channelCount}`);
|
|
@@ -31729,7 +31873,10 @@ var TruePeakAccumulator = class {
|
|
|
31729
31873
|
throw new Error(`TruePeakAccumulator: missing upsampler for channel ${channelIndex}`);
|
|
31730
31874
|
}
|
|
31731
31875
|
const slice = channel.length === frames ? channel : channel.subarray(0, frames);
|
|
31732
|
-
|
|
31876
|
+
if (this.upsampleScratch.length < frames * upsampler.factor) {
|
|
31877
|
+
this.upsampleScratch = new Float32Array(frames * upsampler.factor);
|
|
31878
|
+
}
|
|
31879
|
+
const upsampled = upsampler.upsample(slice, this.upsampleScratch);
|
|
31733
31880
|
for (let index = 0; index < upsampled.length; index++) {
|
|
31734
31881
|
const sample = upsampled[index] ?? 0;
|
|
31735
31882
|
const magnitude = sample < 0 ? -sample : sample;
|
|
@@ -31737,11 +31884,6 @@ var TruePeakAccumulator = class {
|
|
|
31737
31884
|
}
|
|
31738
31885
|
}
|
|
31739
31886
|
}
|
|
31740
|
-
/**
|
|
31741
|
-
* Return the linear amplitude max(|x|) across all upsampled samples
|
|
31742
|
-
* and all channels. Idempotent — additional calls return the same
|
|
31743
|
-
* value. Returns 0 when no samples have been pushed.
|
|
31744
|
-
*/
|
|
31745
31887
|
finalize() {
|
|
31746
31888
|
return this.runningMax;
|
|
31747
31889
|
}
|
|
@@ -32244,10 +32386,10 @@ var _ReadFfmpegNode = class _ReadFfmpegNode extends SourceNode {
|
|
|
32244
32386
|
return new _ReadFfmpegNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
32245
32387
|
}
|
|
32246
32388
|
};
|
|
32247
|
-
_ReadFfmpegNode.
|
|
32389
|
+
_ReadFfmpegNode.nodeName = "Read FFmpeg";
|
|
32248
32390
|
_ReadFfmpegNode.packageName = PACKAGE_NAME;
|
|
32249
32391
|
_ReadFfmpegNode.packageVersion = PACKAGE_VERSION;
|
|
32250
|
-
_ReadFfmpegNode.
|
|
32392
|
+
_ReadFfmpegNode.nodeDescription = "Read audio from a file using FFmpeg";
|
|
32251
32393
|
_ReadFfmpegNode.schema = ffmpegSchema;
|
|
32252
32394
|
var ReadFfmpegNode = _ReadFfmpegNode;
|
|
32253
32395
|
function readFfmpeg(path2, options) {
|
|
@@ -32427,10 +32569,10 @@ var _ReadWavNode = class _ReadWavNode extends SourceNode {
|
|
|
32427
32569
|
return new _ReadWavNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
32428
32570
|
}
|
|
32429
32571
|
};
|
|
32430
|
-
_ReadWavNode.
|
|
32572
|
+
_ReadWavNode.nodeName = "Read WAV";
|
|
32431
32573
|
_ReadWavNode.packageName = PACKAGE_NAME;
|
|
32432
32574
|
_ReadWavNode.packageVersion = PACKAGE_VERSION;
|
|
32433
|
-
_ReadWavNode.
|
|
32575
|
+
_ReadWavNode.nodeDescription = "Read audio from a WAV file";
|
|
32434
32576
|
_ReadWavNode.schema = wavSchema;
|
|
32435
32577
|
var ReadWavNode = _ReadWavNode;
|
|
32436
32578
|
function readWav(path2, options) {
|
|
@@ -32462,10 +32604,10 @@ var _ReadNode = class _ReadNode extends SourceNode {
|
|
|
32462
32604
|
return new _ReadNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
32463
32605
|
}
|
|
32464
32606
|
};
|
|
32465
|
-
_ReadNode.
|
|
32607
|
+
_ReadNode.nodeName = "Read";
|
|
32466
32608
|
_ReadNode.packageName = PACKAGE_NAME;
|
|
32467
32609
|
_ReadNode.packageVersion = PACKAGE_VERSION;
|
|
32468
|
-
_ReadNode.
|
|
32610
|
+
_ReadNode.nodeDescription = "Read audio from a file";
|
|
32469
32611
|
_ReadNode.schema = schema;
|
|
32470
32612
|
var ReadNode = _ReadNode;
|
|
32471
32613
|
function read(path2, options) {
|
|
@@ -32599,10 +32741,10 @@ var _LoudnessStatsNode = class _LoudnessStatsNode extends TargetNode {
|
|
|
32599
32741
|
return new _LoudnessStatsNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
32600
32742
|
}
|
|
32601
32743
|
};
|
|
32602
|
-
_LoudnessStatsNode.
|
|
32744
|
+
_LoudnessStatsNode.nodeName = "Loudness Stats";
|
|
32603
32745
|
_LoudnessStatsNode.packageName = PACKAGE_NAME;
|
|
32604
32746
|
_LoudnessStatsNode.packageVersion = PACKAGE_VERSION;
|
|
32605
|
-
_LoudnessStatsNode.
|
|
32747
|
+
_LoudnessStatsNode.nodeDescription = "Measure integrated loudness, true peak, and loudness range per EBU R128, plus an amplitude-distribution histogram";
|
|
32606
32748
|
_LoudnessStatsNode.schema = schema2;
|
|
32607
32749
|
var LoudnessStatsNode = _LoudnessStatsNode;
|
|
32608
32750
|
function loudnessStats(options) {
|
|
@@ -32942,10 +33084,10 @@ var _SpectrogramNode = class _SpectrogramNode extends TargetNode {
|
|
|
32942
33084
|
return new _SpectrogramNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
32943
33085
|
}
|
|
32944
33086
|
};
|
|
32945
|
-
_SpectrogramNode.
|
|
33087
|
+
_SpectrogramNode.nodeName = "Spectrogram";
|
|
32946
33088
|
_SpectrogramNode.packageName = PACKAGE_NAME;
|
|
32947
33089
|
_SpectrogramNode.packageVersion = PACKAGE_VERSION;
|
|
32948
|
-
_SpectrogramNode.
|
|
33090
|
+
_SpectrogramNode.nodeDescription = "Generate spectrogram visualization data";
|
|
32949
33091
|
_SpectrogramNode.schema = schema3;
|
|
32950
33092
|
var SpectrogramNode = _SpectrogramNode;
|
|
32951
33093
|
function spectrogram(outputPath, options) {
|
|
@@ -33093,10 +33235,10 @@ var _WaveformNode = class _WaveformNode extends TargetNode {
|
|
|
33093
33235
|
return new _WaveformNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33094
33236
|
}
|
|
33095
33237
|
};
|
|
33096
|
-
_WaveformNode.
|
|
33238
|
+
_WaveformNode.nodeName = "Waveform";
|
|
33097
33239
|
_WaveformNode.packageName = PACKAGE_NAME;
|
|
33098
33240
|
_WaveformNode.packageVersion = PACKAGE_VERSION;
|
|
33099
|
-
_WaveformNode.
|
|
33241
|
+
_WaveformNode.nodeDescription = "Generate waveform visualization data";
|
|
33100
33242
|
_WaveformNode.schema = schema4;
|
|
33101
33243
|
var WaveformNode = _WaveformNode;
|
|
33102
33244
|
function waveform(outputPath, options) {
|
|
@@ -33411,10 +33553,10 @@ var _WriteNode = class _WriteNode extends TargetNode {
|
|
|
33411
33553
|
return new _WriteNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33412
33554
|
}
|
|
33413
33555
|
};
|
|
33414
|
-
_WriteNode.
|
|
33556
|
+
_WriteNode.nodeName = "Write";
|
|
33415
33557
|
_WriteNode.packageName = PACKAGE_NAME;
|
|
33416
33558
|
_WriteNode.packageVersion = PACKAGE_VERSION;
|
|
33417
|
-
_WriteNode.
|
|
33559
|
+
_WriteNode.nodeDescription = "Write audio to a file";
|
|
33418
33560
|
_WriteNode.schema = schema5;
|
|
33419
33561
|
var WriteNode = _WriteNode;
|
|
33420
33562
|
function write(path2, options) {
|
|
@@ -33502,10 +33644,10 @@ var _CutNode = class _CutNode extends TransformNode {
|
|
|
33502
33644
|
return new _CutNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33503
33645
|
}
|
|
33504
33646
|
};
|
|
33505
|
-
_CutNode.
|
|
33647
|
+
_CutNode.nodeName = "Cut";
|
|
33506
33648
|
_CutNode.packageName = PACKAGE_NAME;
|
|
33507
33649
|
_CutNode.packageVersion = PACKAGE_VERSION;
|
|
33508
|
-
_CutNode.
|
|
33650
|
+
_CutNode.nodeDescription = "Remove a region of audio";
|
|
33509
33651
|
_CutNode.schema = schema6;
|
|
33510
33652
|
var CutNode = _CutNode;
|
|
33511
33653
|
function cut(regions, options) {
|
|
@@ -33567,10 +33709,10 @@ var _DitherNode = class _DitherNode extends TransformNode {
|
|
|
33567
33709
|
return new _DitherNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33568
33710
|
}
|
|
33569
33711
|
};
|
|
33570
|
-
_DitherNode.
|
|
33712
|
+
_DitherNode.nodeName = "Dither";
|
|
33571
33713
|
_DitherNode.packageName = PACKAGE_NAME;
|
|
33572
33714
|
_DitherNode.packageVersion = PACKAGE_VERSION;
|
|
33573
|
-
_DitherNode.
|
|
33715
|
+
_DitherNode.nodeDescription = "Add shaped noise to reduce quantization distortion";
|
|
33574
33716
|
_DitherNode.schema = schema7;
|
|
33575
33717
|
var DitherNode = _DitherNode;
|
|
33576
33718
|
function dither(bitDepth, options) {
|
|
@@ -33629,10 +33771,10 @@ var _NormalizeNode = class _NormalizeNode extends TransformNode {
|
|
|
33629
33771
|
return new _NormalizeNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33630
33772
|
}
|
|
33631
33773
|
};
|
|
33632
|
-
_NormalizeNode.
|
|
33774
|
+
_NormalizeNode.nodeName = "Normalize";
|
|
33633
33775
|
_NormalizeNode.packageName = PACKAGE_NAME;
|
|
33634
33776
|
_NormalizeNode.packageVersion = PACKAGE_VERSION;
|
|
33635
|
-
_NormalizeNode.
|
|
33777
|
+
_NormalizeNode.nodeDescription = "Adjust peak or loudness level to a target ceiling";
|
|
33636
33778
|
_NormalizeNode.schema = schema8;
|
|
33637
33779
|
var NormalizeNode = _NormalizeNode;
|
|
33638
33780
|
function normalize(options) {
|
|
@@ -33646,53 +33788,57 @@ var schema9 = external_exports.object({
|
|
|
33646
33788
|
after: external_exports.number().min(0).multipleOf(1e-3).default(0).describe("After")
|
|
33647
33789
|
});
|
|
33648
33790
|
var PadStream = class extends BufferedTransformStream {
|
|
33649
|
-
|
|
33650
|
-
|
|
33651
|
-
|
|
33652
|
-
|
|
33653
|
-
|
|
33654
|
-
|
|
33655
|
-
|
|
33656
|
-
|
|
33657
|
-
|
|
33658
|
-
const
|
|
33659
|
-
|
|
33660
|
-
|
|
33661
|
-
|
|
33662
|
-
|
|
33663
|
-
|
|
33664
|
-
|
|
33665
|
-
|
|
33666
|
-
|
|
33667
|
-
|
|
33668
|
-
|
|
33669
|
-
|
|
33670
|
-
|
|
33671
|
-
|
|
33672
|
-
|
|
33673
|
-
|
|
33674
|
-
if (chunkFrames === 0) break;
|
|
33675
|
-
await buffer.write(chunk.samples, sr, bd);
|
|
33676
|
-
if (chunkFrames < CHUNK_FRAMES) break;
|
|
33791
|
+
constructor() {
|
|
33792
|
+
super(...arguments);
|
|
33793
|
+
this.seenChunk = false;
|
|
33794
|
+
this.capturedSampleRate = 44100;
|
|
33795
|
+
this.capturedBitDepth = 32;
|
|
33796
|
+
this.capturedChannels = 0;
|
|
33797
|
+
this.outputOffset = 0;
|
|
33798
|
+
}
|
|
33799
|
+
_unbuffer(chunk) {
|
|
33800
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
33801
|
+
if (!this.seenChunk) {
|
|
33802
|
+
this.seenChunk = true;
|
|
33803
|
+
this.capturedSampleRate = chunk.sampleRate;
|
|
33804
|
+
this.capturedBitDepth = chunk.bitDepth;
|
|
33805
|
+
this.capturedChannels = chunk.samples.length;
|
|
33806
|
+
const leading = Math.round(this.properties.before * chunk.sampleRate);
|
|
33807
|
+
if (leading > 0) {
|
|
33808
|
+
const samples = chunk.samples.map((channel) => {
|
|
33809
|
+
const padded = new Float32Array(leading + frames);
|
|
33810
|
+
padded.set(channel, leading);
|
|
33811
|
+
return padded;
|
|
33812
|
+
});
|
|
33813
|
+
const offset2 = this.outputOffset;
|
|
33814
|
+
this.outputOffset += leading + frames;
|
|
33815
|
+
return { samples, offset: offset2, sampleRate: chunk.sampleRate, bitDepth: chunk.bitDepth };
|
|
33677
33816
|
}
|
|
33678
|
-
} finally {
|
|
33679
|
-
await output.close();
|
|
33680
33817
|
}
|
|
33818
|
+
const offset = this.outputOffset;
|
|
33819
|
+
this.outputOffset += frames;
|
|
33820
|
+
return { samples: chunk.samples, offset, sampleRate: chunk.sampleRate, bitDepth: chunk.bitDepth };
|
|
33681
33821
|
}
|
|
33682
|
-
|
|
33683
|
-
|
|
33684
|
-
|
|
33685
|
-
|
|
33686
|
-
const
|
|
33687
|
-
|
|
33688
|
-
|
|
33689
|
-
|
|
33690
|
-
|
|
33822
|
+
_flush() {
|
|
33823
|
+
if (!this.seenChunk) return void 0;
|
|
33824
|
+
const trailing = Math.round(this.properties.after * this.capturedSampleRate);
|
|
33825
|
+
if (trailing === 0) return void 0;
|
|
33826
|
+
const chunks = [];
|
|
33827
|
+
let remaining = trailing;
|
|
33828
|
+
while (remaining > 0) {
|
|
33829
|
+
const take = Math.min(CHUNK_FRAMES, remaining);
|
|
33830
|
+
const samples = Array.from({ length: this.capturedChannels }, () => new Float32Array(take));
|
|
33831
|
+
const offset = this.outputOffset;
|
|
33832
|
+
this.outputOffset += take;
|
|
33833
|
+
chunks.push({ samples, offset, sampleRate: this.capturedSampleRate, bitDepth: this.capturedBitDepth });
|
|
33834
|
+
remaining -= take;
|
|
33835
|
+
}
|
|
33836
|
+
return chunks;
|
|
33691
33837
|
}
|
|
33692
|
-
}
|
|
33838
|
+
};
|
|
33693
33839
|
var _PadNode = class _PadNode extends TransformNode {
|
|
33694
33840
|
constructor(properties) {
|
|
33695
|
-
super({ bufferSize:
|
|
33841
|
+
super({ bufferSize: 0, latency: 0, ...properties });
|
|
33696
33842
|
this.type = ["buffered-audio-node", "transform", "pad"];
|
|
33697
33843
|
}
|
|
33698
33844
|
static is(value) {
|
|
@@ -33705,10 +33851,10 @@ var _PadNode = class _PadNode extends TransformNode {
|
|
|
33705
33851
|
return new _PadNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33706
33852
|
}
|
|
33707
33853
|
};
|
|
33708
|
-
_PadNode.
|
|
33854
|
+
_PadNode.nodeName = "Pad";
|
|
33709
33855
|
_PadNode.packageName = PACKAGE_NAME;
|
|
33710
33856
|
_PadNode.packageVersion = PACKAGE_VERSION;
|
|
33711
|
-
_PadNode.
|
|
33857
|
+
_PadNode.nodeDescription = "Add silence to start or end of audio";
|
|
33712
33858
|
_PadNode.schema = schema9;
|
|
33713
33859
|
var PadNode = _PadNode;
|
|
33714
33860
|
function pad(options) {
|
|
@@ -33782,10 +33928,10 @@ var _PhaseNode = class _PhaseNode extends TransformNode {
|
|
|
33782
33928
|
return new _PhaseNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33783
33929
|
}
|
|
33784
33930
|
};
|
|
33785
|
-
_PhaseNode.
|
|
33931
|
+
_PhaseNode.nodeName = "Phase";
|
|
33786
33932
|
_PhaseNode.packageName = PACKAGE_NAME;
|
|
33787
33933
|
_PhaseNode.packageVersion = PACKAGE_VERSION;
|
|
33788
|
-
_PhaseNode.
|
|
33934
|
+
_PhaseNode.nodeDescription = "Invert or rotate signal phase";
|
|
33789
33935
|
_PhaseNode.schema = schema10;
|
|
33790
33936
|
var PhaseNode = _PhaseNode;
|
|
33791
33937
|
function phase(options) {
|
|
@@ -33839,10 +33985,10 @@ var _ReverseNode = class _ReverseNode extends TransformNode {
|
|
|
33839
33985
|
return new _ReverseNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33840
33986
|
}
|
|
33841
33987
|
};
|
|
33842
|
-
_ReverseNode.
|
|
33988
|
+
_ReverseNode.nodeName = "Reverse";
|
|
33843
33989
|
_ReverseNode.packageName = PACKAGE_NAME;
|
|
33844
33990
|
_ReverseNode.packageVersion = PACKAGE_VERSION;
|
|
33845
|
-
_ReverseNode.
|
|
33991
|
+
_ReverseNode.nodeDescription = "Reverse audio playback direction";
|
|
33846
33992
|
_ReverseNode.schema = schema11;
|
|
33847
33993
|
var ReverseNode = _ReverseNode;
|
|
33848
33994
|
function reverse(options) {
|
|
@@ -33970,10 +34116,10 @@ var _SpliceNode = class _SpliceNode extends TransformNode {
|
|
|
33970
34116
|
return new _SpliceNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
33971
34117
|
}
|
|
33972
34118
|
};
|
|
33973
|
-
_SpliceNode.
|
|
34119
|
+
_SpliceNode.nodeName = "Splice";
|
|
33974
34120
|
_SpliceNode.packageName = PACKAGE_NAME;
|
|
33975
34121
|
_SpliceNode.packageVersion = PACKAGE_VERSION;
|
|
33976
|
-
_SpliceNode.
|
|
34122
|
+
_SpliceNode.nodeDescription = "Replace a region of audio with processed content";
|
|
33977
34123
|
_SpliceNode.schema = schema12;
|
|
33978
34124
|
var SpliceNode = _SpliceNode;
|
|
33979
34125
|
function splice(insertPath, insertAt, options) {
|
|
@@ -34003,7 +34149,6 @@ function findLastAbove(samples, frames, threshold) {
|
|
|
34003
34149
|
}
|
|
34004
34150
|
|
|
34005
34151
|
// src/transforms/trim/index.ts
|
|
34006
|
-
var CHUNK_FRAMES3 = 44100;
|
|
34007
34152
|
var schema13 = external_exports.object({
|
|
34008
34153
|
threshold: external_exports.number().min(0).max(1).multipleOf(1e-3).default(1e-3).describe("Threshold"),
|
|
34009
34154
|
margin: external_exports.number().min(0).max(1).multipleOf(1e-3).default(0.01).describe("Margin"),
|
|
@@ -34011,93 +34156,66 @@ var schema13 = external_exports.object({
|
|
|
34011
34156
|
end: external_exports.boolean().default(true).describe("End")
|
|
34012
34157
|
});
|
|
34013
34158
|
var TrimStream = class extends BufferedTransformStream {
|
|
34159
|
+
constructor() {
|
|
34160
|
+
super(...arguments);
|
|
34161
|
+
this.firstAbove = Infinity;
|
|
34162
|
+
this.lastAbove = -1;
|
|
34163
|
+
this.scanOffset = 0;
|
|
34164
|
+
this.startFrame = 0;
|
|
34165
|
+
this.endFrame = 0;
|
|
34166
|
+
}
|
|
34167
|
+
async _buffer(chunk, buffer) {
|
|
34168
|
+
await super._buffer(chunk, buffer);
|
|
34169
|
+
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
34170
|
+
if (chunkFrames === 0) return;
|
|
34171
|
+
const threshold = this.properties.threshold;
|
|
34172
|
+
const localFirst = findFirstAbove(chunk.samples, chunkFrames, threshold);
|
|
34173
|
+
if (localFirst < chunkFrames) {
|
|
34174
|
+
const abs = this.scanOffset + localFirst;
|
|
34175
|
+
if (abs < this.firstAbove) this.firstAbove = abs;
|
|
34176
|
+
this.lastAbove = Math.max(this.lastAbove, this.scanOffset + findLastAbove(chunk.samples, chunkFrames, threshold));
|
|
34177
|
+
}
|
|
34178
|
+
this.scanOffset += chunkFrames;
|
|
34179
|
+
}
|
|
34014
34180
|
async _process(buffer) {
|
|
34015
34181
|
const frames = buffer.frames;
|
|
34016
34182
|
const channels = buffer.channels;
|
|
34017
34183
|
if (channels === 0 || frames === 0) return;
|
|
34018
|
-
|
|
34019
|
-
const sr = buffer.sampleRate ?? 44100;
|
|
34020
|
-
const bd = buffer.bitDepth;
|
|
34021
|
-
const marginFrames = Math.round(this.properties.margin * sr);
|
|
34022
|
-
const trimStart = this.properties.start;
|
|
34023
|
-
const trimEnd = this.properties.end;
|
|
34024
|
-
await buffer.reset();
|
|
34025
|
-
let firstAbove = frames;
|
|
34026
|
-
let lastAbove = -1;
|
|
34027
|
-
let scanOffset = 0;
|
|
34028
|
-
for (; ; ) {
|
|
34029
|
-
const chunk = await buffer.read(CHUNK_FRAMES3);
|
|
34030
|
-
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
34031
|
-
if (chunkFrames === 0) break;
|
|
34032
|
-
const localFirst = findFirstAbove(chunk.samples, chunkFrames, threshold);
|
|
34033
|
-
if (localFirst < chunkFrames) {
|
|
34034
|
-
const abs = scanOffset + localFirst;
|
|
34035
|
-
if (abs < firstAbove) firstAbove = abs;
|
|
34036
|
-
lastAbove = Math.max(lastAbove, scanOffset + findLastAbove(chunk.samples, chunkFrames, threshold));
|
|
34037
|
-
}
|
|
34038
|
-
scanOffset += chunkFrames;
|
|
34039
|
-
if (chunkFrames < CHUNK_FRAMES3) break;
|
|
34040
|
-
}
|
|
34041
|
-
if (firstAbove >= frames) {
|
|
34184
|
+
if (this.firstAbove >= frames) {
|
|
34042
34185
|
await buffer.clear();
|
|
34043
34186
|
return;
|
|
34044
34187
|
}
|
|
34188
|
+
const sr = buffer.sampleRate ?? 44100;
|
|
34189
|
+
const marginFrames = Math.round(this.properties.margin * sr);
|
|
34045
34190
|
let startFrame = 0;
|
|
34046
34191
|
let endFrame = frames;
|
|
34047
|
-
if (
|
|
34048
|
-
|
|
34049
|
-
}
|
|
34050
|
-
if (trimEnd) {
|
|
34051
|
-
endFrame = Math.min(frames, lastAbove + 1 + marginFrames);
|
|
34052
|
-
}
|
|
34192
|
+
if (this.properties.start) startFrame = Math.max(0, this.firstAbove - marginFrames);
|
|
34193
|
+
if (this.properties.end) endFrame = Math.min(frames, this.lastAbove + 1 + marginFrames);
|
|
34053
34194
|
if (startFrame >= endFrame) {
|
|
34054
34195
|
await buffer.clear();
|
|
34055
34196
|
return;
|
|
34056
34197
|
}
|
|
34057
|
-
|
|
34058
|
-
|
|
34059
|
-
|
|
34060
|
-
|
|
34061
|
-
|
|
34062
|
-
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
|
|
34066
|
-
|
|
34067
|
-
|
|
34068
|
-
|
|
34069
|
-
|
|
34070
|
-
|
|
34071
|
-
|
|
34072
|
-
|
|
34073
|
-
|
|
34074
|
-
|
|
34075
|
-
|
|
34076
|
-
|
|
34077
|
-
|
|
34078
|
-
const source = chunk.samples[ch];
|
|
34079
|
-
if (source) sliced.push(source.subarray(sliceStart, sliceEnd));
|
|
34080
|
-
else sliced.push(new Float32Array(sliceEnd - sliceStart));
|
|
34081
|
-
}
|
|
34082
|
-
await output.write(sliced, sr, bd);
|
|
34083
|
-
}
|
|
34084
|
-
}
|
|
34085
|
-
copyOffset = chunkEnd;
|
|
34086
|
-
if (copyOffset >= endFrame) break;
|
|
34087
|
-
if (chunkFrames < CHUNK_FRAMES3) break;
|
|
34088
|
-
}
|
|
34089
|
-
await buffer.clear();
|
|
34090
|
-
await output.reset();
|
|
34091
|
-
for (; ; ) {
|
|
34092
|
-
const chunk = await output.read(CHUNK_FRAMES3);
|
|
34093
|
-
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
34094
|
-
if (chunkFrames === 0) break;
|
|
34095
|
-
await buffer.write(chunk.samples, sr, bd);
|
|
34096
|
-
if (chunkFrames < CHUNK_FRAMES3) break;
|
|
34097
|
-
}
|
|
34098
|
-
} finally {
|
|
34099
|
-
await output.close();
|
|
34100
|
-
}
|
|
34198
|
+
this.startFrame = startFrame;
|
|
34199
|
+
this.endFrame = endFrame;
|
|
34200
|
+
}
|
|
34201
|
+
_unbuffer(chunk) {
|
|
34202
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
34203
|
+
const chunkStart = chunk.offset;
|
|
34204
|
+
const chunkEnd = chunkStart + frames;
|
|
34205
|
+
const overlapStart = Math.max(chunkStart, this.startFrame);
|
|
34206
|
+
const overlapEnd = Math.min(chunkEnd, this.endFrame);
|
|
34207
|
+
if (overlapEnd <= overlapStart) return void 0;
|
|
34208
|
+
if (overlapStart === chunkStart && overlapEnd === chunkEnd) {
|
|
34209
|
+
return { samples: chunk.samples, offset: chunkStart - this.startFrame, sampleRate: chunk.sampleRate, bitDepth: chunk.bitDepth };
|
|
34210
|
+
}
|
|
34211
|
+
const sliceStart = overlapStart - chunkStart;
|
|
34212
|
+
const sliceEnd = overlapEnd - chunkStart;
|
|
34213
|
+
return {
|
|
34214
|
+
samples: chunk.samples.map((channel) => channel.subarray(sliceStart, sliceEnd)),
|
|
34215
|
+
offset: overlapStart - this.startFrame,
|
|
34216
|
+
sampleRate: chunk.sampleRate,
|
|
34217
|
+
bitDepth: chunk.bitDepth
|
|
34218
|
+
};
|
|
34101
34219
|
}
|
|
34102
34220
|
};
|
|
34103
34221
|
var _TrimNode = class _TrimNode extends TransformNode {
|
|
@@ -34115,10 +34233,10 @@ var _TrimNode = class _TrimNode extends TransformNode {
|
|
|
34115
34233
|
return new _TrimNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34116
34234
|
}
|
|
34117
34235
|
};
|
|
34118
|
-
_TrimNode.
|
|
34236
|
+
_TrimNode.nodeName = "Trim";
|
|
34119
34237
|
_TrimNode.packageName = PACKAGE_NAME;
|
|
34120
34238
|
_TrimNode.packageVersion = PACKAGE_VERSION;
|
|
34121
|
-
_TrimNode.
|
|
34239
|
+
_TrimNode.nodeDescription = "Remove silence from start and end";
|
|
34122
34240
|
_TrimNode.schema = schema13;
|
|
34123
34241
|
var TrimNode = _TrimNode;
|
|
34124
34242
|
function trim(options) {
|
|
@@ -34160,10 +34278,10 @@ var _DownmixMonoNode = class _DownmixMonoNode extends TransformNode {
|
|
|
34160
34278
|
return new _DownmixMonoNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34161
34279
|
}
|
|
34162
34280
|
};
|
|
34163
|
-
_DownmixMonoNode.
|
|
34281
|
+
_DownmixMonoNode.nodeName = "Downmix Mono";
|
|
34164
34282
|
_DownmixMonoNode.packageName = PACKAGE_NAME;
|
|
34165
34283
|
_DownmixMonoNode.packageVersion = PACKAGE_VERSION;
|
|
34166
|
-
_DownmixMonoNode.
|
|
34284
|
+
_DownmixMonoNode.nodeDescription = "Mix all input channels to a single mono channel by averaging";
|
|
34167
34285
|
_DownmixMonoNode.schema = schema14;
|
|
34168
34286
|
var DownmixMonoNode = _DownmixMonoNode;
|
|
34169
34287
|
function downmixMono(options) {
|
|
@@ -34204,10 +34322,10 @@ var _DuplicateChannelsNode = class _DuplicateChannelsNode extends TransformNode
|
|
|
34204
34322
|
return new _DuplicateChannelsNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34205
34323
|
}
|
|
34206
34324
|
};
|
|
34207
|
-
_DuplicateChannelsNode.
|
|
34325
|
+
_DuplicateChannelsNode.nodeName = "Duplicate Channels";
|
|
34208
34326
|
_DuplicateChannelsNode.packageName = PACKAGE_NAME;
|
|
34209
34327
|
_DuplicateChannelsNode.packageVersion = PACKAGE_VERSION;
|
|
34210
|
-
_DuplicateChannelsNode.
|
|
34328
|
+
_DuplicateChannelsNode.nodeDescription = "Duplicate a mono signal into multiple identical output channels; requires exactly 1 input channel, throws otherwise";
|
|
34211
34329
|
_DuplicateChannelsNode.schema = schema15;
|
|
34212
34330
|
var DuplicateChannelsNode = _DuplicateChannelsNode;
|
|
34213
34331
|
function duplicateChannels(options) {
|
|
@@ -34248,10 +34366,10 @@ var _GainNode = class _GainNode extends TransformNode {
|
|
|
34248
34366
|
return new _GainNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34249
34367
|
}
|
|
34250
34368
|
};
|
|
34251
|
-
_GainNode.
|
|
34369
|
+
_GainNode.nodeName = "Gain";
|
|
34252
34370
|
_GainNode.packageName = PACKAGE_NAME;
|
|
34253
34371
|
_GainNode.packageVersion = PACKAGE_VERSION;
|
|
34254
|
-
_GainNode.
|
|
34372
|
+
_GainNode.nodeDescription = "Adjust signal level by a fixed amount in dB";
|
|
34255
34373
|
_GainNode.schema = schema16;
|
|
34256
34374
|
var GainNode = _GainNode;
|
|
34257
34375
|
function gain(options) {
|
|
@@ -34314,10 +34432,10 @@ var _PanNode = class _PanNode extends TransformNode {
|
|
|
34314
34432
|
return new _PanNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34315
34433
|
}
|
|
34316
34434
|
};
|
|
34317
|
-
_PanNode.
|
|
34435
|
+
_PanNode.nodeName = "Pan";
|
|
34318
34436
|
_PanNode.packageName = PACKAGE_NAME;
|
|
34319
34437
|
_PanNode.packageVersion = PACKAGE_VERSION;
|
|
34320
|
-
_PanNode.
|
|
34438
|
+
_PanNode.nodeDescription = "Position mono signal in stereo field or adjust stereo balance; throws for inputs with more than 2 channels";
|
|
34321
34439
|
_PanNode.schema = schema17;
|
|
34322
34440
|
var PanNode = _PanNode;
|
|
34323
34441
|
function pan(options) {
|
|
@@ -34434,6 +34552,7 @@ var FfmpegStream = class extends BufferedTransformStream {
|
|
|
34434
34552
|
};
|
|
34435
34553
|
controller.enqueue(audioChunk);
|
|
34436
34554
|
this.outputOffset += frameCount;
|
|
34555
|
+
this.emitProgress("emit", this.outputOffset);
|
|
34437
34556
|
this.stdoutStash = merged.subarray(completeBytes);
|
|
34438
34557
|
}
|
|
34439
34558
|
async handleChunk(chunk, controller) {
|
|
@@ -34464,12 +34583,14 @@ var FfmpegStream = class extends BufferedTransformStream {
|
|
|
34464
34583
|
});
|
|
34465
34584
|
}
|
|
34466
34585
|
this.framesProcessed += frames;
|
|
34467
|
-
this.
|
|
34586
|
+
this.emitProgress("buffer", this.framesProcessed, this._sourceTotalFrames);
|
|
34468
34587
|
}
|
|
34469
34588
|
async handleFlushStream(controller) {
|
|
34470
34589
|
const child = this.child;
|
|
34471
34590
|
if (!child) {
|
|
34472
|
-
this.
|
|
34591
|
+
this.emitProgress("buffer", this.framesProcessed, this._sourceTotalFrames, { force: true });
|
|
34592
|
+
this.emitProgress("emit", this.outputOffset, void 0, { force: true });
|
|
34593
|
+
this.events.emit("finished", { framesDone: this.framesProcessed });
|
|
34473
34594
|
return;
|
|
34474
34595
|
}
|
|
34475
34596
|
if (this.pendingDrain) {
|
|
@@ -34486,7 +34607,9 @@ var FfmpegStream = class extends BufferedTransformStream {
|
|
|
34486
34607
|
if (this.stdoutStash.length >= this.inputChannels * 4) {
|
|
34487
34608
|
this.handleStdoutBytes(Buffer.alloc(0), controller);
|
|
34488
34609
|
}
|
|
34489
|
-
this.
|
|
34610
|
+
this.emitProgress("buffer", this.framesProcessed, this._sourceTotalFrames, { force: true });
|
|
34611
|
+
this.emitProgress("emit", this.outputOffset, void 0, { force: true });
|
|
34612
|
+
this.events.emit("finished", { framesDone: this.framesProcessed });
|
|
34490
34613
|
}
|
|
34491
34614
|
async _teardown() {
|
|
34492
34615
|
const child = this.child;
|
|
@@ -34516,10 +34639,10 @@ var _FfmpegNode = class _FfmpegNode extends TransformNode {
|
|
|
34516
34639
|
return new _FfmpegNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
34517
34640
|
}
|
|
34518
34641
|
};
|
|
34519
|
-
_FfmpegNode.
|
|
34642
|
+
_FfmpegNode.nodeName = "FFmpeg";
|
|
34520
34643
|
_FfmpegNode.packageName = PACKAGE_NAME;
|
|
34521
34644
|
_FfmpegNode.packageVersion = PACKAGE_VERSION;
|
|
34522
|
-
_FfmpegNode.
|
|
34645
|
+
_FfmpegNode.nodeDescription = "Process audio through FFmpeg filters";
|
|
34523
34646
|
_FfmpegNode.schema = schema18;
|
|
34524
34647
|
var FfmpegNode = _FfmpegNode;
|
|
34525
34648
|
function ffmpeg(options) {
|
|
@@ -34581,22 +34704,6 @@ function applyBaseRateChunk(args) {
|
|
|
34581
34704
|
return output;
|
|
34582
34705
|
}
|
|
34583
34706
|
|
|
34584
|
-
// src/transforms/loudness-target/utils/curve.ts
|
|
34585
|
-
function gainDbAt(absXDb, anchors) {
|
|
34586
|
-
const { floorDb, pivotDb, limitDb, B: boost, peakGainDb } = anchors;
|
|
34587
|
-
if (floorDb !== null && absXDb <= floorDb) return 0;
|
|
34588
|
-
if (absXDb < pivotDb) {
|
|
34589
|
-
if (floorDb === null) return boost;
|
|
34590
|
-
const position = (absXDb - floorDb) / (pivotDb - floorDb);
|
|
34591
|
-
return position * boost;
|
|
34592
|
-
}
|
|
34593
|
-
if (absXDb < limitDb) {
|
|
34594
|
-
const position = (absXDb - pivotDb) / (limitDb - pivotDb);
|
|
34595
|
-
return boost + position * (peakGainDb - boost);
|
|
34596
|
-
}
|
|
34597
|
-
return limitDb + peakGainDb - absXDb;
|
|
34598
|
-
}
|
|
34599
|
-
|
|
34600
34707
|
// src/transforms/loudness-target/utils/envelope.ts
|
|
34601
34708
|
function windowSamplesFromMs(smoothingMs, sampleRate) {
|
|
34602
34709
|
return Math.max(1, Math.round(smoothingMs * sampleRate / 1e3));
|
|
@@ -34615,61 +34722,75 @@ async function applyBackwardPassOverChunkBuffer(args) {
|
|
|
34615
34722
|
}
|
|
34616
34723
|
const sr = sourceBuffer.sampleRate;
|
|
34617
34724
|
const bd = sourceBuffer.bitDepth;
|
|
34618
|
-
const reversedSource = await reverseBuffer(sourceBuffer);
|
|
34619
34725
|
const filteredReversed = new ChunkBuffer();
|
|
34620
|
-
const iirForwardOrder = minHeldBuffer === void 0 ? void 0 : new ChunkBuffer();
|
|
34621
34726
|
try {
|
|
34622
|
-
|
|
34623
|
-
|
|
34624
|
-
const
|
|
34625
|
-
|
|
34626
|
-
for (; ; ) {
|
|
34627
|
-
const chunk = await reversedSource.read(chunkSize);
|
|
34628
|
-
const data = chunk.samples[0];
|
|
34629
|
-
const chunkLength = data?.length ?? 0;
|
|
34630
|
-
if (data === void 0 || chunkLength === 0) break;
|
|
34631
|
-
const filtered = iir.applyForwardPass(data, backwardState);
|
|
34632
|
-
await filteredReversed.write([filtered], sr, bd);
|
|
34633
|
-
if (chunkLength < chunkSize) break;
|
|
34634
|
-
}
|
|
34635
|
-
await filteredReversed.flushWrites();
|
|
34636
|
-
if (iirForwardOrder === void 0) {
|
|
34637
|
-
await reverseBuffer(filteredReversed, destBuffer);
|
|
34638
|
-
} else {
|
|
34639
|
-
await reverseBuffer(filteredReversed, iirForwardOrder);
|
|
34640
|
-
await iirForwardOrder.flushWrites();
|
|
34641
|
-
await iirForwardOrder.reset();
|
|
34642
|
-
await minHeldBuffer.reset();
|
|
34727
|
+
const backwardState = { value: 0 };
|
|
34728
|
+
let seeded = false;
|
|
34729
|
+
const sourceReader = await sourceBuffer.openReverseReader();
|
|
34730
|
+
try {
|
|
34643
34731
|
for (; ; ) {
|
|
34644
|
-
const
|
|
34645
|
-
const
|
|
34646
|
-
|
|
34647
|
-
if (
|
|
34648
|
-
|
|
34649
|
-
|
|
34650
|
-
if (minData?.length !== chunkLength) {
|
|
34651
|
-
throw new Error(
|
|
34652
|
-
`applyBackwardPassOverChunkBuffer: minHeldBuffer returned ${minData?.length ?? 0} samples; expected ${chunkLength}`
|
|
34653
|
-
);
|
|
34732
|
+
const chunk = await sourceReader.read(chunkSize);
|
|
34733
|
+
const reversed = chunk.samples[0];
|
|
34734
|
+
if (reversed === void 0 || reversed.length === 0) break;
|
|
34735
|
+
if (!seeded) {
|
|
34736
|
+
backwardState.value = reversed[0] ?? 0;
|
|
34737
|
+
seeded = true;
|
|
34654
34738
|
}
|
|
34655
|
-
const
|
|
34656
|
-
|
|
34657
|
-
|
|
34658
|
-
|
|
34659
|
-
|
|
34739
|
+
const filtered = iir.applyForwardPass(reversed, backwardState);
|
|
34740
|
+
await filteredReversed.write([filtered], sr, bd);
|
|
34741
|
+
}
|
|
34742
|
+
} finally {
|
|
34743
|
+
await sourceReader.close();
|
|
34744
|
+
}
|
|
34745
|
+
if (minHeldBuffer !== void 0) await minHeldBuffer.reset();
|
|
34746
|
+
const filteredReader = await filteredReversed.openReverseReader();
|
|
34747
|
+
try {
|
|
34748
|
+
for (; ; ) {
|
|
34749
|
+
const chunk = await filteredReader.read(chunkSize);
|
|
34750
|
+
const forwardOrder = chunk.samples[0];
|
|
34751
|
+
if (forwardOrder === void 0 || forwardOrder.length === 0) break;
|
|
34752
|
+
const stripeFrames = forwardOrder.length;
|
|
34753
|
+
if (minHeldBuffer !== void 0) {
|
|
34754
|
+
const minChunk = await minHeldBuffer.read(stripeFrames);
|
|
34755
|
+
const minData = minChunk.samples[0];
|
|
34756
|
+
if (minData?.length !== stripeFrames) {
|
|
34757
|
+
throw new Error(
|
|
34758
|
+
`applyBackwardPassOverChunkBuffer: minHeldBuffer returned ${minData?.length ?? 0} samples; expected ${stripeFrames}`
|
|
34759
|
+
);
|
|
34760
|
+
}
|
|
34761
|
+
for (let sampleIdx = 0; sampleIdx < stripeFrames; sampleIdx++) {
|
|
34762
|
+
const iirValue = forwardOrder[sampleIdx] ?? 0;
|
|
34763
|
+
const minValue = minData[sampleIdx] ?? 0;
|
|
34764
|
+
forwardOrder[sampleIdx] = iirValue < minValue ? iirValue : minValue;
|
|
34765
|
+
}
|
|
34660
34766
|
}
|
|
34661
|
-
await destBuffer.write([
|
|
34662
|
-
if (chunkLength < chunkSize) break;
|
|
34767
|
+
await destBuffer.write([forwardOrder], sr, bd);
|
|
34663
34768
|
}
|
|
34664
|
-
|
|
34769
|
+
} finally {
|
|
34770
|
+
await filteredReader.close();
|
|
34665
34771
|
}
|
|
34772
|
+
await destBuffer.flushWrites();
|
|
34666
34773
|
} finally {
|
|
34667
|
-
await reversedSource.close();
|
|
34668
34774
|
await filteredReversed.close();
|
|
34669
|
-
if (iirForwardOrder !== void 0) await iirForwardOrder.close();
|
|
34670
34775
|
}
|
|
34671
34776
|
}
|
|
34672
34777
|
|
|
34778
|
+
// src/transforms/loudness-target/utils/curve.ts
|
|
34779
|
+
function gainDbAt(absXDb, anchors) {
|
|
34780
|
+
const { floorDb, pivotDb, limitDb, B: boost, peakGainDb } = anchors;
|
|
34781
|
+
if (floorDb !== null && absXDb <= floorDb) return 0;
|
|
34782
|
+
if (absXDb < pivotDb) {
|
|
34783
|
+
if (floorDb === null) return boost;
|
|
34784
|
+
const position = (absXDb - floorDb) / (pivotDb - floorDb);
|
|
34785
|
+
return position * boost;
|
|
34786
|
+
}
|
|
34787
|
+
if (absXDb < limitDb) {
|
|
34788
|
+
const position = (absXDb - pivotDb) / (limitDb - pivotDb);
|
|
34789
|
+
return boost + position * (peakGainDb - boost);
|
|
34790
|
+
}
|
|
34791
|
+
return limitDb + peakGainDb - absXDb;
|
|
34792
|
+
}
|
|
34793
|
+
|
|
34673
34794
|
// src/transforms/loudness-target/utils/source-caches.ts
|
|
34674
34795
|
async function buildBaseRateDetectionCache(args) {
|
|
34675
34796
|
const { buffer, sampleRate, channelCount, frames, halfWidth } = args;
|
|
@@ -34683,12 +34804,14 @@ async function buildBaseRateDetectionCache(args) {
|
|
|
34683
34804
|
upsamplers.push(new TruePeakUpsampler(OVERSAMPLE_FACTOR));
|
|
34684
34805
|
}
|
|
34685
34806
|
const slidingWindow = new SlidingWindowMaxStream(halfWidth);
|
|
34686
|
-
const detectScratch4x = new Float32Array(
|
|
34687
|
-
const detectScratchBase = new Float32Array(
|
|
34807
|
+
const detectScratch4x = new Float32Array(CHUNK_FRAMES3 * OVERSAMPLE_FACTOR);
|
|
34808
|
+
const detectScratchBase = new Float32Array(CHUNK_FRAMES3);
|
|
34809
|
+
let dbScratch = null;
|
|
34810
|
+
const upsampleScratches = [];
|
|
34688
34811
|
let consumedBaseFrames = 0;
|
|
34689
34812
|
await buffer.reset();
|
|
34690
34813
|
for (; ; ) {
|
|
34691
|
-
const chunk = await buffer.read(
|
|
34814
|
+
const chunk = await buffer.read(CHUNK_FRAMES3);
|
|
34692
34815
|
const channels = chunk.samples;
|
|
34693
34816
|
const chunkFrames = channels[0]?.length ?? 0;
|
|
34694
34817
|
if (chunkFrames === 0) break;
|
|
@@ -34702,7 +34825,12 @@ async function buildBaseRateDetectionCache(args) {
|
|
|
34702
34825
|
continue;
|
|
34703
34826
|
}
|
|
34704
34827
|
const slice = channel.length === chunkFrames ? channel : channel.subarray(0, chunkFrames);
|
|
34705
|
-
|
|
34828
|
+
let scratch = upsampleScratches[channelIdx];
|
|
34829
|
+
if (scratch === void 0 || scratch.length < chunkFrames * OVERSAMPLE_FACTOR) {
|
|
34830
|
+
scratch = new Float32Array(chunkFrames * OVERSAMPLE_FACTOR);
|
|
34831
|
+
upsampleScratches[channelIdx] = scratch;
|
|
34832
|
+
}
|
|
34833
|
+
upChannels.push(upsampler.upsample(slice, scratch));
|
|
34706
34834
|
}
|
|
34707
34835
|
const detect4xChunk = detectScratch4x.subarray(0, upChunkLength);
|
|
34708
34836
|
for (let upIdx = 0; upIdx < upChunkLength; upIdx++) {
|
|
@@ -34729,9 +34857,16 @@ async function buildBaseRateDetectionCache(args) {
|
|
|
34729
34857
|
const isFinal = consumedBaseFrames >= frames;
|
|
34730
34858
|
const pooled = slidingWindow.push(detectBaseChunk, isFinal);
|
|
34731
34859
|
if (pooled.length > 0) {
|
|
34732
|
-
|
|
34860
|
+
if (dbScratch === null || dbScratch.length < pooled.length) {
|
|
34861
|
+
dbScratch = new Float32Array(pooled.length);
|
|
34862
|
+
}
|
|
34863
|
+
const dbChunk = dbScratch.subarray(0, pooled.length);
|
|
34864
|
+
for (let sampleIdx = 0; sampleIdx < pooled.length; sampleIdx++) {
|
|
34865
|
+
dbChunk[sampleIdx] = linearToDb(pooled[sampleIdx] ?? 0);
|
|
34866
|
+
}
|
|
34867
|
+
await detectionEnvelope.write([dbChunk], sampleRate, sourceBitDepth);
|
|
34733
34868
|
}
|
|
34734
|
-
if (chunkFrames <
|
|
34869
|
+
if (chunkFrames < CHUNK_FRAMES3) break;
|
|
34735
34870
|
}
|
|
34736
34871
|
await detectionEnvelope.flushWrites();
|
|
34737
34872
|
return detectionEnvelope;
|
|
@@ -34739,7 +34874,7 @@ async function buildBaseRateDetectionCache(args) {
|
|
|
34739
34874
|
|
|
34740
34875
|
// src/transforms/loudness-target/utils/iterate.ts
|
|
34741
34876
|
var OVERSAMPLE_FACTOR = 4;
|
|
34742
|
-
var
|
|
34877
|
+
var CHUNK_FRAMES3 = 44100;
|
|
34743
34878
|
var BOOST_LOWER_BOUND = -30;
|
|
34744
34879
|
var BOOST_UPPER_BOUND = 30;
|
|
34745
34880
|
var LIMIT_EPSILON_DB = 0.01;
|
|
@@ -34748,6 +34883,30 @@ var PEAK_GAIN_DB_FLOOR = -60;
|
|
|
34748
34883
|
var MIN_SECANT_SLOPE = 0.05;
|
|
34749
34884
|
var DEFAULT_MAX_ATTEMPTS = 10;
|
|
34750
34885
|
var DEFAULT_TOLERANCE = 0.5;
|
|
34886
|
+
var GAIN_LUT_MIN_DB = -80;
|
|
34887
|
+
var GAIN_LUT_MAX_DB = 40;
|
|
34888
|
+
var GAIN_LUT_STEP_DB = 0.01;
|
|
34889
|
+
var GAIN_LUT_INV_STEP = 1 / GAIN_LUT_STEP_DB;
|
|
34890
|
+
var GAIN_LUT_SIZE = Math.round((GAIN_LUT_MAX_DB - GAIN_LUT_MIN_DB) / GAIN_LUT_STEP_DB) + 1;
|
|
34891
|
+
var GAIN_LUT_STORED_SILENCE_DB = -200;
|
|
34892
|
+
function buildGainLut() {
|
|
34893
|
+
const lut = new Float64Array(GAIN_LUT_SIZE);
|
|
34894
|
+
for (let entryIdx = 0; entryIdx < GAIN_LUT_SIZE; entryIdx++) {
|
|
34895
|
+
lut[entryIdx] = Math.pow(10, (GAIN_LUT_MIN_DB + entryIdx * GAIN_LUT_STEP_DB) / 20);
|
|
34896
|
+
}
|
|
34897
|
+
return lut;
|
|
34898
|
+
}
|
|
34899
|
+
function gainLutLerp(lut, gainDb) {
|
|
34900
|
+
if (gainDb < GAIN_LUT_MIN_DB || gainDb >= GAIN_LUT_MAX_DB) {
|
|
34901
|
+
return Math.pow(10, gainDb / 20);
|
|
34902
|
+
}
|
|
34903
|
+
const pos = (gainDb - GAIN_LUT_MIN_DB) * GAIN_LUT_INV_STEP;
|
|
34904
|
+
const lutIdx = pos | 0;
|
|
34905
|
+
const frac = pos - lutIdx;
|
|
34906
|
+
const lo = lut[lutIdx] ?? 0;
|
|
34907
|
+
const hi = lut[lutIdx + 1] ?? 0;
|
|
34908
|
+
return lo + (hi - lo) * frac;
|
|
34909
|
+
}
|
|
34751
34910
|
async function iterateForTargets(args) {
|
|
34752
34911
|
const {
|
|
34753
34912
|
buffer,
|
|
@@ -34768,13 +34927,18 @@ async function iterateForTargets(args) {
|
|
|
34768
34927
|
const channelCount = buffer.channels;
|
|
34769
34928
|
const frames = buffer.frames;
|
|
34770
34929
|
if (channelCount === 0 || frames === 0) {
|
|
34930
|
+
if (args.detectionEnvelope !== void 0) await args.detectionEnvelope.close();
|
|
34771
34931
|
return {
|
|
34772
34932
|
bestSmoothedEnvelopeBuffer: new ChunkBuffer(),
|
|
34773
34933
|
bestB: 0,
|
|
34774
34934
|
bestLimitDb: sourcePeakDb,
|
|
34775
34935
|
bestPeakGainDb: 0,
|
|
34776
34936
|
attempts: [],
|
|
34777
|
-
converged: false
|
|
34937
|
+
converged: false,
|
|
34938
|
+
detectionCacheBuildMs: 0,
|
|
34939
|
+
winnerOutputLufs: null,
|
|
34940
|
+
winnerOutputTruePeakDb: null,
|
|
34941
|
+
winnerOutputLra: null
|
|
34778
34942
|
};
|
|
34779
34943
|
}
|
|
34780
34944
|
const effectiveTargetTp = targetTp ?? sourcePeakDb;
|
|
@@ -34789,13 +34953,15 @@ async function iterateForTargets(args) {
|
|
|
34789
34953
|
let currentPeakGainDb = effectiveTargetTp - currentLimit;
|
|
34790
34954
|
const halfWidth = windowSamplesFromMs(smoothingMs, sampleRate);
|
|
34791
34955
|
const iir = new BidirectionalIir({ smoothingMs, sampleRate });
|
|
34792
|
-
const
|
|
34956
|
+
const tCacheBuild0 = Date.now();
|
|
34957
|
+
const detectionEnvelope = args.detectionEnvelope ?? await buildBaseRateDetectionCache({
|
|
34793
34958
|
buffer,
|
|
34794
34959
|
sampleRate,
|
|
34795
34960
|
channelCount,
|
|
34796
34961
|
frames,
|
|
34797
34962
|
halfWidth
|
|
34798
34963
|
});
|
|
34964
|
+
const detectionCacheBuildMs = args.detectionEnvelope !== void 0 ? 0 : Date.now() - tCacheBuild0;
|
|
34799
34965
|
const forwardEnvelopeBuffer = new ChunkBuffer();
|
|
34800
34966
|
const minHeldEnvelopeBuffer = new ChunkBuffer();
|
|
34801
34967
|
const activeBufferA = new ChunkBuffer();
|
|
@@ -34812,8 +34978,14 @@ async function iterateForTargets(args) {
|
|
|
34812
34978
|
let bestBoost = currentBoost;
|
|
34813
34979
|
let bestPeakGainDb = currentPeakGainDb;
|
|
34814
34980
|
let bestScore = Infinity;
|
|
34981
|
+
let winnerOutputLufs = null;
|
|
34982
|
+
let winnerOutputTruePeakDb = null;
|
|
34983
|
+
let winnerOutputLra = null;
|
|
34984
|
+
let winnerLufsErr = Infinity;
|
|
34985
|
+
let winnerPeakErr = Infinity;
|
|
34815
34986
|
let previousStepMagnitude = Infinity;
|
|
34816
34987
|
for (let attemptIdx = 0; attemptIdx < maxAttempts; attemptIdx++) {
|
|
34988
|
+
const tAttempt0 = Date.now();
|
|
34817
34989
|
const anchors = {
|
|
34818
34990
|
floorDb: anchorBase.floorDb,
|
|
34819
34991
|
pivotDb: anchorBase.pivotDb,
|
|
@@ -34833,31 +35005,35 @@ async function iterateForTargets(args) {
|
|
|
34833
35005
|
sourceBuffer: forwardEnvelopeBuffer,
|
|
34834
35006
|
destBuffer: activeRef,
|
|
34835
35007
|
iir,
|
|
34836
|
-
chunkSize:
|
|
35008
|
+
chunkSize: CHUNK_FRAMES3,
|
|
34837
35009
|
minHeldBuffer: minHeldEnvelopeBuffer
|
|
34838
35010
|
});
|
|
34839
|
-
const measured = await measureAttemptOutput({
|
|
34840
|
-
source: buffer,
|
|
34841
|
-
sampleRate,
|
|
34842
|
-
channelCount,
|
|
34843
|
-
gSmoothed: activeRef
|
|
34844
|
-
});
|
|
35011
|
+
const measured = await measureAttemptOutput({ source: buffer, sampleRate, channelCount, gSmoothed: activeRef });
|
|
34845
35012
|
const lufsErr = measured.outputLufs - targetLufs;
|
|
34846
35013
|
const peakErr = measured.outputTruePeakDb - effectiveTargetTp;
|
|
34847
35014
|
attempts.push({
|
|
34848
35015
|
boost: currentBoost,
|
|
34849
35016
|
limitDb: currentLimit,
|
|
34850
35017
|
lufsErr,
|
|
35018
|
+
outputLufs: measured.outputLufs,
|
|
35019
|
+
outputTruePeakDb: measured.outputTruePeakDb,
|
|
34851
35020
|
outputLra: measured.outputLra,
|
|
34852
35021
|
peakGainDb: currentPeakGainDb,
|
|
34853
|
-
peakErr
|
|
35022
|
+
peakErr,
|
|
35023
|
+
elapsedMs: Date.now() - tAttempt0
|
|
34854
35024
|
});
|
|
34855
|
-
const
|
|
34856
|
-
const
|
|
35025
|
+
const lufsScoreTerm = Math.abs(lufsErr) / tolerance;
|
|
35026
|
+
const peakScoreTerm = skipPeak ? 0 : Math.abs(peakErr) / peakTolerance;
|
|
35027
|
+
const score = Math.max(lufsScoreTerm, peakScoreTerm);
|
|
34857
35028
|
if (score < bestScore) {
|
|
34858
35029
|
bestScore = score;
|
|
34859
35030
|
bestBoost = currentBoost;
|
|
34860
35031
|
bestPeakGainDb = currentPeakGainDb;
|
|
35032
|
+
winnerOutputLufs = measured.outputLufs;
|
|
35033
|
+
winnerOutputTruePeakDb = measured.outputTruePeakDb;
|
|
35034
|
+
winnerOutputLra = measured.outputLra;
|
|
35035
|
+
winnerLufsErr = lufsErr;
|
|
35036
|
+
winnerPeakErr = peakErr;
|
|
34861
35037
|
const previousActive = activeRef;
|
|
34862
35038
|
activeRef = winningRef;
|
|
34863
35039
|
winningRef = previousActive;
|
|
@@ -34869,28 +35045,9 @@ async function iterateForTargets(args) {
|
|
|
34869
35045
|
await forwardEnvelopeBuffer.clear();
|
|
34870
35046
|
await minHeldEnvelopeBuffer.clear();
|
|
34871
35047
|
const matchesToTwoDp = Math.round(Math.abs(lufsErr) * 100) === 0 && (skipPeak || Math.round(Math.abs(peakErr) * 100) === 0);
|
|
34872
|
-
if (matchesToTwoDp) {
|
|
34873
|
-
return {
|
|
34874
|
-
bestSmoothedEnvelopeBuffer: winningRef,
|
|
34875
|
-
bestB: bestBoost,
|
|
34876
|
-
bestLimitDb: currentLimit,
|
|
34877
|
-
bestPeakGainDb,
|
|
34878
|
-
attempts,
|
|
34879
|
-
converged: true
|
|
34880
|
-
};
|
|
34881
|
-
}
|
|
34882
35048
|
const lufsConverged = Math.abs(lufsErr) < tolerance;
|
|
34883
35049
|
const peakConverged = skipPeak || Math.abs(peakErr) < peakTolerance;
|
|
34884
|
-
if (lufsConverged && peakConverged)
|
|
34885
|
-
return {
|
|
34886
|
-
bestSmoothedEnvelopeBuffer: winningRef,
|
|
34887
|
-
bestB: bestBoost,
|
|
34888
|
-
bestLimitDb: currentLimit,
|
|
34889
|
-
bestPeakGainDb,
|
|
34890
|
-
attempts,
|
|
34891
|
-
converged: true
|
|
34892
|
-
};
|
|
34893
|
-
}
|
|
35050
|
+
if (matchesToTwoDp || lufsConverged && peakConverged) break;
|
|
34894
35051
|
if (attemptIdx === maxAttempts - 1) break;
|
|
34895
35052
|
const next = computeBoostStep(attempts, previousStepMagnitude);
|
|
34896
35053
|
currentBoost = clampBoost(next.boost);
|
|
@@ -34902,13 +35059,18 @@ async function iterateForTargets(args) {
|
|
|
34902
35059
|
);
|
|
34903
35060
|
}
|
|
34904
35061
|
}
|
|
35062
|
+
const converged = winningPopulated && (Math.round(Math.abs(winnerLufsErr) * 100) === 0 && (skipPeak || Math.round(Math.abs(winnerPeakErr) * 100) === 0) || Math.abs(winnerLufsErr) < tolerance && (skipPeak || Math.abs(winnerPeakErr) < peakTolerance));
|
|
34905
35063
|
return {
|
|
34906
35064
|
bestSmoothedEnvelopeBuffer: winningRef,
|
|
34907
35065
|
bestB: bestBoost,
|
|
34908
35066
|
bestLimitDb: currentLimit,
|
|
34909
35067
|
bestPeakGainDb,
|
|
34910
35068
|
attempts,
|
|
34911
|
-
converged
|
|
35069
|
+
converged,
|
|
35070
|
+
detectionCacheBuildMs,
|
|
35071
|
+
winnerOutputLufs,
|
|
35072
|
+
winnerOutputTruePeakDb,
|
|
35073
|
+
winnerOutputLra
|
|
34912
35074
|
};
|
|
34913
35075
|
} finally {
|
|
34914
35076
|
await detectionEnvelope.close();
|
|
@@ -34932,20 +35094,21 @@ async function streamCurveAndForwardIir(args) {
|
|
|
34932
35094
|
const forwardState = { value: 0 };
|
|
34933
35095
|
let forwardSeeded = false;
|
|
34934
35096
|
const minStream = new SlidingWindowMinStream(halfWidth);
|
|
34935
|
-
const gWindowScratch = new Float32Array(
|
|
35097
|
+
const gWindowScratch = new Float32Array(CHUNK_FRAMES3);
|
|
35098
|
+
const gainLut = buildGainLut();
|
|
34936
35099
|
const detectionSampleRate = detectionEnvelope.sampleRate;
|
|
34937
35100
|
const detectionBitDepth = detectionEnvelope.bitDepth;
|
|
34938
35101
|
let consumedFrames = 0;
|
|
34939
35102
|
for (; ; ) {
|
|
34940
|
-
const chunk = await detectionEnvelope.read(
|
|
35103
|
+
const chunk = await detectionEnvelope.read(CHUNK_FRAMES3);
|
|
34941
35104
|
const windowChunk = chunk.samples[0];
|
|
34942
35105
|
const chunkLength = windowChunk?.length ?? 0;
|
|
34943
35106
|
if (windowChunk === void 0 || chunkLength === 0) break;
|
|
34944
35107
|
const gWindowChunk = gWindowScratch.subarray(0, chunkLength);
|
|
34945
35108
|
for (let outputIdx = 0; outputIdx < chunkLength; outputIdx++) {
|
|
34946
|
-
const levelDb =
|
|
35109
|
+
const levelDb = windowChunk[outputIdx] ?? GAIN_LUT_STORED_SILENCE_DB;
|
|
34947
35110
|
const gainDb = gainDbAt(levelDb, anchors);
|
|
34948
|
-
gWindowChunk[outputIdx] =
|
|
35111
|
+
gWindowChunk[outputIdx] = gainLutLerp(gainLut, gainDb);
|
|
34949
35112
|
}
|
|
34950
35113
|
consumedFrames += chunkLength;
|
|
34951
35114
|
const isFinal = consumedFrames >= totalFrames;
|
|
@@ -34959,7 +35122,7 @@ async function streamCurveAndForwardIir(args) {
|
|
|
34959
35122
|
await forwardEnvelopeBuffer.write([forwardChunk], detectionSampleRate, detectionBitDepth);
|
|
34960
35123
|
await minHeldEnvelopeBuffer.write([minHeldChunk], detectionSampleRate, detectionBitDepth);
|
|
34961
35124
|
}
|
|
34962
|
-
if (chunkLength <
|
|
35125
|
+
if (chunkLength < CHUNK_FRAMES3) break;
|
|
34963
35126
|
}
|
|
34964
35127
|
await forwardEnvelopeBuffer.flushWrites();
|
|
34965
35128
|
await minHeldEnvelopeBuffer.flushWrites();
|
|
@@ -34970,12 +35133,12 @@ async function measureAttemptOutput(args) {
|
|
|
34970
35133
|
const truePeakAccumulator = new TruePeakAccumulator(sampleRate, channelCount);
|
|
34971
35134
|
const applyOutputScratch = [];
|
|
34972
35135
|
for (let channelIdx = 0; channelIdx < channelCount; channelIdx++) {
|
|
34973
|
-
applyOutputScratch.push(new Float32Array(
|
|
35136
|
+
applyOutputScratch.push(new Float32Array(CHUNK_FRAMES3));
|
|
34974
35137
|
}
|
|
34975
35138
|
await source.reset();
|
|
34976
35139
|
await gSmoothed.reset();
|
|
34977
35140
|
for (; ; ) {
|
|
34978
|
-
const sourceChunk = await source.read(
|
|
35141
|
+
const sourceChunk = await source.read(CHUNK_FRAMES3);
|
|
34979
35142
|
const chunkFrames = sourceChunk.samples[0]?.length ?? 0;
|
|
34980
35143
|
if (chunkFrames === 0) break;
|
|
34981
35144
|
const envelopeChunk = await gSmoothed.read(chunkFrames);
|
|
@@ -34995,7 +35158,7 @@ async function measureAttemptOutput(args) {
|
|
|
34995
35158
|
});
|
|
34996
35159
|
accumulator.push(transformed, chunkFrames);
|
|
34997
35160
|
truePeakAccumulator.push(transformed, chunkFrames);
|
|
34998
|
-
if (chunkFrames <
|
|
35161
|
+
if (chunkFrames < CHUNK_FRAMES3) break;
|
|
34999
35162
|
}
|
|
35000
35163
|
const result = accumulator.finalize();
|
|
35001
35164
|
const truePeakLinear = truePeakAccumulator.finalize();
|
|
@@ -35042,60 +35205,70 @@ function clampLimit(limitDb, pivotDb, sourcePeakDb) {
|
|
|
35042
35205
|
}
|
|
35043
35206
|
|
|
35044
35207
|
// src/transforms/loudness-target/utils/measurement.ts
|
|
35045
|
-
var
|
|
35208
|
+
var CHUNK_FRAMES4 = 44100;
|
|
35046
35209
|
var HISTOGRAM_BUCKETS = 1024;
|
|
35047
35210
|
var PIVOT_FALLBACK_DB = -40;
|
|
35048
|
-
|
|
35049
|
-
|
|
35050
|
-
|
|
35051
|
-
|
|
35052
|
-
|
|
35053
|
-
|
|
35054
|
-
|
|
35055
|
-
|
|
35056
|
-
|
|
35057
|
-
|
|
35058
|
-
|
|
35059
|
-
|
|
35060
|
-
|
|
35061
|
-
|
|
35062
|
-
|
|
35063
|
-
|
|
35064
|
-
|
|
35065
|
-
|
|
35066
|
-
|
|
35067
|
-
|
|
35068
|
-
|
|
35069
|
-
|
|
35070
|
-
|
|
35071
|
-
|
|
35072
|
-
|
|
35073
|
-
|
|
35074
|
-
|
|
35075
|
-
|
|
35076
|
-
|
|
35077
|
-
|
|
35078
|
-
|
|
35079
|
-
|
|
35211
|
+
function emptyMeasurement() {
|
|
35212
|
+
return {
|
|
35213
|
+
integratedLufs: -Infinity,
|
|
35214
|
+
lra: 0,
|
|
35215
|
+
truePeakDb: -Infinity,
|
|
35216
|
+
pivotAutoDb: Number.POSITIVE_INFINITY,
|
|
35217
|
+
floorAutoDb: Number.POSITIVE_INFINITY,
|
|
35218
|
+
limitAutoDb: Number.POSITIVE_INFINITY,
|
|
35219
|
+
shortTermLufs: [],
|
|
35220
|
+
detectionHistogram: { buckets: new Uint32Array(0), bucketMax: 0, totalSamples: 0 }
|
|
35221
|
+
};
|
|
35222
|
+
}
|
|
35223
|
+
var SourceMeasurementAccumulator = class {
|
|
35224
|
+
constructor(sampleRate, channelCount, limitPercentile, halfWidth, detectionEnvelope = null, persistBitDepth) {
|
|
35225
|
+
this.levelsScratch = null;
|
|
35226
|
+
this.baseScratch = null;
|
|
35227
|
+
this.dbScratch = null;
|
|
35228
|
+
// see toDbScratch
|
|
35229
|
+
this.upsampleScratches = [];
|
|
35230
|
+
this.pushedFrames = 0;
|
|
35231
|
+
this.limitPercentile = limitPercentile;
|
|
35232
|
+
this.sampleRate = sampleRate;
|
|
35233
|
+
this.loudness = new LoudnessAccumulator(sampleRate, channelCount);
|
|
35234
|
+
this.truePeak = new TruePeakAccumulator(sampleRate, channelCount);
|
|
35235
|
+
this.detectionHistogram = new AmplitudeHistogramAccumulator(HISTOGRAM_BUCKETS);
|
|
35236
|
+
this.upsamplers = [];
|
|
35237
|
+
for (let channelIdx = 0; channelIdx < channelCount; channelIdx++) {
|
|
35238
|
+
this.upsamplers.push(new TruePeakUpsampler(OVERSAMPLE_FACTOR));
|
|
35239
|
+
}
|
|
35240
|
+
this.slidingWindow = new SlidingWindowMaxStream(halfWidth);
|
|
35241
|
+
this.detectionEnvelope = detectionEnvelope;
|
|
35242
|
+
this.persistBitDepth = persistBitDepth;
|
|
35243
|
+
}
|
|
35244
|
+
async push(channels, frames) {
|
|
35245
|
+
if (frames === 0) return;
|
|
35246
|
+
this.loudness.push(channels, frames);
|
|
35247
|
+
this.truePeak.push(channels, frames);
|
|
35080
35248
|
const upChannels = [];
|
|
35081
|
-
for (let channelIdx = 0; channelIdx <
|
|
35082
|
-
const channel =
|
|
35083
|
-
const upsampler = upsamplers[channelIdx];
|
|
35249
|
+
for (let channelIdx = 0; channelIdx < channels.length; channelIdx++) {
|
|
35250
|
+
const channel = channels[channelIdx];
|
|
35251
|
+
const upsampler = this.upsamplers[channelIdx];
|
|
35084
35252
|
if (channel === void 0 || upsampler === void 0) {
|
|
35085
|
-
upChannels.push(new Float32Array(
|
|
35253
|
+
upChannels.push(new Float32Array(frames * OVERSAMPLE_FACTOR));
|
|
35086
35254
|
continue;
|
|
35087
35255
|
}
|
|
35088
|
-
const slice = channel.length ===
|
|
35089
|
-
|
|
35256
|
+
const slice = channel.length === frames ? channel : channel.subarray(0, frames);
|
|
35257
|
+
let scratch = this.upsampleScratches[channelIdx];
|
|
35258
|
+
if (scratch === void 0 || scratch.length < frames * OVERSAMPLE_FACTOR) {
|
|
35259
|
+
scratch = new Float32Array(frames * OVERSAMPLE_FACTOR);
|
|
35260
|
+
this.upsampleScratches[channelIdx] = scratch;
|
|
35261
|
+
}
|
|
35262
|
+
upChannels.push(upsampler.upsample(slice, scratch));
|
|
35090
35263
|
}
|
|
35091
|
-
const upChunkLength =
|
|
35092
|
-
if (levelsScratch === null || levelsScratch.length < upChunkLength) {
|
|
35093
|
-
levelsScratch = new Float32Array(upChunkLength);
|
|
35264
|
+
const upChunkLength = frames * OVERSAMPLE_FACTOR;
|
|
35265
|
+
if (this.levelsScratch === null || this.levelsScratch.length < upChunkLength) {
|
|
35266
|
+
this.levelsScratch = new Float32Array(upChunkLength);
|
|
35094
35267
|
}
|
|
35095
|
-
if (baseScratch === null || baseScratch.length <
|
|
35096
|
-
baseScratch = new Float32Array(
|
|
35268
|
+
if (this.baseScratch === null || this.baseScratch.length < frames) {
|
|
35269
|
+
this.baseScratch = new Float32Array(frames);
|
|
35097
35270
|
}
|
|
35098
|
-
const levels = levelsScratch;
|
|
35271
|
+
const levels = this.levelsScratch;
|
|
35099
35272
|
for (let upIdx = 0; upIdx < upChunkLength; upIdx++) {
|
|
35100
35273
|
let max = 0;
|
|
35101
35274
|
for (let channelIdx = 0; channelIdx < upChannels.length; channelIdx++) {
|
|
@@ -35105,8 +35278,8 @@ async function measureSource(buffer, sampleRate, limitPercentile, halfWidth) {
|
|
|
35105
35278
|
}
|
|
35106
35279
|
levels[upIdx] = max;
|
|
35107
35280
|
}
|
|
35108
|
-
const baseChunk = baseScratch.subarray(0,
|
|
35109
|
-
for (let baseIdx = 0; baseIdx <
|
|
35281
|
+
const baseChunk = this.baseScratch.subarray(0, frames);
|
|
35282
|
+
for (let baseIdx = 0; baseIdx < frames; baseIdx++) {
|
|
35110
35283
|
const upOffset = baseIdx * OVERSAMPLE_FACTOR;
|
|
35111
35284
|
const s0 = levels[upOffset] ?? 0;
|
|
35112
35285
|
const s1 = levels[upOffset + 1] ?? 0;
|
|
@@ -35116,43 +35289,79 @@ async function measureSource(buffer, sampleRate, limitPercentile, halfWidth) {
|
|
|
35116
35289
|
const m23 = s2 > s3 ? s2 : s3;
|
|
35117
35290
|
baseChunk[baseIdx] = m01 > m23 ? m01 : m23;
|
|
35118
35291
|
}
|
|
35119
|
-
|
|
35120
|
-
const pooled = slidingWindow.push(baseChunk,
|
|
35292
|
+
this.pushedFrames += frames;
|
|
35293
|
+
const pooled = this.slidingWindow.push(baseChunk, false);
|
|
35121
35294
|
if (pooled.length > 0) {
|
|
35122
|
-
detectionHistogram.push([pooled], pooled.length);
|
|
35295
|
+
this.detectionHistogram.push([pooled], pooled.length);
|
|
35296
|
+
if (this.detectionEnvelope !== null) {
|
|
35297
|
+
await this.detectionEnvelope.write([this.toDbScratch(pooled)], this.sampleRate, this.persistBitDepth);
|
|
35298
|
+
}
|
|
35123
35299
|
}
|
|
35124
|
-
if (isLastChunk) break;
|
|
35125
35300
|
}
|
|
35126
|
-
|
|
35127
|
-
|
|
35128
|
-
|
|
35129
|
-
|
|
35130
|
-
|
|
35131
|
-
|
|
35132
|
-
|
|
35133
|
-
|
|
35301
|
+
// LINEAR pooled slider output -> dB, into a reused scratch. The histogram keeps the linear axis; only the
|
|
35302
|
+
// detection-envelope buffer stores dB (never converts `pooled`/`trailing` in place — an axis mixup silently
|
|
35303
|
+
// corrupts limitAutoDb / the predictor).
|
|
35304
|
+
toDbScratch(linear) {
|
|
35305
|
+
if (this.dbScratch === null || this.dbScratch.length < linear.length) {
|
|
35306
|
+
this.dbScratch = new Float32Array(linear.length);
|
|
35307
|
+
}
|
|
35308
|
+
const out = this.dbScratch.subarray(0, linear.length);
|
|
35309
|
+
for (let sampleIdx = 0; sampleIdx < linear.length; sampleIdx++) {
|
|
35310
|
+
out[sampleIdx] = linearToDb(linear[sampleIdx] ?? 0);
|
|
35311
|
+
}
|
|
35312
|
+
return out;
|
|
35134
35313
|
}
|
|
35135
|
-
|
|
35136
|
-
|
|
35137
|
-
|
|
35138
|
-
|
|
35139
|
-
|
|
35140
|
-
|
|
35141
|
-
|
|
35142
|
-
|
|
35143
|
-
// Pooled base-rate detection histogram — same axis the apply
|
|
35144
|
-
// path's `buildBaseRateDetectionCache` produces, feeding the
|
|
35145
|
-
// curve's per-sample `gainDbAt`. `limitAutoDb`'s percentile
|
|
35146
|
-
// walk and `predictOutputLufs` both consume this histogram
|
|
35147
|
-
// (per the 2026-05-13 histogram-axis fix; the prior raw-4× axis
|
|
35148
|
-
// under-estimated typical curve-input levels and produced an
|
|
35149
|
-
// over-aggressive `limitAutoDb`).
|
|
35150
|
-
detectionHistogram: {
|
|
35151
|
-
buckets: histogramResult.buckets,
|
|
35152
|
-
bucketMax: histogramResult.bucketMax,
|
|
35153
|
-
totalSamples
|
|
35314
|
+
async finalize() {
|
|
35315
|
+
if (this.pushedFrames === 0) return emptyMeasurement();
|
|
35316
|
+
const trailing = this.slidingWindow.push(new Float32Array(0), true);
|
|
35317
|
+
if (trailing.length > 0) {
|
|
35318
|
+
this.detectionHistogram.push([trailing], trailing.length);
|
|
35319
|
+
if (this.detectionEnvelope !== null) {
|
|
35320
|
+
await this.detectionEnvelope.write([this.toDbScratch(trailing)], this.sampleRate, this.persistBitDepth);
|
|
35321
|
+
}
|
|
35154
35322
|
}
|
|
35155
|
-
|
|
35323
|
+
if (this.detectionEnvelope !== null) {
|
|
35324
|
+
await this.detectionEnvelope.flushWrites();
|
|
35325
|
+
}
|
|
35326
|
+
const loudnessResult = this.loudness.finalize();
|
|
35327
|
+
const truePeakLin = this.truePeak.finalize();
|
|
35328
|
+
const histogramResult = this.detectionHistogram.finalize();
|
|
35329
|
+
const stats = getLraConsideredStats(loudnessResult.shortTerm);
|
|
35330
|
+
const limitAutoDb = computeLimitAutoDb(histogramResult.buckets, histogramResult.bucketMax, stats.median, this.limitPercentile);
|
|
35331
|
+
let totalSamples = 0;
|
|
35332
|
+
for (let bucketIdx = 0; bucketIdx < histogramResult.buckets.length; bucketIdx++) {
|
|
35333
|
+
totalSamples += histogramResult.buckets[bucketIdx] ?? 0;
|
|
35334
|
+
}
|
|
35335
|
+
return {
|
|
35336
|
+
integratedLufs: loudnessResult.integrated,
|
|
35337
|
+
lra: loudnessResult.range,
|
|
35338
|
+
truePeakDb: linearToDb(truePeakLin),
|
|
35339
|
+
pivotAutoDb: stats.median,
|
|
35340
|
+
floorAutoDb: stats.min,
|
|
35341
|
+
limitAutoDb,
|
|
35342
|
+
shortTermLufs: loudnessResult.shortTerm,
|
|
35343
|
+
detectionHistogram: {
|
|
35344
|
+
buckets: histogramResult.buckets,
|
|
35345
|
+
bucketMax: histogramResult.bucketMax,
|
|
35346
|
+
totalSamples
|
|
35347
|
+
}
|
|
35348
|
+
};
|
|
35349
|
+
}
|
|
35350
|
+
};
|
|
35351
|
+
async function measureSource(buffer, sampleRate, limitPercentile, halfWidth) {
|
|
35352
|
+
const frames = buffer.frames;
|
|
35353
|
+
const channelCount = buffer.channels;
|
|
35354
|
+
if (frames === 0 || channelCount === 0) return emptyMeasurement();
|
|
35355
|
+
const accumulator = new SourceMeasurementAccumulator(sampleRate, channelCount, limitPercentile, halfWidth);
|
|
35356
|
+
await buffer.reset();
|
|
35357
|
+
for (; ; ) {
|
|
35358
|
+
const chunk = await buffer.read(CHUNK_FRAMES4);
|
|
35359
|
+
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
35360
|
+
if (chunkFrames === 0) break;
|
|
35361
|
+
await accumulator.push(chunk.samples, chunkFrames);
|
|
35362
|
+
if (chunkFrames < CHUNK_FRAMES4) break;
|
|
35363
|
+
}
|
|
35364
|
+
return await accumulator.finalize();
|
|
35156
35365
|
}
|
|
35157
35366
|
function computeLimitAutoDb(buckets, bucketMax, pivotAutoDb, limitPercentile) {
|
|
35158
35367
|
if (bucketMax === 0) return Number.POSITIVE_INFINITY;
|
|
@@ -35287,84 +35496,63 @@ var schema19 = external_exports.object({
|
|
|
35287
35496
|
var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
35288
35497
|
constructor() {
|
|
35289
35498
|
super(...arguments);
|
|
35290
|
-
/**
|
|
35291
|
-
* BASE-rate peak-respecting smoothed gain envelope produced by the
|
|
35292
|
-
* winning iteration attempt, held as a disk-backed single-channel
|
|
35293
|
-
* `ChunkBuffer` of size `frames` (post the 2026-05-13 base-rate-
|
|
35294
|
-
* downstream rewrite; no `× OVERSAMPLE_FACTOR` factor). The
|
|
35295
|
-
* envelope is read chunk-by-chunk in `_unbuffer` rather than held
|
|
35296
|
-
* as a flat `Float32Array` — keeps RAM at ~10 MB regardless of
|
|
35297
|
-
* source length. `null` when the stream passes through (silent /
|
|
35298
|
-
* sub-block-length source).
|
|
35299
|
-
*/
|
|
35300
35499
|
this.winningSmoothedEnvelopeBuffer = null;
|
|
35301
|
-
/**
|
|
35302
|
-
* Body gain `B` from the winning iteration attempt. `null` when the
|
|
35303
|
-
* stream passes through (no curve was learned). Diagnostic only —
|
|
35304
|
-
* the apply pass uses the materialised envelope, not `B` directly.
|
|
35305
|
-
*/
|
|
35306
35500
|
this.winningB = null;
|
|
35307
|
-
/**
|
|
35308
|
-
* Limit anchor `limitDb` used for this source — constant across
|
|
35309
|
-
* iteration attempts. Sourced from `limitDbOverride` when explicit,
|
|
35310
|
-
* else from the percentile-derived `limitAutoDb` on the source's
|
|
35311
|
-
* detection-envelope histogram, else `sourcePeakDb` (no limiting).
|
|
35312
|
-
* `null` when the stream passes through. Diagnostic only.
|
|
35313
|
-
*/
|
|
35314
35501
|
this.winningLimitDb = null;
|
|
35315
|
-
/**
|
|
35316
|
-
* `peakGainDb` from the winning attempt — the upper-segment right-
|
|
35317
|
-
* endpoint anchor gain. Starts at the closed-form
|
|
35318
|
-
* `effectiveTargetTp − limitDb` and adjusts via proportional
|
|
35319
|
-
* feedback on observed signed TP error. `null` when the stream
|
|
35320
|
-
* passes through.
|
|
35321
|
-
*/
|
|
35322
35502
|
this.winningPeakGainDb = null;
|
|
35323
|
-
/**
|
|
35324
|
-
* Set to `true` by the first `_unbuffer` call so the
|
|
35325
|
-
* `winningSmoothedEnvelopeBuffer`'s read cursor is rewound exactly
|
|
35326
|
-
* once. The envelope is read-only in `_unbuffer` and consumed
|
|
35327
|
-
* forward in chunk-cadence lockstep with upstream chunks; this
|
|
35328
|
-
* lazy-reset pattern keeps the cursor management out of `_setup`.
|
|
35329
|
-
* Post the 2026-05-13 base-rate-downstream rewrite the
|
|
35330
|
-
* upsampled-source cache no longer exists — `_unbuffer` reads
|
|
35331
|
-
* source samples directly from the framework-provided chunk.
|
|
35332
|
-
*/
|
|
35333
35503
|
this.unbufferCursorsReady = false;
|
|
35334
|
-
|
|
35335
|
-
* Per-chunk wall-clock time spent in `_unbuffer`. Post the
|
|
35336
|
-
* 2026-05-13 base-rate-downstream rewrite, `_unbuffer` is a pure
|
|
35337
|
-
* base-rate multiply per chunk (no upsample, no downsample, no
|
|
35338
|
-
* AA filter — the smoothed envelope is bandlimited far below
|
|
35339
|
-
* base-rate Nyquist, so the multiply introduces no high-frequency
|
|
35340
|
-
* content). Accumulated across all `_unbuffer` calls.
|
|
35341
|
-
*/
|
|
35504
|
+
this.capturedDetectionEnvelope = null;
|
|
35342
35505
|
this.unbufferElapsedMs = 0;
|
|
35343
|
-
/**
|
|
35344
|
-
* Wall-clock breakdown of the learn pass. `iteration` is the wall
|
|
35345
|
-
* time spent inside `iterateForTargets`. `detection` stays at 0 for
|
|
35346
|
-
* QA-driver log parity — detection-envelope build cost is folded
|
|
35347
|
-
* into `iteration`.
|
|
35348
|
-
*/
|
|
35349
35506
|
this.learnTimingMs = {
|
|
35350
35507
|
sourceMeasurement: 0,
|
|
35351
35508
|
detection: 0,
|
|
35352
35509
|
iteration: 0
|
|
35353
35510
|
};
|
|
35354
35511
|
}
|
|
35512
|
+
async _buffer(chunk, buffer) {
|
|
35513
|
+
await super._buffer(chunk, buffer);
|
|
35514
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
35515
|
+
const channelCount = chunk.samples.length;
|
|
35516
|
+
if (frames === 0 || channelCount === 0) return;
|
|
35517
|
+
const tPush0 = Date.now();
|
|
35518
|
+
if (this.measurementAccumulator === void 0) {
|
|
35519
|
+
this.capturedDetectionEnvelope = new ChunkBuffer();
|
|
35520
|
+
this.measurementAccumulator = new SourceMeasurementAccumulator(
|
|
35521
|
+
chunk.sampleRate,
|
|
35522
|
+
channelCount,
|
|
35523
|
+
this.properties.limitPercentile,
|
|
35524
|
+
windowSamplesFromMs(this.properties.smoothing, chunk.sampleRate),
|
|
35525
|
+
this.capturedDetectionEnvelope,
|
|
35526
|
+
chunk.bitDepth
|
|
35527
|
+
);
|
|
35528
|
+
}
|
|
35529
|
+
await this.measurementAccumulator.push(chunk.samples, frames);
|
|
35530
|
+
this.learnTimingMs.sourceMeasurement += Date.now() - tPush0;
|
|
35531
|
+
}
|
|
35355
35532
|
async _process(buffer) {
|
|
35356
35533
|
const frames = buffer.frames;
|
|
35357
35534
|
const channelCount = buffer.channels;
|
|
35358
35535
|
const sampleRate = buffer.sampleRate ?? this.sampleRate ?? 44100;
|
|
35359
35536
|
if (frames === 0 || channelCount === 0) return;
|
|
35360
35537
|
const { targetLufs, targetTp, limitDb: limitDbOverride, limitPercentile, smoothing, tolerance, peakTolerance, maxAttempts } = this.properties;
|
|
35361
|
-
const measurementHalfWidth = windowSamplesFromMs(smoothing, sampleRate);
|
|
35362
35538
|
const tMeasure0 = Date.now();
|
|
35363
|
-
const measurement = await measureSource(buffer, sampleRate, limitPercentile,
|
|
35364
|
-
this.learnTimingMs.sourceMeasurement
|
|
35539
|
+
const measurement = this.measurementAccumulator !== void 0 ? await this.measurementAccumulator.finalize() : await measureSource(buffer, sampleRate, limitPercentile, windowSamplesFromMs(smoothing, sampleRate));
|
|
35540
|
+
this.learnTimingMs.sourceMeasurement += Date.now() - tMeasure0;
|
|
35541
|
+
const capturedDetectionEnvelope = this.capturedDetectionEnvelope;
|
|
35542
|
+
this.capturedDetectionEnvelope = null;
|
|
35543
|
+
const detectionEnvelope = capturedDetectionEnvelope !== null && capturedDetectionEnvelope.frames === frames ? capturedDetectionEnvelope : void 0;
|
|
35544
|
+
if (detectionEnvelope === void 0 && capturedDetectionEnvelope !== null) {
|
|
35545
|
+
this.log(
|
|
35546
|
+
"captured detection envelope frames mismatch; rebuilding at barrier",
|
|
35547
|
+
{ capturedFrames: capturedDetectionEnvelope.frames, bufferFrames: frames },
|
|
35548
|
+
"warn"
|
|
35549
|
+
);
|
|
35550
|
+
await capturedDetectionEnvelope.close();
|
|
35551
|
+
}
|
|
35365
35552
|
const { integratedLufs: sourceLufs, lra: sourceLra, truePeakDb: sourcePeakDb } = measurement;
|
|
35366
35553
|
if (!Number.isFinite(sourceLufs)) {
|
|
35367
|
-
|
|
35554
|
+
if (detectionEnvelope !== void 0) await detectionEnvelope.close();
|
|
35555
|
+
this.log("source has no measurable loudness; pass-through", { sourceLufs });
|
|
35368
35556
|
return;
|
|
35369
35557
|
}
|
|
35370
35558
|
const userPivot = this.properties.pivot;
|
|
@@ -35373,13 +35561,13 @@ var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
|
35373
35561
|
effectivePivotDb = userPivot;
|
|
35374
35562
|
} else if (Number.isFinite(measurement.pivotAutoDb)) {
|
|
35375
35563
|
effectivePivotDb = measurement.pivotAutoDb;
|
|
35376
|
-
|
|
35377
|
-
`[loudness-target] pivot auto-derived to ${effectivePivotDb.toFixed(2)} dBFS (median(considered LRA blocks))`
|
|
35378
|
-
);
|
|
35564
|
+
this.log("pivot auto-derived (median(considered LRA blocks))", { pivotDb: effectivePivotDb });
|
|
35379
35565
|
} else {
|
|
35380
35566
|
effectivePivotDb = -40;
|
|
35381
|
-
|
|
35382
|
-
|
|
35567
|
+
this.log(
|
|
35568
|
+
"pivot auto-derivation produced no considered LRA blocks; falling back. Supply 'pivot' explicitly for tighter control on short or near-silent sources.",
|
|
35569
|
+
{ fallbackPivotDb: effectivePivotDb },
|
|
35570
|
+
"warn"
|
|
35383
35571
|
);
|
|
35384
35572
|
}
|
|
35385
35573
|
const userFloor = this.properties.floor;
|
|
@@ -35388,16 +35576,15 @@ var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
|
35388
35576
|
effectiveFloorDb = userFloor;
|
|
35389
35577
|
} else if (Number.isFinite(measurement.floorAutoDb)) {
|
|
35390
35578
|
effectiveFloorDb = measurement.floorAutoDb;
|
|
35391
|
-
|
|
35392
|
-
`[loudness-target] floor auto-derived to ${effectiveFloorDb.toFixed(2)} dBFS (min(considered LRA blocks))`
|
|
35393
|
-
);
|
|
35579
|
+
this.log("floor auto-derived (min(considered LRA blocks))", { floorDb: effectiveFloorDb });
|
|
35394
35580
|
} else {
|
|
35395
35581
|
effectiveFloorDb = null;
|
|
35396
35582
|
}
|
|
35397
35583
|
if (effectiveFloorDb !== null && effectiveFloorDb >= effectivePivotDb) {
|
|
35398
35584
|
const clampedFloorDb = effectivePivotDb - FLOOR_PIVOT_EPSILON_DB;
|
|
35399
|
-
|
|
35400
|
-
|
|
35585
|
+
this.log(
|
|
35586
|
+
"floor >= pivot; clamping floor to (pivot - epsilon)",
|
|
35587
|
+
{ floorDb: effectiveFloorDb, pivotDb: effectivePivotDb, clampedFloorDb, epsilonDb: FLOOR_PIVOT_EPSILON_DB }
|
|
35401
35588
|
);
|
|
35402
35589
|
effectiveFloorDb = clampedFloorDb;
|
|
35403
35590
|
}
|
|
@@ -35436,19 +35623,20 @@ var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
|
35436
35623
|
maxAttempts,
|
|
35437
35624
|
tolerance,
|
|
35438
35625
|
peakTolerance,
|
|
35439
|
-
seedB
|
|
35626
|
+
seedB,
|
|
35627
|
+
detectionEnvelope
|
|
35440
35628
|
});
|
|
35441
35629
|
this.learnTimingMs.iteration = Date.now() - tIterate0;
|
|
35630
|
+
this.learnTimingMs.detection = result.detectionCacheBuildMs;
|
|
35442
35631
|
this.winningSmoothedEnvelopeBuffer = result.bestSmoothedEnvelopeBuffer;
|
|
35443
35632
|
this.winningB = result.bestB;
|
|
35444
35633
|
this.winningLimitDb = result.bestLimitDb;
|
|
35445
35634
|
this.winningPeakGainDb = result.bestPeakGainDb;
|
|
35446
|
-
const
|
|
35447
|
-
const
|
|
35448
|
-
const
|
|
35449
|
-
const
|
|
35450
|
-
const
|
|
35451
|
-
const peakDeltaRepr = lastAttempt ? lastAttempt.peakErr.toFixed(2) : "n/a";
|
|
35635
|
+
const outputLufsRepr = result.winnerOutputLufs !== null ? result.winnerOutputLufs.toFixed(2) : "n/a";
|
|
35636
|
+
const outputLraRepr = result.winnerOutputLra !== null ? result.winnerOutputLra.toFixed(2) : "n/a";
|
|
35637
|
+
const lufsDeltaRepr = result.winnerOutputLufs !== null ? (result.winnerOutputLufs - targetLufs).toFixed(2) : "n/a";
|
|
35638
|
+
const outputTruePeakRepr = result.winnerOutputTruePeakDb !== null ? result.winnerOutputTruePeakDb.toFixed(2) : "n/a";
|
|
35639
|
+
const peakDeltaRepr = result.winnerOutputTruePeakDb !== null ? (result.winnerOutputTruePeakDb - effectiveTargetTp).toFixed(2) : "n/a";
|
|
35452
35640
|
const bestPeakGainDbRepr = result.bestPeakGainDb.toFixed(4);
|
|
35453
35641
|
const bestLimitDbRepr = result.bestLimitDb.toFixed(4);
|
|
35454
35642
|
const pivotRepr = userPivot === void 0 ? `${effectivePivotDb.toFixed(2)} (auto)` : String(userPivot);
|
|
@@ -35463,21 +35651,53 @@ var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
|
35463
35651
|
}
|
|
35464
35652
|
const limitDbRepr = `${bestLimitDbRepr} (${limitDbSource})`;
|
|
35465
35653
|
const expansiveGeometry = result.bestPeakGainDb > result.bestB;
|
|
35466
|
-
const expansionSuffix = expansiveGeometry ? " EXPANSIVE_UPPER_SEGMENT" : "";
|
|
35467
35654
|
for (let attemptIdx = 0; attemptIdx < result.attempts.length; attemptIdx++) {
|
|
35468
35655
|
const attempt = result.attempts[attemptIdx];
|
|
35469
35656
|
if (attempt === void 0) continue;
|
|
35470
|
-
|
|
35471
|
-
|
|
35472
|
-
|
|
35657
|
+
this.log("attempt", {
|
|
35658
|
+
attempt: attemptIdx + 1,
|
|
35659
|
+
B: attempt.boost,
|
|
35660
|
+
peakGainDb: attempt.peakGainDb,
|
|
35661
|
+
lufsErr: attempt.lufsErr,
|
|
35662
|
+
peakErr: attempt.peakErr,
|
|
35663
|
+
outputLra: attempt.outputLra,
|
|
35664
|
+
elapsedMs: attempt.elapsedMs
|
|
35665
|
+
});
|
|
35666
|
+
this.progress(attemptIdx + 1, maxAttempts);
|
|
35473
35667
|
}
|
|
35474
35668
|
const fmt = (x) => x === void 0 ? "off" : String(x);
|
|
35475
|
-
|
|
35476
|
-
|
|
35477
|
-
|
|
35669
|
+
this.log("iteration", {
|
|
35670
|
+
attempts: result.attempts.length,
|
|
35671
|
+
converged: result.converged,
|
|
35672
|
+
seedB,
|
|
35673
|
+
bestB: result.bestB,
|
|
35674
|
+
bestLimitDb: bestLimitDbRepr,
|
|
35675
|
+
bestPeakGainDb: bestPeakGainDbRepr,
|
|
35676
|
+
outputLufs: outputLufsRepr,
|
|
35677
|
+
lufsDelta: lufsDeltaRepr,
|
|
35678
|
+
outputLra: outputLraRepr,
|
|
35679
|
+
outputTruePeakDb: outputTruePeakRepr,
|
|
35680
|
+
peakDelta: peakDeltaRepr,
|
|
35681
|
+
targetLufs,
|
|
35682
|
+
targetTp: targetTp === void 0 ? "source" : String(targetTp),
|
|
35683
|
+
limitDb: limitDbRepr,
|
|
35684
|
+
limitPercentile,
|
|
35685
|
+
sourceLufs,
|
|
35686
|
+
sourcePeakDb,
|
|
35687
|
+
sourceLra,
|
|
35688
|
+
pivot: pivotRepr,
|
|
35689
|
+
floor: floorRepr,
|
|
35690
|
+
smoothing,
|
|
35691
|
+
tolerance: fmt(tolerance),
|
|
35692
|
+
peakTolerance: fmt(peakTolerance),
|
|
35693
|
+
maxAttempts: fmt(maxAttempts),
|
|
35694
|
+
expansiveUpperSegment: expansiveGeometry
|
|
35695
|
+
});
|
|
35478
35696
|
if (expansiveGeometry) {
|
|
35479
|
-
|
|
35480
|
-
|
|
35697
|
+
this.log(
|
|
35698
|
+
"peakGainDb > B; upper segment of curve is expansive between pivot and limit. Brick-wall above limit still caps output at targetTp \u2014 note for listening QA.",
|
|
35699
|
+
{ peakGainDb: bestPeakGainDbRepr, B: result.bestB },
|
|
35700
|
+
"warn"
|
|
35481
35701
|
);
|
|
35482
35702
|
}
|
|
35483
35703
|
}
|
|
@@ -35487,14 +35707,25 @@ var LoudnessTargetStream = class extends BufferedTransformStream {
|
|
|
35487
35707
|
const bRepr = this.winningB === null ? "n/a" : this.winningB.toFixed(4);
|
|
35488
35708
|
const limitDbRepr = this.winningLimitDb === null ? "n/a" : this.winningLimitDb.toFixed(4);
|
|
35489
35709
|
const peakGainDbRepr = this.winningPeakGainDb === null ? "n/a" : this.winningPeakGainDb.toFixed(4);
|
|
35490
|
-
|
|
35491
|
-
|
|
35492
|
-
|
|
35710
|
+
this.log("timing", {
|
|
35711
|
+
sourceMeasurementMs: this.learnTimingMs.sourceMeasurement,
|
|
35712
|
+
detectionMs: this.learnTimingMs.detection,
|
|
35713
|
+
iterationMs: this.learnTimingMs.iteration,
|
|
35714
|
+
unbufferApplyMs: this.unbufferElapsedMs,
|
|
35715
|
+
totalMs: total,
|
|
35716
|
+
winningB: bRepr,
|
|
35717
|
+
winningLimitDb: limitDbRepr,
|
|
35718
|
+
winningPeakGainDb: peakGainDbRepr
|
|
35719
|
+
});
|
|
35493
35720
|
}
|
|
35494
35721
|
if (this.winningSmoothedEnvelopeBuffer !== null) {
|
|
35495
35722
|
await this.winningSmoothedEnvelopeBuffer.close();
|
|
35496
35723
|
this.winningSmoothedEnvelopeBuffer = null;
|
|
35497
35724
|
}
|
|
35725
|
+
if (this.capturedDetectionEnvelope !== null) {
|
|
35726
|
+
await this.capturedDetectionEnvelope.close();
|
|
35727
|
+
this.capturedDetectionEnvelope = null;
|
|
35728
|
+
}
|
|
35498
35729
|
}
|
|
35499
35730
|
async _unbuffer(chunk) {
|
|
35500
35731
|
const envelopeBuffer = this.winningSmoothedEnvelopeBuffer;
|
|
@@ -35541,10 +35772,10 @@ var _LoudnessTargetNode = class _LoudnessTargetNode extends TransformNode {
|
|
|
35541
35772
|
return new _LoudnessTargetNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
35542
35773
|
}
|
|
35543
35774
|
};
|
|
35544
|
-
_LoudnessTargetNode.
|
|
35775
|
+
_LoudnessTargetNode.nodeName = "Loudness Target";
|
|
35545
35776
|
_LoudnessTargetNode.packageName = PACKAGE_NAME;
|
|
35546
35777
|
_LoudnessTargetNode.packageVersion = PACKAGE_VERSION;
|
|
35547
|
-
_LoudnessTargetNode.
|
|
35778
|
+
_LoudnessTargetNode.nodeDescription = "Peak-aware content-adaptive curve fitting (LUFS, true-peak, LRA) via a single combined gain envelope with a peak-respecting two-stage smoother. The upper-arm peak anchor jointly iterates with the body gain to land both LUFS and true-peak targets in one envelope.";
|
|
35548
35779
|
_LoudnessTargetNode.schema = schema19;
|
|
35549
35780
|
var LoudnessTargetNode = _LoudnessTargetNode;
|
|
35550
35781
|
function loudnessTarget(options) {
|
|
@@ -35552,24 +35783,6 @@ function loudnessTarget(options) {
|
|
|
35552
35783
|
return new LoudnessTargetNode({ ...parsed, id: options.id });
|
|
35553
35784
|
}
|
|
35554
35785
|
|
|
35555
|
-
// src/transforms/loudness-normalize/utils/measurement.ts
|
|
35556
|
-
var CHUNK_FRAMES6 = 44100;
|
|
35557
|
-
async function measureIntegratedLufs(buffer, sampleRate) {
|
|
35558
|
-
const frames = buffer.frames;
|
|
35559
|
-
const channelCount = buffer.channels;
|
|
35560
|
-
if (frames === 0 || channelCount === 0) return -Infinity;
|
|
35561
|
-
const accumulator = new IntegratedLufsAccumulator(sampleRate, channelCount);
|
|
35562
|
-
await buffer.reset();
|
|
35563
|
-
for (; ; ) {
|
|
35564
|
-
const chunk = await buffer.read(CHUNK_FRAMES6);
|
|
35565
|
-
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
35566
|
-
if (chunkFrames === 0) break;
|
|
35567
|
-
accumulator.push(chunk.samples, chunkFrames);
|
|
35568
|
-
if (chunkFrames < CHUNK_FRAMES6) break;
|
|
35569
|
-
}
|
|
35570
|
-
return accumulator.finalize();
|
|
35571
|
-
}
|
|
35572
|
-
|
|
35573
35786
|
// src/transforms/loudness-normalize/index.ts
|
|
35574
35787
|
var schema20 = external_exports.object({
|
|
35575
35788
|
target: external_exports.number().min(-50).max(0).multipleOf(0.1).default(-16).describe("Target integrated loudness (LUFS)")
|
|
@@ -35579,9 +35792,16 @@ var LoudnessNormalizeStream = class extends BufferedTransformStream {
|
|
|
35579
35792
|
super(...arguments);
|
|
35580
35793
|
this.gain = 1;
|
|
35581
35794
|
}
|
|
35582
|
-
async
|
|
35583
|
-
|
|
35584
|
-
const
|
|
35795
|
+
async _buffer(chunk, buffer) {
|
|
35796
|
+
await super._buffer(chunk, buffer);
|
|
35797
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
35798
|
+
const channelCount = chunk.samples.length;
|
|
35799
|
+
if (frames === 0 || channelCount === 0) return;
|
|
35800
|
+
this.accumulator ?? (this.accumulator = new IntegratedLufsAccumulator(chunk.sampleRate, channelCount));
|
|
35801
|
+
this.accumulator.push(chunk.samples, frames);
|
|
35802
|
+
}
|
|
35803
|
+
_process(_buffer) {
|
|
35804
|
+
const integrated = this.accumulator === void 0 ? -Infinity : this.accumulator.finalize();
|
|
35585
35805
|
if (!Number.isFinite(integrated)) {
|
|
35586
35806
|
this.gain = 1;
|
|
35587
35807
|
return;
|
|
@@ -35616,10 +35836,10 @@ var _LoudnessNormalizeNode = class _LoudnessNormalizeNode extends TransformNode
|
|
|
35616
35836
|
return new _LoudnessNormalizeNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
35617
35837
|
}
|
|
35618
35838
|
};
|
|
35619
|
-
_LoudnessNormalizeNode.
|
|
35839
|
+
_LoudnessNormalizeNode.nodeName = "Loudness Normalize";
|
|
35620
35840
|
_LoudnessNormalizeNode.packageName = PACKAGE_NAME;
|
|
35621
35841
|
_LoudnessNormalizeNode.packageVersion = PACKAGE_VERSION;
|
|
35622
|
-
_LoudnessNormalizeNode.
|
|
35842
|
+
_LoudnessNormalizeNode.nodeDescription = "Measure integrated loudness (BS.1770) and apply a single linear gain to hit a target LUFS \u2014 no limiting, no dynamics";
|
|
35623
35843
|
_LoudnessNormalizeNode.schema = schema20;
|
|
35624
35844
|
var LoudnessNormalizeNode = _LoudnessNormalizeNode;
|
|
35625
35845
|
function loudnessNormalize(options) {
|
|
@@ -35628,7 +35848,6 @@ function loudnessNormalize(options) {
|
|
|
35628
35848
|
}
|
|
35629
35849
|
|
|
35630
35850
|
// src/transforms/true-peak-normalize/index.ts
|
|
35631
|
-
var CHUNK_FRAMES7 = 44100;
|
|
35632
35851
|
var schema21 = external_exports.object({
|
|
35633
35852
|
target: external_exports.number().lt(0).default(-1).describe("Target true peak (dBTP). Must be < 0.")
|
|
35634
35853
|
});
|
|
@@ -35637,32 +35856,28 @@ var TruePeakNormalizeStream = class extends BufferedTransformStream {
|
|
|
35637
35856
|
super(...arguments);
|
|
35638
35857
|
this.gain = 1;
|
|
35639
35858
|
}
|
|
35640
|
-
async
|
|
35641
|
-
|
|
35642
|
-
const
|
|
35643
|
-
const
|
|
35644
|
-
if (frames === 0 || channelCount === 0)
|
|
35859
|
+
async _buffer(chunk, buffer) {
|
|
35860
|
+
await super._buffer(chunk, buffer);
|
|
35861
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
35862
|
+
const channelCount = chunk.samples.length;
|
|
35863
|
+
if (frames === 0 || channelCount === 0) return;
|
|
35864
|
+
this.accumulator ?? (this.accumulator = new TruePeakAccumulator(chunk.sampleRate, channelCount));
|
|
35865
|
+
this.accumulator.push(chunk.samples, frames);
|
|
35866
|
+
}
|
|
35867
|
+
_process(_buffer) {
|
|
35868
|
+
if (this.accumulator === void 0) {
|
|
35645
35869
|
this.gain = 1;
|
|
35646
35870
|
return;
|
|
35647
35871
|
}
|
|
35648
|
-
const
|
|
35649
|
-
await buffer.reset();
|
|
35650
|
-
for (; ; ) {
|
|
35651
|
-
const chunk = await buffer.read(CHUNK_FRAMES7);
|
|
35652
|
-
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
35653
|
-
if (chunkFrames === 0) break;
|
|
35654
|
-
accumulator.push(chunk.samples, chunkFrames);
|
|
35655
|
-
if (chunkFrames < CHUNK_FRAMES7) break;
|
|
35656
|
-
}
|
|
35657
|
-
const sourcePeakLinear = accumulator.finalize();
|
|
35872
|
+
const sourcePeakLinear = this.accumulator.finalize();
|
|
35658
35873
|
if (sourcePeakLinear <= 0) {
|
|
35659
35874
|
this.gain = 1;
|
|
35660
|
-
|
|
35875
|
+
this.log("true peak measured", { sourceTpDb: -Infinity, targetDb: this.properties.target, gain: 1 });
|
|
35661
35876
|
return;
|
|
35662
35877
|
}
|
|
35663
35878
|
const sourcePeakDb = 20 * Math.log10(sourcePeakLinear);
|
|
35664
35879
|
this.gain = Math.pow(10, (this.properties.target - sourcePeakDb) / 20);
|
|
35665
|
-
|
|
35880
|
+
this.log("true peak measured", { sourceTpDb: sourcePeakDb, targetDb: this.properties.target, gain: this.gain });
|
|
35666
35881
|
}
|
|
35667
35882
|
_unbuffer(chunk) {
|
|
35668
35883
|
const gain2 = this.gain;
|
|
@@ -35692,10 +35907,10 @@ var _TruePeakNormalizeNode = class _TruePeakNormalizeNode extends TransformNode
|
|
|
35692
35907
|
return new _TruePeakNormalizeNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
35693
35908
|
}
|
|
35694
35909
|
};
|
|
35695
|
-
_TruePeakNormalizeNode.
|
|
35910
|
+
_TruePeakNormalizeNode.nodeName = "True Peak Normalize";
|
|
35696
35911
|
_TruePeakNormalizeNode.packageName = PACKAGE_NAME;
|
|
35697
35912
|
_TruePeakNormalizeNode.packageVersion = PACKAGE_VERSION;
|
|
35698
|
-
_TruePeakNormalizeNode.
|
|
35913
|
+
_TruePeakNormalizeNode.nodeDescription = "Measure source true peak (4\xD7 upsampled, BS.1770-4 style) and apply a single linear gain to hit a target dBTP";
|
|
35699
35914
|
_TruePeakNormalizeNode.schema = schema21;
|
|
35700
35915
|
var TruePeakNormalizeNode = _TruePeakNormalizeNode;
|
|
35701
35916
|
function truePeakNormalize(options) {
|
|
@@ -35968,9 +36183,6 @@ function searchBindingPeak(channelWindows, reflectionRow, order, lambda, targetP
|
|
|
35968
36183
|
if (identityPower <= targetPeakPower || lambda <= 0) {
|
|
35969
36184
|
return {
|
|
35970
36185
|
scale: 0,
|
|
35971
|
-
// One objective evaluation was performed (the identity / c=0
|
|
35972
|
-
// `truePeakPower4x` above) — `iterations` is the deterministic
|
|
35973
|
-
// evaluation count, so the skip path is 1, not 0.
|
|
35974
36186
|
iterations: 1,
|
|
35975
36187
|
committedPeakPower: identityPower,
|
|
35976
36188
|
identityPeakPower: identityPower,
|
|
@@ -36125,42 +36337,41 @@ function stftFrameCount(signalLength, frameSize, hopSize) {
|
|
|
36125
36337
|
if (signalLength < frameSize || hopSize <= 0) return 0;
|
|
36126
36338
|
return Math.floor((signalLength - frameSize) / hopSize) + 1;
|
|
36127
36339
|
}
|
|
36128
|
-
|
|
36129
|
-
|
|
36130
|
-
|
|
36131
|
-
|
|
36132
|
-
|
|
36133
|
-
|
|
36134
|
-
|
|
36135
|
-
|
|
36136
|
-
|
|
36137
|
-
|
|
36138
|
-
|
|
36139
|
-
|
|
36140
|
-
|
|
36141
|
-
|
|
36142
|
-
|
|
36143
|
-
|
|
36144
|
-
|
|
36145
|
-
const samples = chunk.samples[channel];
|
|
36146
|
-
const upsampler = upsamplers[channel];
|
|
36340
|
+
var TruePeakArgmaxAccumulator = class {
|
|
36341
|
+
constructor(channelCount, _sampleRate) {
|
|
36342
|
+
this.channelCount = channelCount;
|
|
36343
|
+
this.runningMax = 0;
|
|
36344
|
+
this.peakInputSample = 0;
|
|
36345
|
+
this.inputBase = 0;
|
|
36346
|
+
this.upsamplers = [];
|
|
36347
|
+
for (let channel = 0; channel < channelCount; channel++) this.upsamplers.push(new TruePeakUpsampler(4));
|
|
36348
|
+
}
|
|
36349
|
+
push(channels, frames) {
|
|
36350
|
+
if (frames <= 0) {
|
|
36351
|
+
this.inputBase += frames > 0 ? frames : 0;
|
|
36352
|
+
return;
|
|
36353
|
+
}
|
|
36354
|
+
for (let channel = 0; channel < this.channelCount; channel++) {
|
|
36355
|
+
const samples = channels[channel];
|
|
36356
|
+
const upsampler = this.upsamplers[channel];
|
|
36147
36357
|
if (samples === void 0 || upsampler === void 0) continue;
|
|
36148
|
-
const slice = samples.length ===
|
|
36358
|
+
const slice = samples.length === frames ? samples : samples.subarray(0, frames);
|
|
36149
36359
|
const upsampled = upsampler.upsample(slice);
|
|
36150
36360
|
for (let index = 0; index < upsampled.length; index++) {
|
|
36151
36361
|
const value = upsampled[index] ?? 0;
|
|
36152
36362
|
const magnitude = value < 0 ? -value : value;
|
|
36153
|
-
if (magnitude > runningMax) {
|
|
36154
|
-
runningMax = magnitude;
|
|
36155
|
-
peakInputSample = inputBase + Math.floor(index / 4);
|
|
36363
|
+
if (magnitude > this.runningMax) {
|
|
36364
|
+
this.runningMax = magnitude;
|
|
36365
|
+
this.peakInputSample = this.inputBase + Math.floor(index / 4);
|
|
36156
36366
|
}
|
|
36157
36367
|
}
|
|
36158
36368
|
}
|
|
36159
|
-
inputBase +=
|
|
36160
|
-
toRead -= got;
|
|
36369
|
+
this.inputBase += frames;
|
|
36161
36370
|
}
|
|
36162
|
-
|
|
36163
|
-
}
|
|
36371
|
+
finalize() {
|
|
36372
|
+
return { db: linearToDb(this.runningMax), peakInputSample: this.peakInputSample };
|
|
36373
|
+
}
|
|
36374
|
+
};
|
|
36164
36375
|
async function streamLatticeTrajectory(buffer, frameSize, hopSize, backend, addonOptions, search) {
|
|
36165
36376
|
const channelCount = buffer.channels;
|
|
36166
36377
|
const signalLength = buffer.frames;
|
|
@@ -36277,40 +36488,40 @@ async function streamLatticeTrajectory(buffer, frameSize, hopSize, backend, addo
|
|
|
36277
36488
|
}
|
|
36278
36489
|
return { trajectory, frameCount, signalLength, windowPeaks, bindingMask };
|
|
36279
36490
|
}
|
|
36280
|
-
|
|
36281
|
-
|
|
36282
|
-
|
|
36283
|
-
|
|
36284
|
-
|
|
36285
|
-
|
|
36286
|
-
|
|
36287
|
-
|
|
36288
|
-
|
|
36289
|
-
|
|
36290
|
-
|
|
36291
|
-
|
|
36292
|
-
const
|
|
36293
|
-
const
|
|
36294
|
-
const
|
|
36295
|
-
|
|
36296
|
-
const out = Array.from({ length: channelCount }, () => new Float32Array(
|
|
36297
|
-
for (let index = 0; index <
|
|
36298
|
-
const framePos = hopSize > 0 ? sample / hopSize : 0;
|
|
36491
|
+
var _LatticeApplyState = class _LatticeApplyState {
|
|
36492
|
+
constructor(trajectory, order, hopSize, channelCount) {
|
|
36493
|
+
this.trajectory = trajectory;
|
|
36494
|
+
this.order = order;
|
|
36495
|
+
this.hopSize = hopSize;
|
|
36496
|
+
this.sample = 0;
|
|
36497
|
+
this.rows = trajectory.rows;
|
|
36498
|
+
this.frameCount = this.rows.length;
|
|
36499
|
+
this.state = Array.from({ length: channelCount }, () => new Float32Array(order));
|
|
36500
|
+
}
|
|
36501
|
+
apply(channels, frames) {
|
|
36502
|
+
const channelCount = this.state.length;
|
|
36503
|
+
const order = this.order;
|
|
36504
|
+
const hopSize = this.hopSize;
|
|
36505
|
+
const frameCount = this.frameCount;
|
|
36506
|
+
const rows = this.rows;
|
|
36507
|
+
const out = Array.from({ length: channelCount }, () => new Float32Array(frames));
|
|
36508
|
+
for (let index = 0; index < frames; index++) {
|
|
36509
|
+
const framePos = hopSize > 0 ? this.sample / hopSize : 0;
|
|
36299
36510
|
const frame0 = Math.min(frameCount - 1, Math.max(0, Math.floor(framePos)));
|
|
36300
36511
|
const frame1 = Math.min(frameCount - 1, frame0 + 1);
|
|
36301
36512
|
const fraction = framePos - frame0;
|
|
36302
|
-
const row0 = rows[frame0] ??
|
|
36303
|
-
const row1 = rows[frame1] ??
|
|
36513
|
+
const row0 = rows[frame0] ?? this.trajectory.identity;
|
|
36514
|
+
const row1 = rows[frame1] ?? this.trajectory.identity;
|
|
36304
36515
|
for (let ch = 0; ch < channelCount; ch++) {
|
|
36305
|
-
const inputValue =
|
|
36516
|
+
const inputValue = channels[ch]?.[index] ?? 0;
|
|
36306
36517
|
const outChannel = out[ch];
|
|
36307
|
-
const chState = state[ch] ?? new Float32Array(order);
|
|
36518
|
+
const chState = this.state[ch] ?? new Float32Array(order);
|
|
36308
36519
|
let signalValue = inputValue;
|
|
36309
36520
|
for (let section = 0; section < order; section++) {
|
|
36310
36521
|
const interpolated = (row0[section] ?? 0) + fraction * ((row1[section] ?? 0) - (row0[section] ?? 0));
|
|
36311
|
-
let kCoeff =
|
|
36312
|
-
if (kCoeff >
|
|
36313
|
-
else if (kCoeff < -
|
|
36522
|
+
let kCoeff = _LatticeApplyState.SCALE * interpolated;
|
|
36523
|
+
if (kCoeff > _LatticeApplyState.MAX_REFLECTION) kCoeff = _LatticeApplyState.MAX_REFLECTION;
|
|
36524
|
+
else if (kCoeff < -_LatticeApplyState.MAX_REFLECTION) kCoeff = -_LatticeApplyState.MAX_REFLECTION;
|
|
36314
36525
|
const cCoeff = Math.sqrt(Math.max(0, 1 - kCoeff * kCoeff));
|
|
36315
36526
|
const delayed = chState[section] ?? 0;
|
|
36316
36527
|
const toDelay = cCoeff * signalValue + kCoeff * delayed;
|
|
@@ -36320,12 +36531,15 @@ async function streamLatticeApply(buffer, smoothedTrajectory, scale, order, hopS
|
|
|
36320
36531
|
}
|
|
36321
36532
|
if (outChannel) outChannel[index] = signalValue;
|
|
36322
36533
|
}
|
|
36323
|
-
sample += 1;
|
|
36534
|
+
this.sample += 1;
|
|
36324
36535
|
}
|
|
36325
|
-
|
|
36326
|
-
toRead -= got;
|
|
36536
|
+
return out;
|
|
36327
36537
|
}
|
|
36328
|
-
}
|
|
36538
|
+
};
|
|
36539
|
+
// Mirrors lattice.ts MAX_REFLECTION (module-private there); both sides must clamp identically.
|
|
36540
|
+
_LatticeApplyState.MAX_REFLECTION = 0.95;
|
|
36541
|
+
_LatticeApplyState.SCALE = 1;
|
|
36542
|
+
var LatticeApplyState = _LatticeApplyState;
|
|
36329
36543
|
|
|
36330
36544
|
// src/transforms/crest-reduce/index.ts
|
|
36331
36545
|
function isPowerOfTwo(value) {
|
|
@@ -36342,28 +36556,8 @@ var schema22 = external_exports.object({
|
|
|
36342
36556
|
var CrestReduceStream = class extends BufferedTransformStream {
|
|
36343
36557
|
constructor() {
|
|
36344
36558
|
super(...arguments);
|
|
36345
|
-
|
|
36346
|
-
|
|
36347
|
-
* `ChunkBuffer` (the `loudnessTarget` `winningSmoothedEnvelopeBuffer`
|
|
36348
|
-
* precedent — NOT a stream-resident full-length `Float32Array`, which
|
|
36349
|
-
* was the design-streaming.md materialization anti-pattern Phase 2
|
|
36350
|
-
* removes). `_process` writes the produced output here at flush;
|
|
36351
|
-
* `_unbuffer` serves it chunk-by-chunk. `null` when the stream passes
|
|
36352
|
-
* through (no audio or a sub-frame input — there is no `strength`
|
|
36353
|
-
* bypass after the 2026-05-17 keystone; the node always runs the
|
|
36354
|
-
* gate/search/lattice path), in which case `_unbuffer` emits the
|
|
36355
|
-
* input verbatim (the length-preserving passthrough fallback).
|
|
36356
|
-
*/
|
|
36357
|
-
this.outputBuffer = null;
|
|
36358
|
-
/**
|
|
36359
|
-
* Set `true` by the first `_unbuffer` call so `outputBuffer`'s read
|
|
36360
|
-
* cursor is rewound exactly once (it is at end-of-buffer after
|
|
36361
|
-
* `_process`'s streaming production write). Rewound lazily in
|
|
36362
|
-
* `_unbuffer` — after `_process` finished — not eagerly, so a stable
|
|
36363
|
-
* cursor state exists (the `loudnessTarget` `unbufferCursorsReady`
|
|
36364
|
-
* precedent).
|
|
36365
|
-
*/
|
|
36366
|
-
this.unbufferCursorReady = false;
|
|
36559
|
+
this.applyOrder = LATTICE_ORDER;
|
|
36560
|
+
this.applyHopSize = 0;
|
|
36367
36561
|
}
|
|
36368
36562
|
async _setup(input, context) {
|
|
36369
36563
|
const fft2 = initFftBackend(context.executionProviders, this.properties);
|
|
@@ -36374,98 +36568,13 @@ var CrestReduceStream = class extends BufferedTransformStream {
|
|
|
36374
36568
|
get hopSize() {
|
|
36375
36569
|
return this.properties.frameSize / 4;
|
|
36376
36570
|
}
|
|
36377
|
-
|
|
36378
|
-
|
|
36379
|
-
|
|
36380
|
-
|
|
36381
|
-
|
|
36382
|
-
|
|
36383
|
-
|
|
36384
|
-
*
|
|
36385
|
-
* The 2026-05-17 KEYSTONE rework — SINGLE-PASS, per-binding-peak
|
|
36386
|
-
* Item-7 search over a TP-DRIVEN gate and a TP-DRIVEN commit
|
|
36387
|
-
* objective; NO `strength` (the node always applies the optimal
|
|
36388
|
-
* value); NO whole-signal never-worsen veto (the only never-worsen
|
|
36389
|
-
* is the per-window commit-only-if-better intrinsic to the Item-7
|
|
36390
|
-
* search):
|
|
36391
|
-
*
|
|
36392
|
-
* 1. WHOLE-SIGNAL 4× true peak + its input-sample index measured
|
|
36393
|
-
* ONCE — a SEQUENTIAL CHUNKED WALK driving per-channel BS.1770-4
|
|
36394
|
-
* 4× `TruePeakUpsampler`s directly
|
|
36395
|
-
* (`measureBufferTruePeakWithArgmax`; 12-tap history carries
|
|
36396
|
-
* across `upsample` so chunk boundaries are invisible). The dBTP
|
|
36397
|
-
* is the gate's proximity reference; `peakInputSample`
|
|
36398
|
-
* force-binds the analysis frame containing the file's global
|
|
36399
|
-
* 4× true peak.
|
|
36400
|
-
* 2. EXTRACT + TP-DRIVEN GATE + ITEM-7 SEARCH, one streaming
|
|
36401
|
-
* sliding-window pass — `streamLatticeTrajectory(buffer, …,
|
|
36402
|
-
* { globalTruePeakDb, peakInputSample, sampleRate, lambda })`: a
|
|
36403
|
-
* `frameSize` ring of the linked-stereo sum + ONE per channel,
|
|
36404
|
-
* computed on the fly (NO whole-signal array). Per hop the
|
|
36405
|
-
* grounded Abel & Smith (Item 9) + RMV §III step-down (Item 8)
|
|
36406
|
-
* reflection row is fitted VERBATIM; the frame is classified by
|
|
36407
|
-
* the TP-DRIVEN gate predicate (`isBindingPeak` —
|
|
36408
|
-
* `peakPriorityAmount` headroom AND ( the window's OWN
|
|
36409
|
-
* per-channel 4× true peak within BINDING_DELTA_DB of the global
|
|
36410
|
-
* 4× true peak, SAME true-peak domain — OR the global-4×-TP
|
|
36411
|
-
* frame, force-bound ); `binding.ts` UNFROZEN); an ACTIVE-band
|
|
36412
|
-
* peak frame runs the §Algorithm Specification Item-7 search
|
|
36413
|
-
* (`searchBindingPeak`, Hong/Kim/Har 2011 §3; `search.ts`
|
|
36414
|
-
* UNFROZEN — its COMMIT OBJECTIVE is the cross-channel 4×
|
|
36415
|
-
* true-peak power, the Eq. 7–9 φ′ raw-sample recurrence kept as
|
|
36416
|
-
* the candidate-proposal direction only) over the PER-CHANNEL
|
|
36417
|
-
* windows with the FULL group-delay-ceiling λ — its committed
|
|
36418
|
-
* scale (commit-only-if-better on the 4× TP power; identity
|
|
36419
|
-
* `c=0` is the guaranteed floor) is the per-active-peak OPTIMAL
|
|
36420
|
-
* decorrelation value; a NON-active frame's envelope value is 0.
|
|
36421
|
-
* There is NO `strength` (λ is ALWAYS the full ceiling,
|
|
36422
|
-
* `groupDelayLambda`). The O(frames) trajectory IS the
|
|
36423
|
-
* decorrelation envelope (the ONLY resident product besides
|
|
36424
|
-
* bounded scratch).
|
|
36425
|
-
* 2b. COMBINE the envelope (the 2026-05-17 PER-PEAK-EXACT model):
|
|
36426
|
-
* `smoothControlTrajectory` = `max` of a per-peak EXACT-OPTIMAL
|
|
36427
|
-
* flat hold centered on the PEAK SAMPLE `n_i` (half-width
|
|
36428
|
-
* `exactHoldHalfWidthFrames(sampleRate, hopSize)` — the
|
|
36429
|
-
* group-delay span, NEVER `smoothing`) that pins the EXACT
|
|
36430
|
-
* Item-7 optimal at every binding peak so whole-signal
|
|
36431
|
-
* true-peak reduction is `smoothing`-INVARIANT, and a
|
|
36432
|
-
* `smoothing`-driven bidirectional zero-phase SPILL
|
|
36433
|
-
* (transient-asymmetry pullback pre-pass THEN forward+backward
|
|
36434
|
-
* `BidirectionalIir` on the trajectory's FRAME-RATE axis,
|
|
36435
|
-
* τ = (ms/1000)·√2 internal to `BidirectionalIir`) whose ONLY
|
|
36436
|
-
* effect is decorrelation spill into the gated gaps + easing
|
|
36437
|
-
* between active values. HARD RULE (design §Rejected
|
|
36438
|
-
* Approaches): both passes are on the CONTROL trajectory ONLY —
|
|
36439
|
-
* NEVER the audio.
|
|
36440
|
-
* 3. ONE streaming PRODUCTION pass over the committed
|
|
36441
|
-
* per-peak-exact + bidirectionally-SMOOTHED decorrelation
|
|
36442
|
-
* envelope. `streamLatticeApply` is the byte-faithful
|
|
36443
|
-
* `processLatticeChannel` transcription (lattice.ts BYTE-FROZEN)
|
|
36444
|
-
* and is called with the literal `1` for its internal
|
|
36445
|
-
* transcribed scalar (an identity no-op at the call site — an
|
|
36446
|
-
* internal arg of the verbatim transcription, NOT a public
|
|
36447
|
-
* surface; there is no `strength`). There is NO whole-signal
|
|
36448
|
-
* never-worsen veto (removed — the per-window
|
|
36449
|
-
* commit-only-if-better in the Item-7 search is the only
|
|
36450
|
-
* never-worsen) and NO per-sample binding gate / edge crossfade:
|
|
36451
|
-
* a non-active segment is the lattice at eased-to-zero
|
|
36452
|
-
* coefficients (a crest-invariant pure delay). Each output chunk
|
|
36453
|
-
* is written straight into the node-owned output `ChunkBuffer`
|
|
36454
|
-
* (served chunk-by-chunk by `_unbuffer`). The streamed lattice
|
|
36455
|
-
* output is BIT-IDENTICAL to processing one contiguous array.
|
|
36456
|
-
*
|
|
36457
|
-
* NO whole-signal `Float32Array` exists anywhere — the
|
|
36458
|
-
* design-streaming.md materialization anti-pattern is GENUINELY
|
|
36459
|
-
* eliminated (not relocated). Resident state: the O(frames) trajectory
|
|
36460
|
-
* + mask + bounded ring (sum + per-channel) / accumulator / chunk
|
|
36461
|
-
* scratch.
|
|
36462
|
-
*
|
|
36463
|
-
* There is NO node-level bypass (the `strength` parameter and its
|
|
36464
|
-
* `strength <= 0` early-return were removed by the 2026-05-17
|
|
36465
|
-
* keystone). The only passthrough is the length-preserving fallback
|
|
36466
|
-
* for no audio / a sub-frame input (no analysis frame fits) —
|
|
36467
|
-
* `_unbuffer` then emits the input verbatim.
|
|
36468
|
-
*/
|
|
36571
|
+
async _buffer(chunk, buffer) {
|
|
36572
|
+
await super._buffer(chunk, buffer);
|
|
36573
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
36574
|
+
if (frames === 0 || chunk.samples.length === 0) return;
|
|
36575
|
+
this.truePeakAccumulator ?? (this.truePeakAccumulator = new TruePeakArgmaxAccumulator(chunk.samples.length, chunk.sampleRate));
|
|
36576
|
+
this.truePeakAccumulator.push(chunk.samples, frames);
|
|
36577
|
+
}
|
|
36469
36578
|
async _process(buffer) {
|
|
36470
36579
|
const { frameSize, smoothing } = this.properties;
|
|
36471
36580
|
const channelCount = buffer.channels;
|
|
@@ -36474,7 +36583,7 @@ var CrestReduceStream = class extends BufferedTransformStream {
|
|
|
36474
36583
|
const sampleRate = buffer.sampleRate ?? this.sampleRate ?? 48e3;
|
|
36475
36584
|
const order = LATTICE_ORDER;
|
|
36476
36585
|
const hopSize = this.hopSize;
|
|
36477
|
-
const { db: inputTpDb, peakInputSample } =
|
|
36586
|
+
const { db: inputTpDb, peakInputSample } = this.truePeakAccumulator?.finalize() ?? { db: linearToDb(0), peakInputSample: 0 };
|
|
36478
36587
|
const lambda = groupDelayLambda(sampleRate, order);
|
|
36479
36588
|
const { trajectory, frameCount } = await streamLatticeTrajectory(buffer, frameSize, hopSize, this.fftBackend, this.fftAddonOptions, {
|
|
36480
36589
|
globalTruePeakDb: inputTpDb,
|
|
@@ -36482,58 +36591,21 @@ var CrestReduceStream = class extends BufferedTransformStream {
|
|
|
36482
36591
|
sampleRate,
|
|
36483
36592
|
lambda
|
|
36484
36593
|
});
|
|
36485
|
-
if (frameCount === 0)
|
|
36486
|
-
|
|
36487
|
-
|
|
36488
|
-
|
|
36489
|
-
const out = new ChunkBuffer();
|
|
36490
|
-
await streamLatticeApply(buffer, smoothed, 1, order, hopSize, async (channels, got) => {
|
|
36491
|
-
await out.write(
|
|
36492
|
-
channels.map((channel) => channel.length === got ? channel : channel.subarray(0, got)),
|
|
36493
|
-
sampleRate
|
|
36494
|
-
);
|
|
36495
|
-
});
|
|
36496
|
-
await out.flushWrites();
|
|
36497
|
-
if (out.frames > 0) this.outputBuffer = out;
|
|
36498
|
-
else await out.close();
|
|
36594
|
+
if (frameCount === 0) return;
|
|
36595
|
+
this.smoothedTrajectory = smoothControlTrajectory(trajectory, smoothing, trajectoryFrameRate(sampleRate, hopSize), exactHoldHalfWidthFrames(sampleRate, hopSize), hopSize);
|
|
36596
|
+
this.applyOrder = order;
|
|
36597
|
+
this.applyHopSize = hopSize;
|
|
36499
36598
|
}
|
|
36500
|
-
|
|
36501
|
-
|
|
36502
|
-
|
|
36503
|
-
|
|
36504
|
-
|
|
36505
|
-
|
|
36506
|
-
|
|
36507
|
-
* input chunk is emitted VERBATIM — the length-preserving passthrough
|
|
36508
|
-
* fallback (nothing mutated, same backing arrays).
|
|
36509
|
-
*/
|
|
36510
|
-
async _unbuffer(chunk) {
|
|
36511
|
-
const output = this.outputBuffer;
|
|
36512
|
-
const length = chunk.samples[0]?.length ?? 0;
|
|
36513
|
-
if (output === null || output.frames === 0 || length === 0) return chunk;
|
|
36514
|
-
if (!this.unbufferCursorReady) {
|
|
36515
|
-
await output.reset();
|
|
36516
|
-
this.unbufferCursorReady = true;
|
|
36517
|
-
}
|
|
36518
|
-
const transformedChunk = await output.read(length);
|
|
36519
|
-
const transformed = transformedChunk.samples;
|
|
36599
|
+
_unbuffer(chunk) {
|
|
36600
|
+
const trajectory = this.smoothedTrajectory;
|
|
36601
|
+
const frames = chunk.samples[0]?.length ?? 0;
|
|
36602
|
+
const channelCount = chunk.samples.length;
|
|
36603
|
+
if (trajectory === void 0 || frames === 0 || channelCount === 0) return chunk;
|
|
36604
|
+
this.applyState ?? (this.applyState = new LatticeApplyState(trajectory, this.applyOrder, this.applyHopSize, channelCount));
|
|
36605
|
+
const transformed = this.applyState.apply(chunk.samples, frames);
|
|
36520
36606
|
const samples = chunk.samples.map((inputChannel, ch) => transformed[ch] ?? inputChannel);
|
|
36521
36607
|
return { samples, offset: chunk.offset, sampleRate: chunk.sampleRate, bitDepth: chunk.bitDepth };
|
|
36522
36608
|
}
|
|
36523
|
-
/**
|
|
36524
|
-
* Idempotent cleanup of the node-owned output `ChunkBuffer` so its
|
|
36525
|
-
* backing temp file is released on EVERY exit path — not only graceful
|
|
36526
|
-
* end-of-stream (the 2026-05-12 `_teardown`-guaranteed-cleanup
|
|
36527
|
-
* Decision; the `loudnessTarget` precedent). `BufferedTransformStream`
|
|
36528
|
-
* already closes its own accumulation `chunkBuffer` in `teardown()`;
|
|
36529
|
-
* this node-owned extra buffer needs its own release.
|
|
36530
|
-
*/
|
|
36531
|
-
async _teardown() {
|
|
36532
|
-
if (this.outputBuffer !== null) {
|
|
36533
|
-
await this.outputBuffer.close();
|
|
36534
|
-
this.outputBuffer = null;
|
|
36535
|
-
}
|
|
36536
|
-
}
|
|
36537
36609
|
};
|
|
36538
36610
|
var _CrestReduceNode = class _CrestReduceNode extends TransformNode {
|
|
36539
36611
|
constructor(properties) {
|
|
@@ -36550,10 +36622,10 @@ var _CrestReduceNode = class _CrestReduceNode extends TransformNode {
|
|
|
36550
36622
|
return new _CrestReduceNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
36551
36623
|
}
|
|
36552
36624
|
};
|
|
36553
|
-
_CrestReduceNode.
|
|
36625
|
+
_CrestReduceNode.nodeName = "Crest Reduce";
|
|
36554
36626
|
_CrestReduceNode.packageName = PACKAGE_NAME;
|
|
36555
36627
|
_CrestReduceNode.packageVersion = PACKAGE_VERSION;
|
|
36556
|
-
_CrestReduceNode.
|
|
36628
|
+
_CrestReduceNode.nodeDescription = "Content-adaptive, magnitude-preserving, phase-only crest-factor reducer \u2014 a pre-limiter headroom stage that rearranges signal phase to flatten true-peak excursions without changing the magnitude spectrum, never increasing crest factor";
|
|
36557
36629
|
_CrestReduceNode.schema = schema22;
|
|
36558
36630
|
var CrestReduceNode = _CrestReduceNode;
|
|
36559
36631
|
function crestReduce(options) {
|
|
@@ -37045,7 +37117,6 @@ var WindowReader = class {
|
|
|
37045
37117
|
getScratch() {
|
|
37046
37118
|
return this.scratch;
|
|
37047
37119
|
}
|
|
37048
|
-
/** Fill the leading `edgePadSamples` of the virtual signal with zeros and load the rest of the first window from `buffer`. */
|
|
37049
37120
|
async preload(buffer, edgePadSamples) {
|
|
37050
37121
|
for (let ch = 0; ch < this.channels; ch++) this.scratch[ch].fill(0);
|
|
37051
37122
|
this.virtualCursor = 0;
|
|
@@ -37055,7 +37126,6 @@ var WindowReader = class {
|
|
|
37055
37126
|
if (remainingInWindow > 0) await this.readInto(buffer, headPad, remainingInWindow);
|
|
37056
37127
|
this.virtualCursor = this.windowSamples;
|
|
37057
37128
|
}
|
|
37058
|
-
/** Slide scratch left by `step` samples; append `step` new samples from `buffer` (zero-filled past end). */
|
|
37059
37129
|
async advance(buffer, step) {
|
|
37060
37130
|
if (step <= 0) return;
|
|
37061
37131
|
const keep = this.windowSamples - step;
|
|
@@ -37169,20 +37239,6 @@ var DeBleedStream = class extends BufferedTransformStream {
|
|
|
37169
37239
|
}
|
|
37170
37240
|
this.referenceBuffers = [];
|
|
37171
37241
|
}
|
|
37172
|
-
/**
|
|
37173
|
-
* Run the first-N-seconds warm-up scan across all target channels and all
|
|
37174
|
-
* references in a single sequential pass over each buffer.
|
|
37175
|
-
*
|
|
37176
|
-
* Each buffer is rewound to its start, then sequentially read for
|
|
37177
|
-
* `warmupSamples` real samples. STFTs are computed once per buffer (one
|
|
37178
|
-
* STFT per channel for the multi-channel target, one STFT per reference
|
|
37179
|
-
* for the references). Per-channel cross-spectral accumulators then
|
|
37180
|
-
* consume the pre-computed STFTs.
|
|
37181
|
-
*
|
|
37182
|
-
* Returns a `channels × refCount` array of `TransferFunction` seeds:
|
|
37183
|
-
* `seedsByChannel[ch][refIndex]`. Degeneracy validation and cold-start
|
|
37184
|
-
* fallback per `validateTransferSeed`.
|
|
37185
|
-
*/
|
|
37186
37242
|
async warmupSeedsAllChannels(buffer, channels, warmupFrames, fftSize, hopSize) {
|
|
37187
37243
|
const { numBins, referenceBuffers } = this;
|
|
37188
37244
|
const refCount = referenceBuffers.length;
|
|
@@ -37229,7 +37285,7 @@ var DeBleedStream = class extends BufferedTransformStream {
|
|
|
37229
37285
|
const validated = seeds.map((seed) => {
|
|
37230
37286
|
const validation = validateTransferSeed(seed);
|
|
37231
37287
|
if (validation.degenerate) {
|
|
37232
|
-
|
|
37288
|
+
this.log("warm-up seed degenerate; falling back to cold-start \u0124(\u2113=0) = 0", { reason: validation.reason }, "warn");
|
|
37233
37289
|
return coldStartSeed(numBins);
|
|
37234
37290
|
}
|
|
37235
37291
|
return seed;
|
|
@@ -37493,17 +37549,18 @@ var DeBleedStream = class extends BufferedTransformStream {
|
|
|
37493
37549
|
if (profileEnabled) {
|
|
37494
37550
|
const total = profileMs.warmup + profileMs.stftRead + profileMs.msad + profileMs.kalman + profileMs.mwf + profileMs.nlm + profileMs.dftt + profileMs.applyMaskIstft + profileMs.write;
|
|
37495
37551
|
const pct = (key) => `${(profileMs[key] / 1e3).toFixed(2)}s (${(profileMs[key] / total * 100).toFixed(1)}%)`;
|
|
37496
|
-
|
|
37497
|
-
|
|
37498
|
-
|
|
37499
|
-
|
|
37500
|
-
|
|
37501
|
-
|
|
37502
|
-
|
|
37503
|
-
|
|
37504
|
-
|
|
37505
|
-
|
|
37506
|
-
|
|
37552
|
+
this.log("profile", {
|
|
37553
|
+
warmup: pct("warmup"),
|
|
37554
|
+
stftRead: pct("stftRead"),
|
|
37555
|
+
msad: pct("msad"),
|
|
37556
|
+
kalman: pct("kalman"),
|
|
37557
|
+
mwf: pct("mwf"),
|
|
37558
|
+
nlm: pct("nlm"),
|
|
37559
|
+
dftt: pct("dftt"),
|
|
37560
|
+
applyMaskIstft: pct("applyMaskIstft"),
|
|
37561
|
+
write: pct("write"),
|
|
37562
|
+
totalS: (total / 1e3).toFixed(2)
|
|
37563
|
+
});
|
|
37507
37564
|
}
|
|
37508
37565
|
}
|
|
37509
37566
|
};
|
|
@@ -37522,10 +37579,10 @@ var _DeBleedNode = class _DeBleedNode extends TransformNode {
|
|
|
37522
37579
|
return new _DeBleedNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
37523
37580
|
}
|
|
37524
37581
|
};
|
|
37525
|
-
_DeBleedNode.
|
|
37582
|
+
_DeBleedNode.nodeName = "De-Bleed Adaptive";
|
|
37526
37583
|
_DeBleedNode.packageName = PACKAGE_NAME;
|
|
37527
37584
|
_DeBleedNode.packageVersion = PACKAGE_VERSION;
|
|
37528
|
-
_DeBleedNode.
|
|
37585
|
+
_DeBleedNode.nodeDescription = "Adaptive (MEF FDAF Kalman + MWF + MSAD) reference-based microphone bleed reduction. Stages 1+2 are MEF Meyer-Elshamy-Fingscheidt 2020; Stage 3 is Lukin-Todd 2D NLM+DFTT post-filter.";
|
|
37529
37586
|
_DeBleedNode.schema = schema23;
|
|
37530
37587
|
var DeBleedNode = _DeBleedNode;
|
|
37531
37588
|
function deBleed(references, options) {
|
|
@@ -37544,7 +37601,7 @@ function deBleed(references, options) {
|
|
|
37544
37601
|
});
|
|
37545
37602
|
}
|
|
37546
37603
|
var require3 = createRequire(import.meta.url);
|
|
37547
|
-
function createOnnxSession(addonPath, modelPath, options) {
|
|
37604
|
+
function createOnnxSession(addonPath, modelPath, options, log) {
|
|
37548
37605
|
let addon;
|
|
37549
37606
|
try {
|
|
37550
37607
|
addon = require3(addonPath);
|
|
@@ -37566,7 +37623,7 @@ function createOnnxSession(addonPath, modelPath, options) {
|
|
|
37566
37623
|
} catch (error50) {
|
|
37567
37624
|
provider = `<unknown> (getProvider() threw: ${error50 instanceof Error ? error50.message : String(error50)})`;
|
|
37568
37625
|
}
|
|
37569
|
-
|
|
37626
|
+
log?.("onnx session created", { modelName, provider });
|
|
37570
37627
|
return {
|
|
37571
37628
|
run(inputs) {
|
|
37572
37629
|
return session.run(inputs);
|
|
@@ -37638,14 +37695,10 @@ var schema24 = external_exports.object({
|
|
|
37638
37695
|
var DeepFilterNet3Stream = class extends BufferedTransformStream {
|
|
37639
37696
|
constructor() {
|
|
37640
37697
|
super(...arguments);
|
|
37641
|
-
// One DfnState per channel — DFN3's recurrent state is per-source, so stereo input
|
|
37642
|
-
// needs two independent states (matches DTLN's per-channel handling).
|
|
37643
|
-
// Allocated lazily on the first `_process` call so we can size to the actual chunk
|
|
37644
|
-
// channel count rather than guessing.
|
|
37645
37698
|
this.dfnStates = [];
|
|
37646
37699
|
}
|
|
37647
37700
|
async _setup(input, context) {
|
|
37648
|
-
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: ["cpu"] });
|
|
37701
|
+
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: ["cpu"] }, (message, data) => this.log(message, data));
|
|
37649
37702
|
const sourceRate = this.properties.sampleRate;
|
|
37650
37703
|
if (sourceRate === DFN3_SAMPLE_RATE) {
|
|
37651
37704
|
return super._setup(input, context);
|
|
@@ -37719,10 +37772,10 @@ var _DeepFilterNet3Node = class _DeepFilterNet3Node extends TransformNode {
|
|
|
37719
37772
|
return new _DeepFilterNet3Node({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
37720
37773
|
}
|
|
37721
37774
|
};
|
|
37722
|
-
_DeepFilterNet3Node.
|
|
37775
|
+
_DeepFilterNet3Node.nodeName = "DeepFilterNet3 (Denoiser)";
|
|
37723
37776
|
_DeepFilterNet3Node.packageName = PACKAGE_NAME;
|
|
37724
37777
|
_DeepFilterNet3Node.packageVersion = PACKAGE_VERSION;
|
|
37725
|
-
_DeepFilterNet3Node.
|
|
37778
|
+
_DeepFilterNet3Node.nodeDescription = "Remove background noise from speech using DeepFilterNet3 (48 kHz full-band CRN)";
|
|
37726
37779
|
_DeepFilterNet3Node.schema = schema24;
|
|
37727
37780
|
var DeepFilterNet3Node = _DeepFilterNet3Node;
|
|
37728
37781
|
function deepFilterNet3(options) {
|
|
@@ -37791,11 +37844,6 @@ var DtlnBlockStream = class {
|
|
|
37791
37844
|
};
|
|
37792
37845
|
this.stftOutput = { real: this.stftRealScratch, imag: this.stftImagScratch };
|
|
37793
37846
|
}
|
|
37794
|
-
/**
|
|
37795
|
-
* Feed `BLOCK_SHIFT` samples; return `BLOCK_SHIFT` samples of output (from the
|
|
37796
|
-
* stable left edge of the OLA accumulator). The first `WARMUP_SHIFTS` calls
|
|
37797
|
-
* return zeros (the sliding window is not yet full of real samples).
|
|
37798
|
-
*/
|
|
37799
37847
|
step(inputBlock) {
|
|
37800
37848
|
if (inputBlock.length !== BLOCK_SHIFT) {
|
|
37801
37849
|
throw new Error(`DtlnBlockStream.step: expected ${String(BLOCK_SHIFT)} samples, got ${String(inputBlock.length)}`);
|
|
@@ -37817,11 +37865,6 @@ var DtlnBlockStream = class {
|
|
|
37817
37865
|
this.olaScratch.fill(0, BLOCK_LEN - BLOCK_SHIFT, BLOCK_LEN);
|
|
37818
37866
|
return out;
|
|
37819
37867
|
}
|
|
37820
|
-
/**
|
|
37821
|
-
* Drain the OLA accumulator's remaining stable samples
|
|
37822
|
-
* (`BLOCK_LEN - BLOCK_SHIFT = 384` samples). After this, the stream's state
|
|
37823
|
-
* is exhausted but no new inference has run.
|
|
37824
|
-
*/
|
|
37825
37868
|
flush() {
|
|
37826
37869
|
const remaining = BLOCK_LEN - BLOCK_SHIFT;
|
|
37827
37870
|
const out = new Float32Array(remaining);
|
|
@@ -37872,15 +37915,15 @@ var schema25 = external_exports.object({
|
|
|
37872
37915
|
fftwAddonPath: external_exports.string().default("").meta({ input: "file", mode: "open", binary: "fftw-addon", download: "https://github.com/visionsofparadise/fftw-addon" }).describe("FFTW native addon \u2014 CPU FFT acceleration")
|
|
37873
37916
|
});
|
|
37874
37917
|
var DTLN_SAMPLE_RATE = 16e3;
|
|
37875
|
-
var
|
|
37918
|
+
var CHUNK_FRAMES5 = 16e3;
|
|
37876
37919
|
var RESAMPLE_DRAIN_CHUNK = 16384;
|
|
37877
37920
|
var STEP_BATCH_SIZE = 16e3;
|
|
37878
37921
|
var WARMUP_SAMPLES = WARMUP_SHIFTS * BLOCK_SHIFT;
|
|
37879
37922
|
var DtlnStream = class extends BufferedTransformStream {
|
|
37880
37923
|
async _setup(input, context) {
|
|
37881
37924
|
const onnxProviders = filterOnnxProviders(context.executionProviders);
|
|
37882
|
-
this.session1 = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath1, { executionProviders: onnxProviders });
|
|
37883
|
-
this.session2 = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath2, { executionProviders: onnxProviders });
|
|
37925
|
+
this.session1 = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath1, { executionProviders: onnxProviders }, (message, data) => this.log(message, data));
|
|
37926
|
+
this.session2 = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath2, { executionProviders: onnxProviders }, (message, data) => this.log(message, data));
|
|
37884
37927
|
const cpuProviders = context.executionProviders.filter((ep) => ep !== "gpu");
|
|
37885
37928
|
const fft2 = initFftBackend(cpuProviders.length > 0 ? cpuProviders : ["cpu"], this.properties);
|
|
37886
37929
|
this.fftBackend = fft2.backend;
|
|
@@ -37924,11 +37967,11 @@ var DtlnStream = class extends BufferedTransformStream {
|
|
|
37924
37967
|
await buffer.clear();
|
|
37925
37968
|
await output.reset();
|
|
37926
37969
|
for (; ; ) {
|
|
37927
|
-
const chunk = await output.read(
|
|
37970
|
+
const chunk = await output.read(CHUNK_FRAMES5);
|
|
37928
37971
|
const got = chunk.samples[0]?.length ?? 0;
|
|
37929
37972
|
if (got === 0) break;
|
|
37930
37973
|
await buffer.write(chunk.samples, sourceRate, bitDepth);
|
|
37931
|
-
if (got <
|
|
37974
|
+
if (got < CHUNK_FRAMES5) break;
|
|
37932
37975
|
}
|
|
37933
37976
|
} finally {
|
|
37934
37977
|
if (pair) {
|
|
@@ -37952,10 +37995,10 @@ var DtlnStream = class extends BufferedTransformStream {
|
|
|
37952
37995
|
let samplesFed = 0;
|
|
37953
37996
|
let warmupRemaining = WARMUP_SAMPLES;
|
|
37954
37997
|
const writerState = { written: 0 };
|
|
37955
|
-
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames:
|
|
37998
|
+
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames: CHUNK_FRAMES5 }) : Promise.resolve();
|
|
37956
37999
|
const drainerDone = pair !== void 0 ? drainResampleOutToBuffer({ resampleOut: pair.resampleOut, output, channels, sourceRate, bitDepth, originalFrames, writerState }) : Promise.resolve();
|
|
37957
38000
|
for (; ; ) {
|
|
37958
|
-
const got16k = await pullNextChunkAt16k({ buffer, pair, channels, frames:
|
|
38001
|
+
const got16k = await pullNextChunkAt16k({ buffer, pair, channels, frames: CHUNK_FRAMES5 });
|
|
37959
38002
|
if (got16k === void 0) break;
|
|
37960
38003
|
const firstChannel = got16k[0];
|
|
37961
38004
|
const chunkFrames = firstChannel?.length ?? 0;
|
|
@@ -38168,10 +38211,10 @@ var _DtlnNode = class _DtlnNode extends TransformNode {
|
|
|
38168
38211
|
return new _DtlnNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
38169
38212
|
}
|
|
38170
38213
|
};
|
|
38171
|
-
_DtlnNode.
|
|
38214
|
+
_DtlnNode.nodeName = "DTLN (Denoiser)";
|
|
38172
38215
|
_DtlnNode.packageName = PACKAGE_NAME;
|
|
38173
38216
|
_DtlnNode.packageVersion = PACKAGE_VERSION;
|
|
38174
|
-
_DtlnNode.
|
|
38217
|
+
_DtlnNode.nodeDescription = "Remove background noise from speech using DTLN neural network";
|
|
38175
38218
|
_DtlnNode.schema = schema25;
|
|
38176
38219
|
var DtlnNode = _DtlnNode;
|
|
38177
38220
|
function dtln(options) {
|
|
@@ -38315,12 +38358,12 @@ var HOP_SIZE2 = 1024;
|
|
|
38315
38358
|
var SEGMENT_SAMPLES = 343980;
|
|
38316
38359
|
var OVERLAP = 0.25;
|
|
38317
38360
|
var TRANSITION_POWER = 1;
|
|
38318
|
-
var
|
|
38361
|
+
var CHUNK_FRAMES6 = 44100;
|
|
38319
38362
|
var RESAMPLE_DRAIN_CHUNK2 = 16384;
|
|
38320
38363
|
var STEM_OUTPUTS = 4 * 2;
|
|
38321
38364
|
var HtdemucsStream = class extends BufferedTransformStream {
|
|
38322
38365
|
async _setup(input, context) {
|
|
38323
|
-
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: ["cpu"] });
|
|
38366
|
+
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: ["cpu"] }, (message, data) => this.log(message, data));
|
|
38324
38367
|
return super._setup(input, context);
|
|
38325
38368
|
}
|
|
38326
38369
|
async _process(buffer) {
|
|
@@ -38362,11 +38405,11 @@ var HtdemucsStream = class extends BufferedTransformStream {
|
|
|
38362
38405
|
await buffer.clear();
|
|
38363
38406
|
await output.reset();
|
|
38364
38407
|
for (; ; ) {
|
|
38365
|
-
const chunk = await output.read(
|
|
38408
|
+
const chunk = await output.read(CHUNK_FRAMES6);
|
|
38366
38409
|
const got = chunk.samples[0]?.length ?? 0;
|
|
38367
38410
|
if (got === 0) break;
|
|
38368
38411
|
await buffer.write(chunk.samples, sourceRate, bitDepth);
|
|
38369
|
-
if (got <
|
|
38412
|
+
if (got < CHUNK_FRAMES6) break;
|
|
38370
38413
|
}
|
|
38371
38414
|
} finally {
|
|
38372
38415
|
if (pair) {
|
|
@@ -38379,7 +38422,7 @@ var HtdemucsStream = class extends BufferedTransformStream {
|
|
|
38379
38422
|
const { buffer, output, channels, originalFrames, sourceRate, bitDepth, stats, pair } = args;
|
|
38380
38423
|
const stride = Math.round((1 - OVERLAP) * SEGMENT_SAMPLES);
|
|
38381
38424
|
const writerState = { written: 0 };
|
|
38382
|
-
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn2({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames:
|
|
38425
|
+
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn2({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames: CHUNK_FRAMES6 }) : Promise.resolve();
|
|
38383
38426
|
const drainerDone = pair !== void 0 ? drainResampleOutToBuffer2({ resampleOut: pair.resampleOut, output, channels, sourceRate, bitDepth, originalFrames, writerState }) : Promise.resolve();
|
|
38384
38427
|
const weight = new Float32Array(SEGMENT_SAMPLES);
|
|
38385
38428
|
const half = SEGMENT_SAMPLES / 2;
|
|
@@ -38425,7 +38468,7 @@ var HtdemucsStream = class extends BufferedTransformStream {
|
|
|
38425
38468
|
if (!inputExhausted) {
|
|
38426
38469
|
while (segFilled < SEGMENT_SAMPLES) {
|
|
38427
38470
|
const need = SEGMENT_SAMPLES - segFilled;
|
|
38428
|
-
const got = await pullNextChunkAt441({ buffer, pair, channels, frames: Math.min(need,
|
|
38471
|
+
const got = await pullNextChunkAt441({ buffer, pair, channels, frames: Math.min(need, CHUNK_FRAMES6) });
|
|
38429
38472
|
if (got === void 0 || got[0].length === 0) {
|
|
38430
38473
|
inputExhausted = true;
|
|
38431
38474
|
break;
|
|
@@ -38540,7 +38583,7 @@ async function computeStreamingStats(buffer, channels) {
|
|
|
38540
38583
|
let sum = 0;
|
|
38541
38584
|
let count = 0;
|
|
38542
38585
|
for (; ; ) {
|
|
38543
|
-
const chunk = await buffer.read(
|
|
38586
|
+
const chunk = await buffer.read(CHUNK_FRAMES6);
|
|
38544
38587
|
const frames = chunk.samples[0]?.length ?? 0;
|
|
38545
38588
|
if (frames === 0) break;
|
|
38546
38589
|
const left = chunk.samples[0];
|
|
@@ -38553,14 +38596,14 @@ async function computeStreamingStats(buffer, channels) {
|
|
|
38553
38596
|
for (let index = 0; index < frames; index++) sum += right[index] ?? 0;
|
|
38554
38597
|
count += frames;
|
|
38555
38598
|
}
|
|
38556
|
-
if (frames <
|
|
38599
|
+
if (frames < CHUNK_FRAMES6) break;
|
|
38557
38600
|
}
|
|
38558
38601
|
const mean = count > 0 ? sum / count : 0;
|
|
38559
38602
|
await buffer.reset();
|
|
38560
38603
|
let variance = 0;
|
|
38561
38604
|
let varCount = 0;
|
|
38562
38605
|
for (; ; ) {
|
|
38563
|
-
const chunk = await buffer.read(
|
|
38606
|
+
const chunk = await buffer.read(CHUNK_FRAMES6);
|
|
38564
38607
|
const frames = chunk.samples[0]?.length ?? 0;
|
|
38565
38608
|
if (frames === 0) break;
|
|
38566
38609
|
const left = chunk.samples[0];
|
|
@@ -38579,7 +38622,7 @@ async function computeStreamingStats(buffer, channels) {
|
|
|
38579
38622
|
}
|
|
38580
38623
|
varCount += frames;
|
|
38581
38624
|
}
|
|
38582
|
-
if (frames <
|
|
38625
|
+
if (frames < CHUNK_FRAMES6) break;
|
|
38583
38626
|
}
|
|
38584
38627
|
const std = varCount > 0 ? Math.sqrt(variance / varCount) || 1 : 1;
|
|
38585
38628
|
return { mean, std };
|
|
@@ -38671,10 +38714,10 @@ var _HtdemucsNode = class _HtdemucsNode extends TransformNode {
|
|
|
38671
38714
|
return new _HtdemucsNode({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
38672
38715
|
}
|
|
38673
38716
|
};
|
|
38674
|
-
_HtdemucsNode.
|
|
38717
|
+
_HtdemucsNode.nodeName = "HTDemucs (Stem Separator)";
|
|
38675
38718
|
_HtdemucsNode.packageName = PACKAGE_NAME;
|
|
38676
38719
|
_HtdemucsNode.packageVersion = PACKAGE_VERSION;
|
|
38677
|
-
_HtdemucsNode.
|
|
38720
|
+
_HtdemucsNode.nodeDescription = "Rebalance stem volumes using HTDemucs source separation";
|
|
38678
38721
|
_HtdemucsNode.schema = schema26;
|
|
38679
38722
|
var HtdemucsNode = _HtdemucsNode;
|
|
38680
38723
|
function htdemucs(modelPath, stems, options) {
|
|
@@ -38830,7 +38873,7 @@ var COMPENSATE = 1.009;
|
|
|
38830
38873
|
var SEGMENT_SAMPLES2 = N_FFT2 + (DIM_T3 - 1) * HOP_SIZE4;
|
|
38831
38874
|
var OVERLAP2 = 0.25;
|
|
38832
38875
|
var TRANSITION_POWER2 = 1;
|
|
38833
|
-
var
|
|
38876
|
+
var CHUNK_FRAMES7 = 44100;
|
|
38834
38877
|
var RESAMPLE_DRAIN_CHUNK3 = 16384;
|
|
38835
38878
|
var KimVocal2Stream = class extends BufferedTransformStream {
|
|
38836
38879
|
constructor(properties) {
|
|
@@ -38838,7 +38881,7 @@ var KimVocal2Stream = class extends BufferedTransformStream {
|
|
|
38838
38881
|
this.fftInstance = new MixedRadixFft(N_FFT2);
|
|
38839
38882
|
}
|
|
38840
38883
|
async _setup(input, context) {
|
|
38841
|
-
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: filterOnnxProviders(context.executionProviders) });
|
|
38884
|
+
this.session = createOnnxSession(this.properties.onnxAddonPath, this.properties.modelPath, { executionProviders: filterOnnxProviders(context.executionProviders) }, (message, data) => this.log(message, data));
|
|
38842
38885
|
return super._setup(input, context);
|
|
38843
38886
|
}
|
|
38844
38887
|
async _process(buffer) {
|
|
@@ -38878,11 +38921,11 @@ var KimVocal2Stream = class extends BufferedTransformStream {
|
|
|
38878
38921
|
await buffer.clear();
|
|
38879
38922
|
await output.reset();
|
|
38880
38923
|
for (; ; ) {
|
|
38881
|
-
const chunk = await output.read(
|
|
38924
|
+
const chunk = await output.read(CHUNK_FRAMES7);
|
|
38882
38925
|
const got = chunk.samples[0]?.length ?? 0;
|
|
38883
38926
|
if (got === 0) break;
|
|
38884
38927
|
await buffer.write(chunk.samples, sourceRate, bitDepth);
|
|
38885
|
-
if (got <
|
|
38928
|
+
if (got < CHUNK_FRAMES7) break;
|
|
38886
38929
|
}
|
|
38887
38930
|
} finally {
|
|
38888
38931
|
if (pair) {
|
|
@@ -38896,7 +38939,7 @@ var KimVocal2Stream = class extends BufferedTransformStream {
|
|
|
38896
38939
|
const stride = Math.round((1 - OVERLAP2) * SEGMENT_SAMPLES2);
|
|
38897
38940
|
const isMono = channels < 2;
|
|
38898
38941
|
const writerState = { written: 0 };
|
|
38899
|
-
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn3({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames:
|
|
38942
|
+
const pumpDone = pair !== void 0 ? pumpSourceToResampleIn3({ buffer, resampleIn: pair.resampleIn, channels, chunkFrames: CHUNK_FRAMES7 }) : Promise.resolve();
|
|
38900
38943
|
const drainerDone = pair !== void 0 ? drainResampleOutToBuffer3({ resampleOut: pair.resampleOut, output, channels, sourceRate, bitDepth, originalFrames, writerState }) : Promise.resolve();
|
|
38901
38944
|
const weight = buildTransitionWindow(SEGMENT_SAMPLES2, TRANSITION_POWER2);
|
|
38902
38945
|
const workspace = createSegmentWorkspace(SEGMENT_SAMPLES2);
|
|
@@ -38911,7 +38954,7 @@ var KimVocal2Stream = class extends BufferedTransformStream {
|
|
|
38911
38954
|
if (!inputExhausted) {
|
|
38912
38955
|
while (segFilled < SEGMENT_SAMPLES2) {
|
|
38913
38956
|
const need = SEGMENT_SAMPLES2 - segFilled;
|
|
38914
|
-
const got = await pullNextChunkAt4412({ buffer, pair, channels, frames: Math.min(need,
|
|
38957
|
+
const got = await pullNextChunkAt4412({ buffer, pair, channels, frames: Math.min(need, CHUNK_FRAMES7) });
|
|
38915
38958
|
if (got === void 0 || got[0].length === 0) {
|
|
38916
38959
|
inputExhausted = true;
|
|
38917
38960
|
break;
|
|
@@ -39087,10 +39130,10 @@ var _KimVocal2Node = class _KimVocal2Node extends TransformNode {
|
|
|
39087
39130
|
return new _KimVocal2Node({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
39088
39131
|
}
|
|
39089
39132
|
};
|
|
39090
|
-
_KimVocal2Node.
|
|
39133
|
+
_KimVocal2Node.nodeName = "Kim Vocal 2 (Stem Separator)";
|
|
39091
39134
|
_KimVocal2Node.packageName = PACKAGE_NAME;
|
|
39092
39135
|
_KimVocal2Node.packageVersion = PACKAGE_VERSION;
|
|
39093
|
-
_KimVocal2Node.
|
|
39136
|
+
_KimVocal2Node.nodeDescription = "Isolate dialogue from background using MDX-Net vocal separation";
|
|
39094
39137
|
_KimVocal2Node.schema = schema27;
|
|
39095
39138
|
var KimVocal2Node = _KimVocal2Node;
|
|
39096
39139
|
function kimVocal2(options) {
|
|
@@ -39103,7 +39146,7 @@ function kimVocal2(options) {
|
|
|
39103
39146
|
id: options.id
|
|
39104
39147
|
});
|
|
39105
39148
|
}
|
|
39106
|
-
var
|
|
39149
|
+
var CHUNK_FRAMES8 = 48e3;
|
|
39107
39150
|
var READY_LINE = "READY\n";
|
|
39108
39151
|
var READY_TIMEOUT_MS = 3e5;
|
|
39109
39152
|
var VstHostExitedBeforeReadyError = class extends Error {
|
|
@@ -39216,7 +39259,7 @@ async function processStreamingThroughVstHost(handle, buffer, channelCount, samp
|
|
|
39216
39259
|
});
|
|
39217
39260
|
await buffer.reset();
|
|
39218
39261
|
for (; ; ) {
|
|
39219
|
-
const chunk = await buffer.read(
|
|
39262
|
+
const chunk = await buffer.read(CHUNK_FRAMES8);
|
|
39220
39263
|
const chunkFrames = chunk.samples[0]?.length ?? 0;
|
|
39221
39264
|
if (chunkFrames === 0) break;
|
|
39222
39265
|
const channelArrays = [];
|
|
@@ -39229,7 +39272,7 @@ async function processStreamingThroughVstHost(handle, buffer, channelCount, samp
|
|
|
39229
39272
|
if (!canWrite) {
|
|
39230
39273
|
await waitForDrain(handle.proc, handle.stdin);
|
|
39231
39274
|
}
|
|
39232
|
-
if (chunkFrames <
|
|
39275
|
+
if (chunkFrames < CHUNK_FRAMES8) break;
|
|
39233
39276
|
}
|
|
39234
39277
|
handle.stdin.end();
|
|
39235
39278
|
await buffer.reset();
|
|
@@ -39277,8 +39320,7 @@ async function processStreamingThroughVstHost(handle, buffer, channelCount, samp
|
|
|
39277
39320
|
var stageSchema = external_exports.object({
|
|
39278
39321
|
pluginPath: external_exports.string().meta({ input: "file", mode: "open", accept: ".vst3" }).describe("VST3 plugin file or bundle"),
|
|
39279
39322
|
pluginName: external_exports.string().optional().describe("Sub-plugin name when pluginPath is a multi-plugin shell (e.g. WaveShell)"),
|
|
39280
|
-
presetPath: external_exports.string().optional().meta({ input: "file", mode: "open", accept: ".vstpreset" }).describe("Optional .vstpreset state file applied after the plugin loads")
|
|
39281
|
-
parameters: external_exports.record(external_exports.string(), external_exports.union([external_exports.number(), external_exports.string(), external_exports.boolean()])).optional().describe("Optional parameter overrides applied after presetPath. Keys map to Pedalboard parameter names exposed by the plugin.")
|
|
39323
|
+
presetPath: external_exports.string().optional().meta({ input: "file", mode: "open", accept: ".vstpreset" }).describe("Optional .vstpreset state file applied after the plugin loads")
|
|
39282
39324
|
});
|
|
39283
39325
|
var schema28 = external_exports.object({
|
|
39284
39326
|
vstHostPath: external_exports.string().default("").meta({ input: "file", mode: "open", binary: "vst-host", download: "https://github.com/visionsofparadise/vst-host" }).describe("vst-host \u2014 Pedalboard-based VST3 host CLI"),
|
|
@@ -39315,7 +39357,7 @@ var Vst3Stream = class extends BufferedTransformStream {
|
|
|
39315
39357
|
];
|
|
39316
39358
|
const handle = await spawnVstHostReady(this.properties.vstHostPath, args, {
|
|
39317
39359
|
onRetry: (failedAttempt, error50) => {
|
|
39318
|
-
|
|
39360
|
+
this.log("vst-host init crash, retrying", { attempt: failedAttempt, error: error50.message }, "warn");
|
|
39319
39361
|
}
|
|
39320
39362
|
});
|
|
39321
39363
|
await processStreamingThroughVstHost(handle, buffer, channels, sampleRate, bd);
|
|
@@ -39351,10 +39393,10 @@ var _Vst3Node = class _Vst3Node extends TransformNode {
|
|
|
39351
39393
|
return new _Vst3Node({ ...this.properties, previousProperties: this.properties, ...overrides });
|
|
39352
39394
|
}
|
|
39353
39395
|
};
|
|
39354
|
-
_Vst3Node.
|
|
39396
|
+
_Vst3Node.nodeName = "VST3";
|
|
39355
39397
|
_Vst3Node.packageName = PACKAGE_NAME;
|
|
39356
39398
|
_Vst3Node.packageVersion = PACKAGE_VERSION;
|
|
39357
|
-
_Vst3Node.
|
|
39399
|
+
_Vst3Node.nodeDescription = "Host a chain of VST3 effect plugins via Pedalboard (whole-file offline mode)";
|
|
39358
39400
|
_Vst3Node.schema = schema28;
|
|
39359
39401
|
var Vst3Node = _Vst3Node;
|
|
39360
39402
|
function vst3(options) {
|