#![cfg_attr(not(feature = "audio"), allow(dead_code))]
use crate::math::{Vec2, Vec2Ext, FloatExt};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct Sound {
pub name: String,
pub is_music: bool,
pub volume: f32,
pub pitch: f32,
pub looping: bool,
pub spatial_min_distance: f32,
pub spatial_max_distance: f32,
pub position: Option<Vec2>,
}
impl Sound {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
is_music: false,
volume: 1.0,
pitch: 1.0,
looping: false,
spatial_min_distance: 50.0,
spatial_max_distance: 500.0,
position: None,
}
}
pub fn as_music(mut self) -> Self {
self.is_music = true;
self
}
pub fn with_volume(mut self, volume: f32) -> Self {
self.volume = volume.clamp(0.0, 1.0);
self
}
pub fn with_pitch(mut self, pitch: f32) -> Self {
self.pitch = pitch.clamp(0.1, 10.0);
self
}
pub fn with_looping(mut self, looping: bool) -> Self {
self.looping = looping;
self
}
pub fn with_position(mut self, pos: Vec2) -> Self {
self.position = Some(pos);
self
}
pub fn with_spatial_range(mut self, min: f32, max: f32) -> Self {
self.spatial_min_distance = min;
self.spatial_max_distance = max;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AudioChannel {
Sfx,
Music,
Ui,
Ambient,
}
pub struct AudioManager {
master_volume: f32,
channel_volumes: HashMap<AudioChannel, f32>,
sounds: HashMap<String, Sound>,
instances: Vec<PlayingInstance>,
listener_position: Vec2,
}
#[derive(Debug, Clone)]
struct PlayingInstance {
sound_name: String,
channel: AudioChannel,
current_volume: f32,
fade: Option<FadeState>,
alive: bool,
}
#[derive(Debug, Clone, Copy)]
struct FadeState {
kind: FadeKind,
duration: f32,
elapsed: f32,
start_volume: f32,
target_volume: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum FadeKind {
In,
Out,
}
#[derive(Debug, Clone, Copy)]
pub enum Fade {
In(f32),
Out(f32),
None,
}
impl Default for Fade {
fn default() -> Self {
Fade::None
}
}
impl AudioManager {
pub fn new() -> Self {
let mut channel_volumes = HashMap::new();
channel_volumes.insert(AudioChannel::Sfx, 1.0);
channel_volumes.insert(AudioChannel::Music, 0.8);
channel_volumes.insert(AudioChannel::Ui, 0.7);
channel_volumes.insert(AudioChannel::Ambient, 0.6);
Self {
master_volume: 1.0,
channel_volumes,
sounds: HashMap::new(),
instances: Vec::new(),
listener_position: Vec2::ZERO,
}
}
pub fn load(&mut self, name: &str, _path: &str) -> Result<(), String> {
let sound = Sound::new(name);
self.sounds.insert(name.to_string(), sound);
Ok(())
}
pub fn load_with_config(&mut self, sound: Sound) {
let name = sound.name.clone();
self.sounds.insert(name, sound);
}
pub fn play(&mut self, name: &str) {
self.play_with_fade(name, AudioChannel::Sfx, Fade::None);
}
pub fn play_on_channel(&mut self, name: &str, channel: AudioChannel) {
self.play_with_fade(name, channel, Fade::None);
}
pub fn play_music(&mut self, name: &str, fade: Fade) {
self.stop_channel(AudioChannel::Music);
self.play_with_fade(name, AudioChannel::Music, fade);
}
fn play_with_fade(&mut self, name: &str, channel: AudioChannel, fade: Fade) {
let sound = match self.sounds.get(name) {
Some(s) => s.clone(),
None => return,
};
let base_volume = sound.volume
* self.master_volume
* self.channel_volumes.get(&channel).copied().unwrap_or(1.0);
let spatial_volume = if let Some(sound_pos) = sound.position {
let dist = sound_pos.distance_to(self.listener_position);
if dist <= sound.spatial_min_distance {
1.0
} else if dist >= sound.spatial_max_distance {
0.0
} else {
1.0 - (dist - sound.spatial_min_distance)
/ (sound.spatial_max_distance - sound.spatial_min_distance)
}
} else {
1.0
};
let start_volume = match fade {
Fade::In(duration) => {
Some(FadeState {
kind: FadeKind::In,
duration,
elapsed: 0.0,
start_volume: 0.0,
target_volume: base_volume * spatial_volume,
})
}
Fade::Out(duration) => {
Some(FadeState {
kind: FadeKind::Out,
duration,
elapsed: 0.0,
start_volume: base_volume * spatial_volume,
target_volume: 0.0,
})
}
Fade::None => None,
};
let current_volume = start_volume
.as_ref()
.map(|f| f.start_volume)
.unwrap_or(base_volume * spatial_volume);
self.instances.push(PlayingInstance {
sound_name: name.to_string(),
channel,
current_volume,
fade: start_volume,
alive: true,
});
}
pub fn stop(&mut self, name: &str) {
for instance in &mut self.instances {
if instance.sound_name == name {
instance.alive = false;
}
}
}
pub fn stop_channel(&mut self, channel: AudioChannel) {
for instance in &mut self.instances {
if instance.channel == channel {
instance.alive = false;
}
}
}
pub fn stop_all(&mut self) {
for instance in &mut self.instances {
instance.alive = false;
}
}
pub fn set_master_volume(&mut self, volume: f32) {
self.master_volume = volume.clamp(0.0, 1.0);
}
pub fn set_channel_volume(&mut self, channel: AudioChannel, volume: f32) {
self.channel_volumes.insert(channel, volume.clamp(0.0, 1.0));
}
pub fn set_listener_position(&mut self, pos: Vec2) {
self.listener_position = pos;
}
pub fn pause_all(&mut self) {
}
pub fn resume_all(&mut self) {
}
pub fn is_playing(&self, name: &str) -> bool {
self.instances.iter().any(|i| i.sound_name == name && i.alive)
}
pub fn update(&mut self, dt: f32) {
for instance in &mut self.instances {
if !instance.alive {
continue;
}
if let Some(fade) = &mut instance.fade {
fade.elapsed += dt;
let t = (fade.elapsed / fade.duration).clamp(0.0, 1.0);
instance.current_volume = fade.start_volume.lerp(fade.target_volume, t);
if t >= 1.0 {
match fade.kind {
FadeKind::Out => instance.alive = false,
FadeKind::In => instance.fade = None,
}
}
}
}
self.instances.retain(|i| i.alive);
}
pub fn playing_count(&self) -> usize {
self.instances.iter().filter(|i| i.alive).count()
}
}
impl Default for AudioManager {
fn default() -> Self {
Self::new()
}
}