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 {
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
}
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
}
}