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