1use std::path::Path;
4
5use sva_core::{
6 Answer, Asked, CliError, Diagnostic, Job, Output, Printed, Report, SAMPLE_LIMIT, Tier, cwd,
7 execute_over, query_data,
8};
9use sva_engine::{
10 Buffer, DEFAULT_FRAME_SECS, PSYCHOACOUSTIC_V1, Representation, answer_buffer, cache_log,
11};
12
13use crate::args::{AnalyzeArgs, RenderArgs};
14use crate::destination::{Framing, refuse_inside, refuse_replacing, write, write_analysis};
15use crate::directory::Directory;
16use crate::store::wait;
17use crate::wav::{SampleEncoding, read_channels};
18use sva_core::success_envelope;
19
20pub fn render(args: &RenderArgs) -> Result<String, CliError> {
22 let here = crate::composition::composition()?;
23 let (dir, target) = crate::composition::located(&here, &args.target)?;
24 for asked in &args.asked {
26 if let Some(dest) = asked.dest.as_deref() {
27 refuse_inside(&dir, dest)?;
28 refuse_replacing(dest, args.confirm)?;
29 }
30 }
31 let mut warnings = Vec::new();
32 let tier = crate::store::opened(&args.cache, &mut warnings)?;
33 let answered = answered(args, &dir, &target, tier, &mut warnings);
34 crate::store::persisted(tier, &mut warnings);
35 answered.map(|data| success_envelope(&data, &warnings))
36}
37
38fn answered(
40 args: &RenderArgs,
41 dir: &Path,
42 target: &str,
43 tier: &Tier<Directory>,
44 warnings: &mut Vec<Diagnostic>,
45) -> Result<String, CliError> {
46 let source = sva_ast::Dir::at(dir);
47 let logs = std::env::var("SVA_LOG").is_ok_and(|level| level == "debug");
48 let job = Job {
49 until: args.until.as_deref(),
50 rate: args.rate,
51 bits: args.bits,
52 asked: &args.asked,
53 flop_budget: args.flop_budget,
54 ..Job::over(&source, target)
55 };
56 let rendered = wait(execute_over(job, tier))?;
57
58 let rate = rendered.config.rate;
59 let stats = rendered.render.cache_stats.as_ref();
60 if let Some(why) = stats.and_then(|stats| stats.unstaged.as_deref()) {
61 let message = format!("this render stopped writing what it computed: {why}");
62 warnings.push(crate::store::warning("store.unstaged", message));
63 }
64 if let (true, Some(stats)) = (logs, stats) {
65 eprint!("{}", cache_log(stats, rate));
66 }
67 let bits = rendered.config.profile.precision_bits;
68 let interval = rendered
69 .render
70 .range
71 .map(|r| (r.start_secs(rate), r.end as f64 / f64::from(rate)));
72 let framing = Framing {
73 target: args.target.clone(),
74 rate,
75 bits: Some(bits),
76 interval,
77 profile: rendered.config.profile.name,
78 encoding: SampleEncoding::of(bits),
79 replace: args.confirm,
80 };
81
82 let mut taken = Vec::with_capacity(args.asked.len());
83 for asked in &args.asked {
84 let node = asked.node.as_deref().unwrap_or(&rendered.target);
85 let answer = rendered.answer(node, asked.representation)?;
86 taken.push(match asked.brief {
87 true => briefed(answer),
88 false => answer,
89 });
90 }
91 let (answers, written) = routed(&args.asked, taken, &framing)?;
92
93 Ok(query_data(&Report {
94 target: &args.target,
95 rate,
96 bits: Some(bits),
97 interval,
98 profile: rendered.config.profile.name,
99 label: rendered.label(),
100 written: &written,
101 answers: &answers,
102 analyses: &[],
103 limit: Some(SAMPLE_LIMIT),
104 }))
105}
106
107type Routed<'a> = (Vec<Printed>, Vec<(String, &'a Path)>);
109
110fn routed<'a>(
112 asked: &'a [Asked],
113 taken: Vec<Answer>,
114 framing: &Framing,
115) -> Result<Routed<'a>, CliError> {
116 let mut answers = Vec::new();
117 let mut written = Vec::new();
118 for (one, answer) in asked.iter().zip(taken) {
119 let printed = Printed {
120 name: one.name.clone(),
121 answer,
122 skim: one.skim,
123 };
124 match one.dest.as_deref() {
125 None => answers.push(printed),
126 Some(dest) => {
127 write(&printed, dest, framing)?;
128 written.push((one.name.clone(), dest));
129 }
130 }
131 }
132 Ok((answers, written))
133}
134
135fn briefed(answer: Answer) -> Answer {
138 let Output::Ledger(entries) = answer.value else {
139 return answer;
140 };
141 Answer {
142 value: Output::Ledger(
143 entries
144 .into_iter()
145 .filter(|e| e.clipped == Some(true))
146 .collect(),
147 ),
148 ..answer
149 }
150}
151
152pub fn analyze(args: &AnalyzeArgs) -> Result<String, CliError> {
155 let dir = cwd()?;
156 let composition = is_composition(&dir);
157 let destinations = args
158 .asked
159 .iter()
160 .map(|a| a.dest.as_deref())
161 .chain(args.analyses.iter().map(|a| a.dest.as_deref()));
162 for dest in destinations {
163 let Some(dest) = dest else {
164 continue;
165 };
166 if composition {
167 refuse_inside(&dir, dest)?;
168 }
169 refuse_replacing(dest, args.confirm)?;
170 }
171 let (planes, rate) = read_channels(&args.path)?;
172 let target = args.path.display().to_string();
173 let held = Buffer::of_planes(
174 rate,
175 planes
176 .iter()
177 .map(|p| p.iter().map(|v| f64::from(*v)).collect())
178 .collect(),
179 );
180 crate::args::check_frame(&args.asked, rate)?;
181 let buffer = held;
182 let interval = Some((0.0, buffer.len() as f64 / f64::from(rate)));
183 let framing = Framing {
184 target: target.clone(),
185 rate,
186 bits: None,
187 interval,
188 profile: PSYCHOACOUSTIC_V1.name,
189 encoding: SampleEncoding::Float,
190 replace: args.confirm,
191 };
192
193 let mut taken = Vec::with_capacity(args.asked.len());
194 for asked in &args.asked {
195 taken.push(
196 answer_buffer(
197 &target,
198 &buffer,
199 asked.representation,
200 PSYCHOACOUSTIC_V1.name,
201 )
202 .map_err(CliError::Engine)?,
203 );
204 }
205 let heard = analysed(args, &buffer)?;
206 let (answers, mut written) = routed(&args.asked, taken, &framing)?;
207 let mut analyses = Vec::new();
208 for (analysis, value) in args.analyses.iter().zip(heard) {
209 match analysis.dest.as_deref() {
210 None => analyses.push((analysis.name.clone(), value)),
211 Some(dest) => {
212 write_analysis(&analysis.name, &value, dest, &framing)?;
213 written.push((analysis.name.clone(), dest));
214 }
215 }
216 }
217 Ok(success_envelope(
218 &query_data(&Report {
219 target: &target,
220 rate,
221 bits: None,
222 interval,
223 profile: PSYCHOACOUSTIC_V1.name,
224 label: None,
225 written: &written,
226 answers: &answers,
227 analyses: &analyses,
228 limit: Some(SAMPLE_LIMIT),
229 }),
230 &[],
231 ))
232}
233
234fn analysed(args: &AnalyzeArgs, buffer: &Buffer) -> Result<Vec<String>, CliError> {
237 if args.analyses.is_empty() {
238 return Ok(Vec::new());
239 }
240 let rate = f64::from(buffer.rate);
241 let mono: Vec<f32> = buffer.plane(0).iter().map(|s| *s as f32).collect();
242 let against = match args.analyses.iter().find_map(|a| a.against.as_deref()) {
243 Some(path) => {
244 let (planes, _) = read_channels(path)?;
245 Some(planes.first().cloned().unwrap_or_default())
246 }
247 None => None,
248 };
249 let name = args.path.display().to_string();
250 let read = |representation| {
251 answer_buffer(&name, buffer, representation, PSYCHOACOUSTIC_V1.name).map(|a| a.value)
252 };
253 let envelope = match read(Representation::Envelope { frame_secs: None }) {
254 Ok(Output::Envelope(frames)) => frames,
255 _ => Vec::new(),
256 };
257 let image = match read(Representation::Stereo {
258 frame_secs: DEFAULT_FRAME_SECS,
259 }) {
260 Ok(Output::Stereo(found)) => Some(*found),
261 _ => None,
262 };
263 let request = sva_analysis::Request {
264 samples: &mono,
265 sample_rate: rate,
266 start_secs: 0.0,
267 frame_secs: DEFAULT_FRAME_SECS,
268 tempo: None,
269 envelope: &envelope,
270 stereo: image.as_ref(),
271 against: against.as_deref(),
272 gated: false,
273 input_envelope: None,
274 };
275 let mut out = Vec::with_capacity(args.analyses.len());
276 for analysis in &args.analyses {
277 out.push(
278 sva_analysis::run(&analysis.name, &request)
279 .map_err(|sva_analysis::AnalysisError(why)| CliError::Usage(why))?,
280 );
281 }
282 Ok(out)
283}
284
285fn is_composition(dir: &Path) -> bool {
287 dir.join(sva_core::ROOT).is_file() || dir.join(sva_ast::VARIABLES).is_dir()
288}