mirror of
https://github.com/steveseguin/caption-local.git
synced 2026-10-09 17:49:30 +00:00
54 lines
2.5 KiB
JavaScript
54 lines
2.5 KiB
JavaScript
/* Bounded capture storage shared by the browser and Node regression tests. */
|
|
(function(root) {
|
|
'use strict';
|
|
class CaptureBuffer {
|
|
constructor(threshold = 0.002) { this.threshold = threshold; this.reset(); }
|
|
reset() { this.parts = []; this.length = 0; this.context = 0; this.speech = false; this.quiet = 0; }
|
|
remove(count) {
|
|
this.length -= count;
|
|
while (count && this.parts.length) {
|
|
if (count >= this.parts[0].length) count -= this.parts.shift().length;
|
|
else { this.parts[0] = this.parts[0].slice(count); count = 0; }
|
|
}
|
|
}
|
|
append(audio) {
|
|
const rms = Math.sqrt(audio.reduce((sum, x) => sum + x*x, 0) / audio.length);
|
|
const voiced = rms >= this.threshold;
|
|
this.quiet = voiced ? 0 : this.quiet + audio.length / 16000;
|
|
this.speech ||= voiced;
|
|
this.parts.push(audio); this.length += audio.length;
|
|
if (!this.speech && this.length > 8000) this.remove(this.length - 8000);
|
|
return rms;
|
|
}
|
|
snapshot(force = false) {
|
|
const fresh = this.length - this.context;
|
|
if (!this.speech || fresh < 160) return null;
|
|
if (!force && fresh < 96000 && !(this.quiet >= 0.6 && fresh >= 5600)) return null;
|
|
const count = Math.min(this.length, 192000);
|
|
const audio = new Float32Array(count); let offset = 0;
|
|
for (const part of this.parts) {
|
|
const size = Math.min(part.length, count - offset);
|
|
audio.set(part.subarray(0, size), offset); offset += size;
|
|
if (offset === count) break;
|
|
}
|
|
return {audio, context: this.context / 16000,
|
|
final: count === this.length && (force || this.quiet >= 0.6)};
|
|
}
|
|
commit(snapshot, seconds) {
|
|
const committed = Math.round(seconds * 16000);
|
|
if (!Number.isFinite(committed) || committed <= this.context || committed > snapshot.audio.length) {
|
|
throw new Error('Service returned an invalid audio boundary; audio has been retained.');
|
|
}
|
|
const remove = snapshot.final ? committed : Math.max(0, committed - 8000);
|
|
this.remove(remove);
|
|
this.context = snapshot.final ? 0 : committed - remove;
|
|
if (snapshot.final) {
|
|
this.speech = this.parts.some(part => Math.sqrt(part.reduce((s, x) => s+x*x, 0)/part.length) >= this.threshold);
|
|
if (!this.speech && this.length > 8000) this.remove(this.length - 8000);
|
|
}
|
|
}
|
|
}
|
|
root.CaptureBuffer = CaptureBuffer;
|
|
if (typeof module !== 'undefined') module.exports = CaptureBuffer;
|
|
})(typeof window === 'undefined' ? globalThis : window);
|