Download lib/features.js from ryusei-46/transcriber-device-bench: direct link, hf CLI and curl.
- Browser
- Download file 4.57 kB
-
https://huggingface.co/spaces/ryusei-46/transcriber-device-bench/resolve/main/lib/features.js
- Command line
-
hf download hf://spaces/ryusei-46/transcriber-device-bench/lib/features.js
-
curl -L -o features.js https://huggingface.co/spaces/ryusei-46/transcriber-device-bench/resolve/main/lib/features.js
4.57 kB
| // NeMo の AudioToMelSpectrogramPreprocessor(推論時・dither なし)と同じ log メル。 | |
| // 11-10 の Parakeet Ultra の測定ページ(test/asr-bench-2026-11-10-eu/browser/harness/app.js)の写しで、メルの本数を引数にした。 | |
| // プリエンファシス 0.97 → 両端に 256 の 0 → 窓 400(対称の Hann を 512 の中央)・hop 160 の FFT 512 のパワー → メル(librosa の slaney、fp32) | |
| // → log(x + 2^-24) → 特徴ごとに平均と不偏標準偏差(+1e-5)で正規化。フレーム数は詰めない(pad_to 0 / 1)。 | |
| // FastConformer(言語別)は 80 本、Parakeet Ultra は 128 本。表(assets/mel{80,128}.bin)は scripts/make_space_assets.py が librosa で作った。 | |
| const N_FFT = 512; | |
| const HOP = 160; | |
| const N_BINS = N_FFT / 2 + 1; | |
| const BITREV = new Uint16Array(N_FFT); | |
| { | |
| const bits = Math.log2(N_FFT); | |
| for (let i = 0; i < N_FFT; i++) { | |
| let r = 0; | |
| for (let b = 0; b < bits; b++) r |= ((i >> b) & 1) << (bits - 1 - b); | |
| BITREV[i] = r; | |
| } | |
| } | |
| const TW_RE = new Float64Array(N_FFT / 2); | |
| const TW_IM = new Float64Array(N_FFT / 2); | |
| for (let i = 0; i < N_FFT / 2; i++) { | |
| TW_RE[i] = Math.cos((-2 * Math.PI * i) / N_FFT); | |
| TW_IM[i] = Math.sin((-2 * Math.PI * i) / N_FFT); | |
| } | |
| function fft512(re, im) { | |
| for (let i = 0; i < N_FFT; i++) { | |
| const j = BITREV[i]; | |
| if (j > i) { | |
| let t = re[i]; | |
| re[i] = re[j]; | |
| re[j] = t; | |
| t = im[i]; | |
| im[i] = im[j]; | |
| im[j] = t; | |
| } | |
| } | |
| for (let size = 2; size <= N_FFT; size <<= 1) { | |
| const half = size >> 1; | |
| const step = N_FFT / size; | |
| for (let start = 0; start < N_FFT; start += size) { | |
| for (let k = 0; k < half; k++) { | |
| const wr = TW_RE[k * step]; | |
| const wi = TW_IM[k * step]; | |
| const a = start + k; | |
| const b = a + half; | |
| const xr = re[b] * wr - im[b] * wi; | |
| const xi = re[b] * wi + im[b] * wr; | |
| re[b] = re[a] - xr; | |
| im[b] = im[a] - xi; | |
| re[a] += xr; | |
| im[a] += xi; | |
| } | |
| } | |
| } | |
| } | |
| /** | |
| * @param {number} nMels 80 か 128 | |
| * @param {Float32Array} table [nMels × 257 のフィルタバンク, 512 の窓] | |
| * @returns {(audio: Float32Array) => { mel: Float32Array, T: number, seqLen: number }} mel は [nMels × T](T = seqLen + 1) | |
| */ | |
| export function makeLogMel(nMels, table) { | |
| const FB = table.subarray(0, nMels * N_BINS); | |
| const WIN = table.subarray(nMels * N_BINS, nMels * N_BINS + N_FFT); | |
| // フィルタの 0 でない範囲(0 を足しても和は変わらないので、範囲だけ回して同じ値になる) | |
| const lo = new Int32Array(nMels); | |
| const hi = new Int32Array(nMels); | |
| for (let m = 0; m < nMels; m++) { | |
| let a = N_BINS; | |
| let b = -1; | |
| for (let k = 0; k < N_BINS; k++) { | |
| if (FB[m * N_BINS + k] !== 0) { | |
| if (a === N_BINS) a = k; | |
| b = k; | |
| } | |
| } | |
| lo[m] = a; | |
| hi[m] = b + 1; | |
| } | |
| const re = new Float64Array(N_FFT); | |
| const im = new Float64Array(N_FFT); | |
| const power = new Float32Array(N_BINS); | |
| return (audio) => { | |
| const n = audio.length; | |
| const seqLen = Math.floor(n / HOP); | |
| const x = new Float32Array(n + N_FFT); | |
| const pad = N_FFT / 2; | |
| if (n > 0) x[pad] = audio[0]; | |
| for (let i = 1; i < n; i++) x[pad + i] = Math.fround(audio[i] - Math.fround(0.97 * audio[i - 1])); | |
| const frames = 1 + Math.floor((x.length - N_FFT) / HOP); | |
| const T = frames; | |
| const mel = new Float32Array(nMels * T); | |
| for (let f = 0; f < frames; f++) { | |
| const off = f * HOP; | |
| for (let i = 0; i < N_FFT; i++) { | |
| re[i] = Math.fround(x[off + i] * WIN[i]); | |
| im[i] = 0; | |
| } | |
| fft512(re, im); | |
| for (let k = 0; k < N_BINS; k++) power[k] = re[k] * re[k] + im[k] * im[k]; | |
| for (let m = 0; m < nMels; m++) { | |
| const row = m * N_BINS; | |
| let s = 0; | |
| for (let k = lo[m]; k < hi[m]; k++) s += FB[row + k] * power[k]; | |
| mel[m * T + f] = Math.log(Math.fround(s) + 2 ** -24); | |
| } | |
| } | |
| // 特徴ごとの正規化(seqLen フレームの平均と不偏標準偏差 + 1e-5)。seqLen 以降は 0 | |
| const L = Math.max(1, seqLen); | |
| for (let m = 0; m < nMels; m++) { | |
| const row = m * T; | |
| let mean = 0; | |
| for (let f = 0; f < L; f++) mean += mel[row + f]; | |
| mean /= L; | |
| let v = 0; | |
| for (let f = 0; f < L; f++) v += (mel[row + f] - mean) ** 2; | |
| const std = Math.sqrt(v / Math.max(1, L - 1)) + 1e-5; | |
| for (let f = 0; f < T; f++) mel[row + f] = f < seqLen ? (mel[row + f] - mean) / std : 0; | |
| } | |
| return { mel, T, seqLen }; | |
| }; | |
| } | |