mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-14 10:49:20 +01:00
fix: updated readme, catch errors
This commit is contained in:
parent
e2269ef6d6
commit
00f27b7f83
5 changed files with 84 additions and 57 deletions
|
@ -3,9 +3,7 @@ name = "scope-tui"
|
|||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
authors = [ "alemi <me@alemi.dev>" ]
|
||||
description = """
|
||||
A simple audio visualization tool for the terminal built with tui-rs and libpulse-simple-binding, inspired from cava
|
||||
"""
|
||||
description = "A simple audio visualization tool for the terminal built with tui-rs and libpulse-simple-binding, inspired from cava"
|
||||
keywords = ["tui", "terminal", "audio", "visualization", "scope", "dashboard"]
|
||||
repository = "https://github.com/alemidev/scope-tui"
|
||||
readme = "README.md"
|
||||
|
|
15
README.md
15
README.md
|
@ -13,17 +13,17 @@ the first version of `scope-tui` was developed, with very minimal settings given
|
|||
|
||||
# Usage
|
||||
```
|
||||
$ scope-tui [OPTIONS] <WIDTH>
|
||||
$ scope-tui [OPTIONS] [DEVICE]
|
||||
|
||||
Arguments:
|
||||
<WIDTH> Size of audio buffer, and width of scope
|
||||
[DEVICE] Audio device to attach to
|
||||
|
||||
Options:
|
||||
-d, --device <DEVICE> Audio device to attach to
|
||||
-s, --scale <SCALE> Max value on Amplitude scale [default: 20000]
|
||||
-b, --buffer <BUFFER> Size of audio buffer, and width of scope [default: 8192]
|
||||
-r, --range <RANGE> Max value, positive and negative, on amplitude scale [default: 20000]
|
||||
--no-reference Don't draw reference line
|
||||
--no-braille Don't use braille dots for drawing lines
|
||||
--scatter Use vintage looking scatter mode
|
||||
--scatter Use vintage looking scatter mode instead of line mode
|
||||
--vectorscope Combine left and right channels into vectorscope view
|
||||
-h, --help Print help information
|
||||
-V, --version Print version information
|
||||
|
@ -32,6 +32,9 @@ Options:
|
|||
The audio buffer size directly impacts resource usage, latency and refresh rate and its limits are given by the audio refresh rate. Larger buffers are slower but less resource intensive. A good starting value might be `8192`
|
||||
|
||||
## Controls
|
||||
* Use `q` or `CTRL+C` to exit. Not all keypresses are caught, so keep trying... (wip!)
|
||||
* Use `q` or `CTRL+C` to exit
|
||||
* Use `<SPACE>` to pause and resume display
|
||||
* Use `-` and `=` to decrease or increase range (`_` and `+` for smaller steps)
|
||||
* Use `v` to toggle vectorscope mode
|
||||
* Use `s` to toggle scatter mode
|
||||
* Decrease/increase terminal font size to increase/decrease scope resolution.
|
||||
|
|
10
src/app.rs
10
src/app.rs
|
@ -101,9 +101,11 @@ impl AppConfig {
|
|||
}
|
||||
|
||||
pub fn update_scale(&mut self, increment: i32) {
|
||||
if increment > 0 || increment.abs() < self.scale as i32 {
|
||||
self.scale = ((self.scale as i32) + increment) as u32;
|
||||
self.update_values();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_scatter(&mut self, scatter: bool) {
|
||||
self.graph_type = if scatter { GraphType::Scatter } else { GraphType::Line };
|
||||
|
@ -111,8 +113,8 @@ impl AppConfig {
|
|||
}
|
||||
|
||||
|
||||
impl From::<crate::Args> for AppConfig {
|
||||
fn from(args: crate::Args) -> Self {
|
||||
impl From::<&crate::Args> for AppConfig {
|
||||
fn from(args: &crate::Args) -> Self {
|
||||
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 };
|
||||
|
||||
|
@ -121,8 +123,8 @@ impl From::<crate::Args> for AppConfig {
|
|||
primary_color: Color::Red,
|
||||
secondary_color: Color::Yellow,
|
||||
axis_color: Color::DarkGray,
|
||||
scale: args.scale,
|
||||
width: args.width / 4, // TODO It's 4 because 2 channels and 2 bytes per sample!
|
||||
scale: args.range,
|
||||
width: args.buffer / 4, // TODO It's 4 because 2 channels and 2 bytes per sample!
|
||||
vectorscope: args.vectorscope,
|
||||
references: !args.no_reference,
|
||||
bounds: ChartBounds::default(),
|
||||
|
|
107
src/main.rs
107
src/main.rs
|
@ -1,9 +1,9 @@
|
|||
mod parser;
|
||||
mod app;
|
||||
|
||||
use std::{io, time::Duration};
|
||||
use std::{io::{self, ErrorKind}, time::Duration};
|
||||
use tui::{
|
||||
backend::CrosstermBackend,
|
||||
backend::{CrosstermBackend, Backend},
|
||||
widgets::{Block, Chart, Axis, Dataset, GraphType},
|
||||
// layout::{Layout, Constraint, Direction},
|
||||
Terminal, text::Span, style::{Style, Color, Modifier}, symbols
|
||||
|
@ -32,11 +32,11 @@ struct Args {
|
|||
|
||||
/// Size of audio buffer, and width of scope
|
||||
#[arg(short, long, default_value_t = 8192)]
|
||||
width: u32,
|
||||
buffer: u32,
|
||||
|
||||
/// Max value on Amplitude scale
|
||||
/// Max value, positive and negative, on amplitude scale
|
||||
#[arg(short, long, default_value_t = 20000)]
|
||||
scale: u32,
|
||||
range: u32,
|
||||
|
||||
/// Don't draw reference line
|
||||
#[arg(long, default_value_t = false)]
|
||||
|
@ -46,7 +46,7 @@ struct Args {
|
|||
#[arg(long, default_value_t = false)]
|
||||
no_braille: bool,
|
||||
|
||||
/// Use vintage looking scatter mode
|
||||
/// Use vintage looking scatter mode instead of line mode
|
||||
#[arg(long, default_value_t = false)]
|
||||
scatter: bool,
|
||||
|
||||
|
@ -78,10 +78,45 @@ fn data_set<'a>(
|
|||
.data(&data)
|
||||
}
|
||||
|
||||
|
||||
fn main() -> Result<(), io::Error> {
|
||||
let args = Args::parse();
|
||||
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor()?;
|
||||
|
||||
match run_app(args, &mut terminal) {
|
||||
Ok(()) => {},
|
||||
Err(e) => {
|
||||
println!("[!] Error executing app: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn run_app<T : Backend>(args: Args, terminal: &mut Terminal<T>) -> Result<(), io::Error> {
|
||||
// prepare globals
|
||||
let mut buffer : Vec<u8> = vec![0; args.buffer as usize];
|
||||
let mut cfg = AppConfig::from(&args);
|
||||
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||
|
||||
let mut pause = false;
|
||||
|
||||
// setup audio capture
|
||||
let spec = Spec {
|
||||
format: Format::S16NE,
|
||||
|
@ -95,8 +130,7 @@ fn main() -> Result<(), io::Error> {
|
|||
None => None,
|
||||
};
|
||||
|
||||
|
||||
let s = Simple::new(
|
||||
let s = match Simple::new(
|
||||
None, // Use the default server
|
||||
"ScopeTUI", // Our application’s name
|
||||
Direction::Record, // We want a record stream
|
||||
|
@ -105,29 +139,27 @@ fn main() -> Result<(), io::Error> {
|
|||
&spec, // Our sample format
|
||||
None, // Use default channel map
|
||||
Some(&BufferAttr {
|
||||
maxlength: 32 * args.width,
|
||||
fragsize: args.width,
|
||||
maxlength: 32 * args.buffer,
|
||||
fragsize: args.buffer,
|
||||
..Default::default()
|
||||
}),
|
||||
).unwrap();
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("[!] Could not connect to pulseaudio : {:?}", e);
|
||||
return Err(io::Error::new(ErrorKind::Other, "could not connect to pulseaudio"));
|
||||
},
|
||||
};
|
||||
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
terminal.hide_cursor().unwrap();
|
||||
|
||||
// prepare globals
|
||||
let mut buffer : Vec<u8> = vec![0; args.width as usize];
|
||||
let mut cfg = AppConfig::from(args);
|
||||
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||
|
||||
let mut pause = false;
|
||||
|
||||
loop {
|
||||
s.read(&mut buffer).unwrap();
|
||||
match s.read(&mut buffer) {
|
||||
Ok(()) => {},
|
||||
Err(e) => {
|
||||
println!("[!] Could not read data from pulseaudio : {:?}", e);
|
||||
return Err(io::Error::new(ErrorKind::Other, "could not read from pulseaudio"));
|
||||
},
|
||||
}
|
||||
|
||||
if !pause {
|
||||
let mut datasets = vec![];
|
||||
|
@ -138,7 +170,7 @@ fn main() -> Result<(), io::Error> {
|
|||
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));
|
||||
|
@ -182,7 +214,7 @@ fn main() -> Result<(), io::Error> {
|
|||
.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)))
|
||||
.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))
|
||||
|
@ -196,8 +228,6 @@ fn main() -> Result<(), io::Error> {
|
|||
KeyModifiers::CONTROL => {
|
||||
match key.code {
|
||||
KeyCode::Char('c') => break,
|
||||
KeyCode::Char('+') => cfg.update_scale(100),
|
||||
KeyCode::Char('-') => cfg.update_scale(-100),
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
|
@ -205,8 +235,10 @@ fn main() -> Result<(), io::Error> {
|
|||
match key.code {
|
||||
KeyCode::Char('q') => break,
|
||||
KeyCode::Char(' ') => pause = !pause,
|
||||
KeyCode::Char('+') => cfg.update_scale(1000),
|
||||
KeyCode::Char('-') => cfg.update_scale(-1000),
|
||||
KeyCode::Char('=') => cfg.update_scale(-1000),
|
||||
KeyCode::Char('-') => cfg.update_scale(1000),
|
||||
KeyCode::Char('+') => cfg.update_scale(-100),
|
||||
KeyCode::Char('_') => cfg.update_scale(100),
|
||||
KeyCode::Char('v') => cfg.set_vectorscope(!cfg.vectorscope()),
|
||||
KeyCode::Char('s') => cfg.set_scatter(!cfg.scatter()),
|
||||
_ => {},
|
||||
|
@ -216,14 +248,5 @@ fn main() -> Result<(), io::Error> {
|
|||
}
|
||||
}
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ pub trait SampleParser {
|
|||
|
||||
pub struct Signed16PCM {}
|
||||
|
||||
/// TODO these are kinda inefficient, can they be faster?
|
||||
impl SampleParser for Signed16PCM {
|
||||
fn oscilloscope(&self, data: &mut [u8]) -> (Vec<(f64, f64)>, Vec<(f64, f64)>) {
|
||||
let mut left = Vec::new(); // TODO does left really come first?
|
||||
|
|
Loading…
Reference in a new issue