modewise_operations/
stft.rs

1use modewise_pipeline::{
2    Artifact, ArtifactKind, Inputs, Operation, OperationDescriptor, Outputs, Port, resolve_params,
3};
4use modewise_spectral::TimeFrequencyTransform;
5use modewise_stft::Stft;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9
10use crate::input;
11
12#[derive(Serialize, Deserialize, JsonSchema)]
13#[serde(deny_unknown_fields)]
14struct Params {
15    /// Window length in samples (periodic Hann).
16    window_len: usize,
17    /// Step between frames in samples.
18    hop: usize,
19}
20
21pub struct StftOperation;
22
23impl Operation for StftOperation {
24    fn summary(&self) -> &'static str {
25        "Short-time Fourier transform power with a periodic Hann window."
26    }
27
28    fn params_schema(&self) -> serde_json::Value {
29        modewise_pipeline::schema_of::<Params>()
30    }
31
32    fn descriptor(&self) -> OperationDescriptor {
33        OperationDescriptor {
34            name: "stft",
35            version: env!("CARGO_PKG_VERSION"),
36            inputs: &[Port {
37                name: "signal",
38                kinds: &[ArtifactKind::Series],
39            }],
40            outputs: &[Port {
41                name: "power",
42                kinds: &[ArtifactKind::TimeFrequency],
43            }],
44        }
45    }
46
47    fn resolve(&self, params: Value) -> Result<Value, String> {
48        let (typed, resolved) = resolve_params::<Params>(params)?;
49        Stft::new(typed.window_len, typed.hop).map_err(|e| e.to_string())?;
50        Ok(resolved)
51    }
52
53    fn run(&self, params: &Value, inputs: &Inputs) -> Result<Outputs, String> {
54        let p: Params = serde_json::from_value(params.clone()).map_err(|e| e.to_string())?;
55        let method = Stft::new(p.window_len, p.hop).map_err(|e| e.to_string())?;
56        let Artifact::Series(signal) = input(inputs, "signal")? else {
57            return Err("input \"signal\" must be a series".into());
58        };
59        let map = method.transform(signal).map_err(|e| e.to_string())?;
60        Ok(Outputs::from([(
61            "power".into(),
62            Artifact::TimeFrequency(map),
63        )]))
64    }
65}