FERS 1.0.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
dsp_filters.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2007-2008 Marc Brooker and Michael Inggs
4// Copyright (c) 2008-present FERS Contributors (see AUTHORS.md).
5//
6// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
7
8/**
9 * @file dsp_filters.cpp
10 * @brief Implementation file for Digital Signal Processing (DSP) filters and upsampling/downsampling functionality.
11 */
12
13#include "dsp_filters.h"
14
15#include <algorithm>
16#include <array>
17#include <cmath>
18#include <complex>
19#include <numeric>
20#include <stdexcept>
21
22#include "core/parameters.h"
23
24constexpr RealType BLACKMAN_A0 = 0.42;
25constexpr RealType BLACKMAN_A1 = 0.5;
26constexpr RealType BLACKMAN_A2 = 0.08;
27
28namespace
29{
30 /**
31 * @brief Sinc function for FIR filter design.
32 *
33 * @param x Input value.
34 * @return Sinc value at x.
35 */
36 constexpr RealType sinc(const RealType x) noexcept { return x == 0 ? 1.0 : std::sin(x * PI) / (x * PI); }
37
38 /**
39 * @brief Generates FIR filter coefficients using the Blackman window.
40 *
41 * @param cutoff Cutoff frequency for the filter.
42 * @param filtLength Length of the filter.
43 * @return Vector of FIR filter coefficients.
44 */
45 std::vector<RealType> blackmanFir(const RealType cutoff, unsigned& filtLength) noexcept
46 {
47 filtLength = params::renderFilterLength() * 2;
48 std::vector<RealType> coeffs(filtLength);
49 const RealType n = filtLength / 2.0;
50 const RealType pi_n = PI / n;
51
52 // We use the Blackman window, for a suitable tradeoff between rolloff and stopband attenuation
53 // Equivalent Kaiser beta = 7.04 (Oppenhiem and Schaffer, Hamming)
54 std::ranges::for_each(coeffs,
55 [cutoff, n, pi_n, i = 0u](RealType& coeff) mutable
56 {
57 const RealType sinc_val = sinc(cutoff * (i - n));
58 const RealType window = BLACKMAN_A0 - BLACKMAN_A1 * std::cos(pi_n * i) +
59 BLACKMAN_A2 * std::cos(2 * pi_n * i);
60 coeff = sinc_val * window;
61 ++i;
62 });
63
64 return coeffs;
65 }
66}
67
68namespace fers_signal
69{
70 void upsample(const std::span<const ComplexType> in, const unsigned size, std::span<ComplexType> out)
71 {
72 const unsigned ratio = params::oversampleRatio();
73 // TODO: this would be better as a multirate upsampler
74 // This implementation is functional but suboptimal.
75 // Users requiring higher accuracy should oversample outside FERS until this is addressed.
76 unsigned filt_length;
77 const auto coeffs = blackmanFir(1 / static_cast<RealType>(ratio), filt_length);
78
79 std::vector tmp(size * ratio + filt_length, ComplexType{0.0, 0.0});
80
81 for (unsigned i = 0; i < size; ++i)
82 {
83 tmp[i * ratio] = in[i];
84 }
85
86 const FirFilter filt(coeffs);
87 filt.filter(tmp);
88
89 const auto delay = filt_length / 2 - 1;
90 std::ranges::copy_n(tmp.begin() + delay, size * ratio, out.begin());
91 }
92
93 std::vector<ComplexType> downsample(std::span<const ComplexType> in)
94 {
95 if (in.empty())
96 {
97 throw std::invalid_argument("Input span is empty in Downsample");
98 }
99
100 const unsigned ratio = params::oversampleRatio();
101 // TODO: Replace with a more efficient multirate downsampling implementation.
102 unsigned filt_length = 0;
103 const auto coeffs = blackmanFir(1 / static_cast<RealType>(ratio), filt_length);
104
105 std::vector tmp(in.size() + filt_length, ComplexType{0, 0});
106
107 std::ranges::copy(in, tmp.begin());
108
109 const FirFilter filt(coeffs);
110 filt.filter(tmp);
111
112 const auto downsampled_size = in.size() / ratio;
113 std::vector<ComplexType> out(downsampled_size);
114 for (unsigned i = 0; i < downsampled_size; ++i)
115 {
116 out[i] = tmp[i * ratio + filt_length / 2] / static_cast<RealType>(ratio);
117 }
118
119 return out;
120 }
121
122 IirFilter::IirFilter(const RealType* denCoeffs, const RealType* numCoeffs, const unsigned order) noexcept :
123 _a(denCoeffs, denCoeffs + order), _b(numCoeffs, numCoeffs + order), _w(order, 0.0), _order(order)
124 {
125 }
126
127 RealType IirFilter::filter(const RealType sample) noexcept
128 {
129 std::ranges::rotate(_w, _w.end() - 1);
130
131 _w[0] = sample;
132
133 for (unsigned j = 1; j < _order; ++j)
134 {
135 _w[0] -= _a[j] * _w[j];
136 }
137
138 return std::inner_product(_b.begin(), _b.end(), _w.begin(), 0.0);
139 }
140
141 void IirFilter::filter(std::span<RealType> samples) noexcept
142 {
143 for (auto& sample : samples)
144 {
145 std::ranges::rotate(_w, _w.end() - 1);
146
147 _w[0] = sample;
148
149 for (unsigned j = 1; j < _order; ++j)
150 {
151 _w[0] -= _a[j] * _w[j];
152 }
153
154 sample = std::inner_product(_b.begin(), _b.end(), _w.begin(), 0.0);
155 }
156 }
157
158 void FirFilter::filter(std::vector<ComplexType>& samples) const
159 {
160 std::vector line(_order, ComplexType{0.0, 0.0});
161
162 for (auto& sample : samples)
163 {
164 line[0] = sample;
165 ComplexType result{0.0, 0.0};
166
167 result = std::transform_reduce(line.rbegin(), line.rend(), _filter.begin(), ComplexType{0.0, 0.0},
168 std::plus<ComplexType>{},
169 [](const ComplexType& x, const RealType coeff) { return x * coeff; });
170
171 sample = result;
172
173 std::rotate(line.rbegin(), line.rbegin() + 1, line.rend());
174 }
175 }
176
178 {
179 /// 11th order elliptic lowpass at 0.1fs
180 constexpr std::array den_coeffs = {1.0,
181 -10.301102119865,
182 48.5214567642597,
183 -137.934509572412,
184 262.914952985445,
185 -352.788381841481,
186 340.027874008585,
187 -235.39260470286,
188 114.698499845697,
189 -37.4634653062448,
190 7.38208765922137,
191 -0.664807695826097};
192
193 constexpr std::array num_coeffs = {2.7301694322809e-06, -1.8508123430239e-05, 5.75739466753894e-05,
194 -0.000104348734423658, 0.000111949190289715, -4.9384188225528e-05,
195 -4.9384188225522e-05, 0.00011194919028971, -0.000104348734423656,
196 5.75739466753884e-05, -1.85081234302388e-05, 2.73016943228086e-06};
197
198 _filter = std::make_unique<IirFilter>(den_coeffs.data(), num_coeffs.data(), den_coeffs.size());
199 }
200
201 void DecadeUpsampler::upsample(const RealType sample, std::span<RealType> out) const
202 {
203 if (out.size() != 10)
204 {
205 throw std::invalid_argument("Output span must have a size of 10.");
206 }
207 out[0] = sample;
208 std::fill(out.begin() + 1, out.end(), 0);
209 _filter->filter(out);
210 }
211}
void upsample(RealType sample, std::span< RealType > out) const
Upsamples a single sample.
Implements a Finite Impulse Response (FIR) filter.
RealType filter(RealType) override
Filters a single sample.
IirFilter(const RealType *denCoeffs, const RealType *numCoeffs, unsigned order) noexcept
Constructs an IIR filter with given numerator and denominator coefficients and order.
RealType filter(RealType sample) noexcept override
Filters a single sample.
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
constexpr RealType PI
Mathematical constant π (pi).
Definition config.h:43
constexpr RealType BLACKMAN_A2
constexpr RealType BLACKMAN_A1
constexpr RealType BLACKMAN_A0
Header file for Digital Signal Processing (DSP) filters and upsampling/downsampling functionality.
void upsample(const std::span< const ComplexType > in, const unsigned size, std::span< ComplexType > out)
Upsamples a signal by a given ratio.
std::vector< ComplexType > downsample(std::span< const ComplexType > in)
Downsamples a signal by a given ratio.
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:139
unsigned renderFilterLength() noexcept
Get the render filter length.
Definition parameters.h:127
Defines the Parameters struct and provides methods for managing simulation parameters.