update openal-soft to 1.24.3

keeping the alt 87514151c4 (diff-73a8dc1ce58605f6c5ea53548454c3bae516ec5132a29c9d7ff7edf9730c75be)
This commit is contained in:
AzaezelX 2025-09-03 11:09:27 -05:00
parent 12db0500e8
commit ba32094b7b
276 changed files with 49304 additions and 8712 deletions

View file

@ -19,6 +19,7 @@
*/
#include "config.h"
#include "config_simd.h"
#include "alu.h"
@ -26,23 +27,25 @@
#include <array>
#include <atomic>
#include <cassert>
#include <chrono>
#include <climits>
#include <cmath>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <new>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alsem.h"
#include "alspan.h"
#include "alstring.h"
#include "atomic.h"
@ -70,6 +73,7 @@
#include "core/mixer/defs.h"
#include "core/mixer/hrtfdefs.h"
#include "core/resampler_limits.h"
#include "core/storage_formats.h"
#include "core/uhjfilter.h"
#include "core/voice.h"
#include "core/voice_change.h"
@ -78,19 +82,18 @@
#include "ringbuffer.h"
#include "strutils.h"
#include "vecmat.h"
#include "vector.h"
struct CTag;
#ifdef HAVE_SSE
#if HAVE_SSE
struct SSETag;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
struct SSE2Tag;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
struct SSE4Tag;
#endif
#ifdef HAVE_NEON
#if HAVE_NEON
struct NEONTag;
#endif
struct PointTag;
@ -143,11 +146,11 @@ HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
inline HrtfDirectMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return MixDirectHrtf_<NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return MixDirectHrtf_<SSETag>;
#endif
@ -186,60 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
case Resampler::Point:
return Resample_<PointTag,CTag>;
case Resampler::Linear:
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<LerpTag,NEONTag>;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<LerpTag,SSE4Tag>;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<LerpTag,SSE2Tag>;
#endif
return Resample_<LerpTag,CTag>;
case Resampler::Spline:
case Resampler::Gaussian:
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<CubicTag,NEONTag>;
#endif
#ifdef HAVE_SSE4_1
#if HAVE_SSE4_1
if((CPUCapFlags&CPU_CAP_SSE4_1))
return Resample_<CubicTag,SSE4Tag>;
#endif
#ifdef HAVE_SSE2
#if HAVE_SSE2
if((CPUCapFlags&CPU_CAP_SSE2))
return Resample_<CubicTag,SSE2Tag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<CubicTag,SSETag>;
#endif
return Resample_<CubicTag,CTag>;
case Resampler::BSinc12:
case Resampler::BSinc24:
case Resampler::BSinc48:
if(increment > MixerFracOne)
{
#ifdef HAVE_NEON
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<BSincTag,NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<BSincTag,SSETag>;
#endif
return Resample_<BSincTag,CTag>;
}
/* fall-through */
[[fallthrough]];
case Resampler::FastBSinc12:
case Resampler::FastBSinc24:
#ifdef HAVE_NEON
case Resampler::FastBSinc48:
#if HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Resample_<FastBSincTag,NEONTag>;
#endif
#ifdef HAVE_SSE
#if HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Resample_<FastBSincTag,SSETag>;
#endif
@ -283,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
case Resampler::BSinc24:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24);
break;
case Resampler::FastBSinc48:
case Resampler::BSinc48:
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc48);
break;
}
return SelectResampler(resampler, increment);
}
@ -334,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo)
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
/* Now apply the BS2B binaural/crossfeed filter. */
Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo);
Bs2b->cross_feed(al::span{RealOut.Buffer[lidx]}.first(SamplesToDo),
al::span{RealOut.Buffer[ridx]}.first(SamplesToDo));
}
@ -434,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx)
ctx->mParams.Velocity = rot * vel;
ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
ctx->mParams.MetersPerUnit = props->MetersPerUnit;
ctx->mParams.MetersPerUnit = props->MetersPerUnit
#if ALSOFT_EAX
* props->DistanceFactor
#endif
;
ctx->mParams.AirAbsorptionGainHF = props->AirAbsorptionGainHF;
ctx->mParams.DopplerFactor = props->DopplerFactor;
ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity;
ctx->mParams.SpeedOfSound = props->SpeedOfSound * props->DopplerVelocity
#if ALSOFT_EAX
/ props->DistanceFactor
#endif
;
ctx->mParams.SourceDistanceModel = props->SourceDistanceModel;
ctx->mParams.mDistanceModel = props->mDistanceModel;
@ -462,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
slot->Target = props->Target;
slot->EffectType = props->Type;
slot->mEffectProps = props->Props;
slot->RoomRolloff = 0.0f;
slot->DecayTime = 0.0f;
slot->DecayLFRatio = 0.0f;
slot->DecayHFRatio = 0.0f;
slot->DecayHFLimit = false;
slot->AirAbsorptionGainHF = 1.0f;
if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props))
{
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
{
slot->RoomRolloff = 0.0f;
slot->DecayTime = 0.0f;
slot->DecayLFRatio = 0.0f;
slot->DecayHFRatio = 0.0f;
slot->DecayHFLimit = false;
slot->AirAbsorptionGainHF = 1.0f;
/* If this effect slot's Auxiliary Send Auto is off, don't apply the
* automatic send adjustments based on source distance.
*/
if(slot->AuxSendAuto)
{
slot->DecayTime = reverbprops->DecayTime;
slot->DecayLFRatio = reverbprops->DecayLFRatio;
slot->DecayHFRatio = reverbprops->DecayHFRatio;
slot->DecayHFLimit = reverbprops->DecayHFLimit;
}
}
EffectState *state{props->State.release()};
@ -493,9 +515,9 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
/* Otherwise, if it would be deleted send it off with a release event. */
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len > 0) LIKELY
if(evt_vec[0].len > 0) LIKELY
{
auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncEffectReleaseEvent>(evt_vec[0].buf);
evt.mEffectState = oldstate;
ring->writeAdvance(1);
}
@ -686,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
/* Don't do anything for < 2nd order. */
if(order < 2) return;
auto P = [](const int i, const int l, const int a, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
static constexpr 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_z)]};
const float rim1{R[-1+2][static_cast<size_t>(i+2_z)]};
@ -701,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
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,
static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
return P(0, l, m, n, last_band, R);
};
auto V = [P](const int l, const int m, const int n, const size_t last_band,
static constexpr auto V = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
using namespace al::numbers;
@ -722,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
const float p1{P(-1, l, -m-1, n, last_band, R)};
return d ? p1*sqrt2_v<float> : (p0 + p1);
};
auto W = [P](const int l, const int m, const int n, const size_t last_band,
static constexpr auto W = [](const int l, const int m, const int n, const size_t last_band,
const AmbiRotateMatrix &R)
{
assert(m != 0);
@ -836,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
};
const auto Frequency = static_cast<float>(Device->Frequency);
const auto Frequency = static_cast<float>(Device->mSampleRate);
const uint NumSends{Device->NumAuxSends};
const size_t num_channels{voice->mChans.size()};
@ -1183,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
}
else for(size_t c{0};c < num_channels;c++)
{
using namespace al::numbers;
/* Skip LFE */
if(chans[c].channel == LFE) continue;
const float pangain{SelectChannelGain(chans[c].channel)};
@ -1194,7 +1214,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
* the source position, at full spread (pi*2), each channel is
* left unchanged.
*/
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread};
const float a{1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread};
std::array pos{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
@ -1310,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
voice->mChans[0].mWetParams[i].Gains.Target);
}
}
else
else for(size_t c{0};c < num_channels;c++)
{
using namespace al::numbers;
const auto pangain = SelectChannelGain(chans[c].channel);
for(size_t c{0};c < num_channels;c++)
/* Special-case LFE */
if(chans[c].channel == LFE)
{
const float pangain{SelectChannelGain(chans[c].channel)};
/* Special-case LFE */
if(chans[c].channel == LFE)
if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
{
if(Device->Dry.Buffer.data() == Device->RealOut.Buffer.data())
{
const uint idx{Device->channelIdxByName(chans[c].channel)};
if(idx != InvalidChannelIndex)
voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base
* pangain;
}
continue;
const auto idx = uint{Device->channelIdxByName(chans[c].channel)};
if(idx != InvalidChannelIndex)
voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain;
}
continue;
}
/* Warp the channel position toward the source position as
* the spread decreases. With no spread, all channels are
* at the source position, at full spread (pi*2), each
* channel position is left unchanged.
*/
const float a{1.0f - (inv_pi_v<float>/2.0f)*Spread};
std::array pos{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
lerpf(chans[c].pos[2], zpos, a)};
const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])};
if(len < 1.0f)
{
pos[0] /= len;
pos[1] /= len;
pos[2] /= len;
}
/* Warp the channel position toward the source position as the
* spread decreases. With no spread, all channels are at the
* source position, at full spread (pi*2), each channel
* position is left unchanged.
*/
const auto a = 1.0f - (al::numbers::inv_pi_v<float>/2.0f)*Spread;
auto pos = std::array{
lerpf(chans[c].pos[0], xpos, a),
lerpf(chans[c].pos[1], ypos, a),
lerpf(chans[c].pos[2], zpos, a)};
const auto len = std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2]);
if(len < 1.0f)
{
pos[0] /= len;
pos[1] /= len;
pos[2] /= len;
}
if(Device->mRenderMode == RenderMode::Pairwise)
pos = ScaleAzimuthFront3(pos);
const auto coeffs = CalcDirectionCoeffs(pos, 0.0f);
if(Device->mRenderMode == RenderMode::Pairwise)
pos = ScaleAzimuthFront3(pos);
const auto coeffs = CalcDirectionCoeffs(pos, 0.0f);
ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain,
voice->mChans[c].mDryParams.Gains.Target);
for(uint i{0};i < NumSends;i++)
{
if(const EffectSlot *Slot{SendSlots[i]})
ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain,
voice->mChans[c].mWetParams[i].Gains.Target);
}
ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain,
voice->mChans[c].mDryParams.Gains.Target);
for(uint i{0};i < NumSends;i++)
{
if(const EffectSlot *Slot{SendSlots[i]})
ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain,
voice->mChans[c].mWetParams[i].Gains.Target);
}
}
}
@ -1473,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
/* Calculate the stepping value */
const auto Pitch = static_cast<float>(voice->mFrequency) /
static_cast<float>(Device->Frequency) * props->Pitch;
static_cast<float>(Device->mSampleRate) * props->Pitch;
if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits;
else
@ -1509,33 +1523,24 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
voice->mDirect.Buffer = Device->Dry.Buffer;
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)
voice->mSend[i].Buffer = {};
}
else
{
/* 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).
*/
RoomRolloff[i] = props->RoomRolloffFactor + SendSlots[i]->RoomRolloff;
}
else
UseDryAttnForRoom.set(i);
if(!SendSlots[i])
voice->mSend[i].Buffer = {};
else
voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
}
}
/* Transform source to listener space (convert to head relative) */
@ -1670,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
std::array<GainTriplet,MaxSendCount> WetGain{};
for(uint i{0};i < NumSends;i++)
{
WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) *
const auto gain = std::clamp(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.test(i)};
const float gain{use_room ? WetGainBase[i] : DryGainBase};
WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax);
WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF;
WetGain[i].HF = WetConeHF * props->Send[i].GainHF;
WetGain[i].LF = props->Send[i].GainLF;
}
/* Distance-based air absorption and initial send decay. */
if(Distance > props->RefDistance) LIKELY
{
const float distance_base{(Distance-props->RefDistance) * props->RolloffFactor};
const float distance_meters{distance_base * context->mParams.MetersPerUnit};
const float dryabsorb{distance_meters * props->AirAbsorptionFactor};
if(dryabsorb > std::numeric_limits<float>::epsilon())
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, dryabsorb);
/* FIXME: In keeping with EAX, the base air absorption gain should be
* taken from the reverb property in the "primary fx slot" when it has
* a reverb effect and the environment flag set, and be applied to the
* direct path and all environment sends, rather than each path using
* the air absorption gain associated with the given slot's effect. At
* this point in the mixer, and even in EFX itself, there's no concept
* of a "primary fx slot" so it's unclear which effect slot should be
* checked.
*
* The HF reference is also intended to be handled the same way, but
* again, there's no concept of a "primary fx slot" here and no way to
* know which effect slot to look at for the reference frequency.
*/
const auto distance_units = float{(Distance-props->RefDistance) * props->RolloffFactor};
const auto distance_meters = float{distance_units * context->mParams.MetersPerUnit};
const auto absorb = float{distance_meters * props->AirAbsorptionFactor};
if(absorb > std::numeric_limits<float>::epsilon())
DryGain.HF *= std::pow(context->mParams.AirAbsorptionGainHF, absorb);
/* If the source's Auxiliary Send Filter Gain Auto is off, no extra
* adjustment is applied to the send gains.
@ -1701,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
continue;
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.
*
* 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;
if(SendSlots[i]->AirAbsorptionGainHF < 1.0f
&& absorb > std::numeric_limits<float>::epsilon())
WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, absorb);
const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec};
@ -1726,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
* with the reverb and source rolloff parameters.
*/
const float baseAttn{DryAttnBase};
const float fact{distance_base / DecayDistance};
const float fact{distance_meters / DecayDistance};
const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn};
WetGain[i].Base *= gain;
}
@ -1771,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
/* Adjust pitch based on the buffer and output frequencies, and calculate
* fixed-point stepping value.
*/
Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->Frequency);
Pitch *= static_cast<float>(voice->mFrequency) / static_cast<float>(Device->mSampleRate);
if(Pitch > float{MaxPitch})
voice->mStep = MaxPitch<<MixerFracBits;
else
@ -1814,9 +1816,9 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
{
RingBuffer *ring{context->mAsyncEvents.get()};
auto evt_vec = ring->getWriteVector();
if(evt_vec.first.len < 1) return;
if(evt_vec[0].len < 1) return;
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec.first.buf);
auto &evt = InitAsyncEvent<AsyncSourceStateEvent>(evt_vec[0].buf);
evt.mId = id;
switch(state)
{
@ -1962,34 +1964,35 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
{
ASSUME(SamplesToDo > 0);
const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) +
nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/
device->Frequency};
const auto curtime = device->getClockTime();
for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire))
auto proc_context = [SamplesToDo,curtime](ContextBase *ctx)
{
const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)};
const auto auxslots = auxslotspan.first(auxslotspan.size()>>1);
const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1);
const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
const auto voices = ctx->getVoicesSpanAcquired();
/* Process pending property updates for objects on the context. */
ProcessParamUpdates(ctx, auxslots, sorted_slots, voices);
/* Clear auxiliary effect slot mixing buffers. */
for(EffectSlot *slot : auxslots)
auto clear_wetbuffers = [](EffectSlot *slot)
{
for(auto &buffer : slot->Wet.Buffer)
buffer.fill(0.0f);
}
auto clear_buffer = [](const FloatBufferSpan buffer)
{ std::fill(buffer.begin(), buffer.end(), 0.0f); };
std::for_each(slot->Wet.Buffer.begin(), slot->Wet.Buffer.end(), clear_buffer);
};
std::for_each(auxslots.begin(), auxslots.end(), clear_wetbuffers);
/* Process voices that have a playing source. */
for(Voice *voice : voices)
auto proc_voice = [ctx,curtime,SamplesToDo](Voice *voice)
{
const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
if(vstate != Voice::Stopped && vstate != Voice::Pending)
voice->mix(vstate, ctx, curtime, SamplesToDo);
}
};
std::for_each(voices.begin(), voices.end(), proc_voice);
/* Process effects. */
if(!auxslots.empty())
@ -2000,60 +2003,54 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
*/
if(!sorted_slots[0])
{
/* First, copy the slots to the sorted list, then partition the
* sorted list so that all slots without a target slot go to
* the end.
/* First, copy the slots to the sorted list and partition them,
* so that all slots without a target slot go to the end.
*/
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; });
auto has_target = [](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != nullptr; };
auto split_point = std::partition_copy(auxslots.rbegin(), auxslots.rend(),
sorted_slots.begin(), sorted_slots.rbegin(), has_target).first;
/* There must be at least one slot without a slot target. */
assert(split_point != sorted_slots.end());
/* Simple case: no more than 1 slot has a target slot. Either
* all slots go right to the output, or the remaining one must
* target an already-partitioned slot.
/* Starting from the back of the sorted list, continue
* partitioning the front of the list given each target until
* all targets are accounted for. This ensures all slots
* without a target go last, all slots directly targeting those
* last slots go second-to-last, all slots directly targeting
* those second-last slots go third-to-last, etc.
*/
if(split_point - sorted_slots.begin() > 1)
auto next_target = sorted_slots.end();
while(std::distance(sorted_slots.begin(), split_point) > 1)
{
/* At least two slots target other slots. Starting from the
* back of the sorted list, continue partitioning the front
* of the list given each target until all targets are
* accounted for. This ensures all slots without a target
* go last, all slots directly targeting those last slots
* go second-to-last, all slots directly targeting those
* second-last slots go third-to-last, etc.
/* This shouldn't happen, but if there's unsorted slots
* left that don't target any sorted slots, they can't
* contribute to the output, so leave them.
*/
auto next_target = sorted_slots.end();
do {
/* This shouldn't happen, but if there's unsorted slots
* left that don't target any sorted slots, they can't
* contribute to the output, so leave them.
*/
if(next_target == split_point) UNLIKELY
break;
if(next_target == split_point) UNLIKELY
break;
--next_target;
split_point = std::partition(sorted_slots.begin(), split_point,
[next_target](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != *next_target; });
} while(split_point - sorted_slots.begin() > 1);
--next_target;
auto not_next = [next_target](const EffectSlot *slot) noexcept -> bool
{ return slot->Target != *next_target; };
split_point = std::partition(sorted_slots.begin(), split_point, not_next);
}
}
for(const EffectSlot *slot : sorted_slots)
auto proc_slot = [SamplesToDo](const EffectSlot *slot)
{
EffectState *state{slot->mEffectState.get()};
state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
}
};
std::for_each(sorted_slots.begin(), sorted_slots.end(), proc_slot);
}
/* Signal the event handler if there are any events to read. */
RingBuffer *ring{ctx->mAsyncEvents.get()};
if(ring->readSpace() > 0)
if(RingBuffer *ring{ctx->mAsyncEvents.get()}; ring->readSpace() > 0)
ctx->mEventSem.post();
}
};
const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)};
std::for_each(contexts.begin(), contexts.end(), proc_context);
}
@ -2152,25 +2149,47 @@ void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, cons
ASSUME(FrameStep > 0);
ASSUME(SamplesToDo > 0);
const auto output = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep}
.subspan(Offset*FrameStep);
size_t c{0};
for(const FloatBufferLine &inbuf : InBuffer)
/* Some Clang versions don't like calling subspan on an rvalue here. */
const auto output_ = al::span{static_cast<T*>(OutBuffer), (Offset+SamplesToDo)*FrameStep};
const auto output = output_.subspan(Offset*FrameStep);
/* If there's extra channels in the interleaved output buffer to skip,
* clear the whole output buffer. This is simpler to ensure the extra
* channels are silent than trying to clear just the extra channels.
*/
if(FrameStep > InBuffer.size())
std::fill(output.begin(), output.end(), SampleConv<T>(0.0f));
auto outbase = output.begin();
for(const auto &srcbuf : InBuffer)
{
auto out = output.begin();
auto conv_sample = [FrameStep,c,&out](const float s) noexcept
const auto src = al::span{srcbuf}.first(SamplesToDo);
auto out = outbase++;
*out = SampleConv<T>(src.front());
std::for_each(src.begin()+1, src.end(), [FrameStep,&out](const float s) noexcept
{
out[c] = SampleConv<T>(s);
out += ptrdiff_t(FrameStep);
};
std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample);
++c;
*out = SampleConv<T>(s);
});
}
if(const size_t extra{FrameStep - c})
}
template<typename T>
void Write(const al::span<const FloatBufferLine> InBuffer, al::span<void*> OutBuffers,
const size_t Offset, const size_t SamplesToDo)
{
ASSUME(SamplesToDo > 0);
auto srcbuf = InBuffer.cbegin();
for(auto *dstbuf : OutBuffers)
{
const auto silence = SampleConv<T>(0.0f);
for(size_t i{0};i < SamplesToDo;++i)
std::fill_n(&output[i*FrameStep + c], extra, silence);
const auto src = al::span{*srcbuf}.first(SamplesToDo);
/* Some Clang versions don't like calling subspan on an rvalue here. */
const auto dst_ = al::span{static_cast<T*>(dstbuf), Offset+SamplesToDo};
const auto dst = dst_.subspan(Offset);
std::transform(src.begin(), src.end(), dst.begin(), SampleConv<T>);
++srcbuf;
}
}
@ -2195,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples)
* 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);
auto clockBaseSec = mClockBaseSec.load(std::memory_order_relaxed) +
seconds32{samplesDone/mSampleRate};
mSamplesDone.store(samplesDone%mSampleRate, std::memory_order_relaxed);
mClockBaseSec.store(clockBaseSec, std::memory_order_relaxed);
}
/* Apply any needed post-process for finalizing the Dry mix to the RealOut
@ -2207,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
postProcess(samplesToDo);
/* Apply compression, limiting sample amplitude if needed or desired. */
if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer.data());
if(Limiter) Limiter->process(samplesToDo, RealOut.Buffer);
/* Apply delays and attenuation for mismatched speaker distances. */
if(ChannelDelays)
@ -2222,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
return samplesToDo;
}
void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint numSamples)
void DeviceBase::renderSamples(const al::span<void*> outBuffers, const uint numSamples)
{
FPUCtl mixer_mode{};
uint total{0};
@ -2230,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint num
{
const uint samplesToDo{renderSamples(todo)};
auto srcbuf = RealOut.Buffer.cbegin();
for(auto *dstbuf : outBuffers)
switch(FmtType)
{
const auto dst = al::span{dstbuf, numSamples}.subspan(total);
std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin());
++srcbuf;
#define HANDLE_WRITE(T) case T: \
Write<DevFmtType_t<T>>(RealOut.Buffer, outBuffers, total, samplesToDo); break;
HANDLE_WRITE(DevFmtByte)
HANDLE_WRITE(DevFmtUByte)
HANDLE_WRITE(DevFmtShort)
HANDLE_WRITE(DevFmtUShort)
HANDLE_WRITE(DevFmtInt)
HANDLE_WRITE(DevFmtUInt)
HANDLE_WRITE(DevFmtFloat)
}
#undef HANDLE_WRITE
total += samplesToDo;
}
@ -2274,7 +2299,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz
}
}
void DeviceBase::handleDisconnect(const char *msg, ...)
void DeviceBase::doDisconnect(std::string msg)
{
const auto mixLock = getWriteMixLock();
@ -2282,29 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...)
{
AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>};
auto &disconnect = std::get<AsyncDisconnectEvent>(evt);
/* NOLINTBEGIN(*-array-to-pointer-decay) */
va_list args, args2;
va_start(args, msg);
va_copy(args2, args);
if(int msglen{vsnprintf(nullptr, 0, msg, args)}; msglen > 0)
{
disconnect.msg.resize(static_cast<uint>(msglen)+1_uz);
vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args2);
}
else
disconnect.msg = "<failed constructing message>";
va_end(args2);
va_end(args);
/* NOLINTEND(*-array-to-pointer-decay) */
while(!disconnect.msg.empty() && disconnect.msg.back() == '\0')
disconnect.msg.pop_back();
disconnect.msg = std::move(msg);
for(ContextBase *ctx : *mContexts.load())
{
RingBuffer *ring{ctx->mAsyncEvents.get()};
auto evt_data = ring->getWriteVector().first;
auto evt_data = ring->getWriteVector()[0];
if(evt_data.len > 0)
{
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);