gled-core 2.30.2

The engine of gled without a user interface: inputs, timing, rendering, Art-Net/DMX output and storage
Documentation
use crate::audio::{
    fft::FREQ_BINS,
    sound_trigger::SoundTrigger,
    sound_trigger_ref::{SoundLevels, SoundTriggerSet},
};
use kanal::{Receiver, Sender, bounded};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::{collections::HashMap, sync::atomic::AtomicUsize};
use tracing::trace;
use uuid::Uuid;

const SOUND_TRIGGER_SAMPLE_INTERVAL_MS: u64 = 10;

type Data = (HashMap<Uuid, SoundTrigger>, SoundTrigger);

static CURRENT: Lazy<Mutex<Data>> =
    Lazy::new(|| Mutex::new((HashMap::new(), SoundTrigger::default())));
static INSTANCE_ID: AtomicUsize = AtomicUsize::new(0);
static SENDERS: Lazy<Mutex<HashMap<usize, Sender<Data>>>> =
    Lazy::new(|| Mutex::new(HashMap::new()));

pub(crate) struct SoundData {
    id: usize,
    receiver: Receiver<Data>,
    pub(crate) triggers: HashMap<Uuid, SoundTrigger>,
    pub(crate) default_trigger: SoundTrigger,
}

impl std::fmt::Debug for SoundData {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SoundData").finish_non_exhaustive()
    }
}

impl Default for SoundData {
    fn default() -> Self {
        let (sender, receiver) = bounded(10);
        let id = INSTANCE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
        SENDERS.lock().insert(id, sender);

        let (triggers, default_trigger) = CURRENT.lock().clone();

        Self {
            id,
            receiver,
            triggers,
            default_trigger,
        }
    }
}

impl Drop for SoundData {
    fn drop(&mut self) {
        SENDERS.lock().remove(&self.id);
    }
}

impl SoundData {
    /// Makes the running triggers match `wanted`: starts missing ones, applies
    /// changed settings and drops triggers nothing uses any more. Returns
    /// whether anything changed, i.e. whether the result has to be saved.
    pub(crate) fn sync_triggers(&mut self, wanted: &SoundTriggerSet) -> bool {
        let mut changed = false;
        self.triggers.retain(|id, _| {
            let keep = wanted.contains_key(id);
            changed |= !keep;
            keep
        });
        for (id, params) in wanted {
            match self.triggers.get_mut(id) {
                Some(trigger) if trigger.params == *params => {}
                Some(trigger) => {
                    trigger.params = *params;
                    changed = true;
                }
                None => {
                    self.triggers.insert(
                        *id,
                        SoundTrigger::new(*params, SOUND_TRIGGER_SAMPLE_INTERVAL_MS as f32 / 1000.),
                    );
                    changed = true;
                }
            }
        }
        changed
    }

    /// The running state of a trigger.
    /// The current level of every running trigger.
    pub(crate) fn trigger_levels(&self) -> impl Iterator<Item = (Uuid, f32)> + '_ {
        self.triggers
            .iter()
            .map(|(id, trigger)| (*id, trigger.current_level))
    }

    #[cfg_attr(feature = "profiling", profiling::function)]
    pub(crate) fn tick(&mut self, fft_samples: [f32; FREQ_BINS]) {
        self.triggers.values_mut().for_each(move |trigger| {
            trigger.tick(fft_samples);
        });
        self.default_trigger.tick(fft_samples);
    }

    pub(crate) fn reset(&mut self) {
        self.triggers.values_mut().for_each(move |trigger| {
            trigger.reset();
        });
    }

    #[cfg_attr(feature = "profiling", profiling::function)]
    pub(crate) fn update(&mut self) {
        trace!("Updating sound trigger data (self.id = {})", self.id);
        while let Ok(Some((triggers, default_trigger))) = self.receiver.try_recv() {
            self.triggers = triggers;
            self.default_trigger = default_trigger;
        }
    }

    pub(crate) fn save(&self) {
        trace!("Saving sound trigger data (self.id = {})", self.id);
        for (id, sender) in SENDERS.lock().iter() {
            if id == &self.id {
                continue;
            }

            trace!("Sending sound trigger data to instance with id: {id}");
            sender
                .send((self.triggers.clone(), self.default_trigger.clone()))
                .expect("Could not send sound trigger data");
        }

        *CURRENT.lock() = (self.triggers.clone(), self.default_trigger.clone());
    }
}

impl SoundLevels for SoundData {
    fn level(&self, id: &Uuid) -> f32 {
        self.triggers
            .get(id)
            .map(|trigger| trigger.current_level)
            .unwrap_or(0.0)
    }

    fn spectrum_bytes(&self) -> [u8; FREQ_BINS * 4] {
        let mut data = [0u8; FREQ_BINS * 4];

        for (i, sample) in self.default_trigger.spectrum.iter().enumerate() {
            let bytes = sample.to_le_bytes();
            data[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
        }
        data
    }
}