-Class(UnlimitedPizza, "Pepperoni").inherits(Widget)({
- INNER_HTML : ' \
-<a class="record-button">⬤</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">X</a> \
- </div> \
- <audio class="record-preview" controls></audio> \
-</div> \
- ',
- PAUSE : '▐▐',
- RECORD : '⬤',
- prototype : {
- maxSize : 1048576,
- recording : false,
- source : null,
- recorder : null,
- context : null,
- workerPath : '/js/vendor/recorderjs/recorderWorker.js',
- init : function init(config) {
- Widget.prototype.init.call(this, config);
-
- if (!this.context) {
- this.context = new (window.AudioContext || window.webkitAudioContext)();
- }
+'use strict';
- if (!this.source) {
- this._getUserMedia({
- audio : true
- }, this._onUserMedia.bind(this), this._onUserMediaError.bind(this))
- }
+const internals = {
+
+ config: {
+ convolverNode: {
+ requestMethod: 'GET',
+ requestLocation: '/reverb.ogg',
+ responseType: 'arraybuffer'
+ },
+ distortionNode: {
+ amount: 400
+ }
+ },
- this.element.html(this.constructor.INNER_HTML);
+ 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.',
+};
- 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');
+const Pepperoni = class Pepperoni {
+ constructor() {
- this._bindEvents();
- },
+ const context = new AudioContext();
- 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));
+ 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';
+ }
+
+ // Generates a wave to be used with the distortion wave shaper node
+
+ _generateDistortion(amount = 50) {
+
+ const sampleRate = 44100;
+ const curve = new Float32Array(sampleRate);
+ const angle = Math.PI / 180;
+
+ 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) );
+ }
+
+ return curve;
+ },
+
+ // Initializes a convolver node by requesting the reverb sound,
+ // decoding it, and attaching it
+
+ _initializeConvolverNode(node) {
+
+ const request = new XMLHttpRequest();
+ request.open(config.convolverNode.requestMethod, config.convolverNode.requestLocation);
+ request.responseType = config.convolverNode.responseType;
+ request.addEventListener('load', (event) => {
+
+ node.context.decodeAudioData(event.target.response, (buffer) => {
+
+ node.buffer = buffer;
+ });
+ });
+ request.send();
+ }
+
+ // Handles the XHR response to the reverb file
+
+
+
+ if (!this.source) {
+ this._getUserMedia({
+ audio : true
+ }, this._onUserMedia.bind(this), this._onUserMediaError.bind(this))
+ }
+
+ this.element.html(this.constructor.INNER_HTML);
+
+ 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();
+};
+
+const Pepperoni.prototype = {
+
+ _createNodes() {
+
+ }
+
+ 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.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();
+ this.clearButton.on('click', function () {
+ if (!this.recording) {
+ this.clear();
}
- },
+ }.bind(this))
- clear : function clear() {
- if (this.recorder) {
- this.progressBar.width(0);
- this.audioElement.attr('src', '');
- this.audioElement[0].load();
- this.recorder.clear()
+ 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;
- finalize : function finalize(callback) {
- if (this.recorder) {
- this.recorder.exportWAV(callback);
+ 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)
+ });
+ },
- getBuffer : function getBuffer(callback) {
- if (this.recorder) {
- this.recorder.getBuffer(callback);
+ _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));
+ },
- _bindEvents : function bindEvents() {
- this.controlButton.on('click', function () {
- if (this.recording) {
- this.stop();
- } else {
- this.record();
- }
- }.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));
+ },
- this.clearButton.on('click', function () {
- if (!this.recording) {
- this.clear();
- }
- }.bind(this))
- },
+ _addNode : function _addNode(node) {
+ var i;
- _onRecording : function _onRecording(buffer) {
- this._buffer = buffer;
+ i = this._activatedNodes.length;
- this.audioElement.attr('src', URL.createObjectURL(buffer));
- this.audioElement[0].load();
- },
+ this._activatedNodes.push(node);
- _onUserMedia : function _onUserMedia(localMediaStream) {
- this.source = this.context.createMediaStreamSource(localMediaStream);
- this.recorder = new Recorder(this.source, {
- workerPath : this.workerPath,
- callback : this._onRecording.bind(this)
- });
- },
+ if (i === 0) {
+ this.source.disconnect();
+ this.source.connect(node);
+ } else {
+ this._activatedNodes[i - 1].disconnect();
+ this._activatedNodes[i - 1].connect(node);
+ }
- _onUserMediaError : function _onUserMediaError(error) {
- console.log("Something went wrong", error);
- this.disable();
- },
+ node.connect(this.recorder.node);
+ this.recorder.context = node.context;
+ this.recorder.node.connect(this.recorder.context.destination)
- _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));
- },
+ console.log("Adding: ", node);
+ },
- _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));
- },
+ _removeNode : function _removeNode(node) {
+ var i;
- // Normalize get user media
- _getUserMedia : (navigator.getUserMedia ||
- navigator.webkitGetUserMedia ||
- navigator.mozGetUserMedia ||
- navigator.msGetUserMedia).bind(navigator)
- }
+ 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)
+}
});