Skip to content

Commit 081c9a0

Browse files
committed
Version 2.4.0
- New Compressor2 class with optional sidechain input, adjustable knee and attack/HOLD/release. - Destruct method for the audio node.
1 parent 308c384 commit 081c9a0

File tree

10 files changed

+127
-21
lines changed

10 files changed

+127
-21
lines changed

Diff for: docs.html

+59-1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ <h3>Web Audio</h3>
180180
// message is a standard JavaScript object.
181181
}
182182
);
183+
184+
// Will return false from the process() method of the AudioWorkletNode and run onDestruct to clean up WebAssembly and more.
185+
audioNode.destruct();
183186
</code></pre>
184187

185188
<h3>AudioWorkletProcessor</h3>
@@ -192,6 +195,10 @@ <h3>AudioWorkletProcessor</h3>
192195
onReady() {
193196
// Runs after the constructor. This is "your" constructor basically.
194197
}
198+
onDestruct() {
199+
// Runs before the node is destroyed.
200+
// Clean up memory and objects here (such as free allocated linear memory or destruct Superpowered objects).
201+
}
195202
onMessageFromMainScope(message) {
196203
// Runs when a message (data) is received from the main scope (main thread).
197204

@@ -1145,11 +1152,62 @@ <h3>Compressor</h3>
11451152
// It's never blocking for real-time usage. You can change all properties and call getGainReductionDb() on any thread, concurrently with process().
11461153
// If process() returns with true, the contents of output are replaced with the audio output. If process() returns with false, the contents of output are not changed.
11471154
let changedOutput = compressor.process(
1148-
input // Pointer to floating point numbers. 32-bit interleaved stereo input.
1155+
input, // Pointer to floating point numbers. 32-bit interleaved stereo input.
1156+
output, // Pointer to floating point numbers. 32-bit interleaved stereo output. Can point to the same location with input (in-place processing).
1157+
128 // Number of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
1158+
);
1159+
1160+
// Destructor (to free up memory).
1161+
compressor.destruct();
1162+
</code></pre>
1163+
1164+
<h3>Compressor2</h3>
1165+
1166+
<p>Compressor with 0 latency, adjustable knee and optional sidechain input. It doesn't allocate any internal buffers and needs less than 1 kb of memory.</p>
1167+
1168+
<pre><code class="language-js">
1169+
// Constructor. Enabled is false by default.
1170+
let compressor = new Superpowered.Compressor2(
1171+
44100 // The initial sample rate in Hz.
1172+
);
1173+
1174+
// Do this when the sample rate changes.
1175+
compressor.samplerate = 48000;
1176+
1177+
// Turns the effect on/off. False by default. The actual switch will happen on the next process() call for smooth, audio-artifact free operation.
1178+
compressor.enabled = true;
1179+
1180+
compressor.attackSec = 0.01; // Attack in seconds (not milliseconds!). Limited between 0.00001 and 10. Default: 0.05 (50 ms).
1181+
compressor.holdSec = 0; // Hold segment before release starts, useful to limit unwanted noise with fast rates. 0 to 1 second (not millisecond). Default: 0.005 (5 ms).
1182+
compressor.releaseSec = 0.2; // Release in seconds (not milliseconds!). Limited between 0.00001 and 10. Default: 0.05 (50 ms).
1183+
compressor.ratio = 5; // Ratio, 1 to 1000. Default: 4.
1184+
compressor.thresholdDb = -16; // Threshold in decibels, limited between 0 and -60. Default: -6.
1185+
compressor.softKneeDb = 3 // Width of soft knee in decibels, 0 to 12. Default: 6.
1186+
compressor.outputGainDb = 3; // Output gain in decibels, limited between -24 and 24. Default: 0.
1187+
compressor.automaticGain = false; // If true, gain is set relative to compressor output at 0dB. Useful in digital environments. Default: true.
1188+
1189+
// Returns the maximum gain reduction in decibels since the last getGainReductionDb() call.
1190+
let gain_reduction_db = compressor.getGainReductionDb();
1191+
1192+
// Processes the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation.
1193+
// It's never blocking for real-time usage. You can change all properties and call getGainReductionDb() on any thread, concurrently with process().
1194+
// If process() returns with true, the contents of output are replaced with the audio output. If process() returns with false, the contents of output are not changed.
1195+
let changedOutput = compressor.process(
1196+
input, // Pointer to floating point numbers. 32-bit interleaved stereo input.
11491197
output, // Pointer to floating point numbers. 32-bit interleaved stereo output. Can point to the same location with input (in-place processing).
11501198
128 // Number of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
11511199
);
11521200

1201+
// Processes the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation.
1202+
// It's never blocking for real-time usage. You can change all properties and call getGainReductionDb() on any thread, concurrently with process().
1203+
// If process() returns with true, the contents of output are replaced with the audio output. If process() returns with false, the contents of output are not changed.
1204+
let changedOutput = compressor.processWithSidechain(
1205+
input, // Pointer to floating point numbers. 32-bit interleaved stereo input.
1206+
sidechain, // Pointer to floating point numbers. 32-bit interleaved stereo sidechain input.
1207+
output, // Pointer to floating point numbers. 32-bit interleaved stereo output. Can point to the same location with input (in-place processing).
1208+
128 // Number of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
1209+
);
1210+
11531211
// Destructor (to free up memory).
11541212
compressor.destruct();
11551213
</code></pre>

Diff for: example_effects/superpowered/SuperpoweredWebAudio.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SuperpoweredTrackLoader } from './SuperpoweredTrackLoaderModule.js';
44
var AudioWorkletHasBrokenModuleImplementation = false;
55

66
class SuperpoweredWebAudio {
7-
constructor(minimumSamplerate, superpowered) {
7+
constructor(minimumSamplerate, superpowered) {
88
AudioWorkletHasBrokenModuleImplementation = (navigator.userAgent.indexOf('AppleWebKit') > -1) || (navigator.userAgent.indexOf('Firefox') > -1);
99
if (AudioWorkletHasBrokenModuleImplementation && (navigator.userAgent.indexOf('Chrome') > -1)) AudioWorkletHasBrokenModuleImplementation = false;
1010
this.Superpowered = superpowered;
@@ -95,6 +95,7 @@ class SuperpoweredWebAudio {
9595
});
9696
}
9797
sendMessageToAudioScope(message, transfer = []) { this.port.postMessage(message, transfer); }
98+
destruct() { this.port.postMessage('___superpowered___destruct___'); }
9899
}
99100

100101
let node = new SuperpoweredNode(this, className);
@@ -115,6 +116,9 @@ class SuperpoweredWebAudio {
115116
node.outputBuffer = this.Superpowered.createFloatArray(1024 * 2);
116117
node.processor = new processorModule.default(this.Superpowered, onMessageFromAudioScope, node.samplerate);
117118
node.sendMessageToAudioScope = function(message, transfer = 0) { node.processor.onMessageFromMainScope(message); }
119+
node.destruct = function() {
120+
node.processor.onDestruct();
121+
}
118122
node.onaudioprocess = function(e) {
119123
node.processor.Superpowered.bufferToWASM(node.inputBuffer, e.inputBuffer);
120124
node.processor.processAudio(node.inputBuffer, node.outputBuffer, node.inputBuffer.array.length / 2);
@@ -131,8 +135,13 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
131135
constructor(options) {
132136
super();
133137
SuperpoweredGlue.__uint_max__sp__ = options.processorOptions.maxChannels;
134-
this.port.onmessage = (event) => { this.onMessageFromMainScope(event.data); };
135-
this.ok = false;
138+
this.state = 0;
139+
this.port.onmessage = (event) => {
140+
if (event.data == '___superpowered___destruct___') {
141+
this.state = -1;
142+
this.onDestruct();
143+
} else this.onMessageFromMainScope(event.data);
144+
};
136145
this.samplerate = options.processorOptions.samplerate;
137146
this.Superpowered = new SuperpoweredGlue();
138147
this.Superpowered.loadFromArrayBuffer(options.processorOptions.wasmCode, this);
@@ -143,14 +152,16 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
143152
this.outputBuffer = this.Superpowered.createFloatArray(128 * 2);
144153
this.onReady();
145154
this.port.postMessage('___superpowered___onready___');
146-
this.ok = true;
155+
this.state = 1;
147156
}
148157
onReady() {}
158+
onDestruct() {}
149159
onMessageFromMainScope(message) {}
150160
sendMessageToMainScope(message) { this.port.postMessage(message); }
151161
processAudio(buffer, parameters) {}
152162
process(inputs, outputs, parameters) {
153-
if (this.ok) {
163+
if (this.state < 0) return false;
164+
if (this.state == 1) {
154165
if (inputs[0].length > 1) this.Superpowered.bufferToWASM(this.inputBuffer, inputs);
155166
this.processAudio(this.inputBuffer, this.outputBuffer, this.inputBuffer.array.length / 2, parameters);
156167
if (outputs[0].length > 1) this.Superpowered.bufferToJS(this.outputBuffer, outputs);
@@ -170,6 +181,7 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
170181
}
171182
onMessageFromAudioScope = null;
172183
onReady() {}
184+
onDestruct() {}
173185
onMessageFromMainScope(message) {}
174186
sendMessageToMainScope(message) { if (!this.loader.onmessage({ data: message })) this.onMessageFromAudioScope(message); }
175187
postMessage(message, transfer = []) { this.onMessageFromMainScope(message); }

Diff for: example_effects/superpowered/superpowered.wasm

7.83 KB
Binary file not shown.

Diff for: example_guitardistortion/superpowered/SuperpoweredWebAudio.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SuperpoweredTrackLoader } from './SuperpoweredTrackLoaderModule.js';
44
var AudioWorkletHasBrokenModuleImplementation = false;
55

66
class SuperpoweredWebAudio {
7-
constructor(minimumSamplerate, superpowered) {
7+
constructor(minimumSamplerate, superpowered) {
88
AudioWorkletHasBrokenModuleImplementation = (navigator.userAgent.indexOf('AppleWebKit') > -1) || (navigator.userAgent.indexOf('Firefox') > -1);
99
if (AudioWorkletHasBrokenModuleImplementation && (navigator.userAgent.indexOf('Chrome') > -1)) AudioWorkletHasBrokenModuleImplementation = false;
1010
this.Superpowered = superpowered;
@@ -95,6 +95,7 @@ class SuperpoweredWebAudio {
9595
});
9696
}
9797
sendMessageToAudioScope(message, transfer = []) { this.port.postMessage(message, transfer); }
98+
destruct() { this.port.postMessage('___superpowered___destruct___'); }
9899
}
99100

100101
let node = new SuperpoweredNode(this, className);
@@ -115,6 +116,9 @@ class SuperpoweredWebAudio {
115116
node.outputBuffer = this.Superpowered.createFloatArray(1024 * 2);
116117
node.processor = new processorModule.default(this.Superpowered, onMessageFromAudioScope, node.samplerate);
117118
node.sendMessageToAudioScope = function(message, transfer = 0) { node.processor.onMessageFromMainScope(message); }
119+
node.destruct = function() {
120+
node.processor.onDestruct();
121+
}
118122
node.onaudioprocess = function(e) {
119123
node.processor.Superpowered.bufferToWASM(node.inputBuffer, e.inputBuffer);
120124
node.processor.processAudio(node.inputBuffer, node.outputBuffer, node.inputBuffer.array.length / 2);
@@ -131,8 +135,13 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
131135
constructor(options) {
132136
super();
133137
SuperpoweredGlue.__uint_max__sp__ = options.processorOptions.maxChannels;
134-
this.port.onmessage = (event) => { this.onMessageFromMainScope(event.data); };
135-
this.ok = false;
138+
this.state = 0;
139+
this.port.onmessage = (event) => {
140+
if (event.data == '___superpowered___destruct___') {
141+
this.state = -1;
142+
this.onDestruct();
143+
} else this.onMessageFromMainScope(event.data);
144+
};
136145
this.samplerate = options.processorOptions.samplerate;
137146
this.Superpowered = new SuperpoweredGlue();
138147
this.Superpowered.loadFromArrayBuffer(options.processorOptions.wasmCode, this);
@@ -143,14 +152,16 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
143152
this.outputBuffer = this.Superpowered.createFloatArray(128 * 2);
144153
this.onReady();
145154
this.port.postMessage('___superpowered___onready___');
146-
this.ok = true;
155+
this.state = 1;
147156
}
148157
onReady() {}
158+
onDestruct() {}
149159
onMessageFromMainScope(message) {}
150160
sendMessageToMainScope(message) { this.port.postMessage(message); }
151161
processAudio(buffer, parameters) {}
152162
process(inputs, outputs, parameters) {
153-
if (this.ok) {
163+
if (this.state < 0) return false;
164+
if (this.state == 1) {
154165
if (inputs[0].length > 1) this.Superpowered.bufferToWASM(this.inputBuffer, inputs);
155166
this.processAudio(this.inputBuffer, this.outputBuffer, this.inputBuffer.array.length / 2, parameters);
156167
if (outputs[0].length > 1) this.Superpowered.bufferToJS(this.outputBuffer, outputs);
@@ -170,6 +181,7 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
170181
}
171182
onMessageFromAudioScope = null;
172183
onReady() {}
184+
onDestruct() {}
173185
onMessageFromMainScope(message) {}
174186
sendMessageToMainScope(message) { if (!this.loader.onmessage({ data: message })) this.onMessageFromAudioScope(message); }
175187
postMessage(message, transfer = []) { this.onMessageFromMainScope(message); }
7.83 KB
Binary file not shown.

Diff for: example_timestretching/superpowered/SuperpoweredWebAudio.js

+17-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { SuperpoweredTrackLoader } from './SuperpoweredTrackLoaderModule.js';
44
var AudioWorkletHasBrokenModuleImplementation = false;
55

66
class SuperpoweredWebAudio {
7-
constructor(minimumSamplerate, superpowered) {
7+
constructor(minimumSamplerate, superpowered) {
88
AudioWorkletHasBrokenModuleImplementation = (navigator.userAgent.indexOf('AppleWebKit') > -1) || (navigator.userAgent.indexOf('Firefox') > -1);
99
if (AudioWorkletHasBrokenModuleImplementation && (navigator.userAgent.indexOf('Chrome') > -1)) AudioWorkletHasBrokenModuleImplementation = false;
1010
this.Superpowered = superpowered;
@@ -95,6 +95,7 @@ class SuperpoweredWebAudio {
9595
});
9696
}
9797
sendMessageToAudioScope(message, transfer = []) { this.port.postMessage(message, transfer); }
98+
destruct() { this.port.postMessage('___superpowered___destruct___'); }
9899
}
99100

100101
let node = new SuperpoweredNode(this, className);
@@ -115,6 +116,9 @@ class SuperpoweredWebAudio {
115116
node.outputBuffer = this.Superpowered.createFloatArray(1024 * 2);
116117
node.processor = new processorModule.default(this.Superpowered, onMessageFromAudioScope, node.samplerate);
117118
node.sendMessageToAudioScope = function(message, transfer = 0) { node.processor.onMessageFromMainScope(message); }
119+
node.destruct = function() {
120+
node.processor.onDestruct();
121+
}
118122
node.onaudioprocess = function(e) {
119123
node.processor.Superpowered.bufferToWASM(node.inputBuffer, e.inputBuffer);
120124
node.processor.processAudio(node.inputBuffer, node.outputBuffer, node.inputBuffer.array.length / 2);
@@ -131,8 +135,13 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
131135
constructor(options) {
132136
super();
133137
SuperpoweredGlue.__uint_max__sp__ = options.processorOptions.maxChannels;
134-
this.port.onmessage = (event) => { this.onMessageFromMainScope(event.data); };
135-
this.ok = false;
138+
this.state = 0;
139+
this.port.onmessage = (event) => {
140+
if (event.data == '___superpowered___destruct___') {
141+
this.state = -1;
142+
this.onDestruct();
143+
} else this.onMessageFromMainScope(event.data);
144+
};
136145
this.samplerate = options.processorOptions.samplerate;
137146
this.Superpowered = new SuperpoweredGlue();
138147
this.Superpowered.loadFromArrayBuffer(options.processorOptions.wasmCode, this);
@@ -143,14 +152,16 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
143152
this.outputBuffer = this.Superpowered.createFloatArray(128 * 2);
144153
this.onReady();
145154
this.port.postMessage('___superpowered___onready___');
146-
this.ok = true;
155+
this.state = 1;
147156
}
148157
onReady() {}
158+
onDestruct() {}
149159
onMessageFromMainScope(message) {}
150160
sendMessageToMainScope(message) { this.port.postMessage(message); }
151161
processAudio(buffer, parameters) {}
152162
process(inputs, outputs, parameters) {
153-
if (this.ok) {
163+
if (this.state < 0) return false;
164+
if (this.state == 1) {
154165
if (inputs[0].length > 1) this.Superpowered.bufferToWASM(this.inputBuffer, inputs);
155166
this.processAudio(this.inputBuffer, this.outputBuffer, this.inputBuffer.array.length / 2, parameters);
156167
if (outputs[0].length > 1) this.Superpowered.bufferToJS(this.outputBuffer, outputs);
@@ -170,6 +181,7 @@ if (!AudioWorkletHasBrokenModuleImplementation && (typeof AudioWorkletProcessor
170181
}
171182
onMessageFromAudioScope = null;
172183
onReady() {}
184+
onDestruct() {}
173185
onMessageFromMainScope(message) {}
174186
sendMessageToMainScope(message) { if (!this.loader.onmessage({ data: message })) this.onMessageFromAudioScope(message); }
175187
postMessage(message, transfer = []) { this.onMessageFromMainScope(message); }
7.83 KB
Binary file not shown.

Diff for: superpowered.bc

9.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)