Initial commit

This commit is contained in:
hodasemi 2023-01-16 13:17:58 +01:00 committed by Michael Hübner
commit 4b31689e41
6 changed files with 703 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

7
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#56084C",
"titleBar.activeBackground": "#780B6B",
"titleBar.activeForeground": "#FFFBFE"
}
}

12
Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "audio"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rg3d-sound = "0.26.0"
anyhow = { version = "1.0.68", features = ["backtrace"] }
utilities = { git = "https://gavania.de/hodasemi/utilities.git" }
assetpath = { git = "https://gavania.de/hodasemi/vulkan_lib.git" }

5
src/lib.rs Normal file
View file

@ -0,0 +1,5 @@
mod shared;
mod sound;
pub use shared::*;
pub use sound::*;

165
src/shared.rs Normal file
View file

@ -0,0 +1,165 @@
use anyhow::Result;
use utilities::prelude::*;
use super::{Music, Sound};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Copy, Clone, Debug)]
pub enum SoundInterpretation {
Generic,
Spatial,
}
#[derive(Debug)]
// pub(crate) struct VolumeHandler {
pub struct VolumeHandler {
sound_volumes: HashMap<String, f32>,
}
impl VolumeHandler {
pub(crate) fn new(volume_info: HashMap<String, f32>) -> Self {
VolumeHandler {
sound_volumes: volume_info,
}
}
pub(crate) fn set_volume(
&mut self,
sound_type: &str,
volume: f32,
audio_objects: &mut AudioObjects,
) -> Result<()> {
// check map if sound type is already present
let changed = match self.sound_volumes.get_mut(sound_type) {
Some(stype) => {
// set volume
if *stype != volume {
*stype = volume;
true
} else {
false
}
}
None => {
// insert volume for requested sound type
self.sound_volumes.insert(sound_type.to_string(), volume);
true
}
};
if changed {
if sound_type == "music" {
audio_objects.apply_to_music(|music| music.set_volume(volume))?;
} else {
audio_objects.apply_to_sound(Some(sound_type), |sound| sound.set_volume(volume))?;
}
}
Ok(())
}
pub(crate) fn volume(&self, sound_type: &str) -> f32 {
self.sound_volumes[sound_type]
}
}
pub trait ClearQueueAudioObject: Send + Sync {
fn end_looping(&self) -> Result<()>;
fn is_playing(&self) -> Result<bool>;
}
#[derive(Default)]
pub struct AudioObjects {
// sound handling
sounds: HashMap<String, Vec<Arc<Sound>>>,
// music handling
music: Vec<Arc<Music>>,
clear_queue: Vec<Arc<dyn ClearQueueAudioObject>>,
}
impl AudioObjects {
pub fn add_sound(&mut self, sound: Arc<Sound>) {
match self.sounds.get_mut(sound.sound_type()) {
Some(sounds) => sounds.push(sound),
None => {
self.sounds
.insert(sound.sound_type().to_string(), vec![sound]);
}
}
}
pub fn add_music(&mut self, music: Arc<Music>) {
self.music.push(music);
}
pub fn apply_to_sound<F>(&self, sound_type: Option<&str>, f: F) -> Result<()>
where
F: Fn(&Arc<Sound>) -> Result<()>,
{
match sound_type {
Some(sound_type) => {
if let Some(sounds) = self.sounds.get(sound_type) {
for sound in sounds.iter() {
f(sound)?;
}
}
}
None => {
for sounds in self.sounds.values() {
for sound in sounds.iter() {
f(sound)?;
}
}
}
}
Ok(())
}
pub fn apply_to_music<F>(&self, f: F) -> Result<()>
where
F: Fn(&Arc<Music>) -> Result<()>,
{
for music in self.music.iter() {
f(music)?;
}
Ok(())
}
pub fn clear(&mut self) {
self.sounds.clear();
self.music.clear();
}
pub fn remove_sound(&mut self, sound: &Arc<Sound>) -> Result<()> {
if let Some(sounds) = self.sounds.get_mut(sound.sound_type()) {
if let Some(old_sound) = erase_arc(sounds, sound) {
old_sound.end_looping()?;
self.clear_queue.push(old_sound);
}
}
Ok(())
}
pub fn check_clear_queue(&mut self) -> Result<()> {
let mut new_queue = Vec::new();
for sound in self.clear_queue.iter() {
if sound.is_playing()? {
new_queue.push(sound.clone());
}
}
self.clear_queue = new_queue;
Ok(())
}
}

512
src/sound.rs Normal file
View file

@ -0,0 +1,512 @@
use anyhow::Result;
use super::{AudioObjects, ClearQueueAudioObject, SoundInterpretation, VolumeHandler};
use rg3d_sound::{
algebra::Vector3,
buffer::{DataSource, SoundBufferResource},
context::SoundContext,
engine::SoundEngine,
futures::executor::block_on,
pool::Handle,
source::{
generic::{GenericSource, GenericSourceBuilder},
spatial::{SpatialSource, SpatialSourceBuilder},
SoundSource, Status,
},
};
use assetpath::AssetPath;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
#[inline]
fn act_on_generic_source<T, F>(
ctx: &SoundContext,
handle: Handle<SoundSource>,
interpretation: SoundInterpretation,
f: F,
) -> Result<T>
where
F: FnOnce(&mut GenericSource) -> Result<T>,
{
let mut state = ctx.state();
let source = state.source_mut(handle);
let generic_source = match interpretation {
SoundInterpretation::Generic => match source {
SoundSource::Generic(generic_source) => generic_source,
SoundSource::Spatial(_) => {
return Err(anyhow::Error::msg(
"Audio Error: called generic on spatial source",
))
}
},
SoundInterpretation::Spatial => match source {
SoundSource::Spatial(spatial_source) => spatial_source.generic_mut(),
SoundSource::Generic(_) => {
return Err(anyhow::Error::msg(
"Audio Error: called spatial on generic source",
))
}
},
};
Ok(f(generic_source)?)
}
#[inline]
fn act_on_spatial_source<T, F>(
ctx: &SoundContext,
handle: Handle<SoundSource>,
interpretation: SoundInterpretation,
f: F,
) -> Result<T>
where
F: FnOnce(&mut SpatialSource) -> Result<T>,
{
let mut state = ctx.state();
let source = state.source_mut(handle);
let spatial_source = match interpretation {
SoundInterpretation::Generic => match source {
SoundSource::Generic(_) => {
return Err(anyhow::Error::msg(
"Audio Error: requested spatial source on generic",
))
}
SoundSource::Spatial(_) => {
return Err(anyhow::Error::msg(
"Audio Error: called generic on spatial source",
))
}
},
SoundInterpretation::Spatial => match source {
SoundSource::Spatial(spatial_source) => spatial_source,
SoundSource::Generic(_) => {
return Err(anyhow::Error::msg(
"Audio Error: called spatial on generic source",
))
}
},
};
Ok(f(spatial_source)?)
}
macro_rules! sound_ctor {
($struct_name:ident) => {
pub struct $struct_name {
handle: Handle<SoundSource>,
rg3d_context: SoundContext,
interpretation: SoundInterpretation,
sound_type: String,
path: AssetPath,
duration: Duration,
}
impl $struct_name {
fn new(
handle: Handle<SoundSource>,
rg3d_context: SoundContext,
interpretation: SoundInterpretation,
sound_type: String,
path: AssetPath,
) -> Result<Arc<Self>> {
let duration = act_on_generic_source(
&rg3d_context,
handle,
interpretation,
|generic_source| Ok(generic_source.playback_time()),
)?;
Ok(Arc::new($struct_name {
handle,
rg3d_context,
interpretation,
sound_type,
path,
duration,
}))
}
pub fn play(&self, enable_looping: Option<bool>) -> Result<()> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| {
generic_source.stop()?;
generic_source.play();
if let Some(looping) = enable_looping {
generic_source.set_looping(looping);
}
Ok(())
},
)
}
pub fn pause(&self) -> Result<()> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| {
generic_source.pause();
Ok(())
},
)
}
pub fn stop_looping(&self) -> Result<()> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| {
generic_source.set_looping(false);
Ok(())
},
)
}
pub fn stop(&self) -> Result<()> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| {
generic_source.stop()?;
Ok(())
},
)
}
pub fn set_position(&self, position: impl Into<[f32; 3]>) -> Result<()> {
act_on_spatial_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|spatial_source| {
let pos = position.into();
spatial_source.set_position(Vector3::new(pos[0], pos[1], pos[2]));
Ok(())
},
)
}
pub fn set_volume(&self, volume: f32) -> Result<()> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| {
generic_source.set_gain(volume);
Ok(())
},
)
}
pub fn file_path(&self) -> &AssetPath {
&self.path
}
pub fn sound_type(&self) -> &str {
&self.sound_type
}
pub fn duration(&self) -> Duration {
self.duration
}
fn is_paused(&self) -> Result<bool> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| Ok(generic_source.status() == Status::Paused),
)
}
}
impl ClearQueueAudioObject for $struct_name {
fn end_looping(&self) -> Result<()> {
self.stop_looping()
}
fn is_playing(&self) -> Result<bool> {
act_on_generic_source(
&self.rg3d_context,
self.handle,
self.interpretation,
|generic_source| Ok(generic_source.status() == Status::Playing),
)
}
}
};
}
sound_ctor!(Music);
sound_ctor!(Sound);
enum SoundType {
Sound,
Music,
}
pub struct SoundHandler {
volume_handler: VolumeHandler,
rg3d_context: SoundContext,
_sound_engine: Arc<Mutex<SoundEngine>>,
data: HashMap<AssetPath, SoundBufferResource>,
audio_objects: AudioObjects,
}
impl SoundHandler {
pub fn new(volume_info: HashMap<String, f32>) -> Result<Self> {
let sound_engine = SoundEngine::new();
let rg3d_context = SoundContext::new();
sound_engine
.lock()
.unwrap()
.add_context(rg3d_context.clone());
let master_volume = match volume_info.get("master") {
Some(v) => *v,
None => 1.0,
};
rg3d_context.state().set_master_gain(master_volume);
Ok(SoundHandler {
volume_handler: VolumeHandler::new(volume_info),
rg3d_context,
_sound_engine: sound_engine,
data: HashMap::new(),
audio_objects: AudioObjects::default(),
})
}
pub fn set_position(&self, position: impl Into<[f32; 3]>) -> Result<()> {
let pos = position.into();
self.rg3d_context
.state()
.listener_mut()
.set_position(Vector3::new(pos[0], pos[1], pos[2]));
Ok(())
}
pub fn set_direction(
&self,
direction: impl Into<[f32; 3]>,
up: impl Into<[f32; 3]>,
) -> Result<()> {
let dir = direction.into();
let up = up.into();
self.rg3d_context.state().listener_mut().set_orientation_rh(
Vector3::new(dir[0], dir[1], dir[2]),
Vector3::new(up[0], up[1], up[2]),
);
Ok(())
}
pub fn load_sound(
&mut self,
path: AssetPath,
sound_type: &str,
interpretation: SoundInterpretation,
) -> Result<Arc<Sound>> {
let volume = self.volume_handler.volume(sound_type);
let handle = self.source_handle(&path, volume, interpretation, SoundType::Sound)?;
let sound = Sound::new(
handle,
self.rg3d_context.clone(),
interpretation,
sound_type.to_string(),
path,
)?;
self.audio_objects.add_sound(sound.clone());
Ok(sound)
}
pub fn load_music(
&mut self,
path: AssetPath,
interpretation: SoundInterpretation,
) -> Result<Arc<Music>> {
let volume = self.volume_handler.volume("music");
let handle = self.source_handle(&path, volume, interpretation, SoundType::Music)?;
let music = Music::new(
handle,
self.rg3d_context.clone(),
interpretation,
"music".to_string(),
path,
)?;
self.audio_objects.add_music(music.clone());
Ok(music)
}
pub fn set_volume(&mut self, sound_type: &str, volume: f32) -> Result<()> {
if sound_type == "master" {
self.rg3d_context.state().set_master_gain(volume);
Ok(())
} else {
self.volume_handler
.set_volume(sound_type, volume, &mut self.audio_objects)
}
}
pub fn volume(&self, sound_type: &str) -> Result<f32> {
if sound_type == "master" {
Ok(self.rg3d_context.state().master_gain())
} else {
Ok(self.volume_handler.volume(sound_type))
}
}
pub fn pause(&mut self) -> Result<()> {
// check if sounds are playing
self.audio_objects.apply_to_sound(None, |sound| {
if sound.is_playing()? {
sound.pause()?
}
Ok(())
})?;
self.audio_objects.apply_to_music(|music| {
if music.is_playing()? {
music.pause()?
}
Ok(())
})?;
Ok(())
}
pub fn resume(&self) -> Result<()> {
// check if sounds are paused
self.audio_objects.apply_to_sound(None, |sound| {
if sound.is_paused()? {
sound.play(None)?
}
Ok(())
})?;
self.audio_objects.apply_to_music(|music| {
if music.is_paused()? {
music.play(None)?
}
Ok(())
})?;
Ok(())
}
pub fn remove_sound(&mut self, sound: &Arc<Sound>) -> Result<()> {
self.audio_objects.remove_sound(sound)
}
fn source_handle(
&mut self,
path: &AssetPath,
volume: f32,
interpretation: SoundInterpretation,
sound_type: SoundType,
) -> Result<Handle<SoundSource>> {
let sound_buffer = match self.data.get(path) {
Some(buffer) => buffer.clone(),
None => {
let data_source =
block_on(DataSource::from_file(&path.full_path())).map_err(|_err| {
anyhow::Error::msg(format!(
"Audio Error: failed loading file {}",
path.full_path()
))
})?;
let buffer = match sound_type {
SoundType::Sound => {
SoundBufferResource::new_generic(data_source).map_err(|_| {
anyhow::Error::msg("Audio Error: failed creating generic sound buffer")
})?
}
SoundType::Music => {
SoundBufferResource::new_streaming(data_source).map_err(|_| {
anyhow::Error::msg(
"Audio Error: failed creating streaming sound buffer",
)
})?
}
};
self.data.insert(path.clone(), buffer.clone());
buffer
}
};
let generic_source_builder = GenericSourceBuilder::new()
.with_buffer(sound_buffer)
.with_status(Status::Stopped)
.with_gain(volume);
let source = match interpretation {
SoundInterpretation::Generic => generic_source_builder.build_source()?,
SoundInterpretation::Spatial => {
SpatialSourceBuilder::new(generic_source_builder.build()?).build_source()
}
};
let handle = self.rg3d_context.state().add_source(source);
Ok(handle)
}
pub fn check_clear_queue(&mut self) -> Result<()> {
self.audio_objects.check_clear_queue()
}
pub fn clear(&mut self) {
self.audio_objects.clear();
self.data.clear();
}
}
impl Drop for SoundHandler {
fn drop(&mut self) {
self.clear();
}
}