diff options
-rw-r--r-- | Alc/ALc.c | 26 | ||||
-rw-r--r-- | Alc/ALu.c | 82 | ||||
-rw-r--r-- | OpenAL32/Include/alMain.h | 3 | ||||
-rw-r--r-- | OpenAL32/Include/alu.h | 18 |
4 files changed, 98 insertions, 31 deletions
@@ -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); @@ -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, |