diff options
author | Chris Robinson <[email protected]> | 2023-01-01 19:50:05 -0800 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2023-01-01 19:50:05 -0800 |
commit | 2e98295fd78a45e444529d44b181af8c1b66dc1f (patch) | |
tree | a97e71fc324186995ee6f0e3c355a8439c626127 /alc/effects/convolution.cpp | |
parent | b959aa9ee667730c61e973ce42bc8fb6afe2cf22 (diff) |
Handle UHJ better with convolution reverb
It's now decoded to B-Format while being FFT'd, and processed as B-Format.
Again, not that UHJ should really ever be used for convolution, but it's a
valid format someone may want to use despite the overhead from converting it.
Diffstat (limited to 'alc/effects/convolution.cpp')
-rw-r--r-- | alc/effects/convolution.cpp | 110 |
1 files changed, 66 insertions, 44 deletions
diff --git a/alc/effects/convolution.cpp b/alc/effects/convolution.cpp index 20e5eff7..e88fb0d0 100644 --- a/alc/effects/convolution.cpp +++ b/alc/effects/convolution.cpp @@ -72,7 +72,7 @@ namespace { */ -void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, +void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, const size_t samples) noexcept { #define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break @@ -194,7 +194,7 @@ struct ConvolutionState final : public EffectState { struct ChannelData { alignas(16) FloatBufferLine mBuffer{}; - float mHfScale{}; + float mHfScale{}, mLfScale{}; BandSplitter mFilter{}; float Current[MAX_OUTPUT_CHANNELS]{}; float Target[MAX_OUTPUT_CHANNELS]{}; @@ -235,7 +235,7 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut, for(auto &chan : *mChans) { const al::span<float> src{chan.mBuffer.data(), samplesToDo}; - chan.mFilter.processHfScale(src, chan.mHfScale); + chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } } @@ -243,6 +243,9 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut, void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer) { + using UhjDecoderType = UhjDecoder<512>; + static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; + constexpr uint MaxConvolveAmbiOrder{1u}; mFifoPos = 0; @@ -260,17 +263,21 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff /* An empty buffer doesn't need a convolution filter. */ if(!buffer.storage || buffer.storage->mSampleLen < 1) return; + mChannels = buffer.storage->mChannels; + mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer.storage->mAmbiLayout; + mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer.storage->mAmbiScaling; + mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); + constexpr size_t m{ConvolveUpdateSize/2 + 1}; - auto bytesPerSample = BytesFromFmt(buffer.storage->mType); - auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder); - auto numChannels = ChannelsFromFmt(buffer.storage->mChannels, - minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder)); + const auto bytesPerSample = BytesFromFmt(buffer.storage->mType); + const auto realChannels = buffer.storage->channelsFromFmt(); + const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); mChans = ChannelDataArray::Create(numChannels); /* The impulse response needs to have the same sample rate as the input and * output. The bsinc24 resampler is decent, but there is high-frequency - * attenation that some people may be able to pick up on. Since this is + * attenuation that some people may be able to pick up on. Since this is * called very infrequently, go ahead and use the polyphase resampler. */ PPhaseResampler resampler; @@ -299,27 +306,46 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff mComplexData = std::make_unique<complex_f[]>(complex_length); std::fill_n(mComplexData.get(), complex_length, complex_f{}); - mChannels = buffer.storage->mChannels; - mAmbiLayout = buffer.storage->mAmbiLayout; - mAmbiScaling = buffer.storage->mAmbiScaling; - mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder); + /* Load the samples from the buffer. */ + const size_t srclinelength{RoundUp(buffer.storage->mSampleLen+DecoderPadding, 16)}; + auto srcsamples = std::make_unique<float[]>(srclinelength * numChannels); + std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f); + for(size_t c{0};c < numChannels && c < realChannels;++c) + LoadSamples(srcsamples.get() + srclinelength*c, buffer.samples.data() + bytesPerSample*c, + realChannels, buffer.storage->mType, buffer.storage->mSampleLen); - auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount)); + if(IsUHJ(mChannels)) + { + auto decoder = std::make_unique<UhjDecoderType>(); + std::array<float*,4> samples{}; + for(size_t c{0};c < numChannels;++c) + samples[c] = srcsamples.get() + srclinelength*c; + decoder->decode({samples.data(), numChannels}, buffer.storage->mSampleLen, + buffer.storage->mSampleLen); + } + + auto ressamples = std::make_unique<double[]>(buffer.storage->mSampleLen + + (resampler ? resampledCount : 0)); complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m; for(size_t c{0};c < numChannels;++c) { - /* Load the samples from the buffer, and resample to match the device. */ - LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels, - buffer.storage->mType, buffer.storage->mSampleLen); - if(device->Frequency != buffer.storage->mSampleRate) - resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount, - srcsamples.get()); + /* Resample to match the device. */ + if(resampler) + { + std::copy_n(srcsamples.get() + srclinelength*c, buffer.storage->mSampleLen, + ressamples.get() + resampledCount); + resampler.process(buffer.storage->mSampleLen, ressamples.get()+resampledCount, + resampledCount, ressamples.get()); + } + else + std::copy_n(srcsamples.get() + srclinelength*c, buffer.storage->mSampleLen, + ressamples.get()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; - std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(), + std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(), [](const double d) noexcept -> float { return static_cast<float>(d); }); auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize); @@ -328,7 +354,7 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff { const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; - auto iter = std::copy_n(&srcsamples[done], todo, fftbuffer.begin()); + auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin()); done += todo; std::fill(iter, fftbuffer.end(), std::complex<double>{}); @@ -389,7 +415,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } }; - if(mNumConvolveSegs < 1) + if(mNumConvolveSegs < 1) [[unlikely]] return; mMix = &ConvolutionState::NormalMix; @@ -397,40 +423,36 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot for(auto &chan : *mChans) std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); const float gain{slot->Gain}; - /* TODO: UHJ should be decoded to B-Format and processed that way, since - * there's no telling if it can ever do a direct-out mix (even if the - * device is outputing UHJ, the effect slot can feed another effect that's - * not UHJ). - * - * Not that UHJ should really ever be used for convolution, but it's a - * valid format regardless. - */ - if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut - && target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX - && target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX) - { - mOutTarget = target.RealOut->Buffer; - const uint lidx = target.RealOut->ChannelIndex[FrontLeft]; - const uint ridx = target.RealOut->ChannelIndex[FrontRight]; - (*mChans)[0].Target[lidx] = gain; - (*mChans)[1].Target[ridx] = gain; - } - else if(IsBFormat(mChannels)) + if(IsAmbisonic(mChannels)) { DeviceBase *device{context->mDevice}; - if(device->mAmbiOrder > mAmbiOrder) + if(mChannels == FmtUHJ2 && !device->mUhjEncoder) + { + mMix = &ConvolutionState::UpsampleMix; + (*mChans)[0].mHfScale = 1.0f; + (*mChans)[0].mLfScale = DecoderBase::sWLFScale; + (*mChans)[1].mHfScale = 1.0f; + (*mChans)[1].mLfScale = DecoderBase::sXYLFScale; + (*mChans)[2].mHfScale = 1.0f; + (*mChans)[2].mLfScale = DecoderBase::sXYLFScale; + } + else if(device->mAmbiOrder > mAmbiOrder) { mMix = &ConvolutionState::UpsampleMix; const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); (*mChans)[0].mHfScale = scales[0]; + (*mChans)[0].mLfScale = 1.0f; for(size_t i{1};i < mChans->size();++i) + { (*mChans)[i].mHfScale = scales[1]; + (*mChans)[i].mLfScale = 1.0f; + } } mOutTarget = target.Main->Buffer; auto&& scales = GetAmbiScales(mAmbiScaling); - const uint8_t *index_map{(mChannels == FmtBFormat2D) ? + const uint8_t *index_map{Is2DAmbisonic(mChannels) ? GetAmbi2DLayout(mAmbiLayout).data() : GetAmbiLayout(mAmbiLayout).data()}; @@ -497,7 +519,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot void ConvolutionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut) { - if(mNumConvolveSegs < 1) + if(mNumConvolveSegs < 1) [[unlikely]] return; constexpr size_t m{ConvolveUpdateSize/2 + 1}; |