repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/noko.js
Raw// noko.js — pure Noko speech-to-text pipeline helpers.
// NO I/O, NO native audio, NO ONNX imports. Receives samples + injected VAD/engine.
// Dual-mode: node `require` (tests) + QML import.
var TARGET_RATE = 16000;
var FRAME_MS = 30;
var FRAME_SAMPLES = 480;
function finiteNumber(value, fallback) {
var n = Number(value);
return isFinite(n) ? n : fallback;
}
function assertPositiveInt(value, name) {
var n = Number(value);
if (!Number.isInteger(n) || n <= 0) throw new Error(name + " must be a positive integer");
return n;
}
function interleavedToMonoFloat32(samples, channels) {
var ch = assertPositiveInt(channels || 1, "channels");
var input = samples || [];
var frames = Math.floor(input.length / ch);
var out = new Float32Array(frames);
for (var i = 0; i < frames; i++) {
var sum = 0;
for (var c = 0; c < ch; c++) sum += finiteNumber(input[i * ch + c], 0);
out[i] = Math.max(-1, Math.min(1, sum / ch));
}
return out;
}
function pcm16leToMonoFloat32(buffer, channels) {
var ch = assertPositiveInt(channels || 1, "channels");
var bytes = buffer || [];
if (bytes.length % 2 !== 0) throw new Error("pcm16le byte length must be even");
var sampleCount = Math.floor(bytes.length / 2);
var tmp = new Float32Array(sampleCount);
for (var i = 0; i < sampleCount; i++) {
var lo = bytes[i * 2];
var hi = bytes[i * 2 + 1];
var v = (hi << 8) | lo;
if (v & 0x8000) v -= 0x10000;
tmp[i] = Math.max(-1, Math.min(1, v / 32768));
}
return interleavedToMonoFloat32(tmp, ch);
}
function float32leToFloat32(buffer, channels) {
var ch = assertPositiveInt(channels || 1, "channels");
var bytes = buffer || [];
if (bytes.length % 4 !== 0) throw new Error("float32le byte length must be divisible by 4");
var sampleCount = Math.floor(bytes.length / 4);
var tmp = new Float32Array(sampleCount);
for (var i = 0; i < sampleCount; i++) {
var value = typeof bytes.readFloatLE === "function" ? bytes.readFloatLE(i * 4) : new DataView(bytes.buffer, bytes.byteOffset || 0, bytes.byteLength || bytes.length).getFloat32(i * 4, true);
tmp[i] = Math.max(-1, Math.min(1, finiteNumber(value, 0)));
}
return interleavedToMonoFloat32(tmp, ch);
}
function resampleLinear(samples, fromRate, toRate) {
var srcRate = assertPositiveInt(fromRate, "fromRate");
var dstRate = assertPositiveInt(toRate || TARGET_RATE, "toRate");
var input = samples || [];
if (input.length === 0 || srcRate === dstRate) return new Float32Array(input);
if (input.length === 1) return new Float32Array([finiteNumber(input[0], 0)]);
var outLen = Math.max(1, Math.round(input.length * dstRate / srcRate));
var out = new Float32Array(outLen);
var scale = (input.length - 1) / Math.max(1, outLen - 1);
for (var i = 0; i < outLen; i++) {
var pos = i * scale;
var left = Math.floor(pos);
var right = Math.min(input.length - 1, left + 1);
var frac = pos - left;
out[i] = finiteNumber(input[left], 0) * (1 - frac) + finiteNumber(input[right], 0) * frac;
}
return out;
}
function splitFrames(samples, frameSamples) {
var size = assertPositiveInt(frameSamples || FRAME_SAMPLES, "frameSamples");
var input = samples || [];
var count = Math.floor(input.length / size);
var out = [];
for (var i = 0; i < count; i++) out.push(new Float32Array(Array.prototype.slice.call(input, i * size, (i + 1) * size)));
return out;
}
function concatFrames(frames) {
var list = frames || [];
var total = 0;
for (var i = 0; i < list.length; i++) total += list[i].length;
var out = new Float32Array(total);
var offset = 0;
for (var j = 0; j < list.length; j++) {
out.set(list[j], offset);
offset += list[j].length;
}
return out;
}
function vadGate(frames, probabilities, options) {
var opts = options || {};
var threshold = finiteNumber(opts.threshold, 0.3);
var preRollLimit = assertPositiveInt(opts.preRoll === undefined ? 15 : opts.preRoll + 1, "preRoll") - 1;
var onsetLimit = assertPositiveInt(opts.onset || 2, "onset");
var hangoverLimit = assertPositiveInt(opts.hangover === undefined ? 15 : opts.hangover + 1, "hangover") - 1;
var list = frames || [];
var probs = probabilities || [];
var out = [];
var kept = [];
var pre = [];
var onset = [];
var inSpeech = false;
var hangover = 0;
for (var i = 0; i < list.length; i++) {
var speech = finiteNumber(probs[i], 0) >= threshold;
if (inSpeech) {
if (speech) {
out.push(list[i]);
kept.push(i);
hangover = hangoverLimit;
} else if (hangover > 0) {
out.push(list[i]);
kept.push(i);
hangover--;
} else {
inSpeech = false;
pre = [{ frame: list[i], index: i }];
onset = [];
}
continue;
}
if (speech) {
onset.push({ frame: list[i], index: i });
if (onset.length >= onsetLimit) {
for (var p = 0; p < pre.length; p++) out.push(pre[p].frame);
for (var pi = 0; pi < pre.length; pi++) kept.push(pre[pi].index);
for (var o = 0; o < onset.length; o++) out.push(onset[o].frame);
for (var oi = 0; oi < onset.length; oi++) kept.push(onset[oi].index);
inSpeech = true;
hangover = hangoverLimit;
pre = [];
onset = [];
}
} else {
onset = [];
pre.push({ frame: list[i], index: i });
if (pre.length > preRollLimit) pre.shift();
}
}
return {
samples: concatFrames(out),
frameIndexes: kept,
speechFrameCount: out.length
};
}
function postprocess(text, junkTokens) {
var out = String(text || "");
var junk = junkTokens || [];
for (var i = 0; i < junk.length; i++) out = out.split(String(junk[i])).join(" ");
return out.replace(/\s+/g, " ").trim();
}
function validateParakeetModelFiles(names) {
var files = (names || []).map(function (n) { return String(n).toLowerCase(); });
function has(part) {
for (var i = 0; i < files.length; i++) if (files[i].indexOf(part) >= 0) return true;
return false;
}
var missing = [];
if (!has("preprocessor") && !has("nemo")) missing.push("preprocessor");
if (!has("encoder")) missing.push("encoder");
if (!has("decoder") && !has("joint")) missing.push("decoder-or-joint");
if (!has("vocab") && !has("token") && !has("model_config")) missing.push("vocabulary-or-tokenizer");
return { ok: missing.length === 0, missing: missing };
}
function EngineState() {
this.state = "unloaded";
this.error = "";
}
EngineState.prototype.set = function (next, error) {
this.state = String(next);
this.error = String(error || "");
return this.state;
};
function historyRecord(transcript, speechSamples, meta) {
var info = meta || {};
return {
timestamp: info.timestamp || new Date().toISOString(),
transcript: String(transcript || ""),
sampleCount: speechSamples ? speechSamples.length : 0,
duration: speechSamples ? Math.round(speechSamples.length / TARGET_RATE * 1000) / 1000 : 0,
model: info.model || "parakeet",
quantization: info.quantization || "unknown",
runtimeProvider: info.runtimeProvider || "unknown",
audioPath: info.audioPath || "",
error: info.error || ""
};
}
function transcribePipeline(input, vad, engine, history, options) {
var opts = options || {};
var state = opts.state || new EngineState();
if (!input || !input.samples) throw new Error("no input samples");
if (!vad || typeof vad.probability !== "function") throw new Error("vad probability function missing");
if (!engine || typeof engine.transcribe !== "function") throw new Error("parakeet engine missing");
state.set("transcribing");
try {
var mono = interleavedToMonoFloat32(input.samples, input.channels || 1);
var resampled = resampleLinear(mono, input.sampleRate || TARGET_RATE, TARGET_RATE);
var frameList = splitFrames(resampled, FRAME_SAMPLES);
var probabilities = [];
for (var i = 0; i < frameList.length; i++) probabilities.push(Number(vad.probability(frameList[i], i)) || 0);
var gated = vadGate(frameList, probabilities, opts.vad || {});
if (gated.samples.length === 0) throw new Error("empty speech buffer after vad");
var raw = engine.transcribe(gated.samples, { sampleRate: TARGET_RATE });
var text = postprocess(raw && raw.text !== undefined ? raw.text : raw, opts.junkTokens || []);
if (!text) throw new Error("empty transcript");
var record = historyRecord(text, gated.samples, Object.assign({}, opts.history || {}, raw && raw.meta ? raw.meta : {}));
if (history && typeof history.write === "function") history.write(record);
state.set("ready");
return {
text: text,
sampleRate: TARGET_RATE,
speechSamples: gated.samples,
frameIndexes: gated.frameIndexes,
history: record,
state: state.state
};
} catch (error) {
state.set("failed", String(error && error.message ? error.message : error));
throw error;
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
TARGET_RATE: TARGET_RATE,
FRAME_MS: FRAME_MS,
FRAME_SAMPLES: FRAME_SAMPLES,
interleavedToMonoFloat32: interleavedToMonoFloat32,
pcm16leToMonoFloat32: pcm16leToMonoFloat32,
float32leToFloat32: float32leToFloat32,
resampleLinear: resampleLinear,
splitFrames: splitFrames,
concatFrames: concatFrames,
vadGate: vadGate,
postprocess: postprocess,
validateParakeetModelFiles: validateParakeetModelFiles,
EngineState: EngineState,
historyRecord: historyRecord,
transcribePipeline: transcribePipeline
};
}