Descripción
La Web Audio API nos permite enrutar sonidos de nodo en nodo y crear canales de procesado complejos para nuestras cadenas de audio.
Para crear un filtro utilizamos la interfaz BiquadFilterNode
, la cual nos permite crear diferentes filtros básicos de segundo orden, que podremos colocar entre la fuente de audio y la salida. Esta interfaz contiene hasta 8 tipos de filtros de audio, activos y pasivos, que podemos utilizar para construir ecualizadores gráficos y otros procesadores de señal de audio, creando nodos de filtrado y especificando, ente sus parámetros básicos, qué parte espectro de frecuencia amplificar o atenuar.
Entre los filtros más comunes que podemos utilizar están:
- Filtro paso bajo: Filtro pasivo de segundo orden con una caida de 12 dB/octava. Podemos especificar los parámetros frequency y Q, que representan la frecuencia de corte y el factor de calidad respectivamente.
- Filtro paso alto: Filtro pasivo de segundo orden con una caida de 12 dB/octava. Podemos especificar los parámetros frequency y Q, que representan la frecuencia de corte y el factor de calidad respectivamente.
- Filtro paso banda: Filtro pasivo de segundo orden. Podemos especificar los parámetros frequency y Q, que representan la frecuencia central y el factor de calidad respectivamente.
- Filtro de graves: Filtro activo con parámetros frequency, gain y Q, que representan la frecuencia de corte, la ganancia o atenuación (±40dB) y el factor de calidad respectivamente.
- Filtro de agudos: Filtro activo con parámetros frequency, gain y Q, que representan la frecuencia de corte, la ganancia o atenuación (±40dB) y el factor de calidad respectivamente.
- Filtro de banda: Filtro activo con parámetros frequency, gain y Q, que representan la frecuencia central, la ganancia o atenuación (±40dB) y el factor de calidad respectivamente.<
Los filtros incluyen parámetros básicos para controlar la función de transferencia, podremos especificar una cierta cantidad de la ganancia, la frecuencia central, o de corte a la que debe aplicarse el filtro, y un factor de calidad. El filtro de paso bajo, por ejemplo, mantiene el rango de frecuencia más baja, pero descarta las frecuencias altas. El punto de corte está determinada por el valor de la frecuencia, y el factor Q que determina la curva de la función de transferencia, pudiendo hacer, por ejemplo, un filtro paso banda más selectivo. El parámetro ganancia sólo está disponible para los filtros activos.
Para crear un filtro paso bajo y extraer las componentes de baja frecuencia de nuestra muestra de sonido, podríamos utilizar el siguiente código:
// Creamos el filtro
contextoDeAudio.createBiquadFilter();
// Conectamos nuestra fuente de audio al filtro, y éste a la salida
fuenteDeAudio.connect(filtro);
filtro.connect(contextoDeAudio.destination);
// Podemos especificar algunos parámetros del paso bajo
filtro.type = 0;
// Para el paso bajo. Revisa la especificación W3C de la Web Audio API
filtro.frequency.value = 440; // establecemos el corte en 440 HZ
//Reproducimos el sonido.
fuenteDeAudio.start(0);
function cadenaDeAudio(fuente) {
//set de filtros
contextoDeAudio.graves = contextoDeAudio.createBiquadFilter();
contextoDeAudio.agudos = contextoDeAudio.createBiquadFilter();
contextoDeAudio.paso_bajo = contextoDeAudio.createBiquadFilter();
//Variables controlables desde usuario:
var lowpass = document.querySelector('#lowpass').value;
var lowpassq = document.querySelector('#lowpassq').value;
var frec_graves = document.querySelector('#graves').value;
var gan_graves = document.querySelector('#gravesdB').value;
var frec_agudos = document.querySelector('#agudos').value;
var gan_agudos = document.querySelector('#agudosdB').value;
//var bandpass = document.querySelector('#bandpass').value;
contextoDeAudio.graves.type = 3 // Graves
contextoDeAudio.graves.frequency.value = frec_graves
contextoDeAudio.agudos.type = 4 // Agudos
contextoDeAudio.agudos.frequency.value = frec_agudos
contextoDeAudio.paso_bajo.type = 0; // Paso Bajo
contextoDeAudio.paso_bajo.frequency.value = lowpass;
//ajustes de volumen
var volumen = document.querySelector('#volumen').value;
if (esFirefox) { contextoDeAudio.volumen = contextoDeAudio.createGain();}else{
contextoDeAudio.volumen = contextoDeAudio.createGainNode();}
contextoDeAudio.analizador = contextoDeAudio.createAnalyser();
//Analizador
contextoDeAudio.analizador.smoothingTimeConstant = 0.3;
contextoDeAudio.analizador.fftSize = 1024;
contextoDeAudio.analizador.minDecibels = -120;
contextoDeAudio.analizador.maxDecibels = 0;
//Ruteo
fuente.connect(contextoDeAudio.volumen);
//el filtro comienza desactivado
//contextoDeAudio.paso_bajo.connect(contextoDeAudio.volumen)
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.analizador.connect(contextoDeAudio.destination);
return fuente;
}
function dibujaEspectro() {
ctxe.clearRect(0, 0, ancho, alto);
var datos_frecuencia = new Uint8Array(contextoDeAudio.analizador.frequencyBinCount);
contextoDeAudio.analizador.getByteFrequencyData(datos_frecuencia);
NumerodeBarras = Math.round(ancho / resolucion);
for (var i = 0; i < NumerodeBarras; i++) {
//for (var i = 0; i < (datos_frecuencia.length); i++) {
var magnitud = datos_frecuencia[i];
// ajustamos las variables para dibujarlas en el canvas
//dibujamos tipo hue ;)
var hue = i / datos_frecuencia.length * 360;
//Marco la frecuencia de corte si los filtros pasivos están activados
if(i==indice && ($('#c1').prop('checked')) ){ctxe.fillStyle='#FFF';}else{
ctxe.fillStyle = 'hsl(' + hue + ', 100%, 50%)';}
ctxe.fillRect(resolucion * i, alto, resolucion - 2, -magnitud + 60);
}
//Actualizamos la barra de progreso en tiempo real
actualiza_barra(retardo);
//Cremao el bucle de RequestAnimationFrame invocando de nuevo a la función.
requestAnimationFrame(dibujaEspectro.bind(this));
}
function potenciometro(slider) {
if (slider.id == 'volumen') {
var volumen = slider.value;
contextoDeAudio.volumen.gain.value = volumen;
document.querySelector('#varvol').innerHTML = volumen;
} else if (slider.id == 'tipofiltro') {
//Seleccionamos entre la carta de filtros pasivos que el nodo BiquadFilterNode posee.
if(slider.value=='bajo'){contextoDeAudio.paso_bajo.type=0;ruteoPasivos();$('#corte').html('Frecuencia de corte:')}
if(slider.value=='alto'){contextoDeAudio.paso_bajo.type=1;ruteoPasivos();$('#corte').html('Frecuencia de corte:')}
if(slider.value=='banda'){contextoDeAudio.paso_bajo.type=2;ruteoPasivos();$('#corte').html('Frecuencia central:')}
}else if (slider.id == 'lowpass') {
// Movemos la frecuencia entre el mínimo valor (40 Hz) y la mitad de la frecuencia de muestreo.
var frec = slider.value;
var minFrec = 10;
// Logaritmo (base 2) para calcular cuantas octavas caen en el rango.
//var numerodeOctavas = Math.log(maxFrec / minFrec) / Math.LN2;
// Calculamos un multiplicador de 0 a 1 basado en una escala exponencial.
//var multiplicador = Math.pow(2, numerodeOctavas * (porcentaje - 1.0));
// Multiplicador por la frecuencia máxima.
//contextoDeAudio.paso_bajo.frequency.value = maxFrec * multiplicador;
indice= Math.round(frec/nyquist*NumerodeBarras);
contextoDeAudio.paso_bajo.frequency.value = frec;
//document.querySelector('#valorlowpass').innerHTML = (maxFrec * multiplicador).toFixed(2);
document.querySelector('#valorlowpass').innerHTML =frec;
} else if (slider.id == 'lowpassq') {
var factor = slider.value;
//Factor de calidad de 0 a 30
contextoDeAudio.paso_bajo.Q.value = factor;
document.querySelector('#valorlowpassq').innerHTML = Math.round(factor * 30);
} else if (slider.id == 'graves') {
var frec = slider.value;
//Ajuste de fecuencia del filtro de graves
contextoDeAudio.graves.frequency.value = frec;
indice= Math.round(frec/nyquist*NumerodeBarras);
document.querySelector('#valorfgraves').innerHTML = frec;
} else if (slider.id == 'gravesdB') {
var dBg = slider.value;
//dB de filtro de graves
contextoDeAudio.graves.gain.value = dBg;
console.log('contextoDeAudio.agudos.gain: ', dBg);
document.querySelector('#valorgravesdB').innerHTML = dBg;
} else if (slider.id == 'agudos') {
var frec = slider.value;
//Ajuste de frec para filtro de agudos
contextoDeAudio.agudos.frequency.value = frec;
indice= Math.round(frec/nyquist*NumerodeBarras);
document.querySelector('#valorfagudos').innerHTML = frec;
} else if (slider.id == 'agudosdB') {
var dBa = slider.value;
//dB ganancia en agudos
contextoDeAudio.agudos.gain.value = dBa;
console.log('contextoDeAudio.agudos.gain ', dBa);
document.querySelector('#valoragudosdB').innerHTML = dBa;
}
}
function activaFiltro(elemento) {
miFuente.disconnect(0);
contextoDeAudio.paso_bajo.disconnect(0);
contextoDeAudio.agudos.disconnect(0)
contextoDeAudio.graves.disconnect(0)
// Verifica el check.
if ($('#g1').prop('checked') && $('#a1').prop('checked')) {
$('input#c1').attr('checked', false);
//Si los activos están funcionando, desactivamos los pasivos
//Llamamos a la función que enruta los filtros activos
ruteoActivos();
} else if ($('#a1').prop('checked') && ($('#g1').prop('checked') == false)) {
$('input#c1').attr('checked', false);
ruteoActivoAgudos();
} else if ($('#g1').prop('checked') && ($('#a1').prop('checked') == false)) {
$('input#c1').attr('checked', false);
ruteoActivoGraves();
} else if ($('#c1').prop('checked')) {
ruteoPasivos();
} else {
// Conectar Directamente.
miFuente.connect(contextoDeAudio.volumen);
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.analizador.connect(contextoDeAudio.destination);
}
}
function ruteoActivos(){
//Ajustamos las variables del filtro según lo configurado en pantalla
var frec_graves = document.querySelector('#graves').value;
var gan_graves = document.querySelector('#gravesdB').value;
var frec_agudos = document.querySelector('#agudos').value;
var gan_agudos = document.querySelector('#agudosdB').value;
//asignamos los valores a los filtros activos,
contextoDeAudio.graves.gain = gan_graves;
contextoDeAudio.graves.frequency.value = frec_graves;
contextoDeAudio.agudos.gain = gan_agudos;
contextoDeAudio.agudos.frequency.value = frec_agudos;
//Ruteo de la funete en función del tipo de filtro sleccionado
miFuente.connect(contextoDeAudio.graves);
contextoDeAudio.graves.connect(contextoDeAudio.agudos);
contextoDeAudio.agudos.connect(contextoDeAudio.volumen)
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.volumen.connect(contextoDeAudio.destination);
}
function ruteoActivoAgudos(){
//Ajustamos las variables del filtro según lo configurado en pantalla
var frec_agudos = document.querySelector('#agudos').value;
var gan_agudos = document.querySelector('#agudosdB').value;
contextoDeAudio.agudos.gain = gan_agudos;
contextoDeAudio.agudos.frequency.value = frec_agudos;
//Ruteo de la funete en función del tipo de filtro sleccionado
miFuente.connect(contextoDeAudio.agudos);
contextoDeAudio.agudos.connect(contextoDeAudio.volumen)
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.volumen.connect(contextoDeAudio.destination);
}
function ruteoActivoGraves(){
//Ajustamos las variables del filtro según lo configurado en pantalla
var frec_graves = document.querySelector('#graves').value;
var gan_graves = document.querySelector('#gravesdB').value;
contextoDeAudio.graves.gain = gan_graves;
contextoDeAudio.graves.frequency.value = frec_graves;
//Ruteo de la funete en función del tipo de filtro sleccionado
miFuente.connect(contextoDeAudio.graves);
contextoDeAudio.graves.connect(contextoDeAudio.volumen)
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.volumen.connect(contextoDeAudio.destination);
}
function ruteoPasivos(){
//Desconectamos el resto de filtros para escuchar el efecto correctamente
$('input#g1').attr('checked', false);
$('input#a1').attr('checked', false);
// Conectar con filtro inicializado en 0.5.
document.querySelector('#lowpass').value = 0.5;
//var lowpass = document.querySelector('#lowpass').value;
document.querySelector('#valorlowpass').innerHTML = 500;
contextoDeAudio.paso_bajo.frequency.value = 500;
var lowpassq = document.querySelector('#lowpassq').value * 30;;
contextoDeAudio.paso_bajo.Q.value = lowpassq;
//Compruebo graves
miFuente.connect(contextoDeAudio.paso_bajo);
contextoDeAudio.paso_bajo.connect(contextoDeAudio.volumen);
contextoDeAudio.volumen.connect(contextoDeAudio.analizador)
contextoDeAudio.analizador.connect(contextoDeAudio.destination);
}