aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Robinson <[email protected]>2023-10-15 00:22:24 -0700
committerChris Robinson <[email protected]>2023-10-15 00:22:24 -0700
commit5619de5ca05684656f32eafd8cfc987fb6161d97 (patch)
tree3cb802debf4d992bcda385f882ffc99818c6ee45
parent5a72f300dcfa63c9b32f23ad3cf256b245ee0cd3 (diff)
Ensure some variables are the same
And clean up some comments
-rw-r--r--alc/effects/convolution.cpp42
-rw-r--r--core/uhjfilter.cpp45
2 files changed, 46 insertions, 41 deletions
diff --git a/alc/effects/convolution.cpp b/alc/effects/convolution.cpp
index 271a4348..06c34a37 100644
--- a/alc/effects/convolution.cpp
+++ b/alc/effects/convolution.cpp
@@ -41,21 +41,21 @@
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.
- * Note that since the 0th and 129th bins are real for a real signal, their
- * imaginary components are always 0 and can be dropped, allowing their real
- * components to be combined so only 128 complex values are stored for the 129
- * bins.
+/* Convolution is implemented using a segmented overlap-add method. The impulse
+ * response is split into multiple segments of 128 samples, and each segment
+ * has an FFT applied with a 256-sample buffer (the latter half left silent) to
+ * get its frequency-domain response. The resulting response has its positive/
+ * non-mirrored frequencies saved (129 bins) in each segment. Note that since
+ * the 0- and half-frequency bins are real for a real signal, their imaginary
+ * components are always 0 and can be dropped, allowing their real components
+ * to be combined so only 128 complex values are stored for the 129 bins.
*
- * Input samples are similarly broken up into 128-sample segments, with an FFT
- * applied to each new incoming segment to get its 129 bins. A history of FFT'd
- * input segments is maintained, equal to the length of the impulse response.
+ * Input samples are similarly broken up into 128-sample segments, with a 256-
+ * sample FFT applied to each new incoming segment to get its 129 bins. A
+ * history of FFT'd input segments is maintained, equal to the number of
+ * impulse response segments.
*
- * To apply the reverberation, each impulse response segment is convolved with
+ * To apply the convolution, each impulse response segment is convolved with
* its paired input segment (using complex multiplies, far cheaper than FIRs),
* accumulating into a 129-bin FFT buffer. The input history is then shifted to
* align with later impulse response segments for the next input segment.
@@ -64,15 +64,15 @@ namespace {
* sample time-domain response for output, which is split in two halves. The
* first half is the 128-sample output, and the second half is a 128-sample
* (really, 127) delayed extension, which gets added to the output next time.
- * Convolving two time-domain responses of lengths N and M results in a time-
- * domain signal of length N+M-1, and this holds true regardless of the
- * convolution being applied in the frequency domain, so these "overflow"
- * samples need to be accounted for.
+ * Convolving two time-domain responses of length N results in a time-domain
+ * signal of length N*2 - 1, and this holds true regardless of the convolution
+ * being applied in the frequency domain, so these "overflow" samples need to
+ * be accounted for.
*
- * To avoid a delay with gathering enough input samples to apply an FFT with,
- * the first segment is applied directly in the time-domain as the samples come
- * in. Once enough have been retrieved, the FFT is applied on the input and
- * it's paired with the remaining (FFT'd) filter segments for processing.
+ * To avoid a delay with gathering enough input samples for the FFT, the first
+ * segment is applied directly in the time-domain as the samples come in. Once
+ * enough have been retrieved, the FFT is applied on the input and it's paired
+ * with the remaining (FFT'd) filter segments for processing.
*/
diff --git a/core/uhjfilter.cpp b/core/uhjfilter.cpp
index 744054a7..d33e5079 100644
--- a/core/uhjfilter.cpp
+++ b/core/uhjfilter.cpp
@@ -29,10 +29,10 @@ using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;
* segment has an FFT applied with a 256-sample buffer (the latter half left
* silent) to get its frequency-domain response.
*
- * Input samples are similarly broken up into 128-sample segments, with an FFT
- * applied with a 256-sample buffer to each new incoming segment to get its
- * frequency-domain response. A history of FFT'd input segments is maintained,
- * equal to the number of filter response segments.
+ * Input samples are similarly broken up into 128-sample segments, with a 256-
+ * sample FFT applied to each new incoming segment to get its frequency-domain
+ * response. A history of FFT'd input segments is maintained, equal to the
+ * number of filter response segments.
*
* To apply the convolution, each filter response segment is convolved with its
* paired input segment (using complex multiplies, far cheaper than time-domain
@@ -82,6 +82,10 @@ struct SplitFilter {
/* Convert to the frequency domain, shift the phase of each bin by +90
* degrees, then convert back to the time domain.
+ *
+ * NOTE: The 0- and half-frequency are always real for a real signal.
+ * To maintain that and their phase (0 or pi), they're heavily
+ * attenuated instead of shifted like the others.
*/
forward_fft(al::span{fftBuffer.get(), fft_size});
fftBuffer[0] *= std::numeric_limits<double>::epsilon();
@@ -204,6 +208,7 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
{
static constexpr auto &Filter = gSplitFilter<N>;
static_assert(sFftLength == Filter.sFftLength);
+ static_assert(sSegmentSize == Filter.sSampleLength);
static_assert(sNumSegments == Filter.sNumSegments);
ASSUME(SamplesToDo > 0);
@@ -223,13 +228,13 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
/* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */
for(size_t base{0};base < SamplesToDo;)
{
- const size_t todo{minz(Filter.sSampleLength-mFifoPos, SamplesToDo-base)};
+ const size_t todo{minz(sSegmentSize-mFifoPos, SamplesToDo-base)};
/* Transform the non-delayed input and store in the later half of the
* filter input.
*/
std::transform(winput+base, winput+base+todo, xinput+base,
- mWXIn.begin()+Filter.sSampleLength + mFifoPos,
+ mWXIn.begin()+sSegmentSize + mFifoPos,
[](const float w, const float x) noexcept -> float
{ return -0.3420201f*w + 0.5098604f*x; });
@@ -247,36 +252,36 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
base += todo;
/* Check whether the input buffer is filled with new samples. */
- if(mFifoPos < Filter.sSampleLength) break;
+ if(mFifoPos < sSegmentSize) break;
mFifoPos = 0;
/* Move the newest input to the front for the next iteration's history. */
- std::copy(mWXIn.cbegin()+Filter.sSampleLength, mWXIn.cend(), mWXIn.begin());
- std::fill(mWXIn.begin()+Filter.sSampleLength, mWXIn.end(), 0.0f);
+ std::copy(mWXIn.cbegin()+sSegmentSize, mWXIn.cend(), mWXIn.begin());
+ std::fill(mWXIn.begin()+sSegmentSize, mWXIn.end(), 0.0f);
/* Overwrite the stale/oldest FFT'd segment with the newest input. */
const size_t curseg{mCurrentSegment};
- pffft_transform(Filter.mFft.get(), mWXIn.data(),
- mWXHistory.data() + curseg*Filter.sFftLength, mWorkData.data(), PFFFT_FORWARD);
+ pffft_transform(Filter.mFft.get(), mWXIn.data(), mWXHistory.data() + curseg*sFftLength,
+ mWorkData.data(), PFFFT_FORWARD);
/* Convolve each input segment with its IR filter counterpart (aligned
* in time).
*/
mFftBuffer.fill(0.0f);
const float *filter{Filter.mFilterData.data()};
- const float *input{mWXHistory.data() + curseg*Filter.sFftLength};
+ const float *input{mWXHistory.data() + curseg*sFftLength};
for(size_t s{curseg};s < sNumSegments;++s)
{
pffft_zconvolve_accumulate(Filter.mFft.get(), input, filter, mFftBuffer.data());
- input += Filter.sFftLength;
- filter += Filter.sFftLength;
+ input += sFftLength;
+ filter += sFftLength;
}
input = mWXHistory.data();
for(size_t s{0};s < curseg;++s)
{
pffft_zconvolve_accumulate(Filter.mFft.get(), input, filter, mFftBuffer.data());
- input += Filter.sFftLength;
- filter += Filter.sFftLength;
+ input += sFftLength;
+ filter += sFftLength;
}
/* Convert back to samples, writing to the output and storing the extra
@@ -285,10 +290,10 @@ void UhjEncoder<N>::encode(float *LeftOut, float *RightOut,
pffft_transform(Filter.mFft.get(), mFftBuffer.data(), mFftBuffer.data(),
mWorkData.data(), PFFFT_BACKWARD);
- for(size_t i{0};i < Filter.sSampleLength;++i)
- mWXOut[i] = mFftBuffer[i] + mWXOut[Filter.sSampleLength+i];
- for(size_t i{0};i < Filter.sSampleLength;++i)
- mWXOut[Filter.sSampleLength+i] = mFftBuffer[Filter.sSampleLength+i];
+ for(size_t i{0};i < sSegmentSize;++i)
+ mWXOut[i] = mFftBuffer[i] + mWXOut[sSegmentSize+i];
+ for(size_t i{0};i < sSegmentSize;++i)
+ mWXOut[sSegmentSize+i] = mFftBuffer[sSegmentSize+i];
/* Shift the input history. */
mCurrentSegment = curseg ? (curseg-1) : (sNumSegments-1);