FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
signal_processor.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2006-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 signal_processor.cpp
10 * @brief Implementation for receiver-side signal processing and rendering.
11 *
12 * This file contains the implementation of functions that perform digital signal
13 * processing on received radar signals, as declared in signal_processor.h.
14 */
15
16#include "signal_processor.h"
17
18#include <algorithm>
19#include <cmath>
20#include <cstdint>
21#include <future>
22#include <limits>
23#include <queue>
24#include <stdexcept>
25#include <tuple>
26#include <vector>
27
28#include "core/parameters.h"
29#include "core/thread_pool.h"
31#include "serial/response.h"
32
33namespace
34{
35 [[nodiscard]] std::int16_t scaleComponentToInt16(const RealType value, const RealType fullscale,
36 bool& clipped) noexcept
37 {
38 if (value > fullscale)
39 {
40 clipped = true;
41 return std::numeric_limits<std::int16_t>::max();
42 }
43 if (value < -fullscale)
44 {
45 clipped = true;
46 return std::numeric_limits<std::int16_t>::min();
47 }
48 if (value == fullscale)
49 {
50 return std::numeric_limits<std::int16_t>::max();
51 }
52 if (value == -fullscale)
53 {
54 return std::numeric_limits<std::int16_t>::min();
55 }
56
57 const RealType scaled =
58 std::round((value / fullscale) * static_cast<RealType>(std::numeric_limits<std::int16_t>::max()));
59 return static_cast<std::int16_t>(
60 std::clamp<RealType>(scaled, static_cast<RealType>(std::numeric_limits<std::int16_t>::min()),
61 static_cast<RealType>(std::numeric_limits<std::int16_t>::max())));
62 }
63
64 /**
65 * @brief Simulate an ADC quantization process on a window of complex samples.
66 * @param data A span of ComplexType objects representing the window to quantize.
67 * @param bits The number of bits used for quantization.
68 * @param fullscale The full-scale value used for quantization.
69 */
70 void adcSimulate(std::span<ComplexType> data, const unsigned bits, const RealType fullscale) noexcept
71 {
72 const RealType levels = std::pow(2, bits - 1);
73
74 for (auto& sample : data)
75 {
76 auto [i, q] = std::tuple{std::clamp(std::floor(levels * sample.real() / fullscale) / levels, -1.0, 1.0),
77 std::clamp(std::floor(levels * sample.imag() / fullscale) / levels, -1.0, 1.0)};
78 sample = ComplexType(i, q);
79 }
80 }
81
82 /**
83 * @brief Process a single response and add its rendered data to a local window buffer.
84 * @param resp A pointer to a Response object to process.
85 * @param localWindow A reference to a vector of ComplexType representing the local window.
86 * @param rate The sample rate in Hz.
87 * @param start The start time of the window in seconds.
88 * @param fracDelay The fractional delay of the window in seconds.
89 * @param localWindowSize The size of the local window in samples.
90 */
91 void processResponse(const serial::Response* resp, std::vector<ComplexType>& localWindow, const RealType rate,
92 const RealType start, const RealType fracDelay, const std::size_t localWindowSize)
93 {
94 unsigned psize = 0;
95 RealType prate = std::numeric_limits<RealType>::quiet_NaN();
96 const auto array = resp->renderBinary(prate, psize, fracDelay);
97 int const start_sample = static_cast<int>(std::round(rate * (resp->startTime() - start)));
99 const auto start_offset = static_cast<std::size_t>(std::max(start_sample, 0));
100
101 for (std::size_t i = roffset; i < psize && i + start_offset < localWindowSize; ++i)
102 {
103 localWindow[i + start_offset] += array[i];
104 }
105 }
106}
107
108namespace processing
109{
110 void renderWindow(std::vector<ComplexType>& window, const RealType length, const RealType start,
111 const RealType fracDelay, const std::span<const std::unique_ptr<serial::Response>> responses)
112 {
113 const RealType end = start + length;
114 std::queue<serial::Response*> work_list;
115
116 for (const auto& response : responses)
117 {
118 if (response->startTime() <= end && response->endTime() >= start)
119 {
120 work_list.push(response.get());
121 }
122 }
123
125 const auto local_window_size = static_cast<std::size_t>(std::ceil(length * rate));
126
128
129 while (!work_list.empty())
130 {
131 const auto* resp = work_list.front();
132 work_list.pop();
134 }
135
136 for (std::size_t i = 0; i < local_window_size; ++i)
137 {
138 window[i] += local_window[i];
139 }
140 }
141
142 void applyThermalNoise(std::span<ComplexType> window, const RealType noiseTemperature, std::mt19937& rngEngine)
143 {
144 if (noiseTemperature == 0)
145 {
146 return;
147 }
148
149 const RealType b = params::rate() / (2.0 * params::oversampleRatio());
151
152 // Split total power equally between the I and Q channels
154 const RealType stddev = std::sqrt(per_channel_power);
155
157 for (auto& sample : window)
158 {
159 sample += ComplexType(generator.getSample(), generator.getSample());
160 }
161 }
162
164 std::mt19937& rngEngine, const RealType sampleRateHz)
165 {
166 if (noiseTemperature == 0 || sampleRateHz <= 0.0)
167 {
168 return;
169 }
170
173 const RealType stddev = std::sqrt(per_channel_power);
174
176 for (auto& sample : window)
177 {
178 sample += ComplexType(generator.getSample(), generator.getSample());
179 }
180 }
181
182 RealType quantizeAndScaleWindow(std::span<ComplexType> window)
183 {
185 for (const auto& sample : window)
186 {
187 const RealType real_abs = std::fabs(sample.real());
188 const RealType imag_abs = std::fabs(sample.imag());
189
190 max_value = std::max({max_value, real_abs, imag_abs});
191 }
192
193 if (const auto adc_bits = params::adcBits(); adc_bits > 0)
194 {
195 adcSimulate(window, adc_bits, max_value);
196 }
197 else if (max_value != 0)
198 {
199 for (auto& sample : window)
200 {
201 sample /= max_value;
202 }
203 }
204 return max_value;
205 }
206
207 FixedFullscaleScalingResult scaleToInt16FixedFullscale(const std::span<const ComplexType> samples,
208 const RealType fullscale)
209 {
210 if (fullscale <= 0.0 || !std::isfinite(fullscale))
211 {
212 throw std::invalid_argument("Fixed full-scale must be positive and finite.");
213 }
214
216 result.samples.reserve(samples.size());
217 for (const auto& sample : samples)
218 {
219 bool clipped = false;
220 const auto i = scaleComponentToInt16(sample.real(), fullscale, clipped);
221 const auto q = scaleComponentToInt16(sample.imag(), fullscale, clipped);
222 if (clipped)
223 {
224 ++result.clipped_sample_count;
225 }
226 result.samples.push_back(FixedFullscaleIqSample{.i = i, .q = q});
227 }
228 return result;
229 }
230}
Generates white Gaussian noise.
Manages radar signal responses from a transmitter.
Definition response.h:42
double RealType
Type for real numbers.
Definition config.h:27
std::complex< RealType > ComplexType
Type for complex numbers.
Definition config.h:35
RealType rate() noexcept
Get the rendering sample rate.
Definition parameters.h:121
RealType boltzmannK() noexcept
Get the Boltzmann constant.
Definition parameters.h:97
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Definition parameters.h:151
unsigned adcBits() noexcept
Get the ADC quantization bits.
Definition parameters.h:133
FixedFullscaleScalingResult scaleToInt16FixedFullscale(const std::span< const ComplexType > samples, const RealType fullscale)
Scales complex samples against a fixed full-scale to signed int16 IQ.
void applyThermalNoise(std::span< ComplexType > window, const RealType noiseTemperature, std::mt19937 &rngEngine)
Applies thermal (Johnson-Nyquist) noise to a window of I/Q samples.
void applyThermalNoiseAtSampleRate(std::span< ComplexType > window, const RealType noiseTemperature, std::mt19937 &rngEngine, const RealType sampleRateHz)
Applies circular complex thermal noise using a caller-specified complex-baseband sample rate.
RealType quantizeAndScaleWindow(std::span< ComplexType > window)
Simulates ADC quantization and scales a window of complex I/Q samples.
void renderWindow(std::vector< ComplexType > &window, const RealType length, const RealType start, const RealType fracDelay, const std::span< const std::unique_ptr< serial::Response > > responses)
Renders a time-window of I/Q data from a collection of raw radar responses.
Header file for noise generator classes.
Defines the Parameters struct and provides methods for managing simulation parameters.
Classes for managing radar signal responses.
Header for receiver-side signal processing and rendering.
math::Vec3 max
RealType b
One complex Cartesian IQ sample scaled for VITA-style signed 16-bit transport.
Result of fixed-fullscale IQ scaling.
A simple thread pool implementation.