]> git.r.bdr.sh - rbdr/r.bdr.sh/blob - jekyll/js/unlimited_pizza/pepperoni.js
Merge branch 'master' of unlimited.pizza:~/nsovocal
[rbdr/r.bdr.sh] / jekyll / js / unlimited_pizza / pepperoni.js
1 'use strict';
2
3 Class(UnlimitedPizza, "Pepperoni").inherits(Widget)({
4 INNER_HTML : ' \
5 <a class="record-button">&#11044</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 : '&#9616;&#9616;',
24 RECORD : '&#11044;',
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 = ev.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 }.bind(this));
82 }.bind(this);
83
84 request = new XMLHttpRequest();
85 request.open('GET', '/reverb.ogg', 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 });