1
0
Fork 0
mirror of https://github.com/alemidev/scope-tui.git synced 2024-11-14 18:59:19 +01:00

feat: multi channel? peaks? better triggering + ui?

idk old commits I forgot to push at the time
This commit is contained in:
əlemi 2023-06-29 16:09:36 +02:00
parent c6a88b13cd
commit c4960cf571
Signed by: alemi
GPG key ID: A4895B84D311642C
3 changed files with 138 additions and 50 deletions

View file

@ -70,36 +70,36 @@ pub fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<()
}, },
} }
if !app.cfg.pause { if !app.cfg.pause {
channels = fmt.oscilloscope(&mut buffer, args.channels); channels = fmt.oscilloscope(&mut buffer, args.channels);
}
if app.cfg.triggering { let mut trigger_offset = 0;
// TODO allow to customize channel to use for triggering
if let Some(ch) = channels.get(0) { if app.cfg.triggering {
let mut discard = 0; // TODO allow to customize channel to use for triggering
for i in 0..ch.len()-1 { // seek to first sample rising through threshold if let Some(ch) = channels.get(0) {
if ch[i] <= app.cfg.threshold && ch[i+1] > app.cfg.threshold { // triggered for i in 0..ch.len() { // seek to first sample rising through threshold
break; if triggered(ch, i, app.cfg.threshold, app.cfg.depth, app.cfg.falling_edge) { // triggered
} else { break;
discard += 1; } else {
} trigger_offset += 1;
}
for ch in channels.iter_mut() {
let limit = if ch.len() < discard { ch.len() } else { discard };
*ch = ch[limit..].to_vec();
} }
} }
// for ch in channels.iter_mut() {
// let limit = if ch.len() < discard { ch.len() } else { discard };
// *ch = ch[limit..].to_vec();
// }
} }
} }
let samples = channels.iter().map(|x| x.len()).max().unwrap_or(0); let mut measures : Vec<(String, Vec<(f64, f64)>)> = vec![];
let mut peaks : Vec<Vec<(f64, f64)>> = vec![];
let mut measures : Vec<(String, Vec<(f64, f64)>)>;
// This third buffer is kinda weird because of lifetimes on Datasets, TODO // This third buffer is kinda weird because of lifetimes on Datasets, TODO
// would be nice to make it more straight forward instead of this deep tuple magic // would be nice to make it more straight forward instead of this deep tuple magic
if app.cfg.vectorscope { if app.cfg.vectorscope {
measures = vec![];
for (i, chunk) in channels.chunks(2).enumerate() { for (i, chunk) in channels.chunks(2).enumerate() {
let mut tmp = vec![]; let mut tmp = vec![];
match chunk.len() { match chunk.len() {
@ -121,16 +121,28 @@ pub fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<()
measures.push((channel_name((i * 2) + 1, true), tmp[..pivot].to_vec())); measures.push((channel_name((i * 2) + 1, true), tmp[..pivot].to_vec()));
} }
} else { } else {
measures = vec![];
for (i, channel) in channels.iter().enumerate() { for (i, channel) in channels.iter().enumerate() {
let mut tmp = vec![]; let mut tmp = vec![];
let mut peak_up = 0.0;
let mut peak_down = 0.0;
for i in 0..channel.len() { for i in 0..channel.len() {
tmp.push((i as f64, channel[i])); if i >= trigger_offset {
tmp.push(((i - trigger_offset) as f64, channel[i]));
}
if channel[i] > peak_up {
peak_up = channel[i];
}
if channel[i] < peak_down {
peak_down = channel[i];
}
} }
measures.push((channel_name(i, false), tmp)); measures.push((channel_name(i, false), tmp));
peaks.push(vec![(0.0, peak_down), (0.0, peak_up)]);
} }
} }
let samples = measures.iter().map(|(_,x)| x.len()).max().unwrap_or(0);
let mut datasets = vec![]; let mut datasets = vec![];
if app.cfg.references { if app.cfg.references {
@ -138,10 +150,20 @@ pub fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<()
datasets.push(data_set("", &app.references.y, app.marker_type(), GraphType::Line, app.cfg.axis_color)); datasets.push(data_set("", &app.references.y, app.marker_type(), GraphType::Line, app.cfg.axis_color));
} }
let trigger_pt = [(0.0, app.cfg.threshold)]; let trigger_pt;
datasets.push(data_set("T", &trigger_pt, app.marker_type(), GraphType::Scatter, Color::Cyan)); if app.cfg.triggering {
trigger_pt = [(0.0, app.cfg.threshold)];
datasets.push(data_set("T", &trigger_pt, app.marker_type(), GraphType::Scatter, Color::Cyan));
}
let m_len = measures.len() - 1; let m_len = measures.len() - 1;
if !app.cfg.vectorscope && app.cfg.peaks {
for (i, pt) in peaks.iter().rev().enumerate() {
datasets.push(data_set("", pt, app.marker_type(), GraphType::Scatter, app.palette(m_len - i)));
}
}
for (i, (name, ds)) in measures.iter().rev().enumerate() { for (i, (name, ds)) in measures.iter().rev().enumerate() {
datasets.push(data_set(&name, ds, app.marker_type(), app.graph_type(), app.palette(m_len - i))); datasets.push(data_set(&name, ds, app.marker_type(), app.graph_type(), app.palette(m_len - i)));
} }
@ -191,6 +213,9 @@ impl App {
if self.cfg.scale < 0 { if self.cfg.scale < 0 {
self.cfg.scale = 0; self.cfg.scale = 0;
} }
if self.cfg.depth < 1 {
self.cfg.depth = 1;
}
if self.cfg.vectorscope { if self.cfg.vectorscope {
self.names.x = "left -".into(); self.names.x = "left -".into();
self.names.y = "| right".into(); self.names.y = "| right".into();
@ -238,12 +263,15 @@ impl From::<&crate::Args> for App {
scale: args.range, scale: args.range,
width: args.buffer / (2 * args.channels as u32), // TODO also make bit depth customizable width: args.buffer / (2 * args.channels as u32), // TODO also make bit depth customizable
triggering: args.triggering, triggering: args.triggering,
depth: args.check_depth,
threshold: args.threshold, threshold: args.threshold,
vectorscope: args.vectorscope, vectorscope: args.vectorscope,
references: !args.no_reference, references: !args.no_reference,
show_ui: !args.no_ui, show_ui: !args.no_ui,
braille: !args.no_braille, braille: !args.no_braille,
scatter: args.scatter, scatter: args.scatter,
falling_edge: args.falling_edge,
peaks: args.show_peaks,
pause: false, pause: false,
}; };
@ -267,24 +295,35 @@ fn header(app: &App, samples: u32, framerate: u32) -> Table<'static> {
Row::new( Row::new(
vec![ vec![
Cell::from(format!("TUI {}", if app.cfg.vectorscope { "Vectorscope" } else { "Oscilloscope" })).style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)), Cell::from(format!("TUI {}", if app.cfg.vectorscope { "Vectorscope" } else { "Oscilloscope" })).style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)),
Cell::from(format!("{} plot", if app.cfg.scatter { "scatter" } else { "line" })), Cell::from(format!("{}",
Cell::from(format!("{}", if app.cfg.pause { "stop" } else { "live" } )), if app.cfg.triggering {
Cell::from(format!("threshold {:.0} ^", app.cfg.threshold)), format!(
Cell::from(format!("range +{}-", app.cfg.scale)), "{} {:.0}{} trigger",
Cell::from(format!("{}/{} samples", samples as u32, app.cfg.width)), if app.cfg.falling_edge { "v" } else { "^" },
app.cfg.threshold,
if app.cfg.depth > 1 { format!(":{}", app.cfg.depth) } else { "".into() },
)
} else {
"live".into()
}
)),
Cell::from(format!("-{}+ range", app.cfg.scale)),
Cell::from(format!("{}/{} sample", app.cfg.width, samples as u32)),
Cell::from(format!("{}fps", framerate)), Cell::from(format!("{}fps", framerate)),
Cell::from(format!("{}{}", if app.cfg.peaks { "|" } else { " " }, if app.cfg.scatter { "***" } else { "---" })),
Cell::from(format!("{}", if app.cfg.pause { "||" } else { "|>" } )),
] ]
) )
] ]
) )
.style(Style::default().fg(Color::Cyan)) .style(Style::default().fg(Color::Cyan))
.widths(&[ .widths(&[
Constraint::Percentage(32), Constraint::Percentage(35),
Constraint::Percentage(12), Constraint::Percentage(15),
Constraint::Percentage(12), Constraint::Percentage(15),
Constraint::Percentage(12), Constraint::Percentage(15),
Constraint::Percentage(12), Constraint::Percentage(6),
Constraint::Percentage(12), Constraint::Percentage(6),
Constraint::Percentage(6) Constraint::Percentage(6)
]) ])
} }
@ -292,7 +331,7 @@ fn header(app: &App, samples: u32, framerate: u32) -> Table<'static> {
fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> { fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> {
let mut quit = false; let mut quit = false;
while event::poll(Duration::from_millis(0))? { // process all enqueued events if event::poll(Duration::from_millis(0))? { // process all enqueued events
let event = event::read()?; let event = event::read()?;
match event { match event {
@ -334,19 +373,25 @@ fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> {
_ => { _ => {
match key.code { match key.code {
KeyCode::Char('q') => quit = true, KeyCode::Char('q') => quit = true,
KeyCode::Char(' ') => app.cfg.pause = !app.cfg.pause, KeyCode::Char(' ') => app.cfg.pause = !app.cfg.pause,
KeyCode::Char('v') => app.cfg.vectorscope = !app.cfg.vectorscope, KeyCode::Char('v') => app.cfg.vectorscope = !app.cfg.vectorscope,
KeyCode::Char('s') => app.cfg.scatter = !app.cfg.scatter, KeyCode::Char('s') => app.cfg.scatter = !app.cfg.scatter,
KeyCode::Char('b') => app.cfg.braille = !app.cfg.braille, KeyCode::Char('b') => app.cfg.braille = !app.cfg.braille,
KeyCode::Char('h') => app.cfg.show_ui = !app.cfg.show_ui, KeyCode::Char('h') => app.cfg.show_ui = !app.cfg.show_ui,
KeyCode::Char('r') => app.cfg.references = !app.cfg.references, KeyCode::Char('r') => app.cfg.references = !app.cfg.references,
KeyCode::Char('t') => app.cfg.triggering = !app.cfg.triggering, KeyCode::Char('e') => app.cfg.falling_edge = !app.cfg.falling_edge,
KeyCode::Up => app.cfg.scale -= 250, // inverted to act as zoom KeyCode::Char('t') => app.cfg.triggering = !app.cfg.triggering,
KeyCode::Down => app.cfg.scale += 250, // inverted to act as zoom KeyCode::Char('p') => app.cfg.peaks = !app.cfg.peaks,
KeyCode::Right => app.cfg.width += 25, KeyCode::Char('=') => app.cfg.depth += 1,
KeyCode::Left => app.cfg.width -= 25, KeyCode::Char('-') => app.cfg.depth -= 1,
KeyCode::PageUp => app.cfg.threshold += 250.0, KeyCode::Char('+') => app.cfg.depth += 10,
KeyCode::PageDown => app.cfg.threshold -= 250.0, KeyCode::Char('_') => app.cfg.depth -= 10,
KeyCode::Up => app.cfg.scale -= 250, // inverted to act as zoom
KeyCode::Down => app.cfg.scale += 250, // inverted to act as zoom
KeyCode::Right => app.cfg.width += 25,
KeyCode::Left => app.cfg.width -= 25,
KeyCode::PageUp => app.cfg.threshold += 250.0,
KeyCode::PageDown => app.cfg.threshold -= 250.0,
KeyCode::Tab => { // only reset "zoom" KeyCode::Tab => { // only reset "zoom"
app.cfg.width = args.buffer / (args.channels as u32 * 2); // TODO ... app.cfg.width = args.buffer / (args.channels as u32 * 2); // TODO ...
app.cfg.scale = args.range; app.cfg.scale = args.range;
@ -369,6 +414,34 @@ fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> {
Ok(quit) Ok(quit)
} }
// TODO can this be made nicer?
fn triggered(data: &[f64], index: usize, threshold: f64, depth: u32, falling_edge:bool) -> bool {
if data.len() < index + (1+depth as usize) { return false; }
if falling_edge {
if data[index] >= threshold {
for i in 1..=depth as usize {
if data[index+i] >= threshold {
return false;
}
}
return true;
} else {
return false;
}
} else {
if data[index] <= threshold {
for i in 1..=depth as usize {
if data[index+i] <= threshold {
return false;
}
}
return true;
} else {
return false;
}
}
}
fn data_set<'a>( fn data_set<'a>(
name: &'a str, name: &'a str,
data: &'a [(f64, f64)], data: &'a [(f64, f64)],

View file

@ -47,9 +47,12 @@ pub struct AppConfig {
pub vectorscope: bool, pub vectorscope: bool,
pub references: bool, pub references: bool,
pub show_ui: bool, pub show_ui: bool,
pub peaks: bool,
pub triggering: bool, pub triggering: bool,
pub threshold: f64, pub threshold: f64,
pub depth: u32,
pub falling_edge: bool,
pub scatter: bool, pub scatter: bool,
pub braille: bool, pub braille: bool,

View file

@ -8,8 +8,9 @@ use tui::{
Terminal, Terminal,
}; };
use crossterm::{ use crossterm::{
event::DisableMouseCapture, execute, execute, terminal::{
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen
},
}; };
use clap::Parser; use clap::Parser;
@ -49,6 +50,10 @@ pub struct Args {
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
vectorscope: bool, vectorscope: bool,
/// Show peaks for each channel as dots
#[arg(long, default_value_t = false)]
show_peaks: bool,
/// Tune buffer size to be in tune with given note (overrides buffer option) /// Tune buffer size to be in tune with given note (overrides buffer option)
#[arg(long, value_name = "NOTE")] #[arg(long, value_name = "NOTE")]
tune: Option<String>, tune: Option<String>,
@ -73,6 +78,14 @@ pub struct Args {
#[arg(long, value_name = "VAL", default_value_t = 0.0)] #[arg(long, value_name = "VAL", default_value_t = 0.0)]
threshold: f64, threshold: f64,
/// Length of trigger check in samples
#[arg(long, value_name = "SMPL", default_value_t = 1)]
check_depth: u32,
/// Trigger upon falling edge instead of rising
#[arg(long, default_value_t = false)]
falling_edge: bool,
/// Don't draw reference line /// Don't draw reference line
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
no_reference: bool, no_reference: bool,
@ -115,7 +128,6 @@ fn main() -> Result<(), std::io::Error> {
execute!( execute!(
terminal.backend_mut(), terminal.backend_mut(),
LeaveAlternateScreen, LeaveAlternateScreen,
DisableMouseCapture
)?; )?;
terminal.show_cursor()?; terminal.show_cursor()?;