FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
if_resampler.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2//
3// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
4//
5// See the GNU GPLv2 LICENSE file in the FERS project root for more information.
6
7/**
8 * @file if_resampler.cpp
9 * @brief Internal FMCW IF rational resampler planning and streaming sink.
10 */
11
12#include "if_resampler.h"
13
14#include <algorithm>
15#include <cmath>
16#include <limits>
17#include <numeric>
18#include <stdexcept>
19#include <string>
20#include <utility>
21
22namespace
23{
25 constexpr RealType kHalfBandPassbandFraction = 0.20;
26
27 [[nodiscard]] RealType sinc(const RealType x) noexcept { return x == 0.0 ? 1.0 : std::sin(PI * x) / (PI * x); }
28
30 {
31 if (x < 0.0)
32 {
33 throw std::invalid_argument("Kaiser Bessel approximation requires x >= 0");
34 }
35
36 RealType t = x / 3.75;
37 if (t <= 1.0)
38 {
39 t *= t;
40 return 1.0 +
41 t * (3.5156229 + t * (3.0899424 + t * (1.2067492 + t * (0.2659732 + t * (0.0360768 + t * 0.0045813)))));
42 }
43
44 const RealType inv = 3.75 / x;
45 const RealType poly = 0.39894228 +
46 inv *
47 (0.01328592 +
48 inv *
49 (0.00225319 +
50 inv *
51 (-0.00157565 +
52 inv *
53 (0.00916281 +
54 inv * (-0.02057706 + inv * (0.02635537 + inv * (-0.01647633 + inv * 0.00392377)))))));
55 return poly * std::exp(x) / std::sqrt(x);
56 }
57
59 {
60 if (attenuation_db > 50.0)
61 {
62 return 0.1102 * (attenuation_db - 8.7);
63 }
64 if (attenuation_db >= 21.0)
65 {
66 return 0.5842 * std::pow(attenuation_db - 21.0, 0.4) + 0.07886 * (attenuation_db - 21.0);
67 }
68 return 0.0;
69 }
70
71 [[nodiscard]] RealType kaiserWindow(const std::size_t index, const std::size_t taps, const RealType beta,
73 {
74 if (taps <= 1)
75 {
76 return 1.0;
77 }
78 const RealType ratio = (2.0 * static_cast<RealType>(index)) / static_cast<RealType>(taps - 1) - 1.0;
79 const RealType arg = beta * std::sqrt(std::max<RealType>(0.0, 1.0 - ratio * ratio));
80 return besselI0(arg) / bessel_beta;
81 }
82
83 [[nodiscard]] std::size_t oddTapCount(std::size_t taps) noexcept
84 {
85 taps = std::max<std::size_t>(3, taps);
86 return (taps % 2 == 0) ? taps + 1 : taps;
87 }
88
89 [[nodiscard]] std::size_t estimateKaiserTapCount(const RealType transition_width_hz,
92 {
93 if (!std::isfinite(transition_width_hz) || transition_width_hz <= 0.0 ||
94 !std::isfinite(design_sample_rate_hz) || design_sample_rate_hz <= 0.0)
95 {
96 throw std::invalid_argument("IF resampler transition width and design sample rate must be positive");
97 }
98
99 const RealType normalized_transition = transition_width_hz / design_sample_rate_hz;
101 {
102 throw std::invalid_argument("IF resampler normalized transition width is outside (0, 0.5)");
103 }
104
106 const RealType estimate = std::max<RealType>(3.0, (attenuation_db - 8.0) / (2.285 * width_rad));
107 return oddTapCount(static_cast<std::size_t>(std::ceil(estimate)));
108 }
109
110 void validateLimit(const bool condition, const std::string& message)
111 {
112 if (!condition)
113 {
114 throw std::invalid_argument(message);
115 }
116 }
117
119 {
120 const RealType nyquist = request.output_sample_rate_hz * 0.5;
121 const RealType available = nyquist - request.filter_bandwidth_hz;
122 if (request.filter_transition_width_hz.has_value())
123 {
124 validateLimit(*request.filter_transition_width_hz > 0.0,
125 "IF resampler filter transition width must be positive");
126 validateLimit(*request.filter_transition_width_hz <= available,
127 "IF resampler filter transition width must fit below output Nyquist");
128 return *request.filter_transition_width_hz;
129 }
130 return available;
131 }
132
133 [[nodiscard]] std::size_t ceilBranchMacs(const std::size_t taps, const std::uint64_t up_factor,
134 const bool interpolates_adjacent_phase) noexcept
135 {
136 const auto up = std::max<std::uint64_t>(1, up_factor);
137 const auto branch = static_cast<std::size_t>((taps + up - 1) / up);
139 }
140
143 const std::uint64_t up_factor, const std::uint64_t down_factor, const RealType passband_hz,
144 const RealType requested_transition_hz, const RealType stopband_attenuation_db,
146 {
148 input_rate_hz * static_cast<RealType>(up_factor) / static_cast<RealType>(down_factor);
149 const RealType limiting_nyquist = 0.5 * std::min(input_rate_hz, output_rate_hz);
151 if (available_transition <= 0.0)
152 {
153 throw std::runtime_error("IF resampler passband does not fit stage Nyquist limit");
154 }
155
157 if (transition_hz <= 0.0)
158 {
159 throw std::runtime_error("IF resampler transition width is too narrow for stage");
160 }
161
163 ? input_rate_hz * static_cast<RealType>(up_factor)
165 const auto taps = estimateKaiserTapCount(transition_hz, design_rate_hz, stopband_attenuation_db);
166 if (taps > limits.max_taps_per_stage)
167 {
168 throw std::runtime_error(
169 "IF resampler FIR tap count " + std::to_string(taps) + " exceeds maximum " +
170 std::to_string(limits.max_taps_per_stage) +
171 "; increase if_sample_rate, reduce if_filter_bandwidth, or increase transition width");
172 }
173
175 stage.kind = kind;
176 stage.input_sample_rate_hz = input_rate_hz;
177 stage.output_sample_rate_hz = output_rate_hz;
178 stage.up_factor = up_factor;
179 stage.down_factor = down_factor;
180 stage.tap_count = taps;
181 stage.phase_refinement =
183 stage.phase_count = static_cast<std::size_t>(up_factor) * stage.phase_refinement;
184 stage.filter_bandwidth_hz = passband_hz;
185 stage.transition_width_hz = transition_hz;
186 stage.cutoff_hz = passband_hz + transition_hz * 0.5;
187 stage.stopband_attenuation_db = stopband_attenuation_db;
188 stage.group_delay_seconds = static_cast<RealType>(taps - 1) / (2.0 * design_rate_hz);
190 {
191 const auto half_band_branch_macs = (taps + 3) / 4;
192 stage.estimated_macs_per_stage_output = static_cast<RealType>(half_band_branch_macs);
193 }
194 else
195 {
196 stage.estimated_macs_per_stage_output =
197 static_cast<RealType>(ceilBranchMacs(taps, up_factor, stage.phase_refinement > 1));
198 }
199 return stage;
200 }
201
202 [[nodiscard]] RealType checkedRatio(const RealType output_sample_rate_hz, const RealType input_sample_rate_hz)
203 {
204 validateLimit(std::isfinite(input_sample_rate_hz) && input_sample_rate_hz > 0.0,
205 "IF resampler input sample rate must be positive");
206 validateLimit(std::isfinite(output_sample_rate_hz) && output_sample_rate_hz > 0.0,
207 "IF resampler output sample rate must be positive");
208 validateLimit(output_sample_rate_hz <= input_sample_rate_hz,
209 "IF resampler output sample rate must be <= input sample rate");
210 return output_sample_rate_hz / input_sample_rate_hz;
211 }
212
213 [[nodiscard]] std::size_t ceilOutputCount(const std::size_t input_count, const std::uint64_t up_factor,
214 const std::uint64_t down_factor) noexcept
215 {
216 const long double scaled = static_cast<long double>(input_count) * static_cast<long double>(up_factor) /
217 static_cast<long double>(down_factor);
218 return static_cast<std::size_t>(std::ceil(scaled - 1.0e-12L));
219 }
220
222 const std::size_t refinement) noexcept
223 {
224 return static_cast<RealType>(ceilBranchMacs(stage.tap_count, stage.up_factor, refinement > 0));
225 }
226
228 {
230 plan.phase_refinement = 1;
231 for (const auto& stage : plan.stages)
232 {
233 plan.estimated_macs_per_output_sample += stage.estimated_macs_per_stage_output;
234 plan.phase_refinement = std::max(plan.phase_refinement, stage.phase_refinement);
235 }
236 }
237
239 {
240 constexpr RealType kFractionalEpsilon = 1.0e-12;
242 {
243 return;
244 }
245
246 auto& stage = plan.stages.back();
247 const RealType base_cost = plan.estimated_macs_per_output_sample - stage.estimated_macs_per_stage_output;
248 std::size_t selected_refinement = 1;
249 for (std::size_t refinement = plan.limits.max_phase_refinement; refinement >= 1; --refinement)
250 {
253 {
255 break;
256 }
257 if (refinement == 1)
258 {
259 break;
260 }
261 }
262
263 stage.phase_refinement = selected_refinement;
264 stage.phase_count = static_cast<std::size_t>(stage.up_factor) * selected_refinement;
265 stage.estimated_macs_per_stage_output = fractionalStageMacs(stage, selected_refinement);
266
267 const RealType u0 = plan.fractional_output_delay_samples * static_cast<RealType>(stage.down_factor) *
268 static_cast<RealType>(selected_refinement);
269 const auto modulus = static_cast<RealType>(stage.phase_count);
270 stage.initial_input_advance = static_cast<std::int64_t>(std::floor(u0 / modulus));
271 const RealType residual = u0 - static_cast<RealType>(stage.initial_input_advance) * modulus;
272 stage.initial_phase_accumulator = static_cast<std::size_t>(std::floor(residual));
273 stage.initial_branch_interpolation_fraction = residual - static_cast<RealType>(stage.initial_phase_accumulator);
274 stage.applies_fractional_delay = true;
275
276 plan.fractional_phase_offset = plan.fractional_output_delay_samples * static_cast<RealType>(stage.down_factor);
277 plan.branch_interpolation_fraction = stage.initial_branch_interpolation_fraction;
280 0.5 / (plan.actual_output_sample_rate_hz * static_cast<RealType>(selected_refinement));
282
283 recomputePlanCost(plan);
285 {
286 throw std::runtime_error("IF resampler fractional-delay MAC cost " +
287 std::to_string(plan.estimated_macs_per_output_sample) + " exceeds maximum " +
288 std::to_string(plan.limits.max_macs_per_output_sample));
289 }
290 }
291}
292
293namespace fers_signal
294{
296 const RealType input_sample_rate_hz, const FmcwIfResamplerLimits& limits)
297 {
298 const RealType target = checkedRatio(output_sample_rate_hz, input_sample_rate_hz);
299 if (std::abs(target - 1.0) <= limits.ratio_relative_tolerance)
300 {
301 return {.numerator = 1,
302 .denominator = 1,
303 .requested_ratio = target,
304 .actual_ratio = 1.0,
305 .relative_error = std::abs(target - 1.0)};
306 }
307
308 std::uint64_t prev_num = 0;
309 std::uint64_t num = 1;
310 std::uint64_t prev_den = 1;
311 std::uint64_t den = 0;
312 RealType x = target;
313 FmcwIfRateRatio best{.requested_ratio = target};
314
315 for (std::size_t iter = 0; iter < 128; ++iter)
316 {
317 const auto a = static_cast<std::uint64_t>(std::floor(x));
318 const auto next_num = a * num + prev_num;
319 const auto next_den = a * den + prev_den;
320 if (next_den == 0 || next_den > limits.max_ratio_denominator)
321 {
322 break;
323 }
324
325 const RealType actual = static_cast<RealType>(next_num) / static_cast<RealType>(next_den);
326 const RealType relative_error = std::abs(actual - target) / target;
327 best = {.numerator = next_num,
328 .denominator = next_den,
329 .requested_ratio = target,
330 .actual_ratio = actual,
331 .relative_error = relative_error};
332 if (relative_error <= limits.ratio_relative_tolerance)
333 {
334 return best;
335 }
336
337 const RealType remainder = x - static_cast<RealType>(a);
338 if (std::abs(remainder) <= std::numeric_limits<RealType>::epsilon())
339 {
340 break;
341 }
342 x = 1.0 / remainder;
343 prev_num = num;
344 num = next_num;
345 prev_den = den;
346 den = next_den;
347 }
348
349 throw std::runtime_error("IF resampler cannot approximate output/input sample-rate ratio within tolerance; "
350 "increase max denominator or choose a representable IF sample rate");
351 }
352
354 {
355 const auto ratio =
356 approximateFmcwIfRateRatio(request.output_sample_rate_hz, request.input_sample_rate_hz, request.limits);
357 validateLimit(std::isfinite(request.filter_bandwidth_hz) && request.filter_bandwidth_hz > 0.0,
358 "IF resampler filter bandwidth must be positive");
359 validateLimit(request.filter_bandwidth_hz < request.output_sample_rate_hz * 0.5,
360 "IF resampler filter bandwidth must be below output Nyquist");
361 validateLimit(request.limits.max_taps_per_stage > 0, "IF resampler max taps per stage must be positive");
362 validateLimit(request.limits.max_macs_per_output_sample > 0.0, "IF resampler max MAC budget must be positive");
363 validateLimit(request.limits.max_phase_refinement > 0, "IF resampler phase refinement limit must be positive");
364
366
368 plan.input_sample_rate_hz = request.input_sample_rate_hz;
369 plan.requested_output_sample_rate_hz = request.output_sample_rate_hz;
370 plan.actual_output_sample_rate_hz = request.input_sample_rate_hz * ratio.actual_ratio;
371 plan.filter_bandwidth_hz = request.filter_bandwidth_hz;
373 plan.stopband_attenuation_db = request.stopband_attenuation_db;
374 plan.overall_ratio = ratio;
375 plan.limits = request.limits;
376
377 auto remaining_up = ratio.numerator;
378 auto remaining_down = ratio.denominator;
379 RealType current_rate_hz = request.input_sample_rate_hz;
380
381 while (remaining_down % 2 == 0 && remaining_down > 1)
382 {
383 if (request.filter_bandwidth_hz > current_rate_hz * kHalfBandPassbandFraction)
384 {
385 break;
386 }
387
388 const RealType available_transition = current_rate_hz * 0.25 - request.filter_bandwidth_hz;
389 if (available_transition <= 0.0)
390 {
391 break;
392 }
393
397 request.filter_bandwidth_hz, halfband_transition,
398 request.stopband_attenuation_db, request.limits));
399 current_rate_hz *= 0.5;
400 remaining_down /= 2;
401 }
402
404 {
406 remaining_down, request.filter_bandwidth_hz, transition_hz,
407 request.stopband_attenuation_db, request.limits));
408 }
409
410 for (const auto& stage : plan.stages)
411 {
412 plan.group_delay_seconds += stage.group_delay_seconds;
413 }
414 recomputePlanCost(plan);
415
416 if (plan.estimated_macs_per_output_sample > request.limits.max_macs_per_output_sample)
417 {
418 throw std::runtime_error(
419 "IF resampler estimated MAC cost " + std::to_string(plan.estimated_macs_per_output_sample) +
420 " exceeds maximum " + std::to_string(request.limits.max_macs_per_output_sample) +
421 "; increase if_sample_rate, reduce if_filter_bandwidth, or increase transition width");
422 }
423
425 plan.warmup_discard_samples = static_cast<std::uint64_t>(std::floor(plan.group_delay_output_samples));
428 plan.fractional_phase_offset = plan.fractional_output_delay_samples * static_cast<RealType>(ratio.denominator);
430 plan.fractional_phase_offset * static_cast<RealType>(plan.phase_refinement) -
431 std::floor(plan.fractional_phase_offset * static_cast<RealType>(plan.phase_refinement));
433 0.5 / (plan.actual_output_sample_rate_hz * static_cast<RealType>(plan.phase_refinement));
435 2.0 * PI * request.filter_bandwidth_hz * plan.estimated_timing_error_seconds;
437
438 return plan;
439 }
440
442 {
443 public:
444 explicit Stage(FmcwIfResamplerStagePlan plan) : _plan(plan) { buildPhaseTable(); }
445
447 {
448 std::vector<ComplexType> emitted;
449 std::size_t skipped_output_samples = 0;
450 };
451
452 void consume(std::span<const ComplexType> block)
453 {
454 if (_finished)
455 {
456 throw std::logic_error("Cannot consume IF resampler input after finish");
457 }
458 _input.insert(_input.end(), block.begin(), block.end());
459 _input_total += block.size();
460 process(false);
461 }
462
464 {
465 if (_finished)
466 {
467 throw std::logic_error("Cannot consume IF resampler input after finish");
468 }
469
471 if (input_count == 0)
472 {
473 return result;
474 }
475
476 const auto drained = std::min(input_count, zeroDrainInputCount());
477 if (drained > 0)
478 {
479 _input.resize(_input.size() + drained, ComplexType{0.0, 0.0});
480 _input_total += drained;
482 process(false);
483 result.emitted = take();
484 }
485
486 if (input_count == 0)
487 {
488 return result;
489 }
490
491 _input_total += input_count;
492 const auto available_outputs = availableOutputCount(_input_total);
493 if (available_outputs > _next_output_index)
494 {
495 result.skipped_output_samples = available_outputs - _next_output_index;
496 _next_output_index = available_outputs;
497 }
498 keepTrailingZeroHistory();
499 return result;
500 }
501
502 [[nodiscard]] std::vector<ComplexType> take()
503 {
504 std::vector<ComplexType> out;
505 out.swap(_pending);
506 return out;
507 }
508
509 [[nodiscard]] std::vector<ComplexType> finish()
510 {
511 if (!_finished)
512 {
513 _finished = true;
514 process(true);
515 _input.clear();
516 }
517 return take();
518 }
519
520 void reset()
521 {
522 _input.clear();
523 _pending.clear();
524 _input_total = 0;
525 _input_start_index = 0;
526 _next_output_index = 0;
527 _finished = false;
528 }
529
530 private:
531 [[nodiscard]] std::size_t zeroDrainInputCount() const noexcept { return _plan.tap_count + 2; }
532
533 [[nodiscard]] std::size_t zeroHistoryKeepCount() const noexcept { return _plan.tap_count + 2; }
534
535 void buildPhaseTable()
536 {
537 const std::size_t taps = _plan.tap_count;
538 const std::size_t phase_count = std::max<std::size_t>(1, _plan.phase_count);
539 _phase_table.assign(phase_count * taps, 0.0);
540
543 const RealType limiting_nyquist = 0.5 * std::min(_plan.input_sample_rate_hz, _plan.output_sample_rate_hz);
544 const RealType cutoff_hz = std::min(_plan.cutoff_hz, limiting_nyquist * 0.999);
545 const RealType cutoff = cutoff_hz / _plan.input_sample_rate_hz;
546 const RealType half = static_cast<RealType>(taps - 1) / 2.0;
547
548 for (std::size_t phase = 0; phase < phase_count; ++phase)
549 {
550 const RealType frac = static_cast<RealType>(phase) / static_cast<RealType>(phase_count);
551 RealType sum = 0.0;
552 for (std::size_t i = 0; i < taps; ++i)
553 {
554 const RealType offset = frac - (static_cast<RealType>(i) - half);
556 const RealType coeff = 2.0 * cutoff * sinc(2.0 * cutoff * offset) * window;
557 _phase_table[phase * taps + i] = coeff;
558 sum += coeff;
559 }
560 if (std::abs(sum) > std::numeric_limits<RealType>::epsilon())
561 {
562 for (std::size_t i = 0; i < taps; ++i)
563 {
564 _phase_table[phase * taps + i] /= sum;
565 }
566 }
567 }
568 }
569
570 [[nodiscard]] bool hasSamplesFor(const std::int64_t base_index, const std::int64_t input_offset,
571 const bool final) const noexcept
572 {
573 const auto half = static_cast<std::int64_t>((_plan.tap_count - 1) / 2);
574 const std::int64_t last_needed = base_index + input_offset + half;
575 return final || last_needed < static_cast<std::int64_t>(_input_total);
576 }
577
578 [[nodiscard]] ComplexType sampleAt(const std::int64_t global_index) const
579 {
580 if (global_index < 0 || global_index >= static_cast<std::int64_t>(_input_total))
581 {
582 return {0.0, 0.0};
583 }
584 const auto local = global_index - _input_start_index;
585 if (local < 0 || local >= static_cast<std::int64_t>(_input.size()))
586 {
587 throw std::logic_error("IF resampler discarded input still needed by FIR branch");
588 }
589 return _input[static_cast<std::size_t>(local)];
590 }
591
592 [[nodiscard]] ComplexType evaluateBranch(const std::size_t phase, const std::int64_t base_index) const
593 {
594 const auto half = static_cast<std::int64_t>((_plan.tap_count - 1) / 2);
595 const auto* coeffs = _phase_table.data() + phase * _plan.tap_count;
596 ComplexType result{0.0, 0.0};
597 for (std::size_t i = 0; i < _plan.tap_count; ++i)
598 {
599 const auto sample_index = base_index + static_cast<std::int64_t>(i) - half;
600 result += sampleAt(sample_index) * coeffs[i];
601 }
602 return result;
603 }
604
605 [[nodiscard]] long double stagePosition(const std::size_t output_index) const noexcept
606 {
607 const std::size_t phase_count = std::max<std::size_t>(1, _plan.phase_count);
608 const long double base = static_cast<long double>(output_index) *
609 static_cast<long double>(_plan.down_factor) / static_cast<long double>(_plan.up_factor);
610 if (!_plan.applies_fractional_delay)
611 {
612 return base;
613 }
614
615 const long double initial_phase = (static_cast<long double>(_plan.initial_phase_accumulator) +
616 static_cast<long double>(_plan.initial_branch_interpolation_fraction)) /
617 static_cast<long double>(phase_count);
618 return base + static_cast<long double>(_plan.initial_input_advance) + initial_phase;
619 }
620
621 struct BranchPosition
622 {
623 std::int64_t base_index = 0;
624 std::size_t lower_phase = 0;
625 std::size_t upper_phase = 0;
626 std::int64_t upper_offset = 0;
627 RealType mu = 0.0;
628 };
629
630 [[nodiscard]] BranchPosition branchPosition(const std::size_t output_index) const noexcept
631 {
632 const std::size_t phase_count = std::max<std::size_t>(1, _plan.phase_count);
633 const long double position = stagePosition(output_index);
634 const auto base_index = static_cast<std::int64_t>(std::floor(position + 1.0e-12L));
635 long double frac = position - static_cast<long double>(base_index);
636 if (frac < 0.0L && frac > -1.0e-12L)
637 {
638 frac = 0.0L;
639 }
640
641 const long double phase_position = frac * static_cast<long double>(phase_count);
642 auto lower_phase = static_cast<std::size_t>(std::floor(phase_position + 1.0e-12L));
643 auto mu = static_cast<RealType>(phase_position - static_cast<long double>(lower_phase));
644 if (lower_phase >= phase_count)
645 {
646 lower_phase = 0;
647 mu = 0.0;
648 }
649
650 std::size_t upper_phase = lower_phase + 1;
651 std::int64_t upper_offset = 0;
652 if (upper_phase == phase_count)
653 {
654 upper_phase = 0;
655 upper_offset = 1;
656 }
657
658 return {.base_index = base_index,
659 .lower_phase = lower_phase,
660 .upper_phase = upper_phase,
661 .upper_offset = upper_offset,
662 .mu = mu};
663 }
664
665 [[nodiscard]] bool outputHasSamplesForTotal(const std::size_t output_index,
666 const std::size_t input_total) const noexcept
667 {
668 const auto branch = branchPosition(output_index);
669 const auto half = static_cast<std::int64_t>((_plan.tap_count - 1) / 2);
670 const std::int64_t last_needed = branch.base_index + branch.upper_offset + half;
672 }
673
674 [[nodiscard]] std::size_t availableOutputCount(const std::size_t input_total) const noexcept
675 {
676 std::size_t low = 0;
677 std::size_t high = ceilOutputCount(input_total, _plan.up_factor, _plan.down_factor);
678 while (low < high)
679 {
680 const auto mid = low + (high - low) / 2;
681 if (outputHasSamplesForTotal(mid, input_total))
682 {
683 low = mid + 1;
684 }
685 else
686 {
687 high = mid;
688 }
689 }
690 return low;
691 }
692
693 void process(const bool final)
694 {
695 const std::size_t final_output_count =
696 final ? ceilOutputCount(_input_total, _plan.up_factor, _plan.down_factor) : 0;
697
698 while (!final || _next_output_index < final_output_count)
699 {
700 const auto branch = branchPosition(_next_output_index);
701 if (!hasSamplesFor(branch.base_index, 0, final) ||
702 !hasSamplesFor(branch.base_index, branch.upper_offset, final))
703 {
704 break;
705 }
706
707 const ComplexType lower = evaluateBranch(branch.lower_phase, branch.base_index);
708 const ComplexType upper = evaluateBranch(branch.upper_phase, branch.base_index + branch.upper_offset);
709 _pending.push_back((1.0 - branch.mu) * lower + branch.mu * upper);
710 ++_next_output_index;
711 }
712
713 discardUnneededInput();
714 }
715
716 void discardUnneededInput()
717 {
718 if (_input.empty())
719 {
720 return;
721 }
722
723 const long double next_position = stagePosition(_next_output_index);
724 const auto half = static_cast<std::int64_t>((_plan.tap_count - 1) / 2);
725 const auto first_needed = static_cast<std::int64_t>(std::floor(next_position + 1.0e-12L)) - half;
726 const auto new_start = std::max<std::int64_t>(_input_start_index, first_needed);
727 const auto discard = new_start - _input_start_index;
728 if (discard <= 0)
729 {
730 return;
731 }
732
733 const auto count = std::min<std::size_t>(static_cast<std::size_t>(discard), _input.size());
734 _input.erase(_input.begin(), _input.begin() + static_cast<std::ptrdiff_t>(count));
735 _input_start_index += static_cast<std::int64_t>(count);
736 }
737
738 void keepTrailingZeroHistory()
739 {
740 const auto keep = std::min<std::size_t>(zeroHistoryKeepCount(), _input_total);
741 _input.assign(keep, ComplexType{0.0, 0.0});
742 _input_start_index = static_cast<std::int64_t>(_input_total - keep);
743 }
744
745 FmcwIfResamplerStagePlan _plan;
746 std::vector<RealType> _phase_table;
747 std::vector<ComplexType> _input;
748 std::vector<ComplexType> _pending;
749 std::size_t _input_total = 0;
750 std::int64_t _input_start_index = 0;
751 std::size_t _next_output_index = 0;
752 bool _finished = false;
753 };
754
756 {
757 for (const auto& stage : _plan.stages)
758 {
759 _stages.push_back(std::make_unique<Stage>(stage));
760 }
761 }
762
766
768 {
769 if (_finished)
770 {
771 throw std::logic_error("Cannot consume IF resampler input after finish");
772 }
773
774 if (_stages.empty())
775 {
776 _output.insert(_output.end(), block.begin(), block.end());
777 return;
778 }
779
780 std::vector<ComplexType> current(block.begin(), block.end());
781 for (auto& stage : _stages)
782 {
783 stage->consume(current);
784 current = stage->take();
785 }
786 _output.insert(_output.end(), current.begin(), current.end());
787 }
788
790 {
791 if (_finished)
792 {
793 throw std::logic_error("Cannot consume IF resampler input after finish");
794 }
795
797 if (input_count == 0)
798 {
799 return result;
800 }
801
802 if (_stages.empty())
803 {
804 result.emitted = takeOutput();
805 result.skipped_output_samples = input_count;
806 return result;
807 }
808
809 std::vector<ComplexType> current;
810 std::size_t zero_count = input_count;
811 for (auto& stage : _stages)
812 {
813 if (!current.empty())
814 {
815 stage->consume(current);
816 current = stage->take();
817 }
818 if (zero_count == 0)
819 {
820 continue;
821 }
822
823 auto zero_result = stage->consumeZeroInput(zero_count);
824 current.insert(current.end(), zero_result.emitted.begin(), zero_result.emitted.end());
825 zero_count = zero_result.skipped_output_samples;
826 }
827
828 _output.insert(_output.end(), current.begin(), current.end());
829 result.emitted = takeOutput();
830 result.skipped_output_samples = zero_count;
831 return result;
832 }
833
834 std::vector<ComplexType> FmcwIfResamplingSink::takeOutput()
835 {
836 std::vector<ComplexType> out;
837 out.swap(_output);
838 return out;
839 }
840
841 std::vector<ComplexType> FmcwIfResamplingSink::finish()
842 {
843 if (_finished)
844 {
845 return takeOutput();
846 }
847 _finished = true;
848
849 if (_stages.empty())
850 {
851 return takeOutput();
852 }
853
854 std::vector<ComplexType> current;
855 for (std::size_t i = 0; i < _stages.size(); ++i)
856 {
857 if (i > 0)
858 {
859 _stages[i]->consume(current);
860 }
861 current = _stages[i]->finish();
862 }
863 _output.insert(_output.end(), current.begin(), current.end());
864 return takeOutput();
865 }
866
868 {
869 for (auto& stage : _stages)
870 {
871 stage->reset();
872 }
873 _output.clear();
874 _finished = false;
875 }
876}
Vec3 position
void consume(std::span< const ComplexType > block)
Stage(FmcwIfResamplerStagePlan plan)
std::vector< ComplexType > finish()
ZeroInputResult consumeZeroInput(std::size_t input_count)
std::vector< ComplexType > take()
std::vector< ComplexType > takeOutput()
FmcwIfResamplingSink(FmcwIfResamplerPlan plan)
FmcwIfZeroInputResult consumeZeroInput(std::size_t input_count)
std::vector< ComplexType > finish()
const FmcwIfResamplerPlan & plan() const noexcept
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
Internal FMCW IF rational resampler planning and streaming sink.
FmcwIfResamplerPlan planFmcwIfResampler(const FmcwIfResamplerRequest &request)
FmcwIfRateRatio approximateFmcwIfRateRatio(const RealType output_sample_rate_hz, const RealType input_sample_rate_hz, const FmcwIfResamplerLimits &limits)
math::Vec3 max
RealType a
std::vector< FmcwIfResamplerStagePlan > stages
FmcwIfResamplerLimits limits