diff options
author | Chris Robinson <[email protected]> | 2020-08-28 05:48:26 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2020-08-28 05:48:26 -0700 |
commit | 52531d8b72e3d876b6692a8b72dfeccfd4ebcfb8 (patch) | |
tree | 244154f311cef826f567086b0862b7408c4a097a | |
parent | 986a58d5b4936ec6bd4dca7769d291e5a5961f75 (diff) |
Support B-Format impulse responses for convolution
-rw-r--r-- | alc/effects/convolution.cpp | 124 |
1 files changed, 107 insertions, 17 deletions
diff --git a/alc/effects/convolution.cpp b/alc/effects/convolution.cpp index 3ad912cb..8acf6010 100644 --- a/alc/effects/convolution.cpp +++ b/alc/effects/convolution.cpp @@ -10,8 +10,11 @@ #include "alcontext.h" #include "almalloc.h" #include "alspan.h" +#include "ambidefs.h" +#include "bformatdec.h" #include "buffer_storage.h" #include "effects/base.h" +#include "filters/splitter.h" #include "fmt_traits.h" #include "logging.h" #include "polyphase_resampler.h" @@ -68,12 +71,33 @@ void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep #undef HANDLE_FMT } + +auto GetAmbiScales(AmbiScaling scaletype) noexcept -> const std::array<float,MAX_AMBI_CHANNELS>& +{ + if(scaletype == AmbiScaling::FuMa) return AmbiScale::FromFuMa; + if(scaletype == AmbiScaling::SN3D) return AmbiScale::FromSN3D; + return AmbiScale::FromN3D; +} + +auto GetAmbiLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI_CHANNELS>& +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa; + return AmbiIndex::FromACN; +} + +auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept -> const std::array<uint8_t,MAX_AMBI2D_CHANNELS>& +{ + if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D; + return AmbiIndex::From2D; +} + + using complex_d = std::complex<double>; constexpr size_t ConvolveUpdateSize{1024}; constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; -#define MAX_FILTER_CHANNELS 2 +#define MAX_FILTER_CHANNELS 4 struct ConvolutionFilter final : public EffectBufferBase { @@ -82,7 +106,10 @@ struct ConvolutionFilter final : public EffectBufferBase { complex_d *mInputHistory{}; complex_d *mConvolveFilter[MAX_FILTER_CHANNELS]{}; - FmtChannels mChannels; + FmtChannels mChannels{}; + AmbiLayout mAmbiLayout{}; + AmbiScaling mAmbiScaling{}; + ALuint mAmbiOrder{}; std::unique_ptr<complex_d[]> mComplexData; @@ -100,13 +127,20 @@ struct ConvolutionState final : public EffectState { alignas(16) FloatBufferLine mTempBuffer[MAX_FILTER_CHANNELS]{}; struct { + float mHfScale{}; + BandSplitter mFilter{}; float Current[MAX_OUTPUT_CHANNELS]{}; float Target[MAX_OUTPUT_CHANNELS]{}; - } mGains[MAX_FILTER_CHANNELS]; + } mOutChans[MAX_FILTER_CHANNELS]; ConvolutionState() = default; ~ConvolutionState() override = default; + void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); + void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo); + void (ConvolutionState::*Mix)(const al::span<FloatBufferLine>,const size_t) + {&ConvolutionState::NormalMix}; + void deviceUpdate(const ALCdevice *device) override; EffectBufferBase *createBuffer(const ALCdevice *device, const BufferStorage &buffer) override; void update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps *props, const EffectTarget target) override; @@ -115,7 +149,26 @@ struct ConvolutionState final : public EffectState { DEF_NEWDEL(ConvolutionState) }; -void ConvolutionState::deviceUpdate(const ALCdevice* /*device*/) +void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut, + const size_t samplesToDo) +{ + for(size_t c{0};c < mNumChannels;++c) + MixSamples({mTempBuffer[c].data(), samplesToDo}, samplesOut, mOutChans[c].Current, + mOutChans[c].Target, samplesToDo, 0); +} + +void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut, + const size_t samplesToDo) +{ + for(size_t c{0};c < mNumChannels;++c) + { + const al::span<float> src{mTempBuffer[c].data(), samplesToDo}; + mOutChans[c].mFilter.processHfScale(src, mOutChans[c].mHfScale); + MixSamples(src, samplesOut, mOutChans[c].Current, mOutChans[c].Target, samplesToDo, 0); + } +} + +void ConvolutionState::deviceUpdate(const ALCdevice *device) { mFifoPos = 0; for(auto &buffer : mOutput) @@ -125,8 +178,11 @@ void ConvolutionState::deviceUpdate(const ALCdevice* /*device*/) for(auto &buffer : mTempBuffer) buffer.fill(0.0); - for(auto &e : mGains) + const BandSplitter splitter{400.0f / static_cast<float>(device->Frequency)}; + for(auto &e : mOutChans) { + e.mHfScale = 1.0f; + e.mFilter = splitter; std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); } @@ -139,7 +195,11 @@ EffectBufferBase *ConvolutionState::createBuffer(const ALCdevice *device, if(buffer.mSampleLen < 1) return nullptr; /* FIXME: Support anything. */ - if(buffer.mChannels != FmtMono && buffer.mChannels != FmtStereo) + if(buffer.mChannels != FmtMono && buffer.mChannels != FmtStereo + && buffer.mChannels != FmtBFormat2D && buffer.mChannels != FmtBFormat3D) + return nullptr; + if((buffer.mChannels == FmtBFormat2D || buffer.mChannels == FmtBFormat3D) + && buffer.mAmbiOrder > 1) return nullptr; /* The impulse response needs to have the same sample rate as the input and @@ -176,6 +236,9 @@ EffectBufferBase *ConvolutionState::createBuffer(const ALCdevice *device, filter->mConvolveFilter[c] = filter->mConvolveFilter[c-1] + filter->mNumConvolveSegs*m; filter->mChannels = buffer.mChannels; + filter->mAmbiLayout = buffer.mAmbiLayout; + filter->mAmbiScaling = buffer.mAmbiScaling; + filter->mAmbiOrder = buffer.mAmbiOrder; auto fftbuffer = std::make_unique<std::array<complex_d,ConvolveUpdateSize>>(); auto srcsamples = std::make_unique<double[]>(maxz(buffer.mSampleLen, resampledCount)); @@ -206,18 +269,47 @@ EffectBufferBase *ConvolutionState::createBuffer(const ALCdevice *device, return filter.release(); } -void ConvolutionState::update(const ALCcontext* /*context*/, const ALeffectslot *slot, +void ConvolutionState::update(const ALCcontext *context, const ALeffectslot *slot, const EffectProps* /*props*/, const EffectTarget target) { mFilter = static_cast<ConvolutionFilter*>(slot->Params.mEffectBuffer); - mNumChannels = ChannelsFromFmt(mFilter->mChannels, 1); + mNumChannels = ChannelsFromFmt(mFilter->mChannels, mFilter->mAmbiOrder); + Mix = &ConvolutionState::NormalMix; /* The iFFT'd response is scaled up by the number of bins, so apply the * inverse to the output mixing gain. */ constexpr size_t m{ConvolveUpdateSize/2 + 1}; const float gain{slot->Params.Gain * (1.0f/m)}; - if(mFilter->mChannels == FmtStereo) + if(mFilter->mChannels == FmtBFormat3D || mFilter->mChannels == FmtBFormat2D) + { + ALCdevice *device{context->mDevice.get()}; + if(device->mAmbiOrder > mFilter->mAmbiOrder) + { + Mix = &ConvolutionState::UpsampleMix; + const auto scales = BFormatDec::GetHFOrderScales(mFilter->mAmbiOrder, + device->mAmbiOrder); + mOutChans[0].mHfScale = scales[0]; + for(size_t i{1};i < mNumChannels;++i) + mOutChans[i].mHfScale = scales[1]; + } + mOutTarget = target.Main->Buffer; + + const auto &scales = GetAmbiScales(mFilter->mAmbiScaling); + const uint8_t *index_map{(mFilter->mChannels == FmtBFormat2D) ? + GetAmbi2DLayout(mFilter->mAmbiLayout).data() : + GetAmbiLayout(mFilter->mAmbiLayout).data()}; + + std::array<float,MAX_AMBI_CHANNELS> coeffs{}; + for(size_t c{0u};c < mNumChannels;++c) + { + const size_t acn{index_map[c]}; + coeffs[acn] = scales[acn]; + ComputePanGains(target.Main, coeffs.data(), gain, mOutChans[c].Target); + coeffs[acn] = 0.0f; + } + } + else if(mFilter->mChannels == FmtStereo) { /* TODO: Add a "direct channels" setting for this effect? */ const ALuint lidx{!target.RealOut ? INVALID_CHANNEL_INDEX : @@ -227,8 +319,8 @@ void ConvolutionState::update(const ALCcontext* /*context*/, const ALeffectslot if(lidx != INVALID_CHANNEL_INDEX && ridx != INVALID_CHANNEL_INDEX) { mOutTarget = target.RealOut->Buffer; - mGains[0].Target[lidx] = gain; - mGains[1].Target[ridx] = gain; + mOutChans[0].Target[lidx] = gain; + mOutChans[1].Target[ridx] = gain; } else { @@ -236,8 +328,8 @@ void ConvolutionState::update(const ALCcontext* /*context*/, const ALeffectslot const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs.data(), gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs.data(), gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs.data(), gain, mOutChans[0].Target); + ComputePanGains(target.Main, rcoeffs.data(), gain, mOutChans[1].Target); } } else if(mFilter->mChannels == FmtMono) @@ -245,7 +337,7 @@ void ConvolutionState::update(const ALCcontext* /*context*/, const ALeffectslot const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), gain, mGains[0].Target); + ComputePanGains(target.Main, coeffs.data(), gain, mOutChans[0].Target); } } @@ -327,9 +419,7 @@ void ConvolutionState::process(const size_t samplesToDo, mFilter->mCurrentSegment = curseg; /* Finally, mix to the output. */ - for(size_t c{0};c < mNumChannels;++c) - MixSamples({mTempBuffer[c].data(), samplesToDo}, samplesOut, mGains[c].Current, - mGains[c].Target, samplesToDo, 0); + (this->*Mix)(samplesOut, samplesToDo); } |