Skip to main content

secs_project/
target.rs

1use crate::pyjson::Shape;
2use crate::{ProjectError, PyJson};
3pub use secs_types::target::TargetCurve;
4
5/// The `target` object [`TargetJson::to_json`] writes; `points` is replaced (F16).
6pub(crate) static TARGET_SHAPE: Shape = Shape::Record {
7    optional: &[],
8    nested: &[],
9};
10
11/// `.secs` conversion of [`TargetCurve`], whose arithmetic lives in
12/// `secs-types` so the engine can sample it without depending on file formats.
13pub trait TargetJson: Sized {
14    /// The `.secs` value SECS 2 writes for the curve.
15    fn to_json(&self) -> PyJson;
16    /// Reads the `.secs` value (an object with `points`, a bare array, or null).
17    fn from_json(value: PyJson) -> Result<Self, ProjectError>;
18}
19
20impl TargetJson for TargetCurve {
21    fn to_json(&self) -> PyJson {
22        PyJson::Object(vec![(
23            "points".into(),
24            PyJson::Array(
25                self.points
26                    .iter()
27                    .map(|(frequency, level)| {
28                        PyJson::Array(vec![PyJson::Float(*frequency), PyJson::Float(*level)])
29                    })
30                    .collect(),
31            ),
32        )])
33    }
34
35    fn from_json(value: PyJson) -> Result<Self, ProjectError> {
36        let points = match &value {
37            PyJson::Null => &[][..],
38            PyJson::Array(points) => points.as_slice(),
39            PyJson::Object(_) => value
40                .get("points")
41                .map_or(&[][..], |value| value.array().unwrap_or(&[])),
42            _ => {
43                return Err(ProjectError::Invalid(
44                    "target must be an object or array".into(),
45                ));
46            }
47        };
48        Ok(Self::new(
49            points
50                .iter()
51                .map(|point| {
52                    let pair = point.array().ok_or_else(|| {
53                        ProjectError::Invalid("target point must be a pair".into())
54                    })?;
55                    if pair.len() != 2 {
56                        return Err(ProjectError::Invalid(
57                            "target point must contain two numbers".into(),
58                        ));
59                    }
60                    Ok((
61                        crate::project::float(&pair[0])?,
62                        crate::project::float(&pair[1])?,
63                    ))
64                })
65                .collect::<Result<Vec<_>, ProjectError>>()?,
66        ))
67    }
68}