update openal

This commit is contained in:
AzaezelX 2024-06-30 14:35:57 -05:00
parent 62f3b93ff9
commit 6721a6b021
287 changed files with 33851 additions and 27325 deletions

View file

@ -22,24 +22,24 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
@ -50,35 +50,37 @@ constexpr float QFactor{5.0f};
struct AutowahState final : public EffectState {
/* Effect parameters */
float mAttackRate;
float mReleaseRate;
float mResonanceGain;
float mPeakGain;
float mFreqMinNorm;
float mBandwidthNorm;
float mEnvDelay;
float mAttackRate{};
float mReleaseRate{};
float mResonanceGain{};
float mPeakGain{};
float mFreqMinNorm{};
float mBandwidthNorm{};
float mEnvDelay{};
/* Filter components derived from the envelope. */
struct {
float cos_w0;
float alpha;
} mEnv[BufferLineSize];
struct FilterParam {
float cos_w0{};
float alpha{};
};
std::array<FilterParam,BufferLineSize> mEnv;
struct {
struct ChannelData {
uint mTargetChannel{InvalidChannelIndex};
/* Effect filters' history. */
struct {
float z1, z2;
} mFilter;
struct FilterHistory {
float z1{}, z2{};
};
FilterHistory mFilter;
/* Effect gains for each output channel */
float mCurrentGain;
float mTargetGain;
} mChans[MaxAmbiChannels];
float mCurrentGain{};
float mTargetGain{};
};
std::array<ChannelData,MaxAmbiChannels> mChans;
/* Effects buffers */
alignas(16) float mBufferOut[BufferLineSize];
alignas(16) FloatBufferLine mBufferOut{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -86,8 +88,6 @@ struct AutowahState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(AutowahState)
};
void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -118,18 +118,19 @@ void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<AutowahProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)};
mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency));
mAttackRate = std::exp(-1.0f / (props.AttackTime*frequency));
mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency));
/* 0-20dB Resonance Peak gain */
mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale);
mResonanceGain = std::sqrt(std::log10(props.Resonance)*10.0f / 3.0f);
mPeakGain = 1.0f - std::log10(props.PeakGain / GainScale);
mFreqMinNorm = MinFreq / frequency;
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
@ -155,23 +156,22 @@ void AutowahState::process(const size_t samplesToDo,
float env_delay{mEnvDelay};
for(size_t i{0u};i < samplesToDo;i++)
{
float w0, sample, a;
/* Envelope follower described on the book: Audio Effects, Theory,
* Implementation and Application.
*/
sample = peak_gain * std::fabs(samplesIn[0][i]);
a = (sample > env_delay) ? attack_rate : release_rate;
const float sample{peak_gain * std::fabs(samplesIn[0][i])};
const float a{(sample > env_delay) ? attack_rate : release_rate};
env_delay = lerpf(sample, env_delay, a);
/* Calculate the cos and alpha components for this sample's filter. */
w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v<float>*2.0f);
const float w0{std::min(bandwidth*env_delay + freq_min, 0.46f) *
(al::numbers::pi_v<float>*2.0f)};
mEnv[i].cos_w0 = std::cos(w0);
mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
}
mEnvDelay = env_delay;
auto chandata = std::begin(mChans);
auto chandata = mChans.begin();
for(const auto &insamples : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
@ -194,18 +194,18 @@ void AutowahState::process(const size_t samplesToDo,
{
const float alpha{mEnv[i].alpha};
const float cos_w0{mEnv[i].cos_w0};
float input, output;
float a[3], b[3];
b[0] = 1.0f + alpha*res_gain;
b[1] = -2.0f * cos_w0;
b[2] = 1.0f - alpha*res_gain;
a[0] = 1.0f + alpha/res_gain;
a[1] = -2.0f * cos_w0;
a[2] = 1.0f - alpha/res_gain;
const std::array b{
1.0f + alpha*res_gain,
-2.0f * cos_w0,
1.0f - alpha*res_gain};
const std::array a{
1.0f + alpha/res_gain,
-2.0f * cos_w0,
1.0f - alpha/res_gain};
input = insamples[i];
output = input*(b[0]/a[0]) + z1;
const float input{insamples[i]};
const float output{input*(b[0]/a[0]) + z1};
z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2;
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
mBufferOut[i] = output;
@ -214,8 +214,8 @@ void AutowahState::process(const size_t samplesToDo,
chandata->mFilter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo);
++chandata;
}
}

View file

@ -4,23 +4,27 @@
#include "core/effects/base.h"
EffectStateFactory *NullStateFactory_getFactory(void);
EffectStateFactory *ReverbStateFactory_getFactory(void);
EffectStateFactory *StdReverbStateFactory_getFactory(void);
EffectStateFactory *AutowahStateFactory_getFactory(void);
EffectStateFactory *ChorusStateFactory_getFactory(void);
EffectStateFactory *CompressorStateFactory_getFactory(void);
EffectStateFactory *DistortionStateFactory_getFactory(void);
EffectStateFactory *EchoStateFactory_getFactory(void);
EffectStateFactory *EqualizerStateFactory_getFactory(void);
EffectStateFactory *FlangerStateFactory_getFactory(void);
EffectStateFactory *FshifterStateFactory_getFactory(void);
EffectStateFactory *ModulatorStateFactory_getFactory(void);
EffectStateFactory *PshifterStateFactory_getFactory(void);
EffectStateFactory* VmorpherStateFactory_getFactory(void);
/* This is a user config option for modifying the overall output of the reverb
* effect.
*/
inline float ReverbBoost{1.0f};
EffectStateFactory *DedicatedStateFactory_getFactory(void);
EffectStateFactory *ConvolutionStateFactory_getFactory(void);
EffectStateFactory *NullStateFactory_getFactory();
EffectStateFactory *ReverbStateFactory_getFactory();
EffectStateFactory *ChorusStateFactory_getFactory();
EffectStateFactory *AutowahStateFactory_getFactory();
EffectStateFactory *CompressorStateFactory_getFactory();
EffectStateFactory *DistortionStateFactory_getFactory();
EffectStateFactory *EchoStateFactory_getFactory();
EffectStateFactory *EqualizerStateFactory_getFactory();
EffectStateFactory *FshifterStateFactory_getFactory();
EffectStateFactory *ModulatorStateFactory_getFactory();
EffectStateFactory *PshifterStateFactory_getFactory();
EffectStateFactory* VmorpherStateFactory_getFactory();
EffectStateFactory *DedicatedStateFactory_getFactory();
EffectStateFactory *ConvolutionStateFactory_getFactory();
#endif /* EFFECTS_BASE_H */

View file

@ -22,34 +22,44 @@
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <limits>
#include <variant>
#include <vector>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/cubic_tables.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "core/resampler_limits.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f});
constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f});
constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2});
constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2});
struct ChorusState final : public EffectState {
al::vector<float,16> mDelayBuffer;
std::vector<float> mDelayBuffer;
uint mOffset{0};
uint mLfoOffset{0};
@ -58,16 +68,17 @@ struct ChorusState final : public EffectState {
uint mLfoDisp{0};
/* Calculated delays to apply to the left and right outputs. */
uint mModDelays[2][BufferLineSize];
std::array<std::array<uint,BufferLineSize>,2> mModDelays{};
/* Temp storage for the modulated left and right outputs. */
alignas(16) float mBuffer[2][BufferLineSize];
alignas(16) std::array<FloatBufferLine,2> mBuffer{};
/* Gains for left and right outputs. */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
/* effect parameters */
ChorusWaveform mWaveform{};
@ -78,66 +89,70 @@ struct ChorusState final : public EffectState {
void calcTriangleDelays(const size_t todo);
void calcSinusoidDelays(const size_t todo);
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
void deviceUpdate(const DeviceBase *device, const float MaxDelay);
void update(const ContextBase *context, const EffectSlot *slot, const ChorusWaveform waveform,
const float delay, const float depth, const float feedback, const float rate,
int phase, const EffectTarget target);
DEF_NEWDEL(ChorusState)
void deviceUpdate(const DeviceBase *device, const BufferStorage*) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) final;
};
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
{
constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay);
const auto frequency = static_cast<float>(Device->Frequency);
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)};
if(maxlen != mDelayBuffer.size())
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
e.Current.fill(0.0f);
e.Target.fill(0.0f);
}
}
void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
const EffectProps *props, const EffectTarget target)
void ChorusState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props_, const EffectTarget target)
{
constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits};
auto &props = std::get<ChorusProps>(*props_);
/* The LFO depth is scaled to be relative to the sample delay. Clamp the
* delay and depth to allow enough padding for resampling.
*/
const DeviceBase *device{Context->mDevice};
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mWaveform = props->Chorus.Waveform;
mWaveform = props.Waveform;
mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
const auto stepscale = float{frequency * gCubicTable.sTableSteps};
mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay);
mDepth = std::min(static_cast<float>(mDelay) * props.Depth,
static_cast<float>(mDelay - mindelay));
mFeedback = props->Chorus.Feedback;
mFeedback = props.Feedback;
/* Gains for left and right sides */
static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2});
auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw;
auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw;
const bool ispairwise{device->mRenderMode == RenderMode::Pairwise};
const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw};
const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw};
/* Attenuate the outputs by -3dB, since we duplicate a single mono input to
* separate left/right outputs.
*/
const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v<float>);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target);
float rate{props->Chorus.Rate};
if(!(rate > 0.0f))
if(!(props.Rate > 0.0f))
{
mLfoOffset = 0;
mLfoRange = 1;
@ -149,7 +164,9 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
/* Calculate LFO coefficient (number of samples per cycle). Limit the
* max range to avoid overflow when calculating the displacement.
*/
uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))};
static constexpr int range_limit{std::numeric_limits<int>::max()/360 - 180};
const auto range = std::round(frequency / props.Rate);
const uint lfo_range{float2uint(std::min(range, float{range_limit}))};
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
mLfoRange = lfo_range;
@ -164,8 +181,8 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
}
/* Calculate lfo phase displacement */
int phase{props->Chorus.Phase};
if(phase < 0) phase = 360 + phase;
auto phase = props.Phase;
if(phase < 0) phase += 360;
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
}
}
@ -178,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@ -188,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@ -219,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
const float depth{mDepth};
const int delay{mDelay};
ASSUME(lfo_range > 0);
ASSUME(todo > 0);
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
{
const float offset_norm{static_cast<float>(offset) * lfo_scale};
@ -229,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
};
uint offset{mLfoOffset};
ASSUME(lfo_range > offset);
auto ldelays = mModDelays[0].begin();
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[0][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
offset = (mLfoOffset+mLfoDisp) % lfo_range;
auto rdelays = mModDelays[1].begin();
for(size_t i{0};i < todo;)
{
size_t rem{minz(todo-i, lfo_range-offset)};
do {
mModDelays[1][i++] = gen_lfo(offset++);
} while(--rem);
if(offset == lfo_range)
offset = 0;
const size_t rem{std::min(todo-i, size_t{lfo_range-offset})};
rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); });
if(offset == lfo_range) offset = 0;
i += rem;
}
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
@ -255,10 +264,10 @@ void ChorusState::calcSinusoidDelays(const size_t todo)
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t bufmask{mDelayBuffer.size()-1};
const auto delaybuf = al::span{mDelayBuffer};
const size_t bufmask{delaybuf.size()-1};
const float feedback{mFeedback};
const uint avgdelay{(static_cast<uint>(mDelay) + MixerFracHalf) >> MixerFracBits};
float *RESTRICT delaybuf{mDelayBuffer.data()};
uint offset{mOffset};
if(mWaveform == ChorusWaveform::Sinusoid)
@ -266,35 +275,39 @@ void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBu
else /*if(mWaveform == ChorusWaveform::Triangle)*/
calcTriangleDelays(samplesToDo);
const uint *RESTRICT ldelays{mModDelays[0]};
const uint *RESTRICT rdelays{mModDelays[1]};
float *RESTRICT lbuffer{al::assume_aligned<16>(mBuffer[0])};
float *RESTRICT rbuffer{al::assume_aligned<16>(mBuffer[1])};
const auto ldelays = al::span{mModDelays[0]};
const auto rdelays = al::span{mModDelays[1]};
const auto lbuffer = al::span{mBuffer[0]};
const auto rbuffer = al::span{mBuffer[1]};
for(size_t i{0u};i < samplesToDo;++i)
{
// Feed the buffer's input first (necessary for delays < 1).
delaybuf[offset&bufmask] = samplesIn[0][i];
// Tap for the left output.
uint delay{offset - (ldelays[i]>>MixerFracBits)};
float mu{static_cast<float>(ldelays[i]&MixerFracMask) * (1.0f/MixerFracOne)};
lbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
size_t delay{offset - (ldelays[i] >> gCubicTable.sTableBits)};
size_t phase{ldelays[i] & gCubicTable.sTableMask};
lbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) +
delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) +
delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) +
delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase);
// Tap for the right output.
delay = offset - (rdelays[i]>>MixerFracBits);
mu = static_cast<float>(rdelays[i]&MixerFracMask) * (1.0f/MixerFracOne);
rbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
delay = offset - (rdelays[i] >> gCubicTable.sTableBits);
phase = rdelays[i] & gCubicTable.sTableMask;
rbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) +
delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) +
delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) +
delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase);
// Accumulate feedback from the average delay of the taps.
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
++offset;
}
MixSamples({lbuffer, samplesToDo}, samplesOut, mGains[0].Current, mGains[0].Target,
MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target,
samplesToDo, 0);
MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target,
MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target,
samplesToDo, 0);
mOffset = offset;
@ -306,15 +319,6 @@ struct ChorusStateFactory final : public EffectStateFactory {
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
/* Flanger is basically a chorus with a really short delay. They can both use
* the same processing functions, so piggyback flanger on the chorus functions.
*/
struct FlangerStateFactory final : public EffectStateFactory {
al::intrusive_ptr<EffectState> create() override
{ return al::intrusive_ptr<EffectState>{new ChorusState{}}; }
};
} // namespace
EffectStateFactory *ChorusStateFactory_getFactory()
@ -322,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory()
static ChorusStateFactory ChorusFactory{};
return &ChorusFactory;
}
EffectStateFactory *FlangerStateFactory_getFactory()
{
static FlangerStateFactory FlangerFactory{};
return &FlangerFactory;
}

View file

@ -32,48 +32,49 @@
#include "config.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
namespace {
#define AMP_ENVELOPE_MIN 0.5f
#define AMP_ENVELOPE_MAX 2.0f
constexpr float AmpEnvelopeMin{0.5f};
constexpr float AmpEnvelopeMax{2.0f};
#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */
#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */
constexpr float AttackTime{0.1f}; /* 100ms to rise from min to max */
constexpr float ReleaseTime{0.2f}; /* 200ms to drop from max to min */
struct CompressorState final : public EffectState {
/* Effect gains for each channel */
struct {
struct TargetGain {
uint mTarget{InvalidChannelIndex};
float mGain{1.0f};
} mChans[MaxAmbiChannels];
};
std::array<TargetGain,MaxAmbiChannels> mChans;
/* Effect parameters */
bool mEnabled{true};
float mAttackMult{1.0f};
float mReleaseMult{1.0f};
float mEnvFollower{1.0f};
alignas(16) FloatBufferLine mGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -81,8 +82,6 @@ struct CompressorState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(CompressorState)
};
void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*)
@ -90,20 +89,20 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage
/* Number of samples to do a full attack and release (non-integer sample
* counts are okay).
*/
const float attackCount{static_cast<float>(device->Frequency) * ATTACK_TIME};
const float releaseCount{static_cast<float>(device->Frequency) * RELEASE_TIME};
const float attackCount{static_cast<float>(device->Frequency) * AttackTime};
const float releaseCount{static_cast<float>(device->Frequency) * ReleaseTime};
/* Calculate per-sample multipliers to attack and release at the desired
* rates.
*/
mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount);
mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount);
mAttackMult = std::pow(AmpEnvelopeMax/AmpEnvelopeMin, 1.0f/attackCount);
mReleaseMult = std::pow(AmpEnvelopeMin/AmpEnvelopeMax, 1.0f/releaseCount);
}
void CompressorState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
{
mEnabled = props->Compressor.OnOff;
mEnabled = std::get<CompressorProps>(*props).OnOff;
mOutTarget = target.Main->Buffer;
auto set_channel = [this](size_t idx, uint outchan, float outgain)
@ -117,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot,
void CompressorState::process(const size_t samplesToDo,
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
{
float gains[256];
const size_t td{minz(256, samplesToDo-base)};
/* Generate the per-sample gains from the signal envelope. */
float env{mEnvFollower};
if(mEnabled)
for(size_t i{0u};i < samplesToDo;++i)
{
for(size_t i{0u};i < td;++i)
{
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN,
AMP_ENVELOPE_MAX)};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
gains[i] = 1.0f / env;
}
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and
* off.
/* Clamp the absolute amplitude to the defined envelope limits,
* then attack or release the envelope to reach it.
*/
for(size_t i{0u};i < td;++i)
{
const float amplitude{1.0f};
if(amplitude > env)
env = minf(env*mAttackMult, amplitude);
else if(amplitude < env)
env = maxf(env*mReleaseMult, amplitude);
const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin,
AmpEnvelopeMax)};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
gains[i] = 1.0f / env;
}
/* Apply the reciprocal of the envelope to normalize the volume
* (compress the dynamic range).
*/
mGains[i] = 1.0f / env;
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = std::cbegin(mChans);
for(const auto &input : samplesIn)
}
else
{
/* Same as above, except the amplitude is forced to 1. This helps
* ensure smooth gain changes when the compressor is turned on and off.
*/
for(size_t i{0u};i < samplesToDo;++i)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const float *RESTRICT src{input.data() + base};
float *RESTRICT dst{samplesOut[outidx].data() + base};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < td;i++)
dst[i] += src[i] * gains[i] * gain;
}
}
++chan;
}
const float amplitude{1.0f};
if(amplitude > env)
env = std::min(env*mAttackMult, amplitude);
else if(amplitude < env)
env = std::max(env*mReleaseMult, amplitude);
base += td;
mGains[i] = 1.0f / env;
}
}
mEnvFollower = env;
/* Now compress the signal amplitude to output. */
auto chan = mChans.cbegin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTarget};
if(outidx != InvalidChannelIndex)
{
const auto dst = al::span{samplesOut[outidx]};
const float gain{chan->mGain};
if(!(std::fabs(gain) > GainSilenceThreshold))
{
for(size_t i{0u};i < samplesToDo;++i)
dst[i] += input[i] * mGains[i] * gain;
}
}
++chan;
}
}

View file

@ -3,13 +3,15 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <complex>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <stdint.h>
#include <utility>
#include <vector>
#include <variant>
#ifdef HAVE_SSE_INTRINSICS
#include <xmmintrin.h>
@ -17,7 +19,6 @@
#include <arm_neon.h>
#endif
#include "albyte.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
@ -30,56 +31,85 @@
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/splitter.h"
#include "core/fmt_traits.h"
#include "core/mixer.h"
#include "core/uhjfilter.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "pffft.h"
#include "polyphase_resampler.h"
#include "vecmat.h"
#include "vector.h"
namespace {
/* Convolution reverb is implemented using a segmented overlap-add method. The
* impulse response is broken up into multiple segments of 128 samples, and
* each segment has an FFT applied with a 256-sample buffer (the latter half
* left silent) to get its frequency-domain response. The resulting response
* has its positive/non-mirrored frequencies saved (129 bins) in each segment.
/* Convolution is implemented using a segmented overlap-add method. The impulse
* response is split into multiple segments of 128 samples, and each segment
* has an FFT applied with a 256-sample buffer (the latter half left silent) to
* get its frequency-domain response. The resulting response has its positive/
* non-mirrored frequencies saved (129 bins) in each segment. Note that since
* the 0- and half-frequency bins are real for a real signal, their imaginary
* components are always 0 and can be dropped, allowing their real components
* to be combined so only 128 complex values are stored for the 129 bins.
*
* Input samples are similarly broken up into 128-sample segments, with an FFT
* applied to each new incoming segment to get its 129 bins. A history of FFT'd
* input segments is maintained, equal to the length of the impulse response.
* Input samples are similarly broken up into 128-sample segments, with a 256-
* sample FFT applied to each new incoming segment to get its 129 bins. A
* history of FFT'd input segments is maintained, equal to the number of
* impulse response segments.
*
* To apply the reverberation, each impulse response segment is convolved with
* To apply the convolution, each impulse response segment is convolved with
* its paired input segment (using complex multiplies, far cheaper than FIRs),
* accumulating into a 256-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for next time.
* accumulating into a 129-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for the next input segment.
*
* An inverse FFT is then applied to the accumulated FFT buffer to get a 256-
* sample time-domain response for output, which is split in two halves. The
* first half is the 128-sample output, and the second half is a 128-sample
* (really, 127) delayed extension, which gets added to the output next time.
* Convolving two time-domain responses of lengths N and M results in a time-
* domain signal of length N+M-1, and this holds true regardless of the
* convolution being applied in the frequency domain, so these "overflow"
* samples need to be accounted for.
* Convolving two time-domain responses of length N results in a time-domain
* signal of length N*2 - 1, and this holds true regardless of the convolution
* being applied in the frequency domain, so these "overflow" samples need to
* be accounted for.
*
* To avoid a delay with gathering enough input samples to apply an FFT with,
* the first segment is applied directly in the time-domain as the samples come
* in. Once enough have been retrieved, the FFT is applied on the input and
* it's paired with the remaining (FFT'd) filter segments for processing.
* To avoid a delay with gathering enough input samples for the FFT, the first
* segment is applied directly in the time-domain as the samples come in. Once
* enough have been retrieved, the FFT is applied on the input and it's paired
* with the remaining (FFT'd) filter segments for processing.
*/
void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
const size_t samples) noexcept
template<FmtType SrcType>
inline void LoadSampleArray(const al::span<float> dst, const std::byte *src,
const std::size_t channel, const std::size_t srcstep) noexcept
{
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
using TypeTraits = al::FmtTypeTraits<SrcType>;
using SampleType = typename TypeTraits::Type;
const auto converter = TypeTraits{};
assert(channel < srcstep);
const auto srcspan = al::span{reinterpret_cast<const SampleType*>(src), dst.size()*srcstep};
auto ssrc = srcspan.cbegin();
std::generate(dst.begin(), dst.end(), [converter,channel,srcstep,&ssrc]
{
const auto ret = converter(ssrc[channel]);
ssrc += ptrdiff_t(srcstep);
return ret;
});
}
void LoadSamples(const al::span<float> dst, const std::byte *src, const size_t channel,
const size_t srcstep, const FmtType srctype) noexcept
{
#define HANDLE_FMT(T) case T: LoadSampleArray<T>(dst, src, channel, srcstep); break
switch(srctype)
{
HANDLE_FMT(FmtUByte);
HANDLE_FMT(FmtShort);
HANDLE_FMT(FmtInt);
HANDLE_FMT(FmtFloat);
HANDLE_FMT(FmtDouble);
HANDLE_FMT(FmtMulaw);
@ -87,47 +117,50 @@ void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep,
/* FIXME: Handle ADPCM decoding here. */
case FmtIMA4:
case FmtMSADPCM:
std::fill_n(dst, samples, 0.0f);
std::fill(dst.begin(), dst.end(), 0.0f);
break;
}
#undef HANDLE_FMT
}
inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept
constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept
{
switch(scaletype)
{
case AmbiScaling::FuMa: return AmbiScale::FromFuMa();
case AmbiScaling::SN3D: return AmbiScale::FromSN3D();
case AmbiScaling::UHJ: return AmbiScale::FromUHJ();
case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa};
case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D};
case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ};
case AmbiScaling::N3D: break;
}
return AmbiScale::FromN3D();
return al::span{AmbiScale::FromN3D};
}
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
return AmbiIndex::FromACN();
if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa};
return al::span{AmbiIndex::FromACN};
}
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept
{
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
return AmbiIndex::FromACN2D();
if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D};
return al::span{AmbiIndex::FromACN2D};
}
struct ChanMap {
constexpr float sin30{0.5f};
constexpr float cos30{0.866025403785f};
constexpr float sin45{al::numbers::sqrt2_v<float>*0.5f};
constexpr float cos45{al::numbers::sqrt2_v<float>*0.5f};
constexpr float sin110{ 0.939692620786f};
constexpr float cos110{-0.342020143326f};
struct ChanPosMap {
Channel channel;
float angle;
float elevation;
std::array<float,3> pos;
};
constexpr float Deg2Rad(float x) noexcept
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
using complex_f = std::complex<float>;
@ -135,10 +168,11 @@ constexpr size_t ConvolveUpdateSize{256};
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *RESTRICT filter)
void apply_fir(al::span<float> dst, const al::span<const float> input, const al::span<const float,ConvolveUpdateSamples> filter)
{
auto src = input.begin();
#ifdef HAVE_SSE_INTRINSICS
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
@ -148,39 +182,40 @@ void apply_fir(al::span<float> dst, const float *RESTRICT src, const float *REST
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
}
++src;
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
output = _mm_cvtss_f32(r4);
++src;
}
return _mm_cvtss_f32(r4);
});
#elif defined(HAVE_NEON)
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j]));
r4 = vaddq_f32(r4, vrev64q_f32(r4));
output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
++src;
}
r4 = vaddq_f32(r4, vrev64q_f32(r4));
return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
});
#else
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&src,filter]
{
float ret{0.0f};
for(size_t j{0};j < ConvolveUpdateSamples;++j)
ret += src[j] * filter[j];
output = ret;
++src;
}
return ret;
});
#endif
}
struct ConvolutionState final : public EffectState {
FmtChannels mChannels{};
AmbiLayout mAmbiLayout{};
@ -188,11 +223,13 @@ struct ConvolutionState final : public EffectState {
uint mAmbiOrder{};
size_t mFifoPos{0};
std::array<float,ConvolveUpdateSamples*2> mInput{};
alignas(16) std::array<float,ConvolveUpdateSamples*2> mInput{};
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
alignas(16) std::array<complex_f,ConvolveUpdateSize> mFftBuffer{};
PFFFTSetup mFft{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftBuffer{};
alignas(16) std::array<float,ConvolveUpdateSize> mFftWorkBuffer{};
size_t mCurrentSegment{0};
size_t mNumConvolveSegs{0};
@ -201,12 +238,11 @@ struct ConvolutionState final : public EffectState {
alignas(16) FloatBufferLine mBuffer{};
float mHfScale{}, mLfScale{};
BandSplitter mFilter{};
float Current[MAX_OUTPUT_CHANNELS]{};
float Target[MAX_OUTPUT_CHANNELS]{};
std::array<float,MaxOutputChannels> Current{};
std::array<float,MaxOutputChannels> Target{};
};
using ChannelDataArray = al::FlexArray<ChannelData>;
std::unique_ptr<ChannelDataArray> mChans;
std::unique_ptr<complex_f[]> mComplexData;
std::vector<ChannelData> mChans;
al::vector<float,16> mComplexData;
ConvolutionState() = default;
@ -222,24 +258,22 @@ struct ConvolutionState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ConvolutionState)
};
void ConvolutionState::NormalMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target,
samplesToDo, 0);
for(auto &chan : mChans)
MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current,
chan.Target, samplesToDo, 0);
}
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
const size_t samplesToDo)
{
for(auto &chan : *mChans)
for(auto &chan : mChans)
{
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
const auto src = al::span{chan.mBuffer}.first(samplesToDo);
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
}
@ -251,19 +285,23 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
using UhjDecoderType = UhjDecoder<512>;
static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding;
constexpr uint MaxConvolveAmbiOrder{1u};
static constexpr uint MaxConvolveAmbiOrder{1u};
if(!mFft)
mFft = PFFFTSetup{ConvolveUpdateSize, PFFFT_REAL};
mFifoPos = 0;
mInput.fill(0.0f);
decltype(mFilter){}.swap(mFilter);
decltype(mOutput){}.swap(mOutput);
mFftBuffer.fill(complex_f{});
mFftBuffer.fill(0.0f);
mFftWorkBuffer.fill(0.0f);
mCurrentSegment = 0;
mNumConvolveSegs = 0;
mChans = nullptr;
mComplexData = nullptr;
decltype(mChans){}.swap(mChans);
decltype(mComplexData){}.swap(mComplexData);
/* An empty buffer doesn't need a convolution filter. */
if(!buffer || buffer->mSampleLen < 1) return;
@ -271,14 +309,12 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
mChannels = buffer->mChannels;
mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
constexpr size_t m{ConvolveUpdateSize/2 + 1};
const auto bytesPerSample = BytesFromFmt(buffer->mType);
const auto realChannels = buffer->channelsFromFmt();
const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder);
mChans = ChannelDataArray::Create(numChannels);
mChans.resize(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
@ -293,7 +329,7 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
buffer->mSampleRate);
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
for(auto &e : *mChans)
for(auto &e : mChans)
e.mFilter = splitter;
mFilter.resize(numChannels, {});
@ -305,126 +341,150 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag
* segment is allocated to simplify handling.
*/
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
mNumConvolveSegs = std::max(mNumConvolveSegs, 2_uz) - 1_uz;
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
mComplexData = std::make_unique<complex_f[]>(complex_length);
std::fill_n(mComplexData.get(), complex_length, complex_f{});
const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)};
mComplexData.resize(complex_length, 0.0f);
/* Load the samples from the buffer. */
const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)};
auto srcsamples = std::make_unique<float[]>(srclinelength * numChannels);
std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f);
auto srcsamples = std::vector<float>(srclinelength * numChannels);
std::fill(srcsamples.begin(), srcsamples.end(), 0.0f);
for(size_t c{0};c < numChannels && c < realChannels;++c)
LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c,
realChannels, buffer->mType, buffer->mSampleLen);
LoadSamples(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen),
buffer->mData.data(), c, realChannels, buffer->mType);
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;
samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(srclinelength*c));
decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen);
}
auto ressamples = std::make_unique<double[]>(buffer->mSampleLen +
(resampler ? resampledCount : 0));
complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m;
auto ressamples = std::vector<double>(buffer->mSampleLen + (resampler ? resampledCount : 0));
auto ffttmp = al::vector<float,16>(ConvolveUpdateSize);
auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize);
auto filteriter = mComplexData.begin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize);
for(size_t c{0};c < numChannels;++c)
{
auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen);
/* Resample to match the device. */
if(resampler)
{
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen,
ressamples.get() + resampledCount);
resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount,
resampledCount, ressamples.get());
auto restmp = al::span{ressamples}.subspan(resampledCount, buffer->mSampleLen);
std::copy(bufsamples.cbegin(), bufsamples.cend(), restmp.begin());
resampler.process(restmp, al::span{ressamples}.first(resampledCount));
}
else
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get());
std::copy(bufsamples.cbegin(), bufsamples.cend(), ressamples.begin());
/* 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(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(),
const size_t first_size{std::min(size_t{resampledCount}, ConvolveUpdateSamples)};
auto sampleseg = al::span{ressamples.cbegin(), first_size};
std::transform(sampleseg.cbegin(), sampleseg.cend(), mFilter[c].rbegin(),
[](const double d) noexcept -> float { return static_cast<float>(d); });
auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize);
size_t done{first_size};
for(size_t s{0};s < mNumConvolveSegs;++s)
{
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
const size_t todo{std::min(resampledCount-done, ConvolveUpdateSamples)};
sampleseg = al::span{ressamples}.subspan(done, todo);
auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin());
/* Apply a double-precision forward FFT for more precise frequency
* measurements.
*/
auto iter = std::copy(sampleseg.cbegin(), sampleseg.cend(), fftbuffer.begin());
done += todo;
std::fill(iter, fftbuffer.end(), std::complex<double>{});
forward_fft(al::span{fftbuffer});
forward_fft(al::as_span(fftbuffer));
filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter);
/* Convert to, and pack in, a float buffer for PFFFT. Note that the
* first bin stores the real component of the half-frequency bin in
* the imaginary component. Also scale the FFT by its length so the
* iFFT'd output will be normalized.
*/
static constexpr float fftscale{1.0f / float{ConvolveUpdateSize}};
for(size_t i{0};i < ConvolveUpdateSamples;++i)
{
ffttmp[i*2 ] = static_cast<float>(fftbuffer[i].real()) * fftscale;
ffttmp[i*2 + 1] = static_cast<float>((i == 0) ?
fftbuffer[ConvolveUpdateSamples].real() : fftbuffer[i].imag()) * fftscale;
}
/* Reorder backward to make it suitable for pffft_zconvolve and the
* subsequent pffft_transform(..., PFFFT_BACKWARD).
*/
mFft.zreorder(ffttmp.data(), al::to_address(filteriter), PFFFT_BACKWARD);
filteriter += ConvolveUpdateSize;
}
}
}
void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps* /*props*/, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
/* NOTE: Stereo and Rear are slightly different from normal mixing (as
* defined in alu.cpp). These are 45 degrees from center, rather than the
* 30 degrees used there.
*
* TODO: LFE is not mixed to output. This will require each buffer channel
/* TODO: LFE is not mixed to output. This will require each buffer channel
* to have its own output target since the main mixing buffer won't have an
* LFE channel (due to being B-Format).
*/
static constexpr ChanMap MonoMap[1]{
{ FrontCenter, 0.0f, 0.0f }
}, StereoMap[2]{
{ FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }
}, RearMap[2]{
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, QuadMap[4]{
{ FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) },
{ BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) }
}, X51Map[6]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) }
}, X61Map[7]{
{ FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
}, X71Map[8]{
{ FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) },
{ FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) },
{ FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) },
{ LFE, 0.0f, 0.0f },
{ BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) },
{ BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) },
{ SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) },
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
static constexpr std::array MonoMap{
ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}}
};
static constexpr std::array StereoMap{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
};
static constexpr std::array RearMap{
ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}},
ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}},
};
static constexpr std::array QuadMap{
ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}},
ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}},
ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}},
ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}},
};
static constexpr std::array X51Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}},
ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}},
};
static constexpr std::array X61Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f} },
ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f} },
ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f} },
};
static constexpr std::array X71Map{
ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}},
ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}},
ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}},
ChanPosMap{LFE, {}},
ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}},
ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}},
ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}},
ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}},
};
if(mNumConvolveSegs < 1) UNLIKELY
return;
auto &props = std::get<ConvolutionProps>(*props_);
mMix = &ConvolutionState::NormalMix;
for(auto &chan : *mChans)
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
for(auto &chan : mChans)
std::fill(chan.Target.begin(), chan.Target.end(), 0.0f);
const float gain{slot->Gain};
if(IsAmbisonic(mChannels))
{
@ -432,49 +492,68 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
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;
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[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;
mChans[i].mHfScale = scales[1];
mChans[i].mLfScale = 1.0f;
}
}
mOutTarget = target.Main->Buffer;
auto&& scales = GetAmbiScales(mAmbiScaling);
const uint8_t *index_map{Is2DAmbisonic(mChannels) ?
GetAmbi2DLayout(mAmbiLayout).data() :
GetAmbiLayout(mAmbiLayout).data()};
alu::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f};
N.normalize();
alu::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f};
V.normalize();
/* Build and normalize right-vector */
alu::Vector U{N.cross_product(V)};
U.normalize();
const std::array mixmatrix{
std::array{1.0f, 0.0f, 0.0f, 0.0f},
std::array{0.0f, U[0], -U[1], U[2]},
std::array{0.0f, -V[0], V[1], -V[2]},
std::array{0.0f, -N[0], N[1], -N[2]},
};
const auto scales = GetAmbiScales(mAmbiScaling);
const auto index_map = Is2DAmbisonic(mChannels) ?
al::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) :
al::span{GetAmbiLayout(mAmbiLayout)}.subspan(0);
std::array<float,MaxAmbiChannels> coeffs{};
for(size_t c{0u};c < mChans->size();++c)
for(size_t c{0u};c < mChans.size();++c)
{
const size_t acn{index_map[c]};
coeffs[acn] = scales[acn];
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target);
coeffs[acn] = 0.0f;
const float scale{scales[acn]};
std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(),
[scale](const float in) noexcept -> float { return in * scale; });
ComputePanGains(target.Main, coeffs, gain, mChans[c].Target);
}
}
else
{
DeviceBase *device{context->mDevice};
al::span<const ChanMap> chanmap{};
al::span<const ChanPosMap> chanmap{};
switch(mChannels)
{
case FmtMono: chanmap = MonoMap; break;
case FmtMonoDup: chanmap = MonoMap; break;
case FmtSuperStereo:
case FmtStereo: chanmap = StereoMap; break;
case FmtRear: chanmap = RearMap; break;
@ -493,28 +572,55 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
mOutTarget = target.Main->Buffer;
if(device->mRenderMode == RenderMode::Pairwise)
{
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
/* Scales the azimuth of the given vector by 3 if it's in front.
* Effectively scales +/-30 degrees to +/-90 degrees, leaving > +90
* and < -90 alone.
*/
auto ScaleAzimuthFront = [](std::array<float,3> pos) -> std::array<float,3>
{
constexpr float half_pi{al::numbers::pi_v<float>*0.5f};
const float abs_azi{std::fabs(azimuth)};
if(!(abs_azi >= half_pi))
return std::copysign(minf(abs_azi*scale, half_pi), azimuth);
return azimuth;
if(pos[2] < 0.0f)
{
/* Normalize the length of the x,z components for a 2D
* vector of the azimuth angle. Negate Z since {0,0,-1} is
* angle 0.
*/
const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])};
float x{pos[0] / len2d};
float z{-pos[2] / len2d};
/* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */
if(z > cos30)
{
/* Triple the angle represented by x,z. */
x = x*3.0f - x*x*x*4.0f;
z = z*z*z*4.0f - z*3.0f;
/* Scale the vector back to fit in 3D. */
pos[0] = x * len2d;
pos[2] = -z * len2d;
}
else
{
/* If azimuth >= 30 degrees, clamp to 90 degrees. */
pos[0] = std::copysign(len2d, pos[0]);
pos[2] = 0.0f;
}
}
return pos;
};
for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f),
chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
const auto coeffs = CalcDirectionCoeffs(ScaleAzimuthFront(chanmap[i].pos), 0.0f);
ComputePanGains(target.Main, coeffs, gain, mChans[i].Target);
}
}
else for(size_t i{0};i < chanmap.size();++i)
{
if(chanmap[i].channel == LFE) continue;
const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f);
ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target);
const auto coeffs = CalcDirectionCoeffs(chanmap[i].pos, 0.0f);
ComputePanGains(target.Main, coeffs, gain, mChans[i].Target);
}
}
}
@ -525,27 +631,26 @@ void ConvolutionState::process(const size_t samplesToDo,
if(mNumConvolveSegs < 1) UNLIKELY
return;
constexpr size_t m{ConvolveUpdateSize/2 + 1};
size_t curseg{mCurrentSegment};
auto &chans = *mChans;
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
const size_t todo{std::min(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)};
std::copy_n(samplesIn[0].begin() + base, todo,
mInput.begin()+ConvolveUpdateSamples+mFifoPos);
std::copy_n(samplesIn[0].begin() + ptrdiff_t(base), todo,
mInput.begin()+ptrdiff_t(ConvolveUpdateSamples+mFifoPos));
/* Apply the FIR for the newly retrieved input samples, and combine it
* with the inverse FFT'd output samples.
*/
for(size_t c{0};c < chans.size();++c)
for(size_t c{0};c < mChans.size();++c)
{
auto buf_iter = chans[c].mBuffer.begin() + base;
apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data());
auto outspan = al::span{mChans[c].mBuffer}.subspan(base, todo);
apply_fir(outspan, al::span{mInput}.subspan(1+mFifoPos), mFilter[c]);
auto fifo_iter = mOutput[c].begin() + mFifoPos;
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
auto fifospan = al::span{mOutput[c]}.subspan(mFifoPos, todo);
std::transform(fifospan.cbegin(), fifospan.cend(), outspan.cbegin(), outspan.begin(),
std::plus{});
}
mFifoPos += todo;
@ -557,59 +662,51 @@ void ConvolutionState::process(const size_t samplesToDo,
/* Move the newest input to the front for the next iteration's history. */
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
std::fill(mInput.begin()+ConvolveUpdateSamples, mInput.end(), 0.0f);
/* Calculate the frequency domain response and add the relevant
/* Calculate the frequency-domain response and add the relevant
* frequency bins to the FFT history.
*/
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
std::fill(fftiter, mFftBuffer.end(), complex_f{});
forward_fft(al::as_span(mFftBuffer));
mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize],
mFftWorkBuffer.data(), PFFFT_FORWARD);
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
for(size_t c{0};c < chans.size();++c)
auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize);
for(size_t c{0};c < mChans.size();++c)
{
std::fill_n(mFftBuffer.begin(), m, complex_f{});
/* Convolve each input segment with its IR filter counterpart
* (aligned in time).
*/
const complex_f *RESTRICT input{&mComplexData[curseg*m]};
mFftBuffer.fill(0.0f);
auto input = mComplexData.cbegin() + ptrdiff_t(curseg*ConvolveUpdateSize);
for(size_t s{curseg};s < mNumConvolveSegs;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += ConvolveUpdateSize;
filter += ConvolveUpdateSize;
}
input = mComplexData.get();
input = mComplexData.cbegin();
for(size_t s{0};s < curseg;++s)
{
for(size_t i{0};i < m;++i,++input,++filter)
mFftBuffer[i] += *input * *filter;
mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter),
mFftBuffer.data());
input += ConvolveUpdateSize;
filter += ConvolveUpdateSize;
}
/* Reconstruct the mirrored/negative frequencies to do a proper
* inverse FFT.
*/
for(size_t i{m};i < ConvolveUpdateSize;++i)
mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]);
/* Apply iFFT to get the 256 (really 255) samples for output. The
* 128 output samples are combined with the last output's 127
* second-half samples (and this output's second half is
* subsequently saved for next time).
*/
inverse_fft(al::as_span(mFftBuffer));
mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_BACKWARD);
/* The iFFT'd response is scaled up by the number of bins, so apply
* the inverse to normalize the output.
*/
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][i] =
(mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) *
(1.0f/float{ConvolveUpdateSize});
for(size_t i{0};i < ConvolveUpdateSamples;++i)
mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real();
/* The filter was attenuated, so the response is already scaled. */
std::transform(mFftBuffer.cbegin(), mFftBuffer.cbegin()+ConvolveUpdateSamples,
mOutput[c].cbegin()+ConvolveUpdateSamples, mOutput[c].begin(), std::plus{});
std::copy(mFftBuffer.cbegin()+ConvolveUpdateSamples, mFftBuffer.cend(),
mOutput[c].begin()+ConvolveUpdateSamples);
}
/* Shift the input history. */

View file

@ -23,18 +23,19 @@
#include <algorithm>
#include <array>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
@ -47,45 +48,36 @@ struct DedicatedState final : public EffectState {
* gains for all possible output channels and not just the main ambisonic
* buffer.
*/
float mCurrentGains[MAX_OUTPUT_CHANNELS];
float mTargetGains[MAX_OUTPUT_CHANNELS];
std::array<float,MaxOutputChannels> mCurrentGains{};
std::array<float,MaxOutputChannels> mTargetGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_,
const EffectTarget target) final;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DedicatedState)
const al::span<FloatBufferLine> samplesOut) final;
};
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
{
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f);
}
void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f);
const float Gain{slot->Gain * props->Dedicated.Gain};
auto &props = std::get<DedicatedProps>(*props_);
const float Gain{slot->Gain * props.Gain};
if(slot->EffectType == EffectSlotType::DedicatedLFE)
{
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
else if(slot->EffectType == EffectSlotType::DedicatedDialog)
if(props.Target == DedicatedProps::Dialog)
{
/* Dialog goes to the front-center speaker if it exists, otherwise it
* plays from the front-center location. */
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
* plays from the front-center location.
*/
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
: InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
@ -94,17 +86,26 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
}
else
{
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
ComputePanGains(target.Main, coeffs, Gain, mTargetGains);
}
}
else if(props.Target == DedicatedProps::Lfe)
{
const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
if(idx != InvalidChannelIndex)
{
mOutTarget = target.RealOut->Buffer;
mTargetGains[idx] = Gain;
}
}
}
void DedicatedState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
samplesToDo, 0);
}

View file

@ -22,30 +22,32 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
struct DistortionState final : public EffectState {
/* Effect gains for each channel */
float mGain[MaxAmbiChannels]{};
std::array<float,MaxAmbiChannels> mGain{};
/* Effect parameters */
BiquadFilter mLowpass;
@ -53,7 +55,7 @@ struct DistortionState final : public EffectState {
float mAttenuation{};
float mEdgeCoeff{};
alignas(16) float mBuffer[2][BufferLineSize]{};
alignas(16) std::array<FloatBufferLine,2> mBuffer{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -61,8 +63,6 @@ struct DistortionState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(DistortionState)
};
void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -72,16 +72,16 @@ void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<DistortionProps>(*props_);
const DeviceBase *device{context->mDevice};
/* Store waveshaper edge settings. */
const float edge{minf(std::sin(al::numbers::pi_v<float>*0.5f * props->Distortion.Edge),
0.99f)};
const float edge{std::min(std::sin(al::numbers::pi_v<float>*0.5f * props.Edge), 0.99f)};
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
float cutoff{props->Distortion.LowpassCutoff};
float cutoff{props.LowpassCutoff};
/* Bandwidth value is constant in octaves. */
float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)};
/* Divide normalized frequency by the amount of oversampling done during
@ -90,15 +90,15 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
auto frequency = static_cast<float>(device->Frequency);
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
cutoff = props->Distortion.EQCenter;
cutoff = props.EQCenter;
/* Convert bandwidth in Hz to octaves. */
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
bandwidth = props.EQBandwidth / (cutoff * 0.67f);
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
ComputePanGains(target.Main, coeffs, slot->Gain*props.Gain, mGain);
}
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
@ -111,7 +111,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
* bandpass filters using high frequencies, at which classic IIR
* filters became unstable.
*/
size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
size_t todo{std::min(BufferLineSize, (samplesToDo-base) * 4_uz)};
/* Fill oversample buffer using zero stuffing. Multiply the sample by
* the amount of oversampling to maintain the signal's power.
@ -124,7 +124,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
* (which is fortunately first step of distortion). So combine three
* operations into the one.
*/
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
mLowpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
/* Second step, do distortion using waveshaper function to emulate
* signal processing during tube overdriving. Three steps of
@ -133,31 +133,39 @@ void DistortionState::process(const size_t samplesToDo, const al::span<const Flo
*/
auto proc_sample = [fc](float smp) -> float
{
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp));
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)) * -1.0f;
smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp));
return smp;
};
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
std::transform(mBuffer[1].begin(), mBuffer[1].begin()+todo, mBuffer[0].begin(),
proc_sample);
/* Third step, do bandpass filtering of distorted signal. */
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]);
todo >>= 2;
const float *outgains{mGain};
for(FloatBufferLine &output : samplesOut)
auto outgains = mGain.cbegin();
auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output)
{
/* Fourth step, final, do attenuation and perform decimation,
* storing only one sample out of four.
*/
const float gain{*(outgains++)};
if(!(std::fabs(gain) > GainSilenceThreshold))
continue;
return;
for(size_t i{0u};i < todo;i++)
output[base+i] += gain * mBuffer[1][i*4];
}
auto src = mBuffer[1].cbegin();
const auto dst = al::span{output}.subspan(base, todo);
auto dec_sample = [gain,&src](float sample) noexcept -> float
{
sample += *src * gain;
src += 4;
return sample;
};
std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample);
};
std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline);
base += todo;
}

View file

@ -22,25 +22,26 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <tuple>
#include <variant>
#include <vector>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
#include "vector.h"
struct BufferStorage;
namespace {
@ -49,33 +50,30 @@ using uint = unsigned int;
constexpr float LowpassFreqRef{5000.0f};
struct EchoState final : public EffectState {
al::vector<float,16> mSampleBuffer;
std::vector<float> mSampleBuffer;
// The echo is two tap. The delay is the number of samples from before the
// current offset
struct {
size_t delay{0u};
} mTap[2];
std::array<size_t,2> mDelayTap{};
size_t mOffset{0u};
/* The panning gains for the two taps */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
BiquadFilter mFilter;
float mFeedGain{0.0f};
alignas(16) float mTempBuffer[2][BufferLineSize];
alignas(16) std::array<FloatBufferLine,2> mTempBuffer{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EchoState)
};
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
@ -87,61 +85,62 @@ void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) +
float2uint(EchoMaxLRDelay*frequency + 0.5f))};
if(maxlen != mSampleBuffer.size())
al::vector<float,16>(maxlen).swap(mSampleBuffer);
decltype(mSampleBuffer)(maxlen).swap(mSampleBuffer);
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
for(auto &e : mGains)
{
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
std::fill(std::begin(e.Target), std::end(e.Target), 0.0f);
std::fill(e.Current.begin(), e.Current.end(), 0.0f);
std::fill(e.Target.begin(), e.Target.end(), 0.0f);
}
}
void EchoState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<EchoProps>(*props_);
const DeviceBase *device{context->mDevice};
const auto frequency = static_cast<float>(device->Frequency);
mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1);
mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay;
mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u);
mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0];
const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
const float gainhf{std::max(1.0f - props.Damping, 0.0625f)}; /* Limit -24dB */
mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
mFeedGain = props->Echo.Feedback;
mFeedGain = props.Feedback;
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
const float angle{std::asin(props->Echo.Spread)};
/* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */
const float x{props.Spread}; /* +x = left */
const float z{std::sqrt(1.0f - x*x)};
const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f);
const auto coeffs0 = CalcAmbiCoeffs( x, 0.0f, z, 0.0f);
const auto coeffs1 = CalcAmbiCoeffs(-x, 0.0f, z, 0.0f);
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, coeffs0, slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, coeffs1, slot->Gain, mGains[1].Target);
}
void EchoState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const size_t mask{mSampleBuffer.size()-1};
float *RESTRICT delaybuf{mSampleBuffer.data()};
const auto delaybuf = al::span{mSampleBuffer};
const size_t mask{delaybuf.size()-1};
size_t offset{mOffset};
size_t tap1{offset - mTap[0].delay};
size_t tap2{offset - mTap[1].delay};
float z1, z2;
size_t tap1{offset - mDelayTap[0]};
size_t tap2{offset - mDelayTap[1]};
ASSUME(samplesToDo > 0);
const BiquadFilter filter{mFilter};
std::tie(z1, z2) = mFilter.getComponents();
auto [z1, z2] = mFilter.getComponents();
for(size_t i{0u};i < samplesToDo;)
{
offset &= mask;
tap1 &= mask;
tap2 &= mask;
size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)};
size_t td{std::min(mask+1 - std::max(offset, std::max(tap1, tap2)), samplesToDo-i)};
do {
/* Feed the delay buffer's input first. */
delaybuf[offset] = samplesIn[0][i];
@ -161,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span<const FloatBuff
mOffset = offset;
for(size_t c{0};c < 2;c++)
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
samplesToDo, 0);
MixSamples(al::span{mTempBuffer[c]}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, samplesToDo, 0);
}

View file

@ -22,24 +22,24 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <utility>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
@ -86,16 +86,17 @@ namespace {
struct EqualizerState final : public EffectState {
struct {
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
BiquadFilter mFilter[4];
std::array<BiquadFilter,4> mFilter;
/* Effect gains for each channel */
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
alignas(16) FloatBufferLine mSampleBuffer{};
@ -105,8 +106,6 @@ struct EqualizerState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(EqualizerState)
};
void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -114,18 +113,17 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &e : mChans)
{
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFilter), std::end(e.mFilter),
std::mem_fn(&BiquadFilter::clear));
std::for_each(e.mFilter.begin(), e.mFilter.end(), std::mem_fn(&BiquadFilter::clear));
e.mCurrentGain = 0.0f;
}
}
void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<EqualizerProps>(*props_);
const DeviceBase *device{context->mDevice};
auto frequency = static_cast<float>(device->Frequency);
float gain, f0norm;
/* Calculate coefficients for the each type of filter. Note that the shelf
* and peaking filters' gain is for the centerpoint of the transition band,
@ -133,22 +131,22 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
* property gains need their dB halved (sqrt of linear gain) for the
* shelf/peak to reach the provided gain.
*/
gain = std::sqrt(props->Equalizer.LowGain);
f0norm = props->Equalizer.LowCutoff / frequency;
float gain{std::sqrt(props.LowGain)};
float f0norm{props.LowCutoff / frequency};
mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
gain = std::sqrt(props->Equalizer.Mid1Gain);
f0norm = props->Equalizer.Mid1Center / frequency;
gain = std::sqrt(props.Mid1Gain);
f0norm = props.Mid1Center / frequency;
mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid1Width);
props.Mid1Width);
gain = std::sqrt(props->Equalizer.Mid2Gain);
f0norm = props->Equalizer.Mid2Center / frequency;
gain = std::sqrt(props.Mid2Gain);
f0norm = props.Mid2Center / frequency;
mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
props->Equalizer.Mid2Width);
props.Mid2Width);
gain = std::sqrt(props->Equalizer.HighGain);
f0norm = props->Equalizer.HighCutoff / frequency;
gain = std::sqrt(props.HighGain);
f0norm = props.HighCutoff / frequency;
mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
/* Copy the filter coefficients for the other input channels. */
@ -171,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
void EqualizerState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
const al::span<float> buffer{mSampleBuffer.data(), samplesToDo};
auto chan = std::begin(mChans);
const auto buffer = al::span{mSampleBuffer}.first(samplesToDo);
auto chan = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chan->mTargetChannel};
if(outidx != InvalidChannelIndex)
if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex)
{
const al::span<const float> inbuf{input.data(), samplesToDo};
DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin());
DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin());
const auto inbuf = al::span{input}.first(samplesToDo);
DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer);
DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer);
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain,
samplesToDo);
}
++chan;

View file

@ -25,23 +25,25 @@
#include <cmath>
#include <complex>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
struct BufferStorage;
namespace {
@ -57,7 +59,7 @@ constexpr size_t HilStep{HilSize / OversampleFactor};
/* Define a Hann window, used to filter the HIL input and output. */
struct Windower {
alignas(16) std::array<double,HilSize> mData;
alignas(16) std::array<double,HilSize> mData{};
Windower()
{
@ -91,10 +93,11 @@ struct FshifterState final : public EffectState {
alignas(16) FloatBufferLine mBufferOut{};
/* Effect gains for each output channel */
struct {
float Current[MaxAmbiChannels]{};
float Target[MaxAmbiChannels]{};
} mGains[2];
struct OutGains {
std::array<float,MaxAmbiChannels> Current{};
std::array<float,MaxAmbiChannels> Target{};
};
std::array<OutGains,2> mGains;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -102,8 +105,6 @@ struct FshifterState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(FshifterState)
};
void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -122,20 +123,21 @@ void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &gain : mGains)
{
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
gain.Current.fill(0.0f);
gain.Target.fill(0.0f);
}
}
void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<FshifterProps>(*props_);
const DeviceBase *device{context->mDevice};
const float step{props->Fshifter.Frequency / static_cast<float>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne);
const float step{props.Frequency / static_cast<float>(device->Frequency)};
mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne);
switch(props->Fshifter.LeftDirection)
switch(props.LeftDirection)
{
case FShifterDirection::Down:
mSign[0] = -1.0;
@ -149,7 +151,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
break;
}
switch(props->Fshifter.RightDirection)
switch(props.RightDirection)
{
case FShifterDirection::Down:
mSign[1] = -1.0;
@ -164,23 +166,23 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
}
static constexpr auto inv_sqrt2 = static_cast<float>(1.0 / al::numbers::sqrt2);
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f});
static constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f});
static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2});
static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2});
auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw;
auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw;
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target);
ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target);
ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target);
}
void FshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
{
size_t todo{minz(HilStep-mCount, samplesToDo-base)};
size_t todo{std::min(HilStep-mCount, samplesToDo-base)};
/* Fill FIFO buffer with samples data */
const size_t pos{mPos};
@ -218,25 +220,27 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
}
/* Process frequency shifter using the analytic signal obtained. */
float *RESTRICT BufferOut{al::assume_aligned<16>(mBufferOut.data())};
for(size_t c{0};c < 2;++c)
{
const double sign{mSign[c]};
const uint phase_step{mPhaseStep[c]};
uint phase_idx{mPhase[c]};
for(size_t k{0};k < samplesToDo;++k)
{
const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
BufferOut[k] = static_cast<float>(mOutdata[k].real()*std::cos(phase) +
mOutdata[k].imag()*std::sin(phase)*mSign[c]);
std::transform(mOutdata.cbegin(), mOutdata.cbegin()+samplesToDo, mBufferOut.begin(),
[&phase_idx,phase_step,sign](const complex_d &in) -> float
{
const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)};
const auto out = static_cast<float>(in.real()*std::cos(phase) +
in.imag()*std::sin(phase)*sign);
phase_idx += phase_step;
phase_idx &= MixerFracMask;
}
phase_idx += phase_step;
phase_idx &= MixerFracMask;
return out;
});
mPhase[c] = phase_idx;
/* Now, mix the processed sound data to the output. */
MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
maxz(samplesToDo, 512), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current,
mGains[c].Target, std::max(samplesToDo, 512_uz), 0);
}
}

View file

@ -22,75 +22,73 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iterator>
#include <functional>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/filters/biquad.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
#include "opthelpers.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 128
struct SinFunc {
static auto Get(uint index, float scale) noexcept(noexcept(std::sin(0.0f))) -> float
{ return std::sin(static_cast<float>(index) * scale); }
};
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
struct SawFunc {
static constexpr auto Get(uint index, float scale) noexcept -> float
{ return static_cast<float>(index)*scale - 1.0f; }
};
inline float Sin(uint index)
{
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
return std::sin(static_cast<float>(index) * scale);
}
struct SquareFunc {
static constexpr auto Get(uint index, float scale) noexcept -> float
{ return float(static_cast<float>(index)*scale < 0.5f)*2.0f - 1.0f; }
};
inline float Saw(uint index)
{ return static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; }
inline float Square(uint index)
{ return static_cast<float>(static_cast<int>((index>>(WAVEFORM_FRACBITS-2))&2) - 1); }
inline float One(uint) { return 1.0f; }
template<float (&func)(uint)>
void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo)
{
for(size_t i{0u};i < todo;i++)
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
}
struct OneFunc {
static constexpr auto Get(uint, float) noexcept -> float
{ return 1.0f; }
};
struct ModulatorState final : public EffectState {
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
std::variant<OneFunc,SinFunc,SawFunc,SquareFunc> mSampleGen;
uint mIndex{0};
uint mStep{1};
uint mRange{1};
float mIndexScale{0.0f};
struct {
alignas(16) FloatBufferLine mModSamples{};
alignas(16) FloatBufferLine mBuffer{};
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
BiquadFilter mFilter;
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -98,8 +96,6 @@ struct ModulatorState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(ModulatorState)
};
void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -113,24 +109,54 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
}
void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<ModulatorProps>(*props_);
const DeviceBase *device{context->mDevice};
const float step{props->Modulator.Frequency / static_cast<float>(device->Frequency)};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
/* The effective frequency will be adjusted to have a whole number of
* samples per cycle (at 48khz, that allows 8000, 6857.14, 6000, 5333.33,
* 4800, etc). We could do better by using fixed-point stepping over a sin
* function, with additive synthesis for the square and sawtooth waveforms,
* but that may need a more efficient sin function since it needs to do
* many iterations per sample.
*/
const float samplesPerCycle{props.Frequency > 0.0f
? static_cast<float>(device->Frequency)/props.Frequency + 0.5f
: 1.0f};
const uint range{static_cast<uint>(std::clamp(samplesPerCycle, 1.0f,
static_cast<float>(device->Frequency)))};
mIndex = static_cast<uint>(uint64_t{mIndex} * range / mRange);
mRange = range;
if(mStep == 0)
mGetSamples = Modulate<One>;
else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid)
mGetSamples = Modulate<Sin>;
else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth)
mGetSamples = Modulate<Saw>;
else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/
mGetSamples = Modulate<Square>;
if(mRange == 1)
{
mIndexScale = 0.0f;
mSampleGen.emplace<OneFunc>();
}
else if(props.Waveform == ModulatorWaveform::Sinusoid)
{
mIndexScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mRange);
mSampleGen.emplace<SinFunc>();
}
else if(props.Waveform == ModulatorWaveform::Sawtooth)
{
mIndexScale = 2.0f / static_cast<float>(mRange-1);
mSampleGen.emplace<SawFunc>();
}
else if(props.Waveform == ModulatorWaveform::Square)
{
/* For square wave, the range should be even (there should be an equal
* number of high and low samples). An odd number of samples per cycle
* would need a more complex value generator.
*/
mRange = (mRange+1) & ~1u;
mIndexScale = 1.0f / static_cast<float>(mRange-1);
mSampleGen.emplace<SquareFunc>();
}
float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
float f0norm{props.HighPassCutoff / static_cast<float>(device->Frequency)};
f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f);
/* Bandwidth value is constant in octaves. */
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
@ -147,34 +173,41 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
for(size_t base{0u};base < samplesToDo;)
ASSUME(samplesToDo > 0);
std::visit([this,samplesToDo](auto&& type)
{
alignas(16) float modsamples[MAX_UPDATE_SAMPLES];
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
const uint range{mRange};
const float scale{mIndexScale};
uint index{mIndex};
mGetSamples(modsamples, mIndex, mStep, td);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
ASSUME(range > 1);
auto chandata = std::begin(mChans);
for(const auto &input : samplesIn)
for(size_t i{0};i < samplesToDo;)
{
const size_t outidx{chandata->mTargetChannel};
if(outidx != InvalidChannelIndex)
{
alignas(16) float temps[MAX_UPDATE_SAMPLES];
chandata->mFilter.process({&input[base], td}, temps);
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
}
++chandata;
size_t rem{std::min(samplesToDo-i, size_t{range-index})};
do {
mModSamples[i++] = type.Get(index++, scale);
} while(--rem);
if(index == range)
index = 0;
}
mIndex = index;
}, mSampleGen);
base += td;
auto chandata = mChans.begin();
for(const auto &input : samplesIn)
{
if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex)
{
chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer);
std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(),
mBuffer.begin(), std::multiplies<>{});
MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx],
chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz));
}
++chandata;
}
}

View file

@ -1,14 +1,15 @@
#include "config.h"
#include <stddef.h>
#include <cstddef>
#include "almalloc.h"
#include "alspan.h"
#include "base.h"
#include "core/bufferline.h"
#include "core/effects/base.h"
#include "intrusive_ptr.h"
struct BufferStorage;
struct ContextBase;
struct DeviceBase;
struct EffectSlot;
@ -25,8 +26,6 @@ struct NullState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(NullState)
};
/* This constructs the effect state. It's called when the object is first

View file

@ -25,22 +25,23 @@
#include <cmath>
#include <complex>
#include <cstdlib>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "alcomplex.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "core/mixer/defs.h"
#include "intrusive_ptr.h"
#include "pffft.h"
struct BufferStorage;
struct ContextBase;
@ -58,7 +59,7 @@ constexpr size_t StftStep{StftSize / OversampleFactor};
/* Define a Hann window, used to filter the STFT input and output. */
struct Windower {
alignas(16) std::array<float,StftSize> mData;
alignas(16) std::array<float,StftSize> mData{};
Windower()
{
@ -82,27 +83,29 @@ struct FrequencyBin {
struct PshifterState final : public EffectState {
/* Effect parameters */
size_t mCount;
size_t mPos;
uint mPitchShiftI;
float mPitchShift;
size_t mCount{};
size_t mPos{};
uint mPitchShiftI{};
float mPitchShift{};
/* Effects buffers */
std::array<float,StftSize> mFIFO;
std::array<float,StftHalfSize+1> mLastPhase;
std::array<float,StftHalfSize+1> mSumPhase;
std::array<float,StftSize> mOutputAccum;
std::array<float,StftSize> mFIFO{};
std::array<float,StftHalfSize+1> mLastPhase{};
std::array<float,StftHalfSize+1> mSumPhase{};
std::array<float,StftSize> mOutputAccum{};
std::array<complex_f,StftSize> mFftBuffer;
PFFFTSetup mFft;
alignas(16) std::array<float,StftSize> mFftBuffer{};
alignas(16) std::array<float,StftSize> mFftWorkBuffer{};
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer{};
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer{};
alignas(16) FloatBufferLine mBufferOut;
alignas(16) FloatBufferLine mBufferOut{};
/* Effect gains for each output channel */
float mCurrentGains[MaxAmbiChannels];
float mTargetGains[MaxAmbiChannels];
std::array<float,MaxAmbiChannels> mCurrentGains{};
std::array<float,MaxAmbiChannels> mTargetGains{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
@ -110,8 +113,6 @@ struct PshifterState final : public EffectState {
const EffectTarget target) override;
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
DEF_NEWDEL(PshifterState)
};
void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
@ -126,26 +127,31 @@ void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
mLastPhase.fill(0.0f);
mSumPhase.fill(0.0f);
mOutputAccum.fill(0.0f);
mFftBuffer.fill(complex_f{});
mFftBuffer.fill(0.0f);
mAnalysisBuffer.fill(FrequencyBin{});
mSynthesisBuffer.fill(FrequencyBin{});
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
mCurrentGains.fill(0.0f);
mTargetGains.fill(0.0f);
if(!mFft)
mFft = PFFFTSetup{StftSize, PFFFT_REAL};
}
void PshifterState::update(const ContextBase*, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
auto &props = std::get<PshifterProps>(*props_);
const int tune{props.CoarseTune*100 + props.FineTune};
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf},
uint{MixerFracOne}*2u);
mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne};
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f});
mOutTarget = target.Main->Buffer;
ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains);
ComputePanGains(target.Main, coeffs, slot->Gain, mTargetGains);
}
void PshifterState::process(const size_t samplesToDo,
@ -162,7 +168,7 @@ void PshifterState::process(const size_t samplesToDo,
for(size_t base{0u};base < samplesToDo;)
{
const size_t todo{minz(StftStep-mCount, samplesToDo-base)};
const size_t todo{std::min(StftStep-mCount, samplesToDo-base)};
/* Retrieve the output samples from the FIFO and fill in the new input
* samples.
@ -186,15 +192,19 @@ void PshifterState::process(const size_t samplesToDo,
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k)
mFftBuffer[k] = mFIFO[src] * gWindow.mData[k];
forward_fft(al::as_span(mFftBuffer));
mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_FORWARD);
/* Analyze the obtained data. Since the real FFT is symmetric, only
* StftHalfSize+1 samples are needed.
*/
for(size_t k{0u};k < StftHalfSize+1;k++)
for(size_t k{0u};k < StftHalfSize+1;++k)
{
const float magnitude{std::abs(mFftBuffer[k])};
const float phase{std::arg(mFftBuffer[k])};
const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} :
(k == StftHalfSize) ? complex_f{mFftBuffer[1]} :
complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]};
const float magnitude{std::abs(cplx)};
const float phase{std::arg(cplx)};
/* Compute the phase difference from the last update and subtract
* the expected phase difference for this bin.
@ -232,8 +242,8 @@ void PshifterState::process(const size_t samplesToDo,
*/
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
const size_t bin_count{minz(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
static constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
const size_t bin_count{std::min(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
for(size_t k{0u};k < bin_count;k++)
{
const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits};
@ -266,21 +276,29 @@ void PshifterState::process(const size_t samplesToDo,
tmp -= static_cast<float>(qpd + (qpd%2));
mSumPhase[k] = tmp * al::numbers::pi_v<float>;
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]);
const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])};
if(k == 0)
mFftBuffer[0] = cplx.real();
else if(k == StftHalfSize)
mFftBuffer[1] = cplx.real();
else
{
mFftBuffer[k*2 + 0] = cplx.real();
mFftBuffer[k*2 + 1] = cplx.imag();
}
}
for(size_t k{StftHalfSize+1};k < StftSize;++k)
mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]);
/* Apply an inverse FFT to get the time-domain signal, and accumulate
* for the output with windowing.
*/
inverse_fft(al::as_span(mFftBuffer));
mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(),
PFFFT_BACKWARD);
static constexpr float scale{3.0f / OversampleFactor / StftSize};
for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;
for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k)
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale;
/* Copy out the accumulated result, then clear for the next iteration. */
std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos);
@ -288,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo,
}
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
maxz(samplesToDo, 512), 0);
MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains,
std::max(samplesToDo, 512_uz), 0);
}

File diff suppressed because it is too large Load diff

View file

@ -34,68 +34,69 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <iterator>
#include <variant>
#include "alc/effects/base.h"
#include "almalloc.h"
#include "alnumbers.h"
#include "alnumeric.h"
#include "alspan.h"
#include "core/ambidefs.h"
#include "core/bufferline.h"
#include "core/context.h"
#include "core/devformat.h"
#include "core/device.h"
#include "core/effects/base.h"
#include "core/effectslot.h"
#include "core/mixer.h"
#include "intrusive_ptr.h"
struct BufferStorage;
namespace {
using uint = unsigned int;
#define MAX_UPDATE_SAMPLES 256
#define NUM_FORMANTS 4
#define NUM_FILTERS 2
#define Q_FACTOR 5.0f
constexpr size_t MaxUpdateSamples{256};
constexpr size_t NumFormants{4};
constexpr float RcpQFactor{1.0f / 5.0f};
enum : size_t {
VowelAIndex,
VowelBIndex,
NumFilters
};
#define VOWEL_A_INDEX 0
#define VOWEL_B_INDEX 1
#define WAVEFORM_FRACBITS 24
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
constexpr size_t WaveformFracBits{24};
constexpr size_t WaveformFracOne{1<<WaveformFracBits};
constexpr size_t WaveformFracMask{WaveformFracOne-1};
inline float Sin(uint index)
{
constexpr float scale{al::numbers::pi_v<float>*2.0f / WAVEFORM_FRACONE};
constexpr float scale{al::numbers::pi_v<float>*2.0f / float{WaveformFracOne}};
return std::sin(static_cast<float>(index) * scale)*0.5f + 0.5f;
}
inline float Saw(uint index)
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
{ return static_cast<float>(index) / float{WaveformFracOne}; }
inline float Triangle(uint index)
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); }
{ return std::fabs(static_cast<float>(index)*(2.0f/WaveformFracOne) - 1.0f); }
inline float Half(uint) { return 0.5f; }
template<float (&func)(uint)>
void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo)
void Oscillate(const al::span<float> dst, uint index, const uint step)
{
for(size_t i{0u};i < todo;i++)
std::generate(dst.begin(), dst.end(), [&index,step]
{
index += step;
index &= WAVEFORM_FRACMASK;
dst[i] = func(index);
}
index &= WaveformFracMask;
return func(index);
});
}
struct FormantFilter
{
struct FormantFilter {
float mCoeff{0.0f};
float mGain{1.0f};
float mS1{0.0f};
@ -106,34 +107,38 @@ struct FormantFilter
: mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
{ }
inline void process(const float *samplesIn, float *samplesOut, const size_t numInput)
void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept
{
/* A state variable filter from a topology-preserving transform.
* Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg
*/
const float g{mCoeff};
const float gain{mGain};
const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))};
const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))};
const float coeff{RcpQFactor + g};
float s1{mS1};
float s2{mS2};
for(size_t i{0u};i < numInput;i++)
{
const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h};
const float B{g*H + s1};
const float L{g*B + s2};
const auto input = al::span{samplesIn, numInput};
const auto output = al::span{samplesOut, numInput};
std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(),
[g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float
{
const float H{(in - coeff*s1 - s2)*h};
const float B{g*H + s1};
const float L{g*B + s2};
s1 = g*H + B;
s2 = g*B + L;
s1 = g*H + B;
s2 = g*B + L;
// Apply peak and accumulate samples.
samplesOut[i] += B * gain;
}
// Apply peak and accumulate samples.
return out + B*gain;
});
mS1 = s1;
mS2 = s2;
}
inline void clear()
void clear() noexcept
{
mS1 = 0.0f;
mS2 = 0.0f;
@ -142,26 +147,27 @@ struct FormantFilter
struct VmorpherState final : public EffectState {
struct {
struct OutParams {
uint mTargetChannel{InvalidChannelIndex};
/* Effect parameters */
FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS];
std::array<std::array<FormantFilter,NumFormants>,NumFilters> mFormants;
/* Effect gains for each channel */
float mCurrentGain{};
float mTargetGain{};
} mChans[MaxAmbiChannels];
};
std::array<OutParams,MaxAmbiChannels> mChans;
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
void (*mGetSamples)(const al::span<float> dst, uint index, const uint step){};
uint mIndex{0};
uint mStep{1};
/* Effects buffers */
alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{};
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferA{};
alignas(16) std::array<float,MaxUpdateSamples> mSampleBufferB{};
alignas(16) std::array<float,MaxUpdateSamples> mLfo{};
void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override;
void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props,
@ -169,14 +175,12 @@ struct VmorpherState final : public EffectState {
void process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn,
const al::span<FloatBufferLine> samplesOut) override;
static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch);
DEF_NEWDEL(VmorpherState)
static std::array<FormantFilter,NumFormants> getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch) noexcept;
};
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch)
std::array<FormantFilter,NumFormants> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
float frequency, float pitch) noexcept
{
/* Using soprano formant set of values to
* better match mid-range frequency space.
@ -232,44 +236,43 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*)
for(auto &e : mChans)
{
e.mTargetChannel = InvalidChannelIndex;
std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]),
std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(),
std::mem_fn(&FormantFilter::clear));
std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]),
std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(),
std::mem_fn(&FormantFilter::clear));
e.mCurrentGain = 0.0f;
}
}
void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
const EffectProps *props, const EffectTarget target)
const EffectProps *props_, const EffectTarget target)
{
auto &props = std::get<VmorpherProps>(*props_);
const DeviceBase *device{context->mDevice};
const float frequency{static_cast<float>(device->Frequency)};
const float step{props->Vmorpher.Rate / frequency};
mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1}));
const float step{props.Rate / frequency};
mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f));
if(mStep == 0)
mGetSamples = Oscillate<Half>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid)
else if(props.Waveform == VMorpherWaveform::Sinusoid)
mGetSamples = Oscillate<Sin>;
else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle)
else if(props.Waveform == VMorpherWaveform::Triangle)
mGetSamples = Oscillate<Triangle>;
else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/
else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/
mGetSamples = Oscillate<Saw>;
const float pitchA{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)};
const float pitchB{std::pow(2.0f,
static_cast<float>(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)};
const float pitchA{std::pow(2.0f, static_cast<float>(props.PhonemeACoarseTuning) / 12.0f)};
const float pitchB{std::pow(2.0f, static_cast<float>(props.PhonemeBCoarseTuning) / 12.0f)};
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB);
auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA);
auto vowelB = getFiltersByPhoneme(props.PhonemeB, frequency, pitchB);
/* Copy the filter coefficients to the input channels. */
for(size_t i{0u};i < slot->Wet.Buffer.size();++i)
{
std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX]));
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX]));
std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin());
std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin());
}
mOutTarget = target.Main->Buffer;
@ -283,18 +286,20 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
{
alignas(16) std::array<float,MaxUpdateSamples> blended{};
/* Following the EFX specification for a conformant implementation which describes
* the effect as a pair of 4-band formant filters blended together using an LFO.
*/
for(size_t base{0u};base < samplesToDo;)
{
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)};
mGetSamples(mLfo, mIndex, mStep, td);
mGetSamples(al::span{mLfo}.first(td), mIndex, mStep);
mIndex += static_cast<uint>(mStep * td);
mIndex &= WAVEFORM_FRACMASK;
mIndex &= WaveformFracMask;
auto chandata = std::begin(mChans);
auto chandata = mChans.begin();
for(const auto &input : samplesIn)
{
const size_t outidx{chandata->mTargetChannel};
@ -304,30 +309,29 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
continue;
}
auto& vowelA = chandata->mFormants[VOWEL_A_INDEX];
auto& vowelB = chandata->mFormants[VOWEL_B_INDEX];
const auto vowelA = al::span{chandata->mFormants[VowelAIndex]};
const auto vowelB = al::span{chandata->mFormants[VowelBIndex]};
/* Process first vowel. */
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
vowelA[0].process(&input[base], mSampleBufferA, td);
vowelA[1].process(&input[base], mSampleBufferA, td);
vowelA[2].process(&input[base], mSampleBufferA, td);
vowelA[3].process(&input[base], mSampleBufferA, td);
std::fill_n(mSampleBufferA.begin(), td, 0.0f);
vowelA[0].process(&input[base], mSampleBufferA.data(), td);
vowelA[1].process(&input[base], mSampleBufferA.data(), td);
vowelA[2].process(&input[base], mSampleBufferA.data(), td);
vowelA[3].process(&input[base], mSampleBufferA.data(), td);
/* Process second vowel. */
std::fill_n(std::begin(mSampleBufferB), td, 0.0f);
vowelB[0].process(&input[base], mSampleBufferB, td);
vowelB[1].process(&input[base], mSampleBufferB, td);
vowelB[2].process(&input[base], mSampleBufferB, td);
vowelB[3].process(&input[base], mSampleBufferB, td);
std::fill_n(mSampleBufferB.begin(), td, 0.0f);
vowelB[0].process(&input[base], mSampleBufferB.data(), td);
vowelB[1].process(&input[base], mSampleBufferB.data(), td);
vowelB[2].process(&input[base], mSampleBufferB.data(), td);
vowelB[3].process(&input[base], mSampleBufferB.data(), td);
alignas(16) float blended[MAX_UPDATE_SAMPLES];
for(size_t i{0u};i < td;i++)
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base),
chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base);
++chandata;
}