Skip to content

Commit 7d61e7e

Browse files
authored
Merge pull request #373 from oshoham/audioworklet-soundfile-amplitude
Replace ScriptProcessorNode with AudioWorkletNode in p5.SoundFile and p5.Amplitude
2 parents e03ab3d + c3b97d9 commit 7d61e7e

12 files changed

+385
-283
lines changed

lib/p5.sound.js

Lines changed: 190 additions & 181 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/p5.sound.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/p5.sound.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/p5.sound.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/amplitude.js

Lines changed: 30 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
define(function (require) {
44
var p5sound = require('master');
5+
var processorNames = require('./audioWorklet/processorNames');
56

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

5152
// set audio context
5253
this.audiocontext = p5sound.audiocontext;
53-
this.processor = this.audiocontext.createScriptProcessor(this.bufferSize, 2, 1);
54+
this._workletNode = new AudioWorkletNode(this.audiocontext, processorNames.amplitudeProcessor, {
55+
outputChannelCount: [1],
56+
parameterData: { smoothing: smoothing || 0 },
57+
processorOptions: { normalize: false }
58+
});
59+
60+
this._workletNode.port.onmessage = function(event) {
61+
if (event.data.name === 'amplitude') {
62+
this.volume = event.data.volume;
63+
this.volNorm = event.data.volNorm;
64+
this.stereoVol = event.data.stereoVol;
65+
this.stereoVolNorm = event.data.stereoVolNorm;
66+
}
67+
}.bind(this);
5468

5569
// for connections
56-
this.input = this.processor;
70+
this.input = this._workletNode;
5771

5872
this.output = this.audiocontext.createGain();
59-
// smoothing defaults to 0
60-
this.smoothing = smoothing || 0;
61-
6273

6374
// the variables to return
6475
this.volume = 0;
65-
this.average = 0;
66-
76+
this.volNorm = 0;
6777
this.stereoVol = [0, 0];
68-
this.stereoAvg = [0, 0];
6978
this.stereoVolNorm = [0, 0];
7079

71-
this.volMax = 0.001;
7280
this.normalize = false;
7381

74-
this.processor.onaudioprocess = this._audioProcess.bind(this);
75-
76-
77-
this.processor.connect(this.output);
82+
this._workletNode.connect(this.output);
7883
this.output.gain.value = 0;
7984

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

8388
// connect to p5sound master output by default, unless set by input()
84-
p5sound.meter.connect(this.processor);
89+
p5sound.meter.connect(this._workletNode);
8590

8691
// add this p5.SoundFile to the soundArray
8792
p5sound.soundArray.push(this);
@@ -128,29 +133,29 @@ define(function (require) {
128133
p5sound.meter.disconnect();
129134

130135
if (smoothing) {
131-
this.smoothing = smoothing;
136+
this._workletNode.parameters.get('smoothing').value = smoothing;
132137
}
133138

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

140145
// if it is a p5.Signal
141146
else if (source instanceof p5.Signal) {
142-
source.output.connect(this.processor);
147+
source.output.connect(this._workletNode);
143148
}
144149
// connect to the sound if it is available
145150
else if (source) {
146-
source.connect(this.processor);
147-
this.processor.disconnect();
148-
this.processor.connect(this.output);
151+
source.connect(this._workletNode);
152+
this._workletNode.disconnect();
153+
this._workletNode.connect(this.output);
149154
}
150155

151156
// otherwise, connect to the master out of p5s instance (default)
152157
else {
153-
p5sound.meter.connect(this.processor);
158+
p5sound.meter.connect(this._workletNode);
154159
}
155160
};
156161

@@ -172,56 +177,6 @@ define(function (require) {
172177
}
173178
};
174179

175-
// TO DO make this stereo / dependent on # of audio channels
176-
p5.Amplitude.prototype._audioProcess = function(event) {
177-
178-
for (var channel = 0; channel < event.inputBuffer.numberOfChannels; channel++) {
179-
var inputBuffer = event.inputBuffer.getChannelData(channel);
180-
var bufLength = inputBuffer.length;
181-
182-
var total = 0;
183-
var sum = 0;
184-
var x;
185-
186-
for (var i = 0; i < bufLength; i++) {
187-
x = inputBuffer[i];
188-
if (this.normalize) {
189-
total += Math.max(Math.min(x/this.volMax, 1), -1);
190-
sum += Math.max(Math.min(x/this.volMax, 1), -1) * Math.max(Math.min(x/this.volMax, 1), -1);
191-
}
192-
else {
193-
total += x;
194-
sum += x * x;
195-
}
196-
}
197-
var average = total/ bufLength;
198-
199-
// ... then take the square root of the sum.
200-
var rms = Math.sqrt(sum / bufLength);
201-
202-
this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * this.smoothing);
203-
this.stereoAvg[channel] = Math.max(average, this.stereoVol[channel] * this.smoothing);
204-
this.volMax = Math.max(this.stereoVol[channel], this.volMax);
205-
}
206-
207-
// add volume from all channels together
208-
var self = this;
209-
var volSum = this.stereoVol.reduce(function(previousValue, currentValue, index) {
210-
self.stereoVolNorm[index - 1] = Math.max(Math.min(self.stereoVol[index - 1]/self.volMax, 1), 0);
211-
self.stereoVolNorm[index] = Math.max(Math.min(self.stereoVol[index]/self.volMax, 1), 0);
212-
213-
return previousValue + currentValue;
214-
});
215-
216-
// volume is average of channels
217-
this.volume = volSum / this.stereoVol.length;
218-
219-
// normalized value
220-
this.volNorm = Math.max(Math.min(this.volume/this.volMax, 1), 0);
221-
222-
223-
};
224-
225180
/**
226181
* Returns a single Amplitude reading at the moment it is called.
227182
* For continuous readings, run in the draw loop.
@@ -288,6 +243,7 @@ define(function (require) {
288243
else {
289244
this.normalize = !this.normalize;
290245
}
246+
this._workletNode.port.postMessage({ name: 'toggleNormalize', normalize: this.normalize });
291247
};
292248

293249
/**
@@ -300,7 +256,7 @@ define(function (require) {
300256
*/
301257
p5.Amplitude.prototype.smooth = function(s) {
302258
if (s >= 0 && s < 1) {
303-
this.smoothing = s;
259+
this._workletNode.parameters.get('smoothing').value = s;
304260
} else {
305261
console.log('Error: smoothing must be between 0 and 1');
306262
}
@@ -320,7 +276,8 @@ define(function (require) {
320276
delete this.output;
321277
}
322278

323-
delete this.processor;
279+
this._workletNode.disconnect();
280+
delete this._workletNode;
324281
};
325282

326283
});
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// import processor name via preval.require so that it's available as a value at compile time
2+
const processorNames = preval.require('./processorNames');
3+
4+
class AmplitudeProcessor extends AudioWorkletProcessor {
5+
static get parameterDescriptors() {
6+
return [
7+
{
8+
name: 'smoothing',
9+
defaultValue: 0,
10+
minValue: 0,
11+
maxValue: 1,
12+
automationRate: 'k-rate'
13+
}
14+
];
15+
}
16+
17+
constructor(options) {
18+
super();
19+
20+
const processorOptions = options.processorOptions || {};
21+
this.normalize = processorOptions.normalize || false;
22+
23+
this.stereoVol = [0, 0];
24+
this.stereoVolNorm = [0, 0];
25+
26+
this.volMax = 0.001;
27+
28+
this.port.onmessage = (event) => {
29+
const data = event.data;
30+
if (data.name === 'toggleNormalize') {
31+
this.normalize = data.normalize;
32+
}
33+
};
34+
}
35+
36+
// TO DO make this stereo / dependent on # of audio channels
37+
process(inputs, outputs, parameters) {
38+
const input = inputs[0];
39+
const output = outputs[0];
40+
const smoothing = parameters.smoothing;
41+
42+
for (let channel = 0; channel < input.length; ++channel) {
43+
const inputBuffer = input[channel];
44+
const bufLength = inputBuffer.length;
45+
46+
let sum = 0;
47+
for (var i = 0; i < bufLength; i++) {
48+
const x = inputBuffer[i];
49+
if (this.normalize) {
50+
sum += Math.max(Math.min(x / this.volMax, 1), -1) * Math.max(Math.min(x / this.volMax, 1), -1);
51+
} else {
52+
sum += x * x;
53+
}
54+
}
55+
56+
// ... then take the square root of the sum.
57+
const rms = Math.sqrt(sum / bufLength);
58+
59+
this.stereoVol[channel] = Math.max(rms, this.stereoVol[channel] * smoothing);
60+
this.volMax = Math.max(this.stereoVol[channel], this.volMax);
61+
}
62+
63+
// calculate stero normalized volume and add volume from all channels together
64+
let volSum = 0;
65+
for (let index = 0; index < this.stereoVol.length; index++) {
66+
this.stereoVolNorm[index] = Math.max(Math.min(this.stereoVol[index] / this.volMax, 1), 0);
67+
volSum += this.stereoVol[index];
68+
}
69+
70+
// volume is average of channels
71+
const volume = volSum / this.stereoVol.length;
72+
73+
// normalized value
74+
const volNorm = Math.max(Math.min(volume / this.volMax, 1), 0);
75+
76+
this.port.postMessage({
77+
name: 'amplitude',
78+
volume: volume,
79+
volNorm: volNorm,
80+
stereoVol: this.stereoVol,
81+
stereoVolNorm: this.stereoVolNorm
82+
});
83+
84+
// pass input through to output
85+
for (let channel = 0; channel < output.length; ++channel) {
86+
output[channel].set(input[channel]);
87+
}
88+
89+
return true;
90+
}
91+
}
92+
93+
registerProcessor(processorNames.amplitudeProcessor, AmplitudeProcessor);

0 commit comments

Comments
 (0)