use std::{collections::HashMap, path::Path};
use anyhow::Ok;
use cpal::{traits::{DeviceTrait, HostTrait}, Host};
use kira::{
sound::{
static_sound::{StaticSoundData, StaticSoundSettings},
streaming::{StreamingSoundData, StreamingSoundHandle, StreamingSoundSettings},
FromFileError,
IntoOptionalRegion,
PlaybackPosition,
Region
},
AudioManager,
AudioManagerSettings,
Decibels,
Panning,
PlaybackRate,
StartTime,
Tween,
Value
};
use log::warn;
use lotus_proc_macros::Resource;
use super::{
super::asset_loader::AssetLoader,
audio_error::AudioError
};
pub struct AudioSettings {
pub start_time: StartTime,
pub start_position: PlaybackPosition,
pub loop_region: Option<Region>,
pub reverse: bool,
pub volume: Value<Decibels>,
pub playback_rate: Value<PlaybackRate>,
pub panning: Value<Panning>,
pub fade_in_tween: Option<Tween>
}
impl Default for AudioSettings {
fn default() -> Self {
return Self {
start_time: StartTime::default(),
start_position: PlaybackPosition::Seconds(0.0),
reverse: false,
loop_region: None,
volume: Value::Fixed(Decibels::IDENTITY),
playback_rate: Value::Fixed(PlaybackRate(1.0)),
panning: Value::Fixed(Panning::CENTER),
fade_in_tween: None
};
}
}
impl AudioSettings {
pub fn start_time(self, start_time: impl Into<StartTime>) -> Self {
return Self {
start_time: start_time.into(),
..self
};
}
pub fn start_position(self, start_position: impl Into<PlaybackPosition>) -> Self {
return Self {
start_position: start_position.into(),
..self
};
}
pub fn loop_region(self, loop_region: impl IntoOptionalRegion) -> Self {
return Self {
loop_region: loop_region.into_optional_region(),
..self
};
}
pub fn volume(self, volume: impl Into<Value<Decibels>>) -> Self {
return Self {
volume: volume.into(),
..self
};
}
pub fn playback_rate(self, playback_rate: impl Into<Value<PlaybackRate>>) -> Self {
return Self {
playback_rate: playback_rate.into(),
..self
};
}
pub fn panning(self, panning: impl Into<Value<Panning>>) -> Self {
return Self {
panning: panning.into(),
..self
};
}
pub fn fade_in_tween(self, fade_in_tween: impl Into<Option<Tween>>) -> Self {
return Self {
fade_in_tween: fade_in_tween.into(),
..self
};
}
pub fn convert_to_static(&self) -> StaticSoundSettings {
return StaticSoundSettings {
start_time: self.start_time,
start_position: self.start_position,
loop_region: self.loop_region,
reverse: self.reverse,
volume: self.volume,
playback_rate: self.playback_rate,
panning: self.panning,
fade_in_tween: self.fade_in_tween
};
}
pub fn default_of_static() -> StaticSoundSettings {
return StaticSoundSettings::default();
}
pub fn convert_to_streaming(&self) -> StreamingSoundSettings {
return StreamingSoundSettings {
start_time: self.start_time,
start_position: self.start_position,
loop_region: self.loop_region,
volume: self.volume,
playback_rate: self.playback_rate,
panning: self.panning,
fade_in_tween: self.fade_in_tween
};
}
pub fn default_of_streaming() -> StreamingSoundSettings {
return StreamingSoundSettings::default();
}
}
#[derive(Resource)]
pub struct AudioSource {
pub audio_manager: Option<AudioManager>,
pub static_sounds: HashMap<String, StaticSoundData>,
pub streaming_sounds: HashMap<String, StreamingSoundData<FromFileError>>,
pub streaming_handles: HashMap<String, StreamingSoundHandle<FromFileError>>
}
unsafe impl Send for AudioSource {}
unsafe impl Sync for AudioSource {}
impl AudioSource {
pub fn new() -> Result<Self, AudioError> {
let cpal_host: Host = cpal::default_host();
if let Some(default_output_device) = cpal_host.default_output_device() {
if default_output_device.default_output_config().is_ok() {
let audio_manager: AudioManager = AudioManager::new(AudioManagerSettings::default()).map_err(AudioError::from)?;
return Ok(Self {
audio_manager: Some(audio_manager),
static_sounds: HashMap::new(),
streaming_sounds: HashMap::new(),
streaming_handles: HashMap::new()
}).map_err(AudioError::from);
}
}
warn!("No output device was found when trying to create the Audio Manager.\nAn empty Audio Source will be created and no sound will be made.");
return Ok(Self {
audio_manager: None,
static_sounds: HashMap::new(),
streaming_sounds: HashMap::new(),
streaming_handles: HashMap::new()
}).map_err(AudioError::from);
}
pub fn check_sound_file_format(&mut self, path: impl AsRef<Path>) -> Result<(), AudioError> {
let file_extension: Option<String> = path.as_ref()
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase());
match file_extension.as_deref() {
Some("wav" | "ogg" | "flac") => Ok(()).map_err(AudioError::from),
_ => Err(AudioError::Generic(anyhow::anyhow!("Format not supported! Use 'wav', 'ogg' or 'flac' instead.")))
}
}
pub fn load_static_sound(&mut self, name: impl Into<String>, path: impl AsRef<Path>, audio_settings: AudioSettings) -> Result<(), AudioError> {
self.check_sound_file_format(&path)?;
let static_sound_data: StaticSoundData = StaticSoundData::from_file(
AssetLoader::get_path(path.as_ref().to_str().unwrap())
).map_err(AudioError::from)?;
self.static_sounds.insert(name.into(), static_sound_data.with_settings(audio_settings.convert_to_static()).clone());
return Ok(()).map_err(AudioError::from);
}
pub fn load_streaming_sound(&mut self, name: impl Into<String>, path: impl AsRef<Path>, audio_settings: AudioSettings) -> Result<(), AudioError> {
self.check_sound_file_format(&path)?;
let streaming_sound_data: StreamingSoundData<FromFileError> = StreamingSoundData::from_file(
AssetLoader::get_path(path.as_ref().to_str().unwrap())
).map_err(AudioError::from)?;
self.streaming_sounds.insert(name.into(), streaming_sound_data.with_settings(audio_settings.convert_to_streaming()));
return Ok(()).map_err(AudioError::from);
}
pub fn play_static_sound(&mut self, name: String) -> Result<(), AudioError> {
if let Some(static_sound_data) = self.static_sounds.get(&name) {
if let Some(audio_manager) = &mut self.audio_manager {
audio_manager.play(static_sound_data.clone()).map_err(AudioError::from)?;
}
}
return Ok(()).map_err(AudioError::from);
}
pub fn play_streaming_sound(&mut self, name: String) -> Result<(), AudioError> {
if let Some(streaming_sound_data) = self.streaming_sounds.remove(&name) {
if let Some(audio_manager) = &mut self.audio_manager {
let stremaing_sound_handle: StreamingSoundHandle<FromFileError> = audio_manager
.play::<StreamingSoundData<FromFileError>>(streaming_sound_data)
.map_err(AudioError::from_play_sound_error)?;
self.streaming_handles.insert(name.clone(), stremaing_sound_handle);
}
}
return Ok(()).map_err(AudioError::from);
}
pub fn resume_streaming_sound(&mut self, name: String) -> Result<(), AudioError> {
if let Some(stremaing_sound_handle) = self.streaming_handles.get_mut(&name) {
stremaing_sound_handle.resume(Tween::default());
}
return Ok(()).map_err(AudioError::from);
}
pub fn pause_streaming_sound(&mut self, name: String) -> Result<(), AudioError> {
if let Some(streaming_sound_handle) = self.streaming_handles.get_mut(&name) {
streaming_sound_handle.pause(Tween::default());
}
return Ok(()).map_err(AudioError::from);
}
pub fn stop_streaming_sound(&mut self, name: String) -> Result<(), AudioError> {
if let Some(mut streaming_sound_handle) = self.streaming_handles.remove(&name) {
streaming_sound_handle.stop(Tween::default());
}
return Ok(()).map_err(AudioError::from);
}
}