Linear Filters with WebAudio API
 October 30, 2018    Antonin Novak
education    education_signals    Click here to start the audio:
Select filter-type:
f = 1000 Hz
Q = 0
G = 0 dB
'use srict';
// Web Audio API part
let file_buffer;
let source_node;
const audio_context = new (window.AudioContext || window.webkitAudioContext)();
const playButton = document.querySelector('#play');
const stopButton = document.querySelector('#stop');
const type_of_filter = document.querySelector('#type_of_filter_selector');
const filter = audio_context.createBiquadFilter();
filter.type = 'lowpass';
filter.Q.value = 0;
filter.frequency.value = 1000;
filter.gain.value = 0;
filter.connect(audio_context.destination);
window.fetch('../../audio_sample.wav')
.then(response => response.arrayBuffer())
.then(arrayBuffer => audio_context.decodeAudioData(arrayBuffer))
.then(audioBuffer => {
playButton.disabled = false;
file_buffer = audioBuffer;
});
playButton.onclick = () => play(file_buffer);
stopButton.onclick = () => stop();
function play(audioBuffer) {
source_node = audio_context.createBufferSource();
source_node.buffer = audioBuffer;
source_node.loop = true;
source_node.connect(filter);
source_node.start();
playButton.disabled = true;
stopButton.disabled = false;
};
function stop(){
source_node.stop();
playButton.disabled = false;
stopButton.disabled = true;
};
// Frequency Repsonse Function
const FRF_frequency_axes = logspace(1, 4, 500);
let magResponseOutput = new Float32Array(FRF_frequency_axes.length);
let phaseResponseOutput = new Float32Array(FRF_frequency_axes.length);
let FRF_magnitude = [];
let FRF_phase = [];
for(let i = 0; i < FRF_frequency_axes.length; i++){
FRF_magnitude[i] = 0;
FRF_phase[i] = 0;
}
// Plotly figure
Plotly.newPlot('mydiv1', [{
x: FRF_frequency_axes,
y: FRF_magnitude,
type: 'line'
},{
x: FRF_frequency_axes,
y: FRF_phase,
type: 'line',
yaxis: 'y2'
}], {
xaxis: {
title: 'Frequency [Hz]',
type: 'log',
range: [1, 4]
},
yaxis: {
title: 'Magnitude [dB]',
range: [-20, 20],
titlefont: {color: 'rgb(30, 120, 180)'},
tickfont: {color: 'rgb(30, 120, 180)'}
},
yaxis2: {
title: 'Phase [deg]',
range: [-200, 200],
overlaying: 'y',
titlefont: {color: 'rgb(255, 127, 14)'},
tickfont: {color: 'rgb(255, 127, 14)'},
side: 'right'
},
margin: {l: 60, r: 60, b: 40, t: 20, pad: 1},
showlegend: false
}, {
displayModeBar: false,
responsive: true
}
);
// prepare the data
calcFrequencyResponse();
const id_divQ = document.querySelector('#div_Q');
const id_divG = document.querySelector('#div_G');
id_divG.style.display = "none";
// Callback functions changeFilter (select menu)
changeFilter = function(value){
filter.type = value;
calcFrequencyResponse();
// divQ display ?
if (['lowshelf','highshelf'].includes(value)) {
id_divQ.style.display = "none";
} else {
id_divQ.style.display = "block";
}
// divG display ?
if (['lowpass','highpass','bandpass','allpass'].includes(value)) {
id_divG.style.display = "none";
} else {
id_divG.style.display = "block";
}
// set Q limits
if (['lowpass','highpass'].includes(value)) {
document.getElementById("ChangeQSlider").min = -10;
document.getElementById("ChangeQSlider").value = 0;
changeQ(0);
} else {
document.getElementById("ChangeQSlider").min = 0;
document.getElementById("ChangeQSlider").value = 1;
changeQ(1);
}
}
// Callback functions changeFreq (slider)
changeFreq = function(Freq) {
document.getElementById("ChangeFreqValue").innerHTML = "F = " + Freq.toString() + " Hz";
filter.frequency.value = Freq;
calcFrequencyResponse();
}
// Callback functions changeQ (slider)
changeQ = function(Q) {
document.getElementById("ChangeQValue").innerHTML = "Q = " + Q.toString();
filter.Q.value = Q;
calcFrequencyResponse();
}
// Callback functions changeG (slider)
changeG = function(G) {
document.getElementById("ChangeGValue").innerHTML = "G = " + G.toString() + " dB";
filter.gain.value = G;
calcFrequencyResponse();
}
function calcFrequencyResponse() {
filter.getFrequencyResponse(FRF_frequency_axes, magResponseOutput, phaseResponseOutput);
for(let i = 0; i < FRF_frequency_axes.length; i++){
FRF_magnitude[i] = 20*Math.log10( Math.abs( magResponseOutput[i] ) );
FRF_phase[i] = 180/Math.PI*phaseResponseOutput[i];
}
//Plotly.restyle('mydiv1', 'y', [FRF_magnitude]);
Plotly.animate('mydiv1', {
data: [{y: FRF_magnitude},{y: FRF_phase}],
traces: [0,1],
layout: {}
}, {
transition: {
duration: 500,
easing: 'cubic-in-out'
},
frame: {
duration: 500
}
})
}
function logspace(from, to, num) {
let a = (to-from)/(num-1);
let data = [...Array(num).keys()].map(x => Math.pow(10,a*x + from));
return new Float32Array(data);
}