Skip to main content

orion_sdr/
core.rs

1// Copyright (c) 2025-2026 G & R Associates LLC
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use num_complex::Complex32 as C32;
5
6#[derive(Debug, Clone, Copy, Default)]
7pub struct WorkReport {
8    pub in_read: usize,
9    pub out_written: usize,
10}
11
12pub trait Block {
13    type In;
14    type Out;
15    fn process(&mut self, input: &[Self::In], output: &mut [Self::Out]) -> WorkReport;
16
17    /// Optional specialized path; default forwards to `process`.
18    #[inline]
19    fn process_into(&mut self, input: &[Self::In], output: &mut [Self::Out]) -> WorkReport {
20        self.process(input, output)
21    }
22}
23
24/// Audio (f32) -> IQ (C32)
25pub struct AudioToIqChain<B: Block<In = f32, Out = C32>> {
26    block: B,
27    out: Vec<C32>,
28}
29impl<B: Block<In = f32, Out = C32>> AudioToIqChain<B> {
30    pub fn new(block: B) -> Self {
31        Self {
32            block,
33            out: Vec::new(),
34        }
35    }
36    /// Original convenience API (takes ownership of Vec)
37    pub fn process(&mut self, input: Vec<f32>) -> Vec<C32> {
38        self.process_ref(&input)
39    }
40    /// Borrowed input to avoid clone in hot paths.
41    pub fn process_ref(&mut self, input: &[f32]) -> Vec<C32> {
42        if self.out.len() < input.len() {
43            self.out.resize(input.len(), C32::new(0.0, 0.0));
44        }
45        let n = input.len();
46        let _wr = self.block.process_into(input, &mut self.out[..n]);
47        self.out[..n].to_vec()
48    }
49    /// Fully preallocated path.
50    pub fn process_into(&mut self, input: &[f32], output: &mut [C32]) -> WorkReport {
51        self.block.process_into(input, output)
52    }
53}
54
55/// IQ (C32) -> IQ (C32)
56pub struct IqToIqChain<B: Block<In = C32, Out = C32>> {
57    block: B,
58    out: Vec<C32>,
59}
60impl<B: Block<In = C32, Out = C32>> IqToIqChain<B> {
61    pub fn new(block: B) -> Self {
62        Self {
63            block,
64            out: Vec::new(),
65        }
66    }
67    pub fn process(&mut self, input: Vec<C32>) -> Vec<C32> {
68        self.process_ref(&input)
69    }
70    pub fn process_ref(&mut self, input: &[C32]) -> Vec<C32> {
71        if self.out.len() < input.len() {
72            self.out.resize(input.len(), C32::new(0.0, 0.0));
73        }
74        let n = input.len();
75        let _wr = self.block.process_into(input, &mut self.out[..n]);
76        self.out[..n].to_vec()
77    }
78    pub fn process_into(&mut self, input: &[C32], output: &mut [C32]) -> WorkReport {
79        self.block.process_into(input, output)
80    }
81}
82
83/// IQ (C32) -> Audio (f32)
84pub struct IqToAudioChain<B: Block<In = C32, Out = f32>> {
85    block: B,
86    out: Vec<f32>,
87}
88impl<B: Block<In = C32, Out = f32>> IqToAudioChain<B> {
89    pub fn new(block: B) -> Self {
90        Self {
91            block,
92            out: Vec::new(),
93        }
94    }
95    pub fn process(&mut self, input: Vec<C32>) -> Vec<f32> {
96        self.process_ref(&input)
97    }
98    pub fn process_ref(&mut self, input: &[C32]) -> Vec<f32> {
99        if self.out.len() < input.len() {
100            self.out.resize(input.len(), 0.0);
101        }
102        let n = input.len();
103        let _wr = self.block.process_into(input, &mut self.out[..n]);
104        self.out[..n].to_vec()
105    }
106    pub fn process_into(&mut self, input: &[C32], output: &mut [f32]) -> WorkReport {
107        self.block.process_into(input, output)
108    }
109}