]> git.r.bdr.sh - rbdr/r.bdr.sh/blob - jekyll/js/unlimited_pizza/pepperoni.js
Adds first wave of effects
[rbdr/r.bdr.sh] / jekyll / js / unlimited_pizza / pepperoni.js
1 Class(UnlimitedPizza, "Pepperoni").inherits(Widget)({
2 INNER_HTML : ' \
3 <a class="record-button">&#11044</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 : '&#9616;&#9616;',
21 RECORD : '&#11044;',
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 });