Skip to content

Commit

Permalink
Merge pull request #373 from oshoham/audioworklet-soundfile-amplitude
Browse files Browse the repository at this point in the history
Replace ScriptProcessorNode with AudioWorkletNode in p5.SoundFile and p5.Amplitude
  • Loading branch information
oshoham authored Jul 16, 2019
2 parents e03ab3d + c3b97d9 commit 7d61e7e
Show file tree
Hide file tree
Showing 12 changed files with 385 additions and 283 deletions.
371 changes: 190 additions & 181 deletions lib/p5.sound.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/p5.sound.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/p5.sound.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/p5.sound.min.js.map

Large diffs are not rendered by default.

28 changes: 21 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

103 changes: 30 additions & 73 deletions src/amplitude.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

define(function (require) {
var p5sound = require('master');
var processorNames = require('./audioWorklet/processorNames');

/**
* Amplitude measures volume between 0.0 and 1.0.
Expand Down Expand Up @@ -50,38 +51,42 @@ define(function (require) {

// set audio context
this.audiocontext = p5sound.audiocontext;
this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1);
this._workletNode = new AudioWorkletNode(this.audiocontext, processorNames.amplitudeProcessor, {
outputChannelCount: [1],
parameterData: { smoothing: smoothing || 0 },
processorOptions: { normalize: false }
});

this._workletNode.port.onmessage = function(event) {
if (event.data.name === 'amplitude') {
this.volume = event.data.volume;
this.volNorm = event.data.volNorm;
this.stereoVol = event.data.stereoVol;
this.stereoVolNorm = event.data.stereoVolNorm;
}
}.bind(this);

// for connections
this.input = this.processor;
this.input = this._workletNode;

this.output = this.audiocontext.createGain();
// smoothing defaults to 0
this.smoothing = smoothing || 0;


// the variables to return
this.volume = 0;
this.average = 0;

this.volNorm = 0;
this.stereoVol = [0, 0];
this.stereoAvg = [0, 0];
this.stereoVolNorm = [0, 0];

this.volMax = 0.001;
this.normalize = false;

this.processor.onaudioprocess = this._audioProcess.bind(this);


this.processor.connect(this.output);
this._workletNode.connect(this.output);
this.output.gain.value = 0;

// this may only be necessary because of a Chrome bug
this.output.connect(this.audiocontext.destination);

// connect to p5sound master output by default, unless set by input()
p5sound.meter.connect(this.processor);
p5sound.meter.connect(this._workletNode);

// add this p5.SoundFile to the soundArray
p5sound.soundArray.push(this);
Expand Down Expand Up @@ -128,29 +133,29 @@ define(function (require) {
p5sound.meter.disconnect();

if (smoothing) {
this.smoothing = smoothing;
this._workletNode.parameters.get('smoothing').value = smoothing;
}

// connect to the master out of p5s instance if no snd is provided
if (source == null) {
console.log('Amplitude input source is not ready! Connecting to master output instead');
p5sound.meter.connect(this.processor);
p5sound.meter.connect(this._workletNode);
}

// if it is a p5.Signal
else if (source instanceof p5.Signal) {
source.output.connect(this.processor);
source.output.connect(this._workletNode);
}
// connect to the sound if it is available
else if (source) {
source.connect(this.processor);
this.processor.disconnect();
this.processor.connect(this.output);
source.connect(this._workletNode);
this._workletNode.disconnect();
this._workletNode.connect(this.output);
}

// otherwise, connect to the master out of p5s instance (default)
else {
p5sound.meter.connect(this.processor);
p5sound.meter.connect(this._workletNode);
}
};

Expand All @@ -172,56 +177,6 @@ define(function (require) {
}
};

// TO DO make this stereo / dependent on # of audio channels
p5.Amplitude.prototype._audioProcess = function(event) {

for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) {
var inputBuffer = event.inputBuffer.getChannelData(channel);
var bufLength = inputBuffer.length;

var total = 0;
var sum = 0;
var x;

for (var i = 0; i < bufLength; i++) {
x = inputBuffer[i];
if (this.normalize) {
total += Math.max(Math.min(x/this.volMax, 1), -1);
sum += Math.max(Math.min(x/this.volMax, 1), -1) * Math.max(Math.min(x/this.volMax, 1), -1);
}
else {
total += x;
sum += x * x;
}
}
var average = total/ bufLength;

// ... then take the square root of the sum.
var rms = Math.sqrt(sum / bufLength);

this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing);
this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing);
this.volMax = Math.max(this.stereoVol[channel], this.volMax);
}

// add volume from all channels together
var self = this;
var volSum = this.stereoVol.reduce(function(previousValue, currentValue, index) {
self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1]/self.volMax, 1), 0);
self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index]/self.volMax, 1), 0);

return previousValue + currentValue;
});

// volume is average of channels
this.volume = volSum / this.stereoVol.length;

// normalized value
this.volNorm = Math.max(Math.min(this.volume/this.volMax, 1), 0);


};

/**
* Returns a single Amplitude reading at the moment it is called.
* For continuous readings, run in the draw loop.
Expand Down Expand Up @@ -288,6 +243,7 @@ define(function (require) {
else {
this.normalize = !this.normalize;
}
this._workletNode.port.postMessage({ name: 'toggleNormalize', normalize: this.normalize });
};

/**
Expand All @@ -300,7 +256,7 @@ define(function (require) {
*/
p5.Amplitude.prototype.smooth = function(s) {
if (s >= 0 && s < 1) {
this.smoothing = s;
this._workletNode.parameters.get('smoothing').value = s;
} else {
console.log('Error: smoothing must be between 0 and 1');
}
Expand All @@ -320,7 +276,8 @@ define(function (require) {
delete this.output;
}

delete this.processor;
this._workletNode.disconnect();
delete this._workletNode;
};

});
93 changes: 93 additions & 0 deletions src/audioWorklet/amplitudeProcessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// import processor name via preval.require so that it's available as a value at compile time
const processorNames = preval.require('./processorNames');

class AmplitudeProcessor extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [
{
name: 'smoothing',
defaultValue: 0,
minValue: 0,
maxValue: 1,
automationRate: 'k-rate'
}
];
}

constructor(options) {
super();

const processorOptions = options.processorOptions || {};
this.normalize = processorOptions.normalize || false;

this.stereoVol = [0, 0];
this.stereoVolNorm = [0, 0];

this.volMax = 0.001;

this.port.onmessage = (event) => {
const data = event.data;
if (data.name === 'toggleNormalize') {
this.normalize = data.normalize;
}
};
}

// TO DO make this stereo / dependent on # of audio channels
process(inputs, outputs, parameters) {
const input = inputs[0];
const output = outputs[0];
const smoothing = parameters.smoothing;

for (let channel = 0; channel < input.length; ++channel) {
const inputBuffer = input[channel];
const bufLength = inputBuffer.length;

let sum = 0;
for (var i = 0; i < bufLength; i++) {
const x = inputBuffer[i];
if (this.normalize) {
sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1);
} else {
sum += x * x;
}
}

// ... then take the square root of the sum.
const rms = Math.sqrt(sum / bufLength);

this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * smoothing);
this.volMax = Math.max(this.stereoVol[channel], this.volMax);
}

// calculate stero normalized volume and add volume from all channels together
let volSum = 0;
for (let index = 0; index < this.stereoVol.length; index++) {
this.stereoVolNorm[index] = Math.max(Math.min(this.stereoVol[index] / this.volMax, 1), 0);
volSum += this.stereoVol[index];
}

// volume is average of channels
const volume = volSum / this.stereoVol.length;

// normalized value
const volNorm = Math.max(Math.min(volume / this.volMax, 1), 0);

this.port.postMessage({
name: 'amplitude',
volume: volume,
volNorm: volNorm,
stereoVol: this.stereoVol,
stereoVolNorm: this.stereoVolNorm
});

// pass input through to output
for (let channel = 0; channel < output.length; ++channel) {
output[channel].set(input[channel]);
}

return true;
}
}

registerProcessor(processorNames.amplitudeProcessor, AmplitudeProcessor);
Loading

0 comments on commit 7d61e7e

Please sign in to comment.