diff options
Diffstat (limited to 'Alc/effects/reverb.c')
-rw-r--r-- | Alc/effects/reverb.c | 2433 |
1 files changed, 1360 insertions, 1073 deletions
diff --git a/Alc/effects/reverb.c b/Alc/effects/reverb.c index e1013309..8ebc089e 100644 --- a/Alc/effects/reverb.c +++ b/Alc/effects/reverb.c @@ -1,6 +1,6 @@ /** - * Reverb for the OpenAL cross platform audio library - * Copyright (C) 2008-2009 by Christopher Fitzgerald. + * Ambisonic reverb engine for the OpenAL cross platform audio library + * Copyright (C) 2008-2017 by Chris Robinson and Christopher Fitzgerald. * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either @@ -27,634 +27,464 @@ #include "alMain.h" #include "alu.h" #include "alAuxEffectSlot.h" -#include "alEffect.h" -#include "alFilter.h" +#include "alListener.h" #include "alError.h" +#include "filters/defs.h" +/* This is a user config option for modifying the overall output of the reverb + * effect. + */ +ALfloat ReverbBoost = 1.0f; /* This is the maximum number of samples processed for each inner loop * iteration. */ #define MAX_UPDATE_SAMPLES 256 -typedef struct DelayLine -{ - // The delay lines use sample lengths that are powers of 2 to allow the - // use of bit-masking instead of a modulus for wrapping. - ALuint Mask; - ALfloat *Line; -} DelayLine; +/* The number of samples used for cross-faded delay lines. This can be used + * to balance the compensation for abrupt line changes and attenuation due to + * minimally lengthed recursive lines. Try to keep this below the device + * update size. + */ +#define FADE_SAMPLES 128 -typedef struct ALreverbState { - DERIVE_FROM_TYPE(ALeffectState); +/* The number of spatialized lines or channels to process. Four channels allows + * for a 3D A-Format response. NOTE: This can't be changed without taking care + * of the conversion matrices, and a few places where the length arrays are + * assumed to have 4 elements. + */ +#define NUM_LINES 4 - ALboolean IsEax; - // All delay lines are allocated as a single buffer to reduce memory - // fragmentation and management code. - ALfloat *SampleBuffer; - ALuint TotalSamples; +/* The B-Format to A-Format conversion matrix. The arrangement of rows is + * deliberately chosen to align the resulting lines to their spatial opposites + * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below + * back left). It's not quite opposite, since the A-Format results in a + * tetrahedron, but it's close enough. Should the model be extended to 8-lines + * in the future, true opposites can be used. + */ +static const aluMatrixf B2A = {{ + { 0.288675134595f, 0.288675134595f, 0.288675134595f, 0.288675134595f }, + { 0.288675134595f, -0.288675134595f, -0.288675134595f, 0.288675134595f }, + { 0.288675134595f, 0.288675134595f, -0.288675134595f, -0.288675134595f }, + { 0.288675134595f, -0.288675134595f, 0.288675134595f, -0.288675134595f } +}}; + +/* Converts A-Format to B-Format. */ +static const aluMatrixf A2B = {{ + { 0.866025403785f, 0.866025403785f, 0.866025403785f, 0.866025403785f }, + { 0.866025403785f, -0.866025403785f, 0.866025403785f, -0.866025403785f }, + { 0.866025403785f, -0.866025403785f, -0.866025403785f, 0.866025403785f }, + { 0.866025403785f, 0.866025403785f, -0.866025403785f, -0.866025403785f } +}}; + +static const ALfloat FadeStep = 1.0f / FADE_SAMPLES; + +/* The all-pass and delay lines have a variable length dependent on the + * effect's density parameter, which helps alter the perceived environment + * size. The size-to-density conversion is a cubed scale: + * + * density = min(1.0, pow(size, 3.0) / DENSITY_SCALE); + * + * The line lengths scale linearly with room size, so the inverse density + * conversion is needed, taking the cube root of the re-scaled density to + * calculate the line length multiplier: + * + * length_mult = max(5.0, cbrtf(density*DENSITY_SCALE)); + * + * The density scale below will result in a max line multiplier of 50, for an + * effective size range of 5m to 50m. + */ +static const ALfloat DENSITY_SCALE = 125000.0f; - // Master effect filters - ALfilterState LpFilter; - ALfilterState HpFilter; // EAX only +/* All delay line lengths are specified in seconds. + * + * To approximate early reflections, we break them up into primary (those + * arriving from the same direction as the source) and secondary (those + * arriving from the opposite direction). + * + * The early taps decorrelate the 4-channel signal to approximate an average + * room response for the primary reflections after the initial early delay. + * + * Given an average room dimension (d_a) and the speed of sound (c) we can + * calculate the average reflection delay (r_a) regardless of listener and + * source positions as: + * + * r_a = d_a / c + * c = 343.3 + * + * This can extended to finding the average difference (r_d) between the + * maximum (r_1) and minimum (r_0) reflection delays: + * + * r_0 = 2 / 3 r_a + * = r_a - r_d / 2 + * = r_d + * r_1 = 4 / 3 r_a + * = r_a + r_d / 2 + * = 2 r_d + * r_d = 2 / 3 r_a + * = r_1 - r_0 + * + * As can be determined by integrating the 1D model with a source (s) and + * listener (l) positioned across the dimension of length (d_a): + * + * r_d = int_(l=0)^d_a (int_(s=0)^d_a |2 d_a - 2 (l + s)| ds) dl / c + * + * The initial taps (T_(i=0)^N) are then specified by taking a power series + * that ranges between r_0 and half of r_1 less r_0: + * + * R_i = 2^(i / (2 N - 1)) r_d + * = r_0 + (2^(i / (2 N - 1)) - 1) r_d + * = r_0 + T_i + * T_i = R_i - r_0 + * = (2^(i / (2 N - 1)) - 1) r_d + * + * Assuming an average of 1m, we get the following taps: + */ +static const ALfloat EARLY_TAP_LENGTHS[NUM_LINES] = +{ + 0.0000000e+0f, 2.0213520e-4f, 4.2531060e-4f, 6.7171600e-4f +}; - struct { - // Modulator delay line. - DelayLine Delay; - - // The vibrato time is tracked with an index over a modulus-wrapped - // range (in samples). - ALuint Index; - ALuint Range; - - // The depth of frequency change (also in samples) and its filter. - ALfloat Depth; - ALfloat Coeff; - ALfloat Filter; - } Mod; - - // Initial effect delay. - DelayLine Delay; - // The tap points for the initial delay. First tap goes to early - // reflections, the last to late reverb. - ALuint DelayTap[2]; +/* The early all-pass filter lengths are based on the early tap lengths: + * + * A_i = R_i / a + * + * Where a is the approximate maximum all-pass cycle limit (20). + */ +static const ALfloat EARLY_ALLPASS_LENGTHS[NUM_LINES] = +{ + 9.7096800e-5f, 1.0720356e-4f, 1.1836234e-4f, 1.3068260e-4f +}; - struct { - // Output gain for early reflections. - ALfloat Gain; +/* The early delay lines are used to transform the primary reflections into + * the secondary reflections. The A-format is arranged in such a way that + * the channels/lines are spatially opposite: + * + * C_i is opposite C_(N-i-1) + * + * The delays of the two opposing reflections (R_i and O_i) from a source + * anywhere along a particular dimension always sum to twice its full delay: + * + * 2 r_a = R_i + O_i + * + * With that in mind we can determine the delay between the two reflections + * and thus specify our early line lengths (L_(i=0)^N) using: + * + * O_i = 2 r_a - R_(N-i-1) + * L_i = O_i - R_(N-i-1) + * = 2 (r_a - R_(N-i-1)) + * = 2 (r_a - T_(N-i-1) - r_0) + * = 2 r_a (1 - (2 / 3) 2^((N - i - 1) / (2 N - 1))) + * + * Using an average dimension of 1m, we get: + */ +static const ALfloat EARLY_LINE_LENGTHS[NUM_LINES] = +{ + 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f +}; - // Early reflections are done with 4 delay lines. - ALfloat Coeff[4]; - DelayLine Delay[4]; - ALuint Offset[4]; +/* The late all-pass filter lengths are based on the late line lengths: + * + * A_i = (5 / 3) L_i / r_1 + */ +static const ALfloat LATE_ALLPASS_LENGTHS[NUM_LINES] = +{ + 1.6182800e-4f, 2.0389060e-4f, 2.8159360e-4f, 3.2365600e-4f +}; - // The gain for each output channel based on 3D panning (only for the - // EAX path). - ALfloat PanGain[4][MAX_OUTPUT_CHANNELS]; - } Early; +/* The late lines are used to approximate the decaying cycle of recursive + * late reflections. + * + * Splitting the lines in half, we start with the shortest reflection paths + * (L_(i=0)^(N/2)): + * + * L_i = 2^(i / (N - 1)) r_d + * + * Then for the opposite (longest) reflection paths (L_(i=N/2)^N): + * + * L_i = 2 r_a - L_(i-N/2) + * = 2 r_a - 2^((i - N / 2) / (N - 1)) r_d + * + * For our 1m average room, we get: + */ +static const ALfloat LATE_LINE_LENGTHS[NUM_LINES] = +{ + 1.9419362e-3f, 2.4466860e-3f, 3.3791220e-3f, 3.8838720e-3f +}; - // Decorrelator delay line. - DelayLine Decorrelator; - // There are actually 4 decorrelator taps, but the first occurs at the - // initial sample. - ALuint DecoTap[3]; - struct { - // Output gain for late reverb. - ALfloat Gain; +typedef struct DelayLineI { + /* The delay lines use interleaved samples, with the lengths being powers + * of 2 to allow the use of bit-masking instead of a modulus for wrapping. + */ + ALsizei Mask; + ALfloat (*Line)[NUM_LINES]; +} DelayLineI; + +typedef struct VecAllpass { + DelayLineI Delay; + ALfloat Coeff; + ALsizei Offset[NUM_LINES][2]; +} VecAllpass; + +typedef struct T60Filter { + /* Two filters are used to adjust the signal. One to control the low + * frequencies, and one to control the high frequencies. + */ + ALfloat MidGain[2]; + BiquadFilter HFFilter, LFFilter; +} T60Filter; - // Attenuation to compensate for the modal density and decay rate of - // the late lines. - ALfloat DensityGain; +typedef struct EarlyReflections { + /* A Gerzon vector all-pass filter is used to simulate initial diffusion. + * The spread from this filter also helps smooth out the reverb tail. + */ + VecAllpass VecAp; - // The feed-back and feed-forward all-pass coefficient. - ALfloat ApFeedCoeff; + /* An echo line is used to complete the second half of the early + * reflections. + */ + DelayLineI Delay; + ALsizei Offset[NUM_LINES][2]; + ALfloat Coeff[NUM_LINES][2]; + + /* The gain for each output channel based on 3D panning. */ + ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]; + ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]; +} EarlyReflections; + +typedef struct LateReverb { + /* A recursive delay line is used fill in the reverb tail. */ + DelayLineI Delay; + ALsizei Offset[NUM_LINES][2]; + + /* Attenuation to compensate for the modal density and decay rate of the + * late lines. + */ + ALfloat DensityGain[2]; - // Mixing matrix coefficient. - ALfloat MixCoeff; + /* T60 decay filters are used to simulate absorption. */ + T60Filter T60[NUM_LINES]; - // Late reverb has 4 parallel all-pass filters. - ALfloat ApCoeff[4]; - DelayLine ApDelay[4]; - ALuint ApOffset[4]; + /* A Gerzon vector all-pass filter is used to simulate diffusion. */ + VecAllpass VecAp; - // In addition to 4 cyclical delay lines. - ALfloat Coeff[4]; - DelayLine Delay[4]; - ALuint Offset[4]; + /* The gain for each output channel based on 3D panning. */ + ALfloat CurrentGain[NUM_LINES][MAX_OUTPUT_CHANNELS]; + ALfloat PanGain[NUM_LINES][MAX_OUTPUT_CHANNELS]; +} LateReverb; - // The cyclical delay lines are 1-pole low-pass filtered. - ALfloat LpCoeff[4]; - ALfloat LpSample[4]; +typedef struct ReverbState { + DERIVE_FROM_TYPE(ALeffectState); - // The gain for each output channel based on 3D panning (only for the - // EAX path). - ALfloat PanGain[4][MAX_OUTPUT_CHANNELS]; - } Late; + /* All delay lines are allocated as a single buffer to reduce memory + * fragmentation and management code. + */ + ALfloat *SampleBuffer; + ALuint TotalSamples; struct { - // Attenuation to compensate for the modal density and decay rate of - // the echo line. - ALfloat DensityGain; - - // Echo delay and all-pass lines. - DelayLine Delay; - DelayLine ApDelay; - - ALfloat Coeff; - ALfloat ApFeedCoeff; - ALfloat ApCoeff; - - ALuint Offset; - ALuint ApOffset; - - // The echo line is 1-pole low-pass filtered. - ALfloat LpCoeff; - ALfloat LpSample; - - // Echo mixing coefficient. - ALfloat MixCoeff; - } Echo; - - // The current read offset for all delay lines. - ALuint Offset; - - // The gain for each output channel (non-EAX path only; aliased from - // Late.PanGain) - ALfloat (*Gain)[MAX_OUTPUT_CHANNELS]; + /* Calculated parameters which indicate if cross-fading is needed after + * an update. + */ + ALfloat Density, Diffusion; + ALfloat DecayTime, HFDecayTime, LFDecayTime; + ALfloat HFReference, LFReference; + } Params; - /* Temporary storage used when processing. */ - ALfloat ReverbSamples[MAX_UPDATE_SAMPLES][4]; - ALfloat EarlySamples[MAX_UPDATE_SAMPLES][4]; -} ALreverbState; + /* Master effect filters */ + struct { + BiquadFilter Lp; + BiquadFilter Hp; + } Filter[NUM_LINES]; -/* This is a user config option for modifying the overall output of the reverb - * effect. - */ -ALfloat ReverbBoost = 1.0f; + /* Core delay line (early reflections and late reverb tap from this). */ + DelayLineI Delay; -/* Specifies whether to use a standard reverb effect in place of EAX reverb */ -ALboolean EmulateEAXReverb = AL_FALSE; + /* Tap points for early reflection delay. */ + ALsizei EarlyDelayTap[NUM_LINES][2]; + ALfloat EarlyDelayCoeff[NUM_LINES][2]; -/* This coefficient is used to define the maximum frequency range controlled - * by the modulation depth. The current value of 0.1 will allow it to swing - * from 0.9x to 1.1x. This value must be below 1. At 1 it will cause the - * sampler to stall on the downswing, and above 1 it will cause it to sample - * backwards. - */ -static const ALfloat MODULATION_DEPTH_COEFF = 0.1f; + /* Tap points for late reverb feed and delay. */ + ALsizei LateFeedTap; + ALsizei LateDelayTap[NUM_LINES][2]; -/* A filter is used to avoid the terrible distortion caused by changing - * modulation time and/or depth. To be consistent across different sample - * rates, the coefficient must be raised to a constant divided by the sample - * rate: coeff^(constant / rate). - */ -static const ALfloat MODULATION_FILTER_COEFF = 0.048f; -static const ALfloat MODULATION_FILTER_CONST = 100000.0f; + /* Coefficients for the all-pass and line scattering matrices. */ + ALfloat MixX; + ALfloat MixY; -// When diffusion is above 0, an all-pass filter is used to take the edge off -// the echo effect. It uses the following line length (in seconds). -static const ALfloat ECHO_ALLPASS_LENGTH = 0.0133f; + EarlyReflections Early; -// Input into the late reverb is decorrelated between four channels. Their -// timings are dependent on a fraction and multiplier. See the -// UpdateDecorrelator() routine for the calculations involved. -static const ALfloat DECO_FRACTION = 0.15f; -static const ALfloat DECO_MULTIPLIER = 2.0f; + LateReverb Late; -// All delay line lengths are specified in seconds. + /* Indicates the cross-fade point for delay line reads [0,FADE_SAMPLES]. */ + ALsizei FadeCount; -// The lengths of the early delay lines. -static const ALfloat EARLY_LINE_LENGTH[4] = -{ - 0.0015f, 0.0045f, 0.0135f, 0.0405f -}; + /* Maximum number of samples to process at once. */ + ALsizei MaxUpdate[2]; -// The lengths of the late all-pass delay lines. -static const ALfloat ALLPASS_LINE_LENGTH[4] = -{ - 0.0151f, 0.0167f, 0.0183f, 0.0200f, -}; + /* The current write offset for all delay lines. */ + ALsizei Offset; -// The lengths of the late cyclical delay lines. -static const ALfloat LATE_LINE_LENGTH[4] = -{ - 0.0211f, 0.0311f, 0.0461f, 0.0680f -}; + /* Temporary storage used when processing. */ + alignas(16) ALfloat TempSamples[NUM_LINES][MAX_UPDATE_SAMPLES]; + alignas(16) ALfloat MixSamples[NUM_LINES][MAX_UPDATE_SAMPLES]; +} ReverbState; -// The late cyclical delay lines have a variable length dependent on the -// effect's density parameter (inverted for some reason) and this multiplier. -static const ALfloat LATE_LINE_MULTIPLIER = 4.0f; +static ALvoid ReverbState_Destruct(ReverbState *State); +static ALboolean ReverbState_deviceUpdate(ReverbState *State, ALCdevice *Device); +static ALvoid ReverbState_update(ReverbState *State, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props); +static ALvoid ReverbState_process(ReverbState *State, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels); +DECLARE_DEFAULT_ALLOCATORS(ReverbState) +DEFINE_ALEFFECTSTATE_VTABLE(ReverbState); -// Basic delay line input/output routines. -static inline ALfloat DelayLineOut(DelayLine *Delay, ALuint offset) +static void ReverbState_Construct(ReverbState *state) { - return Delay->Line[offset&Delay->Mask]; -} + ALsizei i, j; -static inline ALvoid DelayLineIn(DelayLine *Delay, ALuint offset, ALfloat in) -{ - Delay->Line[offset&Delay->Mask] = in; -} + ALeffectState_Construct(STATIC_CAST(ALeffectState, state)); + SET_VTABLE2(ReverbState, ALeffectState, state); -// Given an input sample, this function produces modulation for the late -// reverb. -static inline ALfloat EAXModulation(ALreverbState *State, ALuint offset, ALfloat in) -{ - ALfloat sinus, frac, fdelay; - ALfloat out0, out1; - ALuint delay; - - // Calculate the sinus rythm (dependent on modulation time and the - // sampling rate). The center of the sinus is moved to reduce the delay - // of the effect when the time or depth are low. - sinus = 1.0f - cosf(F_TAU * State->Mod.Index / State->Mod.Range); - - // Step the modulation index forward, keeping it bound to its range. - State->Mod.Index = (State->Mod.Index + 1) % State->Mod.Range; - - // The depth determines the range over which to read the input samples - // from, so it must be filtered to reduce the distortion caused by even - // small parameter changes. - State->Mod.Filter = lerp(State->Mod.Filter, State->Mod.Depth, - State->Mod.Coeff); - - // Calculate the read offset and fraction between it and the next sample. - frac = modff(State->Mod.Filter*sinus + 1.0f, &fdelay); - delay = fastf2u(fdelay); - - // Get the two samples crossed by the offset, and feed the delay line - // with the next input sample. - out0 = DelayLineOut(&State->Mod.Delay, offset - delay); - out1 = DelayLineOut(&State->Mod.Delay, offset - delay - 1); - DelayLineIn(&State->Mod.Delay, offset, in); - - // The output is obtained by linearly interpolating the two samples that - // were acquired above. - return lerp(out0, out1, frac); -} + state->TotalSamples = 0; + state->SampleBuffer = NULL; -// Given some input sample, this function produces four-channel outputs for the -// early reflections. -static inline ALvoid EarlyReflection(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) -{ - ALfloat d[4], v, f[4]; - ALuint i; + state->Params.Density = AL_EAXREVERB_DEFAULT_DENSITY; + state->Params.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + state->Params.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + state->Params.HFDecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME*AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + state->Params.LFDecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME*AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + state->Params.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + state->Params.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - for(i = 0;i < todo;i++) + for(i = 0;i < NUM_LINES;i++) { - ALuint offset = State->Offset+i; - - // Obtain the decayed results of each early delay line. - d[0] = DelayLineOut(&State->Early.Delay[0], offset-State->Early.Offset[0]) * State->Early.Coeff[0]; - d[1] = DelayLineOut(&State->Early.Delay[1], offset-State->Early.Offset[1]) * State->Early.Coeff[1]; - d[2] = DelayLineOut(&State->Early.Delay[2], offset-State->Early.Offset[2]) * State->Early.Coeff[2]; - d[3] = DelayLineOut(&State->Early.Delay[3], offset-State->Early.Offset[3]) * State->Early.Coeff[3]; - - /* The following uses a lossless scattering junction from waveguide - * theory. It actually amounts to a householder mixing matrix, which - * will produce a maximally diffuse response, and means this can - * probably be considered a simple feed-back delay network (FDN). - * N - * --- - * \ - * v = 2/N / d_i - * --- - * i=1 - */ - v = (d[0] + d[1] + d[2] + d[3]) * 0.5f; - // The junction is loaded with the input here. - v += DelayLineOut(&State->Delay, offset-State->DelayTap[0]); - - // Calculate the feed values for the delay lines. - f[0] = v - d[0]; - f[1] = v - d[1]; - f[2] = v - d[2]; - f[3] = v - d[3]; - - // Re-feed the delay lines. - DelayLineIn(&State->Early.Delay[0], offset, f[0]); - DelayLineIn(&State->Early.Delay[1], offset, f[1]); - DelayLineIn(&State->Early.Delay[2], offset, f[2]); - DelayLineIn(&State->Early.Delay[3], offset, f[3]); - - // Output the results of the junction for all four channels. - out[i][0] = State->Early.Gain * f[0]; - out[i][1] = State->Early.Gain * f[1]; - out[i][2] = State->Early.Gain * f[2]; - out[i][3] = State->Early.Gain * f[3]; + BiquadFilter_clear(&state->Filter[i].Lp); + BiquadFilter_clear(&state->Filter[i].Hp); } -} - -// Basic attenuated all-pass input/output routine. -static inline ALfloat AllpassInOut(DelayLine *Delay, ALuint outOffset, ALuint inOffset, ALfloat in, ALfloat feedCoeff, ALfloat coeff) -{ - ALfloat out, feed; - - out = DelayLineOut(Delay, outOffset); - feed = feedCoeff * in; - DelayLineIn(Delay, inOffset, (feedCoeff * (out - feed)) + in); - - // The time-based attenuation is only applied to the delay output to - // keep it from affecting the feed-back path (which is already controlled - // by the all-pass feed coefficient). - return (coeff * out) - feed; -} -// All-pass input/output routine for late reverb. -static inline ALfloat LateAllPassInOut(ALreverbState *State, ALuint offset, ALuint index, ALfloat in) -{ - return AllpassInOut(&State->Late.ApDelay[index], - offset - State->Late.ApOffset[index], - offset, in, State->Late.ApFeedCoeff, - State->Late.ApCoeff[index]); -} - -// Low-pass filter input/output routine for late reverb. -static inline ALfloat LateLowPassInOut(ALreverbState *State, ALuint index, ALfloat in) -{ - in = lerp(in, State->Late.LpSample[index], State->Late.LpCoeff[index]); - State->Late.LpSample[index] = in; - return in; -} - -// Given four decorrelated input samples, this function produces four-channel -// output for the late reverb. -static inline ALvoid LateReverb(ALreverbState *State, ALuint todo, ALfloat (*restrict out)[4]) -{ - ALfloat d[4], f[4]; - ALuint i; + state->Delay.Mask = 0; + state->Delay.Line = NULL; - for(i = 0;i < todo;i++) + for(i = 0;i < NUM_LINES;i++) { - ALuint offset = State->Offset+i; - - f[0] = DelayLineOut(&State->Decorrelator, offset); - f[1] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[0]); - f[2] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[1]); - f[3] = DelayLineOut(&State->Decorrelator, offset-State->DecoTap[2]); - - // Obtain the decayed results of the cyclical delay lines, and add the - // corresponding input channels. Then pass the results through the - // low-pass filters. - f[0] += DelayLineOut(&State->Late.Delay[0], offset-State->Late.Offset[0]) * State->Late.Coeff[0]; - f[1] += DelayLineOut(&State->Late.Delay[1], offset-State->Late.Offset[1]) * State->Late.Coeff[1]; - f[2] += DelayLineOut(&State->Late.Delay[2], offset-State->Late.Offset[2]) * State->Late.Coeff[2]; - f[3] += DelayLineOut(&State->Late.Delay[3], offset-State->Late.Offset[3]) * State->Late.Coeff[3]; - - // This is where the feed-back cycles from line 0 to 1 to 3 to 2 and - // back to 0. - d[0] = LateLowPassInOut(State, 2, f[2]); - d[1] = LateLowPassInOut(State, 0, f[0]); - d[2] = LateLowPassInOut(State, 3, f[3]); - d[3] = LateLowPassInOut(State, 1, f[1]); - - // To help increase diffusion, run each line through an all-pass filter. - // When there is no diffusion, the shortest all-pass filter will feed - // the shortest delay line. - d[0] = LateAllPassInOut(State, offset, 0, d[0]); - d[1] = LateAllPassInOut(State, offset, 1, d[1]); - d[2] = LateAllPassInOut(State, offset, 2, d[2]); - d[3] = LateAllPassInOut(State, offset, 3, d[3]); - - /* Late reverb is done with a modified feed-back delay network (FDN) - * topology. Four input lines are each fed through their own all-pass - * filter and then into the mixing matrix. The four outputs of the - * mixing matrix are then cycled back to the inputs. Each output feeds - * a different input to form a circlular feed cycle. - * - * The mixing matrix used is a 4D skew-symmetric rotation matrix - * derived using a single unitary rotational parameter: - * - * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 - * [ -a, d, c, -b ] - * [ -b, -c, d, a ] - * [ -c, b, -a, d ] - * - * The rotation is constructed from the effect's diffusion parameter, - * yielding: 1 = x^2 + 3 y^2; where a, b, and c are the coefficient y - * with differing signs, and d is the coefficient x. The matrix is - * thus: - * - * [ x, y, -y, y ] n = sqrt(matrix_order - 1) - * [ -y, x, y, y ] t = diffusion_parameter * atan(n) - * [ y, -y, x, y ] x = cos(t) - * [ -y, -y, -y, x ] y = sin(t) / n - * - * To reduce the number of multiplies, the x coefficient is applied - * with the cyclical delay line coefficients. Thus only the y - * coefficient is applied when mixing, and is modified to be: y / x. - */ - f[0] = d[0] + (State->Late.MixCoeff * ( d[1] + -d[2] + d[3])); - f[1] = d[1] + (State->Late.MixCoeff * (-d[0] + d[2] + d[3])); - f[2] = d[2] + (State->Late.MixCoeff * ( d[0] + -d[1] + d[3])); - f[3] = d[3] + (State->Late.MixCoeff * (-d[0] + -d[1] + -d[2] )); - - // Output the results of the matrix for all four channels, attenuated by - // the late reverb gain (which is attenuated by the 'x' mix coefficient). - // Mix early reflections and late reverb. - out[i][0] += State->Late.Gain * f[0]; - out[i][1] += State->Late.Gain * f[1]; - out[i][2] += State->Late.Gain * f[2]; - out[i][3] += State->Late.Gain * f[3]; - - // Re-feed the cyclical delay lines. - DelayLineIn(&State->Late.Delay[0], offset, f[0]); - DelayLineIn(&State->Late.Delay[1], offset, f[1]); - DelayLineIn(&State->Late.Delay[2], offset, f[2]); - DelayLineIn(&State->Late.Delay[3], offset, f[3]); + state->EarlyDelayTap[i][0] = 0; + state->EarlyDelayTap[i][1] = 0; + state->EarlyDelayCoeff[i][0] = 0.0f; + state->EarlyDelayCoeff[i][1] = 0.0f; } -} -// Given an input sample, this function mixes echo into the four-channel late -// reverb. -static inline ALvoid EAXEcho(ALreverbState *State, ALuint todo, ALfloat (*restrict late)[4]) -{ - ALfloat out, feed; - ALuint i; + state->LateFeedTap = 0; - for(i = 0;i < todo;i++) + for(i = 0;i < NUM_LINES;i++) { - ALuint offset = State->Offset+i; - - // Get the latest attenuated echo sample for output. - feed = DelayLineOut(&State->Echo.Delay, offset-State->Echo.Offset) * - State->Echo.Coeff; - - // Mix the output into the late reverb channels. - out = State->Echo.MixCoeff * feed; - late[i][0] += out; - late[i][1] += out; - late[i][2] += out; - late[i][3] += out; - - // Mix the energy-attenuated input with the output and pass it through - // the echo low-pass filter. - feed += DelayLineOut(&State->Delay, offset-State->DelayTap[1]) * - State->Echo.DensityGain; - feed = lerp(feed, State->Echo.LpSample, State->Echo.LpCoeff); - State->Echo.LpSample = feed; - - // Then the echo all-pass filter. - feed = AllpassInOut(&State->Echo.ApDelay, offset-State->Echo.ApOffset, - offset, feed, State->Echo.ApFeedCoeff, - State->Echo.ApCoeff); - - // Feed the delay with the mixed and filtered sample. - DelayLineIn(&State->Echo.Delay, offset, feed); + state->LateDelayTap[i][0] = 0; + state->LateDelayTap[i][1] = 0; } -} -// Perform the non-EAX reverb pass on a given input sample, resulting in -// four-channel output. -static inline ALvoid VerbPass(ALreverbState *State, ALuint todo, const ALfloat *in, ALfloat (*restrict out)[4]) -{ - ALuint i; + state->MixX = 0.0f; + state->MixY = 0.0f; - // Low-pass filter the incoming samples. - for(i = 0;i < todo;i++) - DelayLineIn(&State->Delay, State->Offset+i, - ALfilterState_processSingle(&State->LpFilter, in[i]) - ); - - // Calculate the early reflection from the first delay tap. - EarlyReflection(State, todo, out); - - // Feed the decorrelator from the energy-attenuated output of the second - // delay tap. - for(i = 0;i < todo;i++) + state->Early.VecAp.Delay.Mask = 0; + state->Early.VecAp.Delay.Line = NULL; + state->Early.VecAp.Coeff = 0.0f; + state->Early.Delay.Mask = 0; + state->Early.Delay.Line = NULL; + for(i = 0;i < NUM_LINES;i++) { - ALuint offset = State->Offset+i; - ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * - State->Late.DensityGain; - DelayLineIn(&State->Decorrelator, offset, sample); + state->Early.VecAp.Offset[i][0] = 0; + state->Early.VecAp.Offset[i][1] = 0; + state->Early.Offset[i][0] = 0; + state->Early.Offset[i][1] = 0; + state->Early.Coeff[i][0] = 0.0f; + state->Early.Coeff[i][1] = 0.0f; } - // Calculate the late reverb from the decorrelator taps. - LateReverb(State, todo, out); - - // Step all delays forward one sample. - State->Offset += todo; -} - -// Perform the EAX reverb pass on a given input sample, resulting in four- -// channel output. -static inline ALvoid EAXVerbPass(ALreverbState *State, ALuint todo, const ALfloat *input, ALfloat (*restrict early)[4], ALfloat (*restrict late)[4]) -{ - ALuint i; - - // Band-pass and modulate the incoming samples. - for(i = 0;i < todo;i++) + state->Late.DensityGain[0] = 0.0f; + state->Late.DensityGain[1] = 0.0f; + state->Late.Delay.Mask = 0; + state->Late.Delay.Line = NULL; + state->Late.VecAp.Delay.Mask = 0; + state->Late.VecAp.Delay.Line = NULL; + state->Late.VecAp.Coeff = 0.0f; + for(i = 0;i < NUM_LINES;i++) { - ALfloat sample = input[i]; - sample = ALfilterState_processSingle(&State->LpFilter, sample); - sample = ALfilterState_processSingle(&State->HpFilter, sample); + state->Late.Offset[i][0] = 0; + state->Late.Offset[i][1] = 0; - // Perform any modulation on the input. - sample = EAXModulation(State, State->Offset+i, sample); + state->Late.VecAp.Offset[i][0] = 0; + state->Late.VecAp.Offset[i][1] = 0; - // Feed the initial delay line. - DelayLineIn(&State->Delay, State->Offset+i, sample); + state->Late.T60[i].MidGain[0] = 0.0f; + state->Late.T60[i].MidGain[1] = 0.0f; + BiquadFilter_clear(&state->Late.T60[i].HFFilter); + BiquadFilter_clear(&state->Late.T60[i].LFFilter); } - // Calculate the early reflection from the first delay tap. - EarlyReflection(State, todo, early); - - // Feed the decorrelator from the energy-attenuated output of the second - // delay tap. - for(i = 0;i < todo;i++) - { - ALuint offset = State->Offset+i; - ALfloat sample = DelayLineOut(&State->Delay, offset - State->DelayTap[1]) * - State->Late.DensityGain; - DelayLineIn(&State->Decorrelator, offset, sample); - } - - // Calculate the late reverb from the decorrelator taps. - memset(late, 0, sizeof(*late)*todo); - LateReverb(State, todo, late); - - // Calculate and mix in any echo. - EAXEcho(State, todo, late); - - // Step all delays forward. - State->Offset += todo; -} - -static ALvoid ALreverbState_processStandard(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) -{ - ALfloat (*restrict out)[4] = State->ReverbSamples; - ALuint index, c, i, l; - - /* Process reverb for these samples. */ - for(index = 0;index < SamplesToDo;) + for(i = 0;i < NUM_LINES;i++) { - ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); - - VerbPass(State, todo, &SamplesIn[index], out); - - for(l = 0;l < 4;l++) + for(j = 0;j < MAX_OUTPUT_CHANNELS;j++) { - for(c = 0;c < NumChannels;c++) - { - ALfloat gain = State->Gain[l][c]; - if(!(fabsf(gain) > GAIN_SILENCE_THRESHOLD)) - continue; - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*out[i][l]; - } + state->Early.CurrentGain[i][j] = 0.0f; + state->Early.PanGain[i][j] = 0.0f; + state->Late.CurrentGain[i][j] = 0.0f; + state->Late.PanGain[i][j] = 0.0f; } - - index += todo; } + + state->FadeCount = 0; + state->MaxUpdate[0] = MAX_UPDATE_SAMPLES; + state->MaxUpdate[1] = MAX_UPDATE_SAMPLES; + state->Offset = 0; } -static ALvoid ALreverbState_processEax(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +static ALvoid ReverbState_Destruct(ReverbState *State) { - ALfloat (*restrict early)[4] = State->EarlySamples; - ALfloat (*restrict late)[4] = State->ReverbSamples; - ALuint index, c, i, l; - ALfloat gain; - - /* Process reverb for these samples. */ - for(index = 0;index < SamplesToDo;) - { - ALuint todo = minu(SamplesToDo-index, MAX_UPDATE_SAMPLES); - - EAXVerbPass(State, todo, &SamplesIn[index], early, late); - - for(l = 0;l < 4;l++) - { - for(c = 0;c < NumChannels;c++) - { - gain = State->Early.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*early[i][l]; - } - gain = State->Late.PanGain[l][c]; - if(fabsf(gain) > GAIN_SILENCE_THRESHOLD) - { - for(i = 0;i < todo;i++) - SamplesOut[c][index+i] += gain*late[i][l]; - } - } - } + al_free(State->SampleBuffer); + State->SampleBuffer = NULL; - index += todo; - } + ALeffectState_Destruct(STATIC_CAST(ALeffectState,State)); } -static ALvoid ALreverbState_process(ALreverbState *State, ALuint SamplesToDo, const ALfloat *restrict SamplesIn, ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALuint NumChannels) +/************************************** + * Device Update * + **************************************/ + +static inline ALfloat CalcDelayLengthMult(ALfloat density) { - if(State->IsEax) - ALreverbState_processEax(State, SamplesToDo, SamplesIn, SamplesOut, NumChannels); - else - ALreverbState_processStandard(State, SamplesToDo, SamplesIn, SamplesOut, NumChannels); + return maxf(5.0f, cbrtf(density*DENSITY_SCALE)); } -// Given the allocated sample buffer, this function updates each delay line -// offset. -static inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLine *Delay) +/* Given the allocated sample buffer, this function updates each delay line + * offset. + */ +static inline ALvoid RealizeLineOffset(ALfloat *sampleBuffer, DelayLineI *Delay) { - Delay->Line = &sampleBuffer[(ptrdiff_t)Delay->Line]; + union { + ALfloat *f; + ALfloat (*f4)[NUM_LINES]; + } u; + u.f = &sampleBuffer[(ptrdiff_t)Delay->Line * NUM_LINES]; + Delay->Line = u.f4; } -// Calculate the length of a delay line and store its mask and offset. -static ALuint CalcLineLength(ALfloat length, ptrdiff_t offset, ALuint frequency, ALuint extra, DelayLine *Delay) +/* Calculate the length of a delay line and store its mask and offset. */ +static ALuint CalcLineLength(const ALfloat length, const ptrdiff_t offset, const ALuint frequency, + const ALuint extra, DelayLineI *Delay) { ALuint samples; - // All line lengths are powers of 2, calculated from their lengths, with - // an additional sample in case of rounding errors. - samples = fastf2u(length*frequency) + extra; - samples = NextPowerOf2(samples + 1); - // All lines share a single sample buffer. + /* All line lengths are powers of 2, calculated from their lengths in + * seconds, rounded up. + */ + samples = float2int(ceilf(length*frequency)); + samples = NextPowerOf2(samples + extra); + + /* All lines share a single sample buffer. */ Delay->Mask = samples - 1; - Delay->Line = (ALfloat*)offset; - // Return the sample count for accumulation. + Delay->Line = (ALfloat(*)[NUM_LINES])offset; + + /* Return the sample count for accumulation. */ return samples; } @@ -662,144 +492,173 @@ static ALuint CalcLineLength(ALfloat length, ptrdiff_t offset, ALuint frequency, * for all lines given the sample rate (frequency). If an allocation failure * occurs, it returns AL_FALSE. */ -static ALboolean AllocLines(ALuint frequency, ALreverbState *State) +static ALboolean AllocLines(const ALuint frequency, ReverbState *State) { - ALuint totalSamples, index; - ALfloat length; - ALfloat *newBuffer = NULL; + ALuint totalSamples, i; + ALfloat multiplier, length; - // All delay line lengths are calculated to accomodate the full range of - // lengths given their respective paramters. + /* All delay line lengths are calculated to accomodate the full range of + * lengths given their respective paramters. + */ totalSamples = 0; - /* The modulator's line length is calculated from the maximum modulation - * time and depth coefficient, and halfed for the low-to-high frequency - * swing. An additional sample is added to keep it stable when there is no - * modulation. + /* Multiplier for the maximum density value, i.e. density=1, which is + * actually the least density... */ - length = (AL_EAXREVERB_MAX_MODULATION_TIME*MODULATION_DEPTH_COEFF/2.0f); - totalSamples += CalcLineLength(length, totalSamples, frequency, 1, - &State->Mod.Delay); - - // The initial delay is the sum of the reflections and late reverb - // delays. This must include space for storing a loop update to feed the - // early reflections, decorrelator, and echo. - length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + - AL_EAXREVERB_MAX_LATE_REVERB_DELAY; - totalSamples += CalcLineLength(length, totalSamples, frequency, - MAX_UPDATE_SAMPLES, &State->Delay); - - // The early reflection lines. - for(index = 0;index < 4;index++) - totalSamples += CalcLineLength(EARLY_LINE_LENGTH[index], totalSamples, - frequency, 0, &State->Early.Delay[index]); - - // The decorrelator line is calculated from the lowest reverb density (a - // parameter value of 1). This must include space for storing a loop update - // to feed the late reverb. - length = (DECO_FRACTION * DECO_MULTIPLIER * DECO_MULTIPLIER) * - LATE_LINE_LENGTH[0] * (1.0f + LATE_LINE_MULTIPLIER); + multiplier = CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY); + + /* The main delay length includes the maximum early reflection delay, the + * largest early tap width, the maximum late reverb delay, and the + * largest late tap width. Finally, it must also be extended by the + * update size (MAX_UPDATE_SAMPLES) for block processing. + */ + length = AL_EAXREVERB_MAX_REFLECTIONS_DELAY + EARLY_TAP_LENGTHS[NUM_LINES-1]*multiplier + + AL_EAXREVERB_MAX_LATE_REVERB_DELAY + + (LATE_LINE_LENGTHS[NUM_LINES-1] - LATE_LINE_LENGTHS[0])*0.25f*multiplier; totalSamples += CalcLineLength(length, totalSamples, frequency, MAX_UPDATE_SAMPLES, - &State->Decorrelator); + &State->Delay); - // The late all-pass lines. - for(index = 0;index < 4;index++) - totalSamples += CalcLineLength(ALLPASS_LINE_LENGTH[index], totalSamples, - frequency, 0, &State->Late.ApDelay[index]); + /* The early vector all-pass line. */ + length = EARLY_ALLPASS_LENGTHS[NUM_LINES-1] * multiplier; + totalSamples += CalcLineLength(length, totalSamples, frequency, 0, + &State->Early.VecAp.Delay); - // The late delay lines are calculated from the lowest reverb density. - for(index = 0;index < 4;index++) - { - length = LATE_LINE_LENGTH[index] * (1.0f + LATE_LINE_MULTIPLIER); - totalSamples += CalcLineLength(length, totalSamples, frequency, 0, - &State->Late.Delay[index]); - } + /* The early reflection line. */ + length = EARLY_LINE_LENGTHS[NUM_LINES-1] * multiplier; + totalSamples += CalcLineLength(length, totalSamples, frequency, 0, + &State->Early.Delay); + + /* The late vector all-pass line. */ + length = LATE_ALLPASS_LENGTHS[NUM_LINES-1] * multiplier; + totalSamples += CalcLineLength(length, totalSamples, frequency, 0, + &State->Late.VecAp.Delay); - // The echo all-pass and delay lines. - totalSamples += CalcLineLength(ECHO_ALLPASS_LENGTH, totalSamples, - frequency, 0, &State->Echo.ApDelay); - totalSamples += CalcLineLength(AL_EAXREVERB_MAX_ECHO_TIME, totalSamples, - frequency, 0, &State->Echo.Delay); + /* The late delay lines are calculated from the largest maximum density + * line length. + */ + length = LATE_LINE_LENGTHS[NUM_LINES-1] * multiplier; + totalSamples += CalcLineLength(length, totalSamples, frequency, 0, + &State->Late.Delay); if(totalSamples != State->TotalSamples) { - TRACE("New reverb buffer length: %u samples (%f sec)\n", totalSamples, totalSamples/(float)frequency); - newBuffer = realloc(State->SampleBuffer, sizeof(ALfloat) * totalSamples); - if(newBuffer == NULL) - return AL_FALSE; + ALfloat *newBuffer; + + TRACE("New reverb buffer length: %ux4 samples\n", totalSamples); + newBuffer = al_calloc(16, sizeof(ALfloat[NUM_LINES]) * totalSamples); + if(!newBuffer) return AL_FALSE; + + al_free(State->SampleBuffer); State->SampleBuffer = newBuffer; State->TotalSamples = totalSamples; } - // Update all delays to reflect the new sample buffer. + /* Update all delays to reflect the new sample buffer. */ RealizeLineOffset(State->SampleBuffer, &State->Delay); - RealizeLineOffset(State->SampleBuffer, &State->Decorrelator); - for(index = 0;index < 4;index++) - { - RealizeLineOffset(State->SampleBuffer, &State->Early.Delay[index]); - RealizeLineOffset(State->SampleBuffer, &State->Late.ApDelay[index]); - RealizeLineOffset(State->SampleBuffer, &State->Late.Delay[index]); - } - RealizeLineOffset(State->SampleBuffer, &State->Mod.Delay); - RealizeLineOffset(State->SampleBuffer, &State->Echo.ApDelay); - RealizeLineOffset(State->SampleBuffer, &State->Echo.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Early.VecAp.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Early.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Late.VecAp.Delay); + RealizeLineOffset(State->SampleBuffer, &State->Late.Delay); - // Clear the sample buffer. - for(index = 0;index < State->TotalSamples;index++) - State->SampleBuffer[index] = 0.0f; + /* Clear the sample buffer. */ + for(i = 0;i < State->TotalSamples;i++) + State->SampleBuffer[i] = 0.0f; return AL_TRUE; } -static ALboolean ALreverbState_deviceUpdate(ALreverbState *State, ALCdevice *Device) +static ALboolean ReverbState_deviceUpdate(ReverbState *State, ALCdevice *Device) { - ALuint frequency = Device->Frequency, index; + ALuint frequency = Device->Frequency; + ALfloat multiplier; + ALsizei i, j; - // Allocate the delay lines. + /* Allocate the delay lines. */ if(!AllocLines(frequency, State)) return AL_FALSE; - // Calculate the modulation filter coefficient. Notice that the exponent - // is calculated given the current sample rate. This ensures that the - // resulting filter response over time is consistent across all sample - // rates. - State->Mod.Coeff = powf(MODULATION_FILTER_COEFF, - MODULATION_FILTER_CONST / frequency); + multiplier = CalcDelayLengthMult(AL_EAXREVERB_MAX_DENSITY); + + /* The late feed taps are set a fixed position past the latest delay tap. */ + State->LateFeedTap = float2int((AL_EAXREVERB_MAX_REFLECTIONS_DELAY + + EARLY_TAP_LENGTHS[NUM_LINES-1]*multiplier) * + frequency); - // The early reflection and late all-pass filter line lengths are static, - // so their offsets only need to be calculated once. - for(index = 0;index < 4;index++) + /* Clear filters and gain coefficients since the delay lines were all just + * cleared (if not reallocated). + */ + for(i = 0;i < NUM_LINES;i++) { - State->Early.Offset[index] = fastf2u(EARLY_LINE_LENGTH[index] * - frequency); - State->Late.ApOffset[index] = fastf2u(ALLPASS_LINE_LENGTH[index] * - frequency); + BiquadFilter_clear(&State->Filter[i].Lp); + BiquadFilter_clear(&State->Filter[i].Hp); } - // The echo all-pass filter line length is static, so its offset only - // needs to be calculated once. - State->Echo.ApOffset = fastf2u(ECHO_ALLPASS_LENGTH * frequency); + for(i = 0;i < NUM_LINES;i++) + { + State->EarlyDelayCoeff[i][0] = 0.0f; + State->EarlyDelayCoeff[i][1] = 0.0f; + } + + for(i = 0;i < NUM_LINES;i++) + { + State->Early.Coeff[i][0] = 0.0f; + State->Early.Coeff[i][1] = 0.0f; + } + + State->Late.DensityGain[0] = 0.0f; + State->Late.DensityGain[1] = 0.0f; + for(i = 0;i < NUM_LINES;i++) + { + State->Late.T60[i].MidGain[0] = 0.0f; + State->Late.T60[i].MidGain[1] = 0.0f; + BiquadFilter_clear(&State->Late.T60[i].HFFilter); + BiquadFilter_clear(&State->Late.T60[i].LFFilter); + } + + for(i = 0;i < NUM_LINES;i++) + { + for(j = 0;j < MAX_OUTPUT_CHANNELS;j++) + { + State->Early.CurrentGain[i][j] = 0.0f; + State->Early.PanGain[i][j] = 0.0f; + State->Late.CurrentGain[i][j] = 0.0f; + State->Late.PanGain[i][j] = 0.0f; + } + } + + /* Reset counters and offset base. */ + State->FadeCount = 0; + State->MaxUpdate[0] = MAX_UPDATE_SAMPLES; + State->MaxUpdate[1] = MAX_UPDATE_SAMPLES; + State->Offset = 0; return AL_TRUE; } -// Calculate a decay coefficient given the length of each cycle and the time -// until the decay reaches -60 dB. -static inline ALfloat CalcDecayCoeff(ALfloat length, ALfloat decayTime) +/************************************** + * Effect Update * + **************************************/ + +/* Calculate a decay coefficient given the length of each cycle and the time + * until the decay reaches -60 dB. + */ +static inline ALfloat CalcDecayCoeff(const ALfloat length, const ALfloat decayTime) { - return powf(0.001f/*-60 dB*/, length/decayTime); + return powf(REVERB_DECAY_GAIN, length/decayTime); } -// Calculate a decay length from a coefficient and the time until the decay -// reaches -60 dB. -static inline ALfloat CalcDecayLength(ALfloat coeff, ALfloat decayTime) +/* Calculate a decay length from a coefficient and the time until the decay + * reaches -60 dB. + */ +static inline ALfloat CalcDecayLength(const ALfloat coeff, const ALfloat decayTime) { - return log10f(coeff) * decayTime / log10f(0.001f)/*-60 dB*/; + return log10f(coeff) * decayTime / log10f(REVERB_DECAY_GAIN); } -// Calculate an attenuation to be applied to the input of any echo models to -// compensate for modal density and decay time. -static inline ALfloat CalcDensityGain(ALfloat a) +/* Calculate an attenuation to be applied to the input of any echo models to + * compensate for modal density and decay time. + */ +static inline ALfloat CalcDensityGain(const ALfloat a) { /* The energy of a signal can be obtained by finding the area under the * squared signal. This takes the form of Sum(x_n^2), where x is the @@ -809,32 +668,34 @@ static inline ALfloat CalcDensityGain(ALfloat a) * where a is the attenuation coefficient, and n is the sample. The area * under this decay curve can be calculated as: 1 / (1 - a). * - * Modifying the above equation to find the squared area under the curve + * Modifying the above equation to find the area under the squared curve * (for energy) yields: 1 / (1 - a^2). Input attenuation can then be * calculated by inverting the square root of this approximation, * yielding: 1 / sqrt(1 / (1 - a^2)), simplified to: sqrt(1 - a^2). */ - return sqrtf(1.0f - (a * a)); + return sqrtf(1.0f - a*a); } -// Calculate the mixing matrix coefficients given a diffusion factor. -static inline ALvoid CalcMatrixCoeffs(ALfloat diffusion, ALfloat *x, ALfloat *y) +/* Calculate the scattering matrix coefficients given a diffusion factor. */ +static inline ALvoid CalcMatrixCoeffs(const ALfloat diffusion, ALfloat *x, ALfloat *y) { ALfloat n, t; - // The matrix is of order 4, so n is sqrt (4 - 1). + /* The matrix is of order 4, so n is sqrt(4 - 1). */ n = sqrtf(3.0f); t = diffusion * atanf(n); - // Calculate the first mixing matrix coefficient. + /* Calculate the first mixing matrix coefficient. */ *x = cosf(t); - // Calculate the second mixing matrix coefficient. + /* Calculate the second mixing matrix coefficient. */ *y = sinf(t) / n; } -// Calculate the limited HF ratio for use with the late reverb low-pass -// filters. -static ALfloat CalcLimitedHfRatio(ALfloat hfRatio, ALfloat airAbsorptionGainHF, ALfloat decayTime) +/* Calculate the limited HF ratio for use with the late reverb low-pass + * filters. + */ +static ALfloat CalcLimitedHfRatio(const ALfloat hfRatio, const ALfloat airAbsorptionGainHF, + const ALfloat decayTime, const ALfloat SpeedOfSound) { ALfloat limitRatio; @@ -843,464 +704,906 @@ static ALfloat CalcLimitedHfRatio(ALfloat hfRatio, ALfloat airAbsorptionGainHF, * equation, solve for HF ratio. The delay length is cancelled out of * the equation, so it can be calculated once for all lines. */ - limitRatio = 1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * - SPEEDOFSOUNDMETRESPERSEC); - /* Using the limit calculated above, apply the upper bound to the HF - * ratio. Also need to limit the result to a minimum of 0.1, just like the - * HF ratio parameter. */ - return clampf(limitRatio, 0.1f, hfRatio); -} + limitRatio = 1.0f / (CalcDecayLength(airAbsorptionGainHF, decayTime) * SpeedOfSound); -// Calculate the coefficient for a HF (and eventually LF) decay damping -// filter. -static inline ALfloat CalcDampingCoeff(ALfloat hfRatio, ALfloat length, ALfloat decayTime, ALfloat decayCoeff, ALfloat cw) -{ - ALfloat coeff, g; + /* Using the limit calculated above, apply the upper bound to the HF ratio. + */ + return minf(limitRatio, hfRatio); +} - // Eventually this should boost the high frequencies when the ratio - // exceeds 1. - coeff = 0.0f; - if (hfRatio < 1.0f) - { - // Calculate the low-pass coefficient by dividing the HF decay - // coefficient by the full decay coefficient. - g = CalcDecayCoeff(length, decayTime * hfRatio) / decayCoeff; - // Damping is done with a 1-pole filter, so g needs to be squared. - g *= g; - if(g < 0.9999f) /* 1-epsilon */ - { - /* Be careful with gains < 0.001, as that causes the coefficient - * head towards 1, which will flatten the signal. */ - g = maxf(g, 0.001f); - coeff = (1 - g*cw - sqrtf(2*g*(1-cw) - g*g*(1 - cw*cw))) / - (1 - g); - } - - // Very low decay times will produce minimal output, so apply an - // upper bound to the coefficient. - coeff = minf(coeff, 0.98f); - } - return coeff; +/* Calculates the 3-band T60 damping coefficients for a particular delay line + * of specified length, using a combination of two shelf filter sections given + * decay times for each band split at two reference frequencies. + */ +static void CalcT60DampingCoeffs(const ALfloat length, const ALfloat lfDecayTime, + const ALfloat mfDecayTime, const ALfloat hfDecayTime, + const ALfloat lf0norm, const ALfloat hf0norm, + T60Filter *filter) +{ + ALfloat lfGain = CalcDecayCoeff(length, lfDecayTime); + ALfloat mfGain = CalcDecayCoeff(length, mfDecayTime); + ALfloat hfGain = CalcDecayCoeff(length, hfDecayTime); + + filter->MidGain[1] = mfGain; + BiquadFilter_setParams(&filter->LFFilter, BiquadType_LowShelf, lfGain/mfGain, lf0norm, + calc_rcpQ_from_slope(lfGain/mfGain, 1.0f)); + BiquadFilter_setParams(&filter->HFFilter, BiquadType_HighShelf, hfGain/mfGain, hf0norm, + calc_rcpQ_from_slope(hfGain/mfGain, 1.0f)); } -// Update the EAX modulation index, range, and depth. Keep in mind that this -// kind of vibrato is additive and not multiplicative as one may expect. The -// downswing will sound stronger than the upswing. -static ALvoid UpdateModulator(ALfloat modTime, ALfloat modDepth, ALuint frequency, ALreverbState *State) +/* Update the offsets for the main effect delay line. */ +static ALvoid UpdateDelayLine(const ALfloat earlyDelay, const ALfloat lateDelay, const ALfloat density, const ALfloat decayTime, const ALuint frequency, ReverbState *State) { - ALuint range; + ALfloat multiplier, length; + ALuint i; - /* Modulation is calculated in two parts. + multiplier = CalcDelayLengthMult(density); + + /* Early reflection taps are decorrelated by means of an average room + * reflection approximation described above the definition of the taps. + * This approximation is linear and so the above density multiplier can + * be applied to adjust the width of the taps. A single-band decay + * coefficient is applied to simulate initial attenuation and absorption. * - * The modulation time effects the sinus applied to the change in - * frequency. An index out of the current time range (both in samples) - * is incremented each sample. The range is bound to a reasonable - * minimum (1 sample) and when the timing changes, the index is rescaled - * to the new range (to keep the sinus consistent). - */ - range = maxu(fastf2u(modTime*frequency), 1); - State->Mod.Index = (ALuint)(State->Mod.Index * (ALuint64)range / - State->Mod.Range); - State->Mod.Range = range; - - /* The modulation depth effects the amount of frequency change over the - * range of the sinus. It needs to be scaled by the modulation time so - * that a given depth produces a consistent change in frequency over all - * ranges of time. Since the depth is applied to a sinus value, it needs - * to be halfed once for the sinus range and again for the sinus swing - * in time (half of it is spent decreasing the frequency, half is spent - * increasing it). + * Late reverb taps are based on the late line lengths to allow a zero- + * delay path and offsets that would continue the propagation naturally + * into the late lines. */ - State->Mod.Depth = modDepth * MODULATION_DEPTH_COEFF * modTime / 2.0f / - 2.0f * frequency; -} + for(i = 0;i < NUM_LINES;i++) + { + length = earlyDelay + EARLY_TAP_LENGTHS[i]*multiplier; + State->EarlyDelayTap[i][1] = float2int(length * frequency); -// Update the offsets for the initial effect delay line. -static ALvoid UpdateDelayLine(ALfloat earlyDelay, ALfloat lateDelay, ALuint frequency, ALreverbState *State) -{ - // Calculate the initial delay taps. - State->DelayTap[0] = fastf2u(earlyDelay * frequency); - State->DelayTap[1] = fastf2u((earlyDelay + lateDelay) * frequency); -} + length = EARLY_TAP_LENGTHS[i]*multiplier; + State->EarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime); -// Update the early reflections gain and line coefficients. -static ALvoid UpdateEarlyLines(ALfloat reverbGain, ALfloat earlyGain, ALfloat lateDelay, ALreverbState *State) -{ - ALuint index; - - // Calculate the early reflections gain (from the master effect gain, and - // reflections gain parameters) with a constant attenuation of 0.5. - State->Early.Gain = 0.5f * reverbGain * earlyGain; - - // Calculate the gain (coefficient) for each early delay line using the - // late delay time. This expands the early reflections to the start of - // the late reverb. - for(index = 0;index < 4;index++) - State->Early.Coeff[index] = CalcDecayCoeff(EARLY_LINE_LENGTH[index], - lateDelay); + length = lateDelay + (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS[0])*0.25f*multiplier; + State->LateDelayTap[i][1] = State->LateFeedTap + float2int(length * frequency); + } } -// Update the offsets for the decorrelator line. -static ALvoid UpdateDecorrelator(ALfloat density, ALuint frequency, ALreverbState *State) +/* Update the early reflection line lengths and gain coefficients. */ +static ALvoid UpdateEarlyLines(const ALfloat density, const ALfloat diffusion, const ALfloat decayTime, const ALuint frequency, EarlyReflections *Early) { - ALuint index; - ALfloat length; + ALfloat multiplier, length; + ALsizei i; - /* The late reverb inputs are decorrelated to smooth the reverb tail and - * reduce harsh echos. The first tap occurs immediately, while the - * remaining taps are delayed by multiples of a fraction of the smallest - * cyclical delay time. - * - * offset[index] = (FRACTION (MULTIPLIER^index)) smallest_delay - */ - for(index = 0;index < 3;index++) + multiplier = CalcDelayLengthMult(density); + + /* Calculate the all-pass feed-back/forward coefficient. */ + Early->VecAp.Coeff = sqrtf(0.5f) * powf(diffusion, 2.0f); + + for(i = 0;i < NUM_LINES;i++) { - length = (DECO_FRACTION * powf(DECO_MULTIPLIER, (ALfloat)index)) * - LATE_LINE_LENGTH[0] * (1.0f + (density * LATE_LINE_MULTIPLIER)); - State->DecoTap[index] = fastf2u(length * frequency); + /* Calculate the length (in seconds) of each all-pass line. */ + length = EARLY_ALLPASS_LENGTHS[i] * multiplier; + + /* Calculate the delay offset for each all-pass line. */ + Early->VecAp.Offset[i][1] = float2int(length * frequency); + + /* Calculate the length (in seconds) of each delay line. */ + length = EARLY_LINE_LENGTHS[i] * multiplier; + + /* Calculate the delay offset for each delay line. */ + Early->Offset[i][1] = float2int(length * frequency); + + /* Calculate the gain (coefficient) for each line. */ + Early->Coeff[i][1] = CalcDecayCoeff(length, decayTime); } } -// Update the late reverb gains, line lengths, and line coefficients. -static ALvoid UpdateLateLines(ALfloat reverbGain, ALfloat lateGain, ALfloat xMix, ALfloat density, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) +/* Update the late reverb line lengths and T60 coefficients. */ +static ALvoid UpdateLateLines(const ALfloat density, const ALfloat diffusion, const ALfloat lfDecayTime, const ALfloat mfDecayTime, const ALfloat hfDecayTime, const ALfloat lf0norm, const ALfloat hf0norm, const ALuint frequency, LateReverb *Late) { - ALfloat length; - ALuint index; - - /* Calculate the late reverb gain (from the master effect gain, and late - * reverb gain parameters). Since the output is tapped prior to the - * application of the next delay line coefficients, this gain needs to be - * attenuated by the 'x' mixing matrix coefficient as well. Also attenuate - * the late reverb when echo depth is high and diffusion is low, so the - * echo is slightly stronger than the decorrelated echos in the reverb - * tail. + /* Scaling factor to convert the normalized reference frequencies from + * representing 0...freq to 0...max_reference. */ - State->Late.Gain = reverbGain * lateGain * xMix * - (1.0f - (echoDepth*0.5f*(1.0f - diffusion))); + const ALfloat norm_weight_factor = (ALfloat)frequency / AL_EAXREVERB_MAX_HFREFERENCE; + ALfloat multiplier, length, bandWeights[3]; + ALsizei i; /* To compensate for changes in modal density and decay time of the late * reverb signal, the input is attenuated based on the maximal energy of * the outgoing signal. This approximation is used to keep the apparent * energy of the signal equal for all ranges of density and decay time. * - * The average length of the cyclcical delay lines is used to calculate - * the attenuation coefficient. + * The average length of the delay lines is used to calculate the + * attenuation coefficient. + */ + multiplier = CalcDelayLengthMult(density); + length = (LATE_LINE_LENGTHS[0] + LATE_LINE_LENGTHS[1] + + LATE_LINE_LENGTHS[2] + LATE_LINE_LENGTHS[3]) / 4.0f * multiplier; + length += (LATE_ALLPASS_LENGTHS[0] + LATE_ALLPASS_LENGTHS[1] + + LATE_ALLPASS_LENGTHS[2] + LATE_ALLPASS_LENGTHS[3]) / 4.0f * multiplier; + /* The density gain calculation uses an average decay time weighted by + * approximate bandwidth. This attempts to compensate for losses of energy + * that reduce decay time due to scattering into highly attenuated bands. */ - length = (LATE_LINE_LENGTH[0] + LATE_LINE_LENGTH[1] + - LATE_LINE_LENGTH[2] + LATE_LINE_LENGTH[3]) / 4.0f; - length *= 1.0f + (density * LATE_LINE_MULTIPLIER); - State->Late.DensityGain = CalcDensityGain( - CalcDecayCoeff(length, decayTime) + bandWeights[0] = lf0norm*norm_weight_factor; + bandWeights[1] = hf0norm*norm_weight_factor - lf0norm*norm_weight_factor; + bandWeights[2] = 1.0f - hf0norm*norm_weight_factor; + Late->DensityGain[1] = CalcDensityGain( + CalcDecayCoeff(length, + bandWeights[0]*lfDecayTime + bandWeights[1]*mfDecayTime + bandWeights[2]*hfDecayTime + ) ); - // Calculate the all-pass feed-back and feed-forward coefficient. - State->Late.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); + /* Calculate the all-pass feed-back/forward coefficient. */ + Late->VecAp.Coeff = sqrtf(0.5f) * powf(diffusion, 2.0f); - for(index = 0;index < 4;index++) + for(i = 0;i < NUM_LINES;i++) { - // Calculate the gain (coefficient) for each all-pass line. - State->Late.ApCoeff[index] = CalcDecayCoeff( - ALLPASS_LINE_LENGTH[index], decayTime - ); + /* Calculate the length (in seconds) of each all-pass line. */ + length = LATE_ALLPASS_LENGTHS[i] * multiplier; - // Calculate the length (in seconds) of each cyclical delay line. - length = LATE_LINE_LENGTH[index] * - (1.0f + (density * LATE_LINE_MULTIPLIER)); + /* Calculate the delay offset for each all-pass line. */ + Late->VecAp.Offset[i][1] = float2int(length * frequency); - // Calculate the delay offset for each cyclical delay line. - State->Late.Offset[index] = fastf2u(length * frequency); + /* Calculate the length (in seconds) of each delay line. */ + length = LATE_LINE_LENGTHS[i] * multiplier; - // Calculate the gain (coefficient) for each cyclical line. - State->Late.Coeff[index] = CalcDecayCoeff(length, decayTime); + /* Calculate the delay offset for each delay line. */ + Late->Offset[i][1] = float2int(length*frequency + 0.5f); - // Calculate the damping coefficient for each low-pass filter. - State->Late.LpCoeff[index] = CalcDampingCoeff( - hfRatio, length, decayTime, State->Late.Coeff[index], cw - ); + /* Approximate the absorption that the vector all-pass would exhibit + * given the current diffusion so we don't have to process a full T60 + * filter for each of its four lines. + */ + length += lerp(LATE_ALLPASS_LENGTHS[i], + (LATE_ALLPASS_LENGTHS[0] + LATE_ALLPASS_LENGTHS[1] + + LATE_ALLPASS_LENGTHS[2] + LATE_ALLPASS_LENGTHS[3]) / 4.0f, + diffusion) * multiplier; + + /* Calculate the T60 damping coefficients for each line. */ + CalcT60DampingCoeffs(length, lfDecayTime, mfDecayTime, hfDecayTime, + lf0norm, hf0norm, &Late->T60[i]); + } +} - // Attenuate the cyclical line coefficients by the mixing coefficient - // (x). - State->Late.Coeff[index] *= xMix; +/* Creates a transform matrix given a reverb vector. The vector pans the reverb + * reflections toward the given direction, using its magnitude (up to 1) as a + * focal strength. This function results in a B-Format transformation matrix + * that spatially focuses the signal in the desired direction. + */ +static aluMatrixf GetTransformFromVector(const ALfloat *vec) +{ + aluMatrixf focus; + ALfloat norm[3]; + ALfloat mag; + + /* Normalize the panning vector according to the N3D scale, which has an + * extra sqrt(3) term on the directional components. Converting from OpenAL + * to B-Format also requires negating X (ACN 1) and Z (ACN 3). Note however + * that the reverb panning vectors use left-handed coordinates, unlike the + * rest of OpenAL which use right-handed. This is fixed by negating Z, + * which cancels out with the B-Format Z negation. + */ + mag = sqrtf(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]); + if(mag > 1.0f) + { + norm[0] = vec[0] / mag * -SQRTF_3; + norm[1] = vec[1] / mag * SQRTF_3; + norm[2] = vec[2] / mag * SQRTF_3; + mag = 1.0f; } + else + { + /* If the magnitude is less than or equal to 1, just apply the sqrt(3) + * term. There's no need to renormalize the magnitude since it would + * just be reapplied in the matrix. + */ + norm[0] = vec[0] * -SQRTF_3; + norm[1] = vec[1] * SQRTF_3; + norm[2] = vec[2] * SQRTF_3; + } + + aluMatrixfSet(&focus, + 1.0f, 0.0f, 0.0f, 0.0f, + norm[0], 1.0f-mag, 0.0f, 0.0f, + norm[1], 0.0f, 1.0f-mag, 0.0f, + norm[2], 0.0f, 0.0f, 1.0f-mag + ); + + return focus; } -// Update the echo gain, line offset, line coefficients, and mixing -// coefficients. -static ALvoid UpdateEchoLine(ALfloat reverbGain, ALfloat lateGain, ALfloat echoTime, ALfloat decayTime, ALfloat diffusion, ALfloat echoDepth, ALfloat hfRatio, ALfloat cw, ALuint frequency, ALreverbState *State) +/* Update the early and late 3D panning gains. */ +static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, const ALfloat earlyGain, const ALfloat lateGain, ReverbState *State) { - // Update the offset and coefficient for the echo delay line. - State->Echo.Offset = fastf2u(echoTime * frequency); + aluMatrixf transform, rot; + ALsizei i; + + STATIC_CAST(ALeffectState,State)->OutBuffer = Device->FOAOut.Buffer; + STATIC_CAST(ALeffectState,State)->OutChannels = Device->FOAOut.NumChannels; + + /* Note: _res is transposed. */ +#define MATRIX_MULT(_res, _m1, _m2) do { \ + int row, col; \ + for(col = 0;col < 4;col++) \ + { \ + for(row = 0;row < 4;row++) \ + _res.m[col][row] = _m1.m[row][0]*_m2.m[0][col] + _m1.m[row][1]*_m2.m[1][col] + \ + _m1.m[row][2]*_m2.m[2][col] + _m1.m[row][3]*_m2.m[3][col]; \ + } \ +} while(0) + /* Create a matrix that first converts A-Format to B-Format, then + * transforms the B-Format signal according to the panning vector. + */ + rot = GetTransformFromVector(ReflectionsPan); + MATRIX_MULT(transform, rot, A2B); + memset(&State->Early.PanGain, 0, sizeof(State->Early.PanGain)); + for(i = 0;i < MAX_EFFECT_CHANNELS;i++) + ComputePanGains(&Device->FOAOut, transform.m[i], earlyGain, + State->Early.PanGain[i]); + + rot = GetTransformFromVector(LateReverbPan); + MATRIX_MULT(transform, rot, A2B); + memset(&State->Late.PanGain, 0, sizeof(State->Late.PanGain)); + for(i = 0;i < MAX_EFFECT_CHANNELS;i++) + ComputePanGains(&Device->FOAOut, transform.m[i], lateGain, + State->Late.PanGain[i]); +#undef MATRIX_MULT +} - // Calculate the decay coefficient for the echo line. - State->Echo.Coeff = CalcDecayCoeff(echoTime, decayTime); +static void ReverbState_update(ReverbState *State, const ALCcontext *Context, const ALeffectslot *Slot, const ALeffectProps *props) +{ + const ALCdevice *Device = Context->Device; + const ALlistener *Listener = Context->Listener; + ALuint frequency = Device->Frequency; + ALfloat lf0norm, hf0norm, hfRatio; + ALfloat lfDecayTime, hfDecayTime; + ALfloat gain, gainlf, gainhf; + ALsizei i; + + /* Calculate the master filters */ + hf0norm = minf(props->Reverb.HFReference / frequency, 0.49f); + /* Restrict the filter gains from going below -60dB to keep the filter from + * killing most of the signal. + */ + gainhf = maxf(props->Reverb.GainHF, 0.001f); + BiquadFilter_setParams(&State->Filter[0].Lp, BiquadType_HighShelf, gainhf, hf0norm, + calc_rcpQ_from_slope(gainhf, 1.0f)); + lf0norm = minf(props->Reverb.LFReference / frequency, 0.49f); + gainlf = maxf(props->Reverb.GainLF, 0.001f); + BiquadFilter_setParams(&State->Filter[0].Hp, BiquadType_LowShelf, gainlf, lf0norm, + calc_rcpQ_from_slope(gainlf, 1.0f)); + for(i = 1;i < NUM_LINES;i++) + { + BiquadFilter_copyParams(&State->Filter[i].Lp, &State->Filter[0].Lp); + BiquadFilter_copyParams(&State->Filter[i].Hp, &State->Filter[0].Hp); + } - // Calculate the energy-based attenuation coefficient for the echo delay - // line. - State->Echo.DensityGain = CalcDensityGain(State->Echo.Coeff); + /* Update the main effect delay and associated taps. */ + UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, + props->Reverb.Density, props->Reverb.DecayTime, frequency, + State); - // Calculate the echo all-pass feed coefficient. - State->Echo.ApFeedCoeff = 0.5f * powf(diffusion, 2.0f); + /* Update the early lines. */ + UpdateEarlyLines(props->Reverb.Density, props->Reverb.Diffusion, + props->Reverb.DecayTime, frequency, &State->Early); - // Calculate the echo all-pass attenuation coefficient. - State->Echo.ApCoeff = CalcDecayCoeff(ECHO_ALLPASS_LENGTH, decayTime); + /* Get the mixing matrix coefficients. */ + CalcMatrixCoeffs(props->Reverb.Diffusion, &State->MixX, &State->MixY); - // Calculate the damping coefficient for each low-pass filter. - State->Echo.LpCoeff = CalcDampingCoeff(hfRatio, echoTime, decayTime, - State->Echo.Coeff, cw); + /* If the HF limit parameter is flagged, calculate an appropriate limit + * based on the air absorption parameter. + */ + hfRatio = props->Reverb.DecayHFRatio; + if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) + hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, + props->Reverb.DecayTime, Listener->Params.ReverbSpeedOfSound + ); + + /* Calculate the LF/HF decay times. */ + lfDecayTime = clampf(props->Reverb.DecayTime * props->Reverb.DecayLFRatio, + AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME); + hfDecayTime = clampf(props->Reverb.DecayTime * hfRatio, + AL_EAXREVERB_MIN_DECAY_TIME, AL_EAXREVERB_MAX_DECAY_TIME); + + /* Update the late lines. */ + UpdateLateLines(props->Reverb.Density, props->Reverb.Diffusion, + lfDecayTime, props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, + frequency, &State->Late + ); + + /* Update early and late 3D panning. */ + gain = props->Reverb.Gain * Slot->Params.Gain * ReverbBoost; + Update3DPanning(Device, props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, + props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, + State); + + /* Calculate the max update size from the smallest relevant delay. */ + State->MaxUpdate[1] = mini(MAX_UPDATE_SAMPLES, + mini(State->Early.Offset[0][1], State->Late.Offset[0][1]) + ); - /* Calculate the echo mixing coefficients. The first is applied to the - * echo itself. The second is used to attenuate the late reverb when - * echo depth is high and diffusion is low, so the echo is slightly - * stronger than the decorrelated echos in the reverb tail. + /* Determine if delay-line cross-fading is required. Density is essentially + * a master control for the feedback delays, so changes the offsets of many + * delay lines. */ - State->Echo.MixCoeff = reverbGain * lateGain * echoDepth; + if(State->Params.Density != props->Reverb.Density || + /* Diffusion and decay times influences the decay rate (gain) of the + * late reverb T60 filter. + */ + State->Params.Diffusion != props->Reverb.Diffusion || + State->Params.DecayTime != props->Reverb.DecayTime || + State->Params.HFDecayTime != hfDecayTime || + State->Params.LFDecayTime != lfDecayTime || + /* HF/LF References control the weighting used to calculate the density + * gain. + */ + State->Params.HFReference != props->Reverb.HFReference || + State->Params.LFReference != props->Reverb.LFReference) + State->FadeCount = 0; + State->Params.Density = props->Reverb.Density; + State->Params.Diffusion = props->Reverb.Diffusion; + State->Params.DecayTime = props->Reverb.DecayTime; + State->Params.HFDecayTime = hfDecayTime; + State->Params.LFDecayTime = lfDecayTime; + State->Params.HFReference = props->Reverb.HFReference; + State->Params.LFReference = props->Reverb.LFReference; } -// Update the early and late 3D panning gains. -static ALvoid Update3DPanning(const ALCdevice *Device, const ALfloat *ReflectionsPan, const ALfloat *LateReverbPan, ALfloat Gain, ALreverbState *State) + +/************************************** + * Effect Processing * + **************************************/ + +/* Basic delay line input/output routines. */ +static inline ALfloat DelayLineOut(const DelayLineI *Delay, const ALsizei offset, const ALsizei c) { - static const ALfloat EarlyPanAngles[4] = { - DEG2RAD(0.0f), DEG2RAD(-90.0f), DEG2RAD(90.0f), DEG2RAD(180.0f) - }, LatePanAngles[4] = { - DEG2RAD(45.0f), DEG2RAD(-45.0f), DEG2RAD(135.0f), DEG2RAD(-135.0f) - }; - ALfloat length, ev, az; - ALuint i; + return Delay->Line[offset&Delay->Mask][c]; +} + +/* Cross-faded delay line output routine. Instead of interpolating the + * offsets, this interpolates (cross-fades) the outputs at each offset. + */ +static inline ALfloat FadedDelayLineOut(const DelayLineI *Delay, const ALsizei off0, + const ALsizei off1, const ALsizei c, + const ALfloat sc0, const ALfloat sc1) +{ + return Delay->Line[off0&Delay->Mask][c]*sc0 + + Delay->Line[off1&Delay->Mask][c]*sc1; +} + + +static inline void DelayLineIn(const DelayLineI *Delay, ALsizei offset, const ALsizei c, + const ALfloat *restrict in, ALsizei count) +{ + ALsizei i; + for(i = 0;i < count;i++) + Delay->Line[(offset++)&Delay->Mask][c] = *(in++); +} - length = sqrtf(ReflectionsPan[0]*ReflectionsPan[0] + ReflectionsPan[1]*ReflectionsPan[1] + ReflectionsPan[2]*ReflectionsPan[2]); - if(!(length > FLT_EPSILON)) +/* Applies a scattering matrix to the 4-line (vector) input. This is used + * for both the below vector all-pass model and to perform modal feed-back + * delay network (FDN) mixing. + * + * The matrix is derived from a skew-symmetric matrix to form a 4D rotation + * matrix with a single unitary rotational parameter: + * + * [ d, a, b, c ] 1 = a^2 + b^2 + c^2 + d^2 + * [ -a, d, c, -b ] + * [ -b, -c, d, a ] + * [ -c, b, -a, d ] + * + * The rotation is constructed from the effect's diffusion parameter, + * yielding: + * + * 1 = x^2 + 3 y^2 + * + * Where a, b, and c are the coefficient y with differing signs, and d is the + * coefficient x. The final matrix is thus: + * + * [ x, y, -y, y ] n = sqrt(matrix_order - 1) + * [ -y, x, y, y ] t = diffusion_parameter * atan(n) + * [ y, -y, x, y ] x = cos(t) + * [ -y, -y, -y, x ] y = sin(t) / n + * + * Any square orthogonal matrix with an order that is a power of two will + * work (where ^T is transpose, ^-1 is inverse): + * + * M^T = M^-1 + * + * Using that knowledge, finding an appropriate matrix can be accomplished + * naively by searching all combinations of: + * + * M = D + S - S^T + * + * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) + * whose combination of signs are being iterated. + */ +static inline void VectorPartialScatter(ALfloat *restrict out, const ALfloat *restrict in, + const ALfloat xCoeff, const ALfloat yCoeff) +{ + out[0] = xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]); + out[1] = xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]); + out[2] = xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]); + out[3] = xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ); +} +#define VectorScatterDelayIn(delay, o, in, xcoeff, ycoeff) \ + VectorPartialScatter((delay)->Line[(o)&(delay)->Mask], in, xcoeff, ycoeff) + +/* Utilizes the above, but reverses the input channels. */ +static inline void VectorScatterRevDelayIn(const DelayLineI *Delay, ALint offset, + const ALfloat xCoeff, const ALfloat yCoeff, + const ALfloat (*restrict in)[MAX_UPDATE_SAMPLES], + const ALsizei count) +{ + const DelayLineI delay = *Delay; + ALsizei i, j; + + for(i = 0;i < count;++i) { - for(i = 0;i < 4;i++) - ComputeAngleGains(Device, EarlyPanAngles[i], 0.0f, Gain, State->Early.PanGain[i]); + ALfloat f[NUM_LINES]; + for(j = 0;j < NUM_LINES;j++) + f[NUM_LINES-1-j] = in[j][i]; + + VectorScatterDelayIn(&delay, offset++, f, xCoeff, yCoeff); } - else +} + +/* This applies a Gerzon multiple-in/multiple-out (MIMO) vector all-pass + * filter to the 4-line input. + * + * It works by vectorizing a regular all-pass filter and replacing the delay + * element with a scattering matrix (like the one above) and a diagonal + * matrix of delay elements. + * + * Two static specializations are used for transitional (cross-faded) delay + * line processing and non-transitional processing. + */ +static void VectorAllpass_Unfaded(ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES], ALsizei offset, + const ALfloat xCoeff, const ALfloat yCoeff, ALsizei todo, + VecAllpass *Vap) +{ + const DelayLineI delay = Vap->Delay; + const ALfloat feedCoeff = Vap->Coeff; + ALsizei vap_offset[NUM_LINES]; + ALsizei i, j; + + ASSUME(todo > 0); + + for(j = 0;j < NUM_LINES;j++) + vap_offset[j] = offset-Vap->Offset[j][0]; + for(i = 0;i < todo;i++) { - ev = asinf(clampf(ReflectionsPan[1]/length, -1.0f, 1.0f)); - az = atan2f(ReflectionsPan[0], ReflectionsPan[2]); + ALfloat f[NUM_LINES]; - length = minf(length, 1.0f); - for(i = 0;i < 4;i++) + for(j = 0;j < NUM_LINES;j++) { - /* This is essentially just a lerp, but takes the shortest path - * with respect to circular wrapping. e.g. - * -135 -> +/-180 -> +135 - * instead of - * -135 -> 0 -> +135 */ - float offset, naz, nev; - naz = EarlyPanAngles[i] + (modff((az-EarlyPanAngles[i])*length/F_TAU + 1.5f, &offset)-0.5f)*F_TAU; - nev = (modff((ev )*length/F_TAU + 1.5f, &offset)-0.5f)*F_TAU; - ComputeAngleGains(Device, naz, nev, Gain, State->Early.PanGain[i]); + ALfloat input = samples[j][i]; + ALfloat out = DelayLineOut(&delay, vap_offset[j]++, j) - feedCoeff*input; + f[j] = input + feedCoeff*out; + + samples[j][i] = out; } + + VectorScatterDelayIn(&delay, offset, f, xCoeff, yCoeff); + ++offset; } +} +static void VectorAllpass_Faded(ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES], ALsizei offset, + const ALfloat xCoeff, const ALfloat yCoeff, ALfloat fade, + ALsizei todo, VecAllpass *Vap) +{ + const DelayLineI delay = Vap->Delay; + const ALfloat feedCoeff = Vap->Coeff; + ALsizei vap_offset[NUM_LINES][2]; + ALsizei i, j; + + ASSUME(todo > 0); - length = sqrtf(LateReverbPan[0]*LateReverbPan[0] + LateReverbPan[1]*LateReverbPan[1] + LateReverbPan[2]*LateReverbPan[2]); - if(!(length > FLT_EPSILON)) + fade *= 1.0f/FADE_SAMPLES; + for(j = 0;j < NUM_LINES;j++) { - for(i = 0;i < 4;i++) - ComputeAngleGains(Device, LatePanAngles[i], 0.0f, Gain, State->Late.PanGain[i]); + vap_offset[j][0] = offset-Vap->Offset[j][0]; + vap_offset[j][1] = offset-Vap->Offset[j][1]; } - else + for(i = 0;i < todo;i++) { - ev = asinf(clampf(LateReverbPan[1]/length, -1.0f, 1.0f)); - az = atan2f(LateReverbPan[0], LateReverbPan[2]); + ALfloat f[NUM_LINES]; - length = minf(length, 1.0f); - for(i = 0;i < 4;i++) + for(j = 0;j < NUM_LINES;j++) { - float offset, naz, nev; - naz = LatePanAngles[i] + (modff((az-LatePanAngles[i])*length/F_TAU + 1.5f, &offset)-0.5f)*F_TAU; - nev = (modff((ev )*length/F_TAU + 1.5f, &offset)-0.5f)*F_TAU; - ComputeAngleGains(Device, naz, nev, Gain, State->Late.PanGain[i]); + ALfloat input = samples[j][i]; + ALfloat out = + FadedDelayLineOut(&delay, vap_offset[j][0]++, vap_offset[j][1]++, j, + 1.0f-fade, fade + ) - feedCoeff*input; + f[j] = input + feedCoeff*out; + + samples[j][i] = out; } + fade += FadeStep; + + VectorScatterDelayIn(&delay, offset, f, xCoeff, yCoeff); + ++offset; } } -static ALvoid ALreverbState_update(ALreverbState *State, ALCdevice *Device, const ALeffectslot *Slot) +/* This generates early reflections. + * + * This is done by obtaining the primary reflections (those arriving from the + * same direction as the source) from the main delay line. These are + * attenuated and all-pass filtered (based on the diffusion parameter). + * + * The early lines are then fed in reverse (according to the approximately + * opposite spatial location of the A-Format lines) to create the secondary + * reflections (those arriving from the opposite direction as the source). + * + * The early response is then completed by combining the primary reflections + * with the delayed and attenuated output from the early lines. + * + * Finally, the early response is reversed, scattered (based on diffusion), + * and fed into the late reverb section of the main delay line. + * + * Two static specializations are used for transitional (cross-faded) delay + * line processing and non-transitional processing. + */ +static void EarlyReflection_Unfaded(ReverbState *State, ALsizei offset, const ALsizei todo, + ALfloat (*restrict out)[MAX_UPDATE_SAMPLES]) { - const ALeffectProps *props = &Slot->EffectProps; - ALuint frequency = Device->Frequency; - ALfloat lfscale, hfscale, hfRatio; - ALfloat gainlf, gainhf; - ALfloat cw, x, y; - - if(Slot->EffectType == AL_EFFECT_EAXREVERB && !EmulateEAXReverb) - State->IsEax = AL_TRUE; - else if(Slot->EffectType == AL_EFFECT_REVERB || EmulateEAXReverb) - State->IsEax = AL_FALSE; - - // Calculate the master filters - hfscale = props->Reverb.HFReference / frequency; - gainhf = maxf(props->Reverb.GainHF, 0.0001f); - ALfilterState_setParams(&State->LpFilter, ALfilterType_HighShelf, - gainhf, hfscale, calc_rcpQ_from_slope(gainhf, 0.75f)); - lfscale = props->Reverb.LFReference / frequency; - gainlf = maxf(props->Reverb.GainLF, 0.0001f); - ALfilterState_setParams(&State->HpFilter, ALfilterType_LowShelf, - gainlf, lfscale, calc_rcpQ_from_slope(gainlf, 0.75f)); - - // Update the modulator line. - UpdateModulator(props->Reverb.ModulationTime, props->Reverb.ModulationDepth, - frequency, State); - - // Update the initial effect delay. - UpdateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - frequency, State); + ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples; + const DelayLineI early_delay = State->Early.Delay; + const DelayLineI main_delay = State->Delay; + const ALfloat mixX = State->MixX; + const ALfloat mixY = State->MixY; + ALsizei late_feed_tap; + ALsizei i, j; + + ASSUME(todo > 0); + + /* First, load decorrelated samples from the main delay line as the primary + * reflections. + */ + for(j = 0;j < NUM_LINES;j++) + { + ALsizei early_delay_tap = offset - State->EarlyDelayTap[j][0]; + ALfloat coeff = State->EarlyDelayCoeff[j][0]; + for(i = 0;i < todo;i++) + temps[j][i] = DelayLineOut(&main_delay, early_delay_tap++, j) * coeff; + } - // Update the early lines. - UpdateEarlyLines(props->Reverb.Gain, props->Reverb.ReflectionsGain, - props->Reverb.LateReverbDelay, State); + /* Apply a vector all-pass, to help color the initial reflections based on + * the diffusion strength. + */ + VectorAllpass_Unfaded(temps, offset, mixX, mixY, todo, &State->Early.VecAp); - // Update the decorrelator. - UpdateDecorrelator(props->Reverb.Density, frequency, State); + /* Apply a delay and bounce to generate secondary reflections, combine with + * the primary reflections and write out the result for mixing. + */ + for(j = 0;j < NUM_LINES;j++) + { + ALint early_feedb_tap = offset - State->Early.Offset[j][0]; + ALfloat early_feedb_coeff = State->Early.Coeff[j][0]; - // Get the mixing matrix coefficients (x and y). - CalcMatrixCoeffs(props->Reverb.Diffusion, &x, &y); - // Then divide x into y to simplify the matrix calculation. - State->Late.MixCoeff = y / x; + for(i = 0;i < todo;i++) + out[j][i] = DelayLineOut(&early_delay, early_feedb_tap++, j)*early_feedb_coeff + + temps[j][i]; + } + for(j = 0;j < NUM_LINES;j++) + DelayLineIn(&early_delay, offset, NUM_LINES-1-j, temps[j], todo); - // If the HF limit parameter is flagged, calculate an appropriate limit - // based on the air absorption parameter. - hfRatio = props->Reverb.DecayHFRatio; - if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) - hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime); - - cw = cosf(F_TAU * hfscale); - // Update the late lines. - UpdateLateLines(props->Reverb.Gain, props->Reverb.LateReverbGain, x, - props->Reverb.Density, props->Reverb.DecayTime, - props->Reverb.Diffusion, props->Reverb.EchoDepth, - hfRatio, cw, frequency, State); - - // Update the echo line. - UpdateEchoLine(props->Reverb.Gain, props->Reverb.LateReverbGain, - props->Reverb.EchoTime, props->Reverb.DecayTime, - props->Reverb.Diffusion, props->Reverb.EchoDepth, - hfRatio, cw, frequency, State); - - // Update early and late 3D panning. - Update3DPanning(Device, props->Reverb.ReflectionsPan, - props->Reverb.LateReverbPan, - Slot->Gain * ReverbBoost, State); + /* Also write the result back to the main delay line for the late reverb + * stage to pick up at the appropriate time, appplying a scatter and + * bounce to improve the initial diffusion in the late reverb. + */ + late_feed_tap = offset - State->LateFeedTap; + VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, out, todo); } +static void EarlyReflection_Faded(ReverbState *State, ALsizei offset, const ALsizei todo, + const ALfloat fade, ALfloat (*restrict out)[MAX_UPDATE_SAMPLES]) +{ + ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples; + const DelayLineI early_delay = State->Early.Delay; + const DelayLineI main_delay = State->Delay; + const ALfloat mixX = State->MixX; + const ALfloat mixY = State->MixY; + ALsizei late_feed_tap; + ALsizei i, j; + ASSUME(todo > 0); -static ALvoid ALreverbState_Destruct(ALreverbState *State) -{ - free(State->SampleBuffer); - State->SampleBuffer = NULL; -} + for(j = 0;j < NUM_LINES;j++) + { + ALsizei early_delay_tap0 = offset - State->EarlyDelayTap[j][0]; + ALsizei early_delay_tap1 = offset - State->EarlyDelayTap[j][1]; + ALfloat oldCoeff = State->EarlyDelayCoeff[j][0]; + ALfloat oldCoeffStep = -oldCoeff / FADE_SAMPLES; + ALfloat newCoeffStep = State->EarlyDelayCoeff[j][1] / FADE_SAMPLES; + ALfloat fadeCount = fade; + + for(i = 0;i < todo;i++) + { + const ALfloat fade0 = oldCoeff + oldCoeffStep*fadeCount; + const ALfloat fade1 = newCoeffStep*fadeCount; + temps[j][i] = FadedDelayLineOut(&main_delay, + early_delay_tap0++, early_delay_tap1++, j, fade0, fade1 + ); + fadeCount += 1.0f; + } + } -DECLARE_DEFAULT_ALLOCATORS(ALreverbState) + VectorAllpass_Faded(temps, offset, mixX, mixY, fade, todo, &State->Early.VecAp); -DEFINE_ALEFFECTSTATE_VTABLE(ALreverbState); + for(j = 0;j < NUM_LINES;j++) + { + ALint feedb_tap0 = offset - State->Early.Offset[j][0]; + ALint feedb_tap1 = offset - State->Early.Offset[j][1]; + ALfloat feedb_oldCoeff = State->Early.Coeff[j][0]; + ALfloat feedb_oldCoeffStep = -feedb_oldCoeff / FADE_SAMPLES; + ALfloat feedb_newCoeffStep = State->Early.Coeff[j][1] / FADE_SAMPLES; + ALfloat fadeCount = fade; + + for(i = 0;i < todo;i++) + { + const ALfloat fade0 = feedb_oldCoeff + feedb_oldCoeffStep*fadeCount; + const ALfloat fade1 = feedb_newCoeffStep*fadeCount; + out[j][i] = FadedDelayLineOut(&early_delay, + feedb_tap0++, feedb_tap1++, j, fade0, fade1 + ) + temps[j][i]; + fadeCount += 1.0f; + } + } + for(j = 0;j < NUM_LINES;j++) + DelayLineIn(&early_delay, offset, NUM_LINES-1-j, temps[j], todo); + late_feed_tap = offset - State->LateFeedTap; + VectorScatterRevDelayIn(&main_delay, late_feed_tap, mixX, mixY, out, todo); +} -typedef struct ALreverbStateFactory { - DERIVE_FROM_TYPE(ALeffectStateFactory); -} ALreverbStateFactory; +/* Applies the two T60 damping filter sections. */ +static inline void LateT60Filter(ALfloat *restrict samples, const ALsizei todo, T60Filter *filter) +{ + ALfloat temp[MAX_UPDATE_SAMPLES]; + BiquadFilter_process(&filter->HFFilter, temp, samples, todo); + BiquadFilter_process(&filter->LFFilter, samples, temp, todo); +} -static ALeffectState *ALreverbStateFactory_create(ALreverbStateFactory* UNUSED(factory)) +/* This generates the reverb tail using a modified feed-back delay network + * (FDN). + * + * Results from the early reflections are mixed with the output from the late + * delay lines. + * + * The late response is then completed by T60 and all-pass filtering the mix. + * + * Finally, the lines are reversed (so they feed their opposite directions) + * and scattered with the FDN matrix before re-feeding the delay lines. + * + * Two variations are made, one for for transitional (cross-faded) delay line + * processing and one for non-transitional processing. + */ +static void LateReverb_Unfaded(ReverbState *State, ALsizei offset, const ALsizei todo, + ALfloat (*restrict out)[MAX_UPDATE_SAMPLES]) { - ALreverbState *state; - ALuint index, l; + ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples; + const DelayLineI late_delay = State->Late.Delay; + const DelayLineI main_delay = State->Delay; + const ALfloat mixX = State->MixX; + const ALfloat mixY = State->MixY; + ALsizei i, j; - state = ALreverbState_New(sizeof(*state)); - if(!state) return NULL; - SET_VTABLE2(ALreverbState, ALeffectState, state); + ASSUME(todo > 0); - state->TotalSamples = 0; - state->SampleBuffer = NULL; + /* First, load decorrelated samples from the main and feedback delay lines. + * Filter the signal to apply its frequency-dependent decay. + */ + for(j = 0;j < NUM_LINES;j++) + { + ALsizei late_delay_tap = offset - State->LateDelayTap[j][0]; + ALsizei late_feedb_tap = offset - State->Late.Offset[j][0]; + ALfloat midGain = State->Late.T60[j].MidGain[0]; + const ALfloat densityGain = State->Late.DensityGain[0] * midGain; + for(i = 0;i < todo;i++) + temps[j][i] = DelayLineOut(&main_delay, late_delay_tap++, j)*densityGain + + DelayLineOut(&late_delay, late_feedb_tap++, j)*midGain; + LateT60Filter(temps[j], todo, &State->Late.T60[j]); + } - ALfilterState_clear(&state->LpFilter); - ALfilterState_clear(&state->HpFilter); + /* Apply a vector all-pass to improve micro-surface diffusion, and write + * out the results for mixing. + */ + VectorAllpass_Unfaded(temps, offset, mixX, mixY, todo, &State->Late.VecAp); - state->Mod.Delay.Mask = 0; - state->Mod.Delay.Line = NULL; - state->Mod.Index = 0; - state->Mod.Range = 1; - state->Mod.Depth = 0.0f; - state->Mod.Coeff = 0.0f; - state->Mod.Filter = 0.0f; + for(j = 0;j < NUM_LINES;j++) + memcpy(out[j], temps[j], todo*sizeof(ALfloat)); - state->Delay.Mask = 0; - state->Delay.Line = NULL; - state->DelayTap[0] = 0; - state->DelayTap[1] = 0; + /* Finally, scatter and bounce the results to refeed the feedback buffer. */ + VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, out, todo); +} +static void LateReverb_Faded(ReverbState *State, ALsizei offset, const ALsizei todo, + const ALfloat fade, ALfloat (*restrict out)[MAX_UPDATE_SAMPLES]) +{ + ALfloat (*restrict temps)[MAX_UPDATE_SAMPLES] = State->TempSamples; + const DelayLineI late_delay = State->Late.Delay; + const DelayLineI main_delay = State->Delay; + const ALfloat mixX = State->MixX; + const ALfloat mixY = State->MixY; + ALsizei i, j; - state->Early.Gain = 0.0f; - for(index = 0;index < 4;index++) - { - state->Early.Coeff[index] = 0.0f; - state->Early.Delay[index].Mask = 0; - state->Early.Delay[index].Line = NULL; - state->Early.Offset[index] = 0; - } + ASSUME(todo > 0); - state->Decorrelator.Mask = 0; - state->Decorrelator.Line = NULL; - state->DecoTap[0] = 0; - state->DecoTap[1] = 0; - state->DecoTap[2] = 0; - - state->Late.Gain = 0.0f; - state->Late.DensityGain = 0.0f; - state->Late.ApFeedCoeff = 0.0f; - state->Late.MixCoeff = 0.0f; - for(index = 0;index < 4;index++) + for(j = 0;j < NUM_LINES;j++) { - state->Late.ApCoeff[index] = 0.0f; - state->Late.ApDelay[index].Mask = 0; - state->Late.ApDelay[index].Line = NULL; - state->Late.ApOffset[index] = 0; - - state->Late.Coeff[index] = 0.0f; - state->Late.Delay[index].Mask = 0; - state->Late.Delay[index].Line = NULL; - state->Late.Offset[index] = 0; - - state->Late.LpCoeff[index] = 0.0f; - state->Late.LpSample[index] = 0.0f; + const ALfloat oldMidGain = State->Late.T60[j].MidGain[0]; + const ALfloat midGain = State->Late.T60[j].MidGain[1]; + const ALfloat oldMidStep = -oldMidGain / FADE_SAMPLES; + const ALfloat midStep = midGain / FADE_SAMPLES; + const ALfloat oldDensityGain = State->Late.DensityGain[0] * oldMidGain; + const ALfloat densityGain = State->Late.DensityGain[1] * midGain; + const ALfloat oldDensityStep = -oldDensityGain / FADE_SAMPLES; + const ALfloat densityStep = densityGain / FADE_SAMPLES; + ALsizei late_delay_tap0 = offset - State->LateDelayTap[j][0]; + ALsizei late_delay_tap1 = offset - State->LateDelayTap[j][1]; + ALsizei late_feedb_tap0 = offset - State->Late.Offset[j][0]; + ALsizei late_feedb_tap1 = offset - State->Late.Offset[j][1]; + ALfloat fadeCount = fade; + + for(i = 0;i < todo;i++) + { + const ALfloat fade0 = oldDensityGain + oldDensityStep*fadeCount; + const ALfloat fade1 = densityStep*fadeCount; + const ALfloat gfade0 = oldMidGain + oldMidStep*fadeCount; + const ALfloat gfade1 = midStep*fadeCount; + temps[j][i] = + FadedDelayLineOut(&main_delay, late_delay_tap0++, late_delay_tap1++, j, + fade0, fade1) + + FadedDelayLineOut(&late_delay, late_feedb_tap0++, late_feedb_tap1++, j, + gfade0, gfade1); + fadeCount += 1.0f; + } + LateT60Filter(temps[j], todo, &State->Late.T60[j]); } - for(l = 0;l < 4;l++) + VectorAllpass_Faded(temps, offset, mixX, mixY, fade, todo, &State->Late.VecAp); + + for(j = 0;j < NUM_LINES;j++) + memcpy(out[j], temps[j], todo*sizeof(ALfloat)); + + VectorScatterRevDelayIn(&late_delay, offset, mixX, mixY, temps, todo); +} + +static ALvoid ReverbState_process(ReverbState *State, ALsizei SamplesToDo, const ALfloat (*restrict SamplesIn)[BUFFERSIZE], ALfloat (*restrict SamplesOut)[BUFFERSIZE], ALsizei NumChannels) +{ + ALfloat (*restrict afmt)[MAX_UPDATE_SAMPLES] = State->TempSamples; + ALfloat (*restrict samples)[MAX_UPDATE_SAMPLES] = State->MixSamples; + ALsizei fadeCount = State->FadeCount; + ALsizei offset = State->Offset; + ALsizei base, c; + + /* Process reverb for these samples. */ + for(base = 0;base < SamplesToDo;) { - for(index = 0;index < MAX_OUTPUT_CHANNELS;index++) + ALsizei todo = SamplesToDo - base; + /* If cross-fading, don't do more samples than there are to fade. */ + if(FADE_SAMPLES-fadeCount > 0) { - state->Early.PanGain[l][index] = 0.0f; - state->Late.PanGain[l][index] = 0.0f; + todo = mini(todo, FADE_SAMPLES-fadeCount); + todo = mini(todo, State->MaxUpdate[0]); } + todo = mini(todo, State->MaxUpdate[1]); + /* If this is not the final update, ensure the update size is a + * multiple of 4 for the SIMD mixers. + */ + if(todo < SamplesToDo-base) + todo &= ~3; + + /* Convert B-Format to A-Format for processing. */ + memset(afmt, 0, sizeof(*afmt)*NUM_LINES); + for(c = 0;c < NUM_LINES;c++) + MixRowSamples(afmt[c], B2A.m[c], + SamplesIn, MAX_EFFECT_CHANNELS, base, todo + ); + + /* Process the samples for reverb. */ + for(c = 0;c < NUM_LINES;c++) + { + /* Band-pass the incoming samples. */ + BiquadFilter_process(&State->Filter[c].Lp, samples[0], afmt[c], todo); + BiquadFilter_process(&State->Filter[c].Hp, samples[1], samples[0], todo); + + /* Feed the initial delay line. */ + DelayLineIn(&State->Delay, offset, c, samples[1], todo); + } + + if(UNLIKELY(fadeCount < FADE_SAMPLES)) + { + ALfloat fade = (ALfloat)fadeCount; + + /* Generate early reflections. */ + EarlyReflection_Faded(State, offset, todo, fade, samples); + /* Mix the A-Format results to output, implicitly converting back + * to B-Format. + */ + for(c = 0;c < NUM_LINES;c++) + MixSamples(samples[c], NumChannels, SamplesOut, + State->Early.CurrentGain[c], State->Early.PanGain[c], + SamplesToDo-base, base, todo + ); + + /* Generate and mix late reverb. */ + LateReverb_Faded(State, offset, todo, fade, samples); + for(c = 0;c < NUM_LINES;c++) + MixSamples(samples[c], NumChannels, SamplesOut, + State->Late.CurrentGain[c], State->Late.PanGain[c], + SamplesToDo-base, base, todo + ); + + /* Step fading forward. */ + fadeCount += todo; + if(LIKELY(fadeCount >= FADE_SAMPLES)) + { + /* Update the cross-fading delay line taps. */ + fadeCount = FADE_SAMPLES; + for(c = 0;c < NUM_LINES;c++) + { + State->EarlyDelayTap[c][0] = State->EarlyDelayTap[c][1]; + State->EarlyDelayCoeff[c][0] = State->EarlyDelayCoeff[c][1]; + State->Early.VecAp.Offset[c][0] = State->Early.VecAp.Offset[c][1]; + State->Early.Offset[c][0] = State->Early.Offset[c][1]; + State->Early.Coeff[c][0] = State->Early.Coeff[c][1]; + State->LateDelayTap[c][0] = State->LateDelayTap[c][1]; + State->Late.VecAp.Offset[c][0] = State->Late.VecAp.Offset[c][1]; + State->Late.Offset[c][0] = State->Late.Offset[c][1]; + State->Late.T60[c].MidGain[0] = State->Late.T60[c].MidGain[1]; + } + State->Late.DensityGain[0] = State->Late.DensityGain[1]; + State->MaxUpdate[0] = State->MaxUpdate[1]; + } + } + else + { + /* Generate and mix early reflections. */ + EarlyReflection_Unfaded(State, offset, todo, samples); + for(c = 0;c < NUM_LINES;c++) + MixSamples(samples[c], NumChannels, SamplesOut, + State->Early.CurrentGain[c], State->Early.PanGain[c], + SamplesToDo-base, base, todo + ); + + /* Generate and mix late reverb. */ + LateReverb_Unfaded(State, offset, todo, samples); + for(c = 0;c < NUM_LINES;c++) + MixSamples(samples[c], NumChannels, SamplesOut, + State->Late.CurrentGain[c], State->Late.PanGain[c], + SamplesToDo-base, base, todo + ); + } + + /* Step all delays forward. */ + offset += todo; + + base += todo; } + State->Offset = offset; + State->FadeCount = fadeCount; +} - state->Echo.DensityGain = 0.0f; - state->Echo.Delay.Mask = 0; - state->Echo.Delay.Line = NULL; - state->Echo.ApDelay.Mask = 0; - state->Echo.ApDelay.Line = NULL; - state->Echo.Coeff = 0.0f; - state->Echo.ApFeedCoeff = 0.0f; - state->Echo.ApCoeff = 0.0f; - state->Echo.Offset = 0; - state->Echo.ApOffset = 0; - state->Echo.LpCoeff = 0.0f; - state->Echo.LpSample = 0.0f; - state->Echo.MixCoeff = 0.0f; - state->Offset = 0; +typedef struct ReverbStateFactory { + DERIVE_FROM_TYPE(EffectStateFactory); +} ReverbStateFactory; - state->Gain = state->Late.PanGain; +static ALeffectState *ReverbStateFactory_create(ReverbStateFactory* UNUSED(factory)) +{ + ReverbState *state; + + NEW_OBJ0(state, ReverbState)(); + if(!state) return NULL; return STATIC_CAST(ALeffectState, state); } -DEFINE_ALEFFECTSTATEFACTORY_VTABLE(ALreverbStateFactory); +DEFINE_EFFECTSTATEFACTORY_VTABLE(ReverbStateFactory); -ALeffectStateFactory *ALreverbStateFactory_getFactory(void) +EffectStateFactory *ReverbStateFactory_getFactory(void) { - static ALreverbStateFactory ReverbFactory = { { GET_VTABLE2(ALreverbStateFactory, ALeffectStateFactory) } }; + static ReverbStateFactory ReverbFactory = { { GET_VTABLE2(ReverbStateFactory, EffectStateFactory) } }; - return STATIC_CAST(ALeffectStateFactory, &ReverbFactory); + return STATIC_CAST(EffectStateFactory, &ReverbFactory); } @@ -1311,18 +1614,17 @@ void ALeaxreverb_setParami(ALeffect *effect, ALCcontext *context, ALenum param, { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hflimit out of range"); props->Reverb.DecayHFLimit = val; break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param); } } void ALeaxreverb_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals) -{ - ALeaxreverb_setParami(effect, context, param, vals[0]); -} +{ ALeaxreverb_setParami(effect, context, param, vals[0]); } void ALeaxreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val) { ALeffectProps *props = &effect->Props; @@ -1330,126 +1632,127 @@ void ALeaxreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb density out of range"); props->Reverb.Density = val; break; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb diffusion out of range"); props->Reverb.Diffusion = val; break; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gain out of range"); props->Reverb.Gain = val; break; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainhf out of range"); props->Reverb.GainHF = val; break; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb gainlf out of range"); props->Reverb.GainLF = val; break; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay time out of range"); props->Reverb.DecayTime = val; break; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay hfratio out of range"); props->Reverb.DecayHFRatio = val; break; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb decay lfratio out of range"); props->Reverb.DecayLFRatio = val; break; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections gain out of range"); props->Reverb.ReflectionsGain = val; break; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections delay out of range"); props->Reverb.ReflectionsDelay = val; break; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb gain out of range"); props->Reverb.LateReverbGain = val; break; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb delay out of range"); props->Reverb.LateReverbDelay = val; break; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb air absorption gainhf out of range"); props->Reverb.AirAbsorptionGainHF = val; break; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo time out of range"); props->Reverb.EchoTime = val; break; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb echo depth out of range"); props->Reverb.EchoDepth = val; break; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation time out of range"); props->Reverb.ModulationTime = val; break; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb modulation depth out of range"); props->Reverb.ModulationDepth = val; break; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb hfreference out of range"); props->Reverb.HFReference = val; break; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb lfreference out of range"); props->Reverb.LFReference = val; break; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb room rolloff factor out of range"); props->Reverb.RoomRolloffFactor = val; break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", + param); } } void ALeaxreverb_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals) @@ -1459,21 +1762,17 @@ void ALeaxreverb_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, { case AL_EAXREVERB_REFLECTIONS_PAN: if(!(isfinite(vals[0]) && isfinite(vals[1]) && isfinite(vals[2]))) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); - LockContext(context); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb reflections pan out of range"); props->Reverb.ReflectionsPan[0] = vals[0]; props->Reverb.ReflectionsPan[1] = vals[1]; props->Reverb.ReflectionsPan[2] = vals[2]; - UnlockContext(context); break; case AL_EAXREVERB_LATE_REVERB_PAN: if(!(isfinite(vals[0]) && isfinite(vals[1]) && isfinite(vals[2]))) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); - LockContext(context); + SETERR_RETURN(context, AL_INVALID_VALUE,, "EAX Reverb late reverb pan out of range"); props->Reverb.LateReverbPan[0] = vals[0]; props->Reverb.LateReverbPan[1] = vals[1]; props->Reverb.LateReverbPan[2] = vals[2]; - UnlockContext(context); break; default: @@ -1492,13 +1791,12 @@ void ALeaxreverb_getParami(const ALeffect *effect, ALCcontext *context, ALenum p break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param); } } void ALeaxreverb_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals) -{ - ALeaxreverb_getParami(effect, context, param, vals); -} +{ ALeaxreverb_getParami(effect, context, param, vals); } void ALeaxreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val) { const ALeffectProps *props = &effect->Props; @@ -1585,7 +1883,8 @@ void ALeaxreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum p break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", + param); } } void ALeaxreverb_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals) @@ -1594,18 +1893,14 @@ void ALeaxreverb_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: - LockContext(context); vals[0] = props->Reverb.ReflectionsPan[0]; vals[1] = props->Reverb.ReflectionsPan[1]; vals[2] = props->Reverb.ReflectionsPan[2]; - UnlockContext(context); break; case AL_EAXREVERB_LATE_REVERB_PAN: - LockContext(context); vals[0] = props->Reverb.LateReverbPan[0]; vals[1] = props->Reverb.LateReverbPan[1]; vals[2] = props->Reverb.LateReverbPan[2]; - UnlockContext(context); break; default: @@ -1623,18 +1918,16 @@ void ALreverb_setParami(ALeffect *effect, ALCcontext *context, ALenum param, ALi { case AL_REVERB_DECAY_HFLIMIT: if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hflimit out of range"); props->Reverb.DecayHFLimit = val; break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); } } void ALreverb_setParamiv(ALeffect *effect, ALCcontext *context, ALenum param, const ALint *vals) -{ - ALreverb_setParami(effect, context, param, vals[0]); -} +{ ALreverb_setParami(effect, context, param, vals[0]); } void ALreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALfloat val) { ALeffectProps *props = &effect->Props; @@ -1642,84 +1935,82 @@ void ALreverb_setParamf(ALeffect *effect, ALCcontext *context, ALenum param, ALf { case AL_REVERB_DENSITY: if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb density out of range"); props->Reverb.Density = val; break; case AL_REVERB_DIFFUSION: if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb diffusion out of range"); props->Reverb.Diffusion = val; break; case AL_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gain out of range"); props->Reverb.Gain = val; break; case AL_REVERB_GAINHF: if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb gainhf out of range"); props->Reverb.GainHF = val; break; case AL_REVERB_DECAY_TIME: if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay time out of range"); props->Reverb.DecayTime = val; break; case AL_REVERB_DECAY_HFRATIO: if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb decay hfratio out of range"); props->Reverb.DecayHFRatio = val; break; case AL_REVERB_REFLECTIONS_GAIN: if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections gain out of range"); props->Reverb.ReflectionsGain = val; break; case AL_REVERB_REFLECTIONS_DELAY: if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb reflections delay out of range"); props->Reverb.ReflectionsDelay = val; break; case AL_REVERB_LATE_REVERB_GAIN: if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb gain out of range"); props->Reverb.LateReverbGain = val; break; case AL_REVERB_LATE_REVERB_DELAY: if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb late reverb delay out of range"); props->Reverb.LateReverbDelay = val; break; case AL_REVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb air absorption gainhf out of range"); props->Reverb.AirAbsorptionGainHF = val; break; case AL_REVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - SET_ERROR_AND_RETURN(context, AL_INVALID_VALUE); + SETERR_RETURN(context, AL_INVALID_VALUE,, "Reverb room rolloff factor out of range"); props->Reverb.RoomRolloffFactor = val; break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); } } void ALreverb_setParamfv(ALeffect *effect, ALCcontext *context, ALenum param, const ALfloat *vals) -{ - ALreverb_setParamf(effect, context, param, vals[0]); -} +{ ALreverb_setParamf(effect, context, param, vals[0]); } void ALreverb_getParami(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *val) { @@ -1731,13 +2022,11 @@ void ALreverb_getParami(const ALeffect *effect, ALCcontext *context, ALenum para break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param); } } void ALreverb_getParamiv(const ALeffect *effect, ALCcontext *context, ALenum param, ALint *vals) -{ - ALreverb_getParami(effect, context, param, vals); -} +{ ALreverb_getParami(effect, context, param, vals); } void ALreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *val) { const ALeffectProps *props = &effect->Props; @@ -1792,12 +2081,10 @@ void ALreverb_getParamf(const ALeffect *effect, ALCcontext *context, ALenum para break; default: - SET_ERROR_AND_RETURN(context, AL_INVALID_ENUM); + alSetError(context, AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param); } } void ALreverb_getParamfv(const ALeffect *effect, ALCcontext *context, ALenum param, ALfloat *vals) -{ - ALreverb_getParamf(effect, context, param, vals); -} +{ ALreverb_getParamf(effect, context, param, vals); } DEFINE_ALEFFECT_VTABLE(ALreverb); |