diff options
Diffstat (limited to 'alc/alu.cpp')
-rw-r--r-- | alc/alu.cpp | 451 |
1 files changed, 239 insertions, 212 deletions
diff --git a/alc/alu.cpp b/alc/alu.cpp index e0858b18..b50eaa41 100644 --- a/alc/alu.cpp +++ b/alc/alu.cpp @@ -29,6 +29,7 @@ #include <chrono> #include <climits> #include <cstdarg> +#include <cstdint> #include <cstdio> #include <cstdlib> #include <functional> @@ -37,7 +38,6 @@ #include <memory> #include <new> #include <optional> -#include <stdint.h> #include <utility> #include "almalloc.h" @@ -141,7 +141,7 @@ using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBu HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>}; -inline HrtfDirectMixerFunc SelectHrtfMixer(void) +inline HrtfDirectMixerFunc SelectHrtfMixer() { #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) @@ -261,15 +261,15 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState case Resampler::Linear: break; case Resampler::Cubic: - state->cubic.filter = gCubicSpline.Tab.data(); + state->emplace<CubicState>().filter = gCubicSpline.Tab.data(); break; case Resampler::FastBSinc12: case Resampler::BSinc12: - BsincPrepare(increment, &state->bsinc, &gBSinc12); + BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: - BsincPrepare(increment, &state->bsinc, &gBSinc24); + BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24); break; } return SelectResampler(resampler, increment); @@ -282,7 +282,7 @@ void DeviceBase::ProcessHrtf(const size_t SamplesToDo) const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; const size_t ridx{RealOut.ChannelIndex[FrontRight]}; - MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, + MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData.data(), mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); } @@ -323,8 +323,7 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo) const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ - bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), - SamplesToDo); + Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); } @@ -451,14 +450,14 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; - if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb) + if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props)) { - slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->DecayTime = props->Props.Reverb.DecayTime; - slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; + slot->RoomRolloff = reverbprops->RoomRolloffFactor; + slot->DecayTime = reverbprops->DecayTime; + slot->DecayLFRatio = reverbprops->DecayLFRatio; + slot->DecayHFRatio = reverbprops->DecayHFRatio; + slot->DecayHFLimit = reverbprops->DecayHFLimit; + slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; } else { @@ -499,7 +498,7 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } } - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectTarget output; if(EffectSlot *target{slot->Target}) @@ -679,16 +678,16 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, const AmbiRotateMatrix &R) { - const float ri1{ R[ 1+2][static_cast<size_t>(i+2)]}; - const float rim1{R[-1+2][static_cast<size_t>(i+2)]}; - const float ri0{ R[ 0+2][static_cast<size_t>(i+2)]}; + const float ri1{ R[ 1+2][static_cast<size_t>(i+2_z)]}; + const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]}; + const float ri0{ R[ 0+2][static_cast<size_t>(i+2_z)]}; const size_t y{last_band + static_cast<size_t>(a+l-1)}; if(n == -l) - return ri1*R[last_band][y] + rim1*R[last_band + static_cast<size_t>(l-1)*2][y]; + return ri1*R[last_band][y] + rim1*R[last_band + static_cast<size_t>(l-1_z)*2][y]; if(n == l) - return ri1*R[last_band + static_cast<size_t>(l-1)*2][y] - rim1*R[last_band][y]; - return ri0*R[last_band + static_cast<size_t>(n+l-1)][y]; + return ri1*R[last_band + static_cast<size_t>(l-1_z)*2][y] - rim1*R[last_band][y]; + return ri0*R[last_band + static_cast<size_t>(l-1_z+n)][y]; }; auto U = [P](const int l, const int m, const int n, const size_t last_band, @@ -776,48 +775,54 @@ struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, - const al::span<const GainTriplet,MAX_SENDS> WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], - const VoiceProps *props, const ContextParams &Context, DeviceBase *Device) + const al::span<const GainTriplet,MaxSendCount> WetGain, + const al::span<EffectSlot*,MaxSendCount> SendSlots, const VoiceProps *props, + const ContextParams &Context, DeviceBase *Device) { - static constexpr ChanPosMap MonoMap[1]{ - { FrontCenter, std::array{0.0f, 0.0f, -1.0f} } - }, RearMap[2]{ - { BackLeft, std::array{-sin30, 0.0f, cos30} }, - { BackRight, std::array{ sin30, 0.0f, cos30} }, - }, QuadMap[4]{ - { FrontLeft, std::array{-sin45, 0.0f, -cos45} }, - { FrontRight, std::array{ sin45, 0.0f, -cos45} }, - { BackLeft, std::array{-sin45, 0.0f, cos45} }, - { BackRight, std::array{ sin45, 0.0f, cos45} }, - }, X51Map[6]{ - { FrontLeft, std::array{-sin30, 0.0f, -cos30} }, - { FrontRight, std::array{ sin30, 0.0f, -cos30} }, - { FrontCenter, std::array{ 0.0f, 0.0f, -1.0f} }, - { LFE, {} }, - { SideLeft, std::array{-sin110, 0.0f, -cos110} }, - { SideRight, std::array{ sin110, 0.0f, -cos110} }, - }, X61Map[7]{ - { FrontLeft, std::array{-sin30, 0.0f, -cos30} }, - { FrontRight, std::array{ sin30, 0.0f, -cos30} }, - { FrontCenter, std::array{ 0.0f, 0.0f, -1.0f} }, - { LFE, {} }, - { BackCenter, std::array{ 0.0f, 0.0f, 1.0f} }, - { SideLeft, std::array{-1.0f, 0.0f, 0.0f} }, - { SideRight, std::array{ 1.0f, 0.0f, 0.0f} }, - }, X71Map[8]{ - { FrontLeft, std::array{-sin30, 0.0f, -cos30} }, - { FrontRight, std::array{ sin30, 0.0f, -cos30} }, - { FrontCenter, std::array{ 0.0f, 0.0f, -1.0f} }, - { LFE, {} }, - { BackLeft, std::array{-sin30, 0.0f, cos30} }, - { BackRight, std::array{ sin30, 0.0f, cos30} }, - { SideLeft, std::array{ -1.0f, 0.0f, 0.0f} }, - { SideRight, std::array{ 1.0f, 0.0f, 0.0f} }, + static constexpr std::array MonoMap{ + ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} + }; + static constexpr std::array RearMap{ + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + }; + static constexpr std::array QuadMap{ + ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, + ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, + ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, + ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, + }; + static constexpr std::array X51Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, + ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, + }; + static constexpr std::array X61Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f}}, + ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, + }; + static constexpr std::array X71Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; - ChanPosMap StereoMap[2]{ - { FrontLeft, std::array{-sin30, 0.0f, -cos30} }, - { FrontRight, std::array{ sin30, 0.0f, -cos30} }, + std::array StereoMap{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; const auto Frequency = static_cast<float>(Device->Frequency); @@ -834,45 +839,45 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); } - DirectMode DirectChannels{props->DirectChannels}; - const ChanPosMap *chans{nullptr}; - switch(voice->mFmtChannels) + const auto getChans = [props,&StereoMap](FmtChannels chanfmt) noexcept + -> std::pair<DirectMode,al::span<const ChanPosMap>> { - case FmtMono: - chans = MonoMap; - /* Mono buffers are never played direct. */ - DirectChannels = DirectMode::Off; - break; - - case FmtStereo: - if(DirectChannels == DirectMode::Off) + switch(chanfmt) { - for(size_t i{0};i < 2;++i) + case FmtMono: + /* Mono buffers are never played direct. */ + return {DirectMode::Off, al::span{MonoMap}}; + + case FmtStereo: + if(props->DirectChannels == DirectMode::Off) { - /* StereoPan is counter-clockwise in radians. */ - const float a{props->StereoPan[i]}; - StereoMap[i].pos[0] = -std::sin(a); - StereoMap[i].pos[2] = -std::cos(a); + for(size_t i{0};i < 2;++i) + { + /* StereoPan is counter-clockwise in radians. */ + const float a{props->StereoPan[i]}; + StereoMap[i].pos[0] = -std::sin(a); + StereoMap[i].pos[2] = -std::cos(a); + } } + return {props->DirectChannels, al::span{StereoMap}}; + + case FmtRear: return {props->DirectChannels, al::span{RearMap}}; + case FmtQuad: return {props->DirectChannels, al::span{QuadMap}}; + case FmtX51: return {props->DirectChannels, al::span{X51Map}}; + case FmtX61: return {props->DirectChannels, al::span{X61Map}}; + case FmtX71: return {props->DirectChannels, al::span{X71Map}}; + + case FmtBFormat2D: + case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: + return {DirectMode::Off, {}}; } - chans = StereoMap; - break; - - case FmtRear: chans = RearMap; break; - case FmtQuad: chans = QuadMap; break; - case FmtX51: chans = X51Map; break; - case FmtX61: chans = X61Map; break; - case FmtX71: chans = X71Map; break; - - case FmtBFormat2D: - case FmtBFormat3D: - case FmtUHJ2: - case FmtUHJ3: - case FmtUHJ4: - case FmtSuperStereo: - DirectChannels = DirectMode::Off; - break; - } + return {props->DirectChannels, {}}; + }; + const auto [DirectChannels,chans] = getChans(voice->mFmtChannels); voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); if(auto *decoder{voice->mDecoder.get()}) @@ -1065,8 +1070,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; else if(DirectChannels == DirectMode::RemixMismatch) { - auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool - { return chans[c].channel == map.channel; }; + auto match_channel = [channel=chans[c].channel](const InputRemixMap &map) noexcept + { return channel == map.channel; }; auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), Device->RealOut.RemixMap.cend(), match_channel); if(remap != Device->RealOut.RemixMap.cend()) @@ -1176,7 +1181,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual @@ -1324,7 +1329,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; for(size_t c{0};c < num_channels;c++) { /* Special-case LFE */ @@ -1396,7 +1401,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; - EffectSlot *SendSlots[MAX_SENDS]; + std::array<EffectSlot*,MaxSendCount> SendSlots; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) @@ -1426,7 +1431,8 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex context->mParams.Gain, GainMixMax); DryGain.HF = props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]; + + std::array<GainTriplet,MaxSendCount> WetGain; for(uint i{0};i < Device->NumAuxSends;i++) { WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * @@ -1446,20 +1452,30 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; - EffectSlot *SendSlots[MAX_SENDS]; - uint UseDryAttnForRoom{0}; + std::array<EffectSlot*,MaxSendCount> SendSlots{}; + std::array<float,MaxSendCount> RoomRolloff{}; + std::bitset<MaxSendCount> UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) SendSlots[i] = nullptr; - else if(!SendSlots[i]->AuxSendAuto) + else if(SendSlots[i]->AuxSendAuto) { - /* If the slot's auxiliary send auto is off, the data sent to the - * effect slot is the same as the dry path, sans filter effects. + /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor + * applies to the selected distance model along with the source's + * room rolloff factor, not necessarily the inverse distance model. + * + * Generic Software also applies these rolloff factors regardless + * of any setting. It doesn't seem to use the effect slot's send + * auto for anything, though as far as I understand, it's supposed + * to control whether the send gets the same gain/gainhf as the + * direct path (excluding the filter). */ - UseDryAttnForRoom |= 1u<<i; + RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff; } + else + UseDryAttnForRoom.set(i); if(!SendSlots[i]) voice->mSend[i].Buffer = {}; @@ -1491,62 +1507,77 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Calculate distance attenuation */ float ClampedDist{Distance}; float DryGainBase{props->Gain}; - float WetGainBase{props->Gain}; + std::array<float,MaxSendCount> WetGainBase{}; + WetGainBase.fill(props->Gain); + float DryAttnBase{1.0f}; switch(context->mParams.SourceDistanceModel ? props->mDistanceModel : context->mParams.mDistanceModel) { - case DistanceModel::InverseClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Inverse: - if(props->RefDistance > 0.0f) + case DistanceModel::InverseClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Inverse: + if(props->RefDistance > 0.0f) + { + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) { - float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; - if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; - - dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); - if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; + DryAttnBase = props->RefDistance / dist; + DryGainBase *= DryAttnBase; } - break; - - case DistanceModel::LinearClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Linear: - if(props->MaxDistance != props->RefDistance) - { - float attn{(ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; - DryGainBase *= maxf(1.0f - attn, 0.0f); - attn = (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; - WetGainBase *= maxf(1.0f - attn, 0.0f); + for(size_t i{0};i < NumSends;++i) + { + dist = lerpf(props->RefDistance, ClampedDist, RoomRolloff[i]); + if(dist > 0.0f) WetGainBase[i] *= props->RefDistance / dist; } - break; - - case DistanceModel::ExponentClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Exponent: - if(ClampedDist > 0.0f && props->RefDistance > 0.0f) + } + break; + + case DistanceModel::LinearClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Linear: + if(props->MaxDistance != props->RefDistance) + { + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryAttnBase = maxf(1.0f - attn, 0.0f); + DryGainBase *= DryAttnBase; + + for(size_t i{0};i < NumSends;++i) { - const float dist_ratio{ClampedDist/props->RefDistance}; - DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); - WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); + attn = (ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * RoomRolloff[i]; + WetGainBase[i] *= maxf(1.0f - attn, 0.0f); } - break; + } + break; + + case DistanceModel::ExponentClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Exponent: + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) + { + const float dist_ratio{ClampedDist/props->RefDistance}; + DryAttnBase = std::pow(dist_ratio, -props->RolloffFactor); + DryGainBase *= DryAttnBase; + for(size_t i{0};i < NumSends;++i) + WetGainBase[i] *= std::pow(dist_ratio, -RoomRolloff[i]); + } + break; - case DistanceModel::Disable: - break; + case DistanceModel::Disable: + break; } /* Calculate directional soundcones */ - float ConeHF{1.0f}, WetConeHF{1.0f}; + float ConeHF{1.0f}, WetCone{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { static constexpr float Rad2Deg{static_cast<float>(180.0 / al::numbers::pi)}; @@ -1556,39 +1587,43 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(Angle >= props->OuterAngle) { ConeGain = props->OuterGain; - ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = props->OuterGainHF; } else if(Angle >= props->InnerAngle) { const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; ConeGain = lerpf(1.0f, props->OuterGain, scale); - ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = lerpf(1.0f, props->OuterGainHF, scale); } DryGainBase *= ConeGain; - WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); - - WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); + if(props->WetGainAuto) + WetCone = ConeGain; + if(props->WetGainHFAuto) + WetConeHF = ConeHF; } /* Apply gain and frequency filters */ - DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - GainTriplet DryGain{}; + DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); DryGain.HF = ConeHF * props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]{}; + + std::array<GainTriplet,MaxSendCount> WetGain{}; for(uint i{0};i < NumSends;i++) { + WetGainBase[i] = clampf(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * + context->mParams.Gain; /* If this effect slot's Auxiliary Send Auto is off, then use the dry * path distance and cone attenuation, otherwise use the wet (room) * path distance and cone attenuation. The send filter is used instead * of the direct filter, regardless. */ - const bool use_room{!(UseDryAttnForRoom&(1u<<i))}; - const float gain{use_room ? WetGainBase : DryGainBase}; + const bool use_room{!UseDryAttnForRoom.test(i)}; + const float gain{use_room ? WetGainBase[i] : DryGainBase}; WetGain[i].Base = minf(gain * props->Send[i].Gain, GainMixMax); WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; @@ -1611,25 +1646,15 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; - auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept - { - const float dist{lerpf(refdist, distance, rolloff)}; - if(dist > refdist) return refdist / dist; - return 1.0f; - }; - - /* The reverb effect's room rolloff factor always applies to an - * inverse distance rolloff model. - */ - WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, - SendSlots[i]->RoomRolloff); - if(distance_meters > std::numeric_limits<float>::epsilon()) WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); /* If this effect slot's Auxiliary Send Auto is off, don't apply - * the automatic initial reverb decay (should the reverb's room - * rolloff still apply?). + * the automatic initial reverb decay. + * + * NOTE: Generic Software applies the initial decay regardless of + * this setting. It doesn't seem to use it for anything, only the + * source's send filter gain auto flag affects this. */ if(!SendSlots[i]->AuxSendAuto) continue; @@ -1640,7 +1665,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa */ DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; - DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; + DecayDistance.HF = SendSlots[i]->DecayHFRatio; if(SendSlots[i]->DecayHFLimit) { const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; @@ -1656,14 +1681,18 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); } } - - const float baseAttn = calc_attenuation(Distance, props->RefDistance, - props->RolloffFactor); + DecayDistance.HF *= DecayDistance.Base; /* Apply a decay-time transformation to the wet path, based on the * source distance. The initial decay of the reverb effect is * calculated and applied to the wet path. + * + * FIXME: This is very likely not correct. It more likely should + * work by calculating a rolloff dynamically based on the reverb + * parameters (and source distance?) and add it to the room rolloff + * with the reverb and source rolloff parameters. */ + const float baseAttn{DryAttnBase}; const float fact{distance_base / DecayDistance.Base}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; @@ -1743,7 +1772,7 @@ void CalcSourceParams(Voice *voice, ContextBase *context, bool force) if(props) { - voice->mProps = *props; + voice->mProps = static_cast<VoiceProps&>(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } @@ -1883,7 +1912,7 @@ void ProcessVoiceChanges(ContextBase *ctx) ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } -void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, +void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots, const al::span<Voice*> voices) { ProcessVoiceChanges(ctx); @@ -1892,7 +1921,7 @@ void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; - auto sorted_slots = const_cast<EffectSlot**>(slots.data() + slots.size()); + auto sorted_slots = al::to_address(slots.end()); for(EffectSlot *slot : slots) force |= CalcEffectSlotParams(slot, sorted_slots, ctx); @@ -1910,12 +1939,13 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - const nanoseconds curtime{device->ClockBase + - nanoseconds{seconds{device->SamplesDone}}/device->Frequency}; + const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + + nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/ + device->Frequency}; for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { - const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); + auto auxslots = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()}; /* Process pending property updates for objects on the context. */ @@ -1937,16 +1967,12 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) } /* Process effects. */ - if(const size_t num_slots{auxslots.size()}) + if(!auxslots.empty()) { - auto slots = auxslots.data(); - auto slots_end = slots + num_slots; - /* Sort the slots into extra storage, so that effect slots come * before their effect slot target (or their targets' target). */ - const al::span<EffectSlot*> sorted_slots{const_cast<EffectSlot**>(slots_end), - num_slots}; + const al::span sorted_slots{al::to_address(auxslots.end()), auxslots.size()}; /* Skip sorting if it has already been done. */ if(!sorted_slots[0]) { @@ -1954,7 +1980,7 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) * sorted list so that all slots without a target slot go to * the end. */ - std::copy(slots, slots_end, sorted_slots.begin()); + std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), [](const EffectSlot *slot) noexcept -> bool { return slot->Target != nullptr; }); @@ -2041,11 +2067,12 @@ void ApplyDistanceComp(const al::span<FloatBufferLine> Samples, const size_t Sam void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed, const float quant_scale, const size_t SamplesToDo) { + static constexpr double invRNGRange{1.0 / std::numeric_limits<uint>::max()}; ASSUME(SamplesToDo > 0); /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to - * the desired quantization depth amd before rounding. + * the desired quantization depth and before rounding. */ const float invscale{1.0f / quant_scale}; uint seed{*dither_seed}; @@ -2054,7 +2081,7 @@ void ApplyDither(const al::span<FloatBufferLine> Samples, uint *dither_seed, float val{sample * quant_scale}; uint rng0{dither_rng(&seed)}; uint rng1{dither_rng(&seed)}; - val += static_cast<float>(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); + val += static_cast<float>(rng0*invRNGRange - rng1*invRNGRange); return fast_roundf(val) * invscale; }; for(FloatBufferLine &inout : Samples) @@ -2134,22 +2161,22 @@ uint DeviceBase::renderSamples(const uint numSamples) for(FloatBufferLine &buffer : MixBuffer) buffer.fill(0.0f); - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(MixCount); - - /* Process and mix each context's sources and effects. */ - ProcessContexts(this, samplesToDo); + { + const auto mixLock = getWriteMixLock(); - /* Increment the clock time. Every second's worth of samples is converted - * and added to clock base so that large sample counts don't overflow - * during conversion. This also guarantees a stable conversion. - */ - SamplesDone += samplesToDo; - ClockBase += std::chrono::seconds{SamplesDone / Frequency}; - SamplesDone %= Frequency; + /* Process and mix each context's sources and effects. */ + ProcessContexts(this, samplesToDo); - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(MixCount); + /* Every second's worth of samples is converted and added to clock base + * so that large sample counts don't overflow during conversion. This + * also guarantees a stable conversion. + */ + auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; + auto clockBase = mClockBase.load(std::memory_order_relaxed) + + std::chrono::seconds{samplesDone/Frequency}; + mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); + mClockBase.store(clockBase, std::memory_order_relaxed); + } /* Apply any needed post-process for finalizing the Dry mix to the RealOut * (Ambisonic decode, UHJ encode, etc). @@ -2225,7 +2252,8 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz void DeviceBase::handleDisconnect(const char *msg, ...) { - IncrementRef(MixCount); + const auto mixLock = getWriteMixLock(); + if(Connected.exchange(false, std::memory_order_acq_rel)) { AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>}; @@ -2233,10 +2261,10 @@ void DeviceBase::handleDisconnect(const char *msg, ...) va_list args; va_start(args, msg); - int msglen{vsnprintf(disconnect.msg, sizeof(disconnect.msg), msg, args)}; + int msglen{vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args)}; va_end(args); - if(msglen < 0 || static_cast<size_t>(msglen) >= sizeof(disconnect.msg)) + if(msglen < 0 || static_cast<size_t>(msglen) >= disconnect.msg.size()) disconnect.msg[sizeof(disconnect.msg)-1] = 0; for(ContextBase *ctx : *mContexts.load()) @@ -2267,5 +2295,4 @@ void DeviceBase::handleDisconnect(const char *msg, ...) std::for_each(voicelist.begin(), voicelist.end(), stop_voice); } } - IncrementRef(MixCount); } |