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