mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-16 00:54:54 +00:00
Initial commit
added libraries: opus flac libsndfile updated: libvorbis libogg openal - Everything works as expected for now. Bare in mind libsndfile needed the check for whether or not it could find the xiph libraries removed in order for this to work.
This commit is contained in:
parent
05a083ca6f
commit
a745fc3757
1954 changed files with 431332 additions and 21037 deletions
|
|
@ -65,21 +65,23 @@ struct AutowahState final : public EffectState {
|
|||
} mEnv[BufferLineSize];
|
||||
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect filters' history. */
|
||||
struct {
|
||||
float z1, z2;
|
||||
} Filter;
|
||||
} mFilter;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
float CurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
float TargetGains[MAX_OUTPUT_CHANNELS];
|
||||
float mCurrentGain;
|
||||
float mTargetGain;
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
/* Effects buffers */
|
||||
alignas(16) float mBufferOut[BufferLineSize];
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -88,7 +90,7 @@ struct AutowahState final : public EffectState {
|
|||
DEF_NEWDEL(AutowahState)
|
||||
};
|
||||
|
||||
void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
|
||||
|
|
@ -108,9 +110,10 @@ void AutowahState::deviceUpdate(const DeviceBase*, const Buffer&)
|
|||
|
||||
for(auto &chan : mChans)
|
||||
{
|
||||
std::fill(std::begin(chan.CurrentGains), std::end(chan.CurrentGains), 0.0f);
|
||||
chan.Filter.z1 = 0.0f;
|
||||
chan.Filter.z2 = 0.0f;
|
||||
chan.mTargetChannel = InvalidChannelIndex;
|
||||
chan.mFilter.z1 = 0.0f;
|
||||
chan.mFilter.z2 = 0.0f;
|
||||
chan.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,9 +134,12 @@ void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
|
||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
|
||||
auto set_channel = [this](size_t idx, uint outchan, float outgain)
|
||||
{
|
||||
mChans[idx].mTargetChannel = outchan;
|
||||
mChans[idx].mTargetGain = outgain;
|
||||
};
|
||||
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
|
||||
}
|
||||
|
||||
void AutowahState::process(const size_t samplesToDo,
|
||||
|
|
@ -165,17 +171,24 @@ void AutowahState::process(const size_t samplesToDo,
|
|||
}
|
||||
mEnvDelay = env_delay;
|
||||
|
||||
auto chandata = std::addressof(mChans[0]);
|
||||
auto chandata = std::begin(mChans);
|
||||
for(const auto &insamples : samplesIn)
|
||||
{
|
||||
const size_t outidx{chandata->mTargetChannel};
|
||||
if(outidx == InvalidChannelIndex)
|
||||
{
|
||||
++chandata;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* This effectively inlines BiquadFilter_setParams for a peaking
|
||||
* filter and BiquadFilter_processC. The alpha and cosine components
|
||||
* for the filter coefficients were previously calculated with the
|
||||
* envelope. Because the filter changes for each sample, the
|
||||
* coefficients are transient and don't need to be held.
|
||||
*/
|
||||
float z1{chandata->Filter.z1};
|
||||
float z2{chandata->Filter.z2};
|
||||
float z1{chandata->mFilter.z1};
|
||||
float z2{chandata->mFilter.z2};
|
||||
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
|
|
@ -197,12 +210,12 @@ void AutowahState::process(const size_t samplesToDo,
|
|||
z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]);
|
||||
mBufferOut[i] = output;
|
||||
}
|
||||
chandata->Filter.z1 = z1;
|
||||
chandata->Filter.z2 = z2;
|
||||
chandata->mFilter.z1 = z1;
|
||||
chandata->mFilter.z2 = z2;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({mBufferOut, samplesToDo}, samplesOut, chandata->CurrentGains,
|
||||
chandata->TargetGains, samplesToDo, 0);
|
||||
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
|
||||
chandata->mTargetGain, samplesToDo);
|
||||
++chandata;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,10 +48,8 @@ namespace {
|
|||
|
||||
using uint = unsigned int;
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 256
|
||||
|
||||
struct ChorusState final : public EffectState {
|
||||
al::vector<float,16> mSampleBuffer;
|
||||
al::vector<float,16> mDelayBuffer;
|
||||
uint mOffset{0};
|
||||
|
||||
uint mLfoOffset{0};
|
||||
|
|
@ -59,10 +57,16 @@ struct ChorusState final : public EffectState {
|
|||
float mLfoScale{0.0f};
|
||||
uint mLfoDisp{0};
|
||||
|
||||
/* Gains for left and right sides */
|
||||
/* Calculated delays to apply to the left and right outputs. */
|
||||
uint mModDelays[2][BufferLineSize];
|
||||
|
||||
/* Temp storage for the modulated left and right outputs. */
|
||||
alignas(16) float mBuffer[2][BufferLineSize];
|
||||
|
||||
/* Gains for left and right outputs. */
|
||||
struct {
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
float Current[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
/* effect parameters */
|
||||
|
|
@ -71,10 +75,10 @@ struct ChorusState final : public EffectState {
|
|||
float mDepth{0.0f};
|
||||
float mFeedback{0.0f};
|
||||
|
||||
void getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
|
||||
void getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo);
|
||||
void calcTriangleDelays(const size_t todo);
|
||||
void calcSinusoidDelays(const size_t todo);
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -83,16 +87,16 @@ struct ChorusState final : public EffectState {
|
|||
DEF_NEWDEL(ChorusState)
|
||||
};
|
||||
|
||||
void ChorusState::deviceUpdate(const DeviceBase *Device, const Buffer&)
|
||||
void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
|
||||
{
|
||||
constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)};
|
||||
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)};
|
||||
if(maxlen != mSampleBuffer.size())
|
||||
al::vector<float,16>(maxlen).swap(mSampleBuffer);
|
||||
if(maxlen != mDelayBuffer.size())
|
||||
decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer);
|
||||
|
||||
std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f);
|
||||
std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f);
|
||||
for(auto &e : mGains)
|
||||
{
|
||||
std::fill(std::begin(e.Current), std::end(e.Current), 0.0f);
|
||||
|
|
@ -120,8 +124,13 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
|
|||
mFeedback = props->Chorus.Feedback;
|
||||
|
||||
/* Gains for left and right sides */
|
||||
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
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;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
|
||||
|
|
@ -162,7 +171,7 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
|
|||
}
|
||||
|
||||
|
||||
void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
|
||||
void ChorusState::calcTriangleDelays(const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
|
|
@ -172,22 +181,38 @@ void ChorusState::getTriangleDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si
|
|||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
|
||||
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
|
||||
};
|
||||
std::generate_n(delays[0], todo, gen_lfo);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
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;
|
||||
}
|
||||
|
||||
offset = (mLfoOffset+mLfoDisp) % lfo_range;
|
||||
std::generate_n(delays[1], todo, gen_lfo);
|
||||
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;
|
||||
}
|
||||
|
||||
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
|
||||
}
|
||||
|
||||
void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const size_t todo)
|
||||
void ChorusState::calcSinusoidDelays(const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
|
|
@ -197,69 +222,81 @@ void ChorusState::getSinusoidDelays(uint (*delays)[MAX_UPDATE_SAMPLES], const si
|
|||
ASSUME(lfo_range > 0);
|
||||
ASSUME(todo > 0);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
auto gen_lfo = [&offset,lfo_range,lfo_scale,depth,delay]() -> uint
|
||||
auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint
|
||||
{
|
||||
offset = (offset+1)%lfo_range;
|
||||
const float offset_norm{static_cast<float>(offset) * lfo_scale};
|
||||
return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
|
||||
};
|
||||
std::generate_n(delays[0], todo, gen_lfo);
|
||||
|
||||
uint offset{mLfoOffset};
|
||||
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;
|
||||
}
|
||||
|
||||
offset = (mLfoOffset+mLfoDisp) % lfo_range;
|
||||
std::generate_n(delays[1], todo, gen_lfo);
|
||||
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;
|
||||
}
|
||||
|
||||
mLfoOffset = static_cast<uint>(mLfoOffset+todo) % lfo_range;
|
||||
}
|
||||
|
||||
void ChorusState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const size_t bufmask{mSampleBuffer.size()-1};
|
||||
const size_t bufmask{mDelayBuffer.size()-1};
|
||||
const float feedback{mFeedback};
|
||||
const uint avgdelay{(static_cast<uint>(mDelay) + (MixerFracOne>>1)) >> MixerFracBits};
|
||||
float *RESTRICT delaybuf{mSampleBuffer.data()};
|
||||
const uint avgdelay{(static_cast<uint>(mDelay) + MixerFracHalf) >> MixerFracBits};
|
||||
float *RESTRICT delaybuf{mDelayBuffer.data()};
|
||||
uint offset{mOffset};
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
if(mWaveform == ChorusWaveform::Sinusoid)
|
||||
calcSinusoidDelays(samplesToDo);
|
||||
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])};
|
||||
for(size_t i{0u};i < samplesToDo;++i)
|
||||
{
|
||||
const size_t todo{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
|
||||
// Feed the buffer's input first (necessary for delays < 1).
|
||||
delaybuf[offset&bufmask] = samplesIn[0][i];
|
||||
|
||||
uint moddelays[2][MAX_UPDATE_SAMPLES];
|
||||
if(mWaveform == ChorusWaveform::Sinusoid)
|
||||
getSinusoidDelays(moddelays, todo);
|
||||
else /*if(mWaveform == ChorusWaveform::Triangle)*/
|
||||
getTriangleDelays(moddelays, todo);
|
||||
// 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);
|
||||
|
||||
alignas(16) float temps[2][MAX_UPDATE_SAMPLES];
|
||||
for(size_t i{0u};i < todo;++i)
|
||||
{
|
||||
// Feed the buffer's input first (necessary for delays < 1).
|
||||
delaybuf[offset&bufmask] = samplesIn[0][base+i];
|
||||
// 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);
|
||||
|
||||
// Tap for the left output.
|
||||
uint delay{offset - (moddelays[0][i]>>MixerFracBits)};
|
||||
float mu{static_cast<float>(moddelays[0][i]&MixerFracMask) * (1.0f/MixerFracOne)};
|
||||
temps[0][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Tap for the right output.
|
||||
delay = offset - (moddelays[1][i]>>MixerFracBits);
|
||||
mu = static_cast<float>(moddelays[1][i]&MixerFracMask) * (1.0f/MixerFracOne);
|
||||
temps[1][i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask],
|
||||
delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu);
|
||||
|
||||
// Accumulate feedback from the average delay of the taps.
|
||||
delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback;
|
||||
++offset;
|
||||
}
|
||||
|
||||
for(size_t c{0};c < 2;++c)
|
||||
MixSamples({temps[c], todo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo-base, base);
|
||||
|
||||
base += todo;
|
||||
// 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,
|
||||
samplesToDo, 0);
|
||||
MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target,
|
||||
samplesToDo, 0);
|
||||
|
||||
mOffset = offset;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,10 @@ namespace {
|
|||
|
||||
struct CompressorState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
float mGain[MaxAmbiChannels][MAX_OUTPUT_CHANNELS]{};
|
||||
struct {
|
||||
uint mTarget{InvalidChannelIndex};
|
||||
float mGain{1.0f};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
/* Effect parameters */
|
||||
bool mEnabled{true};
|
||||
|
|
@ -73,7 +76,7 @@ struct CompressorState final : public EffectState {
|
|||
float mEnvFollower{1.0f};
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -82,7 +85,7 @@ struct CompressorState final : public EffectState {
|
|||
DEF_NEWDEL(CompressorState)
|
||||
};
|
||||
|
||||
void CompressorState::deviceUpdate(const DeviceBase *device, const Buffer&)
|
||||
void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*)
|
||||
{
|
||||
/* Number of samples to do a full attack and release (non-integer sample
|
||||
* counts are okay).
|
||||
|
|
@ -103,9 +106,12 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot,
|
|||
mEnabled = props->Compressor.OnOff;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &gains, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, gains); };
|
||||
SetAmbiPanIdentity(std::begin(mGain), slot->Wet.Buffer.size(), set_gains);
|
||||
auto set_channel = [this](size_t idx, uint outchan, float outgain)
|
||||
{
|
||||
mChans[idx].mTarget = outchan;
|
||||
mChans[idx].mGain = outgain;
|
||||
};
|
||||
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
|
||||
}
|
||||
|
||||
void CompressorState::process(const size_t samplesToDo,
|
||||
|
|
@ -158,19 +164,22 @@ void CompressorState::process(const size_t samplesToDo,
|
|||
mEnvFollower = env;
|
||||
|
||||
/* Now compress the signal amplitude to output. */
|
||||
auto changains = std::addressof(mGain[0]);
|
||||
auto chan = std::cbegin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
const float *outgains{*(changains++)};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
const size_t outidx{chan->mTarget};
|
||||
if(outidx != InvalidChannelIndex)
|
||||
{
|
||||
const float gain{*(outgains++)};
|
||||
const float *RESTRICT src{input.data() + base};
|
||||
float *RESTRICT dst{samplesOut[outidx].data() + base};
|
||||
const float gain{chan->mGain};
|
||||
if(!(std::fabs(gain) > GainSilenceThreshold))
|
||||
continue;
|
||||
|
||||
for(size_t i{0u};i < td;i++)
|
||||
output[base+i] += input[base+i] * gains[i] * gain;
|
||||
{
|
||||
for(size_t i{0u};i < td;i++)
|
||||
dst[i] += src[i] * gains[i] * gain;
|
||||
}
|
||||
}
|
||||
++chan;
|
||||
}
|
||||
|
||||
base += td;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ namespace {
|
|||
*/
|
||||
|
||||
|
||||
void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
|
||||
void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype,
|
||||
const size_t samples) noexcept
|
||||
{
|
||||
#define HANDLE_FMT(T) case T: al::LoadSampleArray<T>(dst, src, srcstep, samples); break
|
||||
|
|
@ -84,6 +84,11 @@ void LoadSamples(double *RESTRICT dst, const al::byte *src, const size_t srcstep
|
|||
HANDLE_FMT(FmtDouble);
|
||||
HANDLE_FMT(FmtMulaw);
|
||||
HANDLE_FMT(FmtAlaw);
|
||||
/* FIXME: Handle ADPCM decoding here. */
|
||||
case FmtIMA4:
|
||||
case FmtMSADPCM:
|
||||
std::fill_n(dst, samples, 0.0f);
|
||||
break;
|
||||
}
|
||||
#undef HANDLE_FMT
|
||||
}
|
||||
|
|
@ -124,7 +129,7 @@ constexpr float Deg2Rad(float x) noexcept
|
|||
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
|
||||
|
||||
|
||||
using complex_d = std::complex<double>;
|
||||
using complex_f = std::complex<float>;
|
||||
|
||||
constexpr size_t ConvolveUpdateSize{256};
|
||||
constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2};
|
||||
|
|
@ -187,21 +192,21 @@ struct ConvolutionState final : public EffectState {
|
|||
al::vector<std::array<float,ConvolveUpdateSamples>,16> mFilter;
|
||||
al::vector<std::array<float,ConvolveUpdateSamples*2>,16> mOutput;
|
||||
|
||||
alignas(16) std::array<complex_d,ConvolveUpdateSize> mFftBuffer{};
|
||||
alignas(16) std::array<complex_f,ConvolveUpdateSize> mFftBuffer{};
|
||||
|
||||
size_t mCurrentSegment{0};
|
||||
size_t mNumConvolveSegs{0};
|
||||
|
||||
struct ChannelData {
|
||||
alignas(16) FloatBufferLine mBuffer{};
|
||||
float mHfScale{};
|
||||
float mHfScale{}, mLfScale{};
|
||||
BandSplitter mFilter{};
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
};
|
||||
using ChannelDataArray = al::FlexArray<ChannelData>;
|
||||
std::unique_ptr<ChannelDataArray> mChans;
|
||||
std::unique_ptr<complex_d[]> mComplexData;
|
||||
std::unique_ptr<complex_f[]> mComplexData;
|
||||
|
||||
|
||||
ConvolutionState() = default;
|
||||
|
|
@ -212,7 +217,7 @@ struct ConvolutionState final : public EffectState {
|
|||
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
|
||||
{&ConvolutionState::NormalMix};
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -235,21 +240,24 @@ void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
|
|||
for(auto &chan : *mChans)
|
||||
{
|
||||
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
|
||||
chan.mFilter.processHfScale(src, chan.mHfScale);
|
||||
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
|
||||
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buffer)
|
||||
void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorage *buffer)
|
||||
{
|
||||
using UhjDecoderType = UhjDecoder<512>;
|
||||
static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding;
|
||||
|
||||
constexpr uint MaxConvolveAmbiOrder{1u};
|
||||
|
||||
mFifoPos = 0;
|
||||
mInput.fill(0.0f);
|
||||
decltype(mFilter){}.swap(mFilter);
|
||||
decltype(mOutput){}.swap(mOutput);
|
||||
mFftBuffer.fill(complex_d{});
|
||||
mFftBuffer.fill(complex_f{});
|
||||
|
||||
mCurrentSegment = 0;
|
||||
mNumConvolveSegs = 0;
|
||||
|
|
@ -258,27 +266,31 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff
|
|||
mComplexData = nullptr;
|
||||
|
||||
/* An empty buffer doesn't need a convolution filter. */
|
||||
if(!buffer.storage || buffer.storage->mSampleLen < 1) return;
|
||||
if(!buffer || buffer->mSampleLen < 1) return;
|
||||
|
||||
mChannels = buffer->mChannels;
|
||||
mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout;
|
||||
mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling;
|
||||
mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder);
|
||||
|
||||
constexpr size_t m{ConvolveUpdateSize/2 + 1};
|
||||
auto bytesPerSample = BytesFromFmt(buffer.storage->mType);
|
||||
auto realChannels = ChannelsFromFmt(buffer.storage->mChannels, buffer.storage->mAmbiOrder);
|
||||
auto numChannels = ChannelsFromFmt(buffer.storage->mChannels,
|
||||
minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder));
|
||||
const auto bytesPerSample = BytesFromFmt(buffer->mType);
|
||||
const auto realChannels = buffer->channelsFromFmt();
|
||||
const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder);
|
||||
|
||||
mChans = ChannelDataArray::Create(numChannels);
|
||||
|
||||
/* The impulse response needs to have the same sample rate as the input and
|
||||
* output. The bsinc24 resampler is decent, but there is high-frequency
|
||||
* attenation that some people may be able to pick up on. Since this is
|
||||
* attenuation that some people may be able to pick up on. Since this is
|
||||
* called very infrequently, go ahead and use the polyphase resampler.
|
||||
*/
|
||||
PPhaseResampler resampler;
|
||||
if(device->Frequency != buffer.storage->mSampleRate)
|
||||
resampler.init(buffer.storage->mSampleRate, device->Frequency);
|
||||
if(device->Frequency != buffer->mSampleRate)
|
||||
resampler.init(buffer->mSampleRate, device->Frequency);
|
||||
const auto resampledCount = static_cast<uint>(
|
||||
(uint64_t{buffer.storage->mSampleLen}*device->Frequency+(buffer.storage->mSampleRate-1)) /
|
||||
buffer.storage->mSampleRate);
|
||||
(uint64_t{buffer->mSampleLen}*device->Frequency+(buffer->mSampleRate-1)) /
|
||||
buffer->mSampleRate);
|
||||
|
||||
const BandSplitter splitter{device->mXOverFreq / static_cast<float>(device->Frequency)};
|
||||
for(auto &e : *mChans)
|
||||
|
|
@ -296,43 +308,61 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const Buffer &buff
|
|||
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
|
||||
|
||||
const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)};
|
||||
mComplexData = std::make_unique<complex_d[]>(complex_length);
|
||||
std::fill_n(mComplexData.get(), complex_length, complex_d{});
|
||||
mComplexData = std::make_unique<complex_f[]>(complex_length);
|
||||
std::fill_n(mComplexData.get(), complex_length, complex_f{});
|
||||
|
||||
mChannels = buffer.storage->mChannels;
|
||||
mAmbiLayout = buffer.storage->mAmbiLayout;
|
||||
mAmbiScaling = buffer.storage->mAmbiScaling;
|
||||
mAmbiOrder = minu(buffer.storage->mAmbiOrder, MaxConvolveAmbiOrder);
|
||||
/* Load the samples from the buffer. */
|
||||
const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)};
|
||||
auto srcsamples = std::make_unique<float[]>(srclinelength * numChannels);
|
||||
std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f);
|
||||
for(size_t c{0};c < numChannels && c < realChannels;++c)
|
||||
LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c,
|
||||
realChannels, buffer->mType, buffer->mSampleLen);
|
||||
|
||||
auto srcsamples = std::make_unique<double[]>(maxz(buffer.storage->mSampleLen, resampledCount));
|
||||
complex_d *filteriter = mComplexData.get() + mNumConvolveSegs*m;
|
||||
if(IsUHJ(mChannels))
|
||||
{
|
||||
auto decoder = std::make_unique<UhjDecoderType>();
|
||||
std::array<float*,4> samples{};
|
||||
for(size_t c{0};c < numChannels;++c)
|
||||
samples[c] = srcsamples.get() + srclinelength*c;
|
||||
decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen);
|
||||
}
|
||||
|
||||
auto ressamples = std::make_unique<double[]>(buffer->mSampleLen +
|
||||
(resampler ? resampledCount : 0));
|
||||
complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m;
|
||||
for(size_t c{0};c < numChannels;++c)
|
||||
{
|
||||
/* Load the samples from the buffer, and resample to match the device. */
|
||||
LoadSamples(srcsamples.get(), buffer.samples.data() + bytesPerSample*c, realChannels,
|
||||
buffer.storage->mType, buffer.storage->mSampleLen);
|
||||
if(device->Frequency != buffer.storage->mSampleRate)
|
||||
resampler.process(buffer.storage->mSampleLen, srcsamples.get(), resampledCount,
|
||||
srcsamples.get());
|
||||
/* Resample to match the device. */
|
||||
if(resampler)
|
||||
{
|
||||
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen,
|
||||
ressamples.get() + resampledCount);
|
||||
resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount,
|
||||
resampledCount, ressamples.get());
|
||||
}
|
||||
else
|
||||
std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get());
|
||||
|
||||
/* Store the first segment's samples in reverse in the time-domain, to
|
||||
* apply as a FIR filter.
|
||||
*/
|
||||
const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)};
|
||||
std::transform(srcsamples.get(), srcsamples.get()+first_size, mFilter[c].rbegin(),
|
||||
std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(),
|
||||
[](const double d) noexcept -> float { return static_cast<float>(d); });
|
||||
|
||||
auto fftbuffer = std::vector<std::complex<double>>(ConvolveUpdateSize);
|
||||
size_t done{first_size};
|
||||
for(size_t s{0};s < mNumConvolveSegs;++s)
|
||||
{
|
||||
const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)};
|
||||
|
||||
auto iter = std::copy_n(&srcsamples[done], todo, mFftBuffer.begin());
|
||||
auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin());
|
||||
done += todo;
|
||||
std::fill(iter, mFftBuffer.end(), complex_d{});
|
||||
std::fill(iter, fftbuffer.end(), std::complex<double>{});
|
||||
|
||||
forward_fft(mFftBuffer);
|
||||
filteriter = std::copy_n(mFftBuffer.cbegin(), m, filteriter);
|
||||
forward_fft(al::as_span(fftbuffer));
|
||||
filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -388,7 +418,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
|
|||
{ SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) }
|
||||
};
|
||||
|
||||
if(mNumConvolveSegs < 1)
|
||||
if(mNumConvolveSegs < 1) UNLIKELY
|
||||
return;
|
||||
|
||||
mMix = &ConvolutionState::NormalMix;
|
||||
|
|
@ -396,39 +426,36 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
|
|||
for(auto &chan : *mChans)
|
||||
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
|
||||
const float gain{slot->Gain};
|
||||
/* TODO: UHJ should be decoded to B-Format and processed that way, since
|
||||
* there's no telling if it can ever do a direct-out mix (even if the
|
||||
* device is outputing UHJ, the effect slot can feed another effect that's
|
||||
* not UHJ).
|
||||
*
|
||||
* Not that UHJ should really ever be used for convolution, but it's a
|
||||
* valid format regardless.
|
||||
*/
|
||||
if((mChannels == FmtUHJ2 || mChannels == FmtUHJ3 || mChannels == FmtUHJ4) && target.RealOut
|
||||
&& target.RealOut->ChannelIndex[FrontLeft] != INVALID_CHANNEL_INDEX
|
||||
&& target.RealOut->ChannelIndex[FrontRight] != INVALID_CHANNEL_INDEX)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
const uint lidx = target.RealOut->ChannelIndex[FrontLeft];
|
||||
const uint ridx = target.RealOut->ChannelIndex[FrontRight];
|
||||
(*mChans)[0].Target[lidx] = gain;
|
||||
(*mChans)[1].Target[ridx] = gain;
|
||||
}
|
||||
else if(IsBFormat(mChannels))
|
||||
if(IsAmbisonic(mChannels))
|
||||
{
|
||||
DeviceBase *device{context->mDevice};
|
||||
if(device->mAmbiOrder > mAmbiOrder)
|
||||
if(mChannels == FmtUHJ2 && !device->mUhjEncoder)
|
||||
{
|
||||
mMix = &ConvolutionState::UpsampleMix;
|
||||
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder);
|
||||
(*mChans)[0].mHfScale = 1.0f;
|
||||
(*mChans)[0].mLfScale = DecoderBase::sWLFScale;
|
||||
(*mChans)[1].mHfScale = 1.0f;
|
||||
(*mChans)[1].mLfScale = DecoderBase::sXYLFScale;
|
||||
(*mChans)[2].mHfScale = 1.0f;
|
||||
(*mChans)[2].mLfScale = DecoderBase::sXYLFScale;
|
||||
}
|
||||
else if(device->mAmbiOrder > mAmbiOrder)
|
||||
{
|
||||
mMix = &ConvolutionState::UpsampleMix;
|
||||
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder,
|
||||
device->m2DMixing);
|
||||
(*mChans)[0].mHfScale = scales[0];
|
||||
(*mChans)[0].mLfScale = 1.0f;
|
||||
for(size_t i{1};i < mChans->size();++i)
|
||||
{
|
||||
(*mChans)[i].mHfScale = scales[1];
|
||||
(*mChans)[i].mLfScale = 1.0f;
|
||||
}
|
||||
}
|
||||
mOutTarget = target.Main->Buffer;
|
||||
|
||||
auto&& scales = GetAmbiScales(mAmbiScaling);
|
||||
const uint8_t *index_map{(mChannels == FmtBFormat2D) ?
|
||||
const uint8_t *index_map{Is2DAmbisonic(mChannels) ?
|
||||
GetAmbi2DLayout(mAmbiLayout).data() :
|
||||
GetAmbiLayout(mAmbiLayout).data()};
|
||||
|
||||
|
|
@ -495,7 +522,7 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot
|
|||
void ConvolutionState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
if(mNumConvolveSegs < 1)
|
||||
if(mNumConvolveSegs < 1) UNLIKELY
|
||||
return;
|
||||
|
||||
constexpr size_t m{ConvolveUpdateSize/2 + 1};
|
||||
|
|
@ -515,8 +542,7 @@ void ConvolutionState::process(const size_t samplesToDo,
|
|||
for(size_t c{0};c < chans.size();++c)
|
||||
{
|
||||
auto buf_iter = chans[c].mBuffer.begin() + base;
|
||||
apply_fir({std::addressof(*buf_iter), todo}, mInput.data()+1 + mFifoPos,
|
||||
mFilter[c].data());
|
||||
apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data());
|
||||
|
||||
auto fifo_iter = mOutput[c].begin() + mFifoPos;
|
||||
std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{});
|
||||
|
|
@ -536,20 +562,20 @@ void ConvolutionState::process(const size_t samplesToDo,
|
|||
* frequency bins to the FFT history.
|
||||
*/
|
||||
auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin());
|
||||
std::fill(fftiter, mFftBuffer.end(), complex_d{});
|
||||
forward_fft(mFftBuffer);
|
||||
std::fill(fftiter, mFftBuffer.end(), complex_f{});
|
||||
forward_fft(al::as_span(mFftBuffer));
|
||||
|
||||
std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]);
|
||||
|
||||
const complex_d *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
|
||||
const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m};
|
||||
for(size_t c{0};c < chans.size();++c)
|
||||
{
|
||||
std::fill_n(mFftBuffer.begin(), m, complex_d{});
|
||||
std::fill_n(mFftBuffer.begin(), m, complex_f{});
|
||||
|
||||
/* Convolve each input segment with its IR filter counterpart
|
||||
* (aligned in time).
|
||||
*/
|
||||
const complex_d *RESTRICT input{&mComplexData[curseg*m]};
|
||||
const complex_f *RESTRICT input{&mComplexData[curseg*m]};
|
||||
for(size_t s{curseg};s < mNumConvolveSegs;++s)
|
||||
{
|
||||
for(size_t i{0};i < m;++i,++input,++filter)
|
||||
|
|
@ -573,19 +599,17 @@ void ConvolutionState::process(const size_t samplesToDo,
|
|||
* second-half samples (and this output's second half is
|
||||
* subsequently saved for next time).
|
||||
*/
|
||||
inverse_fft(mFftBuffer);
|
||||
inverse_fft(al::as_span(mFftBuffer));
|
||||
|
||||
/* 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] =
|
||||
static_cast<float>(mFftBuffer[i].real() * (1.0/double{ConvolveUpdateSize})) +
|
||||
mOutput[c][ConvolveUpdateSamples+i];
|
||||
(mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) *
|
||||
(1.0f/float{ConvolveUpdateSize});
|
||||
for(size_t i{0};i < ConvolveUpdateSamples;++i)
|
||||
mOutput[c][ConvolveUpdateSamples+i] =
|
||||
static_cast<float>(mFftBuffer[ConvolveUpdateSamples+i].real() *
|
||||
(1.0/double{ConvolveUpdateSize}));
|
||||
mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real();
|
||||
}
|
||||
|
||||
/* Shift the input history. */
|
||||
|
|
|
|||
|
|
@ -43,11 +43,15 @@ namespace {
|
|||
using uint = unsigned int;
|
||||
|
||||
struct DedicatedState final : public EffectState {
|
||||
/* The "dedicated" effect can output to the real output, so should have
|
||||
* gains for all possible output channels and not just the main ambisonic
|
||||
* buffer.
|
||||
*/
|
||||
float mCurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
float mTargetGains[MAX_OUTPUT_CHANNELS];
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -56,7 +60,7 @@ struct DedicatedState final : public EffectState {
|
|||
DEF_NEWDEL(DedicatedState)
|
||||
};
|
||||
|
||||
void DedicatedState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
|
||||
}
|
||||
|
|
@ -70,9 +74,8 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
|
|||
|
||||
if(slot->EffectType == EffectSlotType::DedicatedLFE)
|
||||
{
|
||||
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, LFE)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex};
|
||||
if(idx != InvalidChannelIndex)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
|
|
@ -82,16 +85,16 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
|
|||
{
|
||||
/* Dialog goes to the front-center speaker if it exists, otherwise it
|
||||
* plays from the front-center location. */
|
||||
const uint idx{!target.RealOut ? INVALID_CHANNEL_INDEX :
|
||||
GetChannelIdxByName(*target.RealOut, FrontCenter)};
|
||||
if(idx != INVALID_CHANNEL_INDEX)
|
||||
const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter]
|
||||
: InvalidChannelIndex};
|
||||
if(idx != InvalidChannelIndex)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
|
||||
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ namespace {
|
|||
|
||||
struct DistortionState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
float mGain[MAX_OUTPUT_CHANNELS]{};
|
||||
float mGain[MaxAmbiChannels]{};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter mLowpass;
|
||||
|
|
@ -53,10 +53,10 @@ struct DistortionState final : public EffectState {
|
|||
float mAttenuation{};
|
||||
float mEdgeCoeff{};
|
||||
|
||||
float mBuffer[2][BufferLineSize]{};
|
||||
alignas(16) float mBuffer[2][BufferLineSize]{};
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -65,7 +65,7 @@ struct DistortionState final : public EffectState {
|
|||
DEF_NEWDEL(DistortionState)
|
||||
};
|
||||
|
||||
void DistortionState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
mLowpass.clear();
|
||||
mBandpass.clear();
|
||||
|
|
@ -95,7 +95,7 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f);
|
||||
mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
|
||||
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ struct EchoState final : public EffectState {
|
|||
|
||||
/* The panning gains for the two taps */
|
||||
struct {
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
float Current[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
BiquadFilter mFilter;
|
||||
|
|
@ -69,7 +69,7 @@ struct EchoState final : public EffectState {
|
|||
|
||||
alignas(16) float mTempBuffer[2][BufferLineSize];
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -78,7 +78,7 @@ struct EchoState final : public EffectState {
|
|||
DEF_NEWDEL(EchoState)
|
||||
};
|
||||
|
||||
void EchoState::deviceUpdate(const DeviceBase *Device, const Buffer&)
|
||||
void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*)
|
||||
{
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
|
||||
|
|
|
|||
|
|
@ -87,18 +87,20 @@ namespace {
|
|||
|
||||
struct EqualizerState final : public EffectState {
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter filter[4];
|
||||
BiquadFilter mFilter[4];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
float TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
FloatBufferLine mSampleBuffer{};
|
||||
alignas(16) FloatBufferLine mSampleBuffer{};
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -107,12 +109,14 @@ struct EqualizerState final : public EffectState {
|
|||
DEF_NEWDEL(EqualizerState)
|
||||
};
|
||||
|
||||
void EqualizerState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
std::for_each(std::begin(e.filter), std::end(e.filter), std::mem_fn(&BiquadFilter::clear));
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
e.mTargetChannel = InvalidChannelIndex;
|
||||
std::for_each(std::begin(e.mFilter), std::end(e.mFilter),
|
||||
std::mem_fn(&BiquadFilter::clear));
|
||||
e.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,48 +135,56 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
*/
|
||||
gain = std::sqrt(props->Equalizer.LowGain);
|
||||
f0norm = props->Equalizer.LowCutoff / frequency;
|
||||
mChans[0].filter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
|
||||
mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.Mid1Gain);
|
||||
f0norm = props->Equalizer.Mid1Center / frequency;
|
||||
mChans[0].filter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
props->Equalizer.Mid1Width);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.Mid2Gain);
|
||||
f0norm = props->Equalizer.Mid2Center / frequency;
|
||||
mChans[0].filter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
props->Equalizer.Mid2Width);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.HighGain);
|
||||
f0norm = props->Equalizer.HighCutoff / frequency;
|
||||
mChans[0].filter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
|
||||
mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f);
|
||||
|
||||
/* Copy the filter coefficients for the other input channels. */
|
||||
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
|
||||
{
|
||||
mChans[i].filter[0].copyParamsFrom(mChans[0].filter[0]);
|
||||
mChans[i].filter[1].copyParamsFrom(mChans[0].filter[1]);
|
||||
mChans[i].filter[2].copyParamsFrom(mChans[0].filter[2]);
|
||||
mChans[i].filter[3].copyParamsFrom(mChans[0].filter[3]);
|
||||
mChans[i].mFilter[0].copyParamsFrom(mChans[0].mFilter[0]);
|
||||
mChans[i].mFilter[1].copyParamsFrom(mChans[0].mFilter[1]);
|
||||
mChans[i].mFilter[2].copyParamsFrom(mChans[0].mFilter[2]);
|
||||
mChans[i].mFilter[3].copyParamsFrom(mChans[0].mFilter[3]);
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
|
||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
|
||||
auto set_channel = [this](size_t idx, uint outchan, float outgain)
|
||||
{
|
||||
mChans[idx].mTargetChannel = outchan;
|
||||
mChans[idx].mTargetGain = outgain;
|
||||
};
|
||||
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
|
||||
}
|
||||
|
||||
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::addressof(mChans[0]);
|
||||
auto chan = std::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
const al::span<const float> inbuf{input.data(), samplesToDo};
|
||||
DualBiquad{chan->filter[0], chan->filter[1]}.process(inbuf, buffer.begin());
|
||||
DualBiquad{chan->filter[2], chan->filter[3]}.process(buffer, buffer.begin());
|
||||
const size_t outidx{chan->mTargetChannel};
|
||||
if(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());
|
||||
|
||||
MixSamples(buffer, samplesOut, chan->CurrentGains, chan->TargetGains, samplesToDo, 0u);
|
||||
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
|
||||
samplesToDo);
|
||||
}
|
||||
++chan;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,53 +48,56 @@ namespace {
|
|||
using uint = unsigned int;
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
#define HIL_SIZE 1024
|
||||
#define OVERSAMP (1<<2)
|
||||
constexpr size_t HilSize{1024};
|
||||
constexpr size_t HilHalfSize{HilSize >> 1};
|
||||
constexpr size_t OversampleFactor{4};
|
||||
|
||||
#define HIL_STEP (HIL_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (HIL_STEP * (OVERSAMP-1))
|
||||
static_assert(HilSize%OversampleFactor == 0, "Factor must be a clean divisor of the size");
|
||||
constexpr size_t HilStep{HilSize / OversampleFactor};
|
||||
|
||||
/* Define a Hann window, used to filter the HIL input and output. */
|
||||
std::array<double,HIL_SIZE> InitHannWindow()
|
||||
{
|
||||
std::array<double,HIL_SIZE> ret;
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. HIL_SIZE */
|
||||
for(size_t i{0};i < HIL_SIZE>>1;i++)
|
||||
struct Windower {
|
||||
alignas(16) std::array<double,HilSize> mData;
|
||||
|
||||
Windower()
|
||||
{
|
||||
constexpr double scale{al::numbers::pi / double{HIL_SIZE}};
|
||||
const double val{std::sin(static_cast<double>(i+1) * scale)};
|
||||
ret[i] = ret[HIL_SIZE-1-i] = val * val;
|
||||
/* Create lookup table of the Hann window for the desired size. */
|
||||
for(size_t i{0};i < HilHalfSize;i++)
|
||||
{
|
||||
constexpr double scale{al::numbers::pi / double{HilSize}};
|
||||
const double val{std::sin((static_cast<double>(i)+0.5) * scale)};
|
||||
mData[i] = mData[HilSize-1-i] = val * val;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
alignas(16) const std::array<double,HIL_SIZE> HannWindow = InitHannWindow();
|
||||
};
|
||||
const Windower gWindow{};
|
||||
|
||||
|
||||
struct FshifterState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
size_t mCount{};
|
||||
size_t mPos{};
|
||||
uint mPhaseStep[2]{};
|
||||
uint mPhase[2]{};
|
||||
double mSign[2]{};
|
||||
std::array<uint,2> mPhaseStep{};
|
||||
std::array<uint,2> mPhase{};
|
||||
std::array<double,2> mSign{};
|
||||
|
||||
/* Effects buffers */
|
||||
double mInFIFO[HIL_SIZE]{};
|
||||
complex_d mOutFIFO[HIL_STEP]{};
|
||||
complex_d mOutputAccum[HIL_SIZE]{};
|
||||
complex_d mAnalytic[HIL_SIZE]{};
|
||||
complex_d mOutdata[BufferLineSize]{};
|
||||
std::array<double,HilSize> mInFIFO{};
|
||||
std::array<complex_d,HilStep> mOutFIFO{};
|
||||
std::array<complex_d,HilSize> mOutputAccum{};
|
||||
std::array<complex_d,HilSize> mAnalytic{};
|
||||
std::array<complex_d,BufferLineSize> mOutdata{};
|
||||
|
||||
alignas(16) float mBufferOut[BufferLineSize]{};
|
||||
alignas(16) FloatBufferLine mBufferOut{};
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
struct {
|
||||
float Current[MAX_OUTPUT_CHANNELS]{};
|
||||
float Target[MAX_OUTPUT_CHANNELS]{};
|
||||
float Current[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -103,19 +106,19 @@ struct FshifterState final : public EffectState {
|
|||
DEF_NEWDEL(FshifterState)
|
||||
};
|
||||
|
||||
void FshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = 0;
|
||||
mPos = FIFO_LATENCY;
|
||||
mPos = HilSize - HilStep;
|
||||
|
||||
std::fill(std::begin(mPhaseStep), std::end(mPhaseStep), 0u);
|
||||
std::fill(std::begin(mPhase), std::end(mPhase), 0u);
|
||||
std::fill(std::begin(mSign), std::end(mSign), 1.0);
|
||||
std::fill(std::begin(mInFIFO), std::end(mInFIFO), 0.0);
|
||||
std::fill(std::begin(mOutFIFO), std::end(mOutFIFO), complex_d{});
|
||||
std::fill(std::begin(mOutputAccum), std::end(mOutputAccum), complex_d{});
|
||||
std::fill(std::begin(mAnalytic), std::end(mAnalytic), complex_d{});
|
||||
mPhaseStep.fill(0u);
|
||||
mPhase.fill(0u);
|
||||
mSign.fill(1.0);
|
||||
mInFIFO.fill(0.0);
|
||||
mOutFIFO.fill(complex_d{});
|
||||
mOutputAccum.fill(complex_d{});
|
||||
mAnalytic.fill(complex_d{});
|
||||
|
||||
for(auto &gain : mGains)
|
||||
{
|
||||
|
|
@ -160,8 +163,13 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
break;
|
||||
}
|
||||
|
||||
const auto lcoeffs = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
const auto rcoeffs = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}, 0.0f);
|
||||
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;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target);
|
||||
|
|
@ -172,7 +180,7 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
|
|||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
size_t todo{minz(HIL_STEP-mCount, samplesToDo-base)};
|
||||
size_t todo{minz(HilStep-mCount, samplesToDo-base)};
|
||||
|
||||
/* Fill FIFO buffer with samples data */
|
||||
const size_t pos{mPos};
|
||||
|
|
@ -185,33 +193,33 @@ void FshifterState::process(const size_t samplesToDo, const al::span<const Float
|
|||
mCount = count;
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(mCount < HIL_STEP) break;
|
||||
if(mCount < HilStep) break;
|
||||
mCount = 0;
|
||||
mPos = (mPos+HIL_STEP) & (HIL_SIZE-1);
|
||||
mPos = (mPos+HilStep) & (HilSize-1);
|
||||
|
||||
/* Real signal windowing and store in Analytic buffer */
|
||||
for(size_t src{mPos}, k{0u};src < HIL_SIZE;++src,++k)
|
||||
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
|
||||
for(size_t src{0u}, k{HIL_SIZE-mPos};src < mPos;++src,++k)
|
||||
mAnalytic[k] = mInFIFO[src]*HannWindow[k];
|
||||
for(size_t src{mPos}, k{0u};src < HilSize;++src,++k)
|
||||
mAnalytic[k] = mInFIFO[src]*gWindow.mData[k];
|
||||
for(size_t src{0u}, k{HilSize-mPos};src < mPos;++src,++k)
|
||||
mAnalytic[k] = mInFIFO[src]*gWindow.mData[k];
|
||||
|
||||
/* Processing signal by Discrete Hilbert Transform (analytical signal). */
|
||||
complex_hilbert(mAnalytic);
|
||||
|
||||
/* Windowing and add to output accumulator */
|
||||
for(size_t dst{mPos}, k{0u};dst < HIL_SIZE;++dst,++k)
|
||||
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
|
||||
for(size_t dst{0u}, k{HIL_SIZE-mPos};dst < mPos;++dst,++k)
|
||||
mOutputAccum[dst] += 2.0/OVERSAMP*HannWindow[k]*mAnalytic[k];
|
||||
for(size_t dst{mPos}, k{0u};dst < HilSize;++dst,++k)
|
||||
mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k];
|
||||
for(size_t dst{0u}, k{HilSize-mPos};dst < mPos;++dst,++k)
|
||||
mOutputAccum[dst] += 2.0/OversampleFactor*gWindow.mData[k]*mAnalytic[k];
|
||||
|
||||
/* Copy out the accumulated result, then clear for the next iteration. */
|
||||
std::copy_n(mOutputAccum + mPos, HIL_STEP, mOutFIFO);
|
||||
std::fill_n(mOutputAccum + mPos, HIL_STEP, complex_d{});
|
||||
std::copy_n(mOutputAccum.cbegin() + mPos, HilStep, mOutFIFO.begin());
|
||||
std::fill_n(mOutputAccum.begin() + mPos, HilStep, complex_d{});
|
||||
}
|
||||
|
||||
/* Process frequency shifter using the analytic signal obtained. */
|
||||
float *RESTRICT BufferOut{mBufferOut};
|
||||
for(int c{0};c < 2;++c)
|
||||
float *RESTRICT BufferOut{al::assume_aligned<16>(mBufferOut.data())};
|
||||
for(size_t c{0};c < 2;++c)
|
||||
{
|
||||
const uint phase_step{mPhaseStep[c]};
|
||||
uint phase_idx{mPhase[c]};
|
||||
|
|
|
|||
|
|
@ -84,14 +84,16 @@ struct ModulatorState final : public EffectState {
|
|||
uint mStep{1};
|
||||
|
||||
struct {
|
||||
BiquadFilter Filter;
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
float TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
BiquadFilter mFilter;
|
||||
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -100,12 +102,13 @@ struct ModulatorState final : public EffectState {
|
|||
DEF_NEWDEL(ModulatorState)
|
||||
};
|
||||
|
||||
void ModulatorState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
e.Filter.clear();
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
e.mTargetChannel = InvalidChannelIndex;
|
||||
e.mFilter.clear();
|
||||
e.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,14 +132,17 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
float f0norm{props->Modulator.HighPassCutoff / static_cast<float>(device->Frequency)};
|
||||
f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f);
|
||||
/* Bandwidth value is constant in octaves. */
|
||||
mChans[0].Filter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
|
||||
mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
|
||||
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
|
||||
mChans[i].Filter.copyParamsFrom(mChans[0].Filter);
|
||||
mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
|
||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
|
||||
auto set_channel = [this](size_t idx, uint outchan, float outgain)
|
||||
{
|
||||
mChans[idx].mTargetChannel = outchan;
|
||||
mChans[idx].mTargetGain = outgain;
|
||||
};
|
||||
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
|
||||
}
|
||||
|
||||
void ModulatorState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
|
|
@ -153,14 +159,18 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa
|
|||
auto chandata = std::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
alignas(16) float temps[MAX_UPDATE_SAMPLES];
|
||||
const size_t outidx{chandata->mTargetChannel};
|
||||
if(outidx != InvalidChannelIndex)
|
||||
{
|
||||
alignas(16) float temps[MAX_UPDATE_SAMPLES];
|
||||
|
||||
chandata->Filter.process({&input[base], td}, temps);
|
||||
for(size_t i{0u};i < td;i++)
|
||||
temps[i] *= modsamples[i];
|
||||
chandata->mFilter.process({&input[base], td}, temps);
|
||||
for(size_t i{0u};i < td;i++)
|
||||
temps[i] *= modsamples[i];
|
||||
|
||||
MixSamples({temps, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
|
||||
samplesToDo-base, base);
|
||||
MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
|
||||
chandata->mTargetGain, samplesToDo-base);
|
||||
}
|
||||
++chandata;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ struct NullState final : public EffectState {
|
|||
NullState();
|
||||
~NullState() override;
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -44,7 +44,7 @@ NullState::~NullState() = default;
|
|||
* format) have been changed. Will always be followed by a call to the update
|
||||
* method, if successful.
|
||||
*/
|
||||
void NullState::deviceUpdate(const DeviceBase* /*device*/, const Buffer& /*buffer*/)
|
||||
void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,34 +47,36 @@ struct ContextBase;
|
|||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
using complex_d = std::complex<double>;
|
||||
using complex_f = std::complex<float>;
|
||||
|
||||
#define STFT_SIZE 1024
|
||||
#define STFT_HALF_SIZE (STFT_SIZE>>1)
|
||||
#define OVERSAMP (1<<2)
|
||||
constexpr size_t StftSize{1024};
|
||||
constexpr size_t StftHalfSize{StftSize >> 1};
|
||||
constexpr size_t OversampleFactor{8};
|
||||
|
||||
#define STFT_STEP (STFT_SIZE / OVERSAMP)
|
||||
#define FIFO_LATENCY (STFT_STEP * (OVERSAMP-1))
|
||||
static_assert(StftSize%OversampleFactor == 0, "Factor must be a clean divisor of the size");
|
||||
constexpr size_t StftStep{StftSize / OversampleFactor};
|
||||
|
||||
/* Define a Hann window, used to filter the STFT input and output. */
|
||||
std::array<double,STFT_SIZE> InitHannWindow()
|
||||
{
|
||||
std::array<double,STFT_SIZE> ret;
|
||||
/* Create lookup table of the Hann window for the desired size, i.e. STFT_SIZE */
|
||||
for(size_t i{0};i < STFT_SIZE>>1;i++)
|
||||
struct Windower {
|
||||
alignas(16) std::array<float,StftSize> mData;
|
||||
|
||||
Windower()
|
||||
{
|
||||
constexpr double scale{al::numbers::pi / double{STFT_SIZE}};
|
||||
const double val{std::sin(static_cast<double>(i+1) * scale)};
|
||||
ret[i] = ret[STFT_SIZE-1-i] = val * val;
|
||||
/* Create lookup table of the Hann window for the desired size. */
|
||||
for(size_t i{0};i < StftHalfSize;i++)
|
||||
{
|
||||
constexpr double scale{al::numbers::pi / double{StftSize}};
|
||||
const double val{std::sin((static_cast<double>(i)+0.5) * scale)};
|
||||
mData[i] = mData[StftSize-1-i] = static_cast<float>(val * val);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
alignas(16) const std::array<double,STFT_SIZE> HannWindow = InitHannWindow();
|
||||
};
|
||||
const Windower gWindow{};
|
||||
|
||||
|
||||
struct FrequencyBin {
|
||||
double Amplitude;
|
||||
double FreqBin;
|
||||
float Magnitude;
|
||||
float FreqBin;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -83,27 +85,27 @@ struct PshifterState final : public EffectState {
|
|||
size_t mCount;
|
||||
size_t mPos;
|
||||
uint mPitchShiftI;
|
||||
double mPitchShift;
|
||||
float mPitchShift;
|
||||
|
||||
/* Effects buffers */
|
||||
std::array<double,STFT_SIZE> mFIFO;
|
||||
std::array<double,STFT_HALF_SIZE+1> mLastPhase;
|
||||
std::array<double,STFT_HALF_SIZE+1> mSumPhase;
|
||||
std::array<double,STFT_SIZE> 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_d,STFT_SIZE> mFftBuffer;
|
||||
std::array<complex_f,StftSize> mFftBuffer;
|
||||
|
||||
std::array<FrequencyBin,STFT_HALF_SIZE+1> mAnalysisBuffer;
|
||||
std::array<FrequencyBin,STFT_HALF_SIZE+1> mSynthesisBuffer;
|
||||
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
|
||||
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
|
||||
|
||||
alignas(16) FloatBufferLine mBufferOut;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
float mCurrentGains[MAX_OUTPUT_CHANNELS];
|
||||
float mTargetGains[MAX_OUTPUT_CHANNELS];
|
||||
float mCurrentGains[MaxAmbiChannels];
|
||||
float mTargetGains[MaxAmbiChannels];
|
||||
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -112,21 +114,21 @@ struct PshifterState final : public EffectState {
|
|||
DEF_NEWDEL(PshifterState)
|
||||
};
|
||||
|
||||
void PshifterState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = 0;
|
||||
mPos = FIFO_LATENCY;
|
||||
mPos = StftSize - StftStep;
|
||||
mPitchShiftI = MixerFracOne;
|
||||
mPitchShift = 1.0;
|
||||
mPitchShift = 1.0f;
|
||||
|
||||
std::fill(mFIFO.begin(), mFIFO.end(), 0.0);
|
||||
std::fill(mLastPhase.begin(), mLastPhase.end(), 0.0);
|
||||
std::fill(mSumPhase.begin(), mSumPhase.end(), 0.0);
|
||||
std::fill(mOutputAccum.begin(), mOutputAccum.end(), 0.0);
|
||||
std::fill(mFftBuffer.begin(), mFftBuffer.end(), complex_d{});
|
||||
std::fill(mAnalysisBuffer.begin(), mAnalysisBuffer.end(), FrequencyBin{});
|
||||
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
|
||||
mFIFO.fill(0.0f);
|
||||
mLastPhase.fill(0.0f);
|
||||
mSumPhase.fill(0.0f);
|
||||
mOutputAccum.fill(0.0f);
|
||||
mFftBuffer.fill(complex_f{});
|
||||
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);
|
||||
|
|
@ -137,16 +139,17 @@ void PshifterState::update(const ContextBase*, const EffectSlot *slot,
|
|||
{
|
||||
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
|
||||
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
|
||||
mPitchShiftI = fastf2u(pitch*MixerFracOne);
|
||||
mPitchShift = mPitchShiftI * double{1.0/MixerFracOne};
|
||||
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
|
||||
mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne};
|
||||
|
||||
const auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}, 0.0f);
|
||||
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains);
|
||||
}
|
||||
|
||||
void PshifterState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
void PshifterState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
/* Pitch shifter engine based on the work of Stephan Bernsee.
|
||||
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
|
||||
|
|
@ -155,103 +158,133 @@ void PshifterState::process(const size_t samplesToDo, const al::span<const Float
|
|||
/* Cycle offset per update expected of each frequency bin (bin 0 is none,
|
||||
* bin 1 is x1, bin 2 is x2, etc).
|
||||
*/
|
||||
constexpr double expected_cycles{al::numbers::pi*2.0 / OVERSAMP};
|
||||
constexpr float expected_cycles{al::numbers::pi_v<float>*2.0f / OversampleFactor};
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
const size_t todo{minz(STFT_STEP-mCount, samplesToDo-base)};
|
||||
const size_t todo{minz(StftStep-mCount, samplesToDo-base)};
|
||||
|
||||
/* Retrieve the output samples from the FIFO and fill in the new input
|
||||
* samples.
|
||||
*/
|
||||
auto fifo_iter = mFIFO.begin()+mPos + mCount;
|
||||
std::transform(fifo_iter, fifo_iter+todo, mBufferOut.begin()+base,
|
||||
[](double d) noexcept -> float { return static_cast<float>(d); });
|
||||
std::copy_n(fifo_iter, todo, mBufferOut.begin()+base);
|
||||
|
||||
std::copy_n(samplesIn[0].begin()+base, todo, fifo_iter);
|
||||
mCount += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether FIFO buffer is filled with new samples. */
|
||||
if(mCount < STFT_STEP) break;
|
||||
if(mCount < StftStep) break;
|
||||
mCount = 0;
|
||||
mPos = (mPos+STFT_STEP) & (mFIFO.size()-1);
|
||||
mPos = (mPos+StftStep) & (mFIFO.size()-1);
|
||||
|
||||
/* Time-domain signal windowing, store in FftBuffer, and apply a
|
||||
* forward FFT to get the frequency-domain signal.
|
||||
*/
|
||||
for(size_t src{mPos}, k{0u};src < STFT_SIZE;++src,++k)
|
||||
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
|
||||
for(size_t src{0u}, k{STFT_SIZE-mPos};src < mPos;++src,++k)
|
||||
mFftBuffer[k] = mFIFO[src] * HannWindow[k];
|
||||
forward_fft(mFftBuffer);
|
||||
for(size_t src{mPos}, k{0u};src < StftSize;++src,++k)
|
||||
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));
|
||||
|
||||
/* Analyze the obtained data. Since the real FFT is symmetric, only
|
||||
* STFT_HALF_SIZE+1 samples are needed.
|
||||
* StftHalfSize+1 samples are needed.
|
||||
*/
|
||||
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
for(size_t k{0u};k < StftHalfSize+1;k++)
|
||||
{
|
||||
const double amplitude{std::abs(mFftBuffer[k])};
|
||||
const double phase{std::arg(mFftBuffer[k])};
|
||||
const float magnitude{std::abs(mFftBuffer[k])};
|
||||
const float phase{std::arg(mFftBuffer[k])};
|
||||
|
||||
/* Compute phase difference and subtract expected phase difference */
|
||||
double tmp{(phase - mLastPhase[k]) - static_cast<double>(k)*expected_cycles};
|
||||
|
||||
/* Map delta phase into +/- Pi interval */
|
||||
int qpd{double2int(tmp / al::numbers::pi)};
|
||||
tmp -= al::numbers::pi * (qpd + (qpd%2));
|
||||
|
||||
/* Get deviation from bin frequency from the +/- Pi interval */
|
||||
tmp /= expected_cycles;
|
||||
|
||||
/* Compute the k-th partials' true frequency and store the
|
||||
* amplitude and frequency bin in the analysis buffer.
|
||||
/* Compute the phase difference from the last update and subtract
|
||||
* the expected phase difference for this bin.
|
||||
*
|
||||
* When oversampling, the expected per-update offset increments by
|
||||
* 1/OversampleFactor for every frequency bin. So, the offset wraps
|
||||
* every 'OversampleFactor' bin.
|
||||
*/
|
||||
mAnalysisBuffer[k].Amplitude = amplitude;
|
||||
mAnalysisBuffer[k].FreqBin = static_cast<double>(k) + tmp;
|
||||
|
||||
/* Store the actual phase[k] for the next frame. */
|
||||
const auto bin_offset = static_cast<float>(k % OversampleFactor);
|
||||
float tmp{(phase - mLastPhase[k]) - bin_offset*expected_cycles};
|
||||
/* Store the actual phase for the next update. */
|
||||
mLastPhase[k] = phase;
|
||||
|
||||
/* Normalize from pi, and wrap the delta between -1 and +1. */
|
||||
tmp *= al::numbers::inv_pi_v<float>;
|
||||
int qpd{float2int(tmp)};
|
||||
tmp -= static_cast<float>(qpd + (qpd%2));
|
||||
|
||||
/* Get deviation from bin frequency (-0.5 to +0.5), and account for
|
||||
* oversampling.
|
||||
*/
|
||||
tmp *= 0.5f * OversampleFactor;
|
||||
|
||||
/* Compute the k-th partials' frequency bin target and store the
|
||||
* magnitude and frequency bin in the analysis buffer. We don't
|
||||
* need the "true frequency" since it's a linear relationship with
|
||||
* the bin.
|
||||
*/
|
||||
mAnalysisBuffer[k].Magnitude = magnitude;
|
||||
mAnalysisBuffer[k].FreqBin = static_cast<float>(k) + tmp;
|
||||
}
|
||||
|
||||
/* Shift the frequency bins according to the pitch adjustment,
|
||||
* accumulating the amplitudes of overlapping frequency bins.
|
||||
* accumulating the magnitudes of overlapping frequency bins.
|
||||
*/
|
||||
std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{});
|
||||
const size_t bin_count{minz(STFT_HALF_SIZE+1,
|
||||
(((STFT_HALF_SIZE+1)<<MixerFracBits) - (MixerFracOne>>1) - 1)/mPitchShiftI + 1)};
|
||||
|
||||
constexpr size_t bin_limit{((StftHalfSize+1)<<MixerFracBits) - MixerFracHalf - 1};
|
||||
const size_t bin_count{minz(StftHalfSize+1, bin_limit/mPitchShiftI + 1)};
|
||||
for(size_t k{0u};k < bin_count;k++)
|
||||
{
|
||||
const size_t j{(k*mPitchShiftI + (MixerFracOne>>1)) >> MixerFracBits};
|
||||
mSynthesisBuffer[j].Amplitude += mAnalysisBuffer[k].Amplitude;
|
||||
mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift;
|
||||
const size_t j{(k*mPitchShiftI + MixerFracHalf) >> MixerFracBits};
|
||||
|
||||
/* If more than two bins end up together, use the target frequency
|
||||
* bin for the one with the dominant magnitude. There might be a
|
||||
* better way to handle this, but it's better than last-index-wins.
|
||||
*/
|
||||
if(mAnalysisBuffer[k].Magnitude > mSynthesisBuffer[j].Magnitude)
|
||||
mSynthesisBuffer[j].FreqBin = mAnalysisBuffer[k].FreqBin * mPitchShift;
|
||||
mSynthesisBuffer[j].Magnitude += mAnalysisBuffer[k].Magnitude;
|
||||
}
|
||||
|
||||
/* Reconstruct the frequency-domain signal from the adjusted frequency
|
||||
* bins.
|
||||
*/
|
||||
for(size_t k{0u};k < STFT_HALF_SIZE+1;k++)
|
||||
for(size_t k{0u};k < StftHalfSize+1;k++)
|
||||
{
|
||||
/* Calculate actual delta phase and accumulate it to get bin phase */
|
||||
mSumPhase[k] += mSynthesisBuffer[k].FreqBin * expected_cycles;
|
||||
/* Calculate the actual delta phase for this bin's target frequency
|
||||
* bin, and accumulate it to get the actual bin phase.
|
||||
*/
|
||||
float tmp{mSumPhase[k] + mSynthesisBuffer[k].FreqBin*expected_cycles};
|
||||
|
||||
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Amplitude, mSumPhase[k]);
|
||||
/* Wrap between -pi and +pi for the sum. If mSumPhase is left to
|
||||
* grow indefinitely, it will lose precision and produce less exact
|
||||
* phase over time.
|
||||
*/
|
||||
tmp *= al::numbers::inv_pi_v<float>;
|
||||
int qpd{float2int(tmp)};
|
||||
tmp -= static_cast<float>(qpd + (qpd%2));
|
||||
mSumPhase[k] = tmp * al::numbers::pi_v<float>;
|
||||
|
||||
mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]);
|
||||
}
|
||||
for(size_t k{STFT_HALF_SIZE+1};k < STFT_SIZE;++k)
|
||||
mFftBuffer[k] = std::conj(mFftBuffer[STFT_SIZE-k]);
|
||||
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 siganl, and accumulate
|
||||
/* Apply an inverse FFT to get the time-domain signal, and accumulate
|
||||
* for the output with windowing.
|
||||
*/
|
||||
inverse_fft(mFftBuffer);
|
||||
for(size_t dst{mPos}, k{0u};dst < STFT_SIZE;++dst,++k)
|
||||
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
|
||||
for(size_t dst{0u}, k{STFT_SIZE-mPos};dst < mPos;++dst,++k)
|
||||
mOutputAccum[dst] += HannWindow[k]*mFftBuffer[k].real() * (4.0/OVERSAMP/STFT_SIZE);
|
||||
inverse_fft(al::as_span(mFftBuffer));
|
||||
|
||||
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;
|
||||
for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k)
|
||||
mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale;
|
||||
|
||||
/* Copy out the accumulated result, then clear for the next iteration. */
|
||||
std::copy_n(mOutputAccum.begin() + mPos, STFT_STEP, mFIFO.begin() + mPos);
|
||||
std::fill_n(mOutputAccum.begin() + mPos, STFT_STEP, 0.0);
|
||||
std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos);
|
||||
std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f);
|
||||
}
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -143,12 +143,14 @@ struct FormantFilter
|
|||
|
||||
struct VmorpherState final : public EffectState {
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect parameters */
|
||||
FormantFilter Formants[NUM_FILTERS][NUM_FORMANTS];
|
||||
FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
float CurrentGains[MAX_OUTPUT_CHANNELS]{};
|
||||
float TargetGains[MAX_OUTPUT_CHANNELS]{};
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
|
||||
|
|
@ -161,7 +163,7 @@ struct VmorpherState final : public EffectState {
|
|||
alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{};
|
||||
alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{};
|
||||
|
||||
void deviceUpdate(const DeviceBase *device, const Buffer &buffer) override;
|
||||
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,
|
||||
|
|
@ -225,15 +227,16 @@ std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome p
|
|||
}
|
||||
|
||||
|
||||
void VmorpherState::deviceUpdate(const DeviceBase*, const Buffer&)
|
||||
void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
std::for_each(std::begin(e.Formants[VOWEL_A_INDEX]), std::end(e.Formants[VOWEL_A_INDEX]),
|
||||
e.mTargetChannel = InvalidChannelIndex;
|
||||
std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]),
|
||||
std::mem_fn(&FormantFilter::clear));
|
||||
std::for_each(std::begin(e.Formants[VOWEL_B_INDEX]), std::end(e.Formants[VOWEL_B_INDEX]),
|
||||
std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]),
|
||||
std::mem_fn(&FormantFilter::clear));
|
||||
std::fill(std::begin(e.CurrentGains), std::end(e.CurrentGains), 0.0f);
|
||||
e.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,14 +268,17 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot,
|
|||
/* 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].Formants[VOWEL_A_INDEX]));
|
||||
std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].Formants[VOWEL_B_INDEX]));
|
||||
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]));
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
auto set_gains = [slot,target](auto &chan, al::span<const float,MaxAmbiChannels> coeffs)
|
||||
{ ComputePanGains(target.Main, coeffs.data(), slot->Gain, chan.TargetGains); };
|
||||
SetAmbiPanIdentity(std::begin(mChans), slot->Wet.Buffer.size(), set_gains);
|
||||
auto set_channel = [this](size_t idx, uint outchan, float outgain)
|
||||
{
|
||||
mChans[idx].mTargetChannel = outchan;
|
||||
mChans[idx].mTargetGain = outgain;
|
||||
};
|
||||
target.Main->setAmbiMixParams(slot->Wet, slot->Gain, set_channel);
|
||||
}
|
||||
|
||||
void VmorpherState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
|
|
@ -291,8 +297,15 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
|
|||
auto chandata = std::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
auto& vowelA = chandata->Formants[VOWEL_A_INDEX];
|
||||
auto& vowelB = chandata->Formants[VOWEL_B_INDEX];
|
||||
const size_t outidx{chandata->mTargetChannel};
|
||||
if(outidx == InvalidChannelIndex)
|
||||
{
|
||||
++chandata;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& vowelA = chandata->mFormants[VOWEL_A_INDEX];
|
||||
auto& vowelB = chandata->mFormants[VOWEL_B_INDEX];
|
||||
|
||||
/* Process first vowel. */
|
||||
std::fill_n(std::begin(mSampleBufferA), td, 0.0f);
|
||||
|
|
@ -313,8 +326,8 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
|
|||
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({blended, td}, samplesOut, chandata->CurrentGains, chandata->TargetGains,
|
||||
samplesToDo-base, base);
|
||||
MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
|
||||
chandata->mTargetGain, samplesToDo-base);
|
||||
++chandata;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue