xref: /aosp_15_r20/external/webrtc/modules/audio_processing/three_band_filter_bank.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 // An implementation of a 3-band FIR filter-bank with DCT modulation, similar to
12 // the proposed in "Multirate Signal Processing for Communication Systems" by
13 // Fredric J Harris.
14 //
15 // The idea is to take a heterodyne system and change the order of the
16 // components to get something which is efficient to implement digitally.
17 //
18 // It is possible to separate the filter using the noble identity as follows:
19 //
20 // H(z) = H0(z^3) + z^-1 * H1(z^3) + z^-2 * H2(z^3)
21 //
22 // This is used in the analysis stage to first downsample serial to parallel
23 // and then filter each branch with one of these polyphase decompositions of the
24 // lowpass prototype. Because each filter is only a modulation of the prototype,
25 // it is enough to multiply each coefficient by the respective cosine value to
26 // shift it to the desired band. But because the cosine period is 12 samples,
27 // it requires separating the prototype even further using the noble identity.
28 // After filtering and modulating for each band, the output of all filters is
29 // accumulated to get the downsampled bands.
30 //
31 // A similar logic can be applied to the synthesis stage.
32 
33 #include "modules/audio_processing/three_band_filter_bank.h"
34 
35 #include <array>
36 
37 #include "rtc_base/checks.h"
38 
39 namespace webrtc {
40 namespace {
41 
42 // Factors to take into account when choosing `kFilterSize`:
43 //   1. Higher `kFilterSize`, means faster transition, which ensures less
44 //      aliasing. This is especially important when there is non-linear
45 //      processing between the splitting and merging.
46 //   2. The delay that this filter bank introduces is
47 //      `kNumBands` * `kSparsity` * `kFilterSize` / 2, so it increases linearly
48 //      with `kFilterSize`.
49 //   3. The computation complexity also increases linearly with `kFilterSize`.
50 
51 // The Matlab code to generate these `kFilterCoeffs` is:
52 //
53 // N = kNumBands * kSparsity * kFilterSize - 1;
54 // h = fir1(N, 1 / (2 * kNumBands), kaiser(N + 1, 3.5));
55 // reshape(h, kNumBands * kSparsity, kFilterSize);
56 //
57 // The code below uses the values of kFilterSize, kNumBands and kSparsity
58 // specified in the header.
59 
60 // Because the total bandwidth of the lower and higher band is double the middle
61 // one (because of the spectrum parity), the low-pass prototype is half the
62 // bandwidth of 1 / (2 * `kNumBands`) and is then shifted with cosine modulation
63 // to the right places.
64 // A Kaiser window is used because of its flexibility and the alpha is set to
65 // 3.5, since that sets a stop band attenuation of 40dB ensuring a fast
66 // transition.
67 
68 constexpr int kSubSampling = ThreeBandFilterBank::kNumBands;
69 constexpr int kDctSize = ThreeBandFilterBank::kNumBands;
70 static_assert(ThreeBandFilterBank::kNumBands *
71                       ThreeBandFilterBank::kSplitBandSize ==
72                   ThreeBandFilterBank::kFullBandSize,
73               "The full band must be split in equally sized subbands");
74 
75 const float
76     kFilterCoeffs[ThreeBandFilterBank::kNumNonZeroFilters][kFilterSize] = {
77         {-0.00047749f, -0.00496888f, +0.16547118f, +0.00425496f},
78         {-0.00173287f, -0.01585778f, +0.14989004f, +0.00994113f},
79         {-0.00304815f, -0.02536082f, +0.12154542f, +0.01157993f},
80         {-0.00346946f, -0.02587886f, +0.04760441f, +0.00607594f},
81         {-0.00154717f, -0.01136076f, +0.01387458f, +0.00186353f},
82         {+0.00186353f, +0.01387458f, -0.01136076f, -0.00154717f},
83         {+0.00607594f, +0.04760441f, -0.02587886f, -0.00346946f},
84         {+0.00983212f, +0.08543175f, -0.02982767f, -0.00383509f},
85         {+0.00994113f, +0.14989004f, -0.01585778f, -0.00173287f},
86         {+0.00425496f, +0.16547118f, -0.00496888f, -0.00047749f}};
87 
88 constexpr int kZeroFilterIndex1 = 3;
89 constexpr int kZeroFilterIndex2 = 9;
90 
91 const float kDctModulation[ThreeBandFilterBank::kNumNonZeroFilters][kDctSize] =
92     {{2.f, 2.f, 2.f},
93      {1.73205077f, 0.f, -1.73205077f},
94      {1.f, -2.f, 1.f},
95      {-1.f, 2.f, -1.f},
96      {-1.73205077f, 0.f, 1.73205077f},
97      {-2.f, -2.f, -2.f},
98      {-1.73205077f, 0.f, 1.73205077f},
99      {-1.f, 2.f, -1.f},
100      {1.f, -2.f, 1.f},
101      {1.73205077f, 0.f, -1.73205077f}};
102 
103 // Filters the input signal `in` with the filter `filter` using a shift by
104 // `in_shift`, taking into account the previous state.
FilterCore(rtc::ArrayView<const float,kFilterSize> filter,rtc::ArrayView<const float,ThreeBandFilterBank::kSplitBandSize> in,const int in_shift,rtc::ArrayView<float,ThreeBandFilterBank::kSplitBandSize> out,rtc::ArrayView<float,kMemorySize> state)105 void FilterCore(
106     rtc::ArrayView<const float, kFilterSize> filter,
107     rtc::ArrayView<const float, ThreeBandFilterBank::kSplitBandSize> in,
108     const int in_shift,
109     rtc::ArrayView<float, ThreeBandFilterBank::kSplitBandSize> out,
110     rtc::ArrayView<float, kMemorySize> state) {
111   constexpr int kMaxInShift = (kStride - 1);
112   RTC_DCHECK_GE(in_shift, 0);
113   RTC_DCHECK_LE(in_shift, kMaxInShift);
114   std::fill(out.begin(), out.end(), 0.f);
115 
116   for (int k = 0; k < in_shift; ++k) {
117     for (int i = 0, j = kMemorySize + k - in_shift; i < kFilterSize;
118          ++i, j -= kStride) {
119       out[k] += state[j] * filter[i];
120     }
121   }
122 
123   for (int k = in_shift, shift = 0; k < kFilterSize * kStride; ++k, ++shift) {
124     RTC_DCHECK_GE(shift, 0);
125     const int loop_limit = std::min(kFilterSize, 1 + (shift >> kStrideLog2));
126     for (int i = 0, j = shift; i < loop_limit; ++i, j -= kStride) {
127       out[k] += in[j] * filter[i];
128     }
129     for (int i = loop_limit, j = kMemorySize + shift - loop_limit * kStride;
130          i < kFilterSize; ++i, j -= kStride) {
131       out[k] += state[j] * filter[i];
132     }
133   }
134 
135   for (int k = kFilterSize * kStride, shift = kFilterSize * kStride - in_shift;
136        k < ThreeBandFilterBank::kSplitBandSize; ++k, ++shift) {
137     for (int i = 0, j = shift; i < kFilterSize; ++i, j -= kStride) {
138       out[k] += in[j] * filter[i];
139     }
140   }
141 
142   // Update current state.
143   std::copy(in.begin() + ThreeBandFilterBank::kSplitBandSize - kMemorySize,
144             in.end(), state.begin());
145 }
146 
147 }  // namespace
148 
149 // Because the low-pass filter prototype has half bandwidth it is possible to
150 // use a DCT to shift it in both directions at the same time, to the center
151 // frequencies [1 / 12, 3 / 12, 5 / 12].
ThreeBandFilterBank()152 ThreeBandFilterBank::ThreeBandFilterBank() {
153   RTC_DCHECK_EQ(state_analysis_.size(), kNumNonZeroFilters);
154   RTC_DCHECK_EQ(state_synthesis_.size(), kNumNonZeroFilters);
155   for (int k = 0; k < kNumNonZeroFilters; ++k) {
156     RTC_DCHECK_EQ(state_analysis_[k].size(), kMemorySize);
157     RTC_DCHECK_EQ(state_synthesis_[k].size(), kMemorySize);
158 
159     state_analysis_[k].fill(0.f);
160     state_synthesis_[k].fill(0.f);
161   }
162 }
163 
164 ThreeBandFilterBank::~ThreeBandFilterBank() = default;
165 
166 // The analysis can be separated in these steps:
167 //   1. Serial to parallel downsampling by a factor of `kNumBands`.
168 //   2. Filtering of `kSparsity` different delayed signals with polyphase
169 //      decomposition of the low-pass prototype filter and upsampled by a factor
170 //      of `kSparsity`.
171 //   3. Modulating with cosines and accumulating to get the desired band.
Analysis(rtc::ArrayView<const float,kFullBandSize> in,rtc::ArrayView<const rtc::ArrayView<float>,ThreeBandFilterBank::kNumBands> out)172 void ThreeBandFilterBank::Analysis(
173     rtc::ArrayView<const float, kFullBandSize> in,
174     rtc::ArrayView<const rtc::ArrayView<float>, ThreeBandFilterBank::kNumBands>
175         out) {
176   // Initialize the output to zero.
177   for (int band = 0; band < ThreeBandFilterBank::kNumBands; ++band) {
178     RTC_DCHECK_EQ(out[band].size(), kSplitBandSize);
179     std::fill(out[band].begin(), out[band].end(), 0);
180   }
181 
182   for (int downsampling_index = 0; downsampling_index < kSubSampling;
183        ++downsampling_index) {
184     // Downsample to form the filter input.
185     std::array<float, kSplitBandSize> in_subsampled;
186     for (int k = 0; k < kSplitBandSize; ++k) {
187       in_subsampled[k] =
188           in[(kSubSampling - 1) - downsampling_index + kSubSampling * k];
189     }
190 
191     for (int in_shift = 0; in_shift < kStride; ++in_shift) {
192       // Choose filter, skip zero filters.
193       const int index = downsampling_index + in_shift * kSubSampling;
194       if (index == kZeroFilterIndex1 || index == kZeroFilterIndex2) {
195         continue;
196       }
197       const int filter_index =
198           index < kZeroFilterIndex1
199               ? index
200               : (index < kZeroFilterIndex2 ? index - 1 : index - 2);
201 
202       rtc::ArrayView<const float, kFilterSize> filter(
203           kFilterCoeffs[filter_index]);
204       rtc::ArrayView<const float, kDctSize> dct_modulation(
205           kDctModulation[filter_index]);
206       rtc::ArrayView<float, kMemorySize> state(state_analysis_[filter_index]);
207 
208       // Filter.
209       std::array<float, kSplitBandSize> out_subsampled;
210       FilterCore(filter, in_subsampled, in_shift, out_subsampled, state);
211 
212       // Band and modulate the output.
213       for (int band = 0; band < ThreeBandFilterBank::kNumBands; ++band) {
214         float* out_band = out[band].data();
215         for (int n = 0; n < kSplitBandSize; ++n) {
216           out_band[n] += dct_modulation[band] * out_subsampled[n];
217         }
218       }
219     }
220   }
221 }
222 
223 // The synthesis can be separated in these steps:
224 //   1. Modulating with cosines.
225 //   2. Filtering each one with a polyphase decomposition of the low-pass
226 //      prototype filter upsampled by a factor of `kSparsity` and accumulating
227 //      `kSparsity` signals with different delays.
228 //   3. Parallel to serial upsampling by a factor of `kNumBands`.
Synthesis(rtc::ArrayView<const rtc::ArrayView<float>,ThreeBandFilterBank::kNumBands> in,rtc::ArrayView<float,kFullBandSize> out)229 void ThreeBandFilterBank::Synthesis(
230     rtc::ArrayView<const rtc::ArrayView<float>, ThreeBandFilterBank::kNumBands>
231         in,
232     rtc::ArrayView<float, kFullBandSize> out) {
233   std::fill(out.begin(), out.end(), 0);
234   for (int upsampling_index = 0; upsampling_index < kSubSampling;
235        ++upsampling_index) {
236     for (int in_shift = 0; in_shift < kStride; ++in_shift) {
237       // Choose filter, skip zero filters.
238       const int index = upsampling_index + in_shift * kSubSampling;
239       if (index == kZeroFilterIndex1 || index == kZeroFilterIndex2) {
240         continue;
241       }
242       const int filter_index =
243           index < kZeroFilterIndex1
244               ? index
245               : (index < kZeroFilterIndex2 ? index - 1 : index - 2);
246 
247       rtc::ArrayView<const float, kFilterSize> filter(
248           kFilterCoeffs[filter_index]);
249       rtc::ArrayView<const float, kDctSize> dct_modulation(
250           kDctModulation[filter_index]);
251       rtc::ArrayView<float, kMemorySize> state(state_synthesis_[filter_index]);
252 
253       // Prepare filter input by modulating the banded input.
254       std::array<float, kSplitBandSize> in_subsampled;
255       std::fill(in_subsampled.begin(), in_subsampled.end(), 0.f);
256       for (int band = 0; band < ThreeBandFilterBank::kNumBands; ++band) {
257         RTC_DCHECK_EQ(in[band].size(), kSplitBandSize);
258         const float* in_band = in[band].data();
259         for (int n = 0; n < kSplitBandSize; ++n) {
260           in_subsampled[n] += dct_modulation[band] * in_band[n];
261         }
262       }
263 
264       // Filter.
265       std::array<float, kSplitBandSize> out_subsampled;
266       FilterCore(filter, in_subsampled, in_shift, out_subsampled, state);
267 
268       // Upsample.
269       constexpr float kUpsamplingScaling = kSubSampling;
270       for (int k = 0; k < kSplitBandSize; ++k) {
271         out[upsampling_index + kSubSampling * k] +=
272             kUpsamplingScaling * out_subsampled[k];
273       }
274     }
275   }
276 }
277 
278 }  // namespace webrtc
279