Skip to content

Instantly share code, notes, and snippets.

@skorotkiewicz
Created September 28, 2025 06:32
Show Gist options
  • Save skorotkiewicz/08b2f3b8c769a13499f9ebbca8c4d454 to your computer and use it in GitHub Desktop.
Save skorotkiewicz/08b2f3b8c769a13499f9ebbca8c4d454 to your computer and use it in GitHub Desktop.
MFCC TS/JS
import FFT from "fft.js";
function hzToMel(hz: number): number {
return 2595 * Math.log10(1 + hz / 700);
}
function melToHz(mel: number): number {
return 700 * (Math.pow(10, mel / 2595) - 1);
}
function createMelFilterbank(
sampleRate: number,
fftSize: number,
numFilters: number,
minHz = 0,
maxHz = sampleRate / 2
) {
const melMin = hzToMel(minHz);
const melMax = hzToMel(maxHz);
const melPoints = Array.from({ length: numFilters + 2 }, (_, i) =>
melToHz(melMin + ((melMax - melMin) * i) / (numFilters + 1))
);
const binPoints = melPoints.map(f => Math.floor((fftSize + 1) * f / sampleRate));
const filters: number[][] = [];
for (let i = 1; i < melPoints.length - 1; i++) {
const filter = new Array(fftSize / 2 + 1).fill(0);
for (let j = binPoints[i - 1]; j < binPoints[i]; j++) {
filter[j] = (j - binPoints[i - 1]) / (binPoints[i] - binPoints[i - 1]);
}
for (let j = binPoints[i]; j < binPoints[i + 1]; j++) {
filter[j] = (binPoints[i + 1] - j) / (binPoints[i + 1] - binPoints[i]);
}
filters.push(filter);
}
return filters;
}
export function calculateMFCC(
data: Float32Array,
sampleRate = 44100,
numCoeffs = 13,
numFilters = 26
): number[] {
const fftSize = 1024;
const fft = new FFT(fftSize);
// 1. Pre-emphasis
const preEmphasized = new Float32Array(data.length);
preEmphasized[0] = data[0];
for (let i = 1; i < data.length; i++) {
preEmphasized[i] = data[i] - 0.97 * data[i - 1];
}
// 2. Pierwsza ramka (normalnie bierzesz wiele)
const frame = preEmphasized.slice(0, fftSize);
// 3. Okno Hamming’a
for (let i = 0; i < frame.length; i++) {
frame[i] *= 0.54 - 0.46 * Math.cos((2 * Math.PI * i) / (frame.length - 1));
}
// 4. FFT
const out = fft.createComplexArray();
fft.realTransform(out, frame);
fft.completeSpectrum(out);
const mags = new Float32Array(fftSize / 2 + 1);
for (let i = 0; i < mags.length; i++) {
const re = out[2 * i];
const im = out[2 * i + 1];
mags[i] = Math.sqrt(re * re + im * im);
}
// 5. Mel filterbank
const filters = createMelFilterbank(sampleRate, fftSize, numFilters);
const energies = filters.map(f =>
f.reduce((sum, val, j) => sum + val * mags[j], 0)
);
// 6. Log
const logEnergies = energies.map(e => Math.log(e + 1e-10));
// 7. DCT-II
const mfccs: number[] = [];
for (let k = 0; k < numCoeffs; k++) {
let sum = 0;
for (let n = 0; n < numFilters; n++) {
sum += logEnergies[n] * Math.cos(Math.PI * k * (2 * n + 1) / (2 * numFilters));
}
mfccs.push(sum);
}
return mfccs;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment