Support B-Format impulse responses for convolution

This commit is contained in:
Chris Robinson 2020-08-28 05:48:26 -07:00
parent 986a58d5b4
commit 52531d8b72

View File

@ -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);
}