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

feat: small refactor, allow editing paused view

This commit is contained in:
əlemi 2022-12-26 02:18:24 +01:00
parent ed246e314f
commit e83a326c89
No known key found for this signature in database
GPG key ID: BBCBFE5D7244634E
2 changed files with 159 additions and 126 deletions

View file

@ -24,6 +24,20 @@ impl Default for ChartBounds {
} }
} }
pub struct ChartReferences {
pub x: Vec<(f64, f64)>,
pub y: Vec<(f64, f64)>,
}
impl Default for ChartReferences {
fn default() -> Self {
ChartReferences {
x: vec![(0.0, 0.0), (0.0, 1.0)],
y: vec![(0.5, 1.0), (0.5, -1.0)]
}
}
}
pub struct AppConfig { pub struct AppConfig {
pub title: String, pub title: String,
pub primary_color: Color, pub primary_color: Color,
@ -36,51 +50,36 @@ pub struct AppConfig {
pub references: bool, pub references: bool,
pub marker_type: symbols::Marker, pub marker_type: symbols::Marker,
graph_type: GraphType, pub graph_type: GraphType,
}
pub struct App {
pub cfg: AppConfig,
pub references: ChartReferences,
bounds: ChartBounds, bounds: ChartBounds,
names: ChartNames, names: ChartNames,
} }
impl AppConfig { impl App {
fn update_values(&mut self) { fn update_values(&mut self) {
if self.vectorscope { if self.cfg.vectorscope {
self.bounds.x = [-(self.scale as f64), self.scale as f64];
self.bounds.y = [-(self.scale as f64), self.scale as f64];
self.names.x = "- left".into(); self.names.x = "- left".into();
self.names.y = "| right".into(); self.names.y = "| right".into();
self.bounds.x = [-(self.cfg.scale as f64), self.cfg.scale as f64];
self.bounds.y = [-(self.cfg.scale as f64), self.cfg.scale as f64];
self.references.x = vec![(-(self.cfg.scale as f64), 0.0), (self.cfg.scale as f64, 0.0)];
self.references.y = vec![(0.0, -(self.cfg.scale as f64)), (0.0, self.cfg.scale as f64)];
} else { } else {
// it makes no sense to show self.scale on the left but it's kinda nice
self.names.x = "- time".into(); self.names.x = "- time".into();
self.names.y = "| amplitude".into(); self.names.y = "| amplitude".into();
self.bounds.x = [0.0, self.width as f64]; self.bounds.x = [0.0, self.cfg.width as f64];
self.bounds.y = [-(self.scale as f64), self.scale as f64]; self.bounds.y = [-(self.cfg.scale as f64), self.cfg.scale as f64];
self.references.x = vec![(0.0, 0.0), (self.cfg.width as f64, 0.0)];
let half_width = self.cfg.width as f64 / 2.0;
self.references.y = vec![(half_width, -(self.cfg.scale as f64)), (half_width, self.cfg.scale as f64)];
} }
} }
pub fn vectorscope(&self) -> bool {
self.vectorscope
}
pub fn scale(&self) -> u32 {
self.scale
}
pub fn width(&self) -> u32 {
self.width
}
pub fn scatter(&self) -> bool {
match self.graph_type {
GraphType::Scatter => true,
_ => false,
}
}
pub fn graph_type(&self) -> GraphType {
self.graph_type
}
pub fn bounds(&self, axis: Axis) -> [f64;2] { pub fn bounds(&self, axis: Axis) -> [f64;2] {
match axis { match axis {
Axis::X => self.bounds.x, Axis::X => self.bounds.x,
@ -95,30 +94,69 @@ impl AppConfig {
} }
} }
pub fn vectorscope(&self) -> bool {
self.cfg.vectorscope
}
pub fn scale(&self) -> u32 {
self.cfg.scale
}
pub fn width(&self) -> u32 {
self.cfg.width
}
pub fn scatter(&self) -> bool {
match self.cfg.graph_type {
GraphType::Scatter => true,
_ => false,
}
}
// pub fn references(&self) -> Vec<Dataset> {
// vec![
// Dataset::default()
// .name("")
// .marker(self.cfg.marker_type)
// .graph_type(GraphType::Line)
// .style(Style::default().fg(self.cfg.axis_color))
// .data(&self.references.x),
// Dataset::default()
// .name("")
// .marker(self.cfg.marker_type)
// .graph_type(GraphType::Line)
// .style(Style::default().fg(self.cfg.axis_color))
// .data(&self.references.y),
// ]
// }
pub fn graph_type(&self) -> GraphType {
self.cfg.graph_type
}
pub fn set_vectorscope(&mut self, vectorscope: bool) { pub fn set_vectorscope(&mut self, vectorscope: bool) {
self.vectorscope = vectorscope; self.cfg.vectorscope = vectorscope;
self.update_values(); self.update_values();
} }
pub fn update_scale(&mut self, increment: i32) { pub fn update_scale(&mut self, increment: i32) {
if increment > 0 || increment.abs() < self.scale as i32 { if increment > 0 || increment.abs() < self.cfg.scale as i32 {
self.scale = ((self.scale as i32) + increment) as u32; self.cfg.scale = ((self.cfg.scale as i32) + increment) as u32;
self.update_values(); self.update_values();
} }
} }
pub fn set_scatter(&mut self, scatter: bool) { pub fn set_scatter(&mut self, scatter: bool) {
self.graph_type = if scatter { GraphType::Scatter } else { GraphType::Line }; self.cfg.graph_type = if scatter { GraphType::Scatter } else { GraphType::Line };
} }
} }
impl From::<&crate::Args> for App {
impl From::<&crate::Args> for AppConfig {
fn from(args: &crate::Args) -> Self { fn from(args: &crate::Args) -> Self {
let marker_type = if args.no_braille { symbols::Marker::Dot } else { symbols::Marker::Braille }; let marker_type = if args.no_braille { symbols::Marker::Dot } else { symbols::Marker::Braille };
let graph_type = if args.scatter { GraphType::Scatter } else { GraphType::Line }; let graph_type = if args.scatter { GraphType::Scatter } else { GraphType::Line };
let mut cfg = AppConfig { let cfg = AppConfig {
title: "TUI Oscilloscope -- <me@alemi.dev>".into(), title: "TUI Oscilloscope -- <me@alemi.dev>".into(),
primary_color: Color::Red, primary_color: Color::Red,
secondary_color: Color::Yellow, secondary_color: Color::Yellow,
@ -127,13 +165,18 @@ impl From::<&crate::Args> for AppConfig {
width: args.buffer / 4, // TODO It's 4 because 2 channels and 2 bytes per sample! width: args.buffer / 4, // TODO It's 4 because 2 channels and 2 bytes per sample!
vectorscope: args.vectorscope, vectorscope: args.vectorscope,
references: !args.no_reference, references: !args.no_reference,
bounds: ChartBounds::default(),
names: ChartNames::default(),
marker_type, graph_type, marker_type, graph_type,
}; };
cfg.update_values(); let mut app = App {
cfg,
references: ChartReferences::default(),
bounds: ChartBounds::default(),
names: ChartNames::default(),
};
cfg app.update_values();
app
} }
} }

View file

@ -21,7 +21,8 @@ use libpulse_binding::sample::{Spec, Format};
use clap::Parser; use clap::Parser;
use parser::{SampleParser, Signed16PCM}; use parser::{SampleParser, Signed16PCM};
use app::AppConfig;
use crate::app::App;
/// A simple oscilloscope/vectorscope for your terminal /// A simple oscilloscope/vectorscope for your terminal
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
@ -38,6 +39,14 @@ struct Args {
#[arg(short, long, default_value_t = 20000)] #[arg(short, long, default_value_t = 20000)]
range: u32, range: u32,
/// Use vintage looking scatter mode instead of line mode
#[arg(long, default_value_t = false)]
scatter: bool,
/// Combine left and right channels into vectorscope view
#[arg(long, default_value_t = false)]
vectorscope: bool,
/// Sample rate to use /// Sample rate to use
#[arg(long, default_value_t = 44100)] #[arg(long, default_value_t = 44100)]
sample_rate: u32, sample_rate: u32,
@ -49,14 +58,6 @@ struct Args {
/// Don't use braille dots for drawing lines /// Don't use braille dots for drawing lines
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
no_braille: bool, no_braille: bool,
/// Use vintage looking scatter mode instead of line mode
#[arg(long, default_value_t = false)]
scatter: bool,
/// Combine left and right channels into vectorscope view
#[arg(long, default_value_t = false)]
vectorscope: bool,
} }
fn poll_event() -> Result<Option<Event>, std::io::Error> { fn poll_event() -> Result<Option<Event>, std::io::Error> {
@ -83,7 +84,7 @@ fn data_set<'a>(
} }
fn main() -> Result<(), io::Error> { fn main() -> Result<(), io::Error> {
let args = Args::parse(); let mut args = Args::parse();
// setup terminal // setup terminal
enable_raw_mode()?; enable_raw_mode()?;
@ -116,7 +117,7 @@ fn main() -> Result<(), io::Error> {
fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io::Error> { fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
// prepare globals // prepare globals
let mut buffer : Vec<u8> = vec![0; args.buffer as usize]; let mut buffer : Vec<u8> = vec![0; args.buffer as usize];
let mut cfg = AppConfig::from(&args); let mut app = App::from(&args);
let fmt = Signed16PCM{}; // TODO some way to choose this? let fmt = Signed16PCM{}; // TODO some way to choose this?
let mut pause = false; let mut pause = false;
@ -157,7 +158,9 @@ fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io
let mut fps = 0; let mut fps = 0;
let mut framerate = 0; let mut framerate = 0;
let mut last_poll = SystemTime::now(); let mut last_poll = Instant::now();
let (mut left, mut right) = (vec![], vec![]);
let mut merged = vec![];
loop { loop {
match s.read(&mut buffer) { match s.read(&mut buffer) {
@ -168,79 +171,66 @@ fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io
}, },
} }
let mut datasets = vec![];
if !pause { if !pause {
let mut datasets = vec![]; if app.vectorscope() {
let (left, right);
let merged;
let mut ref_data_x = Vec::new();
let mut ref_data_y = Vec::new();
if cfg.references {
// TODO find a proper way to put these references...
if cfg.vectorscope() {
for x in -(cfg.scale() as i64)..(cfg.scale() as i64) {
ref_data_x.push((x as f64, 0 as f64));
ref_data_y.push((0 as f64, x as f64));
}
} else {
for x in 0..cfg.width() {
ref_data_x.push((x as f64, 0 as f64));
}
for y in -(cfg.scale() as i64)..(cfg.scale() as i64) {
ref_data_y.push(((cfg.width() as f64) / 2.0, y as f64));
}
}
datasets.push(data_set("X", &ref_data_x, cfg.marker_type, GraphType::Line, cfg.axis_color));
datasets.push(data_set("Y", &ref_data_y, cfg.marker_type, GraphType::Line, cfg.axis_color));
}
if cfg.vectorscope() {
merged = fmt.vectorscope(&mut buffer); merged = fmt.vectorscope(&mut buffer);
let pivot = merged.len() / 2;
datasets.push(data_set("1", &merged[..pivot], cfg.marker_type, cfg.graph_type(), cfg.secondary_color));
datasets.push(data_set("2", &merged[pivot..], cfg.marker_type, cfg.graph_type(), cfg.primary_color));
} else { } else {
(left, right) = fmt.oscilloscope(&mut buffer); (left, right) = fmt.oscilloscope(&mut buffer);
datasets.push(data_set("R", &right, cfg.marker_type, cfg.graph_type(), cfg.secondary_color));
datasets.push(data_set("L", &left, cfg.marker_type, cfg.graph_type(), cfg.primary_color));
fps += 1;
if let Ok(d) = last_poll.elapsed() {
if d.as_secs() >= 1 {
framerate = fps;
fps = 0;
last_poll = SystemTime::now();
}
} }
terminal.draw(|f| {
let size = f.size();
let chart = Chart::new(datasets)
.block(Block::default().title(
Span::styled(
format!(
"TUI {} <me@alemi.dev> -- {} mode -- range {} -- {} samples -- {:.1} kHz -- {} fps",
if cfg.vectorscope() { "Vectorscope" } else { "Oscilloscope" },
if cfg.scatter() { "scatter" } else { "line" },
cfg.scale(), cfg.width(), args.sample_rate as f32 / 1000.0, framerate,
),
Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow))
))
.x_axis(Axis::default()
.title(Span::styled(cfg.name(app::Axis::X), Style::default().fg(Color::Cyan)))
.style(Style::default().fg(cfg.axis_color))
.bounds(cfg.bounds(app::Axis::X))) // TODO allow to have axis sometimes?
.y_axis(Axis::default()
.title(Span::styled(cfg.name(app::Axis::Y), Style::default().fg(Color::Cyan)))
.style(Style::default().fg(cfg.axis_color))
.bounds(cfg.bounds(app::Axis::Y)));
f.render_widget(chart, size)
})?;
} }
if app.cfg.references {
// for reference in app.references() {
// datasets.push(reference);
// }
datasets.push(data_set("", &app.references.x, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
datasets.push(data_set("", &app.references.y, app.cfg.marker_type, GraphType::Line, app.cfg.axis_color));
}
if app.vectorscope() {
let pivot = merged.len() / 2;
datasets.push(data_set("1", &merged[..pivot], app.cfg.marker_type, app.graph_type(), app.cfg.secondary_color));
datasets.push(data_set("2", &merged[pivot..], app.cfg.marker_type, app.graph_type(), app.cfg.primary_color));
} else {
datasets.push(data_set("R", &right, app.cfg.marker_type, app.graph_type(), app.cfg.secondary_color));
datasets.push(data_set("L", &left, app.cfg.marker_type, app.graph_type(), app.cfg.primary_color));
}
fps += 1;
if last_poll.elapsed().as_secs() >= 1 {
framerate = fps;
fps = 0;
last_poll = Instant::now();
}
terminal.draw(|f| {
let size = f.size();
let chart = Chart::new(datasets)
.block(Block::default().title(
Span::styled(
format!(
"TUI {} <me@alemi.dev> -- {} mode -- range {} -- {} samples -- {:.1} kHz -- {} fps",
if app.vectorscope() { "Vectorscope" } else { "Oscilloscope" },
if app.scatter() { "scatter" } else { "line" },
app.scale(), app.width(), args.sample_rate as f32 / 1000.0, framerate,
),
Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow))
))
.x_axis(Axis::default()
.title(Span::styled(app.name(app::Axis::X), Style::default().fg(Color::Cyan)))
.style(Style::default().fg(app.cfg.axis_color))
.bounds(app.bounds(app::Axis::X))) // TODO allow to have axis sometimes?
.y_axis(Axis::default()
.title(Span::styled(app.name(app::Axis::Y), Style::default().fg(Color::Cyan)))
.style(Style::default().fg(app.cfg.axis_color))
.bounds(app.bounds(app::Axis::Y)));
f.render_widget(chart, size)
})?;
if let Some(Event::Key(key)) = poll_event()? { if let Some(Event::Key(key)) = poll_event()? {
match key.modifiers { match key.modifiers {
KeyModifiers::CONTROL => { KeyModifiers::CONTROL => {
@ -253,12 +243,12 @@ fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io
match key.code { match key.code {
KeyCode::Char('q') => break, KeyCode::Char('q') => break,
KeyCode::Char(' ') => pause = !pause, KeyCode::Char(' ') => pause = !pause,
KeyCode::Char('=') => cfg.update_scale(-1000), KeyCode::Char('=') => app.update_scale(-1000),
KeyCode::Char('-') => cfg.update_scale(1000), KeyCode::Char('-') => app.update_scale(1000),
KeyCode::Char('+') => cfg.update_scale(-100), KeyCode::Char('+') => app.update_scale(-100),
KeyCode::Char('_') => cfg.update_scale(100), KeyCode::Char('_') => app.update_scale(100),
KeyCode::Char('v') => cfg.set_vectorscope(!cfg.vectorscope()), KeyCode::Char('v') => app.set_vectorscope(!app.vectorscope()),
KeyCode::Char('s') => cfg.set_scatter(!cfg.scatter()), KeyCode::Char('s') => app.set_scatter(!app.scatter()),
_ => {}, _ => {},
} }
} }