1use std::path::Path;
4
5use sva_core::{
6 Answer, Asked, CliError, Destination, Diagnostic, Hear, Job, Output, Printed, Report,
7 SAMPLE_LIMIT, Session, Tier, cwd, execute_over, query_data,
8};
9use sva_engine::{Buffer, PSYCHOACOUSTIC_V1, answer_buffer, cache_log};
10
11use crate::args::{AnalyzeArgs, RenderArgs};
12use crate::destination::{Framing, refuse_inside, refuse_replacing, write};
13use crate::device::Hearing;
14use crate::directory::Directory;
15use crate::store::wait;
16use crate::wav::{SampleEncoding, read_channels};
17use sva_core::success_envelope;
18
19pub fn render(args: &RenderArgs) -> Result<String, CliError> {
21 let here = crate::composition::composition()?;
22 let (dir, target) = crate::composition::located(&here, &args.target)?;
23 for dest in args.asked.iter().filter_map(Asked::path) {
25 refuse_inside(&dir, dest)?;
26 refuse_replacing(dest, args.confirm)?;
27 }
28 let mut warnings = Vec::new();
29 let tier = crate::store::opened(&args.cache, &mut warnings)?;
30 let answered = answered(args, &dir, &target, tier, &mut warnings);
31 crate::store::persisted(tier, &mut warnings);
32 answered.map(|data| success_envelope(&data, &warnings))
33}
34
35fn answered(
37 args: &RenderArgs,
38 dir: &Path,
39 target: &str,
40 tier: &Tier<Directory>,
41 warnings: &mut Vec<Diagnostic>,
42) -> Result<String, CliError> {
43 let files = sva_ast::Dir::at(dir);
44 let source = sva_core::overlaid(&files, &args.sets)?;
45 let logs = std::env::var("SVA_LOG").is_ok_and(|level| level == "debug");
46 let hearing = hearing(args)?;
47 let job = Job {
48 until: args.until.as_deref(),
49 rate: args.rate,
50 bits: args.bits,
51 asked: &args.asked,
52 threads: args.threads,
53 hear: hearing.as_ref().map(|hearing| hearing as &dyn Hear),
54 ..Job::over(&source, target)
55 };
56 let rendered = wait(execute_over(job, tier, &mut Session::default()));
57 let stopped = hearing.as_ref().and_then(Hearing::stopped);
58 let rendered = match (rendered, stopped) {
59 (_, Some(why)) => return Err(why),
60 (rendered, None) => rendered?,
61 };
62 let played = hearing.map(Hearing::finish).transpose()?;
63
64 warnings.extend(rendered.warnings.iter().map(sva_core::warning_diagnostic));
65 let rate = rendered.config.rate;
66 let stats = rendered.render.cache_stats.as_ref();
67 if let Some(why) = stats.and_then(|stats| stats.unstaged.as_deref()) {
68 let message = format!("this render stopped writing what it computed: {why}");
69 warnings.push(crate::store::warning("store.unstaged", message));
70 }
71 if let (true, Some(stats)) = (logs, stats) {
72 eprint!("{}", cache_log(stats, rate));
73 }
74 let bits = rendered.config.profile.precision_bits;
75 let interval = rendered
76 .render
77 .range
78 .map(|r| (r.start_secs(rate), r.end as f64 / f64::from(rate)));
79 let framing = Framing {
80 target: args.target.clone(),
81 rate,
82 bits: Some(bits),
83 interval,
84 profile: rendered.config.profile.name,
85 encoding: SampleEncoding::of(bits),
86 replace: args.confirm,
87 };
88
89 let kept: Vec<&Asked> = args.asked.iter().filter(|a| !plays(a)).collect();
90 let mut taken = Vec::with_capacity(kept.len());
91 for asked in &kept {
92 let node = asked.node.as_deref().unwrap_or(&rendered.target);
93 let answer = rendered.answer(node, asked.representation)?;
94 taken.push(match asked.brief {
95 true => briefed(answer),
96 false => answer,
97 });
98 }
99 let (answers, written) = routed(&kept, taken, &framing)?;
100
101 Ok(query_data(&Report {
102 target: &args.target,
103 rate,
104 bits: Some(bits),
105 interval,
106 profile: rendered.config.profile.name,
107 label: rendered.label(),
108 written: &written,
109 played: played.as_ref(),
110 answers: &answers,
111 limit: Some(SAMPLE_LIMIT),
112 }))
113}
114
115fn plays(asked: &Asked) -> bool {
116 matches!(asked.dest, Some(Destination::Alsa { .. }))
117}
118
119fn hearing(args: &RenderArgs) -> Result<Option<Hearing>, CliError> {
122 let alsa = args.asked.iter().find_map(|a| match &a.dest {
123 Some(Destination::Alsa {
124 device,
125 buffer_secs,
126 }) => Some((a.name.as_str(), device, *buffer_secs)),
127 _ => None,
128 });
129 let Some((name, device, buffer_secs)) = alsa else {
130 return Ok(None);
131 };
132 let encoding = SampleEncoding::of(sva_core::precision_bits(args.bits)?);
133 let named = (name, args.target.as_str());
134 Hearing::open(device, buffer_secs, encoding, named).map(Some)
135}
136
137type Routed<'a> = (Vec<Printed>, Vec<(String, &'a Path)>);
139
140fn routed<'a>(
143 asked: &[&'a Asked],
144 taken: Vec<Answer>,
145 framing: &Framing,
146) -> Result<Routed<'a>, CliError> {
147 let mut answers = Vec::new();
148 let mut written = Vec::new();
149 for (one, answer) in asked.iter().zip(taken) {
150 let printed = Printed {
151 name: one.name.clone(),
152 answer,
153 skim: one.skim,
154 };
155 match &one.dest {
156 None => answers.push(printed),
157 Some(Destination::File(dest)) => {
158 write(&printed, dest, framing)?;
159 written.push((one.name.clone(), dest.as_path()));
160 }
161 Some(Destination::Alsa { .. }) => {
162 unreachable!("a device heard its reading as it rendered")
163 }
164 }
165 }
166 Ok((answers, written))
167}
168
169fn briefed(answer: Answer) -> Answer {
172 let Output::Ledger(entries) = answer.value else {
173 return answer;
174 };
175 Answer {
176 value: Output::Ledger(
177 entries
178 .into_iter()
179 .filter(|e| e.clipped == Some(true))
180 .collect(),
181 ),
182 ..answer
183 }
184}
185
186pub fn analyze(args: &AnalyzeArgs) -> Result<String, CliError> {
189 let dir = cwd()?;
190 let composition = is_composition(&dir);
191 for dest in args.asked.iter().filter_map(Asked::path) {
192 if composition {
193 refuse_inside(&dir, dest)?;
194 }
195 refuse_replacing(dest, args.confirm)?;
196 }
197 let (planes, rate) = read_channels(&args.path)?;
198 let target = args.path.display().to_string();
199 let held = Buffer::of_planes(
200 rate,
201 planes
202 .iter()
203 .map(|p| p.iter().map(|v| f64::from(*v)).collect())
204 .collect(),
205 );
206 crate::args::check_frame(&args.asked, rate)?;
207 let buffer = held;
208 let interval = Some((0.0, buffer.len() as f64 / f64::from(rate)));
209 let framing = Framing {
210 target: target.clone(),
211 rate,
212 bits: None,
213 interval,
214 profile: PSYCHOACOUSTIC_V1.name,
215 encoding: SampleEncoding::Float,
216 replace: args.confirm,
217 };
218
219 let mut taken = Vec::with_capacity(args.asked.len());
220 for asked in &args.asked {
221 taken.push(
222 answer_buffer(
223 &target,
224 &buffer,
225 asked.representation,
226 PSYCHOACOUSTIC_V1.name,
227 )
228 .map_err(CliError::Engine)?,
229 );
230 }
231 let (answers, written) = routed(&args.asked.iter().collect::<Vec<_>>(), taken, &framing)?;
232 Ok(success_envelope(
233 &query_data(&Report {
234 target: &target,
235 rate,
236 bits: None,
237 interval,
238 profile: PSYCHOACOUSTIC_V1.name,
239 label: None,
240 written: &written,
241 played: None,
242 answers: &answers,
243 limit: Some(SAMPLE_LIMIT),
244 }),
245 &[],
246 ))
247}
248
249fn is_composition(dir: &Path) -> bool {
251 dir.join(sva_core::ROOT).is_file() || dir.join(sva_ast::VARIABLES).is_dir()
252}