First Commit
This commit is contained in:
235
externals/openal-soft/alc/effects/autowah.cpp
vendored
Normal file
235
externals/openal-soft/alc/effects/autowah.cpp
vendored
Normal file
@ -0,0 +1,235 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float GainScale{31621.0f};
|
||||
constexpr float MinFreq{20.0f};
|
||||
constexpr float MaxFreq{2500.0f};
|
||||
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;
|
||||
|
||||
/* Filter components derived from the envelope. */
|
||||
struct {
|
||||
float cos_w0;
|
||||
float alpha;
|
||||
} mEnv[BufferLineSize];
|
||||
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect filters' history. */
|
||||
struct {
|
||||
float z1, z2;
|
||||
} mFilter;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
float mCurrentGain;
|
||||
float mTargetGain;
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
/* Effects buffers */
|
||||
alignas(16) float mBufferOut[BufferLineSize];
|
||||
|
||||
|
||||
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(AutowahState)
|
||||
};
|
||||
|
||||
void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
|
||||
mAttackRate = 1.0f;
|
||||
mReleaseRate = 1.0f;
|
||||
mResonanceGain = 10.0f;
|
||||
mPeakGain = 4.5f;
|
||||
mFreqMinNorm = 4.5e-4f;
|
||||
mBandwidthNorm = 0.05f;
|
||||
mEnvDelay = 0.0f;
|
||||
|
||||
for(auto &e : mEnv)
|
||||
{
|
||||
e.cos_w0 = 0.0f;
|
||||
e.alpha = 0.0f;
|
||||
}
|
||||
|
||||
for(auto &chan : mChans)
|
||||
{
|
||||
chan.mTargetChannel = InvalidChannelIndex;
|
||||
chan.mFilter.z1 = 0.0f;
|
||||
chan.mFilter.z2 = 0.0f;
|
||||
chan.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void AutowahState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const DeviceBase *device{context->mDevice};
|
||||
const auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)};
|
||||
|
||||
mAttackRate = std::exp(-1.0f / (props->Autowah.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);
|
||||
mFreqMinNorm = MinFreq / frequency;
|
||||
mBandwidthNorm = (MaxFreq-MinFreq) / frequency;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
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,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const float attack_rate{mAttackRate};
|
||||
const float release_rate{mReleaseRate};
|
||||
const float res_gain{mResonanceGain};
|
||||
const float peak_gain{mPeakGain};
|
||||
const float freq_min{mFreqMinNorm};
|
||||
const float bandwidth{mBandwidthNorm};
|
||||
|
||||
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;
|
||||
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);
|
||||
mEnv[i].cos_w0 = std::cos(w0);
|
||||
mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor);
|
||||
}
|
||||
mEnvDelay = env_delay;
|
||||
|
||||
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->mFilter.z1};
|
||||
float z2{chandata->mFilter.z2};
|
||||
|
||||
for(size_t i{0u};i < samplesToDo;i++)
|
||||
{
|
||||
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;
|
||||
|
||||
input = insamples[i];
|
||||
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;
|
||||
}
|
||||
chandata->mFilter.z1 = z1;
|
||||
chandata->mFilter.z2 = z2;
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
|
||||
chandata->mTargetGain, samplesToDo);
|
||||
++chandata;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct AutowahStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new AutowahState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *AutowahStateFactory_getFactory()
|
||||
{
|
||||
static AutowahStateFactory AutowahFactory{};
|
||||
return &AutowahFactory;
|
||||
}
|
||||
26
externals/openal-soft/alc/effects/base.h
vendored
Normal file
26
externals/openal-soft/alc/effects/base.h
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
#ifndef EFFECTS_BASE_H
|
||||
#define EFFECTS_BASE_H
|
||||
|
||||
#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);
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory(void);
|
||||
|
||||
EffectStateFactory *ConvolutionStateFactory_getFactory(void);
|
||||
|
||||
#endif /* EFFECTS_BASE_H */
|
||||
330
externals/openal-soft/alc/effects/chorus.cpp
vendored
Normal file
330
externals/openal-soft/alc/effects/chorus.cpp
vendored
Normal file
@ -0,0 +1,330 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "alc/effects/base.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/context.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.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"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
struct ChorusState final : public EffectState {
|
||||
al::vector<float,16> mDelayBuffer;
|
||||
uint mOffset{0};
|
||||
|
||||
uint mLfoOffset{0};
|
||||
uint mLfoRange{1};
|
||||
float mLfoScale{0.0f};
|
||||
uint mLfoDisp{0};
|
||||
|
||||
/* 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[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
/* effect parameters */
|
||||
ChorusWaveform mWaveform{};
|
||||
int mDelay{0};
|
||||
float mDepth{0.0f};
|
||||
float mFeedback{0.0f};
|
||||
|
||||
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;
|
||||
|
||||
DEF_NEWDEL(ChorusState)
|
||||
};
|
||||
|
||||
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 != 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);
|
||||
}
|
||||
}
|
||||
|
||||
void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits};
|
||||
|
||||
/* 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 auto frequency = static_cast<float>(device->Frequency);
|
||||
|
||||
mWaveform = props->Chorus.Waveform;
|
||||
|
||||
mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay);
|
||||
mDepth = minf(props->Chorus.Depth * static_cast<float>(mDelay),
|
||||
static_cast<float>(mDelay - mindelay));
|
||||
|
||||
mFeedback = props->Chorus.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;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target);
|
||||
ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target);
|
||||
|
||||
float rate{props->Chorus.Rate};
|
||||
if(!(rate > 0.0f))
|
||||
{
|
||||
mLfoOffset = 0;
|
||||
mLfoRange = 1;
|
||||
mLfoScale = 0.0f;
|
||||
mLfoDisp = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* 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}))};
|
||||
|
||||
mLfoOffset = mLfoOffset * lfo_range / mLfoRange;
|
||||
mLfoRange = lfo_range;
|
||||
switch(mWaveform)
|
||||
{
|
||||
case ChorusWaveform::Triangle:
|
||||
mLfoScale = 4.0f / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
case ChorusWaveform::Sinusoid:
|
||||
mLfoScale = al::numbers::pi_v<float>*2.0f / static_cast<float>(mLfoRange);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Calculate lfo phase displacement */
|
||||
int phase{props->Chorus.Phase};
|
||||
if(phase < 0) phase = 360 + phase;
|
||||
mLfoDisp = (mLfoRange*static_cast<uint>(phase) + 180) / 360;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ChorusState::calcTriangleDelays(const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
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};
|
||||
return static_cast<uint>(fastf2i((1.0f-std::abs(2.0f-offset_norm)) * depth) + delay);
|
||||
};
|
||||
|
||||
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;
|
||||
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::calcSinusoidDelays(const size_t todo)
|
||||
{
|
||||
const uint lfo_range{mLfoRange};
|
||||
const float lfo_scale{mLfoScale};
|
||||
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};
|
||||
return static_cast<uint>(fastf2i(std::sin(offset_norm)*depth) + delay);
|
||||
};
|
||||
|
||||
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;
|
||||
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{mDelayBuffer.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)
|
||||
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)
|
||||
{
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
struct ChorusStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ 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()
|
||||
{
|
||||
static ChorusStateFactory ChorusFactory{};
|
||||
return &ChorusFactory;
|
||||
}
|
||||
|
||||
EffectStateFactory *FlangerStateFactory_getFactory()
|
||||
{
|
||||
static FlangerStateFactory FlangerFactory{};
|
||||
return &FlangerFactory;
|
||||
}
|
||||
201
externals/openal-soft/alc/effects/compressor.cpp
vendored
Normal file
201
externals/openal-soft/alc/effects/compressor.cpp
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
/**
|
||||
* This file is part of the OpenAL Soft cross platform audio library
|
||||
*
|
||||
* Copyright (C) 2013 by Anis A. Hireche
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Spherical-Harmonic-Transform nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct ContextBase;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
#define AMP_ENVELOPE_MIN 0.5f
|
||||
#define AMP_ENVELOPE_MAX 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 */
|
||||
|
||||
|
||||
struct CompressorState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
struct {
|
||||
uint mTarget{InvalidChannelIndex};
|
||||
float mGain{1.0f};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
/* Effect parameters */
|
||||
bool mEnabled{true};
|
||||
float mAttackMult{1.0f};
|
||||
float mReleaseMult{1.0f};
|
||||
float mEnvFollower{1.0f};
|
||||
|
||||
|
||||
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(CompressorState)
|
||||
};
|
||||
|
||||
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};
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
void CompressorState::update(const ContextBase*, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
mEnabled = props->Compressor.OnOff;
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
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,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
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 < 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.
|
||||
*/
|
||||
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);
|
||||
|
||||
gains[i] = 1.0f / env;
|
||||
}
|
||||
}
|
||||
mEnvFollower = env;
|
||||
|
||||
/* Now compress the signal amplitude to output. */
|
||||
auto chan = std::cbegin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct CompressorStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new CompressorState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *CompressorStateFactory_getFactory()
|
||||
{
|
||||
static CompressorStateFactory CompressorFactory{};
|
||||
return &CompressorFactory;
|
||||
}
|
||||
636
externals/openal-soft/alc/effects/convolution.cpp
vendored
Normal file
636
externals/openal-soft/alc/effects/convolution.cpp
vendored
Normal file
@ -0,0 +1,636 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
#include <xmmintrin.h>
|
||||
#elif defined(HAVE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include "albyte.h"
|
||||
#include "alcomplex.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "base.h"
|
||||
#include "core/ambidefs.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/buffer_storage.h"
|
||||
#include "core/context.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/effectslot.h"
|
||||
#include "core/filters/splitter.h"
|
||||
#include "core/fmt_traits.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "polyphase_resampler.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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* To apply the reverberation, 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
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
|
||||
switch(srctype)
|
||||
{
|
||||
HANDLE_FMT(FmtUByte);
|
||||
HANDLE_FMT(FmtShort);
|
||||
HANDLE_FMT(FmtFloat);
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
inline 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::N3D: break;
|
||||
}
|
||||
return AmbiScale::FromN3D();
|
||||
}
|
||||
|
||||
inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept
|
||||
{
|
||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa();
|
||||
return AmbiIndex::FromACN();
|
||||
}
|
||||
|
||||
inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept
|
||||
{
|
||||
if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D();
|
||||
return AmbiIndex::FromACN2D();
|
||||
}
|
||||
|
||||
|
||||
struct ChanMap {
|
||||
Channel channel;
|
||||
float angle;
|
||||
float elevation;
|
||||
};
|
||||
|
||||
constexpr float Deg2Rad(float x) noexcept
|
||||
{ return static_cast<float>(al::numbers::pi / 180.0 * x); }
|
||||
|
||||
|
||||
using complex_f = std::complex<float>;
|
||||
|
||||
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)
|
||||
{
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
for(float &output : dst)
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < ConvolveUpdateSamples;j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&filter[j])};
|
||||
const __m128 s{_mm_loadu_ps(&src[j])};
|
||||
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < ConvolveUpdateSamples;++j)
|
||||
ret += src[j] * filter[j];
|
||||
output = ret;
|
||||
++src;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct ConvolutionState final : public EffectState {
|
||||
FmtChannels mChannels{};
|
||||
AmbiLayout mAmbiLayout{};
|
||||
AmbiScaling mAmbiScaling{};
|
||||
uint mAmbiOrder{};
|
||||
|
||||
size_t mFifoPos{0};
|
||||
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{};
|
||||
|
||||
size_t mCurrentSegment{0};
|
||||
size_t mNumConvolveSegs{0};
|
||||
|
||||
struct ChannelData {
|
||||
alignas(16) FloatBufferLine mBuffer{};
|
||||
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_f[]> mComplexData;
|
||||
|
||||
|
||||
ConvolutionState() = default;
|
||||
~ConvolutionState() override = default;
|
||||
|
||||
void NormalMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
|
||||
void UpsampleMix(const al::span<FloatBufferLine> samplesOut, const size_t samplesToDo);
|
||||
void (ConvolutionState::*mMix)(const al::span<FloatBufferLine>,const size_t)
|
||||
{&ConvolutionState::NormalMix};
|
||||
|
||||
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(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);
|
||||
}
|
||||
|
||||
void ConvolutionState::UpsampleMix(const al::span<FloatBufferLine> samplesOut,
|
||||
const size_t samplesToDo)
|
||||
{
|
||||
for(auto &chan : *mChans)
|
||||
{
|
||||
const al::span<float> src{chan.mBuffer.data(), samplesToDo};
|
||||
chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale);
|
||||
MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_f{});
|
||||
|
||||
mCurrentSegment = 0;
|
||||
mNumConvolveSegs = 0;
|
||||
|
||||
mChans = nullptr;
|
||||
mComplexData = nullptr;
|
||||
|
||||
/* An empty buffer doesn't need a convolution filter. */
|
||||
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};
|
||||
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
|
||||
* 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->mSampleRate)
|
||||
resampler.init(buffer->mSampleRate, device->Frequency);
|
||||
const auto resampledCount = static_cast<uint>(
|
||||
(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)
|
||||
e.mFilter = splitter;
|
||||
|
||||
mFilter.resize(numChannels, {});
|
||||
mOutput.resize(numChannels, {});
|
||||
|
||||
/* Calculate the number of segments needed to hold the impulse response and
|
||||
* the input history (rounded up), and allocate them. Exclude one segment
|
||||
* which gets applied as a time-domain FIR filter. Make sure at least one
|
||||
* segment is allocated to simplify handling.
|
||||
*/
|
||||
mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples;
|
||||
mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1;
|
||||
|
||||
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{});
|
||||
|
||||
/* 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);
|
||||
|
||||
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)
|
||||
{
|
||||
/* 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(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(&ressamples[done], todo, fftbuffer.begin());
|
||||
done += todo;
|
||||
std::fill(iter, fftbuffer.end(), std::complex<double>{});
|
||||
|
||||
forward_fft(al::as_span(fftbuffer));
|
||||
filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
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
|
||||
* 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) }
|
||||
};
|
||||
|
||||
if(mNumConvolveSegs < 1) UNLIKELY
|
||||
return;
|
||||
|
||||
mMix = &ConvolutionState::NormalMix;
|
||||
|
||||
for(auto &chan : *mChans)
|
||||
std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f);
|
||||
const float gain{slot->Gain};
|
||||
if(IsAmbisonic(mChannels))
|
||||
{
|
||||
DeviceBase *device{context->mDevice};
|
||||
if(mChannels == FmtUHJ2 && !device->mUhjEncoder)
|
||||
{
|
||||
mMix = &ConvolutionState::UpsampleMix;
|
||||
(*mChans)[0].mHfScale = 1.0f;
|
||||
(*mChans)[0].mLfScale = DecoderBase::sWLFScale;
|
||||
(*mChans)[1].mHfScale = 1.0f;
|
||||
(*mChans)[1].mLfScale = DecoderBase::sXYLFScale;
|
||||
(*mChans)[2].mHfScale = 1.0f;
|
||||
(*mChans)[2].mLfScale = DecoderBase::sXYLFScale;
|
||||
}
|
||||
else if(device->mAmbiOrder > mAmbiOrder)
|
||||
{
|
||||
mMix = &ConvolutionState::UpsampleMix;
|
||||
const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder,
|
||||
device->m2DMixing);
|
||||
(*mChans)[0].mHfScale = scales[0];
|
||||
(*mChans)[0].mLfScale = 1.0f;
|
||||
for(size_t i{1};i < mChans->size();++i)
|
||||
{
|
||||
(*mChans)[i].mHfScale = scales[1];
|
||||
(*mChans)[i].mLfScale = 1.0f;
|
||||
}
|
||||
}
|
||||
mOutTarget = target.Main->Buffer;
|
||||
|
||||
auto&& scales = GetAmbiScales(mAmbiScaling);
|
||||
const uint8_t *index_map{Is2DAmbisonic(mChannels) ?
|
||||
GetAmbi2DLayout(mAmbiLayout).data() :
|
||||
GetAmbiLayout(mAmbiLayout).data()};
|
||||
|
||||
std::array<float,MaxAmbiChannels> coeffs{};
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DeviceBase *device{context->mDevice};
|
||||
al::span<const ChanMap> chanmap{};
|
||||
switch(mChannels)
|
||||
{
|
||||
case FmtMono: chanmap = MonoMap; break;
|
||||
case FmtSuperStereo:
|
||||
case FmtStereo: chanmap = StereoMap; break;
|
||||
case FmtRear: chanmap = RearMap; break;
|
||||
case FmtQuad: chanmap = QuadMap; break;
|
||||
case FmtX51: chanmap = X51Map; break;
|
||||
case FmtX61: chanmap = X61Map; break;
|
||||
case FmtX71: chanmap = X71Map; break;
|
||||
case FmtBFormat2D:
|
||||
case FmtBFormat3D:
|
||||
case FmtUHJ2:
|
||||
case FmtUHJ3:
|
||||
case FmtUHJ4:
|
||||
break;
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
if(device->mRenderMode == RenderMode::Pairwise)
|
||||
{
|
||||
auto ScaleAzimuthFront = [](float azimuth, float scale) -> float
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConvolutionState::process(const size_t samplesToDo,
|
||||
const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
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)};
|
||||
|
||||
std::copy_n(samplesIn[0].begin() + base, todo,
|
||||
mInput.begin()+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)
|
||||
{
|
||||
auto buf_iter = chans[c].mBuffer.begin() + base;
|
||||
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<>{});
|
||||
}
|
||||
|
||||
mFifoPos += todo;
|
||||
base += todo;
|
||||
|
||||
/* Check whether the input buffer is filled with new samples. */
|
||||
if(mFifoPos < ConvolveUpdateSamples) break;
|
||||
mFifoPos = 0;
|
||||
|
||||
/* Move the newest input to the front for the next iteration's history. */
|
||||
std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin());
|
||||
|
||||
/* 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));
|
||||
|
||||
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)
|
||||
{
|
||||
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]};
|
||||
for(size_t s{curseg};s < mNumConvolveSegs;++s)
|
||||
{
|
||||
for(size_t i{0};i < m;++i,++input,++filter)
|
||||
mFftBuffer[i] += *input * *filter;
|
||||
}
|
||||
input = mComplexData.get();
|
||||
for(size_t s{0};s < curseg;++s)
|
||||
{
|
||||
for(size_t i{0};i < m;++i,++input,++filter)
|
||||
mFftBuffer[i] += *input * *filter;
|
||||
}
|
||||
|
||||
/* 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));
|
||||
|
||||
/* 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();
|
||||
}
|
||||
|
||||
/* Shift the input history. */
|
||||
curseg = curseg ? (curseg-1) : (mNumConvolveSegs-1);
|
||||
}
|
||||
mCurrentSegment = curseg;
|
||||
|
||||
/* Finally, mix to the output. */
|
||||
(this->*mMix)(samplesOut, samplesToDo);
|
||||
}
|
||||
|
||||
|
||||
struct ConvolutionStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new ConvolutionState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ConvolutionStateFactory_getFactory()
|
||||
{
|
||||
static ConvolutionStateFactory ConvolutionFactory{};
|
||||
return &ConvolutionFactory;
|
||||
}
|
||||
123
externals/openal-soft/alc/effects/dedicated.cpp
vendored
Normal file
123
externals/openal-soft/alc/effects/dedicated.cpp
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2011 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct ContextBase;
|
||||
|
||||
|
||||
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 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(DedicatedState)
|
||||
};
|
||||
|
||||
void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f);
|
||||
}
|
||||
|
||||
void DedicatedState::update(const ContextBase*, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f);
|
||||
|
||||
const float Gain{slot->Gain * props->Dedicated.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)
|
||||
{
|
||||
/* 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]
|
||||
: InvalidChannelIndex};
|
||||
if(idx != InvalidChannelIndex)
|
||||
{
|
||||
mOutTarget = target.RealOut->Buffer;
|
||||
mTargetGains[idx] = Gain;
|
||||
}
|
||||
else
|
||||
{
|
||||
static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f});
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
struct DedicatedStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new DedicatedState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DedicatedStateFactory_getFactory()
|
||||
{
|
||||
static DedicatedStateFactory DedicatedFactory{};
|
||||
return &DedicatedFactory;
|
||||
}
|
||||
178
externals/openal-soft/alc/effects/distortion.cpp
vendored
Normal file
178
externals/openal-soft/alc/effects/distortion.cpp
vendored
Normal file
@ -0,0 +1,178 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "alc/effects/base.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/context.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/effectslot.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "core/mixer.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct DistortionState final : public EffectState {
|
||||
/* Effect gains for each channel */
|
||||
float mGain[MaxAmbiChannels]{};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter mLowpass;
|
||||
BiquadFilter mBandpass;
|
||||
float mAttenuation{};
|
||||
float mEdgeCoeff{};
|
||||
|
||||
alignas(16) float mBuffer[2][BufferLineSize]{};
|
||||
|
||||
|
||||
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(DistortionState)
|
||||
};
|
||||
|
||||
void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
mLowpass.clear();
|
||||
mBandpass.clear();
|
||||
}
|
||||
|
||||
void DistortionState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
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)};
|
||||
mEdgeCoeff = 2.0f * edge / (1.0f-edge);
|
||||
|
||||
float cutoff{props->Distortion.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
|
||||
* processing.
|
||||
*/
|
||||
auto frequency = static_cast<float>(device->Frequency);
|
||||
mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth);
|
||||
|
||||
cutoff = props->Distortion.EQCenter;
|
||||
/* Convert bandwidth in Hz to octaves. */
|
||||
bandwidth = props->Distortion.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});
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain);
|
||||
}
|
||||
|
||||
void DistortionState::process(const size_t samplesToDo, const al::span<const FloatBufferLine> samplesIn, const al::span<FloatBufferLine> samplesOut)
|
||||
{
|
||||
const float fc{mEdgeCoeff};
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
/* Perform 4x oversampling to avoid aliasing. Oversampling greatly
|
||||
* improves distortion quality and allows to implement lowpass and
|
||||
* bandpass filters using high frequencies, at which classic IIR
|
||||
* filters became unstable.
|
||||
*/
|
||||
size_t todo{minz(BufferLineSize, (samplesToDo-base) * 4)};
|
||||
|
||||
/* Fill oversample buffer using zero stuffing. Multiply the sample by
|
||||
* the amount of oversampling to maintain the signal's power.
|
||||
*/
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
mBuffer[0][i] = !(i&3) ? samplesIn[0][(i>>2)+base] * 4.0f : 0.0f;
|
||||
|
||||
/* First step, do lowpass filtering of original signal. Additionally
|
||||
* perform buffer interpolation and lowpass cutoff for oversampling
|
||||
* (which is fortunately first step of distortion). So combine three
|
||||
* operations into the one.
|
||||
*/
|
||||
mLowpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
/* Second step, do distortion using waveshaper function to emulate
|
||||
* signal processing during tube overdriving. Three steps of
|
||||
* waveshaping are intended to modify waveform without boost/clipping/
|
||||
* attenuation process.
|
||||
*/
|
||||
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));
|
||||
return smp;
|
||||
};
|
||||
std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]),
|
||||
proc_sample);
|
||||
|
||||
/* Third step, do bandpass filtering of distorted signal. */
|
||||
mBandpass.process({mBuffer[0], todo}, mBuffer[1]);
|
||||
|
||||
todo >>= 2;
|
||||
const float *outgains{mGain};
|
||||
for(FloatBufferLine &output : samplesOut)
|
||||
{
|
||||
/* 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;
|
||||
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
output[base+i] += gain * mBuffer[1][i*4];
|
||||
}
|
||||
|
||||
base += todo;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct DistortionStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new DistortionState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *DistortionStateFactory_getFactory()
|
||||
{
|
||||
static DistortionStateFactory DistortionFactory{};
|
||||
return &DistortionFactory;
|
||||
}
|
||||
180
externals/openal-soft/alc/effects/echo.cpp
vendored
Normal file
180
externals/openal-soft/alc/effects/echo.cpp
vendored
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <tuple>
|
||||
|
||||
#include "alc/effects/base.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/context.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/effectslot.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
#include "opthelpers.h"
|
||||
#include "vector.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
constexpr float LowpassFreqRef{5000.0f};
|
||||
|
||||
struct EchoState final : public EffectState {
|
||||
al::vector<float,16> 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];
|
||||
size_t mOffset{0u};
|
||||
|
||||
/* The panning gains for the two taps */
|
||||
struct {
|
||||
float Current[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
BiquadFilter mFilter;
|
||||
float mFeedGain{0.0f};
|
||||
|
||||
alignas(16) float mTempBuffer[2][BufferLineSize];
|
||||
|
||||
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*)
|
||||
{
|
||||
const auto frequency = static_cast<float>(Device->Frequency);
|
||||
|
||||
// Use the next power of 2 for the buffer length, so the tap offsets can be
|
||||
// wrapped using a mask instead of a modulo
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void EchoState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
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;
|
||||
|
||||
const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */
|
||||
mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f);
|
||||
|
||||
mFeedGain = props->Echo.Feedback;
|
||||
|
||||
/* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */
|
||||
const float angle{std::asin(props->Echo.Spread)};
|
||||
|
||||
const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f);
|
||||
const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 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);
|
||||
}
|
||||
|
||||
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()};
|
||||
size_t offset{mOffset};
|
||||
size_t tap1{offset - mTap[0].delay};
|
||||
size_t tap2{offset - mTap[1].delay};
|
||||
float z1, z2;
|
||||
|
||||
ASSUME(samplesToDo > 0);
|
||||
|
||||
const BiquadFilter filter{mFilter};
|
||||
std::tie(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)};
|
||||
do {
|
||||
/* Feed the delay buffer's input first. */
|
||||
delaybuf[offset] = samplesIn[0][i];
|
||||
|
||||
/* Get delayed output from the first and second taps. Use the
|
||||
* second tap for feedback.
|
||||
*/
|
||||
mTempBuffer[0][i] = delaybuf[tap1++];
|
||||
mTempBuffer[1][i] = delaybuf[tap2++];
|
||||
const float feedb{mTempBuffer[1][i++]};
|
||||
|
||||
/* Add feedback to the delay buffer with damping and attenuation. */
|
||||
delaybuf[offset++] += filter.processOne(feedb, z1, z2) * mFeedGain;
|
||||
} while(--td);
|
||||
}
|
||||
mFilter.setComponents(z1, z2);
|
||||
mOffset = offset;
|
||||
|
||||
for(size_t c{0};c < 2;c++)
|
||||
MixSamples({mTempBuffer[c], samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target,
|
||||
samplesToDo, 0);
|
||||
}
|
||||
|
||||
|
||||
struct EchoStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new EchoState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *EchoStateFactory_getFactory()
|
||||
{
|
||||
static EchoStateFactory EchoFactory{};
|
||||
return &EchoFactory;
|
||||
}
|
||||
204
externals/openal-soft/alc/effects/equalizer.cpp
vendored
Normal file
204
externals/openal-soft/alc/effects/equalizer.cpp
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2013 by Mike Gorchak
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
/* The document "Effects Extension Guide.pdf" says that low and high *
|
||||
* frequencies are cutoff frequencies. This is not fully correct, they *
|
||||
* are corner frequencies for low and high shelf filters. If they were *
|
||||
* just cutoff frequencies, there would be no need in cutoff frequency *
|
||||
* gains, which are present. Documentation for "Creative Proteus X2" *
|
||||
* software describes 4-band equalizer functionality in a much better *
|
||||
* way. This equalizer seems to be a predecessor of OpenAL 4-band *
|
||||
* equalizer. With low and high shelf filters we are able to cutoff *
|
||||
* frequencies below and/or above corner frequencies using attenuation *
|
||||
* gains (below 1.0) and amplify all low and/or high frequencies using *
|
||||
* gains above 1.0. *
|
||||
* *
|
||||
* Low-shelf Low Mid Band High Mid Band High-shelf *
|
||||
* corner center center corner *
|
||||
* frequency frequency frequency frequency *
|
||||
* 50Hz..800Hz 200Hz..3000Hz 1000Hz..8000Hz 4000Hz..16000Hz *
|
||||
* *
|
||||
* | | | | *
|
||||
* | | | | *
|
||||
* B -----+ /--+--\ /--+--\ +----- *
|
||||
* O |\ | | | | | | /| *
|
||||
* O | \ - | - - | - / | *
|
||||
* S + | \ | | | | | | / | *
|
||||
* T | | | | | | | | | | *
|
||||
* ---------+---------------+------------------+---------------+-------- *
|
||||
* C | | | | | | | | | | *
|
||||
* U - | / | | | | | | \ | *
|
||||
* T | / - | - - | - \ | *
|
||||
* O |/ | | | | | | \| *
|
||||
* F -----+ \--+--/ \--+--/ +----- *
|
||||
* F | | | | *
|
||||
* | | | | *
|
||||
* *
|
||||
* Gains vary from 0.126 up to 7.943, which means from -18dB attenuation *
|
||||
* up to +18dB amplification. Band width varies from 0.01 up to 1.0 in *
|
||||
* octaves for two mid bands. *
|
||||
* *
|
||||
* Implementation is based on the "Cookbook formulae for audio EQ biquad *
|
||||
* filter coefficients" by Robert Bristow-Johnson *
|
||||
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt */
|
||||
|
||||
|
||||
struct EqualizerState final : public EffectState {
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect parameters */
|
||||
BiquadFilter mFilter[4];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
alignas(16) FloatBufferLine mSampleBuffer{};
|
||||
|
||||
|
||||
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(EqualizerState)
|
||||
};
|
||||
|
||||
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));
|
||||
e.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void EqualizerState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
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,
|
||||
* while the effect property gains are for the shelf/peak itself. So the
|
||||
* 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;
|
||||
mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.Mid1Gain);
|
||||
f0norm = props->Equalizer.Mid1Center / frequency;
|
||||
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].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain,
|
||||
props->Equalizer.Mid2Width);
|
||||
|
||||
gain = std::sqrt(props->Equalizer.HighGain);
|
||||
f0norm = props->Equalizer.HighCutoff / frequency;
|
||||
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].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_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::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
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[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
|
||||
samplesToDo);
|
||||
}
|
||||
++chan;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct EqualizerStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new EqualizerState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *EqualizerStateFactory_getFactory()
|
||||
{
|
||||
static EqualizerStateFactory EqualizerFactory{};
|
||||
return &EqualizerFactory;
|
||||
}
|
||||
255
externals/openal-soft/alc/effects/fshifter.cpp
vendored
Normal file
255
externals/openal-soft/alc/effects/fshifter.cpp
vendored
Normal file
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "alc/effects/base.h"
|
||||
#include "alcomplex.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/context.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
constexpr size_t HilSize{1024};
|
||||
constexpr size_t HilHalfSize{HilSize >> 1};
|
||||
constexpr size_t OversampleFactor{4};
|
||||
|
||||
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. */
|
||||
struct Windower {
|
||||
alignas(16) std::array<double,HilSize> mData;
|
||||
|
||||
Windower()
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
const Windower gWindow{};
|
||||
|
||||
|
||||
struct FshifterState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
size_t mCount{};
|
||||
size_t mPos{};
|
||||
std::array<uint,2> mPhaseStep{};
|
||||
std::array<uint,2> mPhase{};
|
||||
std::array<double,2> mSign{};
|
||||
|
||||
/* Effects buffers */
|
||||
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) FloatBufferLine mBufferOut{};
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
struct {
|
||||
float Current[MaxAmbiChannels]{};
|
||||
float Target[MaxAmbiChannels]{};
|
||||
} mGains[2];
|
||||
|
||||
|
||||
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(FshifterState)
|
||||
};
|
||||
|
||||
void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = 0;
|
||||
mPos = HilSize - HilStep;
|
||||
|
||||
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)
|
||||
{
|
||||
std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f);
|
||||
std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void FshifterState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
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);
|
||||
|
||||
switch(props->Fshifter.LeftDirection)
|
||||
{
|
||||
case FShifterDirection::Down:
|
||||
mSign[0] = -1.0;
|
||||
break;
|
||||
case FShifterDirection::Up:
|
||||
mSign[0] = 1.0;
|
||||
break;
|
||||
case FShifterDirection::Off:
|
||||
mPhase[0] = 0;
|
||||
mPhaseStep[0] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(props->Fshifter.RightDirection)
|
||||
{
|
||||
case FShifterDirection::Down:
|
||||
mSign[1] = -1.0;
|
||||
break;
|
||||
case FShifterDirection::Up:
|
||||
mSign[1] = 1.0;
|
||||
break;
|
||||
case FShifterDirection::Off:
|
||||
mPhase[1] = 0;
|
||||
mPhaseStep[1] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
ComputePanGains(target.Main, rcoeffs.data(), 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)};
|
||||
|
||||
/* Fill FIFO buffer with samples data */
|
||||
const size_t pos{mPos};
|
||||
size_t count{mCount};
|
||||
do {
|
||||
mInFIFO[pos+count] = samplesIn[0][base];
|
||||
mOutdata[base] = mOutFIFO[count];
|
||||
++base; ++count;
|
||||
} while(--todo);
|
||||
mCount = count;
|
||||
|
||||
/* Check whether FIFO buffer is filled */
|
||||
if(mCount < HilStep) break;
|
||||
mCount = 0;
|
||||
mPos = (mPos+HilStep) & (HilSize-1);
|
||||
|
||||
/* Real signal windowing and store in Analytic buffer */
|
||||
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 < 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.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{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]};
|
||||
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]);
|
||||
|
||||
phase_idx += phase_step;
|
||||
phase_idx &= MixerFracMask;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct FshifterStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new FshifterState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *FshifterStateFactory_getFactory()
|
||||
{
|
||||
static FshifterStateFactory FshifterFactory{};
|
||||
return &FshifterFactory;
|
||||
}
|
||||
193
externals/openal-soft/alc/effects/modulator.cpp
vendored
Normal file
193
externals/openal-soft/alc/effects/modulator.cpp
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2009 by Chris Robinson.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/filters/biquad.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 128
|
||||
|
||||
#define WAVEFORM_FRACBITS 24
|
||||
#define WAVEFORM_FRACONE (1<<WAVEFORM_FRACBITS)
|
||||
#define WAVEFORM_FRACMASK (WAVEFORM_FRACONE-1)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 ModulatorState final : public EffectState {
|
||||
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
|
||||
|
||||
uint mIndex{0};
|
||||
uint mStep{1};
|
||||
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
BiquadFilter mFilter;
|
||||
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
|
||||
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(ModulatorState)
|
||||
};
|
||||
|
||||
void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
for(auto &e : mChans)
|
||||
{
|
||||
e.mTargetChannel = InvalidChannelIndex;
|
||||
e.mFilter.clear();
|
||||
e.mCurrentGain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void ModulatorState::update(const ContextBase *context, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
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}));
|
||||
|
||||
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>;
|
||||
|
||||
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].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f);
|
||||
for(size_t i{1u};i < slot->Wet.Buffer.size();++i)
|
||||
mChans[i].mFilter.copyParamsFrom(mChans[0].mFilter);
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
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)
|
||||
{
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
alignas(16) float modsamples[MAX_UPDATE_SAMPLES];
|
||||
const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)};
|
||||
|
||||
mGetSamples(modsamples, mIndex, mStep, td);
|
||||
mIndex += static_cast<uint>(mStep * td);
|
||||
mIndex &= WAVEFORM_FRACMASK;
|
||||
|
||||
auto chandata = std::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct ModulatorStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new ModulatorState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *ModulatorStateFactory_getFactory()
|
||||
{
|
||||
static ModulatorStateFactory ModulatorFactory{};
|
||||
return &ModulatorFactory;
|
||||
}
|
||||
84
externals/openal-soft/alc/effects/null.cpp
vendored
Normal file
84
externals/openal-soft/alc/effects/null.cpp
vendored
Normal file
@ -0,0 +1,84 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
#include "base.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct ContextBase;
|
||||
struct DeviceBase;
|
||||
struct EffectSlot;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
struct NullState final : public EffectState {
|
||||
NullState();
|
||||
~NullState() 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,
|
||||
const al::span<FloatBufferLine> samplesOut) override;
|
||||
|
||||
DEF_NEWDEL(NullState)
|
||||
};
|
||||
|
||||
/* This constructs the effect state. It's called when the object is first
|
||||
* created.
|
||||
*/
|
||||
NullState::NullState() = default;
|
||||
|
||||
/* This destructs the effect state. It's called only when the effect instance
|
||||
* is no longer used.
|
||||
*/
|
||||
NullState::~NullState() = default;
|
||||
|
||||
/* This updates the device-dependant effect state. This is called on state
|
||||
* initialization and any time the device parameters (e.g. playback frequency,
|
||||
* format) have been changed. Will always be followed by a call to the update
|
||||
* method, if successful.
|
||||
*/
|
||||
void NullState::deviceUpdate(const DeviceBase* /*device*/, const BufferStorage* /*buffer*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* This updates the effect state with new properties. This is called any time
|
||||
* the effect is (re)loaded into a slot.
|
||||
*/
|
||||
void NullState::update(const ContextBase* /*context*/, const EffectSlot* /*slot*/,
|
||||
const EffectProps* /*props*/, const EffectTarget /*target*/)
|
||||
{
|
||||
}
|
||||
|
||||
/* This processes the effect state, for the given number of samples from the
|
||||
* input to the output buffer. The result should be added to the output buffer,
|
||||
* not replace it.
|
||||
*/
|
||||
void NullState::process(const size_t/*samplesToDo*/,
|
||||
const al::span<const FloatBufferLine> /*samplesIn*/,
|
||||
const al::span<FloatBufferLine> /*samplesOut*/)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
struct NullStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override;
|
||||
};
|
||||
|
||||
/* Creates EffectState objects of the appropriate type. */
|
||||
al::intrusive_ptr<EffectState> NullStateFactory::create()
|
||||
{ return al::intrusive_ptr<EffectState>{new NullState{}}; }
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *NullStateFactory_getFactory()
|
||||
{
|
||||
static NullStateFactory NullFactory{};
|
||||
return &NullFactory;
|
||||
}
|
||||
307
externals/openal-soft/alc/effects/pshifter.cpp
vendored
Normal file
307
externals/openal-soft/alc/effects/pshifter.cpp
vendored
Normal file
@ -0,0 +1,307 @@
|
||||
/**
|
||||
* OpenAL cross platform audio library
|
||||
* Copyright (C) 2018 by Raul Herraiz.
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* Or go to http://www.gnu.org/copyleft/lgpl.html
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
|
||||
#include "alc/effects/base.h"
|
||||
#include "alcomplex.h"
|
||||
#include "almalloc.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alnumeric.h"
|
||||
#include "alspan.h"
|
||||
#include "core/bufferline.h"
|
||||
#include "core/devformat.h"
|
||||
#include "core/device.h"
|
||||
#include "core/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "core/mixer/defs.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
struct ContextBase;
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
using complex_f = std::complex<float>;
|
||||
|
||||
constexpr size_t StftSize{1024};
|
||||
constexpr size_t StftHalfSize{StftSize >> 1};
|
||||
constexpr size_t OversampleFactor{8};
|
||||
|
||||
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. */
|
||||
struct Windower {
|
||||
alignas(16) std::array<float,StftSize> mData;
|
||||
|
||||
Windower()
|
||||
{
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
const Windower gWindow{};
|
||||
|
||||
|
||||
struct FrequencyBin {
|
||||
float Magnitude;
|
||||
float FreqBin;
|
||||
};
|
||||
|
||||
|
||||
struct PshifterState final : public EffectState {
|
||||
/* Effect parameters */
|
||||
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<complex_f,StftSize> mFftBuffer;
|
||||
|
||||
std::array<FrequencyBin,StftHalfSize+1> mAnalysisBuffer;
|
||||
std::array<FrequencyBin,StftHalfSize+1> mSynthesisBuffer;
|
||||
|
||||
alignas(16) FloatBufferLine mBufferOut;
|
||||
|
||||
/* Effect gains for each output channel */
|
||||
float mCurrentGains[MaxAmbiChannels];
|
||||
float mTargetGains[MaxAmbiChannels];
|
||||
|
||||
|
||||
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(PshifterState)
|
||||
};
|
||||
|
||||
void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*)
|
||||
{
|
||||
/* (Re-)initializing parameters and clear the buffers. */
|
||||
mCount = 0;
|
||||
mPos = StftSize - StftStep;
|
||||
mPitchShiftI = MixerFracOne;
|
||||
mPitchShift = 1.0f;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void PshifterState::update(const ContextBase*, const EffectSlot *slot,
|
||||
const EffectProps *props, const EffectTarget target)
|
||||
{
|
||||
const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune};
|
||||
const float pitch{std::pow(2.0f, static_cast<float>(tune) / 1200.0f)};
|
||||
mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2);
|
||||
mPitchShift = static_cast<float>(mPitchShiftI) * float{1.0f/MixerFracOne};
|
||||
|
||||
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)
|
||||
{
|
||||
/* Pitch shifter engine based on the work of Stephan Bernsee.
|
||||
* http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/
|
||||
*/
|
||||
|
||||
/* Cycle offset per update expected of each frequency bin (bin 0 is none,
|
||||
* bin 1 is x1, bin 2 is x2, etc).
|
||||
*/
|
||||
constexpr float expected_cycles{al::numbers::pi_v<float>*2.0f / OversampleFactor};
|
||||
|
||||
for(size_t base{0u};base < samplesToDo;)
|
||||
{
|
||||
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::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 < StftStep) break;
|
||||
mCount = 0;
|
||||
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 < 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
|
||||
* StftHalfSize+1 samples are needed.
|
||||
*/
|
||||
for(size_t k{0u};k < StftHalfSize+1;k++)
|
||||
{
|
||||
const float magnitude{std::abs(mFftBuffer[k])};
|
||||
const float phase{std::arg(mFftBuffer[k])};
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
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 magnitudes of overlapping frequency bins.
|
||||
*/
|
||||
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)};
|
||||
for(size_t k{0u};k < bin_count;k++)
|
||||
{
|
||||
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 < StftHalfSize+1;k++)
|
||||
{
|
||||
/* 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};
|
||||
|
||||
/* 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{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));
|
||||
|
||||
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, StftStep, mFIFO.begin() + mPos);
|
||||
std::fill_n(mOutputAccum.begin() + mPos, StftStep, 0.0f);
|
||||
}
|
||||
|
||||
/* Now, mix the processed sound data to the output. */
|
||||
MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains,
|
||||
maxz(samplesToDo, 512), 0);
|
||||
}
|
||||
|
||||
|
||||
struct PshifterStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new PshifterState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *PshifterStateFactory_getFactory()
|
||||
{
|
||||
static PshifterStateFactory PshifterFactory{};
|
||||
return &PshifterFactory;
|
||||
}
|
||||
1770
externals/openal-soft/alc/effects/reverb.cpp
vendored
Normal file
1770
externals/openal-soft/alc/effects/reverb.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
350
externals/openal-soft/alc/effects/vmorpher.cpp
vendored
Normal file
350
externals/openal-soft/alc/effects/vmorpher.cpp
vendored
Normal file
@ -0,0 +1,350 @@
|
||||
/**
|
||||
* This file is part of the OpenAL Soft cross platform audio library
|
||||
*
|
||||
* Copyright (C) 2019 by Anis A. Hireche
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of Spherical-Harmonic-Transform nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
||||
#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/effectslot.h"
|
||||
#include "core/mixer.h"
|
||||
#include "intrusive_ptr.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
#define MAX_UPDATE_SAMPLES 256
|
||||
#define NUM_FORMANTS 4
|
||||
#define NUM_FILTERS 2
|
||||
#define Q_FACTOR 5.0f
|
||||
|
||||
#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)
|
||||
|
||||
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)*0.5f + 0.5f;
|
||||
}
|
||||
|
||||
inline float Saw(uint index)
|
||||
{ return static_cast<float>(index) / float{WAVEFORM_FRACONE}; }
|
||||
|
||||
inline float Triangle(uint index)
|
||||
{ return std::fabs(static_cast<float>(index)*(2.0f/WAVEFORM_FRACONE) - 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)
|
||||
{
|
||||
for(size_t i{0u};i < todo;i++)
|
||||
{
|
||||
index += step;
|
||||
index &= WAVEFORM_FRACMASK;
|
||||
dst[i] = func(index);
|
||||
}
|
||||
}
|
||||
|
||||
struct FormantFilter
|
||||
{
|
||||
float mCoeff{0.0f};
|
||||
float mGain{1.0f};
|
||||
float mS1{0.0f};
|
||||
float mS2{0.0f};
|
||||
|
||||
FormantFilter() = default;
|
||||
FormantFilter(float f0norm, float gain)
|
||||
: mCoeff{std::tan(al::numbers::pi_v<float> * f0norm)}, mGain{gain}
|
||||
{ }
|
||||
|
||||
inline void process(const float *samplesIn, float *samplesOut, const size_t numInput)
|
||||
{
|
||||
/* 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))};
|
||||
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};
|
||||
|
||||
s1 = g*H + B;
|
||||
s2 = g*B + L;
|
||||
|
||||
// Apply peak and accumulate samples.
|
||||
samplesOut[i] += B * gain;
|
||||
}
|
||||
mS1 = s1;
|
||||
mS2 = s2;
|
||||
}
|
||||
|
||||
inline void clear()
|
||||
{
|
||||
mS1 = 0.0f;
|
||||
mS2 = 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct VmorpherState final : public EffectState {
|
||||
struct {
|
||||
uint mTargetChannel{InvalidChannelIndex};
|
||||
|
||||
/* Effect parameters */
|
||||
FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS];
|
||||
|
||||
/* Effect gains for each channel */
|
||||
float mCurrentGain{};
|
||||
float mTargetGain{};
|
||||
} mChans[MaxAmbiChannels];
|
||||
|
||||
void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){};
|
||||
|
||||
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]{};
|
||||
|
||||
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;
|
||||
|
||||
static std::array<FormantFilter,4> getFiltersByPhoneme(VMorpherPhenome phoneme,
|
||||
float frequency, float pitch);
|
||||
|
||||
DEF_NEWDEL(VmorpherState)
|
||||
};
|
||||
|
||||
std::array<FormantFilter,4> VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme,
|
||||
float frequency, float pitch)
|
||||
{
|
||||
/* Using soprano formant set of values to
|
||||
* better match mid-range frequency space.
|
||||
*
|
||||
* See: https://www.classes.cs.uchicago.edu/archive/1999/spring/CS295/Computing_Resources/Csound/CsManual3.48b1.HTML/Appendices/table3.html
|
||||
*/
|
||||
switch(phoneme)
|
||||
{
|
||||
case VMorpherPhenome::A:
|
||||
return {{
|
||||
{( 800 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(1150 * pitch) / frequency, 0.501187f}, /* std::pow(10.0f, -6 / 20.0f); */
|
||||
{(2900 * pitch) / frequency, 0.025118f}, /* std::pow(10.0f, -32 / 20.0f); */
|
||||
{(3900 * pitch) / frequency, 0.100000f} /* std::pow(10.0f, -20 / 20.0f); */
|
||||
}};
|
||||
case VMorpherPhenome::E:
|
||||
return {{
|
||||
{( 350 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(2000 * pitch) / frequency, 0.100000f}, /* std::pow(10.0f, -20 / 20.0f); */
|
||||
{(2800 * pitch) / frequency, 0.177827f}, /* std::pow(10.0f, -15 / 20.0f); */
|
||||
{(3600 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
|
||||
}};
|
||||
case VMorpherPhenome::I:
|
||||
return {{
|
||||
{( 270 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{(2140 * pitch) / frequency, 0.251188f}, /* std::pow(10.0f, -12 / 20.0f); */
|
||||
{(2950 * pitch) / frequency, 0.050118f}, /* std::pow(10.0f, -26 / 20.0f); */
|
||||
{(3900 * pitch) / frequency, 0.050118f} /* std::pow(10.0f, -26 / 20.0f); */
|
||||
}};
|
||||
case VMorpherPhenome::O:
|
||||
return {{
|
||||
{( 450 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{( 800 * pitch) / frequency, 0.281838f}, /* std::pow(10.0f, -11 / 20.0f); */
|
||||
{(2830 * pitch) / frequency, 0.079432f}, /* std::pow(10.0f, -22 / 20.0f); */
|
||||
{(3800 * pitch) / frequency, 0.079432f} /* std::pow(10.0f, -22 / 20.0f); */
|
||||
}};
|
||||
case VMorpherPhenome::U:
|
||||
return {{
|
||||
{( 325 * pitch) / frequency, 1.000000f}, /* std::pow(10.0f, 0 / 20.0f); */
|
||||
{( 700 * pitch) / frequency, 0.158489f}, /* std::pow(10.0f, -16 / 20.0f); */
|
||||
{(2700 * pitch) / frequency, 0.017782f}, /* std::pow(10.0f, -35 / 20.0f); */
|
||||
{(3800 * pitch) / frequency, 0.009999f} /* std::pow(10.0f, -40 / 20.0f); */
|
||||
}};
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
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::mem_fn(&FormantFilter::clear));
|
||||
std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]),
|
||||
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 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}));
|
||||
|
||||
if(mStep == 0)
|
||||
mGetSamples = Oscillate<Half>;
|
||||
else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid)
|
||||
mGetSamples = Oscillate<Sin>;
|
||||
else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle)
|
||||
mGetSamples = Oscillate<Triangle>;
|
||||
else /*if(props->Vmorpher.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)};
|
||||
|
||||
auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA);
|
||||
auto vowelB = getFiltersByPhoneme(props->Vmorpher.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]));
|
||||
}
|
||||
|
||||
mOutTarget = target.Main->Buffer;
|
||||
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)
|
||||
{
|
||||
/* 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)};
|
||||
|
||||
mGetSamples(mLfo, mIndex, mStep, td);
|
||||
mIndex += static_cast<uint>(mStep * td);
|
||||
mIndex &= WAVEFORM_FRACMASK;
|
||||
|
||||
auto chandata = std::begin(mChans);
|
||||
for(const auto &input : samplesIn)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
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);
|
||||
++chandata;
|
||||
}
|
||||
|
||||
base += td;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct VmorpherStateFactory final : public EffectStateFactory {
|
||||
al::intrusive_ptr<EffectState> create() override
|
||||
{ return al::intrusive_ptr<EffectState>{new VmorpherState{}}; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EffectStateFactory *VmorpherStateFactory_getFactory()
|
||||
{
|
||||
static VmorpherStateFactory VmorpherFactory{};
|
||||
return &VmorpherFactory;
|
||||
}
|
||||
Reference in New Issue
Block a user