1use std::path::Path;
4
5use sva_core::{
6 Answer, Asked, CliError, Diagnostic, Job, Output, Printed, Report, SAMPLE_LIMIT, Session, Tier,
7 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::directory::Directory;
14use crate::store::wait;
15use crate::wav::{SampleEncoding, read_channels};
16use sva_core::success_envelope;
17
18pub fn render(args: &RenderArgs) -> Result<String, CliError> {
20 let here = crate::composition::composition()?;
21 let (dir, target) = crate::composition::located(&here, &args.target)?;
22 for asked in &args.asked {
24 if let Some(dest) = asked.dest.as_deref() {
25 refuse_inside(&dir, dest)?;
26 refuse_replacing(dest, args.confirm)?;
27 }
28 }
29 let mut warnings = Vec::new();
30 let tier = crate::store::opened(&args.cache, &mut warnings)?;
31 let answered = answered(args, &dir, &target, tier, &mut warnings);
32 crate::store::persisted(tier, &mut warnings);
33 answered.map(|data| success_envelope(&data, &warnings))
34}
35
36fn answered(
38 args: &RenderArgs,
39 dir: &Path,
40 target: &str,
41 tier: &Tier<Directory>,
42 warnings: &mut Vec<Diagnostic>,
43) -> Result<String, CliError> {
44 let source = sva_ast::Dir::at(dir);
45 let logs = std::env::var("SVA_LOG").is_ok_and(|level| level == "debug");
46 let job = Job {
47 until: args.until.as_deref(),
48 rate: args.rate,
49 bits: args.bits,
50 asked: &args.asked,
51 flop_budget: args.flop_budget,
52 ..Job::over(&source, target)
53 };
54 let rendered = wait(execute_over(job, tier, &mut Session::default()))?;
55
56 let rate = rendered.config.rate;
57 let stats = rendered.render.cache_stats.as_ref();
58 if let Some(why) = stats.and_then(|stats| stats.unstaged.as_deref()) {
59 let message = format!("this render stopped writing what it computed: {why}");
60 warnings.push(crate::store::warning("store.unstaged", message));
61 }
62 if let (true, Some(stats)) = (logs, stats) {
63 eprint!("{}", cache_log(stats, rate));
64 }
65 let bits = rendered.config.profile.precision_bits;
66 let interval = rendered
67 .render
68 .range
69 .map(|r| (r.start_secs(rate), r.end as f64 / f64::from(rate)));
70 let framing = Framing {
71 target: args.target.clone(),
72 rate,
73 bits: Some(bits),
74 interval,
75 profile: rendered.config.profile.name,
76 encoding: SampleEncoding::of(bits),
77 replace: args.confirm,
78 };
79
80 let mut taken = Vec::with_capacity(args.asked.len());
81 for asked in &args.asked {
82 let node = asked.node.as_deref().unwrap_or(&rendered.target);
83 let answer = rendered.answer(node, asked.representation)?;
84 taken.push(match asked.brief {
85 true => briefed(answer),
86 false => answer,
87 });
88 }
89 let (answers, written) = routed(&args.asked, taken, &framing)?;
90
91 Ok(query_data(&Report {
92 target: &args.target,
93 rate,
94 bits: Some(bits),
95 interval,
96 profile: rendered.config.profile.name,
97 label: rendered.label(),
98 written: &written,
99 answers: &answers,
100 limit: Some(SAMPLE_LIMIT),
101 }))
102}
103
104type Routed<'a> = (Vec<Printed>, Vec<(String, &'a Path)>);
106
107fn routed<'a>(
109 asked: &'a [Asked],
110 taken: Vec<Answer>,
111 framing: &Framing,
112) -> Result<Routed<'a>, CliError> {
113 let mut answers = Vec::new();
114 let mut written = Vec::new();
115 for (one, answer) in asked.iter().zip(taken) {
116 let printed = Printed {
117 name: one.name.clone(),
118 answer,
119 skim: one.skim,
120 };
121 match one.dest.as_deref() {
122 None => answers.push(printed),
123 Some(dest) => {
124 write(&printed, dest, framing)?;
125 written.push((one.name.clone(), dest));
126 }
127 }
128 }
129 Ok((answers, written))
130}
131
132fn briefed(answer: Answer) -> Answer {
135 let Output::Ledger(entries) = answer.value else {
136 return answer;
137 };
138 Answer {
139 value: Output::Ledger(
140 entries
141 .into_iter()
142 .filter(|e| e.clipped == Some(true))
143 .collect(),
144 ),
145 ..answer
146 }
147}
148
149pub fn analyze(args: &AnalyzeArgs) -> Result<String, CliError> {
152 let dir = cwd()?;
153 let composition = is_composition(&dir);
154 for dest in args.asked.iter().filter_map(|a| a.dest.as_deref()) {
155 if composition {
156 refuse_inside(&dir, dest)?;
157 }
158 refuse_replacing(dest, args.confirm)?;
159 }
160 let (planes, rate) = read_channels(&args.path)?;
161 let target = args.path.display().to_string();
162 let held = Buffer::of_planes(
163 rate,
164 planes
165 .iter()
166 .map(|p| p.iter().map(|v| f64::from(*v)).collect())
167 .collect(),
168 );
169 crate::args::check_frame(&args.asked, rate)?;
170 let buffer = held;
171 let interval = Some((0.0, buffer.len() as f64 / f64::from(rate)));
172 let framing = Framing {
173 target: target.clone(),
174 rate,
175 bits: None,
176 interval,
177 profile: PSYCHOACOUSTIC_V1.name,
178 encoding: SampleEncoding::Float,
179 replace: args.confirm,
180 };
181
182 let mut taken = Vec::with_capacity(args.asked.len());
183 for asked in &args.asked {
184 taken.push(
185 answer_buffer(
186 &target,
187 &buffer,
188 asked.representation,
189 PSYCHOACOUSTIC_V1.name,
190 )
191 .map_err(CliError::Engine)?,
192 );
193 }
194 let (answers, written) = routed(&args.asked, taken, &framing)?;
195 Ok(success_envelope(
196 &query_data(&Report {
197 target: &target,
198 rate,
199 bits: None,
200 interval,
201 profile: PSYCHOACOUSTIC_V1.name,
202 label: None,
203 written: &written,
204 answers: &answers,
205 limit: Some(SAMPLE_LIMIT),
206 }),
207 &[],
208 ))
209}
210
211fn is_composition(dir: &Path) -> bool {
213 dir.join(sva_core::ROOT).is_file() || dir.join(sva_ast::VARIABLES).is_dir()
214}