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 {
266 fn legacy_svg_mut(&mut self) -> &mut SvgChartConfig {
269 if let Some(SnapshotOutputMode::SvgChart(ref mut chart)) = self.output_mode {
271 return chart;
272 }
273 self.output_mode = Some(SnapshotOutputMode::SvgChart(SvgChartConfig::default()));
275 match self.output_mode {
276 Some(SnapshotOutputMode::SvgChart(ref mut chart)) => chart,
277 _ => unreachable!("Output mode was just set to SvgChart"),
278 }
279 }
280
281 pub fn chart_layout(&mut self, value: Layout) -> &mut Self {
283 self.legacy_svg_mut().chart_layout = value;
284 self
285 }
286
287 pub fn with_inputs(&mut self, value: bool) -> &mut Self {
289 self.legacy_svg_mut().with_inputs = value;
290 self
291 }
292
293 pub fn svg_width(&mut self, value: usize) -> &mut Self {
295 self.legacy_svg_mut().svg_width = Some(value);
296 self
297 }
298
299 pub fn svg_height_per_channel(&mut self, value: usize) -> &mut Self {
301 self.legacy_svg_mut().svg_height_per_channel = value;
302 self
303 }
304
305 pub fn show_labels(&mut self, value: bool) -> &mut Self {
307 self.legacy_svg_mut().show_labels = value;
308 self
309 }
310
311 pub fn format_x_axis_labels_as_time(&mut self, value: bool) -> &mut Self {
313 self.legacy_svg_mut().format_x_axis_labels_as_time = value;
314 self
315 }
316
317 pub fn max_labels_x_axis(&mut self, value: Option<usize>) -> &mut Self {
319 self.legacy_svg_mut().max_labels_x_axis = value;
320 self
321 }
322
323 pub fn chart_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
325 self.legacy_svg_mut().chart_title = Some(value.into());
326 self
327 }
328
329 pub fn output_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
331 self.legacy_svg_mut().output_titles.push(value.into());
332 self
333 }
334
335 pub fn input_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
337 self.legacy_svg_mut().input_titles.push(value.into());
338 self
339 }
340
341 pub fn output_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
343 self.legacy_svg_mut().output_titles = value.into();
344 self
345 }
346
347 pub fn input_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
349 self.legacy_svg_mut().input_titles = value.into();
350 self
351 }
352
353 pub fn show_grid(&mut self, value: bool) -> &mut Self {
355 self.legacy_svg_mut().show_grid = value;
356 self
357 }
358
359 pub fn line_width(&mut self, value: f32) -> &mut Self {
361 self.legacy_svg_mut().line_width = value;
362 self
363 }
364
365 pub fn background_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
367 self.legacy_svg_mut().background_color = value.into();
368 self
369 }
370
371 pub fn output_colors(&mut self, colors: Vec<String>) -> &mut Self {
373 self.legacy_svg_mut().output_colors = Some(colors);
374 self
375 }
376
377 pub fn output_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
379 let chart = self.legacy_svg_mut();
380 chart
381 .output_colors
382 .get_or_insert_with(Vec::new)
383 .push(value.into());
384 self
385 }
386
387 pub fn input_colors(&mut self, colors: Vec<String>) -> &mut Self {
389 self.legacy_svg_mut().input_colors = Some(colors);
390 self
391 }
392
393 pub fn input_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
395 let chart = self.legacy_svg_mut();
396 chart
397 .input_colors
398 .get_or_insert_with(Vec::new)
399 .push(value.into());
400 self
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_default_builder() {
410 SnapshotConfigBuilder::default()
411 .build()
412 .expect("defaul config builds");
413 }
414
415 #[test]
416 fn legacy_config_compat() {
417 SnapshotConfigBuilder::default()
418 .chart_title("Complete Waveform Test")
419 .show_grid(true)
420 .show_labels(true)
421 .with_inputs(true)
422 .output_color("#FF6B6B")
423 .input_color("#95E77E")
424 .background_color("#2C3E50")
425 .line_width(3.0)
426 .svg_width(1200)
427 .svg_height_per_channel(120)
428 .build()
429 .expect("legacy config builds");
430 }
431}