diff options
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | al/buffer.cpp | 9 | ||||
-rw-r--r-- | al/source.cpp | 8 | ||||
-rw-r--r-- | alc/alu.cpp | 17 | ||||
-rw-r--r-- | alc/voice.cpp | 140 | ||||
-rw-r--r-- | alc/voice.h | 15 | ||||
-rw-r--r-- | core/mixer/defs.h | 7 | ||||
-rw-r--r-- | core/resampler_limits.h | 12 | ||||
-rw-r--r-- | core/uhjfilter.cpp | 67 | ||||
-rw-r--r-- | core/uhjfilter.h | 20 |
10 files changed, 214 insertions, 82 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e4f06ef..bd64c9d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/al/buffer.cpp b/al/buffer.cpp index f63d5c71..436be9ae 100644 --- a/al/buffer.cpp +++ b/al/buffer.cpp @@ -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; diff --git a/al/source.cpp b/al/source.cpp index b2bb0d75..dd9e6cc7 100644 --- a/al/source.cpp +++ b/al/source.cpp @@ -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; diff --git a/alc/alu.cpp b/alc/alu.cpp index 67bb8ebc..86cbefa5 100644 --- a/alc/alu.cpp +++ b/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); diff --git a/alc/voice.cpp b/alc/voice.cpp index f9eca51c..c3e3dca2 100644 --- a/alc/voice.cpp +++ b/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) \ + { \ + 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 \ { \ - al::LoadSampleArray<T>(dst.data() + dstOffset, src, srcstep, samples); \ - src += sampleSize; \ + 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. */ diff --git a/alc/voice.h b/alc/voice.h index 96975efa..8f3476f1 100644 --- a/alc/voice.h +++ b/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 { diff --git a/core/mixer/defs.h b/core/mixer/defs.h index e8e7be6c..ba304f22 100644 --- a/core/mixer/defs.h +++ b/core/mixer/defs.h @@ -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 */ diff --git a/core/resampler_limits.h b/core/resampler_limits.h new file mode 100644 index 00000000..9d4cefda --- /dev/null +++ b/core/resampler_limits.h @@ -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 */ diff --git a/core/uhjfilter.cpp b/core/uhjfilter.cpp index bfcb83a3..d535522c 100644 --- a/core/uhjfilter.cpp +++ b/core/uhjfilter.cpp @@ -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]; + } +} diff --git a/core/uhjfilter.h b/core/uhjfilter.h index 13beea1e..b07488e5 100644 --- a/core/uhjfilter.h +++ b/core/uhjfilter.h @@ -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 */ |