diff options
Diffstat (limited to 'Alc')
-rw-r--r-- | Alc/ALc.c | 21 | ||||
-rw-r--r-- | Alc/ALu.c | 95 | ||||
-rw-r--r-- | Alc/mastering.c | 255 |
3 files changed, 274 insertions, 97 deletions
@@ -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 { @@ -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]; + } +} |