Calculate the output limiter gain using the RMS

This commit is contained in:
Chris Robinson 2017-05-05 07:38:26 -07:00
parent 64f0630fef
commit 074e4496ba
4 changed files with 97 additions and 30 deletions

View File

@ -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);

View File

@ -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));
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 gain storage. */
device->LimiterGain = ApplyLimiter(OutBuffer, OutChannels,
AttackRate, ReleaseRate, device->LimiterGain, device->NFCtrlData,
SamplesToDo
/* Use NFCtrlData for temp value storage. */
ApplyLimiter(Limiter, OutBuffer, OutChannels,
AttackRate, ReleaseRate, device->NFCtrlData, SamplesToDo
);
}

View File

@ -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.

View File

@ -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,