Calculate the output limiter gain using the RMS
This commit is contained in:
parent
64f0630fef
commit
074e4496ba
26
Alc/ALc.c
26
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);
|
||||
|
80
Alc/ALu.c
80
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));
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user