'use strict';
-Class(UnlimitedPizza, "Pepperoni").inherits(Widget)({
- INNER_HTML : ' \
-<a class="record-button">Record.</a> \
-<div class="record-info"> \
- <div class="record-progress"> \
- <div class="record-progress-bar-container"> \
- <div class="record-progress-bar"></div> \
- </div> \
- <a class="record-clear">Clear recording.</a> \
- </div> \
- <audio class="record-preview" controls></audio> \
- <div class="filter-switches"> \
- <input class="filter-switch" name="bandpass-filter" type="checkbox" /> <label for="bandpass-filter">Band Pass</label> \
- <input class="filter-switch" name="hipass-filter" type="checkbox" /> <label for="hipass-filter">Hi Pass</label> \
- <input class="filter-switch" name="lopass-filter" type="checkbox" /> <label for="lopass-filter">Lo Pass</label> \
- <input class="filter-switch" name="reverb-filter" type="checkbox" /> <label for="reverb-filter">Reverb</label> \
- <input class="filter-switch" name="distort-filter" type="checkbox" /> <label for="distort-filter">Distort</label> \
- </div> \
-</div> \
- ',
- PAUSE : 'Pause.',
- RECORD : 'Record.',
- prototype : {
- maxSize : 1048576,
- recording : false,
- source : null,
- recorder : null,
- context : null,
- _delayNode : null,
- _bandPassFilterNode : null,
- _hiPassFilterNode : null,
- _loPassFilterNode : null,
- _convolverNode : null,
- _distortionNode : null,
- _activatedNodes : null,
- workerPath : '/js/vendor/recorderjs/recorderWorker.js',
- init : function init(config) {
- var channels, frameCount, reverbBuffer, request, requestHandler;
-
- Widget.prototype.init.call(this, config);
-
- if (!this.context) {
- this.context = new (window.AudioContext || window.webkitAudioContext)();
- }
-
- this._delayNode = this.context.createDelay(1.0);
- this._bandPassFilterNode = this.context.createBiquadFilter();
- this._hiPassFilterNode = this.context.createBiquadFilter();
- this._loPassFilterNode = this.context.createBiquadFilter();
- this._convolverNode = this.context.createConvolver();
- this._distortionNode = this.context.createWaveShaper();
-
- this._distortionNode.curve = this._generateDistortion(400);
- this._distortionNode.oversample = '4x';
-
- this._activatedNodes = [];
-
- // config lo pass
- this._loPassFilterNode.type = "lowpass";
- this._loPassFilterNode.frequency.value = 1000;
- this._loPassFilterNode.gain.value = 25;
-
- // config hi pass
- this._hiPassFilterNode.type = "highpass";
- this._hiPassFilterNode.frequency.value = 3000;
- this._hiPassFilterNode.gain.value = 25;
-
- // config band pass
- this._bandPassFilterNode.type = "bandpass";
- this._bandPassFilterNode.frequency.value = 2000;
- this._bandPassFilterNode.gain.value = 25;
-
- requestHandler = function bufferFile(ev) {
- var request = ev.target;
- console.log("Reverb loading");
- this.context.decodeAudioData(request.response, function(buffer){
- console.log("Reverb loaded");
- this._convolverNode.buffer = buffer;
- }.bind(this));
- }.bind(this);
-
- request = new XMLHttpRequest();
- request.open('GET', '/reverb.ogg', true);
- request.responseType = 'arraybuffer';
- request.addEventListener('load', requestHandler, false);
- request.send();
-
- if (!this.source) {
- this._getUserMedia({
- audio : true
- }, this._onUserMedia.bind(this), this._onUserMediaError.bind(this))
- }
-
- this.element.html(this.constructor.INNER_HTML);
+const internals = {
- this.controlButton = this.element.find('.record-button');
- this.clearButton = this.element.find('.record-clear');
- this.audioElement = this.element.find('audio');
- this.progressBarContainer = this.element.find('.record-progress-bar-container');
- this.progressBar = this.element.find('.record-progress-bar');
- this.switches = this.element.find('.filter-switch');
-
- this._bindEvents();
+ config: {
+ convolverNode: {
+ requestMethod: 'GET',
+ requestLocation: '/reverb.ogg',
+ responseType: 'arraybuffer'
},
+ distortionNode: {
+ amount: 400
+ }
+ },
+
+ kTemplate: `<a class="record-button">Record.</a>
+<div class="record-info">
+ <div class="record-progress">
+ <div class="record-progress-bar-container">
+ <div class="record-progress-bar"></div>
+ </div>
+ <a class="record-clear">Clear recording.</a>
+ </div>
+ <audio class="record-preview" controls></audio>
+ <div class="filter-switches">
+ <input class="filter-switch" name="bandpass-filter" type="checkbox" /> <label for="bandpass-filter">Band Pass</label>
+ <input class="filter-switch" name="hipass-filter" type="checkbox" /> <label for="hipass-filter">Hi Pass</label>
+ <input class="filter-switch" name="lopass-filter" type="checkbox" /> <label for="lopass-filter">Lo Pass</label>
+ <input class="filter-switch" name="reverb-filter" type="checkbox" /> <label for="reverb-filter">Reverb</label>
+ <input class="filter-switch" name="distort-filter" type="checkbox" /> <label for="distort-filter">Distort</label>
+ </div>
+</div>`,
+ kPauseLabel : 'Pause.',
+ kRecordLabel : 'Record.',
+};
+
+const Pepperoni = class Pepperoni {
+ constructor() {
+
+ const context = new AudioContext();
+
+ const nodes = this._createNodes(context);
+
+ this._activatedNodes = [];
+ },
+
+ _createNodes(context) {
+
+ const delayNode = context.createDelay(1.0);
+
+ const convolverNode = context.createConvolver();
+ this._initializeConvolverNode(convolverNode);
+
+ const loPassFilterNode = context.createBiquadFilter();
+ loPassFilterNode.type = 'lowpass';
+ loPassFilterNode.frequency.value = 1000;
+ loPassFilterNode.gain.value = 25;
+
+ const hiPassFilterNode = context.createBiquadFilter();
+ hiPassFilterNode.type = 'highpass';
+ hiPassFilterNode.frequency.value = 3000;
+ hiPassFilterNode.gain.value = 25;
+
+ const bandPassFilterNode = context.createBiquadFilter();
+ bandPassFilterNode.type = 'bandpass';
+ bandPassFilterNode.frequency.value = 2000;
+ bandPassFilterNode.gain.value = 25;
+
+ const distortionNode = context.createWaveShaper();
+ distortionNode.curve = this._generateDistortion(400);
+ distortionNode.oversample = '4x';
+ }
- record : function record() {
- if (this.recorder && !this.recording) {
- this._canRecord(function handleCanRecord(canRecord) {
- if (canRecord) {
- this.recording = true;
- this.controlButton.addClass('recording')
- this.controlButton.html(this.constructor.PAUSE);
- this._interval = setInterval(this._onRecordCheck.bind(this), 100);
- this.recorder.record();
- }
- }.bind(this));
- }
- },
+ // Generates a wave to be used with the distortion wave shaper node
- stop : function stop() {
- if (this.recorder && this.recording) {
- this.recording = false;
- this.controlButton.removeClass('recording')
- this.controlButton.html(this.constructor.RECORD);
- clearInterval(this._interval);
- this.recorder.stop();
- this.recorder.exportWAV();
- }
- },
+ _generateDistortion(amount = 50) {
- clear : function clear() {
- if (this.recorder) {
- this.progressBar.width(0);
- this.audioElement.attr('src', '');
- this.audioElement[0].load();
- this.recorder.clear()
- }
- },
+ const sampleRate = 44100;
+ const curve = new Float32Array(sampleRate);
+ const angle = Math.PI / 180;
- finalize : function finalize(callback) {
- if (this.recorder) {
- this.recorder.exportWAV(callback);
- }
- },
+ for (let i = 0; i < sampleRate; ++i) {
+ const x = i * 2 / sampleRate - 1;
+ curve[i] = ( 3 + amount ) * x * 20 * angle / ( Math.PI + amount * Math.abs(x) );
+ }
- getBuffer : function getBuffer(callback) {
- if (this.recorder) {
- this.recorder.getBuffer(callback);
- }
- },
+ return curve;
+ },
- _bindEvents : function bindEvents() {
- var pepperoni = this;
+ // Initializes a convolver node by requesting the reverb sound,
+ // decoding it, and attaching it
- this.controlButton.on('click', function () {
- if (this.recording) {
- this.stop();
- } else {
- this.record();
- }
- }.bind(this))
+ _initializeConvolverNode(node) {
- this.clearButton.on('click', function () {
- if (!this.recording) {
- this.clear();
- }
- }.bind(this))
+ const request = new XMLHttpRequest();
+ request.open(config.convolverNode.requestMethod, config.convolverNode.requestLocation);
+ request.responseType = config.convolverNode.responseType;
+ request.addEventListener('load', (event) => {
- this.switches.on('change', function (ev) {
- if (!pepperoni.source) {
- this.checked = false;
- return false;
- }
- switch (this.name) {
- case 'delay-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._delayNode);
- } else {
- pepperoni._removeNode(pepperoni._delayNode);
- }
- break;
-
- case 'hipass-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._hiPassFilterNode);
- } else {
- pepperoni._removeNode(pepperoni._hiPassFilterNode);
- }
- break;
-
- case 'bandpass-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._bandPassFilterNode);
- } else {
- pepperoni._removeNode(pepperoni._bandPassFilterNode);
- }
- break;
-
- case 'lopass-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._loPassFilterNode);
- } else {
- pepperoni._removeNode(pepperoni._loPassFilterNode);
- }
- break;
-
- case 'reverb-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._convolverNode);
- } else {
- pepperoni._removeNode(pepperoni._convolverNode);
- }
- break;
-
- case 'distort-filter':
- if (this.checked) {
- pepperoni._addNode(pepperoni._distortionNode);
- } else {
- pepperoni._removeNode(pepperoni._distortionNode);
- }
-
- break;
- }
+ node.context.decodeAudioData(event.target.response, (buffer) => {
+
+ node.buffer = buffer;
});
- },
+ });
+ request.send();
+ }
- _onRecording : function _onRecording(buffer) {
- this._buffer = buffer;
+ // Handles the XHR response to the reverb file
- this.audioElement.attr('src', URL.createObjectURL(buffer));
- this.audioElement[0].load();
- },
- _onUserMedia : function _onUserMedia(localMediaStream) {
- this.source = this.context.createMediaStreamSource(localMediaStream);
- this.recorder = new Recorder(this.source, {
- workerPath : this.workerPath,
- callback : this._onRecording.bind(this)
- });
- },
- _onUserMediaError : function _onUserMediaError(error) {
- console.log("Something went wrong", error);
- this.disable();
- },
+ if (!this.source) {
+ this._getUserMedia({
+ audio : true
+ }, this._onUserMedia.bind(this), this._onUserMediaError.bind(this))
+ }
- _onRecordCheck : function _onRecordCheck() {
- this._canRecord(function (canRecord, bufferSize) {
- var width = bufferSize * this.progressBarContainer.width() / this.maxSize;
- this.progressBar.width(width);
- if (!canRecord) {
- this.stop();
- }
- }.bind(this));
- },
+ this.element.html(this.constructor.INNER_HTML);
- _canRecord : function _canRecord(callback) {
- this.recorder.getBuffer(function getBuffer(buffer) {
- var bufferSize = buffer[0].length + buffer[1].length;
- callback && callback(bufferSize <= this.maxSize, bufferSize);
- }.bind(this));
- },
+ this.controlButton = this.element.find('.record-button');
+ this.clearButton = this.element.find('.record-clear');
+ this.audioElement = this.element.find('audio');
+ this.progressBarContainer = this.element.find('.record-progress-bar-container');
+ this.progressBar = this.element.find('.record-progress-bar');
+ this.switches = this.element.find('.filter-switch');
- _addNode : function _addNode(node) {
- var i;
+ this._bindEvents();
+};
- i = this._activatedNodes.length;
+const Pepperoni.prototype = {
- this._activatedNodes.push(node);
+ _createNodes() {
+
+ }
- if (i === 0) {
- this.source.disconnect();
- this.source.connect(node);
+ maxSize : 1048576,
+ recording : false,
+ source : null,
+ recorder : null,
+ context : null,
+ _delayNode : null,
+ _bandPassFilterNode : null,
+ _hiPassFilterNode : null,
+ _loPassFilterNode : null,
+ _convolverNode : null,
+ _distortionNode : null,
+ _activatedNodes : null,
+ workerPath : '/js/vendor/recorderjs/recorderWorker.js',
+ init : function init(config) {
+ },
+
+ record : function record() {
+ if (this.recorder && !this.recording) {
+ this._canRecord(function handleCanRecord(canRecord) {
+ if (canRecord) {
+ this.recording = true;
+ this.controlButton.addClass('recording')
+ this.controlButton.html(this.constructor.PAUSE);
+ this._interval = setInterval(this._onRecordCheck.bind(this), 100);
+ this.recorder.record();
+ }
+ }.bind(this));
+ }
+ },
+
+ stop : function stop() {
+ if (this.recorder && this.recording) {
+ this.recording = false;
+ this.controlButton.removeClass('recording')
+ this.controlButton.html(this.constructor.RECORD);
+ clearInterval(this._interval);
+ this.recorder.stop();
+ this.recorder.exportWAV();
+ }
+ },
+
+ clear : function clear() {
+ if (this.recorder) {
+ this.progressBar.width(0);
+ this.audioElement.attr('src', '');
+ this.audioElement[0].load();
+ this.recorder.clear()
+ }
+ },
+
+ finalize : function finalize(callback) {
+ if (this.recorder) {
+ this.recorder.exportWAV(callback);
+ }
+ },
+
+ getBuffer : function getBuffer(callback) {
+ if (this.recorder) {
+ this.recorder.getBuffer(callback);
+ }
+ },
+
+ _bindEvents : function bindEvents() {
+ var pepperoni = this;
+
+ this.controlButton.on('click', function () {
+ if (this.recording) {
+ this.stop();
} else {
- this._activatedNodes[i - 1].disconnect();
- this._activatedNodes[i - 1].connect(node);
+ this.record();
}
+ }.bind(this))
- node.connect(this.recorder.node);
- this.recorder.context = node.context;
- this.recorder.node.connect(this.recorder.context.destination)
-
- console.log("Adding: ", node);
- },
+ this.clearButton.on('click', function () {
+ if (!this.recording) {
+ this.clear();
+ }
+ }.bind(this))
- _removeNode : function _removeNode(node) {
- var i;
-
- i = this._activatedNodes.indexOf(node);
-
- node.disconnect();
-
- if (i === 0 && i + 1 === this._activatedNodes.length) {
- // It was the only one, connect source to recorder.
- this.source.disconnect();
- this.source.connect(this.recorder.node);
- } else if (i === 0) {
- // Normal 0 case, connect source to node. Recorder stays the same
- this.source.disconnect();
- this.source.connect(this._activatedNodes[i+1]);
- } else if (i + 1 === this._activatedNodes.length) {
- // It's not the 0 case, but we need to reconnect to recorder.
- this._activatedNodes[i - 1].disconnect();
- this._activatedNodes[i - 1].connect(this.recorder.node);
- } else {
- // Normal case, connect previous node to node
- this._activatedNodes[i - 1].disconnect();
- this._activatedNodes[i - 1].connect(this._activatedNodes[i + 1]);
+ this.switches.on('change', function (ev) {
+ if (!pepperoni.source) {
+ this.checked = false;
+ return false;
}
+ switch (this.name) {
+ case 'delay-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._delayNode);
+ } else {
+ pepperoni._removeNode(pepperoni._delayNode);
+ }
+ break;
- this._activatedNodes.splice(i, 1);
+ case 'hipass-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._hiPassFilterNode);
+ } else {
+ pepperoni._removeNode(pepperoni._hiPassFilterNode);
+ }
+ break;
- console.log("Removing: ", node);
- },
+ case 'bandpass-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._bandPassFilterNode);
+ } else {
+ pepperoni._removeNode(pepperoni._bandPassFilterNode);
+ }
+ break;
- _generateDistortion : function generateDistortion(amount) {
- var k = typeof amount === 'number' ? amount : 50,
- n_samples = 44100,
- curve = new Float32Array(n_samples),
- deg = Math.PI / 180,
- i = 0,
- x;
- for ( ; i < n_samples; ++i ) {
- x = i * 2 / n_samples - 1;
- curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) );
- }
- return curve;
- },
+ case 'lopass-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._loPassFilterNode);
+ } else {
+ pepperoni._removeNode(pepperoni._loPassFilterNode);
+ }
+ break;
- // Normalize get user media
- _getUserMedia : (navigator.getUserMedia ||
- navigator.webkitGetUserMedia ||
- navigator.mozGetUserMedia ||
- navigator.msGetUserMedia).bind(navigator)
- }
+ case 'reverb-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._convolverNode);
+ } else {
+ pepperoni._removeNode(pepperoni._convolverNode);
+ }
+ break;
+
+ case 'distort-filter':
+ if (this.checked) {
+ pepperoni._addNode(pepperoni._distortionNode);
+ } else {
+ pepperoni._removeNode(pepperoni._distortionNode);
+ }
+
+ break;
+ }
+ });
+ },
+
+ _onRecording : function _onRecording(buffer) {
+ this._buffer = buffer;
+
+ this.audioElement.attr('src', URL.createObjectURL(buffer));
+ this.audioElement[0].load();
+ },
+
+ _onUserMedia : function _onUserMedia(localMediaStream) {
+ this.source = this.context.createMediaStreamSource(localMediaStream);
+ this.recorder = new Recorder(this.source, {
+ workerPath : this.workerPath,
+ callback : this._onRecording.bind(this)
+ });
+ },
+
+ _onUserMediaError : function _onUserMediaError(error) {
+ console.log("Something went wrong", error);
+ this.disable();
+ },
+
+ _onRecordCheck : function _onRecordCheck() {
+ this._canRecord(function (canRecord, bufferSize) {
+ var width = bufferSize * this.progressBarContainer.width() / this.maxSize;
+ this.progressBar.width(width);
+ if (!canRecord) {
+ this.stop();
+ }
+ }.bind(this));
+ },
+
+ _canRecord : function _canRecord(callback) {
+ this.recorder.getBuffer(function getBuffer(buffer) {
+ var bufferSize = buffer[0].length + buffer[1].length;
+ callback && callback(bufferSize <= this.maxSize, bufferSize);
+ }.bind(this));
+ },
+
+ _addNode : function _addNode(node) {
+ var i;
+
+ i = this._activatedNodes.length;
+
+ this._activatedNodes.push(node);
+
+ if (i === 0) {
+ this.source.disconnect();
+ this.source.connect(node);
+ } else {
+ this._activatedNodes[i - 1].disconnect();
+ this._activatedNodes[i - 1].connect(node);
+ }
+
+ node.connect(this.recorder.node);
+ this.recorder.context = node.context;
+ this.recorder.node.connect(this.recorder.context.destination)
+
+ console.log("Adding: ", node);
+ },
+
+ _removeNode : function _removeNode(node) {
+ var i;
+
+ i = this._activatedNodes.indexOf(node);
+
+ node.disconnect();
+
+ if (i === 0 && i + 1 === this._activatedNodes.length) {
+ // It was the only one, connect source to recorder.
+ this.source.disconnect();
+ this.source.connect(this.recorder.node);
+ } else if (i === 0) {
+ // Normal 0 case, connect source to node. Recorder stays the same
+ this.source.disconnect();
+ this.source.connect(this._activatedNodes[i+1]);
+ } else if (i + 1 === this._activatedNodes.length) {
+ // It's not the 0 case, but we need to reconnect to recorder.
+ this._activatedNodes[i - 1].disconnect();
+ this._activatedNodes[i - 1].connect(this.recorder.node);
+ } else {
+ // Normal case, connect previous node to node
+ this._activatedNodes[i - 1].disconnect();
+ this._activatedNodes[i - 1].connect(this._activatedNodes[i + 1]);
+ }
+
+ this._activatedNodes.splice(i, 1);
+
+ console.log("Removing: ", node);
+ },
+
+ // Normalize get user media
+ _getUserMedia : (navigator.getUserMedia ||
+ navigator.webkitGetUserMedia ||
+ navigator.mozGetUserMedia ||
+ navigator.msGetUserMedia).bind(navigator)
+}
});