1
0
Fork 0
mirror of https://github.com/alemidev/scope-tui.git synced 2024-11-23 14:14:48 +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 {
channels = fmt.oscilloscope(&mut buffer, args.channels);
}
let mut trigger_offset = 0;
if app.cfg.triggering {
// TODO allow to customize channel to use for triggering
if let Some(ch) = channels.get(0) {
let mut discard = 0;
for i in 0..ch.len()-1 { // seek to first sample rising through threshold
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
if triggered(ch, i, app.cfg.threshold, app.cfg.depth, app.cfg.falling_edge) { // triggered
break;
} else {
discard += 1;
}
}
for ch in channels.iter_mut() {
let limit = if ch.len() < discard { ch.len() } else { discard };
*ch = ch[limit..].to_vec();
trigger_offset += 1;
}
}
// 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)>)>;
let mut measures : Vec<(String, Vec<(f64, f64)>)> = vec![];
let mut peaks : Vec<Vec<(f64, f64)>> = vec![];
// 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
if app.cfg.vectorscope {
measures = vec![];
for (i, chunk) in channels.chunks(2).enumerate() {
let mut tmp = vec![];
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()));
}
} else {
measures = vec![];
for (i, channel) in channels.iter().enumerate() {
let mut tmp = vec![];
let mut peak_up = 0.0;
let mut peak_down = 0.0;
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));
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![];
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));
}
let trigger_pt = [(0.0, app.cfg.threshold)];
let trigger_pt;
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;
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() {
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 {
self.cfg.scale = 0;
}
if self.cfg.depth < 1 {
self.cfg.depth = 1;
}
if self.cfg.vectorscope {
self.names.x = "left -".into();
self.names.y = "| right".into();
@ -238,12 +263,15 @@ impl From::<&crate::Args> for App {
scale: args.range,
width: args.buffer / (2 * args.channels as u32), // TODO also make bit depth customizable
triggering: args.triggering,
depth: args.check_depth,
threshold: args.threshold,
vectorscope: args.vectorscope,
references: !args.no_reference,
show_ui: !args.no_ui,
braille: !args.no_braille,
scatter: args.scatter,
falling_edge: args.falling_edge,
peaks: args.show_peaks,
pause: false,
};
@ -267,24 +295,35 @@ fn header(app: &App, samples: u32, framerate: u32) -> Table<'static> {
Row::new(
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!("{} plot", if app.cfg.scatter { "scatter" } else { "line" })),
Cell::from(format!("{}", if app.cfg.pause { "stop" } else { "live" } )),
Cell::from(format!("threshold {:.0} ^", app.cfg.threshold)),
Cell::from(format!("range +{}-", app.cfg.scale)),
Cell::from(format!("{}/{} samples", samples as u32, app.cfg.width)),
Cell::from(format!("{}",
if app.cfg.triggering {
format!(
"{} {:.0}{} trigger",
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!("{}{}", if app.cfg.peaks { "|" } else { " " }, if app.cfg.scatter { "***" } else { "---" })),
Cell::from(format!("{}", if app.cfg.pause { "||" } else { "|>" } )),
]
)
]
)
.style(Style::default().fg(Color::Cyan))
.widths(&[
Constraint::Percentage(32),
Constraint::Percentage(12),
Constraint::Percentage(12),
Constraint::Percentage(12),
Constraint::Percentage(12),
Constraint::Percentage(12),
Constraint::Percentage(35),
Constraint::Percentage(15),
Constraint::Percentage(15),
Constraint::Percentage(15),
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> {
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()?;
match event {
@ -340,7 +379,13 @@ fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> {
KeyCode::Char('b') => app.cfg.braille = !app.cfg.braille,
KeyCode::Char('h') => app.cfg.show_ui = !app.cfg.show_ui,
KeyCode::Char('r') => app.cfg.references = !app.cfg.references,
KeyCode::Char('e') => app.cfg.falling_edge = !app.cfg.falling_edge,
KeyCode::Char('t') => app.cfg.triggering = !app.cfg.triggering,
KeyCode::Char('p') => app.cfg.peaks = !app.cfg.peaks,
KeyCode::Char('=') => app.cfg.depth += 1,
KeyCode::Char('-') => app.cfg.depth -= 1,
KeyCode::Char('+') => app.cfg.depth += 10,
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,
@ -369,6 +414,34 @@ fn process_events(app: &mut App, args: &Args) -> Result<bool, io::Error> {
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>(
name: &'a str,
data: &'a [(f64, f64)],

View file

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

View file

@ -8,8 +8,9 @@ use tui::{
Terminal,
};
use crossterm::{
event::DisableMouseCapture, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
execute, terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen
},
};
use clap::Parser;
@ -49,6 +50,10 @@ pub struct Args {
#[arg(long, default_value_t = false)]
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)
#[arg(long, value_name = "NOTE")]
tune: Option<String>,
@ -73,6 +78,14 @@ pub struct Args {
#[arg(long, value_name = "VAL", default_value_t = 0.0)]
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
#[arg(long, default_value_t = false)]
no_reference: bool,
@ -115,7 +128,6 @@ fn main() -> Result<(), std::io::Error> {
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;