Add and use mixers that process one input and output channel

This commit is contained in:
Chris Robinson 2023-01-05 01:47:55 -08:00
parent 58a18ab3c0
commit 23c8a35505
11 changed files with 222 additions and 8 deletions

View File

@ -214,8 +214,8 @@ void AutowahState::process(const size_t samplesToDo,
chandata->mFilter.z2 = z2;
/* Now, mix the processed sound data to the output. */
MixSamples({mBufferOut, samplesToDo}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain,
&chandata->mTargetGain, samplesToDo, 0);
MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo);
++chandata;
}
}

View File

@ -182,8 +182,8 @@ void EqualizerState::process(const size_t samplesToDo, const al::span<const Floa
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], 1}, &chan->mCurrentGain, &chan->mTargetGain,
samplesToDo, 0u);
MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain,
samplesToDo);
}
++chan;
}

View File

@ -168,8 +168,8 @@ void ModulatorState::process(const size_t samplesToDo, const al::span<const Floa
for(size_t i{0u};i < td;i++)
temps[i] *= modsamples[i];
MixSamples({temps, td}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain,
&chandata->mTargetGain, samplesToDo-base, base);
MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
}
++chandata;
}

View File

@ -326,8 +326,8 @@ void VmorpherState::process(const size_t samplesToDo, const al::span<const Float
blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]);
/* Now, mix the processed sound data to the output. */
MixSamples({blended, td}, {&samplesOut[outidx], 1}, &chandata->mCurrentGain,
&chandata->mTargetGain, samplesToDo-base, base);
MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain,
chandata->mTargetGain, samplesToDo-base);
++chandata;
}

View File

@ -14,6 +14,7 @@ struct CTag;
MixerOutFunc MixSamplesOut{Mix_<CTag>};
MixerOneFunc MixSamplesOne{Mix_<CTag>};
std::array<float,MaxAmbiChannels> CalcAmbiCoeffs(const float y, const float z, const float x,

View File

@ -24,6 +24,14 @@ inline void MixSamples(const al::span<const float> InSamples,
const size_t Counter, const size_t OutPos)
{ MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); }
/* Mixer functions that handle one input and one output channel. */
using MixerOneFunc = void(*)(const al::span<const float> InSamples, float *OutBuffer,
float &CurrentGain, const float TargetGain, const size_t Counter);
extern MixerOneFunc MixSamplesOne;
inline void MixSamples(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
const float TargetGain, const size_t Counter)
{ MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); }
/**

View File

@ -67,6 +67,9 @@ float *Resample_(const InterpState *state, float *RESTRICT src, uint frac, uint
template<typename InstTag>
void Mix_(const al::span<const float> InSamples, const al::span<FloatBufferLine> OutBuffer,
float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos);
template<typename InstTag>
void Mix_(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
const float TargetGain, const size_t Counter);
template<typename InstTag>
void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize,

View File

@ -198,3 +198,38 @@ void Mix_<CTag>(const al::span<const float> InSamples, const al::span<FloatBuffe
dst[pos] += InSamples[pos] * gain;
}
}
template<>
void Mix_<CTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
const float TargetGain, const size_t Counter)
{
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
const auto min_len = minz(Counter, InSamples.size());
float *RESTRICT dst{al::assume_aligned<16>(OutBuffer)};
float gain{CurrentGain};
const float step{(TargetGain-gain) * delta};
size_t pos{0};
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
gain = TargetGain;
else
{
float step_count{0.0f};
for(;pos != min_len;++pos)
{
dst[pos] += InSamples[pos] * (gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGain;
else
gain += step*step_count;
}
CurrentGain = gain;
if(!(std::abs(gain) > GainSilenceThreshold))
return;
for(;pos != InSamples.size();++pos)
dst[pos] += InSamples[pos] * gain;
}

View File

@ -305,3 +305,80 @@ void Mix_<NEONTag>(const al::span<const float> InSamples, const al::span<FloatBu
dst[pos] += InSamples[pos] * gain;
}
}
template<>
void Mix_<NEONTag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
const float TargetGain, const size_t Counter)
{
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
const auto min_len = minz(Counter, InSamples.size());
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
float *RESTRICT dst{al::assume_aligned<16>(OutBuffer)};
float gain{CurrentGain};
const float step{(TargetGain-gain) * delta};
size_t pos{0};
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
gain = TargetGain;
else
{
float step_count{0.0f};
/* Mix with applying gain steps in aligned multiples of 4. */
if(size_t todo{min_len >> 2})
{
const float32x4_t four4{vdupq_n_f32(4.0f)};
const float32x4_t step4{vdupq_n_f32(step)};
const float32x4_t gain4{vdupq_n_f32(gain)};
float32x4_t step_count4{vdupq_n_f32(0.0f)};
step_count4 = vsetq_lane_f32(1.0f, step_count4, 1);
step_count4 = vsetq_lane_f32(2.0f, step_count4, 2);
step_count4 = vsetq_lane_f32(3.0f, step_count4, 3);
do {
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
float32x4_t dry4 = vld1q_f32(&dst[pos]);
dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4));
step_count4 = vaddq_f32(step_count4, four4);
vst1q_f32(&dst[pos], dry4);
pos += 4;
} while(--todo);
/* NOTE: step_count4 now represents the next four counts after the
* last four mixed samples, so the lowest element represents the
* next step count to apply.
*/
step_count = vgetq_lane_f32(step_count4, 0);
}
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
{
dst[pos] += InSamples[pos] * (gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGain;
else
gain += step*step_count;
/* Mix until pos is aligned with 4 or the mix is done. */
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
dst[pos] += InSamples[pos] * gain;
}
CurrentGain = gain;
if(!(std::abs(gain) > GainSilenceThreshold))
return;
if(size_t todo{(InSamples.size()-pos) >> 2})
{
const float32x4_t gain4 = vdupq_n_f32(gain);
do {
const float32x4_t val4 = vld1q_f32(&InSamples[pos]);
float32x4_t dry4 = vld1q_f32(&dst[pos]);
dry4 = vmlaq_f32(dry4, val4, gain4);
vst1q_f32(&dst[pos], dry4);
pos += 4;
} while(--todo);
}
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
dst[pos] += InSamples[pos] * gain;
}

View File

@ -270,3 +270,79 @@ void Mix_<SSETag>(const al::span<const float> InSamples, const al::span<FloatBuf
dst[pos] += InSamples[pos] * gain;
}
}
template<>
void Mix_<SSETag>(const al::span<const float> InSamples, float *OutBuffer, float &CurrentGain,
const float TargetGain, const size_t Counter)
{
const float delta{(Counter > 0) ? 1.0f / static_cast<float>(Counter) : 0.0f};
const auto min_len = minz(Counter, InSamples.size());
const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len;
float *RESTRICT dst{al::assume_aligned<16>(OutBuffer)};
float gain{CurrentGain};
const float step{(TargetGain-gain) * delta};
size_t pos{0};
if(!(std::abs(step) > std::numeric_limits<float>::epsilon()))
gain = TargetGain;
else
{
float step_count{0.0f};
/* Mix with applying gain steps in aligned multiples of 4. */
if(size_t todo{min_len >> 2})
{
const __m128 four4{_mm_set1_ps(4.0f)};
const __m128 step4{_mm_set1_ps(step)};
const __m128 gain4{_mm_set1_ps(gain)};
__m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)};
do {
const __m128 val4{_mm_load_ps(&InSamples[pos])};
__m128 dry4{_mm_load_ps(&dst[pos])};
/* dry += val * (gain + step*step_count) */
dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4));
_mm_store_ps(&dst[pos], dry4);
step_count4 = _mm_add_ps(step_count4, four4);
pos += 4;
} while(--todo);
/* NOTE: step_count4 now represents the next four counts after the
* last four mixed samples, so the lowest element represents the
* next step count to apply.
*/
step_count = _mm_cvtss_f32(step_count4);
}
/* Mix with applying left over gain steps that aren't aligned multiples of 4. */
for(size_t leftover{min_len&3};leftover;++pos,--leftover)
{
dst[pos] += InSamples[pos] * (gain + step*step_count);
step_count += 1.0f;
}
if(pos == Counter)
gain = TargetGain;
else
gain += step*step_count;
/* Mix until pos is aligned with 4 or the mix is done. */
for(size_t leftover{aligned_len&3};leftover;++pos,--leftover)
dst[pos] += InSamples[pos] * gain;
}
CurrentGain = gain;
if(!(std::abs(gain) > GainSilenceThreshold))
return;
if(size_t todo{(InSamples.size()-pos) >> 2})
{
const __m128 gain4{_mm_set1_ps(gain)};
do {
const __m128 val4{_mm_load_ps(&InSamples[pos])};
__m128 dry4{_mm_load_ps(&dst[pos])};
dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4));
_mm_store_ps(&dst[pos], dry4);
pos += 4;
} while(--todo);
}
for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover)
dst[pos] += InSamples[pos] * gain;
}

View File

@ -84,6 +84,19 @@ inline MixerOutFunc SelectMixer()
return Mix_<CTag>;
}
inline MixerOneFunc SelectMixerOne()
{
#ifdef HAVE_NEON
if((CPUCapFlags&CPU_CAP_NEON))
return Mix_<NEONTag>;
#endif
#ifdef HAVE_SSE
if((CPUCapFlags&CPU_CAP_SSE))
return Mix_<SSETag>;
#endif
return Mix_<CTag>;
}
inline HrtfMixerFunc SelectHrtfMixer()
{
#ifdef HAVE_NEON
@ -153,6 +166,7 @@ void Voice::InitMixer(al::optional<std::string> resampler)
}
MixSamplesOut = SelectMixer();
MixSamplesOne = SelectMixerOne();
MixHrtfBlendSamples = SelectHrtfBlendMixer();
MixHrtfSamples = SelectHrtfMixer();
}