aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/ALc.c21
-rw-r--r--Alc/ALu.c95
-rw-r--r--Alc/mastering.c255
-rw-r--r--CMakeLists.txt1
-rw-r--r--OpenAL32/Include/alMain.h32
-rw-r--r--OpenAL32/Include/alu.h22
6 files changed, 305 insertions, 121 deletions
diff --git a/Alc/ALc.c b/Alc/ALc.c
index 0e821b35..dcda29c3 100644
--- a/Alc/ALc.c
+++ b/Alc/ALc.c
@@ -1738,6 +1738,12 @@ static void alcSetError(ALCdevice *device, ALCenum errorCode)
}
+struct Compressor *CreateDeviceLimiter(const ALCdevice *device)
+{
+ return CompressorInit(0.0f, 0.0f, AL_FALSE, AL_TRUE, 0.0f, 0.0f, 0.5f, 2.0f,
+ 0.0f, -0.5f, 3.0f, device->Frequency);
+}
+
/* UpdateClockBase
*
* Updates the device's base clock time with however many samples have been
@@ -2224,8 +2230,11 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList)
*/
if(gainLimiter != ALC_FALSE)
{
- if(!device->Limiter)
- device->Limiter = alloc_limiter();
+ if(!device->Limiter || device->Frequency != GetCompressorSampleRate(device->Limiter))
+ {
+ al_free(device->Limiter);
+ device->Limiter = CreateDeviceLimiter(device);
+ }
}
else
{
@@ -3845,7 +3854,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName)
device->FOAOut.NumChannels = 0;
device->RealOut.Buffer = NULL;
device->RealOut.NumChannels = 0;
- device->Limiter = alloc_limiter();
+ device->Limiter = NULL;
device->AvgSpeakerDist = 0.0f;
ATOMIC_INIT(&device->ContextList, NULL);
@@ -4021,6 +4030,8 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName)
alstr_get_cstr(device->DeviceName), NULL, "dither", 1
);
+ device->Limiter = CreateDeviceLimiter(device);
+
if(DefaultEffect.type != AL_EFFECT_NULL)
{
device->DefaultSlot = (ALeffectslot*)device->_slot_mem;
@@ -4378,7 +4389,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN
device->FOAOut.NumChannels = 0;
device->RealOut.Buffer = NULL;
device->RealOut.NumChannels = 0;
- device->Limiter = alloc_limiter();
+ device->Limiter = NULL;
device->AvgSpeakerDist = 0.0f;
ATOMIC_INIT(&device->ContextList, NULL);
@@ -4441,6 +4452,8 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN
device->DitherEnabled = GetConfigValueBool(NULL, NULL, "dither", 1);
+ device->Limiter = CreateDeviceLimiter(device);
+
{
ALCdevice *head = ATOMIC_LOAD_SEQ(&DeviceList);
do {
diff --git a/Alc/ALu.c b/Alc/ALu.c
index fec31b34..103d7962 100644
--- a/Alc/ALu.c
+++ b/Alc/ALu.c
@@ -100,17 +100,6 @@ const aluMatrixf IdentityMatrixf = {{
}};
-struct OutputLimiter *alloc_limiter(void)
-{
- struct OutputLimiter *limiter = al_calloc(16, sizeof(*limiter));
- /* Limiter attack drops -80dB in 50ms. */
- limiter->AttackRate = 0.05f;
- /* Limiter release raises +80dB in 1s. */
- limiter->ReleaseRate = 1.0f;
- limiter->Gain = 1.0f;
- return limiter;
-}
-
void DeinitVoice(ALvoice *voice)
{
struct ALvoiceProps *props;
@@ -1538,78 +1527,6 @@ static void ApplyDistanceComp(ALfloatBUFFERSIZE *restrict Samples, DistanceComp
}
-static_assert(LIMITER_VALUE_MAX < (UINT_MAX/LIMITER_WINDOW_SIZE), "LIMITER_VALUE_MAX is too big");
-
-static void ApplyLimiter(struct OutputLimiter *Limiter,
- ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALsizei NumChans,
- const ALfloat AttackRate, const ALfloat ReleaseRate,
- ALfloat *restrict Values, const ALsizei SamplesToDo)
-{
- bool do_limit = false;
- ALsizei c, i;
-
- OutBuffer = ASSUME_ALIGNED(OutBuffer, 16);
- Values = ASSUME_ALIGNED(Values, 16);
-
- for(i = 0;i < SamplesToDo;i++)
- Values[i] = 0.0f;
-
- /* First, find the maximum amplitude (squared) for each sample position in each channel. */
- for(c = 0;c < NumChans;c++)
- {
- for(i = 0;i < SamplesToDo;i++)
- {
- ALfloat amp = OutBuffer[c][i];
- Values[i] = maxf(Values[i], amp*amp);
- }
- }
-
- /* Next, calculate the gains needed to limit the output. */
- {
- ALfloat lastgain = Limiter->Gain;
- ALsizei wpos = Limiter->Pos;
- ALuint sum = Limiter->SquaredSum;
- ALfloat gain, rms;
-
- for(i = 0;i < SamplesToDo;i++)
- {
- sum -= Limiter->Window[wpos];
- Limiter->Window[wpos] = fastf2u(minf(Values[i]*65536.0f, LIMITER_VALUE_MAX));
- sum += Limiter->Window[wpos];
-
- rms = sqrtf((ALfloat)sum / ((ALfloat)LIMITER_WINDOW_SIZE*65536.0f));
-
- /* Clamp the minimum RMS to 0dB. The uint used for the squared sum
- * inherently limits the maximum RMS to about 21dB, thus the gain
- * ranges from 0dB to -21dB.
- */
- gain = 1.0f / maxf(rms, 1.0f);
- if(lastgain >= gain)
- lastgain = maxf(lastgain*AttackRate, gain);
- else
- lastgain = minf(lastgain/ReleaseRate, gain);
- do_limit |= (lastgain < 1.0f);
- Values[i] = lastgain;
-
- wpos = (wpos+1)&LIMITER_WINDOW_MASK;
- }
-
- Limiter->Gain = lastgain;
- Limiter->Pos = wpos;
- Limiter->SquaredSum = sum;
- }
- if(do_limit)
- {
- /* Finally, apply the gains to each channel. */
- for(c = 0;c < NumChans;c++)
- {
- for(i = 0;i < SamplesToDo;i++)
- OutBuffer[c][i] *= Values[i];
- }
- }
-}
-
-
/* NOTE: Non-dithered conversions have unused extra parameters. */
static inline ALfloat aluF2F(ALfloat val, ...)
{ return val; }
@@ -1857,7 +1774,7 @@ void aluMixData(ALCdevice *device, ALvoid *buffer, ALsizei size)
{
ALfloat (*OutBuffer)[BUFFERSIZE] = device->RealOut.Buffer;
ALsizei OutChannels = device->RealOut.NumChannels;
- struct OutputLimiter *Limiter = device->Limiter;
+ struct Compressor *Limiter = device->Limiter;
ALfloat *DitherValues;
/* Use NFCtrlData for temp value storage. */
@@ -1865,15 +1782,7 @@ void aluMixData(ALCdevice *device, ALvoid *buffer, ALsizei size)
SamplesToDo, OutChannels);
if(Limiter)
- {
- const ALfloat AttackRate = powf(0.0001f, 1.0f/(device->Frequency*Limiter->AttackRate));
- const ALfloat ReleaseRate = powf(0.0001f, 1.0f/(device->Frequency*Limiter->ReleaseRate));
-
- /* Use NFCtrlData for temp value storage. */
- ApplyLimiter(Limiter, OutBuffer, OutChannels,
- AttackRate, ReleaseRate, device->NFCtrlData, SamplesToDo
- );
- }
+ ApplyCompression(Limiter, OutChannels, SamplesToDo, OutBuffer);
/* Dithering. Step 1, generate whitenoise (uniform distribution of
* random values between -1 and +1). Use NFCtrlData for random
diff --git a/Alc/mastering.c b/Alc/mastering.c
new file mode 100644
index 00000000..c9f5bebd
--- /dev/null
+++ b/Alc/mastering.c
@@ -0,0 +1,255 @@
+#include "config.h"
+
+#include <math.h>
+
+#include "alu.h"
+#include "almalloc.h"
+
+#define RMS_WINDOW_SIZE (1<<7)
+#define RMS_WINDOW_MASK (RMS_WINDOW_SIZE-1)
+#define RMS_VALUE_MAX (1<<24)
+
+#define LOOKAHEAD_SIZE (1<<13)
+#define LOOKAHEAD_MASK (LOOKAHEAD_SIZE-1)
+
+static_assert(RMS_VALUE_MAX < (UINT_MAX / RMS_WINDOW_SIZE), "RMS_VALUE_MAX is too big");
+
+typedef struct Compressor {
+ ALfloat PreGain;
+ ALfloat PostGain;
+ ALboolean SummedLink;
+ ALfloat AttackMin;
+ ALfloat AttackMax;
+ ALfloat ReleaseMin;
+ ALfloat ReleaseMax;
+ ALfloat Ratio;
+ ALfloat Threshold;
+ ALfloat Knee;
+ ALuint SampleRate;
+
+ ALuint RmsSum;
+ ALuint *RmsWindow;
+ ALsizei RmsIndex;
+ ALfloat Envelope[BUFFERSIZE];
+ ALfloat EnvLast;
+} Compressor;
+
+/* Multichannel compression is linked via one of two modes:
+ *
+ * Summed - Absolute sum of all channels.
+ * Maxed - Absolute maximum of any channel.
+ */
+static void SumChannels(Compressor *Comp, const ALsizei NumChans, const ALsizei SamplesToDo,
+ ALfloat (*restrict OutBuffer)[BUFFERSIZE])
+{
+ ALsizei c, i;
+
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] = 0.0f;
+
+ for(c = 0;c < NumChans;c++)
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] += OutBuffer[c][i];
+ }
+
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] = fabsf(Comp->Envelope[i]);
+}
+
+static void MaxChannels(Compressor *Comp, const ALsizei NumChans, const ALsizei SamplesToDo,
+ ALfloat (*restrict OutBuffer)[BUFFERSIZE])
+{
+ ALsizei c, i;
+
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] = 0.0f;
+
+ for(c = 0;c < NumChans;c++)
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] = maxf(Comp->Envelope[i], fabsf(OutBuffer[c][i]));
+ }
+}
+
+/* Envelope detection/sensing can be done via:
+ *
+ * RMS - Rectangular windowed root mean square of linking stage.
+ * Peak - Implicit output from linking stage.
+ */
+static void RmsDetection(Compressor *Comp, const ALsizei SamplesToDo)
+{
+ ALuint sum = Comp->RmsSum;
+ ALuint *window = Comp->RmsWindow;
+ ALsizei index = Comp->RmsIndex;
+ ALsizei i;
+
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ ALfloat sig = Comp->Envelope[i];
+
+ sum -= window[index];
+ window[index] = fastf2u(minf(sig * sig * 65536.0f, RMS_VALUE_MAX));
+ sum += window[index];
+ index = (index + 1) & RMS_WINDOW_MASK;
+
+ Comp->Envelope[i] = sqrtf(sum / 65536.0f / RMS_WINDOW_SIZE);
+ }
+
+ Comp->RmsSum = sum;
+ Comp->RmsIndex = index;
+}
+
+/* This isn't a very sophisticated envelope follower, but it gets the job
+ * done. First, it operates at logarithmic scales to keep transitions
+ * appropriate for human hearing. Second, it can apply adaptive (automated)
+ * attack/release adjustments based on the signal.
+ */
+static void FollowEnvelope(Compressor *Comp, const ALsizei SamplesToDo)
+{
+ ALfloat attackMin = Comp->AttackMin;
+ ALfloat attackMax = Comp->AttackMax;
+ ALfloat releaseMin = Comp->ReleaseMin;
+ ALfloat releaseMax = Comp->ReleaseMax;
+ ALfloat last = Comp->EnvLast;
+ ALsizei i;
+
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ ALfloat env = maxf(-6.0f, log10f(Comp->Envelope[i]));
+ ALfloat slope = minf(1.0f, fabsf(env - last) / 4.5f);
+
+ if(env > last)
+ last = minf(env, last + lerp(attackMin, attackMax, 1.0f - (slope * slope)));
+ else
+ last = maxf(env, last + lerp(releaseMin, releaseMax, 1.0f - (slope * slope)));
+
+ Comp->Envelope[i] = last;
+ }
+
+ Comp->EnvLast = last;
+}
+
+/* The envelope is converted to control gain with an optional soft knee. */
+static void EnvelopeGain(Compressor *Comp, const ALsizei SamplesToDo, const ALfloat Slope)
+{
+ const ALfloat threshold = Comp->Threshold;
+ const ALfloat knee = Comp->Knee;
+ ALsizei i;
+
+ if(!(knee > 0.0f))
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ ALfloat gain = Slope * (threshold - Comp->Envelope[i]);
+ Comp->Envelope[i] = powf(10.0f, minf(0.0f, gain));
+ }
+ }
+ else
+ {
+ const ALfloat lower = threshold - (0.5f * knee);
+ const ALfloat upper = threshold + (0.5f * knee);
+ const ALfloat m = 0.5f * Slope / knee;
+
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ ALfloat env = Comp->Envelope[i];
+ ALfloat gain;
+
+ if(env > lower && env < upper)
+ gain = m * (env - lower) * (lower - env);
+ else
+ gain = Slope * (threshold - env);
+
+ Comp->Envelope[i] = powf(10.0f, minf(0.0f, gain));
+ }
+ }
+}
+
+
+Compressor *CompressorInit(const ALfloat PreGainDb, const ALfloat PostGainDb,
+ const ALboolean SummedLink, const ALboolean RmsSensing,
+ const ALfloat AttackTimeMin, const ALfloat AttackTimeMax,
+ const ALfloat ReleaseTimeMin, const ALfloat ReleaseTimeMax,
+ const ALfloat Ratio, const ALfloat ThresholdDb,
+ const ALfloat KneeDb, const ALuint SampleRate)
+{
+ Compressor *Comp;
+ size_t size;
+ ALsizei i;
+
+ size = sizeof(*Comp);
+ if(RmsSensing)
+ size += sizeof(Comp->RmsWindow[0]) * RMS_WINDOW_SIZE;
+ Comp = al_calloc(16, size);
+
+ Comp->PreGain = powf(10.0f, PreGainDb / 20.0f);
+ Comp->PostGain = powf(10.0f, PostGainDb / 20.0f);
+ Comp->SummedLink = SummedLink;
+ Comp->AttackMin = 1.0f / maxf(0.000001f, AttackTimeMin * SampleRate * logf(10.0f));
+ Comp->AttackMax = 1.0f / maxf(0.000001f, AttackTimeMax * SampleRate * logf(10.0f));
+ Comp->ReleaseMin = -1.0f / maxf(0.000001f, ReleaseTimeMin * SampleRate * logf(10.0f));
+ Comp->ReleaseMax = -1.0f / maxf(0.000001f, ReleaseTimeMax * SampleRate * logf(10.0f));
+ Comp->Ratio = Ratio;
+ Comp->Threshold = ThresholdDb / 20.0f;
+ Comp->Knee = maxf(0.0f, KneeDb / 20.0f);
+ Comp->SampleRate = SampleRate;
+
+ Comp->RmsSum = 0;
+ if(RmsSensing)
+ Comp->RmsWindow = (ALuint*)(Comp+1);
+ else
+ Comp->RmsWindow = NULL;
+ Comp->RmsIndex = 0;
+
+ for(i = 0;i < BUFFERSIZE;i++)
+ Comp->Envelope[i] = 0.0f;
+ Comp->EnvLast = -6.0f;
+
+ return Comp;
+}
+
+ALuint GetCompressorSampleRate(const Compressor *Comp)
+{
+ return Comp->SampleRate;
+}
+
+void ApplyCompression(Compressor *Comp, const ALsizei NumChans, const ALsizei SamplesToDo,
+ ALfloat (*restrict OutBuffer)[BUFFERSIZE])
+{
+ ALsizei c, i;
+
+ if(Comp->PreGain != 1.0f)
+ {
+ for(c = 0;c < NumChans;c++)
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ OutBuffer[c][i] *= Comp->PreGain;
+ }
+ }
+
+ if(Comp->SummedLink)
+ SumChannels(Comp, NumChans, SamplesToDo, OutBuffer);
+ else
+ MaxChannels(Comp, NumChans, SamplesToDo, OutBuffer);
+
+ if(Comp->RmsWindow)
+ RmsDetection(Comp, SamplesToDo);
+ FollowEnvelope(Comp, SamplesToDo);
+
+ if(Comp->Ratio > 0.0f)
+ EnvelopeGain(Comp, SamplesToDo, 1.0f - (1.0f / Comp->Ratio));
+ else
+ EnvelopeGain(Comp, SamplesToDo, 1.0f);
+
+ if(Comp->PostGain != 1.0f)
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ Comp->Envelope[i] *= Comp->PostGain;
+ }
+ for(c = 0;c < NumChans;c++)
+ {
+ for(i = 0;i < SamplesToDo;i++)
+ OutBuffer[c][i] *= Comp->Envelope[i];
+ }
+}
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 66402624..15311bc2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -694,6 +694,7 @@ SET(ALC_OBJS Alc/ALc.c
Alc/alcRing.c
Alc/bs2b.c
Alc/converter.c
+ Alc/mastering.c
Alc/effects/chorus.c
Alc/effects/compressor.c
Alc/effects/dedicated.c
diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h
index 2ce98745..6e17651f 100644
--- a/OpenAL32/Include/alMain.h
+++ b/OpenAL32/Include/alMain.h
@@ -380,7 +380,7 @@ extern "C" {
struct Hrtf;
struct HrtfEntry;
-struct OutputLimiter;
+struct Compressor;
#define DEFAULT_OUTPUT_RATE (44100)
@@ -783,7 +783,7 @@ struct ALCdevice_struct
ALsizei NumChannels;
} RealOut;
- struct OutputLimiter *Limiter;
+ struct Compressor *Limiter;
/* The average speaker distance as determined by the ambdec configuration
* (or alternatively, by the NFC-HOA reference delay). Only used for NFC.
@@ -1047,6 +1047,34 @@ vector_al_string SearchDataFiles(const char *match, const char *subdir);
typedef ALfloat ALfloatBUFFERSIZE[BUFFERSIZE];
typedef ALfloat ALfloat2[2];
+
+/* The compressor requires the following information for proper
+ * initialization:
+ *
+ * PreGainDb - Gain applied before detection (in dB).
+ * PostGainDb - Gain applied after compression (in dB).
+ * SummedLink - Whether to use summed (true) or maxed (false) linking.
+ * RmsSensing - Whether to use RMS (true) or Peak (false) sensing.
+ * AttackTimeMin - Minimum attack time (in seconds).
+ * AttackTimeMax - Maximum attack time. Automates when min != max.
+ * ReleaseTimeMin - Minimum release time (in seconds).
+ * ReleaseTimeMax - Maximum release time. Automates when min != max.
+ * Ratio - Compression ratio (x:1). Set to 0 for true limiter.
+ * ThresholdDb - Triggering threshold (in dB).
+ * KneeDb - Knee width (below threshold; in dB).
+ * SampleRate - Sample rate to process.
+ */
+struct Compressor *CompressorInit(const ALfloat PreGainDb, const ALfloat PostGainDb,
+ const ALboolean SummedLink, const ALboolean RmsSensing, const ALfloat AttackTimeMin,
+ const ALfloat AttackTimeMax, const ALfloat ReleaseTimeMin, const ALfloat ReleaseTimeMax,
+ const ALfloat Ratio, const ALfloat ThresholdDb, const ALfloat KneeDb,
+ const ALuint SampleRate);
+
+ALuint GetCompressorSampleRate(const struct Compressor *Comp);
+
+void ApplyCompression(struct Compressor *Comp, const ALsizei NumChans, const ALsizei SamplesToDo,
+ ALfloat (*restrict OutBuffer)[BUFFERSIZE]);
+
#ifdef __cplusplus
}
#endif
diff --git a/OpenAL32/Include/alu.h b/OpenAL32/Include/alu.h
index 8a56ddb2..0b3799c0 100644
--- a/OpenAL32/Include/alu.h
+++ b/OpenAL32/Include/alu.h
@@ -300,28 +300,6 @@ typedef struct ALvoice {
void DeinitVoice(ALvoice *voice);
-#define LIMITER_WINDOW_SIZE (1<<7) /* 128 */
-#define LIMITER_WINDOW_MASK (LIMITER_WINDOW_SIZE-1)
-#define LIMITER_VALUE_MAX (1<<24) /* 16777216 */
-struct OutputLimiter {
- /* RMS detection window, sum of values in the window, and the next write
- * pos. Values are 16.16 fixed-point.
- */
- ALuint Window[LIMITER_WINDOW_SIZE];
- ALuint SquaredSum;
- ALsizei Pos;
-
- /* In milliseconds. */
- ALfloat AttackRate;
- ALfloat ReleaseRate;
-
- /* The gain last used for limiting. */
- ALfloat Gain;
-};
-
-struct OutputLimiter *alloc_limiter(void);
-
-
typedef void (*MixerFunc)(const ALfloat *data, ALsizei OutChans,
ALfloat (*restrict OutBuffer)[BUFFERSIZE], ALfloat *CurrentGains,
const ALfloat *TargetGains, ALsizei Counter, ALsizei OutPos,