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