Skip to main content

gled_model/sound/
trigger_ref.rs

1//! A sound trigger as part of a curve: its settings and a stable id. The
2//! settings live in the project, so they are saved, cloned and synced like
3//! every other value. The running trigger state (filtered spectrum, envelope)
4//! lives in `SoundData`, keyed by the id, and follows these settings through
5//! `SoundData::sync_triggers`.
6
7use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10use uuid::Uuid;
11
12use super::trigger_params::{FREQ_BINS, SoundTriggerParams};
13
14/// Settings of one sound trigger. Projects saved before triggers had an id get
15/// a fresh one when they are loaded.
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17#[serde(from = "SavedTrigger")]
18pub struct SoundTriggerRef {
19    pub id: Uuid,
20    pub params: SoundTriggerParams,
21    pub f_center: f32,
22    pub f_radius: f32,
23    pub(crate) averaging_time: f32,
24}
25
26/// A trigger as saved. Builds of the `aislop` branch saved the band as
27/// `center_bin`/`window_bins` (log-spaced bins) instead of `f_center` and
28/// `f_radius`; such a trigger gets its band back from `params`.
29#[derive(Deserialize)]
30struct SavedTrigger {
31    #[serde(default = "Uuid::new_v4")]
32    id: Uuid,
33    params: SoundTriggerParams,
34    f_center: Option<f32>,
35    f_radius: Option<f32>,
36    averaging_time: f32,
37}
38
39impl From<SavedTrigger> for SoundTriggerRef {
40    fn from(saved: SavedTrigger) -> Self {
41        let (f_center, f_radius) = saved.params.filter_tune();
42        Self {
43            id: saved.id,
44            params: saved.params,
45            f_center: saved.f_center.unwrap_or(f_center),
46            f_radius: saved.f_radius.unwrap_or(f_radius),
47            averaging_time: saved.averaging_time,
48        }
49    }
50}
51
52impl Default for SoundTriggerRef {
53    fn default() -> Self {
54        Self {
55            id: Uuid::new_v4(),
56            params: SoundTriggerParams::default(),
57            f_center: 8200.,
58            f_radius: 990.,
59            averaging_time: 0.1,
60        }
61    }
62}
63
64/// The current output of the running sound triggers, as curves read it. On
65/// the machine with the audio input this is `SoundData`; a remote client gets
66/// the levels from the server.
67pub trait SoundLevels {
68    /// Output level of a trigger in 0..=1, 0 while it is not running.
69    fn level(&self, id: &Uuid) -> f32;
70    /// The current spectrum, as the little-endian `f32` bytes effects read.
71    fn spectrum_bytes(&self) -> [u8; FREQ_BINS * 4];
72}
73
74/// The settings of every sound trigger a value uses, by id.
75pub type SoundTriggerSet = HashMap<Uuid, SoundTriggerParams>;
76
77/// Values that can contain sound triggers.
78pub trait CollectSoundTriggers {
79    /// Adds the triggers of `self` to `triggers`. When two triggers share an
80    /// id, the first one wins; copies of a value share their trigger.
81    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet);
82}
83
84impl CollectSoundTriggers for SoundTriggerRef {
85    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet) {
86        triggers.entry(self.id).or_insert(self.params);
87    }
88}
89
90impl<T: CollectSoundTriggers> CollectSoundTriggers for Option<T> {
91    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet) {
92        if let Some(value) = self {
93            value.collect_sound_triggers(triggers);
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn loads_triggers_saved_without_an_id() {
104        let json = serde_json::to_string(&SoundTriggerRef::default()).expect("serializes");
105        let mut value: serde_json::Value = serde_json::from_str(&json).expect("json");
106        value.as_object_mut().expect("object").remove("id");
107        let old: SoundTriggerRef = serde_json::from_value(value).expect("loads without id");
108        assert_eq!(old.f_center, 8200.);
109    }
110
111    #[test]
112    fn loads_triggers_saved_with_bins() {
113        let json = serde_json::to_string(&SoundTriggerRef::default()).expect("serializes");
114        let mut value: serde_json::Value = serde_json::from_str(&json).expect("json");
115        let object = value.as_object_mut().expect("object");
116        object.remove("f_center");
117        object.remove("f_radius");
118        object.insert("center_bin".to_string(), 134.29.into());
119        object.insert("window_bins".to_string(), 16.0.into());
120        let old: SoundTriggerRef = serde_json::from_value(value).expect("loads with bins");
121        let mut params = SoundTriggerParams::default();
122        params.set_filter_tune(old.f_center, old.f_radius);
123        assert_eq!(params, old.params);
124    }
125
126    #[test]
127    fn copies_share_their_trigger() {
128        let trigger = SoundTriggerRef::default();
129        let mut set = SoundTriggerSet::new();
130        trigger.collect_sound_triggers(&mut set);
131        trigger.clone().collect_sound_triggers(&mut set);
132        assert_eq!(set.len(), 1);
133    }
134}