Decode UHJ buffers to B-Format for mixing
This should also have an adjustment for the shelf filter. Although it's not clear what the appropriate adjustments should be.
This commit is contained in:
parent
8793055e66
commit
35a0f2665f
@ -671,6 +671,7 @@ set(CORE_OBJS
|
||||
core/logging.h
|
||||
core/mastering.cpp
|
||||
core/mastering.h
|
||||
core/resampler_limits.h
|
||||
core/uhjfilter.cpp
|
||||
core/uhjfilter.h
|
||||
core/mixer/defs.h
|
||||
|
@ -51,7 +51,9 @@
|
||||
#include "atomic.h"
|
||||
#include "core/except.h"
|
||||
#include "inprogext.h"
|
||||
#include "core/logging.h"
|
||||
#include "opthelpers.h"
|
||||
#include "voice.h"
|
||||
|
||||
|
||||
namespace {
|
||||
@ -503,7 +505,7 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size,
|
||||
unpackalign, NameFromUserFmtType(SrcType));
|
||||
|
||||
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
|
||||
ALBuf->UnpackAmbiOrder : 0};
|
||||
ALBuf->UnpackAmbiOrder : ((DstChannels == FmtUHJ2) ? 1 : 0)};
|
||||
|
||||
if((access&AL_PRESERVE_DATA_BIT_SOFT))
|
||||
{
|
||||
@ -646,10 +648,11 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq,
|
||||
SETERR_RETURN(context, AL_INVALID_ENUM,, "Unsupported callback format");
|
||||
|
||||
const ALuint ambiorder{(DstChannels == FmtBFormat2D || DstChannels == FmtBFormat3D) ?
|
||||
ALBuf->UnpackAmbiOrder : 0};
|
||||
ALBuf->UnpackAmbiOrder : ((DstChannels == FmtUHJ2) ? 1 : 0)};
|
||||
|
||||
constexpr uint line_size{BufferLineSize + MaxPostVoiceLoad};
|
||||
al::vector<al::byte,16>(FrameSizeFromFmt(DstChannels, DstType, ambiorder) *
|
||||
size_t{BufferLineSize + (MaxResamplerPadding>>1)}).swap(ALBuf->mData);
|
||||
size_t{line_size}).swap(ALBuf->mData);
|
||||
|
||||
ALBuf->mCallback = callback;
|
||||
ALBuf->mUserData = userptr;
|
||||
|
@ -439,13 +439,13 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL
|
||||
std::memory_order_relaxed);
|
||||
|
||||
ALbuffer *buffer{BufferList->mBuffer};
|
||||
ALuint num_channels{buffer->channelsFromFmt()};
|
||||
ALuint num_channels{(buffer->mChannels==FmtUHJ2) ? 3 : buffer->channelsFromFmt()};
|
||||
voice->mFrequency = buffer->mSampleRate;
|
||||
voice->mFmtChannels = buffer->mChannels;
|
||||
voice->mFmtType = buffer->mType;
|
||||
voice->mSampleSize = buffer->bytesFromFmt();
|
||||
voice->mAmbiLayout = buffer->mAmbiLayout;
|
||||
voice->mAmbiScaling = buffer->mAmbiScaling;
|
||||
voice->mFrameSize = buffer->frameSizeFromFmt();
|
||||
voice->mAmbiLayout = (buffer->mChannels==FmtUHJ2) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
|
||||
voice->mAmbiScaling = (buffer->mChannels==FmtUHJ2) ? AmbiScaling::FuMa : buffer->mAmbiScaling;
|
||||
voice->mAmbiOrder = buffer->mAmbiOrder;
|
||||
|
||||
if(buffer->mCallback) voice->mFlags |= VoiceIsCallback;
|
||||
|
17
alc/alu.cpp
17
alc/alu.cpp
@ -789,23 +789,18 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
||||
|
||||
case FmtBFormat2D:
|
||||
case FmtBFormat3D:
|
||||
DirectChannels = DirectMode::Off;
|
||||
break;
|
||||
|
||||
/* TODO: UHJ2 should be treated as BFormat2D for panning. */
|
||||
case FmtUHJ2:
|
||||
DirectChannels = DirectMode::Off;
|
||||
chans = StereoMap;
|
||||
downmix_gain = 1.0f / 2.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
voice->mFlags &= ~(VoiceHasHrtf | VoiceHasNfc);
|
||||
if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D)
|
||||
if(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtBFormat3D
|
||||
|| voice->mFmtChannels == FmtUHJ2)
|
||||
{
|
||||
/* Special handling for B-Format sources. */
|
||||
|
||||
if(Device->AvgSpeakerDist > 0.0f)
|
||||
if(Device->AvgSpeakerDist > 0.0f && voice->mFmtChannels != FmtUHJ2)
|
||||
{
|
||||
if(!(Distance > std::numeric_limits<float>::epsilon()))
|
||||
{
|
||||
@ -904,7 +899,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con
|
||||
/* Convert the rotation matrix for input ordering and scaling, and
|
||||
* whether input is 2D or 3D.
|
||||
*/
|
||||
const uint8_t *index_map{(voice->mFmtChannels == FmtBFormat2D) ?
|
||||
const uint8_t *index_map{
|
||||
(voice->mFmtChannels == FmtBFormat2D || voice->mFmtChannels == FmtUHJ2) ?
|
||||
GetAmbi2DLayout(voice->mAmbiLayout).data() :
|
||||
GetAmbiLayout(voice->mAmbiLayout).data()};
|
||||
|
||||
@ -1561,7 +1557,8 @@ void CalcSourceParams(Voice *voice, ALCcontext *context, bool force)
|
||||
}
|
||||
|
||||
if((voice->mProps.DirectChannels != DirectMode::Off && voice->mFmtChannels != FmtMono
|
||||
&& voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D)
|
||||
&& voice->mFmtChannels != FmtBFormat2D && voice->mFmtChannels != FmtBFormat3D
|
||||
&& voice->mFmtChannels != FmtUHJ2)
|
||||
|| voice->mProps.mSpatializeMode==SpatializeMode::Off
|
||||
|| (voice->mProps.mSpatializeMode==SpatializeMode::Auto && voice->mFmtChannels != FmtMono))
|
||||
CalcNonAttnSourceParams(voice, &voice->mProps, context);
|
||||
|
140
alc/voice.cpp
140
alc/voice.cpp
@ -55,6 +55,7 @@
|
||||
#include "core/logging.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "core/mixer/hrtfdefs.h"
|
||||
#include "core/resampler_limits.h"
|
||||
#include "hrtf.h"
|
||||
#include "inprogext.h"
|
||||
#include "opthelpers.h"
|
||||
@ -81,8 +82,6 @@ MixerFunc MixSamples{Mix_<CTag>};
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint ResamplerPrePadding{MaxResamplerPadding / 2};
|
||||
|
||||
using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize,
|
||||
const MixHrtfFilter *hrtfparams, const size_t BufferSize);
|
||||
using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples,
|
||||
@ -224,17 +223,32 @@ const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *ds
|
||||
|
||||
|
||||
void LoadSamples(const al::span<Voice::BufferLine> dstSamples, const size_t dstOffset,
|
||||
const al::byte *src, const size_t srcOffset, const size_t srcstep, FmtType srctype,
|
||||
const al::byte *src, const size_t srcOffset, const FmtType srctype, const FmtChannels srcchans,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: \
|
||||
{ \
|
||||
constexpr size_t sampleSize{sizeof(al::FmtTypeTraits<T>::Type)}; \
|
||||
src += srcOffset*srcstep*sampleSize; \
|
||||
for(auto &dst : dstSamples) \
|
||||
if(srcchans == FmtUHJ2) \
|
||||
{ \
|
||||
al::LoadSampleArray<T>(dst.data() + dstOffset, src, srcstep, samples); \
|
||||
src += sampleSize; \
|
||||
constexpr size_t srcstep{2u}; \
|
||||
src += srcOffset*srcstep*sampleSize; \
|
||||
al::LoadSampleArray<T>(dstSamples[0].data() + dstOffset, src, \
|
||||
srcstep, samples); \
|
||||
al::LoadSampleArray<T>(dstSamples[1].data() + dstOffset, \
|
||||
src + sampleSize, srcstep, samples); \
|
||||
std::fill_n(dstSamples[2].data() + dstOffset, samples, 0.0f); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
const size_t srcstep{dstSamples.size()}; \
|
||||
src += srcOffset*srcstep*sampleSize; \
|
||||
for(auto &dst : dstSamples) \
|
||||
{ \
|
||||
al::LoadSampleArray<T>(dst.data() + dstOffset, src, srcstep, \
|
||||
samples); \
|
||||
src += sampleSize; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
break
|
||||
@ -252,10 +266,9 @@ void LoadSamples(const al::span<Voice::BufferLine> dstSamples, const size_t dstO
|
||||
}
|
||||
|
||||
void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
const size_t dataPosInt, const FmtType sampleType, const size_t samplesToLoad,
|
||||
const al::span<Voice::BufferLine> voiceSamples)
|
||||
const size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
|
||||
const size_t samplesToLoad, const al::span<Voice::BufferLine> voiceSamples)
|
||||
{
|
||||
const size_t numChannels{voiceSamples.size()};
|
||||
const uint loopStart{buffer->mLoopStart};
|
||||
const uint loopEnd{buffer->mLoopEnd};
|
||||
ASSUME(loopEnd > loopStart);
|
||||
@ -265,14 +278,14 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
{
|
||||
/* Load what's left to play from the buffer */
|
||||
const size_t remaining{minz(samplesToLoad, buffer->mSampleLen-dataPosInt)};
|
||||
LoadSamples(voiceSamples, ResamplerPrePadding, buffer->mSamples, dataPosInt, numChannels,
|
||||
sampleType, remaining);
|
||||
LoadSamples(voiceSamples, MaxResamplerEdge, buffer->mSamples, dataPosInt, sampleType,
|
||||
sampleChannels, remaining);
|
||||
|
||||
if(const size_t toFill{samplesToLoad - remaining})
|
||||
{
|
||||
for(auto &chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer.data() + ResamplerPrePadding - 1 + remaining;
|
||||
auto srcsamples = chanbuffer.data() + MaxResamplerEdge - 1 + remaining;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
}
|
||||
}
|
||||
@ -281,46 +294,44 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
{
|
||||
/* Load what's left of this loop iteration */
|
||||
const size_t remaining{minz(samplesToLoad, loopEnd-dataPosInt)};
|
||||
LoadSamples(voiceSamples, ResamplerPrePadding, buffer->mSamples, dataPosInt, numChannels,
|
||||
sampleType, remaining);
|
||||
LoadSamples(voiceSamples, MaxResamplerEdge, buffer->mSamples, dataPosInt, sampleType,
|
||||
sampleChannels, remaining);
|
||||
|
||||
/* Load repeats of the loop to fill the buffer. */
|
||||
const auto loopSize = static_cast<size_t>(loopEnd - loopStart);
|
||||
size_t samplesLoaded{remaining};
|
||||
while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)})
|
||||
{
|
||||
LoadSamples(voiceSamples, ResamplerPrePadding + samplesLoaded, buffer->mSamples,
|
||||
loopStart, numChannels, sampleType, toFill);
|
||||
LoadSamples(voiceSamples, MaxResamplerEdge + samplesLoaded, buffer->mSamples,
|
||||
loopStart, sampleType, sampleChannels, toFill);
|
||||
samplesLoaded += toFill;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBufferCallback(VoiceBufferItem *buffer, const size_t numCallbackSamples,
|
||||
const FmtType sampleType, const size_t samplesToLoad,
|
||||
const FmtType sampleType, const FmtChannels sampleChannels, const size_t samplesToLoad,
|
||||
const al::span<Voice::BufferLine> voiceSamples)
|
||||
{
|
||||
const size_t numChannels{voiceSamples.size()};
|
||||
/* Load what's left to play from the buffer */
|
||||
const size_t remaining{minz(samplesToLoad, numCallbackSamples)};
|
||||
LoadSamples(voiceSamples, ResamplerPrePadding, buffer->mSamples, 0, numChannels, sampleType,
|
||||
LoadSamples(voiceSamples, MaxResamplerEdge, buffer->mSamples, 0, sampleType, sampleChannels,
|
||||
remaining);
|
||||
|
||||
if(const size_t toFill{samplesToLoad - remaining})
|
||||
{
|
||||
for(auto &chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer.data() + ResamplerPrePadding - 1 + remaining;
|
||||
auto srcsamples = chanbuffer.data() + MaxResamplerEdge - 1 + remaining;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
size_t dataPosInt, const FmtType sampleType, const size_t samplesToLoad,
|
||||
const al::span<Voice::BufferLine> voiceSamples)
|
||||
size_t dataPosInt, const FmtType sampleType, const FmtChannels sampleChannels,
|
||||
const size_t samplesToLoad, const al::span<Voice::BufferLine> voiceSamples)
|
||||
{
|
||||
const size_t numChannels{voiceSamples.size()};
|
||||
/* Crawl the buffer queue to fill in the temp buffer */
|
||||
size_t samplesLoaded{0};
|
||||
while(buffer && samplesLoaded != samplesToLoad)
|
||||
@ -334,8 +345,8 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
}
|
||||
|
||||
const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)};
|
||||
LoadSamples(voiceSamples, ResamplerPrePadding+samplesLoaded, buffer->mSamples, dataPosInt,
|
||||
numChannels, sampleType, remaining);
|
||||
LoadSamples(voiceSamples, MaxResamplerEdge+samplesLoaded, buffer->mSamples, dataPosInt,
|
||||
sampleType, sampleChannels, remaining);
|
||||
|
||||
samplesLoaded += remaining;
|
||||
if(samplesLoaded == samplesToLoad)
|
||||
@ -350,7 +361,7 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem,
|
||||
size_t chanidx{0};
|
||||
for(auto &chanbuffer : voiceSamples)
|
||||
{
|
||||
auto srcsamples = chanbuffer.data() + ResamplerPrePadding - 1 + samplesLoaded;
|
||||
auto srcsamples = chanbuffer.data() + MaxResamplerEdge - 1 + samplesLoaded;
|
||||
std::fill_n(srcsamples + 1, toFill, *srcsamples);
|
||||
++chanidx;
|
||||
}
|
||||
@ -517,6 +528,8 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
else if UNLIKELY(!BufferListItem)
|
||||
Counter = std::min(Counter, 64u);
|
||||
|
||||
const uint PostPadding{MaxResamplerEdge +
|
||||
((mFmtChannels==FmtUHJ2) ? uint{UhjDecoder::sFilterDelay} : 0u)};
|
||||
uint buffers_done{0u};
|
||||
uint OutPos{0u};
|
||||
do {
|
||||
@ -531,7 +544,7 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
/* Calculate the last read src sample pos. */
|
||||
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
|
||||
/* +1 to get the src sample count, include padding. */
|
||||
DataSize64 += 1 + ResamplerPrePadding;
|
||||
DataSize64 += 1 + PostPadding;
|
||||
|
||||
/* Result is guaranteed to be <= BufferLineSize+ResamplerPrePadding
|
||||
* since we won't use more src samples than dst samples+padding.
|
||||
@ -543,18 +556,18 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
uint64_t DataSize64{DstBufferSize};
|
||||
/* Calculate the end src sample pos, include padding. */
|
||||
DataSize64 = (DataSize64*increment + DataPosFrac) >> MixerFracBits;
|
||||
DataSize64 += ResamplerPrePadding;
|
||||
DataSize64 += PostPadding;
|
||||
|
||||
if(DataSize64 <= BufferLineSize + ResamplerPrePadding)
|
||||
if(DataSize64 <= LineSize - MaxResamplerEdge)
|
||||
SrcBufferSize = static_cast<uint>(DataSize64);
|
||||
else
|
||||
{
|
||||
/* If the source size got saturated, we can't fill the desired
|
||||
* dst size. Figure out how many samples we can actually mix.
|
||||
*/
|
||||
SrcBufferSize = BufferLineSize + ResamplerPrePadding;
|
||||
SrcBufferSize = LineSize - MaxResamplerEdge;
|
||||
|
||||
DataSize64 = SrcBufferSize - ResamplerPrePadding;
|
||||
DataSize64 = SrcBufferSize - PostPadding;
|
||||
DataSize64 = ((DataSize64<<MixerFracBits) - DataPosFrac) / increment;
|
||||
if(DataSize64 < DstBufferSize)
|
||||
{
|
||||
@ -563,6 +576,7 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
*/
|
||||
DstBufferSize = static_cast<uint>(DataSize64) & ~3u;
|
||||
}
|
||||
ASSUME(DstBufferSize > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -570,11 +584,8 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
{
|
||||
if(SrcBufferSize > mNumCallbackSamples)
|
||||
{
|
||||
const size_t FrameSize{mChans.size() * mSampleSize};
|
||||
ASSUME(FrameSize > 0);
|
||||
|
||||
const size_t byteOffset{mNumCallbackSamples*FrameSize};
|
||||
const size_t needBytes{SrcBufferSize*FrameSize - byteOffset};
|
||||
const size_t byteOffset{mNumCallbackSamples*mFrameSize};
|
||||
const size_t needBytes{SrcBufferSize*mFrameSize - byteOffset};
|
||||
|
||||
const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData,
|
||||
&BufferListItem->mSamples[byteOffset], static_cast<int>(needBytes))};
|
||||
@ -584,7 +595,7 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
{
|
||||
mFlags |= VoiceCallbackStopped;
|
||||
mNumCallbackSamples += static_cast<uint>(static_cast<uint>(gotBytes) /
|
||||
FrameSize);
|
||||
mFrameSize);
|
||||
}
|
||||
else
|
||||
mNumCallbackSamples = SrcBufferSize;
|
||||
@ -595,7 +606,8 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
{
|
||||
for(auto &chanbuffer : mVoiceSamples)
|
||||
{
|
||||
auto srciter = chanbuffer.data() + ResamplerPrePadding;
|
||||
auto srciter = chanbuffer.data() + MaxResamplerEdge;
|
||||
auto srcend = chanbuffer.data() + MaxResamplerPadding;
|
||||
|
||||
/* When loading from a voice that ended prematurely, only take
|
||||
* the samples that get closest to 0 amplitude. This helps
|
||||
@ -603,29 +615,41 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
*/
|
||||
auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool
|
||||
{ return std::abs(lhs) < std::abs(rhs); };
|
||||
srciter = std::min_element(srciter, srciter+(MaxResamplerPadding>>1), abs_lt);
|
||||
srciter = std::min_element(srciter, srcend, abs_lt);
|
||||
|
||||
std::fill(srciter+1, chanbuffer.data() + ResamplerPrePadding + SrcBufferSize,
|
||||
*srciter);
|
||||
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerPadding;
|
||||
std::fill(srciter+1, chanbuffer.data() + SrcBufferSize, *srciter);
|
||||
}
|
||||
}
|
||||
else if((mFlags&VoiceIsStatic))
|
||||
LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, SrcBufferSize,
|
||||
mVoiceSamples);
|
||||
else if((mFlags&VoiceIsCallback))
|
||||
LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, SrcBufferSize,
|
||||
mVoiceSamples);
|
||||
else
|
||||
LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, SrcBufferSize,
|
||||
mVoiceSamples);
|
||||
{
|
||||
if((mFlags&VoiceIsStatic))
|
||||
LoadBufferStatic(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels,
|
||||
SrcBufferSize, mVoiceSamples);
|
||||
else if((mFlags&VoiceIsCallback))
|
||||
LoadBufferCallback(BufferListItem, mNumCallbackSamples, mFmtType, mFmtChannels,
|
||||
SrcBufferSize, mVoiceSamples);
|
||||
else
|
||||
LoadBufferQueue(BufferListItem, BufferLoopItem, DataPosInt, mFmtType, mFmtChannels,
|
||||
SrcBufferSize, mVoiceSamples);
|
||||
|
||||
if(mDecoder)
|
||||
{
|
||||
std::array<float*,3> samples{{mVoiceSamples[0].data() + MaxResamplerEdge,
|
||||
mVoiceSamples[1].data() + MaxResamplerEdge,
|
||||
mVoiceSamples[2].data() + MaxResamplerEdge}};
|
||||
const size_t srcOffset{(increment*DstBufferSize + DataPosFrac)>>MixerFracBits};
|
||||
SrcBufferSize = SrcBufferSize - PostPadding + MaxResamplerEdge;
|
||||
mDecoder->decode(samples, SrcBufferSize, srcOffset);
|
||||
}
|
||||
}
|
||||
|
||||
ASSUME(DstBufferSize > 0);
|
||||
auto voiceSamples = mVoiceSamples.begin();
|
||||
for(auto &chandata : mChans)
|
||||
{
|
||||
/* Resample, then apply ambisonic upsampling as needed. */
|
||||
float *ResampledData{Resample(&mResampleState,
|
||||
voiceSamples->data() + ResamplerPrePadding, DataPosFrac, increment,
|
||||
voiceSamples->data() + MaxResamplerEdge, DataPosFrac, increment,
|
||||
{Device->ResampledData, DstBufferSize})};
|
||||
if((mFlags&VoiceIsAmbisonic))
|
||||
chandata.mAmbiSplitter.processHfScale({ResampledData, DstBufferSize},
|
||||
@ -720,11 +744,8 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
{
|
||||
if(SrcSamplesDone < mNumCallbackSamples)
|
||||
{
|
||||
const size_t FrameSize{mChans.size() * mSampleSize};
|
||||
ASSUME(FrameSize > 0);
|
||||
|
||||
const size_t byteOffset{SrcSamplesDone*FrameSize};
|
||||
const size_t byteEnd{mNumCallbackSamples*FrameSize};
|
||||
const size_t byteOffset{SrcSamplesDone*mFrameSize};
|
||||
const size_t byteEnd{mNumCallbackSamples*mFrameSize};
|
||||
al::byte *data{BufferListItem->mSamples};
|
||||
std::copy(data+byteOffset, data+byteEnd, data);
|
||||
mNumCallbackSamples -= SrcSamplesDone;
|
||||
@ -802,6 +823,11 @@ void Voice::mix(const State vstate, ALCcontext *Context, const uint SamplesToDo)
|
||||
|
||||
void Voice::prepare(ALCdevice *device)
|
||||
{
|
||||
if(mFmtChannels == FmtUHJ2 && !mDecoder)
|
||||
mDecoder = std::make_unique<UhjDecoder>();
|
||||
else if(mFmtChannels != FmtUHJ2)
|
||||
mDecoder = nullptr;
|
||||
|
||||
/* Clear the stepping value explicitly so the mixer knows not to mix this
|
||||
* until the update gets applied.
|
||||
*/
|
||||
|
15
alc/voice.h
15
alc/voice.h
@ -15,6 +15,7 @@
|
||||
#include "core/filters/splitter.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "core/mixer/hrtfdefs.h"
|
||||
#include "core/uhjfilter.h"
|
||||
#include "vector.h"
|
||||
|
||||
struct ALCcontext;
|
||||
@ -37,6 +38,12 @@ enum class DirectMode : unsigned char {
|
||||
};
|
||||
|
||||
|
||||
/* Maximum number of extra source samples that may need to be loaded, for
|
||||
* resampling or conversion purposes.
|
||||
*/
|
||||
constexpr uint MaxPostVoiceLoad{MaxResamplerEdge + UhjDecoder::sFilterDelay};
|
||||
|
||||
|
||||
enum {
|
||||
AF_None = 0,
|
||||
AF_LowPass = 1,
|
||||
@ -191,11 +198,13 @@ struct Voice {
|
||||
FmtChannels mFmtChannels;
|
||||
FmtType mFmtType;
|
||||
uint mFrequency;
|
||||
uint mSampleSize;
|
||||
uint mFrameSize;
|
||||
AmbiLayout mAmbiLayout;
|
||||
AmbiScaling mAmbiScaling;
|
||||
uint mAmbiOrder;
|
||||
|
||||
std::unique_ptr<UhjDecoder> mDecoder;
|
||||
|
||||
/** Current target parameters used for mixing. */
|
||||
uint mStep{0};
|
||||
|
||||
@ -218,7 +227,9 @@ struct Voice {
|
||||
* now current (which may be overwritten if the buffer data is still
|
||||
* available).
|
||||
*/
|
||||
using BufferLine = std::array<float,BufferLineSize+MaxResamplerPadding>;
|
||||
static constexpr size_t LineSize{BufferLineSize + MaxResamplerPadding +
|
||||
UhjDecoder::sFilterDelay};
|
||||
using BufferLine = std::array<float,LineSize>;
|
||||
al::vector<BufferLine,16> mVoiceSamples{2};
|
||||
|
||||
struct ChannelData {
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/resampler_limits.h"
|
||||
|
||||
struct HrtfChannelState;
|
||||
struct HrtfFilter;
|
||||
@ -19,12 +20,6 @@ constexpr int MixerFracBits{12};
|
||||
constexpr int MixerFracOne{1 << MixerFracBits};
|
||||
constexpr int MixerFracMask{MixerFracOne - 1};
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
|
||||
constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */
|
||||
|
||||
|
||||
|
12
core/resampler_limits.h
Normal file
12
core/resampler_limits.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef CORE_RESAMPLER_LIMITS_H
|
||||
#define CORE_RESAMPLER_LIMITS_H
|
||||
|
||||
/* Maximum number of samples to pad on the ends of a buffer for resampling.
|
||||
* Note that the padding is symmetric (half at the beginning and half at the
|
||||
* end)!
|
||||
*/
|
||||
constexpr int MaxResamplerPadding{48};
|
||||
|
||||
constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1};
|
||||
|
||||
#endif /* CORE_RESAMPLER_LIMITS_H */
|
@ -14,6 +14,8 @@
|
||||
|
||||
namespace {
|
||||
|
||||
static_assert(Uhj2Encoder::sFilterDelay==UhjDecoder::sFilterDelay, "UHJ filter delays mismatch");
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
const PhaseShifterT<Uhj2Encoder::sFilterDelay*2> PShift{};
|
||||
@ -90,3 +92,68 @@ void Uhj2Encoder::encode(const FloatBufferSpan LeftOut, const FloatBufferSpan Ri
|
||||
std::copy(mS.cbegin()+SamplesToDo, mS.cbegin()+SamplesToDo+sFilterDelay, mS.begin());
|
||||
std::copy(mD.cbegin()+SamplesToDo, mD.cbegin()+SamplesToDo+sFilterDelay, mD.begin());
|
||||
}
|
||||
|
||||
|
||||
/* Decoding UHJ is done as:
|
||||
*
|
||||
* S = Left + Right
|
||||
* D = Left - Right
|
||||
*
|
||||
* W = 0.981530*S + 0.197484*j(0.828347*D + 0.767835*T)
|
||||
* X = 0.418504*S - j(0.828347*D + 0.767835*T)
|
||||
* Y = 0.795954*D - 0.676406*T + j(0.186626*S)
|
||||
* Z = 1.023332*Q
|
||||
*
|
||||
* where j is a +90 degree phase shift. 3-channel UHJ excludes Q, while 2-
|
||||
* channel excludes Q and T. The B-Format signal reconstructed from 2-channel
|
||||
* UHJ should not be run through a normal B-Format decoder, as it needs
|
||||
* different shelf filters.
|
||||
*/
|
||||
void UhjDecoder::decode(const al::span<float*, 3> Samples, const size_t SamplesToDo,
|
||||
const size_t ForwardSamples)
|
||||
{
|
||||
ASSUME(SamplesToDo > 0);
|
||||
|
||||
/* S = Left + Right */
|
||||
for(size_t i{0};i < SamplesToDo+sFilterDelay;++i)
|
||||
mS[i] = Samples[0][i] + Samples[1][i];
|
||||
|
||||
/* D = Left - Right */
|
||||
for(size_t i{0};i < SamplesToDo+sFilterDelay;++i)
|
||||
mD[i] = Samples[0][i] - Samples[1][i];
|
||||
|
||||
/* T */
|
||||
for(size_t i{0};i < SamplesToDo+sFilterDelay;++i)
|
||||
mT[i] = Samples[2][i];
|
||||
|
||||
float *woutput{Samples[0]};
|
||||
float *xoutput{Samples[1]};
|
||||
float *youtput{Samples[2]};
|
||||
|
||||
/* Precompute j(0.828347*D + 0.767835*T) and store in xoutput. */
|
||||
auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin());
|
||||
std::transform(mD.cbegin(), mD.cbegin()+SamplesToDo+sFilterDelay, mT.cbegin(), tmpiter,
|
||||
[](const float d, const float t) noexcept { return 0.828347f*d + 0.767835f*t; });
|
||||
std::copy_n(mTemp.cbegin()+ForwardSamples, mDTHistory.size(), mDTHistory.begin());
|
||||
PShift.process({xoutput, SamplesToDo}, mTemp.data());
|
||||
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
{
|
||||
/* W = 0.981530*S + 0.197484*j(0.828347*D + 0.767835*T) */
|
||||
woutput[i] = 0.981530f*mS[i] + 0.197484f*xoutput[i];
|
||||
/* X = 0.418504*S - j(0.828347*D + 0.767835*T) */
|
||||
xoutput[i] = 0.418504f*mS[i] - xoutput[i];
|
||||
}
|
||||
|
||||
/* Precompute j*S and store in youtput. */
|
||||
tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin());
|
||||
std::copy_n(mS.cbegin(), SamplesToDo+sFilterDelay, tmpiter);
|
||||
std::copy_n(mTemp.cbegin()+ForwardSamples, mSHistory.size(), mSHistory.begin());
|
||||
PShift.process({youtput, SamplesToDo}, mTemp.data());
|
||||
|
||||
for(size_t i{0};i < SamplesToDo;++i)
|
||||
{
|
||||
/* Y = 0.795954*D - 0.676406*T + j(0.186626*S) */
|
||||
youtput[i] = 0.795954f*mD[i] - 0.676406f*mT[i] + 0.186626f*youtput[i];
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "bufferline.h"
|
||||
#include "resampler_limits.h"
|
||||
|
||||
|
||||
struct Uhj2Encoder {
|
||||
@ -32,4 +33,23 @@ struct Uhj2Encoder {
|
||||
DEF_NEWDEL(Uhj2Encoder)
|
||||
};
|
||||
|
||||
|
||||
struct UhjDecoder {
|
||||
constexpr static size_t sFilterDelay{128};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mS{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mD{};
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge+sFilterDelay> mT{};
|
||||
|
||||
alignas(16) std::array<float,sFilterDelay-1> mDTHistory{};
|
||||
alignas(16) std::array<float,sFilterDelay-1> mSHistory{};
|
||||
|
||||
alignas(16) std::array<float,BufferLineSize+MaxResamplerEdge + sFilterDelay*2> mTemp{};
|
||||
|
||||
void decode(const al::span<float*,3> Samples, const size_t SamplesToDo,
|
||||
const size_t ForwardSamples);
|
||||
|
||||
DEF_NEWDEL(UhjDecoder)
|
||||
};
|
||||
|
||||
#endif /* CORE_UHJFILTER_H */
|
||||
|
Loading…
Reference in New Issue
Block a user