1use std::path::PathBuf;
4
5use super::reading::{count, hertz};
6use super::{CacheAt, Command, value};
7use sva_core::CliError;
8use sva_core::solve::{DEFAULT_COST_TOL, DEFAULT_MAX_RENDERS, DEFAULT_STEP_TOL, Stops, Vary, vary};
9
10#[derive(Debug, PartialEq)]
11pub struct SolveArgs {
12 pub cost: String,
14 pub vary: Vec<Vary>,
15 pub stops: Stops,
16 pub sets: Vec<(String, String)>,
17 pub rate: Option<u32>,
18 pub threads: Option<std::num::NonZeroUsize>,
19 pub cache: CacheAt,
20 pub write: bool,
21 pub confirm: bool,
22}
23
24fn number(raw: &str, flag: &str, fits: impl Fn(f64) -> bool, says: &str) -> Result<f64, CliError> {
25 match raw.parse::<f64>() {
26 Ok(v) if v.is_finite() && fits(v) => Ok(v),
27 _ => Err(CliError::usage(
28 "cli.bad_value",
29 format!("{flag} takes {says}, not `{raw}`"),
30 )),
31 }
32}
33
34pub(super) fn solve_args(rest: &[String]) -> Result<Command, CliError> {
35 let mut it = rest.iter();
36 let cost = it.next().filter(|a| !a.starts_with("--")).ok_or_else(|| {
37 CliError::usage(
38 "cli.missing_argument",
39 "solve needs a cost, as `sva-cli solve 'norm(repr.bands(@master) - \
40 repr.bands(sample(\"../ref.wav\")))' --vary gain=0..2`, or a cost file, as \
41 `sva-cli solve @fits/lead --vary gain=0..2`",
42 )
43 })?;
44 let mut stops = Stops {
45 max_renders: DEFAULT_MAX_RENDERS,
46 step_tol: DEFAULT_STEP_TOL,
47 cost_tol: DEFAULT_COST_TOL,
48 };
49 let (mut vary_all, mut sets) = (Vec::new(), Vec::new());
50 let (mut rate, mut threads, mut cache) = (None, None, CacheAt::Platform);
51 let (mut write, mut confirm) = (false, false);
52 while let Some(flag) = it.next() {
53 match flag.as_str() {
54 "--vary" => vary_all.push(vary(&value(&mut it, "--vary")?)?),
55 "--set" => sets.push(sva_core::set(&value(&mut it, "--set")?)?),
56 "--max-renders" => {
57 let raw = value(&mut it, "--max-renders")?;
58 stops.max_renders = count(&raw, "--max-renders", "renders")?.get();
59 }
60 "--step-tol" => {
61 let raw = value(&mut it, "--step-tol")?;
62 let says = "a fraction of each variable's first step above 0 and at most 1";
63 stops.step_tol = number(&raw, "--step-tol", |v| v > 0.0 && v <= 1.0, says)?;
64 }
65 "--cost-tol" => {
66 let raw = value(&mut it, "--cost-tol")?;
67 let says = "a cost at or above 0";
68 stops.cost_tol = number(&raw, "--cost-tol", |v| v >= 0.0, says)?;
69 }
70 "--rate" => rate = Some(hertz(&value(&mut it, "--rate")?)?),
71 "--threads" => {
72 threads = Some(count(
73 &value(&mut it, "--threads")?,
74 "--threads",
75 "threads",
76 )?);
77 }
78 "--cache" => {
79 cache = match value(&mut it, "--cache")?.as_str() {
80 "none" => CacheAt::Off,
81 path => CacheAt::Path(PathBuf::from(path)),
82 };
83 }
84 "--write" => write = true,
85 "--confirm" => confirm = true,
86 other => {
87 return Err(CliError::usage(
88 "cli.unknown_argument",
89 format!("unknown argument `{other}`"),
90 ));
91 }
92 }
93 }
94 if vary_all.is_empty() {
95 return Err(CliError::usage(
96 "solve.no_vary",
97 "solve needs at least one `--vary <name>=<lo>..<hi>`, a variable under \
98 `variables/` it fits",
99 ));
100 }
101 if confirm && !write {
102 return Err(CliError::usage(
103 "cli.confirm_without_write",
104 "`--confirm` lets `--write` replace a variable's file; write both, or neither",
105 ));
106 }
107 Ok(Command::Solve(Box::new(SolveArgs {
108 cost: cost.clone(),
109 vary: vary_all,
110 stops,
111 sets,
112 rate,
113 threads,
114 cache,
115 write,
116 confirm,
117 })))
118}