|
@@ -19,6 +19,7 @@
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
#include "config.h"
|
|
|
|
|
+#include "config_simd.h"
|
|
|
|
|
|
|
|
#include "alu.h"
|
|
#include "alu.h"
|
|
|
|
|
|
|
@@ -26,23 +27,25 @@
|
|
|
#include <array>
|
|
#include <array>
|
|
|
#include <atomic>
|
|
#include <atomic>
|
|
|
#include <cassert>
|
|
#include <cassert>
|
|
|
-#include <chrono>
|
|
|
|
|
-#include <climits>
|
|
|
|
|
|
|
+#include <cmath>
|
|
|
#include <cstdarg>
|
|
#include <cstdarg>
|
|
|
|
|
+#include <cstddef>
|
|
|
#include <cstdint>
|
|
#include <cstdint>
|
|
|
#include <cstdio>
|
|
#include <cstdio>
|
|
|
#include <cstdlib>
|
|
#include <cstdlib>
|
|
|
-#include <functional>
|
|
|
|
|
#include <iterator>
|
|
#include <iterator>
|
|
|
#include <limits>
|
|
#include <limits>
|
|
|
#include <memory>
|
|
#include <memory>
|
|
|
-#include <new>
|
|
|
|
|
#include <optional>
|
|
#include <optional>
|
|
|
|
|
+#include <string>
|
|
|
|
|
+#include <string_view>
|
|
|
#include <utility>
|
|
#include <utility>
|
|
|
|
|
+#include <variant>
|
|
|
|
|
|
|
|
#include "almalloc.h"
|
|
#include "almalloc.h"
|
|
|
#include "alnumbers.h"
|
|
#include "alnumbers.h"
|
|
|
#include "alnumeric.h"
|
|
#include "alnumeric.h"
|
|
|
|
|
+#include "alsem.h"
|
|
|
#include "alspan.h"
|
|
#include "alspan.h"
|
|
|
#include "alstring.h"
|
|
#include "alstring.h"
|
|
|
#include "atomic.h"
|
|
#include "atomic.h"
|
|
@@ -70,6 +73,7 @@
|
|
|
#include "core/mixer/defs.h"
|
|
#include "core/mixer/defs.h"
|
|
|
#include "core/mixer/hrtfdefs.h"
|
|
#include "core/mixer/hrtfdefs.h"
|
|
|
#include "core/resampler_limits.h"
|
|
#include "core/resampler_limits.h"
|
|
|
|
|
+#include "core/storage_formats.h"
|
|
|
#include "core/uhjfilter.h"
|
|
#include "core/uhjfilter.h"
|
|
|
#include "core/voice.h"
|
|
#include "core/voice.h"
|
|
|
#include "core/voice_change.h"
|
|
#include "core/voice_change.h"
|
|
@@ -78,19 +82,18 @@
|
|
|
#include "ringbuffer.h"
|
|
#include "ringbuffer.h"
|
|
|
#include "strutils.h"
|
|
#include "strutils.h"
|
|
|
#include "vecmat.h"
|
|
#include "vecmat.h"
|
|
|
-#include "vector.h"
|
|
|
|
|
|
|
|
|
|
struct CTag;
|
|
struct CTag;
|
|
|
-#ifdef HAVE_SSE
|
|
|
|
|
|
|
+#if HAVE_SSE
|
|
|
struct SSETag;
|
|
struct SSETag;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE2
|
|
|
|
|
|
|
+#if HAVE_SSE2
|
|
|
struct SSE2Tag;
|
|
struct SSE2Tag;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE4_1
|
|
|
|
|
|
|
+#if HAVE_SSE4_1
|
|
|
struct SSE4Tag;
|
|
struct SSE4Tag;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+#if HAVE_NEON
|
|
|
struct NEONTag;
|
|
struct NEONTag;
|
|
|
#endif
|
|
#endif
|
|
|
struct PointTag;
|
|
struct PointTag;
|
|
@@ -135,19 +138,19 @@ float NfcScale{1.0f};
|
|
|
|
|
|
|
|
|
|
|
|
|
using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
|
using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut,
|
|
|
- const al::span<const FloatBufferLine> InSamples, float2 *AccumSamples,
|
|
|
|
|
- const al::span<float,BufferLineSize> TempBuf, HrtfChannelState *ChanState, const size_t IrSize,
|
|
|
|
|
- const size_t BufferSize);
|
|
|
|
|
|
|
+ const al::span<const FloatBufferLine> InSamples, const al::span<float2> AccumSamples,
|
|
|
|
|
+ const al::span<float,BufferLineSize> TempBuf, const al::span<HrtfChannelState> ChanState,
|
|
|
|
|
+ const size_t IrSize, const size_t SamplesToDo);
|
|
|
|
|
|
|
|
HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
|
|
HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_<CTag>};
|
|
|
|
|
|
|
|
inline HrtfDirectMixerFunc SelectHrtfMixer()
|
|
inline HrtfDirectMixerFunc SelectHrtfMixer()
|
|
|
{
|
|
{
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+#if HAVE_NEON
|
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
|
return MixDirectHrtf_<NEONTag>;
|
|
return MixDirectHrtf_<NEONTag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE
|
|
|
|
|
|
|
+#if HAVE_SSE
|
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
|
return MixDirectHrtf_<SSETag>;
|
|
return MixDirectHrtf_<SSETag>;
|
|
|
#endif
|
|
#endif
|
|
@@ -176,7 +179,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab
|
|
|
state->sf = sf;
|
|
state->sf = sf;
|
|
|
state->m = table->m[si];
|
|
state->m = table->m[si];
|
|
|
state->l = (state->m/2) - 1;
|
|
state->l = (state->m/2) - 1;
|
|
|
- state->filter = table->Tab + table->filterOffset[si];
|
|
|
|
|
|
|
+ state->filter = table->Tab.subspan(table->filterOffset[si]);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
|
|
inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
|
|
@@ -186,59 +189,62 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment)
|
|
|
case Resampler::Point:
|
|
case Resampler::Point:
|
|
|
return Resample_<PointTag,CTag>;
|
|
return Resample_<PointTag,CTag>;
|
|
|
case Resampler::Linear:
|
|
case Resampler::Linear:
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+#if HAVE_NEON
|
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
|
return Resample_<LerpTag,NEONTag>;
|
|
return Resample_<LerpTag,NEONTag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE4_1
|
|
|
|
|
|
|
+#if HAVE_SSE4_1
|
|
|
if((CPUCapFlags&CPU_CAP_SSE4_1))
|
|
if((CPUCapFlags&CPU_CAP_SSE4_1))
|
|
|
return Resample_<LerpTag,SSE4Tag>;
|
|
return Resample_<LerpTag,SSE4Tag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE2
|
|
|
|
|
|
|
+#if HAVE_SSE2
|
|
|
if((CPUCapFlags&CPU_CAP_SSE2))
|
|
if((CPUCapFlags&CPU_CAP_SSE2))
|
|
|
return Resample_<LerpTag,SSE2Tag>;
|
|
return Resample_<LerpTag,SSE2Tag>;
|
|
|
#endif
|
|
#endif
|
|
|
return Resample_<LerpTag,CTag>;
|
|
return Resample_<LerpTag,CTag>;
|
|
|
- case Resampler::Cubic:
|
|
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+ case Resampler::Spline:
|
|
|
|
|
+ case Resampler::Gaussian:
|
|
|
|
|
+#if HAVE_NEON
|
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
|
return Resample_<CubicTag,NEONTag>;
|
|
return Resample_<CubicTag,NEONTag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE4_1
|
|
|
|
|
|
|
+#if HAVE_SSE4_1
|
|
|
if((CPUCapFlags&CPU_CAP_SSE4_1))
|
|
if((CPUCapFlags&CPU_CAP_SSE4_1))
|
|
|
return Resample_<CubicTag,SSE4Tag>;
|
|
return Resample_<CubicTag,SSE4Tag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE2
|
|
|
|
|
|
|
+#if HAVE_SSE2
|
|
|
if((CPUCapFlags&CPU_CAP_SSE2))
|
|
if((CPUCapFlags&CPU_CAP_SSE2))
|
|
|
return Resample_<CubicTag,SSE2Tag>;
|
|
return Resample_<CubicTag,SSE2Tag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE
|
|
|
|
|
|
|
+#if HAVE_SSE
|
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
|
return Resample_<CubicTag,SSETag>;
|
|
return Resample_<CubicTag,SSETag>;
|
|
|
#endif
|
|
#endif
|
|
|
return Resample_<CubicTag,CTag>;
|
|
return Resample_<CubicTag,CTag>;
|
|
|
case Resampler::BSinc12:
|
|
case Resampler::BSinc12:
|
|
|
case Resampler::BSinc24:
|
|
case Resampler::BSinc24:
|
|
|
|
|
+ case Resampler::BSinc48:
|
|
|
if(increment > MixerFracOne)
|
|
if(increment > MixerFracOne)
|
|
|
{
|
|
{
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+#if HAVE_NEON
|
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
|
return Resample_<BSincTag,NEONTag>;
|
|
return Resample_<BSincTag,NEONTag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE
|
|
|
|
|
|
|
+#if HAVE_SSE
|
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
|
return Resample_<BSincTag,SSETag>;
|
|
return Resample_<BSincTag,SSETag>;
|
|
|
#endif
|
|
#endif
|
|
|
return Resample_<BSincTag,CTag>;
|
|
return Resample_<BSincTag,CTag>;
|
|
|
}
|
|
}
|
|
|
- /* fall-through */
|
|
|
|
|
|
|
+ [[fallthrough]];
|
|
|
case Resampler::FastBSinc12:
|
|
case Resampler::FastBSinc12:
|
|
|
case Resampler::FastBSinc24:
|
|
case Resampler::FastBSinc24:
|
|
|
-#ifdef HAVE_NEON
|
|
|
|
|
|
|
+ case Resampler::FastBSinc48:
|
|
|
|
|
+#if HAVE_NEON
|
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
if((CPUCapFlags&CPU_CAP_NEON))
|
|
|
return Resample_<FastBSincTag,NEONTag>;
|
|
return Resample_<FastBSincTag,NEONTag>;
|
|
|
#endif
|
|
#endif
|
|
|
-#ifdef HAVE_SSE
|
|
|
|
|
|
|
+#if HAVE_SSE
|
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
if((CPUCapFlags&CPU_CAP_SSE))
|
|
|
return Resample_<FastBSincTag,SSETag>;
|
|
return Resample_<FastBSincTag,SSETag>;
|
|
|
#endif
|
|
#endif
|
|
@@ -268,7 +274,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
|
|
|
case Resampler::Point:
|
|
case Resampler::Point:
|
|
|
case Resampler::Linear:
|
|
case Resampler::Linear:
|
|
|
break;
|
|
break;
|
|
|
- case Resampler::Cubic:
|
|
|
|
|
|
|
+ case Resampler::Spline:
|
|
|
|
|
+ state->emplace<CubicState>(al::span{gSplineFilter.mTable});
|
|
|
|
|
+ break;
|
|
|
|
|
+ case Resampler::Gaussian:
|
|
|
state->emplace<CubicState>(al::span{gGaussianFilter.mTable});
|
|
state->emplace<CubicState>(al::span{gGaussianFilter.mTable});
|
|
|
break;
|
|
break;
|
|
|
case Resampler::FastBSinc12:
|
|
case Resampler::FastBSinc12:
|
|
@@ -279,6 +288,10 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState
|
|
|
case Resampler::BSinc24:
|
|
case Resampler::BSinc24:
|
|
|
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24);
|
|
BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc24);
|
|
|
break;
|
|
break;
|
|
|
|
|
+ case Resampler::FastBSinc48:
|
|
|
|
|
+ case Resampler::BSinc48:
|
|
|
|
|
+ BsincPrepare(increment, &state->emplace<BsincState>(), &gBSinc48);
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
return SelectResampler(resampler, increment);
|
|
return SelectResampler(resampler, increment);
|
|
|
}
|
|
}
|
|
@@ -290,8 +303,8 @@ void DeviceBase::ProcessHrtf(const size_t SamplesToDo)
|
|
|
const size_t lidx{RealOut.ChannelIndex[FrontLeft]};
|
|
const size_t lidx{RealOut.ChannelIndex[FrontLeft]};
|
|
|
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
|
|
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
|
|
|
|
|
|
|
|
- MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData.data(),
|
|
|
|
|
- mHrtfState->mTemp, mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo);
|
|
|
|
|
|
|
+ MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData,
|
|
|
|
|
+ mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo)
|
|
void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo)
|
|
@@ -330,7 +343,8 @@ void DeviceBase::ProcessBs2b(const size_t SamplesToDo)
|
|
|
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
|
|
const size_t ridx{RealOut.ChannelIndex[FrontRight]};
|
|
|
|
|
|
|
|
/* Now apply the BS2B binaural/crossfeed filter. */
|
|
/* 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));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -430,11 +444,19 @@ bool CalcContextParams(ContextBase *ctx)
|
|
|
ctx->mParams.Velocity = rot * vel;
|
|
ctx->mParams.Velocity = rot * vel;
|
|
|
|
|
|
|
|
ctx->mParams.Gain = props->Gain * ctx->mGainBoost;
|
|
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.AirAbsorptionGainHF = props->AirAbsorptionGainHF;
|
|
|
|
|
|
|
|
ctx->mParams.DopplerFactor = props->DopplerFactor;
|
|
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.SourceDistanceModel = props->SourceDistanceModel;
|
|
|
ctx->mParams.mDistanceModel = props->mDistanceModel;
|
|
ctx->mParams.mDistanceModel = props->mDistanceModel;
|
|
@@ -458,23 +480,27 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
|
|
|
slot->Target = props->Target;
|
|
slot->Target = props->Target;
|
|
|
slot->EffectType = props->Type;
|
|
slot->EffectType = props->Type;
|
|
|
slot->mEffectProps = props->Props;
|
|
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))
|
|
if(auto *reverbprops = std::get_if<ReverbProps>(&props->Props))
|
|
|
{
|
|
{
|
|
|
slot->RoomRolloff = reverbprops->RoomRolloffFactor;
|
|
slot->RoomRolloff = reverbprops->RoomRolloffFactor;
|
|
|
- slot->DecayTime = reverbprops->DecayTime;
|
|
|
|
|
- slot->DecayLFRatio = reverbprops->DecayLFRatio;
|
|
|
|
|
- slot->DecayHFRatio = reverbprops->DecayHFRatio;
|
|
|
|
|
- slot->DecayHFLimit = reverbprops->DecayHFLimit;
|
|
|
|
|
slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF;
|
|
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()};
|
|
EffectState *state{props->State.release()};
|
|
@@ -489,9 +515,9 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
|
|
|
/* Otherwise, if it would be deleted send it off with a release event. */
|
|
/* Otherwise, if it would be deleted send it off with a release event. */
|
|
|
RingBuffer *ring{context->mAsyncEvents.get()};
|
|
RingBuffer *ring{context->mAsyncEvents.get()};
|
|
|
auto evt_vec = ring->getWriteVector();
|
|
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;
|
|
evt.mEffectState = oldstate;
|
|
|
ring->writeAdvance(1);
|
|
ring->writeAdvance(1);
|
|
|
}
|
|
}
|
|
@@ -508,14 +534,13 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa
|
|
|
|
|
|
|
|
AtomicReplaceHead(context->mFreeEffectSlotProps, props);
|
|
AtomicReplaceHead(context->mFreeEffectSlotProps, props);
|
|
|
|
|
|
|
|
- EffectTarget output;
|
|
|
|
|
- if(EffectSlot *target{slot->Target})
|
|
|
|
|
- output = EffectTarget{&target->Wet, nullptr};
|
|
|
|
|
- else
|
|
|
|
|
|
|
+ const auto output = [slot,context]() -> EffectTarget
|
|
|
{
|
|
{
|
|
|
|
|
+ if(EffectSlot *target{slot->Target})
|
|
|
|
|
+ return EffectTarget{&target->Wet, nullptr};
|
|
|
DeviceBase *device{context->mDevice};
|
|
DeviceBase *device{context->mDevice};
|
|
|
- output = EffectTarget{&device->Dry, &device->RealOut};
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return EffectTarget{&device->Dry, &device->RealOut};
|
|
|
|
|
+ }();
|
|
|
state->update(context, slot, &slot->mEffectProps, output);
|
|
state->update(context, slot, &slot->mEffectProps, output);
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
@@ -683,8 +708,8 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
|
|
|
/* Don't do anything for < 2nd order. */
|
|
/* Don't do anything for < 2nd order. */
|
|
|
if(order < 2) return;
|
|
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 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 rim1{R[-1+2][static_cast<size_t>(i+2_z)]};
|
|
@@ -698,12 +723,12 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
|
|
|
return ri0*R[last_band + static_cast<size_t>(l-1_z+n)][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,
|
|
|
|
|
|
|
+ static constexpr auto U = [](const int l, const int m, const int n, const size_t last_band,
|
|
|
const AmbiRotateMatrix &R)
|
|
const AmbiRotateMatrix &R)
|
|
|
{
|
|
{
|
|
|
return P(0, l, m, n, last_band, 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)
|
|
const AmbiRotateMatrix &R)
|
|
|
{
|
|
{
|
|
|
using namespace al::numbers;
|
|
using namespace al::numbers;
|
|
@@ -719,7 +744,7 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order)
|
|
|
const float p1{P(-1, l, -m-1, n, last_band, R)};
|
|
const float p1{P(-1, l, -m-1, n, last_band, R)};
|
|
|
return d ? p1*sqrt2_v<float> : (p0 + p1);
|
|
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)
|
|
const AmbiRotateMatrix &R)
|
|
|
{
|
|
{
|
|
|
assert(m != 0);
|
|
assert(m != 0);
|
|
@@ -833,7 +858,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
|
|
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
|
|
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 uint NumSends{Device->NumAuxSends};
|
|
|
|
|
|
|
|
const size_t num_channels{voice->mChans.size()};
|
|
const size_t num_channels{voice->mChans.size()};
|
|
@@ -915,6 +940,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
|
|
case TopBackLeft: return lgain;
|
|
case TopBackLeft: return lgain;
|
|
|
case TopBackCenter: break;
|
|
case TopBackCenter: break;
|
|
|
case TopBackRight: return rgain;
|
|
case TopBackRight: return rgain;
|
|
|
|
|
+ case BottomFrontLeft: return lgain;
|
|
|
|
|
+ case BottomFrontRight: return rgain;
|
|
|
|
|
+ case BottomBackLeft: return lgain;
|
|
|
|
|
+ case BottomBackRight: return rgain;
|
|
|
case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7:
|
|
case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7:
|
|
|
case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14:
|
|
case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14:
|
|
|
case Aux15: case MaxChannels: break;
|
|
case Aux15: case MaxChannels: break;
|
|
@@ -1176,8 +1205,6 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
|
|
}
|
|
}
|
|
|
else for(size_t c{0};c < num_channels;c++)
|
|
else for(size_t c{0};c < num_channels;c++)
|
|
|
{
|
|
{
|
|
|
- using namespace al::numbers;
|
|
|
|
|
-
|
|
|
|
|
/* Skip LFE */
|
|
/* Skip LFE */
|
|
|
if(chans[c].channel == LFE) continue;
|
|
if(chans[c].channel == LFE) continue;
|
|
|
const float pangain{SelectChannelGain(chans[c].channel)};
|
|
const float pangain{SelectChannelGain(chans[c].channel)};
|
|
@@ -1187,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
|
|
* the source position, at full spread (pi*2), each channel is
|
|
|
* left unchanged.
|
|
* 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{
|
|
std::array pos{
|
|
|
lerpf(chans[c].pos[0], xpos, a),
|
|
lerpf(chans[c].pos[0], xpos, a),
|
|
|
lerpf(chans[c].pos[1], ypos, a),
|
|
lerpf(chans[c].pos[1], ypos, a),
|
|
@@ -1303,57 +1330,51 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
|
|
voice->mChans[0].mWetParams[i].Gains.Target);
|
|
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);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -1449,7 +1470,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
|
|
void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context)
|
|
void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context)
|
|
|
{
|
|
{
|
|
|
DeviceBase *Device{context->mDevice};
|
|
DeviceBase *Device{context->mDevice};
|
|
|
- std::array<EffectSlot*,MaxSendCount> SendSlots;
|
|
|
|
|
|
|
+ std::array<EffectSlot*,MaxSendCount> SendSlots{};
|
|
|
|
|
|
|
|
voice->mDirect.Buffer = Device->Dry.Buffer;
|
|
voice->mDirect.Buffer = Device->Dry.Buffer;
|
|
|
for(uint i{0};i < Device->NumAuxSends;i++)
|
|
for(uint i{0};i < Device->NumAuxSends;i++)
|
|
@@ -1466,7 +1487,7 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
|
|
|
|
|
|
|
|
/* Calculate the stepping value */
|
|
/* Calculate the stepping value */
|
|
|
const auto Pitch = static_cast<float>(voice->mFrequency) /
|
|
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})
|
|
if(Pitch > float{MaxPitch})
|
|
|
voice->mStep = MaxPitch<<MixerFracBits;
|
|
voice->mStep = MaxPitch<<MixerFracBits;
|
|
|
else
|
|
else
|
|
@@ -1474,13 +1495,13 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex
|
|
|
voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
|
|
voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState);
|
|
|
|
|
|
|
|
/* Calculate gains */
|
|
/* Calculate gains */
|
|
|
- GainTriplet DryGain;
|
|
|
|
|
- DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
|
|
|
|
|
|
|
+ GainTriplet DryGain{};
|
|
|
|
|
+ DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
|
|
|
props->Direct.Gain * context->mParams.Gain, GainMixMax);
|
|
props->Direct.Gain * context->mParams.Gain, GainMixMax);
|
|
|
DryGain.HF = props->Direct.GainHF;
|
|
DryGain.HF = props->Direct.GainHF;
|
|
|
DryGain.LF = props->Direct.GainLF;
|
|
DryGain.LF = props->Direct.GainLF;
|
|
|
|
|
|
|
|
- std::array<GainTriplet,MaxSendCount> WetGain;
|
|
|
|
|
|
|
+ std::array<GainTriplet,MaxSendCount> WetGain{};
|
|
|
for(uint i{0};i < Device->NumAuxSends;i++)
|
|
for(uint i{0};i < Device->NumAuxSends;i++)
|
|
|
{
|
|
{
|
|
|
WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
|
|
WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) *
|
|
@@ -1502,33 +1523,24 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
|
|
|
voice->mDirect.Buffer = Device->Dry.Buffer;
|
|
voice->mDirect.Buffer = Device->Dry.Buffer;
|
|
|
std::array<EffectSlot*,MaxSendCount> SendSlots{};
|
|
std::array<EffectSlot*,MaxSendCount> SendSlots{};
|
|
|
std::array<float,MaxSendCount> RoomRolloff{};
|
|
std::array<float,MaxSendCount> RoomRolloff{};
|
|
|
- std::bitset<MaxSendCount> UseDryAttnForRoom{0};
|
|
|
|
|
for(uint i{0};i < NumSends;i++)
|
|
for(uint i{0};i < NumSends;i++)
|
|
|
{
|
|
{
|
|
|
SendSlots[i] = props->Send[i].Slot;
|
|
SendSlots[i] = props->Send[i].Slot;
|
|
|
if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
|
|
if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None)
|
|
|
|
|
+ {
|
|
|
SendSlots[i] = nullptr;
|
|
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
|
|
/* NOTE: Contrary to the EFX docs, the effect's room rolloff factor
|
|
|
* applies to the selected distance model along with the source's
|
|
* applies to the selected distance model along with the source's
|
|
|
* room rolloff factor, not necessarily the inverse distance model.
|
|
* 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;
|
|
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;
|
|
voice->mSend[i].Buffer = SendSlots[i]->Wet.Buffer;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Transform source to listener space (convert to head relative) */
|
|
/* Transform source to listener space (convert to head relative) */
|
|
@@ -1663,28 +1675,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
|
|
|
std::array<GainTriplet,MaxSendCount> WetGain{};
|
|
std::array<GainTriplet,MaxSendCount> WetGain{};
|
|
|
for(uint i{0};i < NumSends;i++)
|
|
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;
|
|
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].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;
|
|
WetGain[i].LF = props->Send[i].GainLF;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Distance-based air absorption and initial send decay. */
|
|
/* Distance-based air absorption and initial send decay. */
|
|
|
if(Distance > props->RefDistance) LIKELY
|
|
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
|
|
/* If the source's Auxiliary Send Filter Gain Auto is off, no extra
|
|
|
* adjustment is applied to the send gains.
|
|
* adjustment is applied to the send gains.
|
|
@@ -1694,18 +1712,9 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
|
|
|
if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
|
|
if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f))
|
|
|
continue;
|
|
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};
|
|
const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec};
|
|
|
|
|
|
|
@@ -1719,7 +1728,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
|
|
|
* with the reverb and source rolloff parameters.
|
|
* with the reverb and source rolloff parameters.
|
|
|
*/
|
|
*/
|
|
|
const float baseAttn{DryAttnBase};
|
|
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};
|
|
const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn};
|
|
|
WetGain[i].Base *= gain;
|
|
WetGain[i].Base *= gain;
|
|
|
}
|
|
}
|
|
@@ -1764,7 +1773,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa
|
|
|
/* Adjust pitch based on the buffer and output frequencies, and calculate
|
|
/* Adjust pitch based on the buffer and output frequencies, and calculate
|
|
|
* fixed-point stepping value.
|
|
* 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})
|
|
if(Pitch > float{MaxPitch})
|
|
|
voice->mStep = MaxPitch<<MixerFracBits;
|
|
voice->mStep = MaxPitch<<MixerFracBits;
|
|
|
else
|
|
else
|
|
@@ -1807,9 +1816,9 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state)
|
|
|
{
|
|
{
|
|
|
RingBuffer *ring{context->mAsyncEvents.get()};
|
|
RingBuffer *ring{context->mAsyncEvents.get()};
|
|
|
auto evt_vec = ring->getWriteVector();
|
|
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;
|
|
evt.mId = id;
|
|
|
switch(state)
|
|
switch(state)
|
|
|
{
|
|
{
|
|
@@ -1929,7 +1938,7 @@ void ProcessVoiceChanges(ContextBase *ctx)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
|
|
void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
|
|
|
- const al::span<Voice*> voices)
|
|
|
|
|
|
|
+ const al::span<EffectSlot*> sorted_slots, const al::span<Voice*> voices)
|
|
|
{
|
|
{
|
|
|
ProcessVoiceChanges(ctx);
|
|
ProcessVoiceChanges(ctx);
|
|
|
|
|
|
|
@@ -1937,9 +1946,9 @@ void ProcessParamUpdates(ContextBase *ctx, const al::span<EffectSlot*> slots,
|
|
|
if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY
|
|
if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY
|
|
|
{
|
|
{
|
|
|
bool force{CalcContextParams(ctx)};
|
|
bool force{CalcContextParams(ctx)};
|
|
|
- auto sorted_slots = al::to_address(slots.end());
|
|
|
|
|
|
|
+ auto sorted_slot_base = al::to_address(sorted_slots.begin());
|
|
|
for(EffectSlot *slot : slots)
|
|
for(EffectSlot *slot : slots)
|
|
|
- force |= CalcEffectSlotParams(slot, sorted_slots, ctx);
|
|
|
|
|
|
|
+ force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx);
|
|
|
|
|
|
|
|
for(Voice *voice : voices)
|
|
for(Voice *voice : voices)
|
|
|
{
|
|
{
|
|
@@ -1955,97 +1964,93 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo)
|
|
|
{
|
|
{
|
|
|
ASSUME(SamplesToDo > 0);
|
|
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)
|
|
|
{
|
|
{
|
|
|
- auto auxslots = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)};
|
|
|
|
|
- const al::span<Voice*> voices{ctx->getVoicesSpanAcquired()};
|
|
|
|
|
|
|
+ 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 auto voices = ctx->getVoicesSpanAcquired();
|
|
|
|
|
|
|
|
/* Process pending property updates for objects on the context. */
|
|
/* Process pending property updates for objects on the context. */
|
|
|
- ProcessParamUpdates(ctx, auxslots, voices);
|
|
|
|
|
|
|
+ ProcessParamUpdates(ctx, auxslots, sorted_slots, voices);
|
|
|
|
|
|
|
|
/* Clear auxiliary effect slot mixing buffers. */
|
|
/* 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. */
|
|
/* 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)};
|
|
const Voice::State vstate{voice->mPlayState.load(std::memory_order_acquire)};
|
|
|
if(vstate != Voice::Stopped && vstate != Voice::Pending)
|
|
if(vstate != Voice::Stopped && vstate != Voice::Pending)
|
|
|
voice->mix(vstate, ctx, curtime, SamplesToDo);
|
|
voice->mix(vstate, ctx, curtime, SamplesToDo);
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ std::for_each(voices.begin(), voices.end(), proc_voice);
|
|
|
|
|
|
|
|
/* Process effects. */
|
|
/* Process effects. */
|
|
|
if(!auxslots.empty())
|
|
if(!auxslots.empty())
|
|
|
{
|
|
{
|
|
|
/* Sort the slots into extra storage, so that effect slots come
|
|
/* Sort the slots into extra storage, so that effect slots come
|
|
|
- * before their effect slot target (or their targets' target).
|
|
|
|
|
|
|
+ * before their effect slot target (or their targets' target). Skip
|
|
|
|
|
+ * sorting if it has already been done.
|
|
|
*/
|
|
*/
|
|
|
- const al::span sorted_slots{al::to_address(auxslots.end()), auxslots.size()};
|
|
|
|
|
- /* Skip sorting if it has already been done. */
|
|
|
|
|
if(!sorted_slots[0])
|
|
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. */
|
|
/* There must be at least one slot without a slot target. */
|
|
|
assert(split_point != sorted_slots.end());
|
|
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;
|
|
|
|
|
-
|
|
|
|
|
- --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);
|
|
|
|
|
|
|
+ if(next_target == split_point) UNLIKELY
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ --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()};
|
|
EffectState *state{slot->mEffectState.get()};
|
|
|
state->process(SamplesToDo, slot->Wet.Buffer, state->mOutTarget);
|
|
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. */
|
|
/* 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();
|
|
ctx->mEventSem.post();
|
|
|
- }
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ const auto contexts = al::span{*device->mContexts.load(std::memory_order_acquire)};
|
|
|
|
|
+ std::for_each(contexts.begin(), contexts.end(), proc_context);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -2144,25 +2149,47 @@ void Write(const al::span<const FloatBufferLine> InBuffer, void *OutBuffer, cons
|
|
|
ASSUME(FrameStep > 0);
|
|
ASSUME(FrameStep > 0);
|
|
|
ASSUME(SamplesToDo > 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);
|
|
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;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -2187,10 +2214,10 @@ uint DeviceBase::renderSamples(const uint numSamples)
|
|
|
* also guarantees a stable conversion.
|
|
* also guarantees a stable conversion.
|
|
|
*/
|
|
*/
|
|
|
auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo;
|
|
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
|
|
/* Apply any needed post-process for finalizing the Dry mix to the RealOut
|
|
@@ -2199,7 +2226,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
|
|
|
postProcess(samplesToDo);
|
|
postProcess(samplesToDo);
|
|
|
|
|
|
|
|
/* Apply compression, limiting sample amplitude if needed or desired. */
|
|
/* 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. */
|
|
/* Apply delays and attenuation for mismatched speaker distances. */
|
|
|
if(ChannelDelays)
|
|
if(ChannelDelays)
|
|
@@ -2214,7 +2241,7 @@ uint DeviceBase::renderSamples(const uint numSamples)
|
|
|
return samplesToDo;
|
|
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{};
|
|
FPUCtl mixer_mode{};
|
|
|
uint total{0};
|
|
uint total{0};
|
|
@@ -2222,13 +2249,19 @@ void DeviceBase::renderSamples(const al::span<float*> outBuffers, const uint num
|
|
|
{
|
|
{
|
|
|
const uint samplesToDo{renderSamples(todo)};
|
|
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;
|
|
total += samplesToDo;
|
|
|
}
|
|
}
|
|
@@ -2266,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();
|
|
const auto mixLock = getWriteMixLock();
|
|
|
|
|
|
|
@@ -2274,21 +2307,12 @@ void DeviceBase::handleDisconnect(const char *msg, ...)
|
|
|
{
|
|
{
|
|
|
AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>};
|
|
AsyncEvent evt{std::in_place_type<AsyncDisconnectEvent>};
|
|
|
auto &disconnect = std::get<AsyncDisconnectEvent>(evt);
|
|
auto &disconnect = std::get<AsyncDisconnectEvent>(evt);
|
|
|
-
|
|
|
|
|
- /* NOLINTBEGIN(*-array-to-pointer-decay) */
|
|
|
|
|
- va_list args;
|
|
|
|
|
- va_start(args, msg);
|
|
|
|
|
- int msglen{vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args)};
|
|
|
|
|
- va_end(args);
|
|
|
|
|
- /* NOLINTEND(*-array-to-pointer-decay) */
|
|
|
|
|
-
|
|
|
|
|
- if(msglen < 0 || static_cast<size_t>(msglen) >= disconnect.msg.size())
|
|
|
|
|
- disconnect.msg[sizeof(disconnect.msg)-1] = 0;
|
|
|
|
|
|
|
+ disconnect.msg = std::move(msg);
|
|
|
|
|
|
|
|
for(ContextBase *ctx : *mContexts.load())
|
|
for(ContextBase *ctx : *mContexts.load())
|
|
|
{
|
|
{
|
|
|
RingBuffer *ring{ctx->mAsyncEvents.get()};
|
|
RingBuffer *ring{ctx->mAsyncEvents.get()};
|
|
|
- auto evt_data = ring->getWriteVector().first;
|
|
|
|
|
|
|
+ auto evt_data = ring->getWriteVector()[0];
|
|
|
if(evt_data.len > 0)
|
|
if(evt_data.len > 0)
|
|
|
{
|
|
{
|
|
|
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);
|
|
al::construct_at(reinterpret_cast<AsyncEvent*>(evt_data.buf), evt);
|