diff --git a/crates/comet_app/Cargo.toml b/crates/comet_app/Cargo.toml index e12cf8a..59bd1f5 100755 --- a/crates/comet_app/Cargo.toml +++ b/crates/comet_app/Cargo.toml @@ -11,6 +11,7 @@ comet_colors = { path = "../comet_colors" } comet_log = { path = "../comet_log" } comet_input = { path = "../comet_input" } comet_structs = { path = "../comet_structs" } +comet_sound = { path = "../comet_sound" } winit = { version = "0.29", features = ["rwh_05"] } pollster = "0.3" diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs index ca08f30..214bfc4 100755 --- a/crates/comet_app/src/app.rs +++ b/crates/comet_app/src/app.rs @@ -3,6 +3,7 @@ use comet_ecs::{Camera2D, Component, Entity, Render2D, Scene, Text, Transform2D, use comet_input::keyboard::Key; use comet_log::*; use comet_renderer::renderer::Renderer; +use comet_sound::*; use std::any::{type_name, Any, TypeId}; use std::sync::Arc; use winit::dpi::LogicalSize; @@ -30,6 +31,7 @@ pub struct App { delta_time: f32, update_timer: f32, game_state: Option>, + audio: Box, scene: Scene, fullscreen: bool, should_quit: bool, @@ -47,6 +49,7 @@ impl App { delta_time: 0.0, update_timer: 0.0166667, game_state: None, + audio: Box::new(KiraAudio::new()), scene: Scene::new(), fullscreen: false, should_quit: false, @@ -246,6 +249,38 @@ impl App { self.scene.has_prefab(name) } + pub fn load_audio(&mut self, name: &str, path: &str) { + self.audio.load(name, path); + } + + pub fn play_audio(&mut self, name: &str, looped: bool) { + self.audio.play(name, looped); + } + + pub fn pause_audio(&mut self, name: &str) { + self.audio.pause(name); + } + + pub fn stop_audio(&mut self, name: &str) { + self.audio.stop(name); + } + + pub fn stop_all_audio(&mut self) { + self.audio.stop_all(); + } + + pub fn update_audio(&mut self, dt: f32) { + self.audio.update(dt); + } + + pub fn is_playing(&self, name: &str) -> bool { + self.audio.is_playing(name) + } + + pub fn set_volume(&mut self, name: &str, volume: f32) { + self.audio.set_volume(name, volume); + } + /// Stops the event loop and with that quits the `App`. pub fn quit(&mut self) { self.should_quit = true; diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index 39b87c7..8f1a845 100755 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -2,6 +2,7 @@ // You can use these components as is or as a reference to create your own components // Also just as a nomenclature: bundles are a component made up of multiple components, // so it's a collection of components bundled together (like Transform2D) +// They are intended to work with the base suite of systems provided by the engine. use crate::math::{v2, v3}; use crate::{Entity, Scene}; use comet_colors::Color as ColorTrait; @@ -78,6 +79,15 @@ pub struct Timer { done: bool, } +#[derive(Component)] +pub struct AudioSource { + name: &'static str, + path: Option<&'static str>, + looped: bool, + volume: f32, + pitch: f32, +} + // ################################################## // # BUNDLES # // ################################################## @@ -271,6 +281,14 @@ impl Render2D { scale: v2::new(1.0, 1.0), } } + + pub fn scale(&self) -> v2 { + self.scale + } + + pub fn set_scale(&mut self, scale: v2) { + self.scale = scale; + } } impl Render for Render2D { @@ -548,3 +566,47 @@ impl Timer { self.done = false; } } + +impl AudioSource { + pub fn new(name: &'static str, path: Option<&'static str>) -> Self { + Self { + name, + path, + looped: false, + volume: 1.0, + pitch: 1.0, + } + } + + pub fn name(&self) -> &str { + self.name + } + + pub fn path(&self) -> Option<&str> { + self.path + } + + pub fn looped(&self) -> bool { + self.looped + } + + pub fn volume(&self) -> f32 { + self.volume + } + + pub fn pitch(&self) -> f32 { + self.pitch + } + + pub fn set_looped(&mut self, looped: bool) { + self.looped = looped; + } + + pub fn set_volume(&mut self, volume: f32) { + self.volume = volume.clamp(0.0, 1.0); + } + + pub fn set_pitch(&mut self, pitch: f32) { + self.pitch = pitch; + } +} diff --git a/crates/comet_sound/Cargo.toml b/crates/comet_sound/Cargo.toml index a2d1566..5894411 100644 --- a/crates/comet_sound/Cargo.toml +++ b/crates/comet_sound/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -rodio = "0.12.0" \ No newline at end of file +kira = "0.10.8" diff --git a/crates/comet_sound/src/audio.rs b/crates/comet_sound/src/audio.rs new file mode 100644 index 0000000..772dff1 --- /dev/null +++ b/crates/comet_sound/src/audio.rs @@ -0,0 +1,13 @@ +pub trait Audio { + fn new() -> Self + where + Self: Sized; + fn load(&mut self, name: &str, path: &str); + fn play(&mut self, name: &str, looped: bool); + fn pause(&mut self, name: &str); + fn stop(&mut self, name: &str); + fn stop_all(&mut self); + fn update(&mut self, dt: f32); + fn is_playing(&self, name: &str) -> bool; + fn set_volume(&mut self, name: &str, volume: f32); +} diff --git a/crates/comet_sound/src/kira.rs b/crates/comet_sound/src/kira.rs new file mode 100644 index 0000000..bfbf09b --- /dev/null +++ b/crates/comet_sound/src/kira.rs @@ -0,0 +1,86 @@ +use crate::audio::Audio; +use kira::{ + sound::static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings}, + AudioManager, AudioManagerSettings, Decibels, Tween, +}; +use std::{collections::HashMap, path::Path}; + +pub struct KiraAudio { + manager: AudioManager, + sounds: HashMap, + handles: HashMap, +} + +impl KiraAudio { + fn load_sound(path: &Path) -> Option { + StaticSoundData::from_file(path).ok() + } +} + +impl Audio for KiraAudio { + fn new() -> Self { + Self { + manager: AudioManager::new(AudioManagerSettings::default()).unwrap(), + sounds: HashMap::new(), + handles: HashMap::new(), + } + } + + fn load(&mut self, name: &str, path: &str) { + if let Some(sound) = Self::load_sound(Path::new(path)) { + self.sounds.insert(name.to_string(), sound); + } + } + + fn play(&mut self, name: &str, looped: bool) { + if let Some(sound) = self.sounds.get(name) { + let mut settings = StaticSoundSettings::default(); + + if looped { + settings = settings.loop_region(..); + } + + if let Ok(handle) = self.manager.play(sound.clone().with_settings(settings)) { + self.handles.insert(name.to_string(), handle); + } + } + } + + fn pause(&mut self, name: &str) { + if let Some(handle) = self.handles.get_mut(name) { + handle.pause(Tween::default()); + } + } + + fn stop(&mut self, name: &str) { + if let Some(handle) = self.handles.get_mut(name) { + handle.stop(Tween::default()); + } + } + + fn stop_all(&mut self) { + for handle in self.handles.values_mut() { + handle.stop(Tween::default()); + } + } + + // KiraAudio needs no updating function, it just exists to make the trait happy + fn update(&mut self, _dt: f32) {} + + fn is_playing(&self, name: &str) -> bool { + self.handles.contains_key(name) + } + + fn set_volume(&mut self, name: &str, volume: f32) { + let vol = volume.clamp(0.0, 1.0); + let db = if vol == 0.0 { + Decibels::from(-80.0) // effectively silent + } else { + Decibels::from(20.0 * vol.log10()) + }; + + if let Some(handle) = self.handles.get_mut(name) { + handle.set_volume(db, Tween::default()); + } + } +} diff --git a/crates/comet_sound/src/lib.rs b/crates/comet_sound/src/lib.rs new file mode 100644 index 0000000..fc49c59 --- /dev/null +++ b/crates/comet_sound/src/lib.rs @@ -0,0 +1,5 @@ +mod audio; +mod kira; + +pub use audio::Audio; +pub use kira::KiraAudio; \ No newline at end of file diff --git a/crates/comet_sound/src/main.rs b/crates/comet_sound/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/crates/comet_sound/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -}