@storyteller-platform/ghost-story 0.0.1
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/LICENSE.md +611 -0
- package/README.md +18 -0
- package/dist/api/APIOptions.cjs +16 -0
- package/dist/api/APIOptions.d.cts +18 -0
- package/dist/api/APIOptions.d.ts +18 -0
- package/dist/api/APIOptions.js +0 -0
- package/dist/api/Recognition.cjs +263 -0
- package/dist/api/Recognition.d.cts +77 -0
- package/dist/api/Recognition.d.ts +77 -0
- package/dist/api/Recognition.js +233 -0
- package/dist/api/VoiceActivityDetection.cjs +77 -0
- package/dist/api/VoiceActivityDetection.d.cts +24 -0
- package/dist/api/VoiceActivityDetection.d.ts +24 -0
- package/dist/api/VoiceActivityDetection.js +43 -0
- package/dist/audio/AudioConverter.cjs +331 -0
- package/dist/audio/AudioConverter.d.cts +53 -0
- package/dist/audio/AudioConverter.d.ts +53 -0
- package/dist/audio/AudioConverter.js +310 -0
- package/dist/audio/AudioFormat.cjs +151 -0
- package/dist/audio/AudioFormat.d.cts +25 -0
- package/dist/audio/AudioFormat.d.ts +25 -0
- package/dist/audio/AudioFormat.js +123 -0
- package/dist/audio/AudioSource.cjs +119 -0
- package/dist/audio/AudioSource.d.cts +33 -0
- package/dist/audio/AudioSource.d.ts +33 -0
- package/dist/audio/AudioSource.js +88 -0
- package/dist/audio/index.cjs +74 -0
- package/dist/audio/index.d.cts +6 -0
- package/dist/audio/index.d.ts +6 -0
- package/dist/audio/index.js +54 -0
- package/dist/cli/bin.cjs +277 -0
- package/dist/cli/bin.d.cts +1 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +275 -0
- package/dist/cli/config.cjs +347 -0
- package/dist/cli/config.d.cts +33 -0
- package/dist/cli/config.d.ts +33 -0
- package/dist/cli/config.js +285 -0
- package/dist/cli/install.cjs +334 -0
- package/dist/cli/install.d.cts +62 -0
- package/dist/cli/install.d.ts +62 -0
- package/dist/cli/install.js +316 -0
- package/dist/cli/whisper-server.cjs +172 -0
- package/dist/cli/whisper-server.d.cts +24 -0
- package/dist/cli/whisper-server.d.ts +24 -0
- package/dist/cli/whisper-server.js +152 -0
- package/dist/config.cjs +60 -0
- package/dist/config.d.cts +12 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +32 -0
- package/dist/convert.cjs +88 -0
- package/dist/convert.d.cts +12 -0
- package/dist/convert.d.ts +12 -0
- package/dist/convert.js +63 -0
- package/dist/encodings/Ascii.cjs +75 -0
- package/dist/encodings/Ascii.d.cts +13 -0
- package/dist/encodings/Ascii.d.ts +13 -0
- package/dist/encodings/Ascii.js +48 -0
- package/dist/encodings/Base64.cjs +155 -0
- package/dist/encodings/Base64.d.cts +5 -0
- package/dist/encodings/Base64.d.ts +5 -0
- package/dist/encodings/Base64.js +129 -0
- package/dist/encodings/TextEncodingsCommon.cjs +16 -0
- package/dist/encodings/TextEncodingsCommon.d.cts +6 -0
- package/dist/encodings/TextEncodingsCommon.d.ts +6 -0
- package/dist/encodings/TextEncodingsCommon.js +0 -0
- package/dist/index.cjs +153 -0
- package/dist/index.d.cts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +140 -0
- package/dist/recognition/AmazonTranscribeSTT.cjs +188 -0
- package/dist/recognition/AmazonTranscribeSTT.d.cts +21 -0
- package/dist/recognition/AmazonTranscribeSTT.d.ts +21 -0
- package/dist/recognition/AmazonTranscribeSTT.js +160 -0
- package/dist/recognition/AzureCognitiveServicesSTT.cjs +124 -0
- package/dist/recognition/AzureCognitiveServicesSTT.d.cts +21 -0
- package/dist/recognition/AzureCognitiveServicesSTT.d.ts +21 -0
- package/dist/recognition/AzureCognitiveServicesSTT.js +95 -0
- package/dist/recognition/DeepgramSTT.cjs +172 -0
- package/dist/recognition/DeepgramSTT.d.cts +23 -0
- package/dist/recognition/DeepgramSTT.d.ts +23 -0
- package/dist/recognition/DeepgramSTT.js +153 -0
- package/dist/recognition/GoogleCloudSTT.cjs +125 -0
- package/dist/recognition/GoogleCloudSTT.d.cts +35 -0
- package/dist/recognition/GoogleCloudSTT.d.ts +35 -0
- package/dist/recognition/GoogleCloudSTT.js +107 -0
- package/dist/recognition/OpenAICloudSTT.cjs +180 -0
- package/dist/recognition/OpenAICloudSTT.d.cts +29 -0
- package/dist/recognition/OpenAICloudSTT.d.ts +29 -0
- package/dist/recognition/OpenAICloudSTT.js +150 -0
- package/dist/recognition/WhisperCppSTT.cjs +296 -0
- package/dist/recognition/WhisperCppSTT.d.cts +40 -0
- package/dist/recognition/WhisperCppSTT.d.ts +40 -0
- package/dist/recognition/WhisperCppSTT.js +275 -0
- package/dist/recognition/WhisperServerSTT.cjs +119 -0
- package/dist/recognition/WhisperServerSTT.d.cts +24 -0
- package/dist/recognition/WhisperServerSTT.d.ts +24 -0
- package/dist/recognition/WhisperServerSTT.js +105 -0
- package/dist/utilities/FileSystem.cjs +54 -0
- package/dist/utilities/FileSystem.d.cts +3 -0
- package/dist/utilities/FileSystem.d.ts +3 -0
- package/dist/utilities/FileSystem.js +20 -0
- package/dist/utilities/Locale.cjs +46 -0
- package/dist/utilities/Locale.d.cts +9 -0
- package/dist/utilities/Locale.d.ts +9 -0
- package/dist/utilities/Locale.js +20 -0
- package/dist/utilities/ObjectUtilities.cjs +41 -0
- package/dist/utilities/ObjectUtilities.d.cts +3 -0
- package/dist/utilities/ObjectUtilities.d.ts +3 -0
- package/dist/utilities/ObjectUtilities.js +7 -0
- package/dist/utilities/Timeline.cjs +120 -0
- package/dist/utilities/Timeline.d.cts +23 -0
- package/dist/utilities/Timeline.d.ts +23 -0
- package/dist/utilities/Timeline.js +94 -0
- package/dist/utilities/Timing.cjs +287 -0
- package/dist/utilities/Timing.d.cts +64 -0
- package/dist/utilities/Timing.d.ts +64 -0
- package/dist/utilities/Timing.js +256 -0
- package/dist/utilities/WhisperTimeline.cjs +344 -0
- package/dist/utilities/WhisperTimeline.d.cts +86 -0
- package/dist/utilities/WhisperTimeline.d.ts +86 -0
- package/dist/utilities/WhisperTimeline.js +313 -0
- package/dist/vad/ActiveGate.cjs +357 -0
- package/dist/vad/ActiveGate.d.cts +53 -0
- package/dist/vad/ActiveGate.d.ts +53 -0
- package/dist/vad/ActiveGate.js +329 -0
- package/dist/vad/ActiveGateOg.cjs +1366 -0
- package/dist/vad/ActiveGateOg.d.cts +33 -0
- package/dist/vad/ActiveGateOg.d.ts +33 -0
- package/dist/vad/ActiveGateOg.js +1341 -0
- package/dist/vad/Silero.cjs +174 -0
- package/dist/vad/Silero.d.cts +25 -0
- package/dist/vad/Silero.d.ts +25 -0
- package/dist/vad/Silero.js +153 -0
- package/package.json +125 -0
|
@@ -0,0 +1,1366 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var ActiveGateOg_exports = {};
|
|
20
|
+
__export(ActiveGateOg_exports, {
|
|
21
|
+
createDynamicUint16Array: () => createDynamicUint16Array,
|
|
22
|
+
detectVoiceActivity: () => detectVoiceActivity
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(ActiveGateOg_exports);
|
|
25
|
+
var import_promises = require("fs/promises");
|
|
26
|
+
var import_convert = require("../convert.cjs");
|
|
27
|
+
var import_Ascii = require("../encodings/Ascii.cjs");
|
|
28
|
+
var import_ObjectUtilities = require("../utilities/ObjectUtilities.cjs");
|
|
29
|
+
async function detectVoiceActivity(filepath, options) {
|
|
30
|
+
var _a;
|
|
31
|
+
const { rawAudio } = await ensureRawAudio(filepath);
|
|
32
|
+
const channelCount = rawAudio.audioChannels.length;
|
|
33
|
+
const firstChannel = rawAudio.audioChannels[0];
|
|
34
|
+
if (!firstChannel) {
|
|
35
|
+
throw new Error("no audio channels found");
|
|
36
|
+
}
|
|
37
|
+
const sampleCount = firstChannel.length;
|
|
38
|
+
const sampleRate = rawAudio.sampleRate;
|
|
39
|
+
const audioDuration = getRawAudioDuration(rawAudio);
|
|
40
|
+
options = (0, import_ObjectUtilities.extendDeep)(defaultAdaptiveGateOptions, options);
|
|
41
|
+
const gateVAD = new AdaptiveGateVAD(sampleRate, channelCount, options);
|
|
42
|
+
const frameDuration = 0.01;
|
|
43
|
+
const frameRecords = [];
|
|
44
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
|
|
45
|
+
const timePosition = sampleIndex / sampleRate;
|
|
46
|
+
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
|
|
47
|
+
const channel = rawAudio.audioChannels[channelIndex];
|
|
48
|
+
if (!channel) {
|
|
49
|
+
throw new Error(`channel ${channelIndex} not found`);
|
|
50
|
+
}
|
|
51
|
+
const sample = channel[sampleIndex];
|
|
52
|
+
if (sample === void 0) {
|
|
53
|
+
throw new Error(`sample at index ${sampleIndex} not found`);
|
|
54
|
+
}
|
|
55
|
+
gateVAD.process(sample, channelIndex);
|
|
56
|
+
}
|
|
57
|
+
const lastRecord = frameRecords[frameRecords.length - 1];
|
|
58
|
+
if (frameRecords.length == 0 || lastRecord && timePosition > lastRecord.timePosition + frameDuration) {
|
|
59
|
+
const record = {
|
|
60
|
+
timePosition,
|
|
61
|
+
loudness: gateVAD.loudnessEstimator.currentLoudness,
|
|
62
|
+
minimumLoudness: gateVAD.minimumLoudnessEstimator.currentPeak,
|
|
63
|
+
maximumLoudness: gateVAD.maximumLoudnessEstimator.currentPeak
|
|
64
|
+
};
|
|
65
|
+
frameRecords.push(record);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const frameActive = [];
|
|
69
|
+
for (let i = 0; i < frameRecords.length; i++) {
|
|
70
|
+
frameActive[i] = false;
|
|
71
|
+
}
|
|
72
|
+
{
|
|
73
|
+
const backwardExtensionFrameCount = Math.floor(
|
|
74
|
+
options.backwardExtensionDuration / frameDuration
|
|
75
|
+
);
|
|
76
|
+
const relativeThreshold = options.relativeThreshold;
|
|
77
|
+
let extendedActivityStartIndex = frameRecords.length;
|
|
78
|
+
for (let i = frameRecords.length - 1; i >= 0; i--) {
|
|
79
|
+
const record = frameRecords[i];
|
|
80
|
+
if (!record) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const referenceLoudness = Math.max(record.maximumLoudness, -30);
|
|
84
|
+
let isActive = false;
|
|
85
|
+
if (i >= extendedActivityStartIndex) {
|
|
86
|
+
isActive = true;
|
|
87
|
+
}
|
|
88
|
+
if (record.loudness >= referenceLoudness + relativeThreshold) {
|
|
89
|
+
isActive = true;
|
|
90
|
+
extendedActivityStartIndex = Math.max(
|
|
91
|
+
i - backwardExtensionFrameCount,
|
|
92
|
+
0
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
frameActive[i] = isActive;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const timeline = [];
|
|
99
|
+
for (let i = 0; i < frameRecords.length; i++) {
|
|
100
|
+
const record = frameRecords[i];
|
|
101
|
+
if (!record) {
|
|
102
|
+
console.warn("[ghost-story] No record found for frame", i);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const isActive = frameActive[i];
|
|
106
|
+
const activityText = isActive ? "active" : "inactive";
|
|
107
|
+
const startTime = record.timePosition;
|
|
108
|
+
const endTime = Math.min(startTime + frameDuration, audioDuration);
|
|
109
|
+
if (timeline.length == 0 || ((_a = timeline.at(-1)) == null ? void 0 : _a.text) != activityText) {
|
|
110
|
+
timeline.push({
|
|
111
|
+
type: "segment",
|
|
112
|
+
text: activityText,
|
|
113
|
+
startTime,
|
|
114
|
+
endTime
|
|
115
|
+
});
|
|
116
|
+
} else {
|
|
117
|
+
timeline.at(-1).endTime = endTime;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return timeline;
|
|
121
|
+
}
|
|
122
|
+
function getRawAudioDuration(rawAudio) {
|
|
123
|
+
if (rawAudio.audioChannels.length == 0 || rawAudio.sampleRate == 0 || !rawAudio.audioChannels[0]) {
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
return rawAudio.audioChannels[0].length / rawAudio.sampleRate;
|
|
127
|
+
}
|
|
128
|
+
async function ensureRawAudio(input) {
|
|
129
|
+
const inputAsRawAudio = await decodeToChannels(input);
|
|
130
|
+
return inputAsRawAudio;
|
|
131
|
+
}
|
|
132
|
+
async function decodeToChannels(input) {
|
|
133
|
+
const outputPath = await (0, import_convert.fileToWav)(input);
|
|
134
|
+
const waveAudio = await readFileAsBinary(outputPath);
|
|
135
|
+
return decodeWaveToRawAudio(waveAudio);
|
|
136
|
+
}
|
|
137
|
+
function decodeWaveToRawAudio(waveFileBuffer, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
|
|
138
|
+
const rawAudio = decodeWaveToFloat32Channels(
|
|
139
|
+
waveFileBuffer,
|
|
140
|
+
ignoreTruncatedChunks,
|
|
141
|
+
ignoreOverflowingDataChunks
|
|
142
|
+
);
|
|
143
|
+
return { rawAudio };
|
|
144
|
+
}
|
|
145
|
+
class AdaptiveGateVAD {
|
|
146
|
+
constructor(sampleRate, channelCount, options) {
|
|
147
|
+
this.sampleRate = sampleRate;
|
|
148
|
+
this.channelCount = channelCount;
|
|
149
|
+
this.options = options;
|
|
150
|
+
this.channelHighpassFilters = [];
|
|
151
|
+
this.channelLowpassFilters = [];
|
|
152
|
+
for (let i = 0; i < this.channelCount; i++) {
|
|
153
|
+
this.channelHighpassFilters.push(
|
|
154
|
+
createHighpassFilter(this.sampleRate, options.lowCutoff)
|
|
155
|
+
);
|
|
156
|
+
this.channelLowpassFilters.push(
|
|
157
|
+
createLowpassFilter(this.sampleRate, options.highCutoff)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
this.loudnessEstimator = new LoudnessEstimator({
|
|
161
|
+
sampleRate: this.sampleRate,
|
|
162
|
+
channelCount: this.channelCount,
|
|
163
|
+
positiveAdaptationRate: options.positiveAdaptationRate,
|
|
164
|
+
negativeAdaptationRate: options.negativeAdaptationRate,
|
|
165
|
+
initialEstimate: -60,
|
|
166
|
+
minimumLoudness: -60,
|
|
167
|
+
applyKWeighting: false
|
|
168
|
+
});
|
|
169
|
+
const ticksPerSecond = this.sampleRate * this.channelCount;
|
|
170
|
+
this.minimumLoudnessEstimator = new DecayingPeakEstimator(
|
|
171
|
+
{
|
|
172
|
+
kind: "minimum",
|
|
173
|
+
decayPerSecond: options.peakLoudnessDecay,
|
|
174
|
+
initialPeak: -60
|
|
175
|
+
},
|
|
176
|
+
ticksPerSecond
|
|
177
|
+
);
|
|
178
|
+
this.maximumLoudnessEstimator = new DecayingPeakEstimator(
|
|
179
|
+
{
|
|
180
|
+
kind: "maximum",
|
|
181
|
+
decayPerSecond: options.peakLoudnessDecay,
|
|
182
|
+
initialPeak: -60
|
|
183
|
+
},
|
|
184
|
+
ticksPerSecond
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
channelHighpassFilters;
|
|
188
|
+
channelLowpassFilters;
|
|
189
|
+
loudnessEstimator;
|
|
190
|
+
minimumLoudnessEstimator;
|
|
191
|
+
maximumLoudnessEstimator;
|
|
192
|
+
process(sample, channelIndex) {
|
|
193
|
+
const highpassFilter = this.channelHighpassFilters[channelIndex];
|
|
194
|
+
const lowpassFilter = this.channelLowpassFilters[channelIndex];
|
|
195
|
+
if (!highpassFilter || !lowpassFilter) {
|
|
196
|
+
throw new Error(`filters for channel ${channelIndex} not found`);
|
|
197
|
+
}
|
|
198
|
+
sample = highpassFilter.filter(sample);
|
|
199
|
+
sample = lowpassFilter.filter(sample);
|
|
200
|
+
this.loudnessEstimator.process(sample, channelIndex);
|
|
201
|
+
const currentLoudness = this.loudnessEstimator.currentLoudness;
|
|
202
|
+
this.minimumLoudnessEstimator.process(currentLoudness);
|
|
203
|
+
if (currentLoudness >= -60) {
|
|
204
|
+
this.maximumLoudnessEstimator.process(currentLoudness);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const defaultAdaptiveGateOptions = {
|
|
209
|
+
lowCutoff: 100,
|
|
210
|
+
highCutoff: 1e3,
|
|
211
|
+
positiveAdaptationRate: 400,
|
|
212
|
+
negativeAdaptationRate: 10,
|
|
213
|
+
peakLoudnessDecay: 4,
|
|
214
|
+
backwardExtensionDuration: 0.2,
|
|
215
|
+
relativeThreshold: -15
|
|
216
|
+
};
|
|
217
|
+
class LoudnessEstimator {
|
|
218
|
+
constructor(options) {
|
|
219
|
+
this.options = options;
|
|
220
|
+
const initialMeanSquares = decibelsToGainFactor(options.initialEstimate) ** 2;
|
|
221
|
+
const ticksPerSecond = this.options.sampleRate * this.options.channelCount;
|
|
222
|
+
for (let i = 0; i < options.channelCount; i++) {
|
|
223
|
+
const weightingFilter = new KWeightingFilter(options.sampleRate);
|
|
224
|
+
const channelMeanSquares = new SmoothEstimator(
|
|
225
|
+
options.positiveAdaptationRate / ticksPerSecond,
|
|
226
|
+
options.negativeAdaptationRate / ticksPerSecond,
|
|
227
|
+
initialMeanSquares
|
|
228
|
+
);
|
|
229
|
+
this.channelFilters.push(weightingFilter);
|
|
230
|
+
this.channelMeanSquares.push(channelMeanSquares);
|
|
231
|
+
}
|
|
232
|
+
this.minPower = decibelsToGainFactor(options.minimumLoudness) ** 2;
|
|
233
|
+
}
|
|
234
|
+
channelFilters = [];
|
|
235
|
+
channelMeanSquares = [];
|
|
236
|
+
minPower;
|
|
237
|
+
process(sample, channel) {
|
|
238
|
+
let filteredSample;
|
|
239
|
+
if (this.options.applyKWeighting) {
|
|
240
|
+
const filter = this.channelFilters[channel];
|
|
241
|
+
if (!filter) {
|
|
242
|
+
throw new Error(`channel filter ${channel} not found`);
|
|
243
|
+
}
|
|
244
|
+
filteredSample = filter.process(sample);
|
|
245
|
+
} else {
|
|
246
|
+
filteredSample = sample;
|
|
247
|
+
}
|
|
248
|
+
const filteredSampleSquared = filteredSample ** 2;
|
|
249
|
+
const channelMeanSquares = this.channelMeanSquares[channel];
|
|
250
|
+
if (!channelMeanSquares) {
|
|
251
|
+
throw new Error(`channel mean squares ${channel} not found`);
|
|
252
|
+
}
|
|
253
|
+
channelMeanSquares.update(filteredSampleSquared);
|
|
254
|
+
channelMeanSquares.estimate = Math.max(
|
|
255
|
+
channelMeanSquares.estimate,
|
|
256
|
+
this.minPower
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
get currentLoudness() {
|
|
260
|
+
let totalMeanSquares = 0;
|
|
261
|
+
for (let i = 0; i < this.options.channelCount; i++) {
|
|
262
|
+
const channelMeanSquare = this.channelMeanSquares[i];
|
|
263
|
+
if (!channelMeanSquare) {
|
|
264
|
+
throw new Error(`channel mean square ${i} not found`);
|
|
265
|
+
}
|
|
266
|
+
totalMeanSquares += channelMeanSquare.estimate;
|
|
267
|
+
}
|
|
268
|
+
const rms = Math.sqrt(totalMeanSquares / this.options.channelCount);
|
|
269
|
+
const rmsDecibels = gainFactorToDecibels(rms);
|
|
270
|
+
return rmsDecibels;
|
|
271
|
+
}
|
|
272
|
+
getCurrentRMSForChannel(channel) {
|
|
273
|
+
const channelMeanSquare = this.channelMeanSquares[channel];
|
|
274
|
+
if (!channelMeanSquare) {
|
|
275
|
+
throw new Error(`channel mean square ${channel} not found`);
|
|
276
|
+
}
|
|
277
|
+
return Math.sqrt(channelMeanSquare.estimate);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
class DecayingPeakEstimator {
|
|
281
|
+
constructor(options, ticksPerSecond) {
|
|
282
|
+
this.options = options;
|
|
283
|
+
this.ticksPerSecond = ticksPerSecond;
|
|
284
|
+
this.currentPeak = options.initialPeak;
|
|
285
|
+
this.decayPerTick = this.options.decayPerSecond / this.ticksPerSecond;
|
|
286
|
+
this.process = options.kind === "maximum" ? this.processMaximum.bind(this) : this.processMinimum.bind(this);
|
|
287
|
+
}
|
|
288
|
+
decayPerTick;
|
|
289
|
+
currentPeak;
|
|
290
|
+
process;
|
|
291
|
+
processMaximum(value) {
|
|
292
|
+
this.currentPeak -= this.decayPerTick;
|
|
293
|
+
this.currentPeak = Math.max(value, this.currentPeak);
|
|
294
|
+
}
|
|
295
|
+
processMinimum(value) {
|
|
296
|
+
this.currentPeak += this.decayPerTick;
|
|
297
|
+
this.currentPeak = Math.min(value, this.currentPeak);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function createLowpassFilter(sampleRate, cutoffFrequency, q = 0.7071) {
|
|
301
|
+
return createFilter("lowpass", sampleRate, cutoffFrequency, q, 0);
|
|
302
|
+
}
|
|
303
|
+
function createHighpassFilter(sampleRate, cutoffFrequency, q = 0.7071) {
|
|
304
|
+
return createFilter("highpass", sampleRate, cutoffFrequency, q, 0);
|
|
305
|
+
}
|
|
306
|
+
function createHighshelfFilter(sampleRate, midpointFrequency, gain) {
|
|
307
|
+
return createFilter("highshelf", sampleRate, midpointFrequency, 0, gain);
|
|
308
|
+
}
|
|
309
|
+
function createFilter(filterType, sampleRate, frequency, q, gain) {
|
|
310
|
+
const coefficients = getFilterCoefficients(
|
|
311
|
+
filterType,
|
|
312
|
+
sampleRate,
|
|
313
|
+
frequency,
|
|
314
|
+
q,
|
|
315
|
+
gain
|
|
316
|
+
);
|
|
317
|
+
return new BiquadFilter(coefficients);
|
|
318
|
+
}
|
|
319
|
+
class BiquadFilter {
|
|
320
|
+
b0 = 0;
|
|
321
|
+
b1 = 0;
|
|
322
|
+
b2 = 0;
|
|
323
|
+
a1 = 0;
|
|
324
|
+
a2 = 0;
|
|
325
|
+
prevInput1 = 0;
|
|
326
|
+
prevInput2 = 0;
|
|
327
|
+
prevOutput1 = 0;
|
|
328
|
+
prevOutput2 = 0;
|
|
329
|
+
constructor(coefficients) {
|
|
330
|
+
this.setCoefficients(coefficients);
|
|
331
|
+
}
|
|
332
|
+
filter(sample) {
|
|
333
|
+
const filteredSample = this.b0 * sample + this.b1 * this.prevInput1 + this.b2 * this.prevInput2 - this.a1 * this.prevOutput1 - this.a2 * this.prevOutput2;
|
|
334
|
+
this.prevInput2 = this.prevInput1;
|
|
335
|
+
this.prevInput1 = sample;
|
|
336
|
+
this.prevOutput2 = this.prevOutput1;
|
|
337
|
+
this.prevOutput1 = filteredSample;
|
|
338
|
+
return filteredSample;
|
|
339
|
+
}
|
|
340
|
+
filterSamplesInPlace(samples) {
|
|
341
|
+
for (let i = 0; i < samples.length; i++) {
|
|
342
|
+
const sample = samples[i];
|
|
343
|
+
if (sample === void 0) {
|
|
344
|
+
throw new Error(`sample at index ${i} not found`);
|
|
345
|
+
}
|
|
346
|
+
samples[i] = this.filter(sample);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
reset() {
|
|
350
|
+
this.prevInput1 = 0;
|
|
351
|
+
this.prevInput2 = 0;
|
|
352
|
+
this.prevOutput1 = 0;
|
|
353
|
+
this.prevOutput2 = 0;
|
|
354
|
+
}
|
|
355
|
+
setCoefficients(coefficients) {
|
|
356
|
+
this.b0 = coefficients.b0;
|
|
357
|
+
this.b1 = coefficients.b1;
|
|
358
|
+
this.b2 = coefficients.b2;
|
|
359
|
+
this.a1 = coefficients.a1;
|
|
360
|
+
this.a2 = coefficients.a2;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function getFilterCoefficients(filterType, sampleRate, centerFrequency, q, gain) {
|
|
364
|
+
const nyquistFrequency = sampleRate / 2;
|
|
365
|
+
const freqRatio = clamp(centerFrequency / nyquistFrequency, 0, 1);
|
|
366
|
+
return filterCoefficientsFunction[filterType](freqRatio, q, gain);
|
|
367
|
+
}
|
|
368
|
+
function getLowpassFilterCoefficients(freqRatio, q, _gain) {
|
|
369
|
+
let b0;
|
|
370
|
+
let b1;
|
|
371
|
+
let b2;
|
|
372
|
+
let a0;
|
|
373
|
+
let a1;
|
|
374
|
+
let a2;
|
|
375
|
+
if (freqRatio == 1) {
|
|
376
|
+
b0 = 1;
|
|
377
|
+
b1 = 0;
|
|
378
|
+
b2 = 0;
|
|
379
|
+
a0 = 1;
|
|
380
|
+
a1 = 0;
|
|
381
|
+
a2 = 0;
|
|
382
|
+
} else {
|
|
383
|
+
const theta = Math.PI * freqRatio;
|
|
384
|
+
const alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20));
|
|
385
|
+
const cosw = Math.cos(theta);
|
|
386
|
+
const beta = (1 - cosw) / 2;
|
|
387
|
+
b0 = beta;
|
|
388
|
+
b1 = 2 * beta;
|
|
389
|
+
b2 = beta;
|
|
390
|
+
a0 = 1 + alpha;
|
|
391
|
+
a1 = -2 * cosw;
|
|
392
|
+
a2 = 1 - alpha;
|
|
393
|
+
}
|
|
394
|
+
return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
395
|
+
}
|
|
396
|
+
function getHighpassFilterCoefficients(freqRatio, q, _gain) {
|
|
397
|
+
let b0;
|
|
398
|
+
let b1;
|
|
399
|
+
let b2;
|
|
400
|
+
let a0;
|
|
401
|
+
let a1;
|
|
402
|
+
let a2;
|
|
403
|
+
if (freqRatio == 1) {
|
|
404
|
+
b0 = 0;
|
|
405
|
+
b1 = 0;
|
|
406
|
+
b2 = 0;
|
|
407
|
+
a0 = 1;
|
|
408
|
+
a1 = 0;
|
|
409
|
+
a2 = 0;
|
|
410
|
+
} else if (freqRatio == 0) {
|
|
411
|
+
b0 = 1;
|
|
412
|
+
b1 = 0;
|
|
413
|
+
b2 = 0;
|
|
414
|
+
a0 = 1;
|
|
415
|
+
a1 = 0;
|
|
416
|
+
a2 = 0;
|
|
417
|
+
} else {
|
|
418
|
+
const theta = Math.PI * freqRatio;
|
|
419
|
+
const alpha = Math.sin(theta) / (2 * Math.pow(10, q / 20));
|
|
420
|
+
const cosw = Math.cos(theta);
|
|
421
|
+
const beta = (1 + cosw) / 2;
|
|
422
|
+
b0 = beta;
|
|
423
|
+
b1 = -2 * beta;
|
|
424
|
+
b2 = beta;
|
|
425
|
+
a0 = 1 + alpha;
|
|
426
|
+
a1 = -2 * cosw;
|
|
427
|
+
a2 = 1 - alpha;
|
|
428
|
+
}
|
|
429
|
+
return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
430
|
+
}
|
|
431
|
+
function getBandpassFilterCoefficients(freqRatio, q, _gain = 0) {
|
|
432
|
+
let b0;
|
|
433
|
+
let b1;
|
|
434
|
+
let b2;
|
|
435
|
+
let a0;
|
|
436
|
+
let a1;
|
|
437
|
+
let a2;
|
|
438
|
+
let coefficients;
|
|
439
|
+
if (freqRatio > 0 && freqRatio < 1) {
|
|
440
|
+
const w0 = Math.PI * freqRatio;
|
|
441
|
+
if (q > 0) {
|
|
442
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
443
|
+
const k = Math.cos(w0);
|
|
444
|
+
b0 = alpha;
|
|
445
|
+
b1 = 0;
|
|
446
|
+
b2 = -alpha;
|
|
447
|
+
a0 = 1 + alpha;
|
|
448
|
+
a1 = -2 * k;
|
|
449
|
+
a2 = 1 - alpha;
|
|
450
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
451
|
+
} else {
|
|
452
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
coefficients = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
456
|
+
}
|
|
457
|
+
return coefficients;
|
|
458
|
+
}
|
|
459
|
+
function getLowShelfFilterCoefficients(freqRatio, _q = 0, gain) {
|
|
460
|
+
let b0;
|
|
461
|
+
let b1;
|
|
462
|
+
let b2;
|
|
463
|
+
let a0;
|
|
464
|
+
let a1;
|
|
465
|
+
let a2;
|
|
466
|
+
let coefficients;
|
|
467
|
+
const S = 1;
|
|
468
|
+
const A = Math.pow(10, gain / 40);
|
|
469
|
+
if (freqRatio == 1) {
|
|
470
|
+
coefficients = { b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
471
|
+
} else if (freqRatio == 0) {
|
|
472
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
473
|
+
} else {
|
|
474
|
+
const w0 = Math.PI * freqRatio;
|
|
475
|
+
const alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
|
|
476
|
+
const k = Math.cos(w0);
|
|
477
|
+
const k2 = 2 * Math.sqrt(A) * alpha;
|
|
478
|
+
const Ap1 = A + 1;
|
|
479
|
+
const Am1 = A - 1;
|
|
480
|
+
b0 = A * (Ap1 - Am1 * k + k2);
|
|
481
|
+
b1 = 2 * A * (Am1 - Ap1 * k);
|
|
482
|
+
b2 = A * (Ap1 - Am1 * k - k2);
|
|
483
|
+
a0 = Ap1 + Am1 * k + k2;
|
|
484
|
+
a1 = -2 * (Am1 + Ap1 * k);
|
|
485
|
+
a2 = Ap1 + Am1 * k - k2;
|
|
486
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
487
|
+
}
|
|
488
|
+
return coefficients;
|
|
489
|
+
}
|
|
490
|
+
function getHighShelfFilterCoefficients(freqRatio, _q = 0, gain) {
|
|
491
|
+
let b0;
|
|
492
|
+
let b1;
|
|
493
|
+
let b2;
|
|
494
|
+
let a0;
|
|
495
|
+
let a1;
|
|
496
|
+
let a2;
|
|
497
|
+
let coefficients;
|
|
498
|
+
const A = Math.pow(10, gain / 40);
|
|
499
|
+
if (freqRatio == 1) {
|
|
500
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
501
|
+
} else if (freqRatio > 0) {
|
|
502
|
+
const w0 = Math.PI * freqRatio;
|
|
503
|
+
const S = 1;
|
|
504
|
+
const alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
|
|
505
|
+
const k = Math.cos(w0);
|
|
506
|
+
const k2 = 2 * Math.sqrt(A) * alpha;
|
|
507
|
+
const Ap1 = A + 1;
|
|
508
|
+
const Am1 = A - 1;
|
|
509
|
+
b0 = A * (Ap1 + Am1 * k + k2);
|
|
510
|
+
b1 = -2 * A * (Am1 + Ap1 * k);
|
|
511
|
+
b2 = A * (Ap1 + Am1 * k - k2);
|
|
512
|
+
a0 = Ap1 - Am1 * k + k2;
|
|
513
|
+
a1 = 2 * (Am1 - Ap1 * k);
|
|
514
|
+
a2 = Ap1 - Am1 * k - k2;
|
|
515
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
516
|
+
} else {
|
|
517
|
+
coefficients = { b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
518
|
+
}
|
|
519
|
+
return coefficients;
|
|
520
|
+
}
|
|
521
|
+
function getPeakingFilterCoefficients(freqRatio, q, gain) {
|
|
522
|
+
let b0;
|
|
523
|
+
let b1;
|
|
524
|
+
let b2;
|
|
525
|
+
let a0;
|
|
526
|
+
let a1;
|
|
527
|
+
let a2;
|
|
528
|
+
let coefficients;
|
|
529
|
+
const A = Math.pow(10, gain / 40);
|
|
530
|
+
if (freqRatio > 0 && freqRatio < 1) {
|
|
531
|
+
if (q > 0) {
|
|
532
|
+
const w0 = Math.PI * freqRatio;
|
|
533
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
534
|
+
const k = Math.cos(w0);
|
|
535
|
+
b0 = 1 + alpha * A;
|
|
536
|
+
b1 = -2 * k;
|
|
537
|
+
b2 = 1 - alpha * A;
|
|
538
|
+
a0 = 1 + alpha / A;
|
|
539
|
+
a1 = -2 * k;
|
|
540
|
+
a2 = 1 - alpha / A;
|
|
541
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
542
|
+
} else {
|
|
543
|
+
coefficients = { b0: A * A, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
547
|
+
}
|
|
548
|
+
return coefficients;
|
|
549
|
+
}
|
|
550
|
+
function getNotchFilterCoefficients(freqRatio, q, _gain) {
|
|
551
|
+
let b0;
|
|
552
|
+
let b1;
|
|
553
|
+
let b2;
|
|
554
|
+
let a0;
|
|
555
|
+
let a1;
|
|
556
|
+
let a2;
|
|
557
|
+
let coefficients;
|
|
558
|
+
if (freqRatio > 0 && freqRatio < 1) {
|
|
559
|
+
if (q > 0) {
|
|
560
|
+
const w0 = Math.PI * freqRatio;
|
|
561
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
562
|
+
const k = Math.cos(w0);
|
|
563
|
+
b0 = 1;
|
|
564
|
+
b1 = -2 * k;
|
|
565
|
+
b2 = 1;
|
|
566
|
+
a0 = 1 + alpha;
|
|
567
|
+
a1 = -2 * k;
|
|
568
|
+
a2 = 1 - alpha;
|
|
569
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
570
|
+
} else {
|
|
571
|
+
coefficients = { b0: 0, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
572
|
+
}
|
|
573
|
+
} else {
|
|
574
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
575
|
+
}
|
|
576
|
+
return coefficients;
|
|
577
|
+
}
|
|
578
|
+
function getAllpassFilterCoefficients(freqRatio, q, _gain) {
|
|
579
|
+
let b0;
|
|
580
|
+
let b1;
|
|
581
|
+
let b2;
|
|
582
|
+
let a0;
|
|
583
|
+
let a1;
|
|
584
|
+
let a2;
|
|
585
|
+
let coefficients;
|
|
586
|
+
if (freqRatio > 0 && freqRatio < 1) {
|
|
587
|
+
if (q > 0) {
|
|
588
|
+
const w0 = Math.PI * freqRatio;
|
|
589
|
+
const alpha = Math.sin(w0) / (2 * q);
|
|
590
|
+
const k = Math.cos(w0);
|
|
591
|
+
b0 = 1 - alpha;
|
|
592
|
+
b1 = -2 * k;
|
|
593
|
+
b2 = 1 + alpha;
|
|
594
|
+
a0 = 1 + alpha;
|
|
595
|
+
a1 = -2 * k;
|
|
596
|
+
a2 = 1 - alpha;
|
|
597
|
+
coefficients = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
|
|
598
|
+
} else {
|
|
599
|
+
coefficients = { b0: -1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
coefficients = { b0: 1, b1: 0, b2: 0, a1: 0, a2: 0 };
|
|
603
|
+
}
|
|
604
|
+
return coefficients;
|
|
605
|
+
}
|
|
606
|
+
function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) {
|
|
607
|
+
const scale = 1 / a0;
|
|
608
|
+
return {
|
|
609
|
+
b0: b0 * scale,
|
|
610
|
+
b1: b1 * scale,
|
|
611
|
+
b2: b2 * scale,
|
|
612
|
+
a1: a1 * scale,
|
|
613
|
+
a2: a2 * scale
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function clamp(num, min, max) {
|
|
617
|
+
return Math.max(min, Math.min(max, num));
|
|
618
|
+
}
|
|
619
|
+
const filterCoefficientsFunction = {
|
|
620
|
+
lowpass: getLowpassFilterCoefficients,
|
|
621
|
+
highpass: getHighpassFilterCoefficients,
|
|
622
|
+
bandpass: getBandpassFilterCoefficients,
|
|
623
|
+
lowshelf: getLowShelfFilterCoefficients,
|
|
624
|
+
highshelf: getHighShelfFilterCoefficients,
|
|
625
|
+
peaking: getPeakingFilterCoefficients,
|
|
626
|
+
notch: getNotchFilterCoefficients,
|
|
627
|
+
allpass: getAllpassFilterCoefficients
|
|
628
|
+
};
|
|
629
|
+
class SmoothEstimator {
|
|
630
|
+
constructor(positiveAdaptationRate, negativeAdaptationRate, initialEstimate = 0) {
|
|
631
|
+
this.positiveAdaptationRate = positiveAdaptationRate;
|
|
632
|
+
this.negativeAdaptationRate = negativeAdaptationRate;
|
|
633
|
+
this.estimate = initialEstimate;
|
|
634
|
+
}
|
|
635
|
+
estimate;
|
|
636
|
+
update(target, adaptaionRateFactor = 1) {
|
|
637
|
+
const residual = target - this.estimate;
|
|
638
|
+
const adaptationRate = residual >= 0 ? this.positiveAdaptationRate : this.negativeAdaptationRate;
|
|
639
|
+
const stepSize = residual * adaptationRate * adaptaionRateFactor;
|
|
640
|
+
this.estimate += stepSize;
|
|
641
|
+
}
|
|
642
|
+
updateDamped(target, dampingReference, dampingCurvature, adaptationRateFactor = 1) {
|
|
643
|
+
const residual = target - this.estimate;
|
|
644
|
+
const scaledResidualMagnitude = Math.abs(residual) * dampingCurvature;
|
|
645
|
+
const dampingFactor = scaledResidualMagnitude / (scaledResidualMagnitude + dampingReference);
|
|
646
|
+
const adaptationRate = residual >= 0 ? this.positiveAdaptationRate : this.negativeAdaptationRate;
|
|
647
|
+
const stepSize = residual * adaptationRate * adaptationRateFactor * dampingFactor;
|
|
648
|
+
this.estimate += stepSize;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
class KWeightingFilter {
|
|
652
|
+
constructor(sampleRate, useStandard44100Filters = false) {
|
|
653
|
+
this.sampleRate = sampleRate;
|
|
654
|
+
this.useStandard44100Filters = useStandard44100Filters;
|
|
655
|
+
if (useStandard44100Filters) {
|
|
656
|
+
this.highShelfFilter = new BiquadFilter({
|
|
657
|
+
b0: 1.53512485958697,
|
|
658
|
+
b1: -2.69169618940638,
|
|
659
|
+
b2: 1.19839281085285,
|
|
660
|
+
a1: -1.69065929318241,
|
|
661
|
+
a2: 0.73248077421585
|
|
662
|
+
});
|
|
663
|
+
this.highPassFilter = new BiquadFilter({
|
|
664
|
+
b0: 1,
|
|
665
|
+
b1: -2,
|
|
666
|
+
b2: 1,
|
|
667
|
+
a1: -1.99004745483398,
|
|
668
|
+
a2: 0.99007225036621
|
|
669
|
+
});
|
|
670
|
+
} else {
|
|
671
|
+
this.highShelfFilter = createHighshelfFilter(sampleRate, 2e3, 4);
|
|
672
|
+
this.highPassFilter = createHighpassFilter(sampleRate, 1.5, 0.01);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
highShelfFilter;
|
|
676
|
+
highPassFilter;
|
|
677
|
+
process(sample) {
|
|
678
|
+
let outputSample = sample;
|
|
679
|
+
outputSample = this.highShelfFilter.filter(outputSample);
|
|
680
|
+
outputSample = this.highPassFilter.filter(outputSample);
|
|
681
|
+
return outputSample;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
function gainFactorToDecibels(gainFactor) {
|
|
685
|
+
return gainFactor <= 1e-5 ? -100 : 20 * Math.log10(gainFactor);
|
|
686
|
+
}
|
|
687
|
+
function decibelsToGainFactor(decibels) {
|
|
688
|
+
return decibels <= -100 ? 0 : Math.pow(10, 0.05 * decibels);
|
|
689
|
+
}
|
|
690
|
+
function decodeWaveToFloat32Channels(waveData, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
|
|
691
|
+
const {
|
|
692
|
+
decodedAudioBuffer,
|
|
693
|
+
sampleRate,
|
|
694
|
+
channelCount,
|
|
695
|
+
bitDepth,
|
|
696
|
+
sampleFormat
|
|
697
|
+
} = decodeWaveToBuffer(
|
|
698
|
+
waveData,
|
|
699
|
+
ignoreTruncatedChunks,
|
|
700
|
+
ignoreOverflowingDataChunks
|
|
701
|
+
);
|
|
702
|
+
const audioChannels = bufferToFloat32Channels(
|
|
703
|
+
decodedAudioBuffer,
|
|
704
|
+
channelCount,
|
|
705
|
+
bitDepth,
|
|
706
|
+
sampleFormat
|
|
707
|
+
);
|
|
708
|
+
return {
|
|
709
|
+
audioChannels,
|
|
710
|
+
sampleRate
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function decodeWaveToBuffer(waveData, ignoreTruncatedChunks = true, ignoreOverflowingDataChunks = true) {
|
|
714
|
+
let readOffset = 0;
|
|
715
|
+
const riffId = (0, import_Ascii.decodeAscii)(waveData.subarray(readOffset, readOffset + 4));
|
|
716
|
+
if (riffId !== "RIFF") {
|
|
717
|
+
throw new Error("Not a valid wave file. No RIFF id found at offset 0.");
|
|
718
|
+
}
|
|
719
|
+
readOffset += 4;
|
|
720
|
+
let riffChunkSize = readUint32LE(waveData, readOffset);
|
|
721
|
+
readOffset += 4;
|
|
722
|
+
const waveId = (0, import_Ascii.decodeAscii)(waveData.subarray(readOffset, readOffset + 4));
|
|
723
|
+
if (waveId !== "WAVE") {
|
|
724
|
+
throw new Error("Not a valid wave file. No WAVE id found at offset 8.");
|
|
725
|
+
}
|
|
726
|
+
if (ignoreOverflowingDataChunks && riffChunkSize === 4294967295) {
|
|
727
|
+
riffChunkSize = waveData.length - 8;
|
|
728
|
+
}
|
|
729
|
+
if (riffChunkSize < waveData.length - 8) {
|
|
730
|
+
throw new Error(
|
|
731
|
+
`RIFF chunk length ${riffChunkSize} is smaller than the remaining size of the buffer (${waveData.length - 8})`
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
if (!ignoreTruncatedChunks && riffChunkSize > waveData.length - 8) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
`RIFF chunk length (${riffChunkSize}) is greater than the remaining size of the buffer (${waveData.length - 8})`
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
readOffset += 4;
|
|
740
|
+
let formatSubChunkBodyBuffer;
|
|
741
|
+
const dataBuffers = [];
|
|
742
|
+
while (true) {
|
|
743
|
+
const subChunkIdentifier = (0, import_Ascii.decodeAscii)(
|
|
744
|
+
waveData.subarray(readOffset, readOffset + 4)
|
|
745
|
+
);
|
|
746
|
+
readOffset += 4;
|
|
747
|
+
let subChunkSize = readUint32LE(waveData, readOffset);
|
|
748
|
+
readOffset += 4;
|
|
749
|
+
if (!ignoreTruncatedChunks && subChunkSize > waveData.length - readOffset) {
|
|
750
|
+
throw new Error(
|
|
751
|
+
`Encountered a '${subChunkIdentifier}' subchunk with a size of ${subChunkSize} which is greater than the remaining size of the buffer (${waveData.length - readOffset})`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
if (subChunkIdentifier === "fmt ") {
|
|
755
|
+
formatSubChunkBodyBuffer = waveData.subarray(
|
|
756
|
+
readOffset,
|
|
757
|
+
readOffset + subChunkSize
|
|
758
|
+
);
|
|
759
|
+
} else if (subChunkIdentifier === "data") {
|
|
760
|
+
if (!formatSubChunkBodyBuffer) {
|
|
761
|
+
throw new Error(
|
|
762
|
+
"A data subchunk was encountered before a format subchunk"
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
if (ignoreOverflowingDataChunks && subChunkSize === 4294967295) {
|
|
766
|
+
subChunkSize = waveData.length - readOffset;
|
|
767
|
+
}
|
|
768
|
+
const subChunkData = waveData.subarray(
|
|
769
|
+
readOffset,
|
|
770
|
+
readOffset + subChunkSize
|
|
771
|
+
);
|
|
772
|
+
dataBuffers.push(subChunkData);
|
|
773
|
+
}
|
|
774
|
+
readOffset += subChunkSize;
|
|
775
|
+
if (readOffset >= waveData.length) {
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!formatSubChunkBodyBuffer) {
|
|
780
|
+
throw new Error("No format subchunk was found in the wave file");
|
|
781
|
+
}
|
|
782
|
+
if (dataBuffers.length === 0) {
|
|
783
|
+
throw new Error("No data subchunks were found in the wave file");
|
|
784
|
+
}
|
|
785
|
+
const waveFormat = WaveFormatHeader.deserializeFrom(formatSubChunkBodyBuffer);
|
|
786
|
+
const sampleFormat = waveFormat.sampleFormat;
|
|
787
|
+
const channelCount = waveFormat.channelCount;
|
|
788
|
+
const sampleRate = waveFormat.sampleRate;
|
|
789
|
+
const bitDepth = waveFormat.bitDepth;
|
|
790
|
+
const speakerPositionMask = waveFormat.speakerPositionMask;
|
|
791
|
+
const decodedAudioBuffer = concatUint8Arrays(dataBuffers);
|
|
792
|
+
return {
|
|
793
|
+
decodedAudioBuffer,
|
|
794
|
+
sampleRate,
|
|
795
|
+
channelCount,
|
|
796
|
+
bitDepth,
|
|
797
|
+
sampleFormat,
|
|
798
|
+
speakerPositionMask
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function concatUint8Arrays(arrays) {
|
|
802
|
+
return concatTypedArrays(Uint8Array, arrays);
|
|
803
|
+
}
|
|
804
|
+
function concatTypedArrays(TypedArrayConstructor, arrays) {
|
|
805
|
+
let totalLength = 0;
|
|
806
|
+
for (const array of arrays) {
|
|
807
|
+
totalLength += array.length;
|
|
808
|
+
}
|
|
809
|
+
const result = new TypedArrayConstructor(totalLength);
|
|
810
|
+
let writeOffset = 0;
|
|
811
|
+
for (const array of arrays) {
|
|
812
|
+
result.set(array, writeOffset);
|
|
813
|
+
writeOffset += array.length;
|
|
814
|
+
}
|
|
815
|
+
return result;
|
|
816
|
+
}
|
|
817
|
+
function readUint16LE(buffer, offset) {
|
|
818
|
+
const byte0 = buffer[offset];
|
|
819
|
+
const byte1 = buffer[offset + 1];
|
|
820
|
+
if (byte0 === void 0 || byte1 === void 0) {
|
|
821
|
+
throw new Error(`buffer access out of bounds at offset ${offset}`);
|
|
822
|
+
}
|
|
823
|
+
return byte0 | byte1 << 8;
|
|
824
|
+
}
|
|
825
|
+
function writeUint16LE(buffer, value, offset) {
|
|
826
|
+
if (value < 0 || value > 65535) {
|
|
827
|
+
throw new Error(
|
|
828
|
+
`Value ${value} is outside the range of a 16-bit unsigned integer`
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
buffer[offset] = value & 255;
|
|
832
|
+
buffer[offset + 1] = value >>> 8 & 255;
|
|
833
|
+
}
|
|
834
|
+
function readUint32LE(buffer, offset) {
|
|
835
|
+
return readInt32LE(buffer, offset) >>> 0;
|
|
836
|
+
}
|
|
837
|
+
function writeUint32LE(buffer, value, offset) {
|
|
838
|
+
if (value < 0 || value > 4294967295) {
|
|
839
|
+
throw new Error(
|
|
840
|
+
`Value ${value} is outside the range of a 32-bit unsigned integer`
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
buffer[offset] = value & 255;
|
|
844
|
+
buffer[offset + 1] = value >>> 8 & 255;
|
|
845
|
+
buffer[offset + 2] = value >>> 16 & 255;
|
|
846
|
+
buffer[offset + 3] = value >>> 24 & 255;
|
|
847
|
+
}
|
|
848
|
+
class WaveFormatHeader {
|
|
849
|
+
sampleFormat;
|
|
850
|
+
// 2 bytes LE
|
|
851
|
+
channelCount;
|
|
852
|
+
// 2 bytes LE
|
|
853
|
+
sampleRate;
|
|
854
|
+
// 4 bytes LE
|
|
855
|
+
get byteRate() {
|
|
856
|
+
return this.sampleRate * this.bytesPerSample * this.channelCount;
|
|
857
|
+
}
|
|
858
|
+
// 4 bytes LE
|
|
859
|
+
get blockAlign() {
|
|
860
|
+
return this.bytesPerSample * this.channelCount;
|
|
861
|
+
}
|
|
862
|
+
// 2 bytes LE
|
|
863
|
+
bitDepth;
|
|
864
|
+
// 2 bytes LE
|
|
865
|
+
speakerPositionMask;
|
|
866
|
+
// 4 bytes LE
|
|
867
|
+
get guid() {
|
|
868
|
+
return sampleFormatToGuid[this.sampleFormat];
|
|
869
|
+
}
|
|
870
|
+
// 16 bytes BE
|
|
871
|
+
get bytesPerSample() {
|
|
872
|
+
return this.bitDepth / 8;
|
|
873
|
+
}
|
|
874
|
+
constructor(channelCount, sampleRate, bitDepth, sampleFormat, speakerPositionMask = 0) {
|
|
875
|
+
this.sampleFormat = sampleFormat;
|
|
876
|
+
this.channelCount = channelCount;
|
|
877
|
+
this.sampleRate = sampleRate;
|
|
878
|
+
this.bitDepth = bitDepth;
|
|
879
|
+
this.speakerPositionMask = speakerPositionMask;
|
|
880
|
+
}
|
|
881
|
+
serialize(useExtensibleFormat) {
|
|
882
|
+
const sampleFormatId = this.sampleFormat;
|
|
883
|
+
const serializedSize = sampleFormatToSerializedSize[sampleFormatId];
|
|
884
|
+
const result = new Uint8Array(serializedSize);
|
|
885
|
+
writeAscii(result, "fmt ", 0);
|
|
886
|
+
writeUint32LE(result, serializedSize - 8, 4);
|
|
887
|
+
writeUint16LE(result, sampleFormatId, 8);
|
|
888
|
+
writeUint16LE(result, this.channelCount, 10);
|
|
889
|
+
writeUint32LE(result, this.sampleRate, 12);
|
|
890
|
+
writeUint32LE(result, this.byteRate, 16);
|
|
891
|
+
writeUint16LE(result, this.blockAlign, 20);
|
|
892
|
+
writeUint16LE(result, this.bitDepth, 22);
|
|
893
|
+
if (useExtensibleFormat) {
|
|
894
|
+
writeUint16LE(result, serializedSize - 26, 24);
|
|
895
|
+
writeUint16LE(result, this.bitDepth, 26);
|
|
896
|
+
writeUint32LE(result, this.speakerPositionMask, 28);
|
|
897
|
+
if (this.guid) {
|
|
898
|
+
result.set(decodeHex(this.guid), 32);
|
|
899
|
+
} else {
|
|
900
|
+
throw new Error(
|
|
901
|
+
`Extensible format is not supported for sample format ${this.sampleFormat}`
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
static deserializeFrom(formatChunkBody) {
|
|
908
|
+
let sampleFormat = readUint16LE(formatChunkBody, 0);
|
|
909
|
+
const channelCount = readUint16LE(formatChunkBody, 2);
|
|
910
|
+
const sampleRate = readUint32LE(formatChunkBody, 4);
|
|
911
|
+
const bitDepth = readUint16LE(formatChunkBody, 14);
|
|
912
|
+
let speakerPositionMask = 0;
|
|
913
|
+
if (sampleFormat === 65534) {
|
|
914
|
+
if (formatChunkBody.length < 40) {
|
|
915
|
+
throw new Error(
|
|
916
|
+
`Format subchunk specifies a format id of 65534 (extensible) but its body size is ${formatChunkBody.length} bytes, which is smaller than the minimum expected of 40 bytes`
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
speakerPositionMask = readUint16LE(formatChunkBody, 20);
|
|
920
|
+
const guid = encodeHex(formatChunkBody.subarray(24, 40));
|
|
921
|
+
if (guid === sampleFormatToGuid[SampleFormat.PCM]) {
|
|
922
|
+
sampleFormat = SampleFormat.PCM;
|
|
923
|
+
} else if (guid === sampleFormatToGuid[SampleFormat.Float]) {
|
|
924
|
+
sampleFormat = SampleFormat.Float;
|
|
925
|
+
} else if (guid === sampleFormatToGuid[SampleFormat.Alaw]) {
|
|
926
|
+
sampleFormat = SampleFormat.Alaw;
|
|
927
|
+
} else if (guid === sampleFormatToGuid[SampleFormat.Mulaw]) {
|
|
928
|
+
sampleFormat = SampleFormat.Mulaw;
|
|
929
|
+
} else {
|
|
930
|
+
throw new Error(
|
|
931
|
+
`Unsupported format GUID in extended format subchunk: ${guid}`
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
if (sampleFormat === SampleFormat.PCM) {
|
|
936
|
+
if (bitDepth !== 8 && bitDepth !== 16 && bitDepth !== 24 && bitDepth !== 32) {
|
|
937
|
+
throw new Error(
|
|
938
|
+
`PCM audio has a bit depth of ${bitDepth}, which is not supported`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
} else if (sampleFormat === SampleFormat.Float) {
|
|
942
|
+
if (bitDepth !== 32 && bitDepth !== 64) {
|
|
943
|
+
throw new Error(
|
|
944
|
+
`IEEE float audio has a bit depth of ${bitDepth}, which is not supported`
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
} else if (sampleFormat === SampleFormat.Alaw) {
|
|
948
|
+
if (bitDepth !== 8) {
|
|
949
|
+
throw new Error(
|
|
950
|
+
`Alaw audio has a bit depth of ${bitDepth}, which is not supported`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
} else if (sampleFormat === SampleFormat.Mulaw) {
|
|
954
|
+
if (bitDepth !== 8) {
|
|
955
|
+
throw new Error(
|
|
956
|
+
`Mulaw audio has a bit depth of ${bitDepth}, which is not supported`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
} else {
|
|
960
|
+
throw new Error(`Wave audio format id ${sampleFormat} is not supported`);
|
|
961
|
+
}
|
|
962
|
+
return new WaveFormatHeader(
|
|
963
|
+
channelCount,
|
|
964
|
+
sampleRate,
|
|
965
|
+
bitDepth,
|
|
966
|
+
sampleFormat,
|
|
967
|
+
speakerPositionMask
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
const SampleFormat = {
|
|
972
|
+
PCM: 1,
|
|
973
|
+
Float: 3,
|
|
974
|
+
Alaw: 6,
|
|
975
|
+
Mulaw: 7
|
|
976
|
+
};
|
|
977
|
+
const sampleFormatToSerializedSize = {
|
|
978
|
+
[SampleFormat.PCM]: 24,
|
|
979
|
+
[SampleFormat.Float]: 26,
|
|
980
|
+
[SampleFormat.Alaw]: 26,
|
|
981
|
+
[SampleFormat.Mulaw]: 26,
|
|
982
|
+
65534: 48
|
|
983
|
+
};
|
|
984
|
+
const sampleFormatToGuid = {
|
|
985
|
+
[SampleFormat.PCM]: "0100000000001000800000aa00389b71",
|
|
986
|
+
[SampleFormat.Float]: "0300000000001000800000aa00389b71",
|
|
987
|
+
[SampleFormat.Alaw]: "0600000000001000800000aa00389b71",
|
|
988
|
+
[SampleFormat.Mulaw]: "0700000000001000800000aa00389b71"
|
|
989
|
+
};
|
|
990
|
+
function bufferToFloat32Channels(audioBuffer, channelCount, sourceBitDepth, sourceSampleFormat) {
|
|
991
|
+
let interleavedChannels;
|
|
992
|
+
if (sourceSampleFormat === SampleFormat.PCM && sourceBitDepth === 16) {
|
|
993
|
+
interleavedChannels = int16PcmToFloat32(bytesLEToInt16Array(audioBuffer));
|
|
994
|
+
} else {
|
|
995
|
+
throw new Error(`Unsupported PCM bit depth: ${sourceBitDepth}`);
|
|
996
|
+
}
|
|
997
|
+
audioBuffer = new Uint8Array(0);
|
|
998
|
+
return deinterleaveChannels(interleavedChannels, channelCount);
|
|
999
|
+
}
|
|
1000
|
+
function readInt32LE(buffer, offset) {
|
|
1001
|
+
const byte0 = buffer[offset];
|
|
1002
|
+
const byte1 = buffer[offset + 1];
|
|
1003
|
+
const byte2 = buffer[offset + 2];
|
|
1004
|
+
const byte3 = buffer[offset + 3];
|
|
1005
|
+
if (byte0 === void 0 || byte1 === void 0 || byte2 === void 0 || byte3 === void 0) {
|
|
1006
|
+
throw new Error(`buffer access out of bounds at offset ${offset}`);
|
|
1007
|
+
}
|
|
1008
|
+
const value = byte0 | byte1 << 8 | byte2 << 16 | byte3 << 24;
|
|
1009
|
+
return value;
|
|
1010
|
+
}
|
|
1011
|
+
function interleaveChannels(channels) {
|
|
1012
|
+
const channelCount = channels.length;
|
|
1013
|
+
const firstChannel = channels[0];
|
|
1014
|
+
if (!firstChannel) {
|
|
1015
|
+
throw new Error("Empty channel array received");
|
|
1016
|
+
}
|
|
1017
|
+
if (channelCount === 1) {
|
|
1018
|
+
return firstChannel;
|
|
1019
|
+
}
|
|
1020
|
+
const sampleCount = firstChannel.length;
|
|
1021
|
+
const result = new Float32Array(sampleCount * channelCount);
|
|
1022
|
+
let writeIndex = 0;
|
|
1023
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
|
|
1024
|
+
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
|
|
1025
|
+
const channel = channels[channelIndex];
|
|
1026
|
+
if (!channel) {
|
|
1027
|
+
throw new Error(`channel ${channelIndex} not found`);
|
|
1028
|
+
}
|
|
1029
|
+
const sample = channel[sampleIndex];
|
|
1030
|
+
if (sample === void 0) {
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
`sample at index ${sampleIndex} not found in channel ${channelIndex}`
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
result[writeIndex++] = sample;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return result;
|
|
1039
|
+
}
|
|
1040
|
+
function deinterleaveChannels(interleavedChannels, channelCount) {
|
|
1041
|
+
if (channelCount < 1) {
|
|
1042
|
+
throw new Error(
|
|
1043
|
+
`Invalid channel count of ${channelCount} received, which is smaller than 1`
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (channelCount === 1) {
|
|
1047
|
+
return [interleavedChannels];
|
|
1048
|
+
}
|
|
1049
|
+
if (interleavedChannels.length % channelCount !== 0) {
|
|
1050
|
+
throw new Error(
|
|
1051
|
+
`Size of interleaved channels (${interleaveChannels.length}) is not a multiple of channel count (${channelCount})`
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
const sampleCount = interleavedChannels.length / channelCount;
|
|
1055
|
+
const channels = [];
|
|
1056
|
+
for (let i = 0; i < channelCount; i++) {
|
|
1057
|
+
channels.push(new Float32Array(sampleCount));
|
|
1058
|
+
}
|
|
1059
|
+
let readIndex = 0;
|
|
1060
|
+
for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {
|
|
1061
|
+
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
|
|
1062
|
+
const channel = channels[channelIndex];
|
|
1063
|
+
if (!channel) {
|
|
1064
|
+
throw new Error(`channel ${channelIndex} not found`);
|
|
1065
|
+
}
|
|
1066
|
+
const sample = interleavedChannels[readIndex++];
|
|
1067
|
+
if (sample === void 0) {
|
|
1068
|
+
throw new Error(
|
|
1069
|
+
`interleaved sample at index ${readIndex - 1} not found`
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
channel[sampleIndex] = sample;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return channels;
|
|
1076
|
+
}
|
|
1077
|
+
function int16PcmToFloat32(input) {
|
|
1078
|
+
const sampleCount = input.length;
|
|
1079
|
+
const output = new Float32Array(sampleCount);
|
|
1080
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
1081
|
+
const sample = input[i];
|
|
1082
|
+
if (sample === void 0) {
|
|
1083
|
+
throw new Error(`input sample at index ${i} not found`);
|
|
1084
|
+
}
|
|
1085
|
+
output[i] = sample * (1 / 32768);
|
|
1086
|
+
}
|
|
1087
|
+
return output;
|
|
1088
|
+
}
|
|
1089
|
+
function writeAscii(buffer, asciiString, writeStartOffset) {
|
|
1090
|
+
const writeEndOffset = Math.min(
|
|
1091
|
+
writeStartOffset + asciiString.length,
|
|
1092
|
+
buffer.length
|
|
1093
|
+
);
|
|
1094
|
+
let readOffset = 0;
|
|
1095
|
+
let writeOffset = writeStartOffset;
|
|
1096
|
+
while (writeOffset < writeEndOffset) {
|
|
1097
|
+
const charCode = asciiString.charCodeAt(readOffset++);
|
|
1098
|
+
if (charCode >= 128) {
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
`Character '${asciiString[readOffset]}' (code: ${charCode}) at offset ${readOffset} can't be encoded as ASCII`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
buffer[writeOffset++] = charCode;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
const isLittleEndianArch = testIfLittleEndian();
|
|
1107
|
+
function testIfLittleEndian() {
|
|
1108
|
+
const uint16Array = new Uint16Array([4386]);
|
|
1109
|
+
const bytes = new Uint8Array(uint16Array.buffer);
|
|
1110
|
+
return bytes[0] === 34;
|
|
1111
|
+
}
|
|
1112
|
+
function reverseByteGroups(bytes, groupSize) {
|
|
1113
|
+
const result = bytes.slice();
|
|
1114
|
+
reverseByteGroupsInPlace(result, groupSize);
|
|
1115
|
+
return result;
|
|
1116
|
+
}
|
|
1117
|
+
function reverseByteGroupsInPlace(bytes, groupSize) {
|
|
1118
|
+
if (bytes.length % groupSize !== 0) {
|
|
1119
|
+
throw new Error(`Byte count must be an integer multiple of the group size.`);
|
|
1120
|
+
}
|
|
1121
|
+
const halfGroupSize = Math.floor(groupSize / 2);
|
|
1122
|
+
for (let offset = 0; offset < bytes.length; offset += groupSize) {
|
|
1123
|
+
const groupFirstElementOffset = offset;
|
|
1124
|
+
const groupLastElementOffset = offset + groupSize - 1;
|
|
1125
|
+
for (let i = 0; i < halfGroupSize; i++) {
|
|
1126
|
+
const offset1 = groupFirstElementOffset + i;
|
|
1127
|
+
const offset2 = groupLastElementOffset - i;
|
|
1128
|
+
const valueAtOffset1 = bytes[offset1];
|
|
1129
|
+
const valueAtOffset2 = bytes[offset2];
|
|
1130
|
+
if (valueAtOffset1 === void 0 || valueAtOffset2 === void 0) {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
`bytes access out of bounds at offsets ${offset1}, ${offset2}`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
bytes[offset1] = valueAtOffset2;
|
|
1136
|
+
bytes[offset2] = valueAtOffset1;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
function reverseByteGroupsIfBigEndian(bytes, groupSize) {
|
|
1141
|
+
if (isLittleEndianArch) {
|
|
1142
|
+
return bytes;
|
|
1143
|
+
} else {
|
|
1144
|
+
return reverseByteGroups(bytes, groupSize);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
function bytesLEToInt16Array(bytes) {
|
|
1148
|
+
bytes = reverseByteGroupsIfBigEndian(bytes, 2);
|
|
1149
|
+
return new Int16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2);
|
|
1150
|
+
}
|
|
1151
|
+
class DynamicTypedArray {
|
|
1152
|
+
constructor(TypedArrayConstructor, initialCapacity = 4) {
|
|
1153
|
+
this.TypedArrayConstructor = TypedArrayConstructor;
|
|
1154
|
+
this.elements = new TypedArrayConstructor(initialCapacity);
|
|
1155
|
+
}
|
|
1156
|
+
elements;
|
|
1157
|
+
length = 0;
|
|
1158
|
+
add(newElement) {
|
|
1159
|
+
const newLength = this.length + 1;
|
|
1160
|
+
if (newLength > this.capacity) {
|
|
1161
|
+
this.ensureCapacity(newLength);
|
|
1162
|
+
}
|
|
1163
|
+
this.elements[this.length] = newElement;
|
|
1164
|
+
this.length = newLength;
|
|
1165
|
+
}
|
|
1166
|
+
addMany(...newElements) {
|
|
1167
|
+
this.addArray(newElements);
|
|
1168
|
+
}
|
|
1169
|
+
addArray(newElements) {
|
|
1170
|
+
const newLength = this.length + newElements.length;
|
|
1171
|
+
if (newLength > this.capacity) {
|
|
1172
|
+
this.ensureCapacity(newLength);
|
|
1173
|
+
}
|
|
1174
|
+
this.elements.set(newElements, this.length);
|
|
1175
|
+
this.length = newLength;
|
|
1176
|
+
}
|
|
1177
|
+
ensureCapacity(requiredCapacity) {
|
|
1178
|
+
if (requiredCapacity > this.capacity) {
|
|
1179
|
+
const newCapacity = requiredCapacity * 2;
|
|
1180
|
+
const newElements = new this.TypedArrayConstructor(newCapacity);
|
|
1181
|
+
newElements.set(this.toTypedArray());
|
|
1182
|
+
this.elements = newElements;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
get capacity() {
|
|
1186
|
+
return this.elements.length;
|
|
1187
|
+
}
|
|
1188
|
+
toTypedArray() {
|
|
1189
|
+
return this.elements.subarray(0, this.length);
|
|
1190
|
+
}
|
|
1191
|
+
clear() {
|
|
1192
|
+
this.length = 0;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
function createDynamicUint8Array(initialCapacity) {
|
|
1196
|
+
return new DynamicTypedArray(Uint8Array, initialCapacity);
|
|
1197
|
+
}
|
|
1198
|
+
function createDynamicUint16Array(initialCapacity) {
|
|
1199
|
+
return new DynamicTypedArray(Uint16Array, initialCapacity);
|
|
1200
|
+
}
|
|
1201
|
+
async function readFileAsBinary(filePath) {
|
|
1202
|
+
const chunkSize = 2 ** 20;
|
|
1203
|
+
const fileInfo = await (0, import_promises.stat)(filePath);
|
|
1204
|
+
const fileSize = fileInfo.size;
|
|
1205
|
+
const fileReader = new FileReader(filePath);
|
|
1206
|
+
const buffer = new Uint8Array(chunkSize);
|
|
1207
|
+
const result = createDynamicUint8Array(fileSize);
|
|
1208
|
+
while (!fileReader.isFinished) {
|
|
1209
|
+
const chunk = await fileReader.readChunk(buffer);
|
|
1210
|
+
result.addArray(chunk);
|
|
1211
|
+
}
|
|
1212
|
+
return result.toTypedArray();
|
|
1213
|
+
}
|
|
1214
|
+
class FileReader {
|
|
1215
|
+
constructor(filePath) {
|
|
1216
|
+
this.filePath = filePath;
|
|
1217
|
+
}
|
|
1218
|
+
fileHandle;
|
|
1219
|
+
finished = false;
|
|
1220
|
+
disposed = false;
|
|
1221
|
+
readOffset = 0;
|
|
1222
|
+
async readChunk(buffer) {
|
|
1223
|
+
if (this.isDisposed) {
|
|
1224
|
+
throw new Error(`FileReader has been disposed`);
|
|
1225
|
+
}
|
|
1226
|
+
await this.openIfNeeded();
|
|
1227
|
+
let bufferWriteOffset = 0;
|
|
1228
|
+
while (bufferWriteOffset < buffer.length) {
|
|
1229
|
+
const remainingSizeInBuffer = buffer.length - bufferWriteOffset;
|
|
1230
|
+
let bytesRead;
|
|
1231
|
+
try {
|
|
1232
|
+
;
|
|
1233
|
+
({ bytesRead } = await this.fileHandle.read(buffer, {
|
|
1234
|
+
offset: bufferWriteOffset,
|
|
1235
|
+
length: remainingSizeInBuffer,
|
|
1236
|
+
position: this.readOffset
|
|
1237
|
+
}));
|
|
1238
|
+
} catch (e) {
|
|
1239
|
+
await this.dispose();
|
|
1240
|
+
throw e;
|
|
1241
|
+
}
|
|
1242
|
+
if (bytesRead === 0) {
|
|
1243
|
+
this.finished = true;
|
|
1244
|
+
await this.dispose();
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
bufferWriteOffset += bytesRead;
|
|
1248
|
+
this.readOffset += bytesRead;
|
|
1249
|
+
}
|
|
1250
|
+
return buffer.subarray(0, bufferWriteOffset);
|
|
1251
|
+
}
|
|
1252
|
+
async openIfNeeded() {
|
|
1253
|
+
if (this.isDisposed) {
|
|
1254
|
+
throw new Error(`FileReader has been disposed`);
|
|
1255
|
+
}
|
|
1256
|
+
if (this.isOpened) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
this.fileHandle = await (0, import_promises.open)(this.filePath, "r");
|
|
1260
|
+
}
|
|
1261
|
+
async dispose() {
|
|
1262
|
+
if (this.isDisposed) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
if (this.isOpened) {
|
|
1266
|
+
try {
|
|
1267
|
+
await this.fileHandle.close();
|
|
1268
|
+
} catch (_e) {
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
this.disposed = true;
|
|
1272
|
+
this.readOffset = 0;
|
|
1273
|
+
this.fileHandle = void 0;
|
|
1274
|
+
}
|
|
1275
|
+
get isOpened() {
|
|
1276
|
+
return this.fileHandle !== void 0;
|
|
1277
|
+
}
|
|
1278
|
+
get isDisposed() {
|
|
1279
|
+
return this.disposed;
|
|
1280
|
+
}
|
|
1281
|
+
get isFinished() {
|
|
1282
|
+
return this.finished;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
function encodeHex(buffer) {
|
|
1286
|
+
const asciiBuffer = encodeHexAsAsciiBuffer(buffer);
|
|
1287
|
+
return (0, import_Ascii.decodeAscii)(asciiBuffer);
|
|
1288
|
+
}
|
|
1289
|
+
function encodeHexAsAsciiBuffer(buffer) {
|
|
1290
|
+
const bufferLen = buffer.length;
|
|
1291
|
+
const charCodes = new Uint8Array(bufferLen * 2);
|
|
1292
|
+
let readOffset = 0;
|
|
1293
|
+
let writeOffset = 0;
|
|
1294
|
+
while (readOffset < bufferLen) {
|
|
1295
|
+
const value = buffer[readOffset++];
|
|
1296
|
+
if (value === void 0) {
|
|
1297
|
+
throw new Error(`buffer value at offset ${readOffset - 1} not found`);
|
|
1298
|
+
}
|
|
1299
|
+
const valueHigh4Bits = value >>> 4 & 15;
|
|
1300
|
+
const valueLow4Bits = value & 15;
|
|
1301
|
+
const highCharCode = hexCharCodeLookup[valueHigh4Bits];
|
|
1302
|
+
const lowCharCode = hexCharCodeLookup[valueLow4Bits];
|
|
1303
|
+
if (highCharCode === void 0 || lowCharCode === void 0) {
|
|
1304
|
+
throw new Error(`hex char code lookup failed for value ${value}`);
|
|
1305
|
+
}
|
|
1306
|
+
charCodes[writeOffset++] = highCharCode;
|
|
1307
|
+
charCodes[writeOffset++] = lowCharCode;
|
|
1308
|
+
}
|
|
1309
|
+
return charCodes;
|
|
1310
|
+
}
|
|
1311
|
+
function decodeHex(hexString) {
|
|
1312
|
+
const hexLength = hexString.length;
|
|
1313
|
+
if (hexLength % 2 !== 0) {
|
|
1314
|
+
throw new Error(
|
|
1315
|
+
`Hexadecimal string doesn't have an even number of characters`
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
const buffer = new Uint8Array(hexLength / 2);
|
|
1319
|
+
let readOffset = 0;
|
|
1320
|
+
let writeOffset = 0;
|
|
1321
|
+
while (readOffset < hexLength) {
|
|
1322
|
+
const valueHigh4Bits = hexCharCodeToValue(
|
|
1323
|
+
hexString.charCodeAt(readOffset++)
|
|
1324
|
+
);
|
|
1325
|
+
const valueLow4Bits = hexCharCodeToValue(hexString.charCodeAt(readOffset++));
|
|
1326
|
+
const value = valueHigh4Bits << 4 | valueLow4Bits;
|
|
1327
|
+
buffer[writeOffset++] = value;
|
|
1328
|
+
}
|
|
1329
|
+
return buffer;
|
|
1330
|
+
}
|
|
1331
|
+
function hexCharCodeToValue(hexCharCode) {
|
|
1332
|
+
if (hexCharCode >= 48 && hexCharCode <= 57) {
|
|
1333
|
+
return hexCharCode - 48;
|
|
1334
|
+
} else if (hexCharCode >= 97 && hexCharCode <= 102) {
|
|
1335
|
+
return 10 + hexCharCode - 97;
|
|
1336
|
+
} else if (hexCharCode >= 65 && hexCharCode <= 70) {
|
|
1337
|
+
return 10 + hexCharCode - 65;
|
|
1338
|
+
} else {
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
`Can't decode character '${String.fromCharCode(hexCharCode)}' (code: ${hexCharCode}) as hexadecimal`
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
const hexCharCodeLookup = new Uint8Array([
|
|
1345
|
+
48,
|
|
1346
|
+
49,
|
|
1347
|
+
50,
|
|
1348
|
+
51,
|
|
1349
|
+
52,
|
|
1350
|
+
53,
|
|
1351
|
+
54,
|
|
1352
|
+
55,
|
|
1353
|
+
56,
|
|
1354
|
+
57,
|
|
1355
|
+
97,
|
|
1356
|
+
98,
|
|
1357
|
+
99,
|
|
1358
|
+
100,
|
|
1359
|
+
101,
|
|
1360
|
+
102
|
|
1361
|
+
]);
|
|
1362
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1363
|
+
0 && (module.exports = {
|
|
1364
|
+
createDynamicUint16Array,
|
|
1365
|
+
detectVoiceActivity
|
|
1366
|
+
});
|