diff options
author | Chris Robinson <[email protected]> | 2019-03-31 18:54:43 -0700 |
---|---|---|
committer | Chris Robinson <[email protected]> | 2019-03-31 18:54:43 -0700 |
commit | c88b09170cd5cebf7c64b283fa8dbec4d2f6d29e (patch) | |
tree | 3fc2b032018b7a656b17b4deefd6a92569d105ea | |
parent | d8c76ba0c06b49ef73697390594f7359cc9e7e71 (diff) |
Use phase correction for the front stablizer
-rw-r--r-- | Alc/alu.cpp | 55 | ||||
-rw-r--r-- | Alc/filters/splitter.h | 8 | ||||
-rw-r--r-- | Alc/panning.cpp | 15 |
3 files changed, 68 insertions, 10 deletions
diff --git a/Alc/alu.cpp b/Alc/alu.cpp index dd160202..4492b1f3 100644 --- a/Alc/alu.cpp +++ b/Alc/alu.cpp @@ -1471,20 +1471,65 @@ void ApplyStablizer(FrontStablizer *Stablizer, ALfloat (*RESTRICT Buffer)[BUFFER ASSUME(SamplesToDo > 0); ASSUME(NumChannels > 0); - /* Apply an all-pass to all channels, except the front-left and front- - * right, so they maintain the same relative phase. + /* Apply a delay to all channels, except the front-left and front-right, so + * they maintain correct timing. */ for(ALsizei i{0};i < NumChannels;i++) { if(i == lidx || i == ridx) continue; - Stablizer->APFilter[i].process(Buffer[i], SamplesToDo); + + auto &DelayBuf = Stablizer->DelayBuf[i]; + auto buffer_end = Buffer[i] + SamplesToDo; + if(LIKELY(SamplesToDo >= ALsizei{FrontStablizer::DelayLength})) + { + auto delay_end = std::rotate(Buffer[i], buffer_end - FrontStablizer::DelayLength, + buffer_end); + std::swap_ranges(Buffer[i], delay_end, std::begin(DelayBuf)); + } + else + { + auto delay_start = std::swap_ranges(Buffer[i], buffer_end, std::begin(DelayBuf)); + std::rotate(std::begin(DelayBuf), delay_start, std::end(DelayBuf)); + } } + SplitterAllpass APFilter = Stablizer->APFilter; ALfloat (&lsplit)[2][BUFFERSIZE] = Stablizer->LSplit; ALfloat (&rsplit)[2][BUFFERSIZE] = Stablizer->RSplit; - Stablizer->LFilter.process(lsplit[1], lsplit[0], Buffer[lidx], SamplesToDo); - Stablizer->RFilter.process(rsplit[1], rsplit[0], Buffer[ridx], SamplesToDo); + auto &tmpbuf = Stablizer->TempBuf; + + /* This applies the band-splitter, preserving phase at the cost of some + * delay. The shorter the delay, the more error seeps into the result. + */ + auto apply_splitter = [&APFilter,&tmpbuf,SamplesToDo](const ALfloat *RESTRICT Buffer, + ALfloat (&DelayBuf)[FrontStablizer::DelayLength], BandSplitter &Filter, + ALfloat (&splitbuf)[2][BUFFERSIZE]) -> void + { + /* Combine the delayed samples and the input samples into the temp + * buffer, in reverse. Then copy the final samples back into the delay + * buffer for next time. Note that the delay buffer's samples are + * stored backwards here. + */ + auto tmpbuf_end = std::begin(tmpbuf) + SamplesToDo; + std::copy_n(std::begin(DelayBuf), FrontStablizer::DelayLength, tmpbuf_end); + std::reverse_copy(Buffer, Buffer+SamplesToDo, tmpbuf_end); + std::copy_n(std::begin(tmpbuf), FrontStablizer::DelayLength, std::begin(DelayBuf)); + + /* Apply an all-pass on the reversed signal, then reverse the samples + * to get the forward signal with a reversed phase shift. + */ + APFilter.process(tmpbuf, SamplesToDo+FrontStablizer::DelayLength); + std::reverse(std::begin(tmpbuf), tmpbuf_end+FrontStablizer::DelayLength); + + /* Now apply the band-splitter, combining its phase shift with the + * reversed phase shift, restoring the original phase on the split + * signal. + */ + Filter.process(splitbuf[1], splitbuf[0], tmpbuf, SamplesToDo); + }; + apply_splitter(Buffer[lidx], Stablizer->DelayBuf[lidx], Stablizer->LFilter, lsplit); + apply_splitter(Buffer[ridx], Stablizer->DelayBuf[ridx], Stablizer->RFilter, rsplit); for(ALsizei i{0};i < SamplesToDo;i++) { diff --git a/Alc/filters/splitter.h b/Alc/filters/splitter.h index 83281366..ccb4f104 100644 --- a/Alc/filters/splitter.h +++ b/Alc/filters/splitter.h @@ -38,11 +38,17 @@ using SplitterAllpass = SplitterAllpassR<float>; struct FrontStablizer { - SplitterAllpass APFilter[MAX_OUTPUT_CHANNELS]; + static constexpr size_t DelayLength{256u}; + + alignas(16) float DelayBuf[MAX_OUTPUT_CHANNELS][DelayLength]; + + SplitterAllpass APFilter; BandSplitter LFilter, RFilter; alignas(16) float LSplit[2][BUFFERSIZE]; alignas(16) float RSplit[2][BUFFERSIZE]; + alignas(16) float TempBuf[BUFFERSIZE + DelayLength]; + DEF_NEWDEL(FrontStablizer) }; diff --git a/Alc/panning.cpp b/Alc/panning.cpp index f8ca7149..34984c94 100644 --- a/Alc/panning.cpp +++ b/Alc/panning.cpp @@ -27,6 +27,7 @@ #include <cassert> #include <cmath> +#include <chrono> #include <numeric> #include <algorithm> #include <functional> @@ -54,6 +55,8 @@ constexpr std::array<int,MAX_AMBI_CHANNELS> AmbiIndex::From3D; namespace { using namespace std::placeholders; +using std::chrono::seconds; +using std::chrono::nanoseconds; inline const char *GetLabelFromChannel(Channel channel) { @@ -850,12 +853,16 @@ void aluInitRenderer(ALCdevice *device, ALint hrtf_id, HrtfRequestMode hrtf_appr stablizer->LFilter.init(scale); stablizer->RFilter = stablizer->LFilter; - /* Initialize all-pass filters for all other channels. */ - stablizer->APFilter[0].init(scale); - std::fill(std::begin(stablizer->APFilter)+1, std::end(stablizer->APFilter), - stablizer->APFilter[0]); + /* Initialize an all-pass filter for the phase corrector. */ + stablizer->APFilter.init(scale); device->Stablizer = std::move(stablizer); + /* NOTE: Don't know why this has to be "copied" into a local + * static constexpr variable to avoid a reference on + * FrontStablizer::DelayLength... + */ + static constexpr size_t StablizerDelay{FrontStablizer::DelayLength}; + device->FixedLatency += nanoseconds{seconds{StablizerDelay}} / device->Frequency; } break; case DevFmtMono: |