]>
Commit | Line | Data |
---|---|---|
1 | 'use strict'; | |
2 | ||
3 | Class(UnlimitedPizza, "Pepperoni").inherits(Widget)({ | |
4 | INNER_HTML : ' \ | |
5 | <a class="record-button">⬤</a> \ | |
6 | <div class="record-info"> \ | |
7 | <div class="record-progress"> \ | |
8 | <div class="record-progress-bar-container"> \ | |
9 | <div class="record-progress-bar"></div> \ | |
10 | </div> \ | |
11 | <a class="record-clear">X</a> \ | |
12 | </div> \ | |
13 | <audio class="record-preview" controls></audio> \ | |
14 | <div class="filter-switches"> \ | |
15 | <input class="filter-switch" name="bandpass-filter" type="checkbox" /> <label for="bandpass-filter">Band Pass</label> \ | |
16 | <input class="filter-switch" name="hipass-filter" type="checkbox" /> <label for="hipass-filter">Hi Pass</label> \ | |
17 | <input class="filter-switch" name="lopass-filter" type="checkbox" /> <label for="lopass-filter">Lo Pass</label> \ | |
18 | <input class="filter-switch" name="reverb-filter" type="checkbox" /> <label for="reverb-filter">Reverb</label> \ | |
19 | <input class="filter-switch" name="distort-filter" type="checkbox" /> <label for="distort-filter">Distort</label> \ | |
20 | </div> \ | |
21 | </div> \ | |
22 | ', | |
23 | PAUSE : '▐▐', | |
24 | RECORD : '⬤', | |
25 | prototype : { | |
26 | maxSize : 1048576, | |
27 | recording : false, | |
28 | source : null, | |
29 | recorder : null, | |
30 | context : null, | |
31 | _delayNode : null, | |
32 | _bandPassFilterNode : null, | |
33 | _hiPassFilterNode : null, | |
34 | _loPassFilterNode : null, | |
35 | _convolverNode : null, | |
36 | _distortionNode : null, | |
37 | _activatedNodes : null, | |
38 | workerPath : '/js/vendor/recorderjs/recorderWorker.js', | |
39 | init : function init(config) { | |
40 | var channels, frameCount, reverbBuffer, request, requestHandler; | |
41 | ||
42 | Widget.prototype.init.call(this, config); | |
43 | ||
44 | if (!this.context) { | |
45 | this.context = new (window.AudioContext || window.webkitAudioContext)(); | |
46 | } | |
47 | ||
48 | this._delayNode = this.context.createDelay(1.0); | |
49 | this._bandPassFilterNode = this.context.createBiquadFilter(); | |
50 | this._hiPassFilterNode = this.context.createBiquadFilter(); | |
51 | this._loPassFilterNode = this.context.createBiquadFilter(); | |
52 | this._convolverNode = this.context.createConvolver(); | |
53 | this._distortionNode = this.context.createWaveShaper(); | |
54 | ||
55 | this._distortionNode.curve = this._generateDistortion(400); | |
56 | this._distortionNode.oversample = '4x'; | |
57 | ||
58 | this._activatedNodes = []; | |
59 | ||
60 | // config lo pass | |
61 | this._loPassFilterNode.type = "lowpass"; | |
62 | this._loPassFilterNode.frequency.value = 1000; | |
63 | this._loPassFilterNode.gain.value = 25; | |
64 | ||
65 | // config hi pass | |
66 | this._hiPassFilterNode.type = "highpass"; | |
67 | this._hiPassFilterNode.frequency.value = 3000; | |
68 | this._hiPassFilterNode.gain.value = 25; | |
69 | ||
70 | // config band pass | |
71 | this._bandPassFilterNode.type = "bandpass"; | |
72 | this._bandPassFilterNode.frequency.value = 2000; | |
73 | this._bandPassFilterNode.gain.value = 25; | |
74 | ||
75 | requestHandler = function bufferFile(ev) { | |
76 | var request = event.target; | |
77 | console.log("Reverb loading"); | |
78 | this.context.decodeAudioData(request.response, function(buffer){ | |
79 | console.log("Reverb loaded"); | |
80 | this._convolverNode.buffer = buffer; | |
81 | }); | |
82 | }.bind(this); | |
83 | ||
84 | request = new XMLHttpRequest(); | |
85 | request.open('GET', '/reverb.aiff', true); | |
86 | request.responseType = 'arraybuffer'; | |
87 | request.addEventListener('load', requestHandler, false); | |
88 | request.send(); | |
89 | ||
90 | if (!this.source) { | |
91 | this._getUserMedia({ | |
92 | audio : true | |
93 | }, this._onUserMedia.bind(this), this._onUserMediaError.bind(this)) | |
94 | } | |
95 | ||
96 | this.element.html(this.constructor.INNER_HTML); | |
97 | ||
98 | this.controlButton = this.element.find('.record-button'); | |
99 | this.clearButton = this.element.find('.record-clear'); | |
100 | this.audioElement = this.element.find('audio'); | |
101 | this.progressBarContainer = this.element.find('.record-progress-bar-container'); | |
102 | this.progressBar = this.element.find('.record-progress-bar'); | |
103 | this.switches = this.element.find('.filter-switch'); | |
104 | ||
105 | this._bindEvents(); | |
106 | }, | |
107 | ||
108 | record : function record() { | |
109 | if (this.recorder && !this.recording) { | |
110 | this._canRecord(function handleCanRecord(canRecord) { | |
111 | if (canRecord) { | |
112 | this.recording = true; | |
113 | this.controlButton.addClass('recording') | |
114 | this.controlButton.html(this.constructor.PAUSE); | |
115 | this._interval = setInterval(this._onRecordCheck.bind(this), 100); | |
116 | this.recorder.record(); | |
117 | } | |
118 | }.bind(this)); | |
119 | } | |
120 | }, | |
121 | ||
122 | stop : function stop() { | |
123 | if (this.recorder && this.recording) { | |
124 | this.recording = false; | |
125 | this.controlButton.removeClass('recording') | |
126 | this.controlButton.html(this.constructor.RECORD); | |
127 | clearInterval(this._interval); | |
128 | this.recorder.stop(); | |
129 | this.recorder.exportWAV(); | |
130 | } | |
131 | }, | |
132 | ||
133 | clear : function clear() { | |
134 | if (this.recorder) { | |
135 | this.progressBar.width(0); | |
136 | this.audioElement.attr('src', ''); | |
137 | this.audioElement[0].load(); | |
138 | this.recorder.clear() | |
139 | } | |
140 | }, | |
141 | ||
142 | finalize : function finalize(callback) { | |
143 | if (this.recorder) { | |
144 | this.recorder.exportWAV(callback); | |
145 | } | |
146 | }, | |
147 | ||
148 | getBuffer : function getBuffer(callback) { | |
149 | if (this.recorder) { | |
150 | this.recorder.getBuffer(callback); | |
151 | } | |
152 | }, | |
153 | ||
154 | _bindEvents : function bindEvents() { | |
155 | var pepperoni = this; | |
156 | ||
157 | this.controlButton.on('click', function () { | |
158 | if (this.recording) { | |
159 | this.stop(); | |
160 | } else { | |
161 | this.record(); | |
162 | } | |
163 | }.bind(this)) | |
164 | ||
165 | this.clearButton.on('click', function () { | |
166 | if (!this.recording) { | |
167 | this.clear(); | |
168 | } | |
169 | }.bind(this)) | |
170 | ||
171 | this.switches.on('change', function (ev) { | |
172 | if (!pepperoni.source) { | |
173 | this.checked = false; | |
174 | return false; | |
175 | } | |
176 | switch (this.name) { | |
177 | case 'delay-filter': | |
178 | if (this.checked) { | |
179 | pepperoni._addNode(pepperoni._delayNode); | |
180 | } else { | |
181 | pepperoni._removeNode(pepperoni._delayNode); | |
182 | } | |
183 | break; | |
184 | ||
185 | case 'hipass-filter': | |
186 | if (this.checked) { | |
187 | pepperoni._addNode(pepperoni._hiPassFilterNode); | |
188 | } else { | |
189 | pepperoni._removeNode(pepperoni._hiPassFilterNode); | |
190 | } | |
191 | break; | |
192 | ||
193 | case 'bandpass-filter': | |
194 | if (this.checked) { | |
195 | pepperoni._addNode(pepperoni._bandPassFilterNode); | |
196 | } else { | |
197 | pepperoni._removeNode(pepperoni._bandPassFilterNode); | |
198 | } | |
199 | break; | |
200 | ||
201 | case 'lopass-filter': | |
202 | if (this.checked) { | |
203 | pepperoni._addNode(pepperoni._loPassFilterNode); | |
204 | } else { | |
205 | pepperoni._removeNode(pepperoni._loPassFilterNode); | |
206 | } | |
207 | break; | |
208 | ||
209 | case 'reverb-filter': | |
210 | if (this.checked) { | |
211 | pepperoni._addNode(pepperoni._convolverNode); | |
212 | } else { | |
213 | pepperoni._removeNode(pepperoni._convolverNode); | |
214 | } | |
215 | break; | |
216 | ||
217 | case 'distort-filter': | |
218 | if (this.checked) { | |
219 | pepperoni._addNode(pepperoni._distortionNode); | |
220 | } else { | |
221 | pepperoni._removeNode(pepperoni._distortionNode); | |
222 | } | |
223 | ||
224 | break; | |
225 | } | |
226 | }); | |
227 | }, | |
228 | ||
229 | _onRecording : function _onRecording(buffer) { | |
230 | this._buffer = buffer; | |
231 | ||
232 | this.audioElement.attr('src', URL.createObjectURL(buffer)); | |
233 | this.audioElement[0].load(); | |
234 | }, | |
235 | ||
236 | _onUserMedia : function _onUserMedia(localMediaStream) { | |
237 | this.source = this.context.createMediaStreamSource(localMediaStream); | |
238 | this.recorder = new Recorder(this.source, { | |
239 | workerPath : this.workerPath, | |
240 | callback : this._onRecording.bind(this) | |
241 | }); | |
242 | }, | |
243 | ||
244 | _onUserMediaError : function _onUserMediaError(error) { | |
245 | console.log("Something went wrong", error); | |
246 | this.disable(); | |
247 | }, | |
248 | ||
249 | _onRecordCheck : function _onRecordCheck() { | |
250 | this._canRecord(function (canRecord, bufferSize) { | |
251 | var width = bufferSize * this.progressBarContainer.width() / this.maxSize; | |
252 | this.progressBar.width(width); | |
253 | if (!canRecord) { | |
254 | this.stop(); | |
255 | } | |
256 | }.bind(this)); | |
257 | }, | |
258 | ||
259 | _canRecord : function _canRecord(callback) { | |
260 | this.recorder.getBuffer(function getBuffer(buffer) { | |
261 | var bufferSize = buffer[0].length + buffer[1].length; | |
262 | callback && callback(bufferSize <= this.maxSize, bufferSize); | |
263 | }.bind(this)); | |
264 | }, | |
265 | ||
266 | _addNode : function _addNode(node) { | |
267 | var i; | |
268 | ||
269 | i = this._activatedNodes.length; | |
270 | ||
271 | this._activatedNodes.push(node); | |
272 | ||
273 | if (i === 0) { | |
274 | this.source.disconnect(); | |
275 | this.source.connect(node); | |
276 | } else { | |
277 | this._activatedNodes[i - 1].disconnect(); | |
278 | this._activatedNodes[i - 1].connect(node); | |
279 | } | |
280 | ||
281 | node.connect(this.recorder.node); | |
282 | this.recorder.context = node.context; | |
283 | this.recorder.node.connect(this.recorder.context.destination) | |
284 | ||
285 | console.log("Adding: ", node); | |
286 | }, | |
287 | ||
288 | _removeNode : function _removeNode(node) { | |
289 | var i; | |
290 | ||
291 | i = this._activatedNodes.indexOf(node); | |
292 | ||
293 | node.disconnect(); | |
294 | ||
295 | if (i === 0 && i + 1 === this._activatedNodes.length) { | |
296 | // It was the only one, connect source to recorder. | |
297 | this.source.disconnect(); | |
298 | this.source.connect(this.recorder.node); | |
299 | } else if (i === 0) { | |
300 | // Normal 0 case, connect source to node. Recorder stays the same | |
301 | this.source.disconnect(); | |
302 | this.source.connect(this._activatedNodes[i+1]); | |
303 | } else if (i + 1 === this._activatedNodes.length) { | |
304 | // It's not the 0 case, but we need to reconnect to recorder. | |
305 | this._activatedNodes[i - 1].disconnect(); | |
306 | this._activatedNodes[i - 1].connect(this.recorder.node); | |
307 | } else { | |
308 | // Normal case, connect previous node to node | |
309 | this._activatedNodes[i - 1].disconnect(); | |
310 | this._activatedNodes[i - 1].connect(this._activatedNodes[i + 1]); | |
311 | } | |
312 | ||
313 | this._activatedNodes.splice(i, 1); | |
314 | ||
315 | console.log("Removing: ", node); | |
316 | }, | |
317 | ||
318 | _generateDistortion : function generateDistortion(amount) { | |
319 | var k = typeof amount === 'number' ? amount : 50, | |
320 | n_samples = 44100, | |
321 | curve = new Float32Array(n_samples), | |
322 | deg = Math.PI / 180, | |
323 | i = 0, | |
324 | x; | |
325 | for ( ; i < n_samples; ++i ) { | |
326 | x = i * 2 / n_samples - 1; | |
327 | curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); | |
328 | } | |
329 | return curve; | |
330 | }, | |
331 | ||
332 | // Normalize get user media | |
333 | _getUserMedia : (navigator.getUserMedia || | |
334 | navigator.webkitGetUserMedia || | |
335 | navigator.mozGetUserMedia || | |
336 | navigator.msGetUserMedia).bind(navigator) | |
337 | } | |
338 | }); |