use crate::audio::{Audio, Synth};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UiSound {
Open,
Close,
Navigate,
Wrap,
Activate,
Edge,
Change,
Typing,
}
impl UiSound {
pub const ALL: [UiSound; 8] = [
UiSound::Open,
UiSound::Close,
UiSound::Navigate,
UiSound::Wrap,
UiSound::Activate,
UiSound::Edge,
UiSound::Change,
UiSound::Typing,
];
pub fn name(self) -> &'static str {
match self {
UiSound::Open => "open",
UiSound::Close => "close",
UiSound::Navigate => "navigate",
UiSound::Wrap => "wrap",
UiSound::Activate => "activate",
UiSound::Edge => "edge",
UiSound::Change => "change",
UiSound::Typing => "typing",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UiSounds {
pub open: Option<String>,
pub close: Option<String>,
pub navigate: Option<String>,
pub wrap: Option<String>,
pub activate: Option<String>,
pub edge: Option<String>,
pub change: Option<String>,
pub typing: Option<String>,
}
impl Default for UiSounds {
fn default() -> Self {
Self::builtin()
}
}
impl UiSounds {
pub fn builtin() -> Self {
let mut sounds = Self::none();
for kind in UiSound::ALL {
if kind != UiSound::Typing {
sounds.set(kind, Some(format!("ui:builtin:{}", kind.name())));
}
}
sounds
}
pub fn none() -> Self {
Self {
open: None,
close: None,
navigate: None,
wrap: None,
activate: None,
edge: None,
change: None,
typing: None,
}
}
pub fn from_folder(folder: &str) -> Self {
let mut sounds = Self::none();
for kind in UiSound::ALL {
sounds.set(kind, Some(format!("{}/{}", folder.trim_end_matches('/'), kind.name())));
}
sounds
}
pub fn with(mut self, kind: UiSound, name: impl Into<String>) -> Self {
self.set(kind, Some(name.into()));
self
}
pub fn without(mut self, kind: UiSound) -> Self {
self.set(kind, None);
self
}
pub fn set(&mut self, kind: UiSound, name: Option<String>) {
*self.slot(kind) = name;
}
fn slot(&mut self, kind: UiSound) -> &mut Option<String> {
match kind {
UiSound::Open => &mut self.open,
UiSound::Close => &mut self.close,
UiSound::Navigate => &mut self.navigate,
UiSound::Wrap => &mut self.wrap,
UiSound::Activate => &mut self.activate,
UiSound::Edge => &mut self.edge,
UiSound::Change => &mut self.change,
UiSound::Typing => &mut self.typing,
}
}
pub fn get(&self, kind: UiSound) -> Option<&str> {
match kind {
UiSound::Open => self.open.as_deref(),
UiSound::Close => self.close.as_deref(),
UiSound::Navigate => self.navigate.as_deref(),
UiSound::Wrap => self.wrap.as_deref().or(self.navigate.as_deref()),
UiSound::Activate => self.activate.as_deref(),
UiSound::Edge => self.edge.as_deref(),
UiSound::Change => self.change.as_deref(),
UiSound::Typing => self.typing.as_deref(),
}
}
}
pub(crate) fn ensure_builtin(audio: &mut Audio, name: &str) {
let Some(kind) = name.strip_prefix("ui:builtin:") else {
return;
};
if audio.is_loaded(name) {
return;
}
let synth = match kind {
"navigate" => Synth::sine(1400.0).length(0.035).decay(120.0).volume(0.35),
"wrap" => Synth::sine(1400.0).length(0.03).decay(120.0).volume(0.3).then(Synth::sine(1900.0).length(0.04).decay(120.0).volume(0.3)),
"change" => Synth::sine(1000.0).length(0.04).decay(90.0).volume(0.35),
"activate" => Synth::sine(700.0).length(0.05).decay(40.0).volume(0.35).then(Synth::sine(1050.0).length(0.07).decay(40.0).volume(0.35)),
"edge" => Synth::sine(180.0).length(0.08).decay(45.0).volume(0.5),
"open" => Synth::sine(500.0).sweep_to(980.0).length(0.12).decay(15.0).volume(0.25),
"close" => Synth::sine(950.0).sweep_to(590.0).length(0.12).decay(15.0).volume(0.25),
"typing" => Synth::noise().length(0.015).decay(250.0).volume(0.2),
_ => return,
};
audio.register(name, synth);
}