mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 16:44:36 +00:00
update openal
This commit is contained in:
parent
62f3b93ff9
commit
6721a6b021
287 changed files with 33851 additions and 27325 deletions
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue