Skip to content

Commit

Permalink
replacement es5 classes to es6 classes feat p5.SoundRecorder (#532)
Browse files Browse the repository at this point in the history
* Replacement of es5 functiond to es6 classes feat p5.SoundRecorder
* removed saveSound method from soundRecorder , as it should be a global method to be used by soundFile , so placed that in helpers.js
  • Loading branch information
endurance21 authored Aug 29, 2020
1 parent 147d826 commit 838f3ac
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 134 deletions.
8 changes: 6 additions & 2 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ p5.prototype.userStartAudio = userStartAudio;

import './master';

import { freqToMidi } from './helpers';
import { freqToMidi, saveSound } from './helpers';
p5.prototype.freqToMidi = freqToMidi;
p5.prototype.saveSound = saveSound;

import './errorHandler';
import './audioWorklet';
Expand Down Expand Up @@ -69,11 +70,14 @@ p5.SoundLoop = SoundLoop;
import Compressor from './compressor';
p5.Compressor = Compressor;

import './soundRecorder';

import peakDetect from './peakDetect';
p5.peakDetect = peakDetect;

import SoundRecorder from './soundRecorder';
p5.SoundRecorder = SoundRecorder;


import Distortion from './distortion';
p5.Distortion = Distortion;

Expand Down
17 changes: 17 additions & 0 deletions src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,23 @@ export function safeBufferSize(idealBufferSize) {
return bufferSize;
}

/**
* Save a p5.SoundFile as a .wav file. The browser will prompt the user
* to download the file to their device.
* For uploading audio to a server, use
* <a href="/docs/reference/#/p5.SoundFile/saveBlob">`p5.SoundFile.saveBlob`</a>.
*
* @for p5
* @method saveSound
* @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save
* @param {String} fileName name of the resulting .wav file.
*/
// add to p5.prototype as this is used by the p5 `save()` method.
export function saveSound(soundFile, fileName) {
const dataView = convertToWav(soundFile.buffer);
p5.prototype.writeFile([dataView], fileName, 'wav');
}

// export default {
// // convertToWav: convertToWav,
// // midiToFreq: midiToFreq,
Expand Down
251 changes: 119 additions & 132 deletions src/soundRecorder.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// inspiration: recorder.js, Tone.js & typedarray.org

import p5sound from './master';
import { convertToWav, safeBufferSize } from './helpers';
import { safeBufferSize } from './helpers';
import processorNames from './audioWorklet/processorNames';

const ac = p5sound.audiocontext;
Expand Down Expand Up @@ -80,145 +80,132 @@ const ac = p5sound.audiocontext;
* }
* </div></code>
*/
p5.SoundRecorder = function () {
this.input = ac.createGain();
this.output = ac.createGain();

this._inputChannels = 2;
this._outputChannels = 2; // stereo output, even if input is mono

const workletBufferSize = safeBufferSize(1024);

this._workletNode = new AudioWorkletNode(
ac,
processorNames.recorderProcessor,
{
outputChannelCount: [this._outputChannels],
processorOptions: {
numInputChannels: this._inputChannels,
bufferSize: workletBufferSize,
},
}
);

this._workletNode.port.onmessage = function (event) {
if (event.data.name === 'buffers') {
const buffers = [
new Float32Array(event.data.leftBuffer),
new Float32Array(event.data.rightBuffer),
];
this._callback(buffers);
}
}.bind(this);
class SoundRecorder {
constructor() {
this.input = ac.createGain();
this.output = ac.createGain();

this._inputChannels = 2;
this._outputChannels = 2; // stereo output, even if input is mono

const workletBufferSize = safeBufferSize(1024);

this._workletNode = new AudioWorkletNode(
ac,
processorNames.recorderProcessor,
{
outputChannelCount: [this._outputChannels],
processorOptions: {
numInputChannels: this._inputChannels,
bufferSize: workletBufferSize,
},
}
);

this._workletNode.port.onmessage = function (event) {
if (event.data.name === 'buffers') {
const buffers = [
new Float32Array(event.data.leftBuffer),
new Float32Array(event.data.rightBuffer),
];
this._callback(buffers);
}
}.bind(this);

/**
* callback invoked when the recording is over
* @private
* @type Function(Float32Array)
*/
this._callback = function () {};

// connections
this._workletNode.connect(p5.soundOut._silentNode);
this.setInput();

// add this p5.SoundFile to the soundArray
p5sound.soundArray.push(this);
}

/**
* callback invoked when the recording is over
* @private
* @type Function(Float32Array)
* Connect a specific device to the p5.SoundRecorder.
* If no parameter is given, p5.SoundRecorer will record
* all audible p5.sound from your sketch.
*
* @method setInput
* @for p5.SoundRecorder
* @param {Object} [unit] p5.sound object or a web audio unit
* that outputs sound
*/
this._callback = function () {};

// connections
this._workletNode.connect(p5.soundOut._silentNode);
this.setInput();

// add this p5.SoundFile to the soundArray
p5sound.soundArray.push(this);
};

/**
* Connect a specific device to the p5.SoundRecorder.
* If no parameter is given, p5.SoundRecorer will record
* all audible p5.sound from your sketch.
*
* @method setInput
* @for p5.SoundRecorder
* @param {Object} [unit] p5.sound object or a web audio unit
* that outputs sound
*/
p5.SoundRecorder.prototype.setInput = function (unit) {
this.input.disconnect();
this.input = null;
this.input = ac.createGain();
this.input.connect(this._workletNode);
this.input.connect(this.output);
if (unit) {
unit.connect(this.input);
} else {
p5.soundOut.output.connect(this.input);
setInput(unit) {
this.input.disconnect();
this.input = null;
this.input = ac.createGain();
this.input.connect(this._workletNode);
this.input.connect(this.output);
if (unit) {
unit.connect(this.input);
} else {
p5.soundOut.output.connect(this.input);
}
}
};

/**
* Start recording. To access the recording, provide
* a p5.SoundFile as the first parameter. The p5.SoundRecorder
* will send its recording to that p5.SoundFile for playback once
* recording is complete. Optional parameters include duration
* (in seconds) of the recording, and a callback function that
* will be called once the complete recording has been
* transfered to the p5.SoundFile.
*
* @method record
* @for p5.SoundRecorder
* @param {p5.SoundFile} soundFile p5.SoundFile
* @param {Number} [duration] Time (in seconds)
* @param {Function} [callback] The name of a function that will be
* called once the recording completes
*/
p5.SoundRecorder.prototype.record = function (sFile, duration, callback) {
this._workletNode.port.postMessage({ name: 'start', duration: duration });

if (sFile && callback) {
this._callback = function (buffer) {
sFile.setBuffer(buffer);
callback();
};
} else if (sFile) {
this._callback = function (buffer) {
sFile.setBuffer(buffer);
};
/**
* Start recording. To access the recording, provide
* a p5.SoundFile as the first parameter. The p5.SoundRecorder
* will send its recording to that p5.SoundFile for playback once
* recording is complete. Optional parameters include duration
* (in seconds) of the recording, and a callback function that
* will be called once the complete recording has been
* transfered to the p5.SoundFile.
*
* @method record
* @for p5.SoundRecorder
* @param {p5.SoundFile} soundFile p5.SoundFile
* @param {Number} [duration] Time (in seconds)
* @param {Function} [callback] The name of a function that will be
* called once the recording completes
*/
record(sFile, duration, callback) {
this._workletNode.port.postMessage({ name: 'start', duration: duration });

if (sFile && callback) {
this._callback = function (buffer) {
sFile.setBuffer(buffer);
callback();
};
} else if (sFile) {
this._callback = function (buffer) {
sFile.setBuffer(buffer);
};
}
}
};

/**
* Stop the recording. Once the recording is stopped,
* the results will be sent to the p5.SoundFile that
* was given on .record(), and if a callback function
* was provided on record, that function will be called.
*
* @method stop
* @for p5.SoundRecorder
*/
p5.SoundRecorder.prototype.stop = function () {
this._workletNode.port.postMessage({ name: 'stop' });
};
/**
* Stop the recording. Once the recording is stopped,
* the results will be sent to the p5.SoundFile that
* was given on .record(), and if a callback function
* was provided on record, that function will be called.
*
* @method stop
* @for p5.SoundRecorder
*/
stop() {
this._workletNode.port.postMessage({ name: 'stop' });
}

p5.SoundRecorder.prototype.dispose = function () {
// remove reference from soundArray
var index = p5sound.soundArray.indexOf(this);
p5sound.soundArray.splice(index, 1);
dispose() {
// remove reference from soundArray
var index = p5sound.soundArray.indexOf(this);
p5sound.soundArray.splice(index, 1);

this._callback = function () {};
if (this.input) {
this.input.disconnect();
this._callback = function () {};
if (this.input) {
this.input.disconnect();
}
this.input = null;
this._workletNode = null;
}
this.input = null;
this._workletNode = null;
};
}

/**
* Save a p5.SoundFile as a .wav file. The browser will prompt the user
* to download the file to their device.
* For uploading audio to a server, use
* <a href="/docs/reference/#/p5.SoundFile/saveBlob">`p5.SoundFile.saveBlob`</a>.
*
* @for p5
* @method saveSound
* @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save
* @param {String} fileName name of the resulting .wav file.
*/
// add to p5.prototype as this is used by the p5 `save()` method.
p5.prototype.saveSound = function (soundFile, fileName) {
const dataView = convertToWav(soundFile.buffer);
p5.prototype.writeFile([dataView], fileName, 'wav');
};
export default SoundRecorder;

0 comments on commit 838f3ac

Please sign in to comment.