FX Class: Compressor 2

Interactive example


Behold, a great sounding side-chain compressor running in the browser that you've been looking for!


Overview

This compressor 2 effect is a slightly different compressor to the first version seen in Superpowered Compressor. Compressor 2, allows the use of an optional side-chain input and adjustable knee. The parameters, default values and ranges are also slightly tweaked for a different sound.

A side-chain compressor is used to reduce the volume of an audio source based on the input from another source. It is often used to artificially create a 'pumping' effect in music, but also used widely in broadcast and podcasts to duck music while a presenter is talking for example.

The outputGainDb property is set with values between `-24` and `24` with a default of 0. The attackSec and releaseSec properties are limited to values between `0.0001` and `10` (in seconds). holdSec is used to hold the gain reduction for a period of time between 0 and 1 before the compressor release begins. This is useful to limit unwanted noise with fast rates.

The thresholdDb and ratio properties are integral to the effect with the ratio specifying the amount of attenuation applied to the signal and threshold setting the level at which the compression effect is engaged.

The softKneeDb represents the width of the compressors soft knee attenuation curve between `0` and `12` in decibels. automaticGain allows the gain is set relative to compressor output at 0dB. Useful in digital environments.

Finally, there is a getGainReductionDb() method which will return the maximum gain reduction in decibels since the last getGainReductionDb() call.

Use process() for use without a side-chain input or use processWithSidechain() is the side-chain functionality is required.

The compressor2 class operates with 0 latency, doesn't allocate any internal buffers and needs less than 1 kb of memory.


Implementation example

The compressor is is an FX class, it expects both an input and output buffer. The process() method should be called on every loop of the audio processing block.

class SuperpoweredCompressor2StageProcessor extends SuperpoweredWebAudio.AudioWorkletProcessor {
// runs after the constructor
onReady() {
this.mixer = new this.Superpowered.StereoMixer(this.samplerate, 2, 2, 0, 0.501, 2, false);
this.player = new this.Superpowered.AdvancedAudioPlayer(this.samplerate, 2, 2, 0, 0.501, 2, false);
// this.player.loopOnEOF = true;
this.playerOutputBuffer = new this.Superpowered.Float32Buffer(4096);
this.sidechain = new this.Superpowered.AdvancedAudioPlayer(this.samplerate, 2, 2, 0, 0.501, 2, false);
// this.sidechain.loopOnEOF = true;
this.sidechainOutputBuffer = new this.Superpowered.Float32Buffer(4096);
this.compressor = new this.Superpowered.Compressor2(
this.samplerate, // The initial sample rate in Hz.
);
this.compressorOutputBuffer = new this.Superpowered.Float32Buffer(4096);
this.compressor.automaticGain = false;
this.compressor.enabled = true;
this.compressor.attackSec = 0.03;
this.compressor.holdSec = 0.21;
this.compressor.releaseSec = 0.5;
this.compressor.outputGainDb = 0;
this.compressor.ratio = 10;
this.compressor.thresholdDb = -50;
this.compressorGain = 1;
this.sidechainGain = 1;
this.loadAudio('/audio/samples/kicks.wav');
this.loadAudio('/audio/samples/pads.wav');
}
onDestruct() {
this.mixer.destruct();
this.player.destruct();
this.playerOutputBuffer.free();
this.sidechain.destruct();
this.sidechainOutputBuffer.free();
this.compressor.destruct();
}
// Called from elsewhere
loadAudio(url) {
this.Superpowered.downloadAndDecode(url, this);
}
// messages are received from the main scope through this method.
onMessageFromMainScope(message) {
if (typeof message.inputGainDb !== 'undefined') this.compressor.inputGainDb = message.inputGainDb;
if (typeof message.outputGainDb !== 'undefined') this.compressor.outputGainDb = message.outputGainDb;
if (typeof message.attackSec !== 'undefined') this.compressor.attackSec = message.attackSec;
if (typeof message.releaseSec !== 'undefined') this.compressor.releaseSec = message.releaseSec;
if (typeof message.ratio !== 'undefined') this.compressor.ratio = message.ratio;
if (typeof message.thresholdDb !== 'undefined') this.compressor.thresholdDb = message.thresholdDb;
if (typeof message.hpCutOffHz !== 'undefined') this.compressor.hpCutOffHz = message.hpCutOffHz;
if (typeof message.holdSec !== 'undefined') this.compressor.holdSec = message.holdSec;
if (typeof message.automaticGain !== 'undefined') this.compressor.automaticGain = message.automaticGain;
if (typeof message.softKneeDb !== 'undefined') this.compressor.softKneeDb = message.softKneeDb;
if (typeof message.enabled !== 'undefined') this.compressor.enabled = Boolean(message.enabled);
if (typeof message.soloSidechain !== 'undefined') {
this.compressorGain = message.soloSidechain ? 0 : 1;
}
if (message.SuperpoweredLoaded) {
// Once the asst is loaded, load buffer into player
// quick conditional to check which file it is that has loaded
if (message.SuperpoweredLoaded.url.includes('kicks')) {
this.sidechain.openMemory(this.Superpowered.arrayBufferToWASM(message.SuperpoweredLoaded.buffer), true, false);
} else {
this.player.openMemory(this.Superpowered.arrayBufferToWASM(message.SuperpoweredLoaded.buffer), true, false);
}
}
}
processAudio(inputBuffer, outputBuffer, buffersize, parameters) {
switch (this.player.getLatestEvent()) {
case this.Superpowered.AdvancedAudioPlayer.PlayerEvent_Opened:
this.player.loop(0, this.player.getDurationMs(), true, 255, false, 0, false, false);
this.player.play();
}
switch (this.sidechain.getLatestEvent()) {
case this.Superpowered.AdvancedAudioPlayer.PlayerEvent_Opened:
this.sidechain.loop(0, this.sidechain.getDurationMs(), true, 255, false, 0, false, false);
this.sidechain.play();
}
// Pads player
if (!this.player.processStereo(this.playerOutputBuffer.pointer, false, buffersize , 1)) {
this.Superpowered.memorySet(this.playerOutputBuffer.pointer, 0, buffersize * 8); // 8 bytes for each frame (1 channel is 4 bytes)
}
// Kick drum player
if (!this.sidechain.processStereo(this.sidechainOutputBuffer.pointer, false, buffersize , 1)) {
this.Superpowered.memorySet(this.sidechainOutputBuffer.pointer, 0, buffersize * 8); // 8 bytes for each frame (1 channel is 4 bytes)
}
// Sidechain compressor
if (!this.compressor.processWithSidechain(this.playerOutputBuffer.pointer, this.sidechainOutputBuffer.pointer, this.compressorOutputBuffer.pointer, buffersize)) {
this.Superpowered.memorySet(this.compressorOutputBuffer.pointer, 0, buffersize * 8); // 8 bytes for each frame (1 channel is 4 bytes)
}
// Output gain. Default value for all: 1. Changes between consecutive process() calls are automatically smoothed.
this.mixer.inputGain[0] = this.compressorGain; // left side
this.mixer.inputGain[1] = this.compressorGain; // right side
this.mixer.inputGain[2] = this.sidechainGain; // left side - why does this not work?
this.mixer.inputGain[3] = this.sidechainGain; // right side - why does this not work?
// Processes the audio. Has no return value.
this.mixer.process(
this.compressorOutputBuffer.pointer, // Pointer to floating point numbers. 32-bit interleaved stereo input buffer for the first input. Can be null.
this.sidechainOutputBuffer.pointer, // Pointer to floating point numbers. 32-bit interleaved stereo input buffer for the second input. Can be null.
0, // Pointer to floating point numbers. 32-bit interleaved stereo input buffer for the third input. Can be null.
0, // Pointer to floating point numbers. 32-bit interleaved stereo input buffer for the fourth input. Can be null.
outputBuffer.pointer, // Pointer to floating point numbers. 32-bit interleaved stereo output buffer.
buffersize // Number of frames to process. Must be an even number.
);
}
}
#include "SuperpoweredCompressor2.h"
void initCompressor() {
unsigned int sampleRate = 44100;
sidechainCompressor = new Superpowered::Compressor2(sampleRate);
}
void configureCompressor() {
sidechainCompressor->outputGainDb = 0;
sidechainCompressor->attackSec = 0.01;
sidechainCompressor->holdSec = 0;
sidechainCompressor->releaseSec = 0.2;
sidechainCompressor->ratio = 10;
sidechainCompressor->thresholdDb = -16;
sidechainCompressor->softKneeDb = 3;
sidechainCompressor->automaticGain = false;
sidechainCompressor->enabled = true;
}
void processAudio(float *interleavedInput, float *interleavedOutput, unsigned int numberOfFrames, unsigned int samplerate) {
// Ensure the samplerate is in sync on every audio processing callback
sidechainCompressor->samplerate = samplerate;
// Render the output buffers
bool outputChanged = sidechainCompressor->processWithSidechain(interleavedInput, interleavedInput, interleavedOutput, numberOfFrames);
float gainReductionDb = sidechainCompressor->getGainReductionDb();
...
}

Properties

attackSec

PROPERTY
Attack in seconds (not milliseconds!).
TypeMinMaxDefault
Number
0.00001100.05 (50 ms)

attackSec

PROPERTY
Attack in seconds (not milliseconds!).
TypeMinMaxDefault
float
0.00001100.05 (50 ms)

automaticGain

PROPERTY
If true, gain is set relative to compressor output at 0dB. Useful in digital environments.
TypeMinMaxDefault
Boolean
true

automaticGain

PROPERTY
If true, gain is set relative to compressor output at 0dB. Useful in digital environments.
TypeMinMaxDefault
bool
true

holdSec

PROPERTY
Hold segment (in seconds, not milliseconds) before release starts, useful to limit unwanted noise with fast rates.
TypeMinMaxDefault
Number
010.005 (5ms)

holdSec

PROPERTY
Hold segment (in seconds, not milliseconds) before release starts, useful to limit unwanted noise with fast rates.
TypeMinMaxDefault
float
010.005 (5ms)

outputGainDb

PROPERTY
Output gain in decibels.
TypeMinMaxDefault
Number
-24240

outputGainDb

PROPERTY
Output gain in decibels.
TypeMinMaxDefault
float
-24240

ratio

PROPERTY
Gain reduction ratio.
TypeMinMaxDefault
Number
110004

ratio

PROPERTY
Gain reduction ratio.
TypeMinMaxDefault
float
110004

releaseSec

PROPERTY
Release in seconds (not milliseconds!).
TypeMinMaxDefault
Number
0.00001100.05 (50ms)

releaseSec

PROPERTY
Release in seconds (not milliseconds!).
TypeMinMaxDefault
float
0.00001100.05 (50ms)

softKneeDb

PROPERTY
Width of soft knee in decibels.
TypeMinMaxDefault
Number
0126

softKneeDb

PROPERTY
Width of soft knee in decibels.
TypeMinMaxDefault
float
0126

thresholdDb

PROPERTY
Threshold in decibels.
TypeMinMaxDefault
Number
-600-6

thresholdDb

PROPERTY
Threshold in decibels.
TypeMinMaxDefault
float
-600-6
Inherited from FX Class

enabled

PROPERTY
Turns the effect on/off. The actual switch will happen on the next process() call for smooth, audio-artifact free operation.
TypeMinMaxDefault
Boolean
false

enabled

PROPERTY
Turns the effect on/off. The actual switch will happen on the next process() call for smooth, audio-artifact free operation.
TypeMinMaxDefault
bool
false

samplerate

PROPERTY
The sample rate of the audio context, you must update if it changes.
TypeMinMaxDefault
Number

samplerate

PROPERTY
The sample rate of the audio context, you must update if it changes.
TypeMinMaxDefault
unsigned int

Methods

  • constructor

    METHOD
    Creates an instance of the Compressor2 class.
    Parameters
    NameTypeDescription
    samplerateNumberThe initial sample rate in Hz
    Returns
    TypeDescription
    Superpowered.Compressor2The constructed instance.
  • constructor

    METHOD
    Creates an instance of the Compressor2 class.
    Parameters
    NameTypeDefaultDescription
    samplerateunsigned intThe initial sample rate in Hz
    Returns
    TypeDescription
    Superpowered::Compressor2The constructed instance.
  • getGainReductionDb

    METHOD
    Returns the maximum gain reduction in decibels since the last getGainReductionDb() call.
    Parameters
    None
    Returns
    TypeDescription
    NumberReduction in db.
  • getGainReductionDb

    METHOD
    Returns the maximum gain reduction in decibels since the last getGainReductionDb() call.
    Parameters
    None
    Returns
    TypeDescription
    floatReduction in db.
  • processWithSidechain

    METHOD
    Processes/renders the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation. It's never blocking for real-time usage. You can change all effect properties on any thread, concurrently with process().
    Parameters
    NameTypeDescription
    inputNumber32-bit interleaved stereo input.
    sidechainNumber32-bit interleaved stereo sidechain input.
    outputNumber32-bit interleaved stereo output.
    numberOfFramesNumberNumber of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
    Returns
    TypeDescription
    NumberIf returns with true, the contents of output are replaced with the audio output. If returns with false, it indicates silence. The contents of output are not changed in this case (not overwritten with zeros).
  • processWithSidechain

    METHOD
    Processes/renders the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation. It's never blocking for real-time usage. You can change all effect properties on any thread, concurrently with process().
    Parameters
    NameTypeDefaultDescription
    inputNumber32-bit interleaved stereo input.
    sidechainNumber32-bit interleaved stereo sidechain input.
    outputNumber32-bit interleaved stereo output.
    numberOfFramesNumberNumber of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
    Returns
    TypeDescription
    BooleanIf returns with true, the contents of output are replaced with the audio output. If returns with false, it indicates silence. The contents of output are not changed in this case (not overwritten with zeros).

Inherited from FX Class
  • destruct

    METHOD
    Destructor to free Linear Memory.
    Parameters
    None
    Returns
    None
  • process

    METHOD
    Processes/renders the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation. It's never blocking for real-time usage.
    Parameters
    NameTypeDescription
    inputNumber (pointer in Linear Memory)32-bit interleaved stereo input.
    outputNumber (pointer in Linear Memory)32-bit interleaved stereo output.
    numberOfFramesNumberNumber of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
    Returns
    TypeDescription
    BooleanIf process() returns with true, the contents of output are replaced with the audio output. If process() returns with false, it indicates silence. The contents of output are not changed in this case (not overwritten with zeros).
  • process

    METHOD
    Processes/renders the audio. Always call it in the audio processing callback, regardless if the effect is enabled or not for smooth, audio-artifact free operation. It's never blocking for real-time usage. You can change all effect properties on any thread, concurrently with process().
    Parameters
    NameTypeDefaultDescription
    inputfloat *32-bit interleaved stereo input.
    outputfloat *32-bit interleaved stereo output.
    numberOfFramesunsigned intNumber of frames to process. Recommendations for best performance: multiply of 4, minimum 64.
    Returns
    TypeDescription
    boolIf process() returns with true, the contents of output are replaced with the audio output. If process() returns with false, it indicates silence. The contents of output are not changed in this case (not overwritten with zeros).
v1.0.33