1use std::path::{Path, PathBuf};
4
5use sva_engine::{DEFAULT_FRAME_SECS, Horizon, Representation};
6
7use crate::cli_error::CliError;
8
9pub const REPRESENTATIONS: [&str; 15] = [
10 "lines",
11 "atoms",
12 "spectrum",
13 "envelope",
14 "derivative",
15 "samples",
16 "ledger",
17 "pitch",
18 "formants",
19 "stereo",
20 "bands",
21 "crest",
22 "loudness",
23 "alias",
24 "bindings",
25];
26
27pub const RETIRED: [(&str, &str); 2] = [
28 ("exact-envelope", "envelope, symbolic on a closed form"),
29 ("exact-derivative", "derivative, symbolic on a closed form"),
30];
31
32#[derive(Clone, Debug, PartialEq)]
34pub struct Asked {
35 pub name: String,
36 pub representation: Representation,
37 pub dest: Option<PathBuf>,
38}
39
40pub fn is_wav(path: &Path) -> bool {
41 path.extension()
42 .is_some_and(|e| e.eq_ignore_ascii_case("wav"))
43}
44
45pub const DEFAULT_OVERSAMPLE: u32 = 4;
46pub const DEFAULT_MAX_PEAKS: usize = 16;
47pub const DEFAULT_LEDGER_DEPTH: usize = 3;
48
49#[derive(Clone, Copy, Debug, PartialEq)]
50pub struct Shaping {
51 pub frame_secs: Option<f64>,
52 pub depth: usize,
53 pub peaks: usize,
54 pub oversample: u32,
55}
56
57impl Default for Shaping {
58 fn default() -> Shaping {
59 Shaping {
60 frame_secs: None,
61 depth: DEFAULT_LEDGER_DEPTH,
62 peaks: DEFAULT_MAX_PEAKS,
63 oversample: DEFAULT_OVERSAMPLE,
64 }
65 }
66}
67
68pub fn representation_for(name: &str, shape: Shaping) -> Option<Representation> {
69 let frame = shape.frame_secs.unwrap_or(DEFAULT_FRAME_SECS);
70 Some(match name {
71 "spectrum" => Representation::Spectrum {
72 max_peaks: shape.peaks,
73 frame_secs: shape.frame_secs,
74 },
75 "envelope" => Representation::Envelope {
76 frame_secs: shape.frame_secs,
77 },
78 "pitch" => Representation::Pitch {
79 max_notes: shape.peaks,
80 frame_secs: frame,
81 },
82 "formants" => Representation::Formants {
83 max_formants: shape.peaks,
84 frame_secs: frame,
85 },
86 "stereo" => Representation::Stereo { frame_secs: frame },
87 "ledger" => Representation::Ledger { depth: shape.depth },
88 "alias" => Representation::Alias {
89 oversample: shape.oversample,
90 },
91 other => Representation::from_name(other)?,
92 })
93}
94
95pub fn retired(name: &str) -> Option<&'static str> {
96 RETIRED
97 .iter()
98 .find(|(gone, _)| *gone == name)
99 .map(|(_, write)| *write)
100}
101
102#[derive(Clone, Copy, Debug, PartialEq)]
105pub enum WindowEdge {
106 Secs(f64),
107 Bars(f64),
108 End,
109}
110
111pub fn window_edge(raw: &str, flag: &str) -> Result<WindowEdge, CliError> {
112 let refuse = || {
113 CliError::Usage(format!(
114 "{flag} needs a time — a number of seconds, `<n>s`, `<n>b`, or `end`, got `{raw}`"
115 ))
116 };
117 if raw == "end" {
118 return Ok(WindowEdge::End);
119 }
120 let (number, wrap): (&str, fn(f64) -> WindowEdge) = match raw.strip_suffix('b') {
121 Some(bars) => (bars, WindowEdge::Bars),
122 None => (raw.strip_suffix('s').unwrap_or(raw), WindowEdge::Secs),
123 };
124 match number.parse::<f64>() {
125 Ok(v) if v.is_finite() && v >= 0.0 => Ok(wrap(v)),
126 _ => Err(refuse()),
127 }
128}
129
130fn edge(
131 given: Option<WindowEdge>,
132 flag: &str,
133 unset: f64,
134 seconds_per_bar: Option<f64>,
135) -> Result<f64, CliError> {
136 let secs = match given {
137 None | Some(WindowEdge::End) => return Ok(unset),
138 Some(WindowEdge::Secs(v)) => v,
139 Some(WindowEdge::Bars(v)) => match seconds_per_bar {
140 Some(secs) => v * secs,
141 None => {
142 return Err(CliError::BadTempo(format!(
143 "{flag} is written in bars and nothing here declares a tempo; \
144 state `variables/bpm` and `variables/meter`, or write seconds"
145 )));
146 }
147 },
148 };
149 match secs.is_finite() && secs >= 0.0 {
150 true => Ok(secs),
151 false => Err(CliError::Usage(format!(
152 "{flag} needs a time at or after zero, got `{secs}`"
153 ))),
154 }
155}
156
157pub fn window_for(
160 from: Option<WindowEdge>,
161 to: Option<WindowEdge>,
162 seconds_per_bar: Option<f64>,
163) -> Result<Horizon, CliError> {
164 Ok(Horizon::secs(
165 edge(from, "--from", 0.0, seconds_per_bar)?,
166 edge(to, "--to", f64::INFINITY, seconds_per_bar)?,
167 ))
168}