mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 14:14:48 +01:00
feat: initial refactor, keys to change settings
it's now possible to change most settings while scope-tui. Keybinds are `v` for vectorscope/oscilloscope, `s` for scatter/line and `+/-` to manage range.
This commit is contained in:
parent
a1abb8f290
commit
0116056072
3 changed files with 293 additions and 172 deletions
137
src/app.rs
Normal file
137
src/app.rs
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
use tui::{style::Color, widgets::GraphType, symbols};
|
||||||
|
|
||||||
|
// use crate::parser::SampleParser;
|
||||||
|
|
||||||
|
pub enum Axis {
|
||||||
|
X, Y
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ChartNames {
|
||||||
|
x: String,
|
||||||
|
y: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub struct ChartBounds {
|
||||||
|
x: [f64;2],
|
||||||
|
y: [f64;2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ChartBounds {
|
||||||
|
fn default() -> Self {
|
||||||
|
ChartBounds { x: [0.0, 0.0], y: [0.0, 0.0] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppConfig {
|
||||||
|
pub title: String,
|
||||||
|
pub primary_color: Color,
|
||||||
|
pub secondary_color: Color,
|
||||||
|
pub axis_color: Color,
|
||||||
|
|
||||||
|
scale: u32,
|
||||||
|
width: u32,
|
||||||
|
vectorscope: bool,
|
||||||
|
pub references: bool,
|
||||||
|
|
||||||
|
pub marker_type: symbols::Marker,
|
||||||
|
graph_type: GraphType,
|
||||||
|
|
||||||
|
bounds: ChartBounds,
|
||||||
|
names: ChartNames,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppConfig {
|
||||||
|
fn update_values(&mut self) {
|
||||||
|
if self.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.y = "| right".into();
|
||||||
|
} else {
|
||||||
|
// it makes no sense to show self.scale on the left but it's kinda nice
|
||||||
|
self.names.x = "- time".into();
|
||||||
|
self.names.y = "| amplitude".into();
|
||||||
|
self.bounds.x = [0.0, self.width as f64];
|
||||||
|
self.bounds.y = [-(self.scale as f64), self.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] {
|
||||||
|
match axis {
|
||||||
|
Axis::X => self.bounds.x,
|
||||||
|
Axis::Y => self.bounds.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self, axis: Axis) -> &str {
|
||||||
|
match axis {
|
||||||
|
Axis::X => self.names.x.as_str(),
|
||||||
|
Axis::Y => self.names.y.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_vectorscope(&mut self, vectorscope: bool) {
|
||||||
|
self.vectorscope = vectorscope;
|
||||||
|
self.update_values();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_scale(&mut self, increment: 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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
let mut cfg = AppConfig {
|
||||||
|
title: "TUI Oscilloscope -- <me@alemi.dev>".into(),
|
||||||
|
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!
|
||||||
|
vectorscope: args.vectorscope,
|
||||||
|
references: !args.no_reference,
|
||||||
|
bounds: ChartBounds::default(),
|
||||||
|
names: ChartNames::default(),
|
||||||
|
marker_type, graph_type,
|
||||||
|
};
|
||||||
|
|
||||||
|
cfg.update_values();
|
||||||
|
|
||||||
|
cfg
|
||||||
|
}
|
||||||
|
}
|
262
src/main.rs
262
src/main.rs
|
@ -1,12 +1,15 @@
|
||||||
|
mod parser;
|
||||||
|
mod app;
|
||||||
|
|
||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
use tui::{
|
use tui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
widgets::{Block, Chart, Axis, GraphType, Dataset, BorderType},
|
widgets::{Block, Chart, Axis, Dataset, GraphType},
|
||||||
// layout::{Layout, Constraint, Direction},
|
// layout::{Layout, Constraint, Direction},
|
||||||
Terminal, text::Span, style::{Style, Color}, symbols
|
Terminal, text::Span, style::{Style, Color, Modifier}, symbols
|
||||||
};
|
};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
|
event::{self, DisableMouseCapture, Event, KeyCode, KeyModifiers},
|
||||||
execute,
|
execute,
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
};
|
};
|
||||||
|
@ -17,17 +20,20 @@ use libpulse_binding::sample::{Spec, Format};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
|
use parser::{SampleParser, Signed16PCM};
|
||||||
|
use app::AppConfig;
|
||||||
|
|
||||||
/// A simple oscilloscope/vectorscope for your terminal
|
/// A simple oscilloscope/vectorscope for your terminal
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Size of audio buffer, and width of scope
|
|
||||||
width: u32,
|
|
||||||
|
|
||||||
/// Audio device to attach to
|
/// Audio device to attach to
|
||||||
#[arg(short, long)]
|
|
||||||
device: Option<String>,
|
device: Option<String>,
|
||||||
|
|
||||||
|
/// Size of audio buffer, and width of scope
|
||||||
|
#[arg(short, long, default_value_t = 8192)]
|
||||||
|
width: u32,
|
||||||
|
|
||||||
/// Max value on Amplitude scale
|
/// Max value on Amplitude scale
|
||||||
#[arg(short, long, default_value_t = 20000)]
|
#[arg(short, long, default_value_t = 20000)]
|
||||||
scale: u32,
|
scale: u32,
|
||||||
|
@ -49,64 +55,6 @@ struct Args {
|
||||||
vectorscope: bool,
|
vectorscope: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SampleParser {
|
|
||||||
fn oscilloscope(data: &mut [u8]) -> (Vec<(f64, f64)>, Vec<(f64, f64)>);
|
|
||||||
fn vectorscope (data: &mut [u8]) -> Vec<(f64, f64)>;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Signed16PCM {}
|
|
||||||
|
|
||||||
impl SampleParser for Signed16PCM {
|
|
||||||
fn oscilloscope(data: &mut [u8]) -> (Vec<(f64, f64)>, Vec<(f64, f64)>) {
|
|
||||||
let mut left = Vec::new(); // TODO does left really come first?
|
|
||||||
let mut right = Vec::new();
|
|
||||||
let mut buf : i16 = 0;
|
|
||||||
let mut count : f64 = 0.0;
|
|
||||||
let mut flip = false;
|
|
||||||
let mut side = false;
|
|
||||||
for sample in data {
|
|
||||||
if flip {
|
|
||||||
buf |= (*sample as i16) << 8;
|
|
||||||
if side {
|
|
||||||
left.push((count, buf as f64));
|
|
||||||
} else {
|
|
||||||
right.push((count, buf as f64));
|
|
||||||
count += 1.0;
|
|
||||||
}
|
|
||||||
buf = 0;
|
|
||||||
side = !side;
|
|
||||||
} else {
|
|
||||||
buf |= *sample as i16;
|
|
||||||
}
|
|
||||||
flip = !flip;
|
|
||||||
}
|
|
||||||
(left, right)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vectorscope(data: &mut [u8]) -> Vec<(f64, f64)> {
|
|
||||||
let mut out = Vec::new(); // TODO does left really come first?
|
|
||||||
let mut buf : i16 = 0;
|
|
||||||
let mut flip = false;
|
|
||||||
let mut point = None;
|
|
||||||
for sample in data {
|
|
||||||
if flip {
|
|
||||||
buf |= (*sample as i16) << 8;
|
|
||||||
if point.is_none() {
|
|
||||||
point = Some(buf as f64);
|
|
||||||
} else {
|
|
||||||
out.push((point.unwrap(), buf as f64));
|
|
||||||
point = None;
|
|
||||||
}
|
|
||||||
buf = 0;
|
|
||||||
} else {
|
|
||||||
buf |= *sample as i16;
|
|
||||||
}
|
|
||||||
flip = !flip;
|
|
||||||
}
|
|
||||||
out
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll_event() -> Result<Option<Event>, std::io::Error> {
|
fn poll_event() -> Result<Option<Event>, std::io::Error> {
|
||||||
if event::poll(Duration::from_millis(0))? {
|
if event::poll(Duration::from_millis(0))? {
|
||||||
Ok(Some(event::read()?))
|
Ok(Some(event::read()?))
|
||||||
|
@ -115,6 +63,21 @@ fn poll_event() -> Result<Option<Event>, std::io::Error> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn data_set<'a>(
|
||||||
|
name: &'a str,
|
||||||
|
data: &'a Vec<(f64, f64)>,
|
||||||
|
marker_type: symbols::Marker,
|
||||||
|
graph_type: GraphType,
|
||||||
|
axis_color: Color
|
||||||
|
) -> Dataset<'a> {
|
||||||
|
Dataset::default()
|
||||||
|
.name(name)
|
||||||
|
.marker(marker_type)
|
||||||
|
.graph_type(graph_type)
|
||||||
|
.style(Style::default().fg(axis_color))
|
||||||
|
.data(&data)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fn main() -> Result<(), io::Error> {
|
fn main() -> Result<(), io::Error> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
@ -132,8 +95,6 @@ fn main() -> Result<(), io::Error> {
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 s = Simple::new(
|
let s = Simple::new(
|
||||||
None, // Use the default server
|
None, // Use the default server
|
||||||
|
@ -153,128 +114,79 @@ fn main() -> Result<(), io::Error> {
|
||||||
// setup terminal
|
// setup terminal
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
terminal.hide_cursor().unwrap();
|
terminal.hide_cursor().unwrap();
|
||||||
|
|
||||||
|
// prepare globals
|
||||||
let mut buffer : Vec<u8> = vec![0; args.width as usize];
|
let mut buffer : Vec<u8> = vec![0; args.width as usize];
|
||||||
// let mut buffer : [u8; WINDOW] = [0;WINDOW];
|
let mut cfg = AppConfig::from(args);
|
||||||
let y_bounds : [f64; 2];
|
let fmt = Signed16PCM{}; // TODO some way to choose this?
|
||||||
let x_bounds : [f64; 2];
|
|
||||||
let reference_x : Dataset;
|
|
||||||
let reference_y : Dataset;
|
|
||||||
|
|
||||||
let mut ref_data_x = Vec::new();
|
|
||||||
let mut ref_data_y = Vec::new();
|
|
||||||
|
|
||||||
let mut pause = false;
|
let mut pause = false;
|
||||||
|
|
||||||
if args.vectorscope {
|
|
||||||
x_bounds = [-(args.scale as f64), args.scale as f64];
|
|
||||||
y_bounds = [-(args.scale as f64), args.scale as f64];
|
|
||||||
|
|
||||||
for x in -(args.scale as i64)..(args.scale as i64) {
|
|
||||||
ref_data_x.push((x as f64, 0 as f64));
|
|
||||||
ref_data_y.push((0 as f64, x as f64));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x_bounds = [0.0, args.width as f64 / 4.0];
|
|
||||||
y_bounds = [-(args.scale as f64), args.scale as f64];
|
|
||||||
|
|
||||||
for x in 0..args.width/4 {
|
|
||||||
ref_data_x.push((x as f64, 0 as f64));
|
|
||||||
}
|
|
||||||
for y in -(args.scale as i64)..(args.scale as i64) {
|
|
||||||
ref_data_y.push(((args.width as f64) / 8.0, y as f64));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reference_x = Dataset::default()
|
|
||||||
.name("X")
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(GraphType::Line)
|
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
|
||||||
.data(&ref_data_x);
|
|
||||||
reference_y = Dataset::default()
|
|
||||||
.name("Y")
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(GraphType::Line)
|
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
|
||||||
.data(&ref_data_y);
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
s.read(&mut buffer).unwrap();
|
s.read(&mut buffer).unwrap();
|
||||||
|
|
||||||
let mut datasets = vec![];
|
|
||||||
let (left, right) : (Vec<(f64, f64)>, Vec<(f64, f64)>);
|
|
||||||
let merged : Vec<(f64, f64)>;
|
|
||||||
let labels_x : Vec<Span>;
|
|
||||||
let labels_y : Vec<Span>;
|
|
||||||
let title_x : String;
|
|
||||||
let title_y : String;
|
|
||||||
|
|
||||||
if !args.no_reference {
|
|
||||||
datasets.push(reference_x.clone());
|
|
||||||
datasets.push(reference_y.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.vectorscope {
|
|
||||||
merged = Signed16PCM::vectorscope(&mut buffer);
|
|
||||||
datasets.push(
|
|
||||||
Dataset::default()
|
|
||||||
.name("V")
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(graph_type)
|
|
||||||
.style(Style::default().fg(Color::Red))
|
|
||||||
.data(&merged)
|
|
||||||
);
|
|
||||||
labels_x = vec![Span::from("-"), Span::from("0"), Span::from("+")];
|
|
||||||
labels_y = vec![Span::from("-"), Span::from("0"), Span::from("+")];
|
|
||||||
title_x = "left".into();
|
|
||||||
title_y = "right".into();
|
|
||||||
} else {
|
|
||||||
(left, right) = Signed16PCM::oscilloscope(&mut buffer);
|
|
||||||
datasets.push(
|
|
||||||
Dataset::default()
|
|
||||||
.name("R")
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(graph_type)
|
|
||||||
.style(Style::default().fg(Color::Yellow))
|
|
||||||
.data(&right)
|
|
||||||
);
|
|
||||||
datasets.push(
|
|
||||||
Dataset::default()
|
|
||||||
.name("L")
|
|
||||||
.marker(marker_type)
|
|
||||||
.graph_type(graph_type)
|
|
||||||
.style(Style::default().fg(Color::Red))
|
|
||||||
.data(&left)
|
|
||||||
);
|
|
||||||
labels_x = vec![Span::from("0"), Span::from(format!("{}", args.width / 4))];
|
|
||||||
labels_y = vec![Span::from("-"), Span::from("0"), Span::from("+")];
|
|
||||||
title_x = "sample".into();
|
|
||||||
title_y = "amplitude".into();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !pause {
|
if !pause {
|
||||||
|
let mut datasets = vec![];
|
||||||
|
let (left, right);
|
||||||
|
let merged;
|
||||||
|
|
||||||
|
let mut ref_data_x = Vec::new();
|
||||||
|
let mut ref_data_y = Vec::new();
|
||||||
|
|
||||||
|
if cfg.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);
|
||||||
|
datasets.push(data_set("V", &merged, cfg.marker_type, cfg.graph_type(), cfg.primary_color));
|
||||||
|
} else {
|
||||||
|
(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));
|
||||||
|
}
|
||||||
|
|
||||||
terminal.draw(|f| {
|
terminal.draw(|f| {
|
||||||
let size = f.size();
|
let size = f.size();
|
||||||
let chart = Chart::new(datasets)
|
let chart = Chart::new(datasets)
|
||||||
.block(Block::default()
|
.block(Block::default().title(
|
||||||
.border_type(BorderType::Rounded)
|
Span::styled(
|
||||||
.border_style(Style::default().fg(Color::DarkGray))
|
format!(
|
||||||
.title(Span::styled("TUI Oscilloscope -- <me@alemi.dev>", Style::default().fg(Color::Cyan))))
|
"TUI {} <me@alemi.dev> -- {} mode -- range {} -- {} samples",
|
||||||
|
if cfg.vectorscope() { "Vectorscope" } else { "Oscilloscope" },
|
||||||
|
if cfg.scatter() { "scatter" } else { "line" },
|
||||||
|
cfg.scale(), cfg.width(),
|
||||||
|
),
|
||||||
|
Style::default().add_modifier(Modifier::BOLD).fg(Color::Yellow))
|
||||||
|
))
|
||||||
.x_axis(Axis::default()
|
.x_axis(Axis::default()
|
||||||
.title(Span::styled(title_x.as_str(), Style::default().fg(Color::Cyan)))
|
.title(Span::styled(cfg.name(app::Axis::X), Style::default().fg(Color::Cyan)))
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
.style(Style::default().fg(cfg.axis_color))
|
||||||
.bounds(x_bounds)
|
.bounds(cfg.bounds(app::Axis::X)))
|
||||||
.labels(labels_x))
|
|
||||||
.y_axis(Axis::default()
|
.y_axis(Axis::default()
|
||||||
.title(Span::styled(title_y.as_str(), Style::default().fg(Color::Cyan)))
|
.title(Span::styled(cfg.name(app::Axis::Y), Style::default().fg(Color::Cyan)))
|
||||||
.style(Style::default().fg(Color::DarkGray))
|
.style(Style::default().fg(cfg.axis_color))
|
||||||
.bounds(y_bounds)
|
.bounds(cfg.bounds(app::Axis::Y)));
|
||||||
.labels(labels_y));
|
|
||||||
f.render_widget(chart, size)
|
f.render_widget(chart, size)
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -284,6 +196,8 @@ fn main() -> Result<(), io::Error> {
|
||||||
KeyModifiers::CONTROL => {
|
KeyModifiers::CONTROL => {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Char('c') => break,
|
KeyCode::Char('c') => break,
|
||||||
|
KeyCode::Char('+') => cfg.update_scale(100),
|
||||||
|
KeyCode::Char('-') => cfg.update_scale(-100),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -291,6 +205,10 @@ fn main() -> Result<(), io::Error> {
|
||||||
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('-') => cfg.update_scale(-1000),
|
||||||
|
KeyCode::Char('v') => cfg.set_vectorscope(!cfg.vectorscope()),
|
||||||
|
KeyCode::Char('s') => cfg.set_scatter(!cfg.scatter()),
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
66
src/parser.rs
Normal file
66
src/parser.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// use libpulse_binding::sample::Format;
|
||||||
|
|
||||||
|
// pub fn parser(fmt: Format) -> impl SampleParser {
|
||||||
|
// match fmt {
|
||||||
|
// Format::S16NE => Signed16PCM {},
|
||||||
|
// _ => panic!("parser not implemented for this format")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub trait SampleParser {
|
||||||
|
fn oscilloscope(&self, data: &mut [u8]) -> (Vec<(f64, f64)>, Vec<(f64, f64)>);
|
||||||
|
fn vectorscope (&self, data: &mut [u8]) -> Vec<(f64, f64)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Signed16PCM {}
|
||||||
|
|
||||||
|
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?
|
||||||
|
let mut right = Vec::new();
|
||||||
|
let mut buf : i16 = 0;
|
||||||
|
let mut count : f64 = 0.0;
|
||||||
|
let mut flip = false;
|
||||||
|
let mut side = false;
|
||||||
|
for sample in data {
|
||||||
|
if flip {
|
||||||
|
buf |= (*sample as i16) << 8;
|
||||||
|
if side {
|
||||||
|
left.push((count, buf as f64));
|
||||||
|
} else {
|
||||||
|
right.push((count, buf as f64));
|
||||||
|
count += 1.0;
|
||||||
|
}
|
||||||
|
buf = 0;
|
||||||
|
side = !side;
|
||||||
|
} else {
|
||||||
|
buf |= *sample as i16;
|
||||||
|
}
|
||||||
|
flip = !flip;
|
||||||
|
}
|
||||||
|
(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vectorscope(&self, data: &mut [u8]) -> Vec<(f64, f64)> {
|
||||||
|
let mut out = Vec::new(); // TODO does left really come first?
|
||||||
|
let mut buf : i16 = 0;
|
||||||
|
let mut flip = false;
|
||||||
|
let mut point = None;
|
||||||
|
for sample in data {
|
||||||
|
if flip {
|
||||||
|
buf |= (*sample as i16) << 8;
|
||||||
|
if point.is_none() {
|
||||||
|
point = Some(buf as f64);
|
||||||
|
} else {
|
||||||
|
out.push((point.unwrap(), buf as f64));
|
||||||
|
point = None;
|
||||||
|
}
|
||||||
|
buf = 0;
|
||||||
|
} else {
|
||||||
|
buf |= *sample as i16;
|
||||||
|
}
|
||||||
|
flip = !flip;
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue