gled-model 2.30.0

The data model of gled: projects, scenes, effects, animations, curves and assets
Documentation
//! A sound trigger as part of a curve: its settings and a stable id. The
//! settings live in the project, so they are saved, cloned and synced like
//! every other value. The running trigger state (filtered spectrum, envelope)
//! lives in `SoundData`, keyed by the id, and follows these settings through
//! `SoundData::sync_triggers`.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use super::trigger_params::{FREQ_BINS, SoundTriggerParams};

/// Settings of one sound trigger. Projects saved before triggers had an id get
/// a fresh one when they are loaded.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct SoundTriggerRef {
    #[serde(default = "Uuid::new_v4")]
    pub id: Uuid,
    pub params: SoundTriggerParams,
    pub f_center: f32,
    pub f_radius: f32,
    pub(crate) averaging_time: f32,
}

impl Default for SoundTriggerRef {
    fn default() -> Self {
        Self {
            id: Uuid::new_v4(),
            params: SoundTriggerParams::default(),
            f_center: 8200.,
            f_radius: 990.,
            averaging_time: 0.1,
        }
    }
}

/// The current output of the running sound triggers, as curves read it. On
/// the machine with the audio input this is `SoundData`; a remote client gets
/// the levels from the server.
pub trait SoundLevels {
    /// Output level of a trigger in 0..=1, 0 while it is not running.
    fn level(&self, id: &Uuid) -> f32;
    /// The current spectrum, as the little-endian `f32` bytes effects read.
    fn spectrum_bytes(&self) -> [u8; FREQ_BINS * 4];
}

/// The settings of every sound trigger a value uses, by id.
pub type SoundTriggerSet = HashMap<Uuid, SoundTriggerParams>;

/// Values that can contain sound triggers.
pub trait CollectSoundTriggers {
    /// Adds the triggers of `self` to `triggers`. When two triggers share an
    /// id, the first one wins; copies of a value share their trigger.
    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet);
}

impl CollectSoundTriggers for SoundTriggerRef {
    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet) {
        triggers.entry(self.id).or_insert(self.params);
    }
}

impl<T: CollectSoundTriggers> CollectSoundTriggers for Option<T> {
    fn collect_sound_triggers(&self, triggers: &mut SoundTriggerSet) {
        if let Some(value) = self {
            value.collect_sound_triggers(triggers);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn loads_triggers_saved_without_an_id() {
        let json = serde_json::to_string(&SoundTriggerRef::default()).expect("serializes");
        let mut value: serde_json::Value = serde_json::from_str(&json).expect("json");
        value.as_object_mut().expect("object").remove("id");
        let old: SoundTriggerRef = serde_json::from_value(value).expect("loads without id");
        assert_eq!(old.f_center, 8200.);
    }

    #[test]
    fn copies_share_their_trigger() {
        let trigger = SoundTriggerRef::default();
        let mut set = SoundTriggerSet::new();
        trigger.collect_sound_triggers(&mut set);
        trigger.clone().collect_sound_triggers(&mut set);
        assert_eq!(set.len(), 1);
    }
}