Back to Home

JavaScript Equalizer

javascript equalizer

JavaScript Equalizer

  • Tutorial
There were already several articles on the Web Audio API on the hub: creating a visualizer , vocoder and piano in 30 24 lines. A search on all the Internet for an equalizer persistently issued tutorials on creating spectrograms. (If the title of this article has confused you, or you bought the picture :) and you were expecting just the visualization of the audio, here or here ). But it’s just that I didn’t meet the equalizer (although I’m sure that somewhere he is). Perhaps this is such a simple task that it is not worth writing about it. But then, why not make it even easier?




What did you want to get?

Suppose we already have some kind of player. In the simplest case, this is a bare audioelement.

I want us to be able to fasten the equalizer to it
var audio = document.getElementById('audio');
equalize(audio); // как-то так, 
so that you don’t have to think, and all this would not affect the work of the player itself.
But, let's start from the beginning.

API


Any work with the Web Audio API begins with creating a context:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext();

What is important - such an object should be one. First, in order for all related objects to work together, they must be created in the same context. Secondly, if you create several contexts (according to observations, 3-4), the browser will crash :)

( UPD: as of 09/21/15, an error occurs when creating more contexts Uncaught NotSupportedError: Failed to construct 'AudioContext': The number of hardware contexts provided (6) is greater than or equal to the maximum bound (6). That is, chrome allows you to create up to six contexts at a time. )

The first thing we need is to create a wrapper for HTMLMediaElement, with which we will work:
var source = context.createMediaElementSource(audio);


The createMediaElementSource method also works with

Object elements source- this is the first link in the chain (in the literal sense) that we are building. In the simplest case, the circuit consists of only two links - the source is immediately connected to the output.
source.connect(context.destination);

Here context.destination is, roughly speaking, your columns.
The equalizer itself is built from filters created using createBiquadFilter .

Filter Creation Code:
var createFilter = function (frequency) {
  var filter = context.createBiquadFilter();
  filter.type = 'peaking'; // тип фильтра
  filter.frequency.value = frequency; // частота
  filter.Q.value = 1; // Q-factor
  filter.gain.value = 0;
  return filter;
};

The only parameter in this case is the frequency. The remaining parameters coincide for all filters or change during program operation. It:
  • type - type of filter. It can take one of the values: lowpass, highpass, bandpass, lowshelf, highshelf, peaking, notch, allpass.We only need a peaking filter - it allows you to selectively emphasize or weaken a limited band of the sound spectrum. Read more.
  • Q - quality factor - changes the bandwidth of the frequencies that the filter affects.
  • gain - the force with which the filter affects the frequency band.

It is necessary to create filters for the entire set of frequencies. For a 10-band equalizer, this can be 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000 и 16000Hz (the values ​​are copied from winamp).
var createFilters = function () {
  var frequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000],
    filters;
  // создаем фильтры
  filters = frequencies.map(createFilter);
  // цепляем их последовательно.
  // Каждый фильтр, кроме первого, соединяется с предыдущим.
  // Удачно, что reduce без начального значения как раз пропускает первый элемент.
  filters.reduce(function (prev, curr) {
    prev.connect(curr);
    return curr;
  });
  return filters;
};

It is very important to connect the filters in series. When I wrote the first version, my filters were connected in parallel, and the output was nothing but a terrible rumble. The cure was not immediately found (mainly because the answer marked as 'solution' is not correct).

It remains only to tie it all together:
window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = new AudioContext(),
  audio = document.getElementById('audio');
var createFilter = function (frequency) {
  var filter = context.createBiquadFilter();
  filter.type = 'peaking'; // тип фильтра
  filter.frequency.value = frequency; // частота
  filter.Q.value = 1; // Q-factor
  filter.gain.value = 0;
  return filter;
};
var createFilters = function () {
  var frequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000],
    filters = frequencies.map(createFilter);
  filters.reduce(function (prev, curr) {
    prev.connect(curr);
    return curr;
  });
  return filters;
};
var equalize = function (audio) {
  var source = context.createMediaElementSource(audio),
    filters = createFilters();
  // источник цепляем к первому фильтру 
  source.connect(filters[0]);
  // а последний фильтр - к выходу
  filters[filters.length - 1].connect(context.destination);
};
equalize(audio);

Like this. Equalizer in 30 lines. Then it’s the small business - to tie the controls, but this is an elementary task.
Something like that
// схематично
var bindEvents = function (inputs) {
  inputs.forEach(function (item, i) {
    item.addEventListener('change', function (e) {
      filters[i].gain.value = e.target.value;
    }, false);
  });
};


Here, in fact, is a demo where an ogg file is streamed and passed through our equalizer, but only Google Chrome users can enjoy it, users of other browsers will have to bother to open a local file, and not even any . Because…

Moment of frustration


Having collected the first version of the player, I decided to fasten a soundcloud to it. It's great to drive songs from the cloud through the equalizer. In the end, everything started up ... but only in chrome - Mozilla stubbornly refused to reproduce the stream. But at the same time, I ran local files with a bang. And then it turned out scary:
To prevent this [information leakage], a MediaElementAudioSourceNode must output silence instead of the normal output of the HTMLMediaElement if it has been created using an HTMLMediaElement for which the execution of the fetch algorithm labeled the resource as CORS-cross-origin. ( documentation )

That is, CORS and the Web Audio API are incompatible. And the most interesting thing is that in chrome this bunch still works. I think this is still a bug and it should be closed soon (although it has been present for a long time), so you should not use this feature . ( upd: as of 07/12/15, the bug is closed, the equalizer for CORS resources in chrome does not work )

upd: as rightly noted in the comments, CORS can be configured using the crossorigin attribute , but for this, the header must be added to the stream Access-Control-Allow-Origin.

And for downloaded files, for example, you can use ObjectURL:
// схематично
fileInput.addEventListener('change', function (e) {
  var url = URL.createObjectURL(e.target.files[0]);
  audio.src = url;
}, false);


Total


In general, the Web Audio API is already supported pretty well and can be widely used. And most importantly - api allows you to write a very high-level code, and you can write your own equalizer in 30 lines if you do not like this :)

Materials:

References:


PS It is nice to know that the article got into the top habr for 2014. 2nd place in the API category

Read Next