omnius 1.0.426 → 1.0.427
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/dist/index.js +102 -19
- package/dist/scripts/live-whisper.py +12 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -603619,7 +603619,9 @@ var init_listen = __esm({
|
|
|
603619
603619
|
case "transcript":
|
|
603620
603620
|
this.emit("transcript", {
|
|
603621
603621
|
text: evt.text,
|
|
603622
|
-
isFinal: evt.isFinal ?? false
|
|
603622
|
+
isFinal: evt.isFinal ?? false,
|
|
603623
|
+
consensus: typeof evt.consensus === "boolean" ? evt.consensus : void 0,
|
|
603624
|
+
doa: typeof evt.doa === "number" ? evt.doa : void 0
|
|
603623
603625
|
});
|
|
603624
603626
|
break;
|
|
603625
603627
|
case "consensus_rejected":
|
|
@@ -604016,7 +604018,7 @@ var init_listen = __esm({
|
|
|
604016
604018
|
this.lastTranscriptTime = Date.now();
|
|
604017
604019
|
this.pendingText = evt.text.trim();
|
|
604018
604020
|
updateListenLiveState({ lastTranscript: this.pendingText, lastTranscriptAt: this.lastTranscriptTime });
|
|
604019
|
-
this.emit("transcript", this.pendingText, evt.isFinal);
|
|
604021
|
+
this.emit("transcript", this.pendingText, evt.isFinal, { consensus: evt.consensus, doa: evt.doa });
|
|
604020
604022
|
if (this.config.mode === "auto") this.resetSilenceTimer();
|
|
604021
604023
|
});
|
|
604022
604024
|
fallback.on("error", (err) => {
|
|
@@ -617229,6 +617231,25 @@ function rms(arr) {
|
|
|
617229
617231
|
return Math.sqrt(sum / arr.length);
|
|
617230
617232
|
}
|
|
617231
617233
|
|
|
617234
|
+
// Linear resampler: browsers frequently IGNORE the requested AudioContext
|
|
617235
|
+
// sample rate and run at the hardware rate (typically 48kHz). Sending those
|
|
617236
|
+
// frames as-if-16kHz made the audio unpack 3x slow/pitch-dropped server-side,
|
|
617237
|
+
// wrecking ASR. Always resample the actual context rate down to 16kHz.
|
|
617238
|
+
function resampleTo16k(input, srcRate) {
|
|
617239
|
+
if (srcRate === 16000) return input;
|
|
617240
|
+
const ratio = srcRate / 16000;
|
|
617241
|
+
const outLen = Math.floor(input.length / ratio);
|
|
617242
|
+
const out = new Float32Array(outLen);
|
|
617243
|
+
for (let i = 0; i < outLen; i++) {
|
|
617244
|
+
const pos = i * ratio;
|
|
617245
|
+
const i0 = Math.floor(pos);
|
|
617246
|
+
const i1 = Math.min(input.length - 1, i0 + 1);
|
|
617247
|
+
const frac = pos - i0;
|
|
617248
|
+
out[i] = input[i0] * (1 - frac) + input[i1] * frac;
|
|
617249
|
+
}
|
|
617250
|
+
return out;
|
|
617251
|
+
}
|
|
617252
|
+
|
|
617232
617253
|
async function toggleMic() {
|
|
617233
617254
|
if (micActive) { stopMic(); return; }
|
|
617234
617255
|
try {
|
|
@@ -617237,17 +617258,19 @@ async function toggleMic() {
|
|
|
617237
617258
|
audio: { sampleRate: 16000, channelCount: 1, echoCancellation: true, noiseSuppression: true }
|
|
617238
617259
|
});
|
|
617239
617260
|
const source = micCtx.createMediaStreamSource(micStream);
|
|
617261
|
+
const actualRate = micCtx.sampleRate; // may be 48000 regardless of the request
|
|
617240
617262
|
scriptProcessor = micCtx.createScriptProcessor(4096, 1, 1);
|
|
617241
617263
|
scriptProcessor.onaudioprocess = (e) => {
|
|
617242
617264
|
if (!micActive || !ws || ws.readyState !== 1) return;
|
|
617243
|
-
const
|
|
617265
|
+
const raw = e.inputBuffer.getChannelData(0);
|
|
617266
|
+
const input = resampleTo16k(raw, actualRate);
|
|
617244
617267
|
const int16 = new Int16Array(input.length);
|
|
617245
617268
|
for (let i = 0; i < input.length; i++) {
|
|
617246
617269
|
const s = Math.max(-1, Math.min(1, input[i]));
|
|
617247
617270
|
int16[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;
|
|
617248
617271
|
}
|
|
617249
617272
|
ws.send(int16.buffer);
|
|
617250
|
-
micLevel = Math.min(1, rms(
|
|
617273
|
+
micLevel = Math.min(1, rms(raw) * 5);
|
|
617251
617274
|
};
|
|
617252
617275
|
source.connect(scriptProcessor);
|
|
617253
617276
|
scriptProcessor.connect(micCtx.destination);
|
|
@@ -617256,7 +617279,8 @@ async function toggleMic() {
|
|
|
617256
617279
|
micBtn.textContent = 'stop';
|
|
617257
617280
|
micBtn.classList.add('active');
|
|
617258
617281
|
hintEl.textContent = 'listening...';
|
|
617259
|
-
|
|
617282
|
+
// Declare the post-resample rate (and the raw context rate for debugging)
|
|
617283
|
+
ws.send(JSON.stringify({ type: 'mic_start', username: 'web-user', sampleRate: 16000, contextRate: actualRate }));
|
|
617260
617284
|
} catch (err) {
|
|
617261
617285
|
hintEl.textContent = 'mic error: ' + err.message;
|
|
617262
617286
|
}
|
|
@@ -617629,6 +617653,23 @@ connectPP();
|
|
|
617629
617653
|
</body>
|
|
617630
617654
|
</html>`;
|
|
617631
617655
|
}
|
|
617656
|
+
function resamplePcm16Mono(pcm, srcRate, dstRate) {
|
|
617657
|
+
if (srcRate === dstRate || srcRate <= 0 || dstRate <= 0) return pcm;
|
|
617658
|
+
const srcSamples = Math.floor(pcm.length / 2);
|
|
617659
|
+
if (srcSamples === 0) return pcm;
|
|
617660
|
+
const ratio = srcRate / dstRate;
|
|
617661
|
+
const outSamples = Math.max(1, Math.floor(srcSamples / ratio));
|
|
617662
|
+
const out = Buffer.allocUnsafe(outSamples * 2);
|
|
617663
|
+
for (let i2 = 0; i2 < outSamples; i2++) {
|
|
617664
|
+
const pos = i2 * ratio;
|
|
617665
|
+
const i0 = Math.min(srcSamples - 1, Math.floor(pos));
|
|
617666
|
+
const i1 = Math.min(srcSamples - 1, i0 + 1);
|
|
617667
|
+
const frac = pos - i0;
|
|
617668
|
+
const sample = pcm.readInt16LE(i0 * 2) * (1 - frac) + pcm.readInt16LE(i1 * 2) * frac;
|
|
617669
|
+
out.writeInt16LE(Math.max(-32768, Math.min(32767, Math.round(sample))), i2 * 2);
|
|
617670
|
+
}
|
|
617671
|
+
return out;
|
|
617672
|
+
}
|
|
617632
617673
|
function renderVoiceSessionStart(tunnelUrl) {
|
|
617633
617674
|
renderBoxedBlock({
|
|
617634
617675
|
title: "☁ Live Voice Session",
|
|
@@ -617936,17 +617977,26 @@ var init_voice_session = __esm({
|
|
|
617936
617977
|
clearInterval(keepaliveInterval);
|
|
617937
617978
|
}
|
|
617938
617979
|
}, 1e4);
|
|
617980
|
+
let clientPcmRate = 16e3;
|
|
617939
617981
|
ws.on("message", (data, isBinary) => {
|
|
617940
617982
|
if (isBinary) {
|
|
617941
617983
|
if (!this.ttsSpeaking) {
|
|
617942
|
-
|
|
617984
|
+
let pcm = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
617985
|
+
if (clientPcmRate !== 16e3 && clientPcmRate > 0) {
|
|
617986
|
+
pcm = resamplePcm16Mono(pcm, clientPcmRate, 16e3);
|
|
617987
|
+
}
|
|
617988
|
+
this.onUserAudio?.(pcm, clientId);
|
|
617943
617989
|
}
|
|
617944
617990
|
} else {
|
|
617945
617991
|
try {
|
|
617946
617992
|
const msg = JSON.parse(data.toString());
|
|
617947
|
-
if (msg.type === "mic_start"
|
|
617948
|
-
|
|
617949
|
-
|
|
617993
|
+
if (msg.type === "mic_start") {
|
|
617994
|
+
if (msg.username) {
|
|
617995
|
+
const entry = this.state.connectedUsers.get(clientId);
|
|
617996
|
+
if (entry) entry.username = msg.username;
|
|
617997
|
+
}
|
|
617998
|
+
const declared = Number(msg.sampleRate);
|
|
617999
|
+
clientPcmRate = Number.isFinite(declared) && declared >= 8e3 && declared <= 192e3 ? declared : 16e3;
|
|
617950
618000
|
}
|
|
617951
618001
|
} catch {
|
|
617952
618002
|
}
|
|
@@ -697514,6 +697564,9 @@ Rules:
|
|
|
697514
697564
|
// VAD segment capture
|
|
697515
697565
|
captureBuffer = "";
|
|
697516
697566
|
captureStartTime = 0;
|
|
697567
|
+
consensusMetadataSeen = false;
|
|
697568
|
+
consensusText = null;
|
|
697569
|
+
consensusSignalScore = null;
|
|
697517
697570
|
silenceTimer = null;
|
|
697518
697571
|
maxSegmentTimer = null;
|
|
697519
697572
|
lastSignalScore = null;
|
|
@@ -697604,18 +697657,25 @@ Rules:
|
|
|
697604
697657
|
let isFinal;
|
|
697605
697658
|
let snr;
|
|
697606
697659
|
let confidence2;
|
|
697660
|
+
let consensus;
|
|
697607
697661
|
if (typeof args[0] === "object" && args[0] !== null) {
|
|
697608
697662
|
const evt = args[0];
|
|
697609
697663
|
text2 = evt.text ?? "";
|
|
697610
697664
|
isFinal = evt.isFinal ?? false;
|
|
697611
697665
|
snr = evt.snr;
|
|
697612
697666
|
confidence2 = evt.confidence;
|
|
697667
|
+
consensus = typeof evt.consensus === "boolean" ? evt.consensus : void 0;
|
|
697613
697668
|
} else {
|
|
697614
697669
|
text2 = String(args[0] ?? "");
|
|
697615
697670
|
isFinal = Boolean(args[1]);
|
|
697671
|
+
const meta = args[2];
|
|
697672
|
+
if (meta && typeof meta === "object") {
|
|
697673
|
+
const m2 = meta;
|
|
697674
|
+
if (typeof m2.consensus === "boolean") consensus = m2.consensus;
|
|
697675
|
+
}
|
|
697616
697676
|
}
|
|
697617
697677
|
if (!text2.trim()) return;
|
|
697618
|
-
this.handleTranscript(text2.trim(), isFinal, snr, confidence2);
|
|
697678
|
+
this.handleTranscript(text2.trim(), isFinal, snr, confidence2, consensus);
|
|
697619
697679
|
};
|
|
697620
697680
|
this._onError = (err) => {
|
|
697621
697681
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -697682,7 +697742,7 @@ Rules:
|
|
|
697682
697742
|
// ---------------------------------------------------------------------------
|
|
697683
697743
|
// Transcript handling — VAD-style segment capture (Voryn pattern)
|
|
697684
697744
|
// ---------------------------------------------------------------------------
|
|
697685
|
-
handleTranscript(text2, isFinal, snr, confidence2) {
|
|
697745
|
+
handleTranscript(text2, isFinal, snr, confidence2, consensus) {
|
|
697686
697746
|
if (!this.active) return;
|
|
697687
697747
|
if (this._state !== "LISTENING" && this._state !== "CAPTURING") {
|
|
697688
697748
|
return;
|
|
@@ -697695,7 +697755,7 @@ Rules:
|
|
|
697695
697755
|
}
|
|
697696
697756
|
if (this._state === "LISTENING") {
|
|
697697
697757
|
this.setState("CAPTURING");
|
|
697698
|
-
this.
|
|
697758
|
+
this.resetCaptureState();
|
|
697699
697759
|
this.captureStartTime = Date.now();
|
|
697700
697760
|
this.maxSegmentTimer = setTimeout(() => {
|
|
697701
697761
|
if (this._state === "CAPTURING") {
|
|
@@ -697703,8 +697763,18 @@ Rules:
|
|
|
697703
697763
|
}
|
|
697704
697764
|
}, MAX_SEGMENT_MS);
|
|
697705
697765
|
}
|
|
697706
|
-
|
|
697707
|
-
this.lastSignalScore =
|
|
697766
|
+
const signalScore = typeof snr === "number" && !Number.isNaN(snr) ? clamp0116(snr) : computeSignalFromText(text2, confidence2);
|
|
697767
|
+
this.lastSignalScore = signalScore;
|
|
697768
|
+
if (consensus !== void 0) {
|
|
697769
|
+
this.consensusMetadataSeen = true;
|
|
697770
|
+
if (consensus === true) {
|
|
697771
|
+
this.consensusText = text2;
|
|
697772
|
+
this.consensusSignalScore = signalScore;
|
|
697773
|
+
this.captureBuffer = text2;
|
|
697774
|
+
}
|
|
697775
|
+
} else {
|
|
697776
|
+
this.captureBuffer = text2;
|
|
697777
|
+
}
|
|
697708
697778
|
this.emit("snr", { score: this.lastSignalScore });
|
|
697709
697779
|
this.onPartialTranscript(text2);
|
|
697710
697780
|
if (this.silenceTimer) clearTimeout(this.silenceTimer);
|
|
@@ -697715,11 +697785,22 @@ Rules:
|
|
|
697715
697785
|
}
|
|
697716
697786
|
}, waitMs);
|
|
697717
697787
|
}
|
|
697788
|
+
resetCaptureState() {
|
|
697789
|
+
this.captureBuffer = "";
|
|
697790
|
+
this.captureStartTime = 0;
|
|
697791
|
+
this.lastSignalScore = null;
|
|
697792
|
+
this.consensusMetadataSeen = false;
|
|
697793
|
+
this.consensusText = null;
|
|
697794
|
+
this.consensusSignalScore = null;
|
|
697795
|
+
}
|
|
697718
697796
|
// ---------------------------------------------------------------------------
|
|
697719
697797
|
// Segment finalization → Transcribing → Thinking → Speaking
|
|
697720
697798
|
// ---------------------------------------------------------------------------
|
|
697721
697799
|
finalizeSegment() {
|
|
697722
|
-
const
|
|
697800
|
+
const rawText = this.captureBuffer.trim();
|
|
697801
|
+
const requiresConsensus = this.consensusMetadataSeen;
|
|
697802
|
+
const text2 = requiresConsensus ? (this.consensusText ?? "").trim() : rawText;
|
|
697803
|
+
const signalScore = requiresConsensus ? this.consensusSignalScore ?? this.lastSignalScore : this.lastSignalScore;
|
|
697723
697804
|
if (this.silenceTimer) {
|
|
697724
697805
|
clearTimeout(this.silenceTimer);
|
|
697725
697806
|
this.silenceTimer = null;
|
|
@@ -697728,18 +697809,20 @@ Rules:
|
|
|
697728
697809
|
clearTimeout(this.maxSegmentTimer);
|
|
697729
697810
|
this.maxSegmentTimer = null;
|
|
697730
697811
|
}
|
|
697731
|
-
this.
|
|
697812
|
+
this.resetCaptureState();
|
|
697732
697813
|
if (!text2) {
|
|
697814
|
+
if (requiresConsensus && rawText) {
|
|
697815
|
+
if (this.debugSnr) this.onStatus(`Ignoring utterance without dual-ASR consensus: ${truncateForLog(rawText, 48)}`);
|
|
697816
|
+
this.emit("consensusFiltered", { text: rawText });
|
|
697817
|
+
}
|
|
697733
697818
|
this.setState("LISTENING");
|
|
697734
697819
|
return;
|
|
697735
697820
|
}
|
|
697736
|
-
const score = Math.min(
|
|
697821
|
+
const score = Math.min(signalScore ?? 1, computeSignalFromText(text2));
|
|
697737
697822
|
if (score < MIN_SIGNAL_SCORE || NOISE_ONLY_RE.test(text2)) {
|
|
697738
697823
|
if (this.debugSnr) this.onStatus(`Ignoring low-signal utterance (SNR:${score.toFixed(2)}): ${truncateForLog(text2, 48)}`);
|
|
697739
697824
|
this.emit("snrFiltered", { score, text: text2 });
|
|
697740
697825
|
this.setState("LISTENING");
|
|
697741
|
-
this.captureBuffer = "";
|
|
697742
|
-
this.lastSignalScore = null;
|
|
697743
697826
|
return;
|
|
697744
697827
|
}
|
|
697745
697828
|
this.setState("TRANSCRIBING");
|
|
@@ -76,10 +76,12 @@ def emit_error(msg: str):
|
|
|
76
76
|
emit({"type": "error", "message": msg})
|
|
77
77
|
|
|
78
78
|
|
|
79
|
-
def emit_transcript(text: str, is_final: bool = False, doa=None):
|
|
79
|
+
def emit_transcript(text: str, is_final: bool = False, doa=None, consensus=None):
|
|
80
80
|
event = {"type": "transcript", "text": text, "isFinal": is_final}
|
|
81
81
|
if doa is not None:
|
|
82
82
|
event["doa"] = doa
|
|
83
|
+
if consensus is not None:
|
|
84
|
+
event["consensus"] = bool(consensus)
|
|
83
85
|
emit(event)
|
|
84
86
|
|
|
85
87
|
# ---------------------------------------------------------------------------
|
|
@@ -441,7 +443,7 @@ def main():
|
|
|
441
443
|
result = run_transcribe(model, amplify(samples))
|
|
442
444
|
text = _segment_filtered_text(result)
|
|
443
445
|
if text:
|
|
444
|
-
emit_transcript(text, is_final=False)
|
|
446
|
+
emit_transcript(text, is_final=False, consensus=False)
|
|
445
447
|
except Exception as e:
|
|
446
448
|
emit_error(f"Transcription error: {e}")
|
|
447
449
|
|
|
@@ -481,7 +483,14 @@ def main():
|
|
|
481
483
|
if text == last_final_text:
|
|
482
484
|
return
|
|
483
485
|
last_final_text = text
|
|
484
|
-
|
|
486
|
+
# consensus=True only when BOTH models ran and converged — the
|
|
487
|
+
# downstream voicechat gate requires convergence for pass-through.
|
|
488
|
+
emit_transcript(
|
|
489
|
+
text,
|
|
490
|
+
is_final=True,
|
|
491
|
+
doa=tuning.doa() if tuning else None,
|
|
492
|
+
consensus=consensus_model is not None,
|
|
493
|
+
)
|
|
485
494
|
except Exception as e:
|
|
486
495
|
emit_error(f"Transcription error: {e}")
|
|
487
496
|
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnius",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.427",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "omnius",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.427",
|
|
10
10
|
"bundleDependencies": [
|
|
11
11
|
"image-to-ascii"
|
|
12
12
|
],
|
package/package.json
CHANGED