📜 ⬆️ ⬇️

Javascript equalizer

On Habré there have already been several articles on the Web Audio API: creating a visualizer , vocoder and piano in 30 24 lines. The search for all the Internet at the request of the equalizer stubbornly issued tutorials on the creation of spectrograms. (If the title of this article confused you or you bought the picture :) and expected to render the audio - go here or here ). But it was just an equalizer that I never met (although I’m sure that he does exist somewhere). Perhaps this is such a simple task that you should not write about it. But, in that case, why not make it even easier?




What do you want to get?

Let we already have some kind of player. In the simplest case, this is a bare audio element.
 <audio controls id="audio" src="path/to/file"></audio> 

I want us to be able to tie the equalizer to it
 var audio = document.getElementById('audio'); equalize(audio); // - , 
so that I don’t have to think and it wouldn’t affect the work of the player.
But let's start from the beginning.
')

API


Any work with the Web Audio API begins with the creation of the 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 (3-4 observations), the browser will fall :)

( UPD: as of 9/21/15, when creating more contexts, the error 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 the same 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 <video /> elements

The source object is the first link in the chain (literally) 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 with 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 are the same for all filters or change while the program is running. It:

You need to create filters for the whole set of frequencies. For a 10-band equalizer, these can be 60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000 16000 Hz (values ​​are drawn 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 had nothing but a terrible crash. The medicine was not found immediately (mainly because the answer, marked as 'decision', 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. Next thing is small - 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, the demo , where the ogg file is streamed and passed through our equalizer, but only Google Chrome users will be able to enjoy it, users of other browsers will have to bother to open a local file, and even not anyhow . 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 ... but only in chrome - Mozilla stubbornly refused to reproduce the stream. But local files while running with a bang. And then it turned out terrible:
If you haven’t been able to receive this information, it has been created. ( documentation )

That is, CORS and 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 around for a long time), so you shouldn’t 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, the CORS can be configured using the crossorigin attribute, but to do this, the Access-Control-Allow-Origin header should be added to the stream itself.

And for uploaded 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 fairly well supported 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's nice to know that the article hit the top of Habra for 2014. 2nd place in the API category

Source: https://habr.com/ru/post/240819/


All Articles