use std::{
f64::consts::PI,
io::Cursor,
path::Path,
sync::{Arc, OnceLock},
thread,
time::Duration,
};
use anyhow::Result;
use crossbeam::channel::{unbounded, Sender};
use glam::{Quat, Vec3};
use kira::{
manager::{backend::DefaultBackend, AudioManager, AudioManagerSettings, Capacities},
sound::{
static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings},
FromFileError,
},
spatial::{
emitter::{EmitterHandle, EmitterSettings},
listener::ListenerSettings,
scene::SpatialSceneSettings,
},
tween::Value,
};
#[derive(Clone, Copy, Debug, Error)]
#[error("The audio server is not started for this session.")]
pub struct NoAudioServerError;
pub(crate) fn audio_server() -> Sender<AudioUpdate> {
let (send, recv) = unbounded();
thread::spawn(|| {
let recv = recv;
let (manager_settings, scene_settings) = SETTINGS.audio.get().make();
let mut audio_manager = AudioManager::<DefaultBackend>::new(manager_settings);
if let Ok(audio_manager) = audio_manager.as_mut() {
let mut spacial_scene = audio_manager
.add_spatial_scene(scene_settings)
.expect("impossible");
loop {
match recv.recv() {
Ok(AudioUpdate::Play(sound)) => {
let mut emitter = sound.emitter.lock();
let mut settings: StaticSoundSettings = sound.settings.into();
if let Some(spatial_emitter) = emitter.get() {
if sound.object.is_none() {
emitter.take();
} else {
settings = settings.output_destination(spatial_emitter);
};
}
if let (None, Some(object)) = (emitter.get(), &sound.object) {
if let Ok(spatial_emitter) = spacial_scene.add_emitter(
object.transform.position.extend(0.0),
sound.spatial_settings().into(),
) {
settings = settings.output_destination(&spatial_emitter);
let _ = emitter.set(spatial_emitter);
}
}
let handle = audio_manager.play(StaticSoundData {
sample_rate: sound.data.sample_rate,
frames: sound.data.frames,
settings,
});
sound.handle.lock().take();
let _ = sound.handle.lock().set(handle.map_err(|x| x.into()));
}
Ok(AudioUpdate::NewLayer(layer)) => {
if let Ok(listener) = spacial_scene.add_listener(
Vec3::ZERO,
Quat::IDENTITY,
ListenerSettings::default(),
) {
let _ = layer.listener.lock().set(listener);
};
}
Ok(AudioUpdate::SettingsChange(settings)) => {
let (manager_settings, scene_settings) = settings.make();
if let Ok(mut manager) =
AudioManager::<DefaultBackend>::new(manager_settings)
{
spacial_scene = manager
.add_spatial_scene(scene_settings)
.expect("unreachable");
*audio_manager = manager;
} else {
break;
};
}
_ => (),
};
}
}
});
send
}
pub(crate) enum AudioUpdate {
Play(Sound),
NewLayer(Arc<Layer>),
SettingsChange(AudioSettings),
}
pub use kira::{
dsp::Frame,
sound::{
EndPosition, IntoOptionalRegion, PlaybackPosition, PlaybackRate, PlaybackState, Region,
},
spatial::emitter::EmitterDistances as Distances,
tween::Easing,
Volume,
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Tween {
pub duration: Duration,
pub easing: Easing,
}
impl Default for Tween {
fn default() -> Self {
kira::tween::Tween::default().into()
}
}
impl From<kira::tween::Tween> for Tween {
fn from(value: kira::tween::Tween) -> Self {
Self {
duration: value.duration,
easing: value.easing,
}
}
}
impl From<Tween> for kira::tween::Tween {
fn from(value: Tween) -> Self {
Self {
duration: value.duration,
easing: value.easing,
start_time: kira::StartTime::Immediate,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct AudioSettings {
pub sound_capacity: usize,
pub object_bound_sound_capacity: usize,
pub spatial_scene_capacity: usize,
}
impl AudioSettings {
pub fn new() -> Self {
Self::default()
}
pub fn set_sound_capacities(&mut self, sound_capacity: usize) {
self.sound_capacity = sound_capacity;
}
pub fn sound_capacity(mut self, sound_capacity: usize) -> Self {
self.sound_capacity = sound_capacity;
self
}
pub fn set_object_bound_sound_capacity(&mut self, sound_capacity: usize) {
self.object_bound_sound_capacity = sound_capacity;
}
pub fn object_bound_sound_capacity(mut self, sound_capacity: usize) -> Self {
self.object_bound_sound_capacity = sound_capacity;
self
}
pub fn set_spatial_scene_capacity(&mut self, scene_capacity: usize) {
self.spatial_scene_capacity = scene_capacity;
}
pub fn spatial_scene_capacity(mut self, scene_capacity: usize) -> Self {
self.spatial_scene_capacity = scene_capacity;
self
}
pub(crate) fn make(&self) -> (AudioManagerSettings<DefaultBackend>, SpatialSceneSettings) {
let manager_settings = AudioManagerSettings {
capacities: Capacities {
command_capacity: 256,
sound_capacity: self.sound_capacity,
clock_capacity: 1,
spatial_scene_capacity: self.spatial_scene_capacity,
..Default::default()
},
..Default::default()
};
let scene_settings = SpatialSceneSettings::new()
.emitter_capacity(self.object_bound_sound_capacity)
.listener_capacity(self.spatial_scene_capacity);
(manager_settings, scene_settings)
}
}
impl Default for AudioSettings {
fn default() -> Self {
Self {
sound_capacity: 256,
object_bound_sound_capacity: 256,
spatial_scene_capacity: 8,
}
}
}
use parking_lot::Mutex;
use thiserror::Error;
use crate::{
objects::{scenes::Layer, Object},
SETTINGS,
};
use super::RESOURCES;
#[derive(Clone, Debug, PartialEq)]
pub struct SoundData {
pub sample_rate: u32,
pub frames: Arc<[Frame]>,
}
impl SoundData {
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, FromFileError> {
let sound_data = StaticSoundData::from_file(path, StaticSoundSettings::default())?;
Ok(Self {
sample_rate: sound_data.sample_rate,
frames: sound_data.frames,
})
}
pub fn from_cursor<T: AsRef<[u8]> + Send + Sync + 'static>(
cursor: Cursor<T>,
) -> Result<Self, FromFileError> {
let sound_data = StaticSoundData::from_cursor(cursor, StaticSoundSettings::default())?;
Ok(Self {
sample_rate: sound_data.sample_rate,
frames: sound_data.frames,
})
}
pub fn duration(&self) -> Duration {
Duration::from_secs_f64(self.frames.len() as f64 / self.sample_rate as f64)
}
pub fn gen_square_wave(frequency: f64, length: f64) -> Self {
let sample_rate = 44100;
let num_samples = (sample_rate as f64 * length) as usize;
let period_samples = (sample_rate as f64 / frequency) as usize;
let mut frames = Vec::with_capacity(num_samples);
let mut sample_counter = 0;
for _ in 0..num_samples {
let value = if sample_counter < period_samples / 2 {
1.0
} else {
-1.0
};
let frame = Frame {
left: value,
right: value,
};
frames.push(frame);
sample_counter = (sample_counter + 1) % period_samples;
}
Self {
sample_rate,
frames: Arc::from(frames),
}
}
pub fn gen_sine_wave(frequency: f64, length: f64) -> Self {
let sample_rate = 44100;
let num_samples = (sample_rate as f64 * length) as usize;
let angular_frequency = 2.0 * PI * frequency;
let mut frames = Vec::with_capacity(num_samples);
for i in 0..num_samples {
let t = i as f32 / sample_rate as f32;
let value = (angular_frequency as f32 * t).sin();
let frame = Frame {
left: value,
right: value,
};
frames.push(frame);
}
Self {
sample_rate,
frames: Arc::from(frames),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpatialSettings {
pub distances: Distances,
pub attenuation_function: Option<Easing>,
pub spatialization: bool,
}
impl SpatialSettings {
pub fn new() -> Self {
Self::from(EmitterSettings::new())
}
}
impl Default for SpatialSettings {
fn default() -> Self {
Self::new()
}
}
impl From<EmitterSettings> for SpatialSettings {
fn from(value: EmitterSettings) -> Self {
Self {
distances: value.distances,
attenuation_function: value.attenuation_function,
spatialization: value.enable_spatialization,
}
}
}
impl From<SpatialSettings> for EmitterSettings {
fn from(value: SpatialSettings) -> Self {
Self::default()
.distances(value.distances)
.attenuation_function(value.attenuation_function)
.enable_spatialization(value.spatialization)
}
}
#[derive(Clone)]
pub struct Sound {
data: SoundData,
settings: SoundSettings,
spatial_settings: SpatialSettings,
emitter: Arc<Mutex<OnceLock<EmitterHandle>>>,
handle: Arc<Mutex<OnceLock<Result<StaticSoundHandle>>>>,
object: Option<Object>,
}
impl Sound {
pub fn new(data: SoundData, settings: SoundSettings) -> Self {
Self {
data,
settings,
spatial_settings: SpatialSettings::new(),
emitter: Arc::new(Mutex::new(OnceLock::new())),
handle: Arc::new(Mutex::new(OnceLock::new())),
object: None,
}
}
pub fn set_settings(&mut self, settings: SoundSettings) {
self.settings = settings;
}
pub fn settings(&self) -> SoundSettings {
self.settings
}
pub fn set_spatial_settings(&mut self, settings: SpatialSettings) {
self.spatial_settings = settings;
}
pub fn spatial_settings(&self) -> SpatialSettings {
self.spatial_settings
}
pub fn data(&self) -> &SoundData {
&self.data
}
pub fn state(&self) -> PlaybackState {
if let Some(Ok(handle)) = self.handle.lock().get() {
handle.state()
} else {
PlaybackState::Stopped
}
}
pub fn position(&self) -> f64 {
if let Some(Ok(handle)) = self.handle.lock().get() {
handle.position()
} else {
0.0
}
}
pub fn set_volume(&mut self, volume: impl Into<Volume>, tween: Tween) -> Result<()> {
let volume = volume.into();
let value_volume = Value::Fixed(volume);
self.settings.volume = volume;
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.set_volume(value_volume, tween.into())?;
}
Ok(())
}
pub fn set_playback_rate(
&mut self,
playback_rate: impl Into<PlaybackRate>,
tween: Tween,
) -> Result<()> {
let playback_rate = playback_rate.into();
let value_playback_rate = Value::Fixed(playback_rate);
self.settings.playback_rate = playback_rate;
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.set_playback_rate(value_playback_rate, tween.into())?;
}
Ok(())
}
pub fn set_panning(&mut self, panning: f64, tween: Tween) -> Result<()> {
let value_panning = Value::Fixed(panning);
self.settings.panning = panning;
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.set_panning(value_panning, tween.into())?;
}
Ok(())
}
pub fn set_playback_region(&mut self, playback_region: impl Into<Region>) -> Result<()> {
self.settings.playback_region = playback_region.into();
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.set_playback_region(self.settings.playback_region)?;
}
Ok(())
}
pub fn set_loop_region(&mut self, loop_region: impl IntoOptionalRegion) -> Result<()> {
self.settings.loop_region = loop_region.into_optional_loop_region();
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.set_loop_region(self.settings.loop_region)?;
}
Ok(())
}
pub fn bind_to_object(&mut self, object: Option<&Object>) {
self.object = object.cloned();
}
pub fn object(&self) -> Option<&Object> {
self.object.as_ref()
}
pub fn update(&mut self, tween: Tween) -> Result<()> {
if let (Some(emitter), Some(object)) = (self.emitter.lock().get_mut(), &mut self.object) {
object.update();
emitter.set_position(object.transform.position.extend(0.0), tween.into())?
}
Ok(())
}
pub fn play(&mut self) -> Result<()> {
if self.state() != PlaybackState::Playing {
RESOURCES
.audio_server
.send(AudioUpdate::Play(self.clone()))
.ok()
.ok_or(NoAudioServerError)?;
}
Ok(())
}
pub fn pause(&mut self, tween: Tween) -> Result<()> {
if self.state() != PlaybackState::Paused {
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.pause(tween.into())?;
}
}
Ok(())
}
pub fn resume(&mut self, tween: Tween) -> Result<()> {
if self.state() != PlaybackState::Playing {
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.resume(tween.into())?;
}
}
Ok(())
}
pub fn stop(&mut self, tween: Tween) -> Result<()> {
if self.state() != PlaybackState::Stopped {
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.stop(tween.into())?;
}
}
Ok(())
}
pub fn seek_to(&mut self, position: f64) -> Result<()> {
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.seek_to(position)?;
}
Ok(())
}
pub fn seek_by(&mut self, position: f64) -> Result<()> {
if let Some(Ok(handle)) = self.handle.lock().get_mut() {
handle.seek_by(position)?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SoundSettings {
pub playback_region: Region,
pub loop_region: Option<Region>,
pub reverse: bool,
pub volume: Volume,
pub playback_rate: PlaybackRate,
pub panning: f64,
pub fade_in_tween: Option<Tween>,
}
macro_rules! builder_pattern {
($field:ident, $title:expr, $type:ty) => {
#[doc=concat!("Sets ", $title, " and returns self.")]
#[inline]
pub fn $field(mut self, $field: impl Into<$type>) -> Self {
self.$field = $field.into();
self
}
};
}
impl SoundSettings {
pub fn new() -> Self {
let settings = StaticSoundSettings::new();
let (volume, playback_rate, panning) =
if let (Value::Fixed(volume), Value::Fixed(playback_rate), Value::Fixed(panning)) =
(settings.volume, settings.playback_rate, settings.panning)
{
(volume, playback_rate, panning)
} else {
unreachable!()
};
Self {
playback_region: settings.playback_region,
loop_region: settings.loop_region,
reverse: settings.reverse,
volume,
playback_rate,
panning,
fade_in_tween: settings.fade_in_tween.map(Tween::from),
}
}
builder_pattern!(playback_region, "the playback region", Region);
builder_pattern!(loop_region, "the optional loop region", Option<Region>);
builder_pattern!(reverse, "whether this sound plays reverse", bool);
builder_pattern!(volume, "the volume", Volume);
builder_pattern!(playback_rate, "the playback rate", PlaybackRate);
builder_pattern!(panning, "the panning", f64);
builder_pattern!(fade_in_tween, "the fade in tween", Option<Tween>);
}
impl From<SoundSettings> for StaticSoundSettings {
fn from(value: SoundSettings) -> StaticSoundSettings {
StaticSoundSettings::new()
.playback_region(value.playback_region)
.loop_region(value.loop_region)
.reverse(value.reverse)
.volume(value.volume)
.playback_rate(value.playback_rate)
.panning(value.panning)
.fade_in_tween(value.fade_in_tween.map(kira::tween::Tween::from))
}
}
impl From<StaticSoundSettings> for SoundSettings {
fn from(value: StaticSoundSettings) -> Self {
let (volume, playback_rate, panning) =
if let (Value::Fixed(volume), Value::Fixed(playback_rate), Value::Fixed(panning)) =
(value.volume, value.playback_rate, value.panning)
{
(volume, playback_rate, panning)
} else {
unreachable!()
};
Self::new()
.playback_region(value.playback_region)
.loop_region(value.loop_region)
.reverse(value.reverse)
.volume(volume)
.playback_rate(playback_rate)
.panning(panning)
.fade_in_tween(value.fade_in_tween.map(Tween::from))
}
}
impl Default for SoundSettings {
fn default() -> Self {
Self::new()
}
}