Skip to content

Instantly share code, notes, and snippets.

@Joehoel
Last active May 14, 2024 09:57
Show Gist options
  • Save Joehoel/edfa6667dc1f93d711f088212db7ab8b to your computer and use it in GitHub Desktop.
Save Joehoel/edfa6667dc1f93d711f088212db7ab8b to your computer and use it in GitHub Desktop.
hoorsimulatie.ts
import { FFT } from 'fft.js';
import { decode } from 'wav-decoder';
import { encode } from 'wav-encoder';
import * as fs from 'fs';
// Constants
const TWO_PI = 2 * Math.PI;
const SAMPLE_RATE = 44100;
const FFT_SIZE = 2048;
const HALF_FFT_SIZE = FFT_SIZE / 2;
const TAP_SIZE = HALF_FFT_SIZE;
// Frequency and smearing values
const FREQUENCIES = [125, 187, 250, 375, 500, 750, 1000, 1500, 2000, 3000, 4000, 6000, 8000, 12000, 16000];
const SMEARING_VALUES = [0.4, 0.5, 0.9, 1.2, 1.5, 1.8, 2.0, 2.3, 3.1, 3.9];
// Utility Functions
function createLowPassFilter(cutoffFrequency: number, size: number): Float64Array {
const filter = new Float64Array(size);
const M = size - 1;
let sum = 0;
for (let i = 0; i < size; i++) {
const x = i - M / 2;
if (x === 0) {
filter[i] = cutoffFrequency * TWO_PI;
} else {
filter[i] = Math.sin(cutoffFrequency * TWO_PI * x) / x;
}
filter[i] *= 0.42 - 0.5 * Math.cos(TWO_PI * i / M) + 0.08 * Math.cos(2 * TWO_PI * i / M);
sum += filter[i];
}
for (let i = 0; i < size; i++) {
filter[i] /= sum;
}
return filter;
}
function applyFilter(fft: FFT, signal: Float64Array, filter: Float64Array): Float64Array {
const freqSignal = fft.createComplexArray();
fft.realTransform(freqSignal, signal);
fft.complete(freqSignal);
for (let i = 0; i < filter.length; i++) {
freqSignal[i * 2] *= filter[i];
freqSignal[i * 2 + 1] *= filter[i];
}
const filteredSignal = fft.createComplexArray();
fft.inverseTransform(filteredSignal, freqSignal);
return filteredSignal.subarray(0, signal.length);
}
function createSmearingFilter(size: number, bandwidth: number): Float64Array {
const filter = new Float64Array(size);
const halfSize = size / 2;
let sum = 0;
for (let i = 0; i < size; i++) {
const x = (i - halfSize) / bandwidth;
filter[i] = Math.exp(-Math.PI * x * x);
sum += filter[i];
}
for (let i = 0; i < size; i++) {
filter[i] /= sum;
}
return filter;
}
async function processAudio(filePath: string, outputFilePath: string, ear: number) {
const buffer = fs.readFileSync(filePath);
const audioData = await decode(buffer);
const channelData = audioData.channelData[0]; // assuming mono audio
const fft = new FFT(FFT_SIZE);
const lowPassFilter = createLowPassFilter(60 / HALF_FFT_SIZE, TAP_SIZE);
const smearingFilter = createSmearingFilter(FFT_SIZE, SMEARING_VALUES[Math.floor(averagePTA / 10)] * HALF_FFT_SIZE / Math.log2(SAMPLE_RATE));
const processedData = new Float64Array(channelData.length);
for (let i = 0; i < channelData.length; i += FFT_SIZE) {
const segment = channelData.subarray(i, i + FFT_SIZE);
const filteredSegment = applyFilter(fft, segment, lowPassFilter);
const smearedSegment = applyFilter(fft, filteredSegment, smearingFilter);
processedData.set(smearedSegment, i);
}
const encodedWav = await encode({
sampleRate: SAMPLE_RATE,
channelData: [processedData]
});
fs.writeFileSync(outputFilePath, Buffer.from(encodedWav));
}
processAudio('input.wav', 'output.wav', 1).then(() => {
console.log('Processing completed.');
}).catch(err => {
console.error('Error processing audio:', err);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment