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