2 |
3 | An [article](http://habrahabr.ru/post/240819/) about how it works (_rus_)
4 | [List](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats) of supported formats.
5 | [Can i use](http://caniuse.com/#feat=audio-api) - support of Web Audio API
6 |
7 | [Try it online](http://martinschulz.github.io/equalizer/)
8 |
9 |
Usage:
10 | * Add script to your html:
11 | ```html
12 |
13 |
16 | ```
17 | or _require_ it
18 | ```javascript
19 | require([
20 | 'path/to/equalizer'
21 | ], function (equalizer) {
22 | // or here *
23 | });
24 | ```
25 | * Use it:
26 | ```javascript
27 | var equalizer = new Equalizer({
28 | // css selector of your audio element or element itself (existing)
29 | audio: '.my-audio-element',
30 | // css selector of input elements or NodeList itself, if they already exist
31 | inputs: '.my-inputs',
32 | // otherwise, you can specify container element (existing).
33 | // input elements will be created and append to this container.
34 | // css selector of container element or element itself
35 | container: '.my-container'
36 | });
37 | // You have to specify inputs (if they're already created) OR container,
38 | // if you want to create inputs dynamically
39 | // (jQuery objects are also allowed)
40 |
41 | // then you can turn off it
42 | equalizer.disconnect();
43 | // and turn on
44 | equalizer.connect();
45 | ```
46 |
47 | Also there's couple of events:
48 | ```javascript
49 | new Equalizer({
50 | /* param */
51 | }).on('error', function (data) {
52 | // do some error stuff. i.e. hide controls
53 | console.log(data.message);
54 | }).on('change', function (data) {
55 | console.log('input no.' + data.index + ' changed. new value is ' + data.value);
56 | });
57 | ```
58 | Full list of events available for now:
59 | error, change, disconnect, connect
60 |
61 |
62 |
--------------------------------------------------------------------------------
/equalizer.min.js:
--------------------------------------------------------------------------------
1 | (function(root,factory){"use strict";if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{console.log("ok");root.equalizer=factory()}})(this,function(){"use strict";var context=null,audio=null,filters=[],$$=document.querySelectorAll.bind(document),$=document.querySelector.bind(document),createContext=function(){var previous=window&&window.equalizer;if(previous&&previous.context){context=previous.context}else{context=new AudioContext}},createInputs=function(className,container){var inputs=[],node,i;for(i=0;i<10;i++){node=document.createElement("input");node.className=className.slice(1);container.appendChild(node);inputs.push(node)}return inputs},initInputsData=function(inputs){[].forEach.call(inputs,function(item){item.setAttribute("min",-16);item.setAttribute("max",16);item.setAttribute("step",.1);item.setAttribute("value",0);item.setAttribute("type","range")})},initEvents=function(inputs){[].forEach.call(inputs,function(item,i){item.addEventListener("change",function(e){filters[i].gain.value=e.target.value},false)})},createFilter=function(frequency){var filter=context.createBiquadFilter();filter.type="peaking";filter.frequency.value=frequency;filter.gain.value=0;filter.Q.value=1;return filter},createFilters=function(){var frequencies=[60,170,310,600,1e3,3e3,6e3,12e3,14e3,16e3];filters=frequencies.map(function(frequency){return createFilter(frequency)});filters.reduce(function(prev,curr){prev.connect(curr);return curr})},validateParam=function(param){if(!param){throw new TypeError("equalizer: param required")}var container=$(param.container),inputs=$$(param.inputs);if(param.audio instanceof HTMLMediaElement){audio=param.audio}else if(typeof param.audio==="string"){audio=$(param.audio);if(!audio){throw new TypeError("equalizer: there's no element that match selector"+param.audio)}}else{throw new TypeError('equalizer: parameter "audio" must be string or an audio element')}if(!container&&!inputs.length){throw new TypeError("equalizer: there's no elements match \""+param.container+'" or "'+param.selector)}if(!inputs.length){inputs=createInputs(param.selector||"",container)}return inputs},bindEqualizer=function(){var source=context.createMediaElementSource(audio);source.connect(filters[0]);filters[9].connect(context.destination)},equalizer=function(param){var inputs=validateParam(param);createContext();createFilters();initInputsData(inputs);initEvents(inputs);bindEqualizer()};equalizer.context=context;return equalizer});
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
37 |
38 |
--------------------------------------------------------------------------------
/src/js/equalizer.js:
--------------------------------------------------------------------------------
1 |
2 | (function (root, factory) {
3 | 'use strict';
4 |
5 | if (typeof define === 'function' && define.amd) {
6 | // AMD. Register as an anonymous module.
7 | define([], factory);
8 | } else if (typeof exports === 'object') {
9 | // Node. Does not work with strict CommonJS, but
10 | // only CommonJS-like environments that support module.exports,
11 | // like Node.
12 | module.exports = factory();
13 | } else {
14 | // Browser globals (root is window)
15 | root.Equalizer = factory();
16 | }
17 |
18 | }(this, function () {
19 | 'use strict';
20 |
21 | var
22 | $$ = document.querySelectorAll.bind(document),
23 | $ = document.querySelector.bind(document),
24 |
25 | AudioContext = window.AudioContext || window.webkitAudioContext;
26 |
27 | var
28 | Equalizer = function (param) {
29 | // if it's not supported - do nothing
30 | if (!AudioContext) {
31 | this.trigger('error', {
32 | message: 'AudioContext not supported'
33 | }, true);
34 |
35 | return;
36 | }
37 |
38 | /** AudioContext object */
39 | // avoid multiple AudioContext creating
40 | this.context = window.__context || new AudioContext();
41 | window.__context = this.context;
42 |
43 | this.frequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000];
44 |
45 | this.length = this.frequencies.length;
46 |
47 | this.connected = false;
48 |
49 | this.initInputs(param);
50 |
51 | this.createFilters();
52 | this.initInputsAttrs();
53 | this.initEvents();
54 | this.connectEqualizer();
55 | };
56 |
57 | // <### pubsub ###>
58 | addPubsub(Equalizer);
59 |
60 | /**
61 | * creates input elements
62 | * @param {HTMLElement} container
63 | * @returns [HTMLElement]
64 | */
65 | Equalizer.prototype.createInputs = function (container) {
66 | var
67 | inputs = [],
68 | node,
69 | i;
70 |
71 | for (i = 0; i < this.length; i++) {
72 | node = document.createElement('input');
73 | container.appendChild(node);
74 | inputs.push(node);
75 | }
76 |
77 | return inputs;
78 | };
79 |
80 | /**
81 | * init inputs range and step
82 | */
83 | Equalizer.prototype.initInputsAttrs = function () {
84 | [].forEach.call(this.inputs, function (item) {
85 | item.setAttribute('min', -16);
86 | item.setAttribute('max', 16);
87 | item.setAttribute('step', 0.1);
88 | item.setAttribute('value', 0);
89 | item.setAttribute('type', 'range');
90 | });
91 | };
92 |
93 | /**
94 | * bind input.change events to the filters
95 | */
96 | Equalizer.prototype.initEvents = function () {
97 | var
98 | self = this;
99 |
100 | [].forEach.call(this.inputs, function (item, i) {
101 | item.addEventListener('change', function (e) {
102 | self.filters[i].gain.value = e.target.value;
103 |
104 | self.trigger('change', {
105 | value: e.target.value,
106 | inputElement: e.target,
107 | index: i
108 | });
109 | }, false);
110 | });
111 | };
112 |
113 | /**
114 | * creates single BiquadFilter object
115 | * @param frequency {number}
116 | * @returns {BiquadFilter}
117 | */
118 | Equalizer.prototype.createFilter = function (frequency) {
119 | var
120 | filter = this.context.createBiquadFilter();
121 |
122 | filter.type = 'peaking';
123 | filter.frequency.value = frequency;
124 | filter.gain.value = 0;
125 | filter.Q.value = 1;
126 |
127 | return filter;
128 | };
129 |
130 | /**
131 | * create filter for each frequency and connect
132 | */
133 | Equalizer.prototype.createFilters = function () {
134 | // create filters
135 | this.filters = this.frequencies.map(this.createFilter.bind(this));
136 |
137 | // create chain
138 | this.filters.reduce(function (prev, curr) {
139 | prev.connect(curr);
140 | return curr;
141 | });
142 | };
143 |
144 | /**
145 | * check param and create input elements if necessary
146 | *
147 | * @holycrap {WTF} Why Must Life Be So Hard http://www.youtube.com/watch?v=rH48caFgZcI
148 | *
149 | * @returns {array|NodeList} input elements
150 | */
151 | Equalizer.prototype.initInputs = function (param) {
152 | if (!param) {
153 | throw new TypeError('equalizer: param required');
154 | }
155 |
156 | var
157 | container,
158 | inputs = [];
159 |
160 | if (param.audio instanceof HTMLMediaElement) {
161 | this.audio = param.audio;
162 | } else if (typeof param.audio === 'string') {
163 | this.audio = $(param.audio);
164 |
165 | if (!this.audio) {
166 | throw new TypeError('equalizer: there\'s no element that match selector' +
167 | param.audio);
168 | }
169 | } else {
170 | throw new TypeError('equalizer: parameter "audio" must be string or an audio element');
171 | }
172 |
173 | // container specified
174 | if (param.container) {
175 | // css selector
176 | if (typeof param.container === 'string') {
177 | container = $(param.container);
178 | if (!container) {
179 | throw new TypeError('equalizer: there\'s no element that match selector' +
180 | param.container);
181 | }
182 | // plain html element
183 | } else if (param.container instanceof HTMLElement) {
184 | container = param.container;
185 | // jquery object
186 | } else if (param.container.jquery && param.container[0]) {
187 | container = param.container[0];
188 | } else {
189 | throw new TypeError('equalizer: invalid parameter container: ' + param.container);
190 | }
191 |
192 | inputs = this.createInputs(container);
193 | } else {
194 | // no container
195 | if (typeof param.inputs === 'string') {
196 | inputs = $$(param.inputs);
197 | // plainh html collection
198 | } else if (param.inputs instanceof NodeList) {
199 | inputs = param.inputs;
200 | // jquery object
201 | } else if (param.inputs.jquery) {
202 | param.inputs.each(function (i, item) {
203 | inputs.push(item);
204 | });
205 | } else {
206 | throw new TypeError('equalizer: invalid parameter inputs: ' + param.container);
207 | }
208 | }
209 |
210 | if (inputs.length !== this.length) {
211 | throw new TypeError('equalizer: required exactly ' + this.length + ' elements, but ' +
212 | inputs.length + ' found');
213 | }
214 |
215 | this.inputs = inputs;
216 | };
217 |
218 | /**
219 | * create a chain
220 | */
221 | Equalizer.prototype.connectEqualizer = function () {
222 | this.source = this.context.createMediaElementSource(this.audio);
223 |
224 | this.source.connect(this.filters[0]);
225 | this.filters[this.length - 1].connect(this.context.destination);
226 | };
227 |
228 | /**
229 | * turn off
230 | */
231 | Equalizer.prototype.disconnect = function () {
232 | if (!this.connected) {
233 | return this;
234 | }
235 |
236 | this.trigger('disconnect', {});
237 | this.source.disconnect();
238 | this.source.connect(this.destination);
239 |
240 | return this;
241 | };
242 |
243 | /**
244 | * turn on
245 | */
246 | Equalizer.prototype.connect = function () {
247 | if (this.connected) {
248 | return this;
249 | }
250 |
251 | this.trigger('connect', {});
252 | this.source.disconnect();
253 | this.source.connect(this.filters[0]);
254 |
255 | return this;
256 | };
257 |
258 | return Equalizer;
259 | }));
--------------------------------------------------------------------------------
/src/js/util/pubsub.js:
--------------------------------------------------------------------------------
1 | /**
2 | * incredibly simple pub-sub
3 | * @param {object} object - object to extend
4 | */
5 | var addPubsub = function (object) {
6 | 'use strict';
7 |
8 | object.prototype.on = function (eventName, callback) {
9 | if (!this.__callbacks) {
10 | this.__callbacks = {};
11 | }
12 |
13 | if (!this.__callbacks[eventName]) {
14 | this.__callbacks[eventName] = [];
15 | }
16 |
17 | this.__callbacks[eventName].push(callback);
18 | if (this.__callbacks[eventName].deferred) {
19 | callback(this.__callbacks[eventName].data);
20 | }
21 |
22 | return this;
23 | };
24 |
25 | object.prototype.trigger = function (eventName, data, deferred) {
26 | if (!this.__callbacks || !this.__callbacks[eventName]) {
27 | if (deferred) {
28 | this.__callbacks = this.__callbacks || {};
29 |
30 | this.__callbacks[eventName] = [];
31 | this.__callbacks[eventName].deferred = true;
32 | this.__callbacks[eventName].data = data;
33 | }
34 |
35 | return;
36 | }
37 |
38 | this.__callbacks[eventName].forEach(function (callback) {
39 | callback(data);
40 | });
41 |
42 | return this;
43 | };
44 | };
--------------------------------------------------------------------------------