0
$\begingroup$

I'm creating an HTML that applies a low-pass Windowed Sinc filter with a window size of 8192 to white, pink, or Brownian noise. There are nine radio buttons, eight of which set a cutoff frequency and one which disables the filter.

I've got the noise generator working, with all three "colours" generating noise with no problem.

I've been looking for a way to implement a Windowed Sinc filter using the Blackman window in JavaScript. I've found one site that seemed to be pretty promising, but on that site, the event listeners are implemented and the processing itself is done (in a script called "dfilter-0.js") which is extremely large and messy. There are so many unnecessary functions and chains of functions that I can never figure out where anything is or what anything does.

My question is, could I get some help cleaning up the "dfilter-0.js" (none of the online tools I've found worked)? Or alternatively, how does one implement a low pass Windowed Sinc filter with the parameters (cutoff frequency, Blackman window size) and connect it to an AudioContext?

I've found an Audacity plugin that performs the filtering that I want, but it uses the Nyquist language. How would I implement that in JavaScript and HTML?

$\endgroup$

2 Answers 2

0
$\begingroup$

I stumbled into FIR filters this week for another reason, and had a go implementing one with ConvolverNode as Marcus suggested.

I found a library called fili.js and used the FIR code from there with minor edits for the blackman window. It seems to work OK, though I'm not sure what number to use as the filter's order.

function createLowpassKernel(sampleRate, cutoff, order) {
    const omega = 2 * Math.PI * cutoff / sampleRate;
    const kernel = new Float32Array(order + 1);
    let sum = 0;

    for (let i = 0; i <= order; i++) {
        // Sinc impulse
        const offset = i - order / 2;
        const impulse = offset === 0 ? omega : Math.sin(omega * offset) / offset;

        // Blackman window
        const theta = 2 * Math.PI * (i / order);
        const win = 0.42 - 0.5 * Math.cos(theta) + 0.08 * Math.cos(theta * 2);

        // Apply the blackman window to the sinc impulse
        const result = impulse * win;
        kernel[i] = result;
        sum += result;
    }

    // Normalize kernel
    for (let i = 0; i <= order; ++i) {
        kernel[i] /= sum;
    }

    return kernel;
}

let audioCtx;

function setupAudio() {
    // Create a new audio context if necessary
    if (audioCtx) return;
    audioCtx = new window.AudioContext();

    // Make a lowpass kernel based on an order and cutoff
    const order = 8192;
    const cutoff = 2000;
    const sampleRate = audioCtx.sampleRate;
    const kernel = createLowpassKernel(sampleRate, cutoff, order);

    // Create a convolver to apply the kernel
    const convolver = audioCtx.createConvolver();
    convolver.normalize = false; // We already normalized it, don't normalize again
    convolver.connect(audioCtx.destination);

    // Create a buffer source to play audio for convolving
    const bufferSource = audioCtx.createBufferSource();
    bufferSource.connect(convolver);

    // Convolvers require an ArrayBuffer, so make one from the Float32Array
    const arrayBuffer = audioCtx.createBuffer(1, order + 1, sampleRate);
    arrayBuffer.copyToChannel(kernel, 0);
    convolver.buffer = arrayBuffer;

    // Fetch and play audio through the convolution filter
    fetch("https://s3-us-west-2.amazonaws.com/s.cdpn.io/858/outfoxing.mp3")
        .then(data => data.arrayBuffer())
        .then(arrayBuffer => audioCtx.decodeAudioData(arrayBuffer))
        .then(audio => {
            bufferSource.buffer = audio;
            bufferSource.start();
        });
}
<button onclick="setupAudio();">Setup Audio</button>

That being said, it's much easier to use a BiquadFilterNode. Though biquad filters are IIR, you can get a pretty similar result with a couple of layers in a butterworth formation.

$\endgroup$
-1
$\begingroup$

A FIR filter is just a "block" of coefficients, a vector, being convolved with your signal.

Hence, https://developer.mozilla.org/en-US/docs/Web/API/ConvolverNode is kind of audio filtering object you need to create and apply.

$\endgroup$
1
  • $\begingroup$ So, I have a function that calculates the impulse response for a Blackman window as an array of numbers. Can I just input that as the buffer for a ConvolverNode? $\endgroup$
    – 000
    Mar 14, 2019 at 3:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.