mirror of
https://github.com/alemidev/scope-tui.git
synced 2024-11-23 14:14:48 +01:00
chore: separated lib from app code
so various display modes can be imported as deps, but by default it still compiles a binary target
This commit is contained in:
parent
f62586d1d1
commit
58a084e154
9 changed files with 72 additions and 61 deletions
18
Cargo.toml
18
Cargo.toml
|
@ -10,12 +10,22 @@ keywords = ["cli", "tui", "audio", "visualization", "scope"]
|
||||||
repository = "https://git.alemi.dev/scope-tui.git"
|
repository = "https://git.alemi.dev/scope-tui.git"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "scope"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "scope-tui"
|
||||||
|
path = "src/main.rs"
|
||||||
|
required-features = ["app"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.0.32", features = ["derive"] }
|
|
||||||
derive_more = "0.99.17"
|
derive_more = "0.99.17"
|
||||||
thiserror = "1.0.48"
|
thiserror = "1.0.48"
|
||||||
rustfft = "6.1.0"
|
rustfft = "6.1.0"
|
||||||
|
# for TUI app
|
||||||
|
clap = { version = "4.0.32", features = ["derive"], optional = true }
|
||||||
# cross platform audio library backend
|
# cross platform audio library backend
|
||||||
cpal = { version = "0.15.3", optional = true }
|
cpal = { version = "0.15.3", optional = true }
|
||||||
# for TUI backend
|
# for TUI backend
|
||||||
|
@ -26,9 +36,11 @@ libpulse-binding = { version = "2.0", optional = true }
|
||||||
libpulse-simple-binding = { version = "2.25", optional = true }
|
libpulse-simple-binding = { version = "2.25", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["tui", "file", "cpal"]
|
default = ["tui"]
|
||||||
file = []
|
#default = ["app", "file", "cpal"]
|
||||||
|
app = ["dep:clap", "tui"]
|
||||||
tui = ["dep:ratatui", "dep:crossterm"]
|
tui = ["dep:ratatui", "dep:crossterm"]
|
||||||
|
file = []
|
||||||
cpal = ["dep:cpal"]
|
cpal = ["dep:cpal"]
|
||||||
pulseaudio = ["dep:libpulse-binding", "dep:libpulse-simple-binding"]
|
pulseaudio = ["dep:libpulse-binding", "dep:libpulse-simple-binding"]
|
||||||
|
|
||||||
|
|
32
src/app.rs
32
src/app.rs
|
@ -47,9 +47,9 @@ impl App {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let oscilloscope = Oscilloscope::from_args(source);
|
let oscilloscope = Oscilloscope::default();
|
||||||
let vectorscope = Vectorscope::from_args(source);
|
let vectorscope = Vectorscope::default();
|
||||||
let spectroscope = Spectroscope::from_args(source);
|
let spectroscope = Spectroscope::from(source);
|
||||||
|
|
||||||
App {
|
App {
|
||||||
graph, oscilloscope, vectorscope, spectroscope,
|
graph, oscilloscope, vectorscope, spectroscope,
|
||||||
|
@ -175,32 +175,6 @@ impl App {
|
||||||
|
|
||||||
// TODO can these be removed or merged somewhere else?
|
// TODO can these be removed or merged somewhere else?
|
||||||
|
|
||||||
pub fn update_value_f(val: &mut f64, base: f64, magnitude: f64, range: Range<f64>) {
|
|
||||||
let delta = base * magnitude;
|
|
||||||
if *val + delta > range.end {
|
|
||||||
*val = range.end
|
|
||||||
} else if *val + delta < range.start {
|
|
||||||
*val = range.start
|
|
||||||
} else {
|
|
||||||
*val += delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_value_i(val: &mut u32, inc: bool, base: u32, magnitude: f64, range: Range<u32>) {
|
|
||||||
let delta = (base as f64 * magnitude) as u32;
|
|
||||||
if inc {
|
|
||||||
if range.end - delta < *val {
|
|
||||||
*val = range.end
|
|
||||||
} else {
|
|
||||||
*val += delta
|
|
||||||
}
|
|
||||||
} else if range.start + delta > *val {
|
|
||||||
*val = range.start
|
|
||||||
} else {
|
|
||||||
*val -= delta
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_header<'a>(cfg: &GraphConfig, module_header: &'a str, kind_o_scope: &'static str, fps: usize, pause: bool) -> Table<'a> {
|
fn make_header<'a>(cfg: &GraphConfig, module_header: &'a str, kind_o_scope: &'static str, fps: usize, pause: bool) -> Table<'a> {
|
||||||
Table::new(
|
Table::new(
|
||||||
vec![
|
vec![
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub enum Dimension {
|
||||||
X, Y
|
X, Y
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct GraphConfig {
|
pub struct GraphConfig {
|
||||||
pub pause: bool,
|
pub pause: bool,
|
||||||
pub samples: u32,
|
pub samples: u32,
|
||||||
|
@ -36,7 +36,6 @@ impl GraphConfig {
|
||||||
#[allow(clippy::ptr_arg)] // TODO temporarily! it's a shitty solution
|
#[allow(clippy::ptr_arg)] // TODO temporarily! it's a shitty solution
|
||||||
pub trait DisplayMode {
|
pub trait DisplayMode {
|
||||||
// MUST define
|
// MUST define
|
||||||
fn from_args(args: &crate::cfg::SourceOptions) -> Self where Self : Sized;
|
|
||||||
fn axis(&self, cfg: &GraphConfig, dimension: Dimension) -> Axis; // TODO simplify this
|
fn axis(&self, cfg: &GraphConfig, dimension: Dimension) -> Axis; // TODO simplify this
|
||||||
fn process(&mut self, cfg: &GraphConfig, data: &Matrix<f64>) -> Vec<DataSet>;
|
fn process(&mut self, cfg: &GraphConfig, data: &Matrix<f64>) -> Vec<DataSet>;
|
||||||
fn mode_str(&self) -> &'static str;
|
fn mode_str(&self) -> &'static str;
|
||||||
|
@ -83,3 +82,29 @@ impl DataSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn update_value_f(val: &mut f64, base: f64, magnitude: f64, range: std::ops::Range<f64>) {
|
||||||
|
let delta = base * magnitude;
|
||||||
|
if *val + delta > range.end {
|
||||||
|
*val = range.end
|
||||||
|
} else if *val + delta < range.start {
|
||||||
|
*val = range.start
|
||||||
|
} else {
|
||||||
|
*val += delta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_value_i(val: &mut u32, inc: bool, base: u32, magnitude: f64, range: std::ops::Range<u32>) {
|
||||||
|
let delta = (base as f64 * magnitude) as u32;
|
||||||
|
if inc {
|
||||||
|
if range.end - delta < *val {
|
||||||
|
*val = range.end
|
||||||
|
} else {
|
||||||
|
*val += delta
|
||||||
|
}
|
||||||
|
} else if range.start + delta > *val {
|
||||||
|
*val = range.start
|
||||||
|
} else {
|
||||||
|
*val -= delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use crossterm::event::{Event, KeyModifiers, KeyCode};
|
use crossterm::event::{Event, KeyModifiers, KeyCode};
|
||||||
use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
||||||
|
|
||||||
use crate::{app::{update_value_f, update_value_i}, input::Matrix};
|
use crate::input::Matrix;
|
||||||
|
|
||||||
use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
use super::{update_value_f, update_value_i, DataSet, Dimension, DisplayMode, GraphConfig};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Oscilloscope {
|
pub struct Oscilloscope {
|
||||||
|
@ -15,10 +15,6 @@ pub struct Oscilloscope {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayMode for Oscilloscope {
|
impl DisplayMode for Oscilloscope {
|
||||||
fn from_args(_opts: &crate::cfg::SourceOptions) -> Self {
|
|
||||||
Oscilloscope::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mode_str(&self) -> &'static str {
|
fn mode_str(&self) -> &'static str {
|
||||||
"oscillo"
|
"oscillo"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::collections::VecDeque;
|
||||||
use crossterm::event::{Event, KeyCode};
|
use crossterm::event::{Event, KeyCode};
|
||||||
use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
use ratatui::{widgets::{Axis, GraphType}, style::Style, text::Span};
|
||||||
|
|
||||||
use crate::{app::update_value_i, input::Matrix};
|
use crate::input::Matrix;
|
||||||
|
|
||||||
use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
use super::{update_value_i, DataSet, Dimension, DisplayMode, GraphConfig};
|
||||||
|
|
||||||
use rustfft::{FftPlanner, num_complex::Complex};
|
use rustfft::{FftPlanner, num_complex::Complex};
|
||||||
|
|
||||||
|
@ -37,17 +37,20 @@ pub fn hann_window(samples: &[f64]) -> Vec<f64> {
|
||||||
windowed_samples
|
windowed_samples
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayMode for Spectroscope {
|
#[cfg(feature = "app")]
|
||||||
fn from_args(opts: &crate::cfg::SourceOptions) -> Self {
|
impl From<&crate::cfg::SourceOptions> for Spectroscope {
|
||||||
|
fn from(value: &crate::cfg::SourceOptions) -> Self {
|
||||||
Spectroscope {
|
Spectroscope {
|
||||||
sampling_rate: opts.sample_rate,
|
sampling_rate: value.sample_rate,
|
||||||
buffer_size: opts.buffer,
|
buffer_size: value.buffer,
|
||||||
average: 1, buf: Vec::new(),
|
average: 1, buf: Vec::new(),
|
||||||
window: false,
|
window: false,
|
||||||
log_y: true,
|
log_y: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayMode for Spectroscope {
|
||||||
fn mode_str(&self) -> &'static str {
|
fn mode_str(&self) -> &'static str {
|
||||||
"spectro"
|
"spectro"
|
||||||
}
|
}
|
||||||
|
@ -80,7 +83,7 @@ impl DisplayMode for Spectroscope {
|
||||||
Dimension::X => ("frequency -", [20.0f64.ln(), ((cfg.samples as f64 / cfg.width as f64) * 20000.0).ln()]),
|
Dimension::X => ("frequency -", [20.0f64.ln(), ((cfg.samples as f64 / cfg.width as f64) * 20000.0).ln()]),
|
||||||
Dimension::Y => (
|
Dimension::Y => (
|
||||||
if self.log_y { "| level" } else { "| amplitude" },
|
if self.log_y { "| level" } else { "| amplitude" },
|
||||||
[if self.log_y { 0. } else { 0.0 }, cfg.scale * 7.5] // very arbitrary but good default
|
[0.0, cfg.scale * 7.5] // very arbitrary but good default
|
||||||
),
|
),
|
||||||
// TODO super arbitraty! wtf! also ugly inline ifs, get this thing together!
|
// TODO super arbitraty! wtf! also ugly inline ifs, get this thing together!
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,10 +8,6 @@ use super::{DisplayMode, GraphConfig, DataSet, Dimension};
|
||||||
pub struct Vectorscope {}
|
pub struct Vectorscope {}
|
||||||
|
|
||||||
impl DisplayMode for Vectorscope {
|
impl DisplayMode for Vectorscope {
|
||||||
fn from_args(_opts: &crate::cfg::SourceOptions) -> Self {
|
|
||||||
Vectorscope::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mode_str(&self) -> &'static str {
|
fn mode_str(&self) -> &'static str {
|
||||||
"vector"
|
"vector"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ pub mod format;
|
||||||
#[cfg(feature = "pulseaudio")]
|
#[cfg(feature = "pulseaudio")]
|
||||||
pub mod pulse;
|
pub mod pulse;
|
||||||
|
|
||||||
|
#[cfg(feature = "file")]
|
||||||
pub mod file;
|
pub mod file;
|
||||||
|
|
||||||
|
#[cfg(feature = "cpal")]
|
||||||
pub mod cpal;
|
pub mod cpal;
|
||||||
|
|
||||||
pub type Matrix<T> = Vec<Vec<T>>;
|
pub type Matrix<T> = Vec<Vec<T>>;
|
||||||
|
|
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
pub mod music;
|
||||||
|
pub mod input;
|
||||||
|
pub mod display;
|
||||||
|
|
||||||
|
#[cfg(feature = "app")]
|
||||||
|
pub mod cfg;
|
||||||
|
|
||||||
|
#[cfg(feature = "app")]
|
||||||
|
pub mod app;
|
14
src/main.rs
14
src/main.rs
|
@ -1,11 +1,5 @@
|
||||||
mod app;
|
use scope::app::App;
|
||||||
mod cfg;
|
use scope::cfg::{ScopeArgs, ScopeSource};
|
||||||
mod music;
|
|
||||||
mod input;
|
|
||||||
mod display;
|
|
||||||
|
|
||||||
use app::App;
|
|
||||||
use cfg::{ScopeArgs, ScopeSource};
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||||
use crossterm::{execute, terminal::{
|
use crossterm::{execute, terminal::{
|
||||||
|
@ -25,12 +19,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
#[cfg(feature = "file")]
|
#[cfg(feature = "file")]
|
||||||
ScopeSource::File { path, limit_rate } => {
|
ScopeSource::File { path, limit_rate } => {
|
||||||
input::file::FileSource::new(&path, &args.opts, limit_rate)?
|
scope::input::file::FileSource::new(&path, &args.opts, limit_rate)?
|
||||||
},
|
},
|
||||||
|
|
||||||
#[cfg(feature = "cpal")]
|
#[cfg(feature = "cpal")]
|
||||||
ScopeSource::Audio { device, timeout } => {
|
ScopeSource::Audio { device, timeout } => {
|
||||||
input::cpal::DefaultAudioDeviceWithCPAL::new(device.as_deref(), &args.opts, timeout)?
|
scope::input::cpal::DefaultAudioDeviceWithCPAL::new(device.as_deref(), &args.opts, timeout)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue