Skip to content

Instantly share code, notes, and snippets.

@44100hertz
Created September 4, 2022 04:13
Show Gist options
  • Save 44100hertz/c1641d07a0e2af726e5f03880ff90abc to your computer and use it in GitHub Desktop.
Save 44100hertz/c1641d07a0e2af726e5f03880ff90abc to your computer and use it in GitHub Desktop.
hand-rolled FIR sinc filter
const WINDOW_SIZE = 4; /* Determines how much of normalized sinc function is sampled
1 = Just the center spike (-Pi/2 thru Pi/2)
*/
const SAMPLE_POINTS = 63; // Determines quality of output
const MIN_FREQ = 20; // Determines buffer length
const MAX_FREQ = 20000; // Determines shutoff frequency
const coefficients = Array(SAMPLE_POINTS).fill(0).map((_,i) => {
const thru = i / (SAMPLE_POINTS-1);
const pos = thru * WINDOW_SIZE - WINDOW_SIZE / 2.0;
const sinc = pos === 0 ? 1 : Math.sin(pos * Math.PI) / pos / Math.PI; // Sinc (lowpass FIR)
const tailoff = 1 - 2 * Math.abs(thru - 0.5); // Triangle
// console.log(thru, pos, sinc, tailoff, sinc * tailoff);
return sinc * tailoff;
});
console.log(coefficients);
const windowDivisor = coefficients.reduce((acc, x) => acc + x);
export default class Filter {
buffer: WaveBuffer;
private scale: number = 0; // How much to scale sinc window for desired frequency
private underflow: number = 0;
private mute: boolean = false;
private disable: boolean = false;
constructor(
private srate: number,
) {
this.buffer = new WaveBuffer(WINDOW_SIZE * srate / MIN_FREQ);
}
setFrequency(frequency: number) {
this.mute = frequency < MIN_FREQ;
this.disable = frequency >= MAX_FREQ;
this.scale = this.srate / frequency;
}
push(sample: number) {
this.underflow = 0;
this.buffer.push(sample);
}
getSample(): number {
++this.underflow;
if (this.mute) return 0; // TODO: fix popping
if (this.disable) return this.buffer.get(0);
let acc = 0;
for (let i=0; i<SAMPLE_POINTS; ++i) {
const samplePos = i / SAMPLE_POINTS;
const scale = coefficients[i];
acc += this.buffer.get(samplePos * this.scale * WINDOW_SIZE) * scale;
}
return acc / windowDivisor;
}
isStopped(): boolean {
return this.underflow >= this.scale * WINDOW_SIZE;
}
}
// A simple buffer for filter and delay effects
//
class WaveBuffer {
buf: number[];
pos: number;
constructor(
public buflen: number
) {
this.buf = new Array(buflen).fill(0);
this.pos = 0;
}
// Add a value to the ring buffer
push(v: number) {
this.buf[this.pos] = v;
this.pos = (this.pos + 1) % this.buflen;
}
// Get a value from [index] values ago.
get(index: number) {
if (index > this.buflen) return -1;
const pos = (this.pos - index + this.buflen) % this.buflen;
const ipos = Math.floor(pos);
const ipos2 = (ipos + 1) % this.buflen;
const fpos = pos - ipos;
return this.buf[ipos] * (1-fpos) + this.buf[ipos2] * fpos; // Linear interpolation
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment