use std::str::FromStr;
use derive_builder::Builder;
use fundsp::DEFAULT_SR;
use crate::warmup::WarmUp;
pub use crate::chart::Layout;
const DEFAULT_HEIGHT: usize = 500;
#[derive(Debug, Clone, Builder)]
pub struct SnapshotConfig {
#[builder(default = "fundsp::DEFAULT_SR")]
pub sample_rate: f64,
#[builder(default = "1024")]
pub num_samples: usize,
#[builder(default = "Processing::default()")]
pub processing_mode: Processing,
#[builder(default = "WarmUp::None")]
pub warm_up: WarmUp,
#[builder(default = "false")]
pub allow_abnormal_samples: bool,
#[builder(
default = "SnapshotOutputMode::SvgChart(SvgChartConfig::default())",
try_setter,
setter(into)
)]
pub output_mode: SnapshotOutputMode,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum SvgPreserveAspectRatioAlignment {
#[default]
None,
XMinYMin,
XMidYMin,
XMaxYMin,
XMinYMid,
XMidYMid,
XMaxYMid,
XMinYMax,
XMidYMax,
XMaxYMax,
}
impl std::fmt::Display for SvgPreserveAspectRatioAlignment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SvgPreserveAspectRatioAlignment::None => write!(f, "none"),
SvgPreserveAspectRatioAlignment::XMinYMin => write!(f, "xMinYMin"),
SvgPreserveAspectRatioAlignment::XMidYMin => write!(f, "xMidYMin"),
SvgPreserveAspectRatioAlignment::XMaxYMin => write!(f, "xMaxYMin"),
SvgPreserveAspectRatioAlignment::XMinYMid => write!(f, "xMinYMid"),
SvgPreserveAspectRatioAlignment::XMidYMid => write!(f, "xMidYMid"),
SvgPreserveAspectRatioAlignment::XMaxYMid => write!(f, "xMaxYMid"),
SvgPreserveAspectRatioAlignment::XMinYMax => write!(f, "xMinYMax"),
SvgPreserveAspectRatioAlignment::XMidYMax => write!(f, "xMidYMax"),
SvgPreserveAspectRatioAlignment::XMaxYMax => write!(f, "xMaxYMax"),
}
}
}
impl FromStr for SvgPreserveAspectRatioAlignment {
type Err = ();
fn from_str(input: &str) -> Result<SvgPreserveAspectRatioAlignment, Self::Err> {
match input {
"none" => Ok(SvgPreserveAspectRatioAlignment::None),
"xMinYMin" => Ok(SvgPreserveAspectRatioAlignment::XMinYMin),
"xMidYMin" => Ok(SvgPreserveAspectRatioAlignment::XMidYMin),
"xMaxYMin" => Ok(SvgPreserveAspectRatioAlignment::XMaxYMin),
"xMinYMid" => Ok(SvgPreserveAspectRatioAlignment::XMinYMid),
"xMidYMid" => Ok(SvgPreserveAspectRatioAlignment::XMidYMid),
"xMaxYMid" => Ok(SvgPreserveAspectRatioAlignment::XMaxYMid),
"xMinYMax" => Ok(SvgPreserveAspectRatioAlignment::XMinYMax),
"xMidYMax" => Ok(SvgPreserveAspectRatioAlignment::XMidYMax),
"xMaxYMax" => Ok(SvgPreserveAspectRatioAlignment::XMaxYMax),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum SvgPreserveAspectRatioKwd {
#[default]
None,
Meet,
Slice,
}
impl FromStr for SvgPreserveAspectRatioKwd {
type Err = ();
fn from_str(input: &str) -> Result<SvgPreserveAspectRatioKwd, Self::Err> {
match input {
"meet" => Ok(SvgPreserveAspectRatioKwd::Meet),
"slice" => Ok(SvgPreserveAspectRatioKwd::Slice),
"" => Ok(SvgPreserveAspectRatioKwd::None),
_ => Err(()),
}
}
}
impl std::fmt::Display for SvgPreserveAspectRatioKwd {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SvgPreserveAspectRatioKwd::None => write!(f, ""),
SvgPreserveAspectRatioKwd::Meet => write!(f, " meet"),
SvgPreserveAspectRatioKwd::Slice => write!(f, " slice"),
}
}
}
#[derive(Debug, Clone, Copy, Default, Builder)]
#[builder(default)]
pub struct SvgPreserveAspectRatio {
pub alignment: SvgPreserveAspectRatioAlignment,
pub kwd: SvgPreserveAspectRatioKwd,
}
impl std::fmt::Display for SvgPreserveAspectRatio {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let SvgPreserveAspectRatioAlignment::None = self.alignment {
write!(f, "none")
} else {
write!(f, "{}{}", self.alignment, self.kwd)
}
}
}
impl FromStr for SvgPreserveAspectRatio {
type Err = ();
fn from_str(input: &str) -> Result<SvgPreserveAspectRatio, Self::Err> {
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.is_empty() {
return Err(());
}
let alignment = SvgPreserveAspectRatioAlignment::from_str(parts[0])?;
let kwd = if parts.len() > 1 {
SvgPreserveAspectRatioKwd::from_str(parts[1])?
} else {
SvgPreserveAspectRatioKwd::None
};
Ok(SvgPreserveAspectRatio { alignment, kwd })
}
}
impl SvgPreserveAspectRatio {
pub fn center() -> Self {
Self {
alignment: SvgPreserveAspectRatioAlignment::XMidYMid,
kwd: SvgPreserveAspectRatioKwd::None,
}
}
pub fn scale_to_fit() -> Self {
Self {
alignment: SvgPreserveAspectRatioAlignment::XMidYMid,
kwd: SvgPreserveAspectRatioKwd::Meet,
}
}
pub fn scale_to_fill() -> Self {
Self {
alignment: SvgPreserveAspectRatioAlignment::XMidYMid,
kwd: SvgPreserveAspectRatioKwd::Slice,
}
}
}
#[derive(Debug, Clone, Builder)]
pub struct SvgChartConfig {
#[builder(default)]
pub chart_layout: Layout,
#[builder(default)]
pub with_inputs: bool,
#[builder(default, setter(strip_option))]
pub svg_width: Option<usize>,
#[builder(default = "DEFAULT_HEIGHT")]
pub svg_height_per_channel: usize,
#[builder(default, try_setter, setter(strip_option, into))]
pub preserve_aspect_ratio: Option<SvgPreserveAspectRatio>,
#[builder(default = "true")]
pub show_labels: bool,
#[builder(default)]
pub format_x_axis_labels_as_time: bool,
#[builder(default = "Some(5)")]
pub max_labels_x_axis: Option<usize>,
#[builder(default, setter(into, strip_option))]
pub chart_title: Option<String>,
#[builder(default, setter(into, each(into, name = "output_title")))]
pub output_titles: Vec<String>,
#[builder(default, setter(into, each(into, name = "input_title")))]
pub input_titles: Vec<String>,
#[builder(default)]
pub show_grid: bool,
#[builder(default = "2.0")]
pub line_width: f32,
#[builder(default = "\"#000000\".to_string()", setter(into))]
pub background_color: String,
#[builder(default, setter(into, strip_option, each(into, name = "output_color")))]
pub output_colors: Option<Vec<String>>,
#[builder(default, setter(into, strip_option, each(into, name = "input_color")))]
pub input_colors: Option<Vec<String>>,
}
#[derive(Debug, Clone)]
pub enum WavOutput {
Wav16,
Wav32,
}
#[derive(Debug, Clone)]
pub enum SnapshotOutputMode {
SvgChart(SvgChartConfig),
Wav(WavOutput),
}
#[derive(Debug, Clone, Copy, Default)]
pub enum Processing {
#[default]
Tick,
Batch(u8),
}
impl TryFrom<SvgChartConfigBuilder> for SnapshotOutputMode {
type Error = SvgChartConfigBuilderError;
fn try_from(value: SvgChartConfigBuilder) -> Result<Self, Self::Error> {
let inner = value.build()?;
Ok(SnapshotOutputMode::SvgChart(inner))
}
}
impl From<WavOutput> for SnapshotOutputMode {
fn from(value: WavOutput) -> Self {
SnapshotOutputMode::Wav(value)
}
}
impl From<SvgChartConfig> for SnapshotOutputMode {
fn from(value: SvgChartConfig) -> Self {
SnapshotOutputMode::SvgChart(value)
}
}
impl Default for SnapshotConfig {
fn default() -> Self {
Self {
num_samples: 1024,
sample_rate: DEFAULT_SR,
processing_mode: Processing::default(),
warm_up: WarmUp::default(),
allow_abnormal_samples: false,
output_mode: SnapshotOutputMode::SvgChart(SvgChartConfig::default()),
}
}
}
impl Default for SvgChartConfig {
fn default() -> Self {
Self {
svg_width: None,
svg_height_per_channel: DEFAULT_HEIGHT,
preserve_aspect_ratio: None,
with_inputs: false,
chart_title: None,
output_titles: Vec::new(),
input_titles: Vec::new(),
show_grid: false,
show_labels: true,
max_labels_x_axis: Some(5),
output_colors: None,
input_colors: None,
background_color: "#000000".to_string(),
line_width: 2.0,
chart_layout: Layout::default(),
format_x_axis_labels_as_time: false,
}
}
}
impl SnapshotConfig {
pub fn file_name(&self, name: Option<&'_ str>) -> String {
match &self.output_mode {
SnapshotOutputMode::SvgChart(svg_chart_config) => match name {
Some(name) => format!("{name}.svg"),
None => match &svg_chart_config.chart_title {
Some(name) => format!("{name}.svg"),
None => ".svg".to_string(),
},
},
SnapshotOutputMode::Wav(_) => match name {
Some(name) => format!("{name}.wav"),
None => ".wav".to_string(),
},
}
}
pub fn maybe_title(&mut self, name: &str) {
if matches!(
self.output_mode,
SnapshotOutputMode::SvgChart(SvgChartConfig {
chart_title: None,
..
})
) && let SnapshotOutputMode::SvgChart(ref mut svg_chart_config) = self.output_mode
{
svg_chart_config.chart_title = Some(name.to_string());
}
}
}
impl SnapshotConfigBuilder {
fn legacy_svg_mut(&mut self) -> &mut SvgChartConfig {
if let Some(SnapshotOutputMode::SvgChart(ref mut chart)) = self.output_mode {
return chart;
}
self.output_mode = Some(SnapshotOutputMode::SvgChart(SvgChartConfig::default()));
match self.output_mode {
Some(SnapshotOutputMode::SvgChart(ref mut chart)) => chart,
_ => unreachable!("Output mode was just set to SvgChart"),
}
}
pub fn chart_layout(&mut self, value: Layout) -> &mut Self {
self.legacy_svg_mut().chart_layout = value;
self
}
pub fn with_inputs(&mut self, value: bool) -> &mut Self {
self.legacy_svg_mut().with_inputs = value;
self
}
pub fn svg_width(&mut self, value: usize) -> &mut Self {
self.legacy_svg_mut().svg_width = Some(value);
self
}
pub fn svg_height_per_channel(&mut self, value: usize) -> &mut Self {
self.legacy_svg_mut().svg_height_per_channel = value;
self
}
pub fn show_labels(&mut self, value: bool) -> &mut Self {
self.legacy_svg_mut().show_labels = value;
self
}
pub fn format_x_axis_labels_as_time(&mut self, value: bool) -> &mut Self {
self.legacy_svg_mut().format_x_axis_labels_as_time = value;
self
}
pub fn max_labels_x_axis(&mut self, value: Option<usize>) -> &mut Self {
self.legacy_svg_mut().max_labels_x_axis = value;
self
}
pub fn chart_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().chart_title = Some(value.into());
self
}
pub fn output_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().output_titles.push(value.into());
self
}
pub fn input_title<S: Into<String>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().input_titles.push(value.into());
self
}
pub fn output_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().output_titles = value.into();
self
}
pub fn input_titles<S: Into<Vec<String>>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().input_titles = value.into();
self
}
pub fn show_grid(&mut self, value: bool) -> &mut Self {
self.legacy_svg_mut().show_grid = value;
self
}
pub fn line_width(&mut self, value: f32) -> &mut Self {
self.legacy_svg_mut().line_width = value;
self
}
pub fn background_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
self.legacy_svg_mut().background_color = value.into();
self
}
pub fn output_colors(&mut self, colors: Vec<String>) -> &mut Self {
self.legacy_svg_mut().output_colors = Some(colors);
self
}
pub fn output_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
let chart = self.legacy_svg_mut();
chart
.output_colors
.get_or_insert_with(Vec::new)
.push(value.into());
self
}
pub fn input_colors(&mut self, colors: Vec<String>) -> &mut Self {
self.legacy_svg_mut().input_colors = Some(colors);
self
}
pub fn input_color<S: Into<String>>(&mut self, value: S) -> &mut Self {
let chart = self.legacy_svg_mut();
chart
.input_colors
.get_or_insert_with(Vec::new)
.push(value.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_builder() {
SnapshotConfigBuilder::default()
.build()
.expect("defaul config builds");
}
#[test]
fn legacy_config_compat() {
SnapshotConfigBuilder::default()
.chart_title("Complete Waveform Test")
.show_grid(true)
.show_labels(true)
.with_inputs(true)
.output_color("#FF6B6B")
.input_color("#95E77E")
.background_color("#2C3E50")
.line_width(3.0)
.svg_width(1200)
.svg_height_per_channel(120)
.build()
.expect("legacy config builds");
}
}