1use crossterm::event::{Event, KeyCode, KeyModifiers};
2use ratatui::{
3 style::Style,
4 text::Span,
5 widgets::{Axis, GraphType},
6};
7
8use crate::input::Matrix;
9
10use super::{update_value_f, update_value_i, DataSet, Dimension, DisplayMode, GraphConfig};
11
12pub struct Oscilloscope {
13 pub triggering: bool,
14 pub falling_edge: bool,
15 pub threshold: f64,
16 pub depth: u32,
17 pub peaks: bool,
18}
19
20impl Default for Oscilloscope {
21 fn default() -> Self {
22 Oscilloscope {
23 triggering: false,
24 falling_edge: false,
25 threshold: 0.0,
26 depth: 0,
27 peaks: true,
28 }
29 }
30}
31
32impl DisplayMode for Oscilloscope {
33 fn mode_str(&self) -> &'static str {
34 "oscillo"
35 }
36
37 fn channel_name(&self, index: usize) -> String {
38 match index {
39 0 => "L".into(),
40 1 => "R".into(),
41 _ => format!("{}", index),
42 }
43 }
44
45 fn header(&self, _: &GraphConfig) -> String {
46 if self.triggering {
47 format!(
48 "{} {:.0}{} trigger",
49 if self.falling_edge { "v" } else { "^" },
50 self.threshold,
51 if self.depth > 1 {
52 format!(":{}", self.depth)
53 } else {
54 "".into()
55 },
56 )
57 } else {
58 "live".into()
59 }
60 }
61
62 fn axis(&'_ self, cfg: &GraphConfig, dimension: Dimension) -> Axis<'_> {
63 let (name, bounds) = match dimension {
64 Dimension::X => ("time -", [0.0, cfg.samples as f64]),
65 Dimension::Y => ("| amplitude", [-cfg.scale, cfg.scale]),
66 };
67 let mut a = Axis::default();
68 if cfg.show_ui {
69 a = a.title(Span::styled(name, Style::default().fg(cfg.labels_color)));
71 }
72 a.style(Style::default().fg(cfg.axis_color)).bounds(bounds)
73 }
74
75 fn references(&self, cfg: &GraphConfig) -> Vec<DataSet> {
76 vec![DataSet::new(
77 None,
78 vec![(0.0, 0.0), (cfg.samples as f64, 0.0)],
79 cfg.marker_type,
80 GraphType::Line,
81 cfg.axis_color,
82 )]
83 }
84
85 fn process(&mut self, cfg: &GraphConfig, data: &Matrix<f64>) -> Vec<DataSet> {
86 let mut out = Vec::new();
87
88 let mut trigger_offset = 0;
89 if self.depth == 0 {
90 self.depth = 1
91 }
92 if self.triggering {
93 for i in 0..data[0].len() {
94 if triggered(&data[0], i, self.threshold, self.depth, self.falling_edge) {
95 break;
97 }
98 trigger_offset += 1;
99 }
100 }
101
102 if self.triggering {
103 out.push(DataSet::new(
104 Some("T".into()),
105 vec![(0.0, self.threshold)],
106 cfg.marker_type,
107 GraphType::Scatter,
108 cfg.labels_color,
109 ));
110 }
111
112 for (n, channel) in data.iter().enumerate().rev() {
113 let (mut min, mut max) = (0.0, 0.0);
114 let mut tmp = Vec::new();
115 for (i, sample) in channel.iter().enumerate() {
116 if *sample < min {
117 min = *sample
118 };
119 if *sample > max {
120 max = *sample
121 };
122 if i >= trigger_offset {
123 tmp.push(((i - trigger_offset) as f64, *sample));
124 }
125 }
126
127 if self.peaks {
128 out.push(DataSet::new(
129 None,
130 vec![(0.0, min), (0.0, max)],
131 cfg.marker_type,
132 GraphType::Scatter,
133 cfg.palette(n),
134 ))
135 }
136
137 out.push(DataSet::new(
138 Some(self.channel_name(n)),
139 tmp,
140 cfg.marker_type,
141 if cfg.scatter {
142 GraphType::Scatter
143 } else {
144 GraphType::Line
145 },
146 cfg.palette(n),
147 ));
148 }
149
150 out
151 }
152
153 fn handle(&mut self, event: Event) {
154 if let Event::Key(key) = event {
155 let magnitude = match key.modifiers {
156 KeyModifiers::SHIFT => 10.0,
157 KeyModifiers::CONTROL => 5.0,
158 KeyModifiers::ALT => 0.2,
159 _ => 1.0,
160 };
161 match key.code {
162 KeyCode::PageUp => {
163 update_value_f(&mut self.threshold, 250.0, magnitude, 0.0..32768.0)
164 }
165 KeyCode::PageDown => {
166 update_value_f(&mut self.threshold, -250.0, magnitude, 0.0..32768.0)
167 }
168 KeyCode::Char('t') => self.triggering = !self.triggering,
169 KeyCode::Char('e') => self.falling_edge = !self.falling_edge,
170 KeyCode::Char('p') => self.peaks = !self.peaks,
171 KeyCode::Char('=') => update_value_i(&mut self.depth, true, 1, 1.0, 1..65535),
172 KeyCode::Char('-') => update_value_i(&mut self.depth, false, 1, 1.0, 1..65535),
173 KeyCode::Char('+') => update_value_i(&mut self.depth, true, 10, 1.0, 1..65535),
174 KeyCode::Char('_') => update_value_i(&mut self.depth, false, 10, 1.0, 1..65535),
175 KeyCode::Esc => {
176 self.triggering = false;
177 }
178 _ => {}
179 }
180 }
181 }
182}
183
184#[allow(clippy::collapsible_else_if)] fn triggered(data: &[f64], index: usize, threshold: f64, depth: u32, falling_edge: bool) -> bool {
186 if data.len() < index + (1 + depth as usize) {
187 return false;
188 }
189 if falling_edge {
190 if data[index] >= threshold {
191 for i in 1..=depth as usize {
192 if data[index + i] >= threshold {
193 return false;
194 }
195 }
196 true
197 } else {
198 false
199 }
200 } else {
201 if data[index] <= threshold {
202 for i in 1..=depth as usize {
203 if data[index + i] <= threshold {
204 return false;
205 }
206 }
207 true
208 } else {
209 false
210 }
211 }
212}