Skip to main content

std_rs/snl/
femto.rs

1//! Femto amplifier gain control — native Rust port of `femto.st`.
2//!
3//! This implements the gain selection state machine for Femto low-noise
4//! current amplifiers. The amplifier gain is controlled by 4 digital bits
5//! (G1, G2, G3, NO) which map to a gain index via a lookup table.
6//!
7//! # Gain Lookup Table
8//!
9//! | Index | Bits (NO:G3:G2:G1) | Gain (V/A) |
10//! |-------|-------------------|------------|
11//! |   0   | 0:0:0:0           | 10^5       |
12//! |   1   | 0:0:0:1           | 10^6       |
13//! |   2   | 0:0:1:0           | 10^7       |
14//! |   3   | 0:0:1:1           | 10^8       |
15//! |   4   | 0:1:0:0           | 10^9       |
16//! |   5   | 0:1:0:1           | 10^10      |
17//! |   6   | 0:1:1:0           | 10^11      |
18//! |   7   | 0:1:1:1           | (unused)   |
19//! |   8   | 1:0:0:0           | 10^3       |
20//! |   9   | 1:0:0:1           | 10^4       |
21//! |  10   | 1:0:1:0           | 10^5       |
22//! |  11   | 1:0:1:1           | 10^6       |
23//! |  12   | 1:1:0:0           | 10^7       |
24//! |  13   | 1:1:0:1           | 10^8       |
25//! |  14   | 1:1:1:0           | 10^9       |
26//! |  15   | 1:1:1:1           | (unused)   |
27
28/// Gain power lookup: `gain = 10^POWERS[gainidx]`.
29/// Index 7 and 15 are unused (mapped to power 0).
30pub const POWERS: [u32; 16] = [5, 6, 7, 8, 9, 10, 11, 0, 3, 4, 5, 6, 7, 8, 9, 0];
31
32pub const MIN_GAIN: i32 = 0;
33pub const MAX_GAIN: i32 = 15;
34pub const UNUSED_GAIN: i32 = 7;
35
36/// Decode 4 gain bits into a gain index (0–15).
37pub fn bits_to_gain_index(g1: bool, g2: bool, g3: bool, no: bool) -> i32 {
38    let t0 = g1 as i32;
39    let t1 = g2 as i32;
40    let t2 = g3 as i32;
41    let tx = no as i32;
42    (tx << 3) | (t2 << 2) | (t1 << 1) | t0
43}
44
45/// Encode a gain index into 4 gain bits (g1, g2, g3, no).
46pub fn gain_index_to_bits(idx: i32) -> (bool, bool, bool, bool) {
47    let g1 = (idx & 1) != 0;
48    let g2 = (idx & 2) != 0;
49    let g3 = (idx & 4) != 0;
50    let no = (idx & 8) != 0;
51    (g1, g2, g3, no)
52}
53
54/// Validate a gain index. Returns `true` if valid.
55pub fn is_valid_gain_index(idx: i32) -> bool {
56    (MIN_GAIN..MAX_GAIN).contains(&idx) && idx != UNUSED_GAIN
57}
58
59/// Compute the gain value for a given index.
60pub fn gain_for_index(idx: i32) -> f64 {
61    if !(0..16).contains(&idx) {
62        return 0.0;
63    }
64    10.0_f64.powi(POWERS[idx as usize] as i32)
65}
66
67/// State of the femto amplifier state machine.
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum FemtoState {
70    Init,
71    Idle,
72    ChangeGain,
73    UpdateGain,
74}
75
76/// Femto amplifier gain controller.
77///
78/// Port of the `femto.st` SNL program as a pure Rust state machine.
79/// Call `step()` to advance the state machine when events occur.
80pub struct FemtoController {
81    pub state: FemtoState,
82    pub gain_index: i32,
83    pub current_gain: i32,
84    pub g1: bool,
85    pub g2: bool,
86    pub g3: bool,
87    pub no: bool,
88    pub gain: f64,
89}
90
91impl Default for FemtoController {
92    fn default() -> Self {
93        Self {
94            state: FemtoState::Init,
95            gain_index: 0,
96            current_gain: -1,
97            g1: false,
98            g2: false,
99            g3: false,
100            no: false,
101            gain: 0.0,
102        }
103    }
104}
105
106/// Events that drive the femto state machine.
107#[derive(Debug, Clone, Copy)]
108pub enum FemtoEvent {
109    /// Gain bits changed from hardware.
110    BitsChanged {
111        g1: bool,
112        g2: bool,
113        g3: bool,
114        no: bool,
115    },
116    /// User requested a specific gain index.
117    GainIndexChanged(i32),
118}
119
120impl FemtoController {
121    /// Advance the state machine by one step given an event.
122    /// Returns the new state.
123    pub fn step(&mut self, event: Option<FemtoEvent>) -> FemtoState {
124        match self.state {
125            FemtoState::Init => {
126                // Initialize from current bit state
127                if let Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) = event {
128                    self.g1 = g1;
129                    self.g2 = g2;
130                    self.g3 = g3;
131                    self.no = no;
132                }
133
134                let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
135                self.gain_index = if !self.g1 && !self.g2 && !self.g3 && !self.no {
136                    8 // Default to 1e3 when all bits are off
137                } else if !is_valid_gain_index(idx) {
138                    6 // Default to 1e11
139                } else {
140                    idx
141                };
142
143                self.current_gain = -1;
144                self.gain = gain_for_index(self.gain_index);
145                self.state = FemtoState::ChangeGain;
146            }
147
148            FemtoState::Idle => match event {
149                Some(FemtoEvent::BitsChanged { g1, g2, g3, no }) => {
150                    self.g1 = g1;
151                    self.g2 = g2;
152                    self.g3 = g3;
153                    self.no = no;
154                    self.state = FemtoState::UpdateGain;
155                }
156                Some(FemtoEvent::GainIndexChanged(idx)) => {
157                    self.gain_index = idx;
158                    self.state = FemtoState::ChangeGain;
159                }
160                None => {}
161            },
162
163            FemtoState::ChangeGain => {
164                // Validate requested gain
165                if self.current_gain == self.gain_index || !is_valid_gain_index(self.gain_index) {
166                    // Invalid or no change: revert to current gain
167                    if self.current_gain >= 0 && self.current_gain != self.gain_index {
168                        self.gain_index = self.current_gain;
169                        self.gain = gain_for_index(self.current_gain);
170                    }
171                    self.state = FemtoState::Idle;
172                } else {
173                    // Apply gain: set bits
174                    let (g1, g2, g3, no) = gain_index_to_bits(self.gain_index);
175                    self.g1 = g1;
176                    self.g2 = g2;
177                    self.g3 = g3;
178                    self.no = no;
179                    self.current_gain = self.gain_index;
180                    self.gain = gain_for_index(self.gain_index);
181                    self.state = FemtoState::Idle;
182                }
183            }
184
185            FemtoState::UpdateGain => {
186                // Bits changed externally: recompute gain index
187                let idx = bits_to_gain_index(self.g1, self.g2, self.g3, self.no);
188                self.gain_index = if !is_valid_gain_index(idx) { 6 } else { idx };
189                self.current_gain = self.gain_index;
190                self.gain = gain_for_index(self.gain_index);
191                self.state = FemtoState::Idle;
192            }
193        }
194
195        self.state
196    }
197}