scope/
cfg.rs

1use clap::{Parser, Subcommand};
2
3use crate::music::Note;
4
5const HELP_TEMPLATE : &str = "{before-help}\
6{name} {version} -- by {author}
7{about}
8
9{usage-heading} {usage}
10
11{all-args}{after-help}
12";
13
14/// a simple oscilloscope/vectorscope for your terminal
15#[derive(Parser, Debug)]
16#[command(author, version, about, long_about = None, help_template = HELP_TEMPLATE)]
17pub struct ScopeArgs {
18	#[clap(subcommand)]
19	pub source: ScopeSource,
20	
21	#[command(flatten)]
22	pub opts: SourceOptions,
23
24	#[command(flatten)]
25	pub ui: UiOptions,
26}
27
28#[derive(Debug, Clone, Parser)]
29pub struct UiOptions {
30	/// floating point vertical scale, from 0 to 1
31	#[arg(short, long, value_name = "x", default_value_t = 1.0)]
32	pub scale: f32,
33
34	/// use vintage looking scatter mode instead of line mode
35	#[arg(long, default_value_t = false)]
36	pub scatter: bool,
37
38	/// don't draw reference line
39	#[arg(long, default_value_t = false)]
40	pub no_reference: bool,
41
42	/// hide UI and only draw waveforms
43	#[arg(long, default_value_t = false)]
44	pub no_ui: bool,
45
46	/// don't use braille dots for drawing lines
47	#[arg(long, default_value_t = false)]
48	pub no_braille: bool,
49}
50
51#[derive(Debug, Clone, Subcommand)]
52pub enum ScopeSource {
53
54	#[cfg(feature = "pulseaudio")]
55	/// use PulseAudio Simple api to read data from an audio sink
56	Pulse {
57		/// source device to attach to
58		device: Option<String>,
59
60		/// PulseAudio server buffer size, in block number
61		#[arg(long, value_name = "N", default_value_t = 32)]
62		server_buffer: u32,
63	},
64
65	#[cfg(feature = "file")]
66	/// use a file from filesystem and read its content
67	File {
68		/// path on filesystem of file or pipe
69		path: String,
70
71		/// limit data flow to match requested sample rate (UNIMPLEMENTED)
72		#[arg(short, long, default_value_t = false)]
73		limit_rate: bool,
74	},
75
76	#[cfg(feature = "cpal")]
77	/// use new experimental CPAL backend
78	Audio {
79		/// source device to attach to
80		device: Option<String>,
81
82		/// timeout (in seconds) waiting for audio stream
83		#[arg(long, default_value_t = 60)]
84		timeout: u64,
85
86		/// just list available devices and quit
87		#[arg(long, default_value_t = false)]
88		list: bool,
89	}
90}
91
92#[derive(Debug, Clone, Parser)]
93pub struct SourceOptions {
94	/// number of channels to open
95	#[arg(long, value_name = "N", default_value_t = 2)]
96	pub channels: usize,
97
98	/// size of audio buffer, and width of scope
99	#[arg(short, long, value_name = "SIZE", default_value_t = 2048)]
100	pub buffer: u32,
101
102	/// sample rate to use
103	#[arg(long, value_name = "HZ", default_value_t = 48000)]
104	pub sample_rate: u32,
105
106	/// tune buffer size to be in tune with given note (overrides buffer option)
107	#[arg(long, value_name = "NOTE")]
108	pub tune: Option<String>,
109}
110
111// TODO its convenient to keep this here but it's not really the best place...
112impl SourceOptions {
113	pub fn tune(&mut self) {
114		if let Some(txt) = &self.tune { // TODO make it less jank
115			if let Ok(note) = txt.parse::<Note>() {
116				self.buffer = note.tune_buffer_size(self.sample_rate);
117				while self.buffer % (self.channels as u32 * 2) != 0 { // TODO customizable bit depth
118					self.buffer += 1; // TODO jank but otherwise it doesn't align
119				}
120			} else {
121				eprintln!("[!] Unrecognized note '{}', ignoring option", txt);
122			}
123		}
124	}
125}