#ifndef _ALU_H_ #define _ALU_H_ #include <array> #include <atomic> #include <cmath> #include <cstddef> #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alBuffer.h" #include "alcmain.h" #include "almalloc.h" #include "alspan.h" #include "ambidefs.h" #include "filters/biquad.h" #include "filters/nfc.h" #include "filters/splitter.h" #include "hrtf.h" #include "logging.h" struct ALbufferlistitem; struct ALeffectslot; struct BSincTable; enum class DistanceModel; #define MAX_PITCH 255 #define MAX_SENDS 16 #define DITHER_RNG_SEED 22222 enum SpatializeMode { SpatializeOff = AL_FALSE, SpatializeOn = AL_TRUE, SpatializeAuto = AL_AUTO_SOFT }; enum Resampler { PointResampler, LinearResampler, FIR4Resampler, BSinc12Resampler, BSinc24Resampler, ResamplerMax = BSinc24Resampler }; extern Resampler ResamplerDefault; /* The number of distinct scale and phase intervals within the bsinc filter * table. */ #define BSINC_SCALE_BITS 4 #define BSINC_SCALE_COUNT (1<<BSINC_SCALE_BITS) #define BSINC_PHASE_BITS 4 #define BSINC_PHASE_COUNT (1<<BSINC_PHASE_BITS) /* Interpolator state. Kind of a misnomer since the interpolator itself is * stateless. This just keeps it from having to recompute scale-related * mappings for every sample. */ struct BsincState { ALfloat sf; /* Scale interpolation factor. */ ALsizei m; /* Coefficient count. */ ALsizei l; /* Left coefficient offset. */ /* Filter coefficients, followed by the scale, phase, and scale-phase * delta coefficients. Starting at phase index 0, each subsequent phase * index follows contiguously. */ const ALfloat *filter; }; union InterpState { BsincState bsinc; }; using ResamplerFunc = const ALfloat*(*)(const InterpState *state, const ALfloat *RESTRICT src, ALsizei frac, ALint increment, ALfloat *RESTRICT dst, ALsizei dstlen); void BsincPrepare(const ALuint increment, BsincState *state, const BSincTable *table); extern const BSincTable bsinc12; extern const BSincTable bsinc24; enum { AF_None = 0, AF_LowPass = 1, AF_HighPass = 2, AF_BandPass = AF_LowPass | AF_HighPass }; struct MixHrtfFilter { const HrirArray<ALfloat> *Coeffs; ALsizei Delay[2]; ALfloat Gain; ALfloat GainStep; }; struct DirectParams { BiquadFilter LowPass; BiquadFilter HighPass; NfcFilter NFCtrlFilter; struct { HrtfFilter Old; HrtfFilter Target; HrtfState State; } Hrtf; struct { ALfloat Current[MAX_OUTPUT_CHANNELS]; ALfloat Target[MAX_OUTPUT_CHANNELS]; } Gains; }; struct SendParams { BiquadFilter LowPass; BiquadFilter HighPass; struct { ALfloat Current[MAX_OUTPUT_CHANNELS]; ALfloat Target[MAX_OUTPUT_CHANNELS]; } Gains; }; struct ALvoicePropsBase { ALfloat Pitch; ALfloat Gain; ALfloat OuterGain; ALfloat MinGain; ALfloat MaxGain; ALfloat InnerAngle; ALfloat OuterAngle; ALfloat RefDistance; ALfloat MaxDistance; ALfloat RolloffFactor; std::array<ALfloat,3> Position; std::array<ALfloat,3> Velocity; std::array<ALfloat,3> Direction; std::array<ALfloat,3> OrientAt; std::array<ALfloat,3> OrientUp; ALboolean HeadRelative; DistanceModel mDistanceModel; Resampler mResampler; ALboolean DirectChannels; SpatializeMode mSpatializeMode; ALboolean DryGainHFAuto; ALboolean WetGainAuto; ALboolean WetGainHFAuto; ALfloat OuterGainHF; ALfloat AirAbsorptionFactor; ALfloat RoomRolloffFactor; ALfloat DopplerFactor; std::array<ALfloat,2> StereoPan; ALfloat Radius; /** Direct filter and auxiliary send info. */ struct { ALfloat Gain; ALfloat GainHF; ALfloat HFReference; ALfloat GainLF; ALfloat LFReference; } Direct; struct SendData { ALeffectslot *Slot; ALfloat Gain; ALfloat GainHF; ALfloat HFReference; ALfloat GainLF; ALfloat LFReference; } Send[MAX_SENDS]; }; struct ALvoiceProps : public ALvoicePropsBase { std::atomic<ALvoiceProps*> next{nullptr}; DEF_NEWDEL(ALvoiceProps) }; #define VOICE_IS_STATIC (1u<<0) #define VOICE_IS_FADING (1u<<1) /* Fading sources use gain stepping for smooth transitions. */ #define VOICE_IS_AMBISONIC (1u<<2) /* Voice needs HF scaling for ambisonic upsampling. */ #define VOICE_HAS_HRTF (1u<<3) #define VOICE_HAS_NFC (1u<<4) struct ALvoice { enum State { Stopped = 0, Playing = 1, Stopping = 2 }; std::atomic<ALvoiceProps*> mUpdate{nullptr}; std::atomic<ALuint> mSourceID{0u}; std::atomic<State> mPlayState{Stopped}; ALvoicePropsBase mProps; /** * Source offset in samples, relative to the currently playing buffer, NOT * the whole queue. */ std::atomic<ALuint> mPosition; /** Fractional (fixed-point) offset to the next sample. */ std::atomic<ALsizei> mPositionFrac; /* Current buffer queue item being played. */ std::atomic<ALbufferlistitem*> mCurrentBuffer; /* Buffer queue item to loop to at end of queue (will be NULL for non- * looping voices). */ std::atomic<ALbufferlistitem*> mLoopBuffer; /* Properties for the attached buffer(s). */ FmtChannels mFmtChannels; ALuint mFrequency; ALsizei mNumChannels; ALsizei mSampleSize; /** Current target parameters used for mixing. */ ALint mStep; ResamplerFunc mResampler; InterpState mResampleState; ALuint mFlags; struct DirectData { int FilterType; al::span<FloatBufferLine> Buffer; }; DirectData mDirect; struct SendData { int FilterType; al::span<FloatBufferLine> Buffer; }; std::array<SendData,MAX_SENDS> mSend; struct ChannelData { alignas(16) std::array<ALfloat,MAX_RESAMPLE_PADDING*2> mPrevSamples; ALfloat mAmbiScale; BandSplitter mAmbiSplitter; DirectParams mDryParams; std::array<SendParams,MAX_SENDS> mWetParams; }; std::array<ChannelData,MAX_INPUT_CHANNELS> mChans; ALvoice() = default; ALvoice(const ALvoice&) = delete; ~ALvoice() { delete mUpdate.exchange(nullptr, std::memory_order_acq_rel); } ALvoice& operator=(const ALvoice&) = delete; ALvoice& operator=(ALvoice&& rhs) noexcept { ALvoiceProps *old_update{mUpdate.load(std::memory_order_relaxed)}; mUpdate.store(rhs.mUpdate.exchange(old_update, std::memory_order_relaxed), std::memory_order_relaxed); mSourceID.store(rhs.mSourceID.load(std::memory_order_relaxed), std::memory_order_relaxed); mPlayState.store(rhs.mPlayState.load(std::memory_order_relaxed), std::memory_order_relaxed); mProps = rhs.mProps; mPosition.store(rhs.mPosition.load(std::memory_order_relaxed), std::memory_order_relaxed); mPositionFrac.store(rhs.mPositionFrac.load(std::memory_order_relaxed), std::memory_order_relaxed); mCurrentBuffer.store(rhs.mCurrentBuffer.load(std::memory_order_relaxed), std::memory_order_relaxed); mLoopBuffer.store(rhs.mLoopBuffer.load(std::memory_order_relaxed), std::memory_order_relaxed); mFmtChannels = rhs.mFmtChannels; mFrequency = rhs.mFrequency; mNumChannels = rhs.mNumChannels; mSampleSize = rhs.mSampleSize; mStep = rhs.mStep; mResampler = rhs.mResampler; mResampleState = rhs.mResampleState; mFlags = rhs.mFlags; mDirect = rhs.mDirect; mSend = rhs.mSend; mChans = rhs.mChans; return *this; } }; using MixerFunc = void(*)(const ALfloat *data, const al::span<FloatBufferLine> OutBuffer, ALfloat *CurrentGains, const ALfloat *TargetGains, const ALsizei Counter, const ALsizei OutPos, const ALsizei BufferSize); using RowMixerFunc = void(*)(FloatBufferLine &OutBuffer, const ALfloat *gains, const al::span<const FloatBufferLine> InSamples, const ALsizei InPos, const ALsizei BufferSize); using HrtfMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, MixHrtfFilter *hrtfparams, const ALsizei BufferSize); using HrtfMixerBlendFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const ALfloat *InSamples, float2 *AccumSamples, const ALsizei OutPos, const ALsizei IrSize, const HrtfFilter *oldparams, MixHrtfFilter *newparams, const ALsizei BufferSize); using HrtfDirectMixerFunc = void(*)(FloatBufferLine &LeftOut, FloatBufferLine &RightOut, const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples, DirectHrtfState *State, const ALsizei BufferSize); #define GAIN_MIX_MAX (1000.0f) /* +60dB */ #define GAIN_SILENCE_THRESHOLD (0.00001f) /* -100dB */ #define SPEEDOFSOUNDMETRESPERSEC (343.3f) #define AIRABSORBGAINHF (0.99426f) /* -0.05dB */ /* Target gain for the reverb decay feedback reaching the decay time. */ #define REVERB_DECAY_GAIN (0.001f) /* -60 dB */ #define FRACTIONBITS (12) #define FRACTIONONE (1<<FRACTIONBITS) #define FRACTIONMASK (FRACTIONONE-1) inline ALfloat lerp(ALfloat val1, ALfloat val2, ALfloat mu) noexcept { return val1 + (val2-val1)*mu; } inline ALfloat cubic(ALfloat val1, ALfloat val2, ALfloat val3, ALfloat val4, ALfloat mu) noexcept { ALfloat mu2 = mu*mu, mu3 = mu2*mu; ALfloat a0 = -0.5f*mu3 + mu2 + -0.5f*mu; ALfloat a1 = 1.5f*mu3 + -2.5f*mu2 + 1.0f; ALfloat a2 = -1.5f*mu3 + 2.0f*mu2 + 0.5f*mu; ALfloat a3 = 0.5f*mu3 + -0.5f*mu2; return val1*a0 + val2*a1 + val3*a2 + val4*a3; } enum HrtfRequestMode { Hrtf_Default = 0, Hrtf_Enable = 1, Hrtf_Disable = 2, }; void aluInit(void); void aluInitMixer(void); ResamplerFunc SelectResampler(Resampler resampler); /* aluInitRenderer * * Set up the appropriate panning method and mixing method given the device * properties. */ void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appreq, HrtfRequestMode hrtf_userreq); void aluInitEffectPanning(ALeffectslot *slot, ALCdevice *device); void ProcessHrtf(ALCdevice *device, const ALsizei SamplesToDo); void ProcessAmbiDec(ALCdevice *device, const ALsizei SamplesToDo); void ProcessUhj(ALCdevice *device, const ALsizei SamplesToDo); void ProcessBs2b(ALCdevice *device, const ALsizei SamplesToDo); /** * Calculates ambisonic encoder coefficients using the X, Y, and Z direction * components, which must represent a normalized (unit length) vector, and the * spread is the angular width of the sound (0...tau). * * NOTE: The components use ambisonic coordinates. As a result: * * Ambisonic Y = OpenAL -X * Ambisonic Z = OpenAL Y * Ambisonic X = OpenAL -Z * * The components are ordered such that OpenAL's X, Y, and Z are the first, * second, and third parameters respectively -- simply negate X and Z. */ void CalcAmbiCoeffs(const ALfloat y, const ALfloat z, const ALfloat x, const ALfloat spread, ALfloat (&coeffs)[MAX_AMBI_CHANNELS]); /** * CalcDirectionCoeffs * * Calculates ambisonic coefficients based on an OpenAL direction vector. The * vector must be normalized (unit length), and the spread is the angular width * of the sound (0...tau). */ inline void CalcDirectionCoeffs(const ALfloat (&dir)[3], ALfloat spread, ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) { /* Convert from OpenAL coords to Ambisonics. */ CalcAmbiCoeffs(-dir[0], dir[1], -dir[2], spread, coeffs); } /** * CalcAngleCoeffs * * Calculates ambisonic coefficients based on azimuth and elevation. The * azimuth and elevation parameters are in radians, going right and up * respectively. */ inline void CalcAngleCoeffs(ALfloat azimuth, ALfloat elevation, ALfloat spread, ALfloat (&coeffs)[MAX_AMBI_CHANNELS]) { ALfloat x = -std::sin(azimuth) * std::cos(elevation); ALfloat y = std::sin(elevation); ALfloat z = std::cos(azimuth) * std::cos(elevation); CalcAmbiCoeffs(x, y, z, spread, coeffs); } /** * ComputePanGains * * Computes panning gains using the given channel decoder coefficients and the * pre-calculated direction or angle coefficients. For B-Format sources, the * coeffs are a 'slice' of a transform matrix for the input channel, used to * scale and orient the sound samples. */ void ComputePanGains(const MixParams *mix, const ALfloat*RESTRICT coeffs, ALfloat ingain, ALfloat (&gains)[MAX_OUTPUT_CHANNELS]); inline std::array<ALfloat,MAX_AMBI_CHANNELS> GetAmbiIdentityRow(size_t i) noexcept { std::array<ALfloat,MAX_AMBI_CHANNELS> ret{}; ret[i] = 1.0f; return ret; } void MixVoice(ALvoice *voice, ALvoice::State vstate, const ALuint SourceID, ALCcontext *Context, const ALsizei SamplesToDo); void aluMixData(ALCdevice *device, ALvoid *OutBuffer, ALsizei NumSamples); /* Caller must lock the device state, and the mixer must not be running. */ void aluHandleDisconnect(ALCdevice *device, const char *msg, ...) DECL_FORMAT(printf, 2, 3); extern MixerFunc MixSamples; extern RowMixerFunc MixRowSamples; extern const ALfloat ConeScale; extern const ALfloat ZScale; extern const ALboolean OverrideReverbSpeedOfSound; #endif