Created
September 28, 2025 06:32
-
-
Save skorotkiewicz/08b2f3b8c769a13499f9ebbca8c4d454 to your computer and use it in GitHub Desktop.
MFCC TS/JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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