aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Alc/ALc.c26
-rw-r--r--Alc/ALu.c82
-rw-r--r--OpenAL32/Include/alMain.h3
-rw-r--r--OpenAL32/Include/alu.h18
4 files changed, 98 insertions, 31 deletions
diff --git a/Alc/ALc.c b/Alc/ALc.c
index f1319b16..fe8a0266 100644
--- a/Alc/ALc.c
+++ b/Alc/ALc.c
@@ -1762,7 +1762,7 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList)
{
enum HrtfRequestMode hrtf_userreq = Hrtf_Default;
enum HrtfRequestMode hrtf_appreq = Hrtf_Default;
- ALCenum gainLimiter = (device->LimiterGain > 0.0f);
+ ALCenum gainLimiter = !!device->Limiter;
const ALsizei old_sends = device->NumAuxSends;
ALsizei new_sends = device->NumAuxSends;
enum DevFmtChannels oldChans;
@@ -2216,7 +2216,16 @@ static ALCenum UpdateDeviceParams(ALCdevice *device, const ALCint *attrList)
if(ConfigValueBool(alstr_get_cstr(device->DeviceName), NULL, "output-limiter", &val))
gainLimiter = val;
- device->LimiterGain = gainLimiter ? 1.0f : 0.0f;
+ if(gainLimiter)
+ {
+ if(!device->Limiter)
+ device->Limiter = alloc_limiter();
+ }
+ else if(device->Limiter)
+ {
+ al_free(device->Limiter);
+ device->Limiter = NULL;
+ }
/* Need to delay returning failure until replacement Send arrays have been
* allocated with the appropriate size.
@@ -2418,6 +2427,9 @@ static ALCvoid FreeDevice(ALCdevice *device)
ambiup_free(device->AmbiUp);
device->AmbiUp = NULL;
+ al_free(device->Limiter);
+ device->Limiter = NULL;
+
al_free(device->ChannelDelay[0].Buffer);
for(i = 0;i < MAX_OUTPUT_CHANNELS;i++)
{
@@ -3167,7 +3179,7 @@ static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALC
values[i++] = device->HrtfStatus;
values[i++] = ALC_OUTPUT_LIMITER_SOFT;
- values[i++] = (device->LimiterGain > 0.0f) ? ALC_TRUE : ALC_FALSE;
+ values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE;
almtx_unlock(&device->BackendLock);
values[i++] = 0;
@@ -3275,7 +3287,7 @@ static ALCsizei GetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALC
return 1;
case ALC_OUTPUT_LIMITER_SOFT:
- values[0] = (device->LimiterGain > 0.0f) ? ALC_TRUE : ALC_FALSE;
+ values[0] = device->Limiter ? ALC_TRUE : ALC_FALSE;
return 1;
default:
@@ -3383,7 +3395,7 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname,
values[i++] = device->HrtfStatus;
values[i++] = ALC_OUTPUT_LIMITER_SOFT;
- values[i++] = (device->LimiterGain > 0.0f) ? ALC_TRUE : ALC_FALSE;
+ values[i++] = device->Limiter ? ALC_TRUE : ALC_FALSE;
clock = V0(device->Backend,getClockLatency)();
values[i++] = ALC_DEVICE_CLOCK_SOFT;
@@ -3826,7 +3838,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName)
device->FOAOut.NumChannels = 0;
device->RealOut.Buffer = NULL;
device->RealOut.NumChannels = 0;
- device->LimiterGain = 1.0f;
+ device->Limiter = alloc_limiter();
device->AvgSpeakerDist = 0.0f;
ATOMIC_INIT(&device->ContextList, NULL);
@@ -4355,7 +4367,7 @@ ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceN
device->FOAOut.NumChannels = 0;
device->RealOut.Buffer = NULL;
device->RealOut.NumChannels = 0;
- device->LimiterGain = 1.0f;
+ device->Limiter = alloc_limiter();
device->AvgSpeakerDist = 0.0f;
ATOMIC_INIT(&device->ContextList, NULL);
diff --git a/Alc/ALu.c b/Alc/ALu.c
index 2817f982..f9defedd 100644
--- a/Alc/ALu.c
+++ b/Alc/ALu.c
@@ -100,6 +100,17 @@ 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;
@@ -1400,47 +1411,74 @@ static void UpdateContextSources(ALCcontext *ctx, const struct ALeffectslotArray
}
-static ALfloat ApplyLimiter(ALfloat (*restrict OutBuffer)[BUFFERSIZE], const ALsizei NumChans,
- const ALfloat AttackRate, const ALfloat ReleaseRate,
- const ALfloat InGain, ALfloat (*restrict Gains),
- const ALsizei SamplesToDo)
+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);
- Gains = ASSUME_ALIGNED(Gains, 16);
+ Values = ASSUME_ALIGNED(Values, 16);
for(i = 0;i < SamplesToDo;i++)
- Gains[i] = 1.0f;
+ Values[i] = 0.0f;
+ /* First, find the maximum amplitude (squared) for each sample position in each channel. */
for(c = 0;c < NumChans;c++)
{
- ALfloat lastgain = InGain;
for(i = 0;i < SamplesToDo;i++)
{
+ ALfloat amp_sqr = OutBuffer[c][i] * OutBuffer[c][i];
+ Values[i] = maxf(Values[i], amp_sqr);
+ }
+ }
+
+ /* Next, calculate the gains needed to limit the output. */
+ {
+ ALfloat lastgain = Limiter->Gain;
+ ALsizei wpos = Limiter->Pos;
+ ALfloat sum = 0.0f;
+ ALfloat gain;
+
+ /* Unfortunately we can't store the running sum due to fp inaccuracies
+ * causing it to drift over time. So we need to recalculate it every
+ * once in a while (i.e. every invocation).
+ */
+ for(i = 0;i < LIMITER_WINDOW_SIZE;i++)
+ sum += Limiter->Window[i];
+
+ for(i = 0;i < SamplesToDo;i++)
+ {
+ sum -= Limiter->Window[wpos];
+ Limiter->Window[wpos] = Values[i];
+ sum += Values[i];
+
/* Clamp limiter range to 0dB...-80dB. */
- ALfloat gain = 1.0f / clampf(fabsf(OutBuffer[c][i]), 1.0f, 1000.0f);
+ gain = 1.0f / clampf(sqrtf(sum / (ALfloat)LIMITER_WINDOW_SIZE), 1.0f, 1000.0f);
if(lastgain >= gain)
lastgain = maxf(lastgain*AttackRate, gain);
else
lastgain = minf(lastgain/ReleaseRate, gain);
do_limit |= (lastgain < 1.0f);
+ Values[i] = lastgain;
- lastgain = minf(lastgain, Gains[i]);
- Gains[i] = lastgain;
+ wpos = (wpos+1)&LIMITER_WINDOW_MASK;
}
+
+ Limiter->Gain = lastgain;
+ Limiter->Pos = wpos;
}
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] *= Gains[i];
+ OutBuffer[c][i] *= Values[i];
}
}
-
- return Gains[SamplesToDo-1];
}
static inline ALfloat aluF2F(ALfloat val)
@@ -1689,19 +1727,17 @@ void aluMixData(ALCdevice *device, ALvoid *buffer, ALsizei size)
{
ALfloat (*OutBuffer)[BUFFERSIZE] = device->RealOut.Buffer;
ALsizei OutChannels = device->RealOut.NumChannels;
+ struct OutputLimiter *Limiter = device->Limiter;
DistanceComp *DistComp;
- if(device->LimiterGain > 0.0f)
+ if(Limiter)
{
- /* Limiter attack drops -80dB in 50ms. */
- const ALfloat AttackRate = powf(0.0001f, 1.0f/(device->Frequency*0.05f));
- /* Limiter release raises +80dB in 1s. */
- const ALfloat ReleaseRate = powf(0.0001f, 1.0f/(device->Frequency*1.0f));
-
- /* Use NFCtrlData for temp gain storage. */
- device->LimiterGain = ApplyLimiter(OutBuffer, OutChannels,
- AttackRate, ReleaseRate, device->LimiterGain, device->NFCtrlData,
- SamplesToDo
+ 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
);
}
diff --git a/OpenAL32/Include/alMain.h b/OpenAL32/Include/alMain.h
index 1b1b1909..55f52bef 100644
--- a/OpenAL32/Include/alMain.h
+++ b/OpenAL32/Include/alMain.h
@@ -391,6 +391,7 @@ extern "C" {
struct Hrtf;
struct HrtfEntry;
+struct OutputLimiter;
#define DEFAULT_OUTPUT_RATE (44100)
@@ -793,7 +794,7 @@ struct ALCdevice_struct
ALsizei NumChannels;
} RealOut;
- ALfloat LimiterGain;
+ struct OutputLimiter *Limiter;
/* The average speaker distance as determined by the ambdec configuration
* (or alternatively, by the NFC-HOA reference delay). Only used for NFC.
diff --git a/OpenAL32/Include/alu.h b/OpenAL32/Include/alu.h
index 8e2fe1e9..6fdbac6f 100644
--- a/OpenAL32/Include/alu.h
+++ b/OpenAL32/Include/alu.h
@@ -297,6 +297,24 @@ typedef struct ALvoice {
void DeinitVoice(ALvoice *voice);
+#define LIMITER_WINDOW_SIZE (1<<7) /* 128 */
+#define LIMITER_WINDOW_MASK (LIMITER_WINDOW_SIZE-1)
+struct OutputLimiter {
+ /* RMS detection window and the next write pos. */
+ alignas(16) ALfloat Window[LIMITER_WINDOW_SIZE];
+ 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,