1use derive_builder::Builder;
2use fundsp::DEFAULT_SR;
3
4use crate::warmup::WarmUp;
5
6pub use crate::chart::Layout;
7
8const DEFAULT_HEIGHT: usize = 500;
9
10#[derive(Debug, Clone, Builder)]
11pub struct SnapshotConfig {
13 #[builder(default = "fundsp::DEFAULT_SR")]
18 pub sample_rate: f64,
19 #[builder(default = "1024")]
23 pub num_samples: usize,
24 #[builder(default = "Processing::default()")]
28 pub processing_mode: Processing,
29 #[builder(default = "WarmUp::None")]
33 pub warm_up: WarmUp,
34 #[builder(default = "false")]
42 pub allow_abnormal_samples: bool,
43
44 #[builder(
50 default = "SnapshotOutputMode::SvgChart(SvgChartConfig::default())",
51 try_setter,
52 setter(into)
53 )]
54 pub output_mode: SnapshotOutputMode,
55}
56
57#[derive(Debug, Clone, Builder)]
58pub struct SvgChartConfig {
59 #[builder(default)]
66 pub chart_layout: Layout,
67 #[builder(default)]
71 pub with_inputs: bool,
72 #[builder(default, setter(strip_option))]
76 pub svg_width: Option<usize>,
77 #[builder(default = "DEFAULT_HEIGHT")]
81 pub svg_height_per_channel: usize,
82
83 #[builder(default = "true")]
88 pub show_labels: bool,
89 #[builder(default)]
95 pub format_x_axis_labels_as_time: bool,
96 #[builder(default = "Some(5)")]
100 pub max_labels_x_axis: Option<usize>,
101 #[builder(default, setter(into, strip_option))]
105 pub chart_title: Option<String>,
106 #[builder(default, setter(into, each(into, name = "output_title")))]
110 pub output_titles: Vec<String>,
111 #[builder(default, setter(into, each(into, name = "input_title")))]
115 pub input_titles: Vec<String>,
116
117 #[builder(default)]
122 pub show_grid: bool,
123 #[builder(default = "2.0")]
127 pub line_width: f32,
128
129 #[builder(default = "\"#000000\".to_string()", setter(into))]
134 pub background_color: String,
135 #[builder(default, setter(into, strip_option, each(into, name = "output_color")))]
139 pub output_colors: Option<Vec<String>>,
140 #[builder(default, setter(into, strip_option, each(into, name = "input_color")))]
144 pub input_colors: Option<Vec<String>>,
145}
146
147#[derive(Debug, Clone)]
148pub enum WavOutput {
149 Wav16,
150 Wav32,
151}
152
153#[derive(Debug, Clone)]
154pub enum SnapshotOutputMode {
155 SvgChart(SvgChartConfig),
156 Wav(WavOutput),
157}
158
159#[derive(Debug, Clone, Copy, Default)]
161pub enum Processing {
162 #[default]
163 Tick,
165 Batch(u8),
169}
170
171impl TryFrom<SvgChartConfigBuilder> for SnapshotOutputMode {
172 type Error = SvgChartConfigBuilderError;
173
174 fn try_from(value: SvgChartConfigBuilder) -> Result<Self, Self::Error> {
175 let inner = value.build()?;
176 Ok(SnapshotOutputMode::SvgChart(inner))
177 }
178}
179
180impl From<WavOutput> for SnapshotOutputMode {
181 fn from(value: WavOutput) -> Self {
182 SnapshotOutputMode::Wav(value)
183 }
184}
185
186impl From<SvgChartConfig> for SnapshotOutputMode {
187 fn from(value: SvgChartConfig) -> Self {
188 SnapshotOutputMode::SvgChart(value)
189 }
190}
191
192impl Default for SnapshotConfig {
193 fn default() -> Self {
194 Self {
195 num_samples: 1024,
196 sample_rate: DEFAULT_SR,
197 processing_mode: Processing::default(),
198 warm_up: WarmUp::default(),
199 allow_abnormal_samples: false,
200 output_mode: SnapshotOutputMode::SvgChart(SvgChartConfig::default()),
201 }
202 }
203}
204
205impl Default for SvgChartConfig {
206 fn default() -> Self {
207 Self {
208 svg_width: None,
209 svg_height_per_channel: DEFAULT_HEIGHT,
210 with_inputs: false,
211 chart_title: None,
212 output_titles: Vec::new(),
213 input_titles: Vec::new(),
214 show_grid: false,
215 show_labels: true,
216 max_labels_x_axis: Some(5),
217 output_colors: None,
218 input_colors: None,
219 background_color: "#000000".to_string(),
220 line_width: 2.0,
221 chart_layout: Layout::default(),
222 format_x_axis_labels_as_time: false,
223 }
224 }
225}
226
227impl SnapshotConfig {
228 pub fn file_name(&self, name: Option<&'_ str>) -> String {
232 match &self.output_mode {
233 SnapshotOutputMode::SvgChart(svg_chart_config) => match name {
234 Some(name) => format!("{name}.svg"),
235 None => match &svg_chart_config.chart_title {
236 Some(name) => format!("{name}.svg"),
237 None => ".svg".to_string(),
238 },
239 },
240 SnapshotOutputMode::Wav(_) => match name {
241 Some(name) => format!("{name}.wav"),
242 None => ".wav".to_string(),
243 },
244 }
245 }
246
247 pub fn maybe_title(&mut self, name: &str) {
251 if matches!(
252 self.output_mode,
253 SnapshotOutputMode::SvgChart(SvgChartConfig {
254 chart_title: None,
255 ..
256 })
257 ) && let SnapshotOutputMode::SvgChart(ref mut svg_chart_config) = self.output_mode
258 {
259 svg_chart_config.chart_title = Some(name.to_string());
260 }
261 }
262}
263
264impl SnapshotConfigBuilder {
265 fn legacy_svg_mut(&mut self) -> &mut SvgChartConfig {
268 if let Some(SnapshotOutputMode::SvgChart(ref mut chart)) = self.output_mode {
270 return chart;
271 }
272 self.output_mode = Some(SnapshotOutputMode::SvgChart(SvgChartConfig::default()));
274 match self.output_mode {
275 Some(SnapshotOutputMode::SvgChart(ref mut chart)) => chart,
276 _ => unreachable!("Output mode was just set to SvgChart"),
277 }
278 }
279
280 pub fn chart_layout(&mut self, value: Layout) -> &mut Self {
282 self.legacy_svg_mut().chart_layout = value;
283 self
284 }
285
286 pub fn with_inputs(&mut self, value: bool) -> &mut Self {
288 self.legacy_svg_mut().with_inputs = value;
289 self
290 }
291
292 pub fn svg_width(&mut self, value: usize) -> &mut Self {
294 self.legacy_svg_mut().svg_width = Some(value);
295 self
296 }
297
298 pub fn svg_height_per_channel(&mut self, value: usize) -> &mut Self {
300 self.legacy_svg_mut().svg_height_per_channel = value;
301 self
302 }
303
304 pub fn show_labels(&mut self, value: bool) -> &mut Self {
306 self.legacy_svg_mut().show_labels = value;
307 self
308 }
309
310 pub fn format_x_axis_labels_as_time(&mut self, value: bool) -> &mut Self {
312 self.legacy_svg_mut().format_x_axis_labels_as_time = value;
313 self
314 }
315
316 pub fn max_labels_x_axis(&mut self, value: Option<usize>) -> &mut Self {
318 self.legacy_svg_mut().max_labels_x_axis = value;
319 self
320 }
321
322 pub fn chart_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
324 self.legacy_svg_mut().chart_title = Some(value.into());
325 self
326 }
327
328 pub fn output_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
330 self.legacy_svg_mut().output_titles.push(value.into());
331 self
332 }
333
334 pub fn input_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
336 self.legacy_svg_mut().input_titles.push(value.into());
337 self
338 }
339
340 pub fn output_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
342 self.legacy_svg_mut().output_titles = value.into();
343 self
344 }
345
346 pub fn input_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
348 self.legacy_svg_mut().input_titles = value.into();
349 self
350 }
351
352 pub fn show_grid(&mut self, value: bool) -> &mut Self {
354 self.legacy_svg_mut().show_grid = value;
355 self
356 }
357
358 pub fn line_width(&mut self, value: f32) -> &mut Self {
360 self.legacy_svg_mut().line_width = value;
361 self
362 }
363
364 pub fn background_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
366 self.legacy_svg_mut().background_color = value.into();
367 self
368 }
369
370 pub fn output_colors(&mut self, colors: Vec<String>) -> &mut Self {
372 self.legacy_svg_mut().output_colors = Some(colors);
373 self
374 }
375
376 pub fn output_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
378 let chart = self.legacy_svg_mut();
379 chart
380 .output_colors
381 .get_or_insert_with(Vec::new)
382 .push(value.into());
383 self
384 }
385
386 pub fn input_colors(&mut self, colors: Vec<String>) -> &mut Self {
388 self.legacy_svg_mut().input_colors = Some(colors);
389 self
390 }
391
392 pub fn input_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
394 let chart = self.legacy_svg_mut();
395 chart
396 .input_colors
397 .get_or_insert_with(Vec::new)
398 .push(value.into());
399 self
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_default_builder() {
409 SnapshotConfigBuilder::default()
410 .build()
411 .expect("defaul config builds");
412 }
413
414 #[test]
415 fn legacy_config_compat() {
416 SnapshotConfigBuilder::default()
417 .chart_title("Complete Waveform Test")
418 .show_grid(true)
419 .show_labels(true)
420 .with_inputs(true)
421 .output_color("#FF6B6B")
422 .input_color("#95E77E")
423 .background_color("#2C3E50")
424 .line_width(3.0)
425 .svg_width(1200)
426 .svg_height_per_channel(120)
427 .build()
428 .expect("legacy config builds");
429 }
430}