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