mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 22:24:48 +01:00
feat: small refactor, allow editing paused view
This commit is contained in:
parent
ed246e314f
commit
e83a326c89
2 changed files with 159 additions and 126 deletions
127
src/app.rs
127
src/app.rs
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
src/main.rs
158
src/main.rs
|
@ -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()),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue