#![cfg(feature = "audio")]
use std::fmt;
use std::io;
use std::path;
use std::time;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use crate::context::Has;
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem::Filesystem;
pub struct AudioContext {
fs: Filesystem,
stream: rodio::MixerDeviceSink,
}
impl AudioContext {
pub fn new(fs: &Filesystem) -> GameResult<Self> {
let stream = rodio::DeviceSinkBuilder::open_default_sink().map_err(|_e| {
GameError::AudioError(String::from(
"Could not initialize sound system using default output device (for some reason)",
))
})?;
Ok(Self {
fs: fs.clone(),
stream,
})
}
}
impl AudioContext {
#[inline]
pub fn device(&self) -> &rodio::MixerDeviceSink {
&self.stream
}
}
impl fmt::Debug for AudioContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<AudioContext: {self:p}>")
}
}
#[derive(Clone, Debug)]
pub struct SoundData(Arc<[u8]>);
impl SoundData {
pub fn new<P: AsRef<path::Path>>(fs: &impl Has<Filesystem>, path: P) -> GameResult<Self> {
let data = fs.retrieve().read(path.as_ref())?;
Self::from_bytes(&data)
}
pub fn from_bytes(data: &[u8]) -> GameResult<Self> {
let this = Self(Arc::from(data));
if let Err(err) = this.decoder() {
return Err(GameError::AudioError(format!(
"Could not decode the given audio data: {err}"
)));
}
Ok(this)
}
fn decoder(&self) -> Result<rodio::Decoder<io::Cursor<Self>>, rodio::decoder::DecoderError> {
let cursor = io::Cursor::new(self.clone());
rodio::Decoder::new(cursor)
}
}
impl AsRef<[u8]> for SoundData {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
pub trait SoundSource {
fn play(&self) {
self.stop();
self.play_later();
self.resume();
}
fn play_later(&self);
fn play_detached(self);
fn set_repeat(&mut self, repeat: bool);
fn set_fade_in(&mut self, dur: time::Duration);
fn set_start(&mut self, dur: time::Duration);
fn set_pitch(&mut self, ratio: f32);
fn repeat(&self) -> bool;
fn pause(&self);
fn resume(&self);
fn stop(&self);
fn stopped(&self) -> bool;
fn volume(&self) -> f32;
fn set_volume(&mut self, value: f32);
fn paused(&self) -> bool;
fn playing(&self) -> bool;
fn elapsed(&self) -> time::Duration;
fn set_query_interval(&mut self, t: time::Duration);
}
#[derive(Debug)]
struct SourceState {
data: SoundData,
repeat: bool,
fade_in: time::Duration,
skip_duration: time::Duration,
speed: f32,
query_interval: time::Duration,
play_time: Arc<AtomicU64>,
}
impl SourceState {
pub fn new(data: SoundData) -> Self {
SourceState {
data,
repeat: false,
fade_in: time::Duration::from_millis(0),
skip_duration: time::Duration::from_millis(0),
speed: 1.0,
query_interval: time::Duration::from_millis(100),
play_time: Arc::new(AtomicU64::new(0)),
}
}
pub fn set_repeat(&mut self, repeat: bool) {
self.repeat = repeat;
}
pub fn set_fade_in(&mut self, dur: time::Duration) {
self.fade_in = dur;
}
pub fn set_start(&mut self, dur: time::Duration) {
self.skip_duration = dur;
}
pub fn set_pitch(&mut self, ratio: f32) {
self.speed = ratio;
}
pub fn repeat(&self) -> bool {
self.repeat
}
pub fn elapsed(&self) -> time::Duration {
let t = self.play_time.load(Ordering::Relaxed);
time::Duration::from_micros(t)
}
pub fn set_query_interval(&mut self, t: time::Duration) {
self.query_interval = t;
}
fn to_source(&self) -> impl rodio::Source + Send + 'static {
use rodio::Source;
let counter = self.play_time.clone();
let period_mus = self.query_interval.as_micros() as u64;
let fade_in = self.fade_in.max(time::Duration::from_micros(1));
let decoder = rodio::Decoder::new(io::Cursor::new(self.data.clone())).unwrap();
let source: Box<dyn rodio::Source + Send> = if self.repeat {
Box::new(decoder.repeat_infinite())
} else {
Box::new(decoder)
};
source
.skip_duration(self.skip_duration)
.speed(self.speed)
.fade_in(fade_in)
.periodic_access(self.query_interval, move |_| {
let _ = counter.fetch_add(period_mus, Ordering::Relaxed);
})
}
}
pub struct Source {
sink: rodio::Player,
state: SourceState,
}
impl Source {
pub fn new(ctx: &impl Has<AudioContext>, path: impl AsRef<path::Path>) -> GameResult<Self> {
let audio = ctx.retrieve();
let data = SoundData::new(&audio.fs, path.as_ref())?;
Self::from_data(audio, data)
}
pub fn from_data(audio: &impl Has<AudioContext>, data: SoundData) -> GameResult<Self> {
let state = SourceState::new(data);
let sink = rodio::Player::connect_new(audio.retrieve().stream.mixer());
Ok(Source { sink, state })
}
}
impl SoundSource for Source {
fn play_later(&self) {
self.sink.append(self.state.to_source());
}
fn play_detached(self) {
self.play();
self.sink.detach();
}
fn set_repeat(&mut self, repeat: bool) {
self.state.set_repeat(repeat)
}
fn set_fade_in(&mut self, dur: time::Duration) {
self.state.set_fade_in(dur)
}
fn set_start(&mut self, dur: time::Duration) {
self.state.set_start(dur)
}
fn set_pitch(&mut self, ratio: f32) {
self.state.set_pitch(ratio)
}
fn repeat(&self) -> bool {
self.state.repeat()
}
fn pause(&self) {
self.sink.pause()
}
fn resume(&self) {
self.sink.play()
}
fn stop(&self) {
self.state.play_time.store(0, Ordering::SeqCst);
self.sink.clear();
}
fn stopped(&self) -> bool {
self.sink.empty()
}
fn volume(&self) -> f32 {
self.sink.volume()
}
fn set_volume(&mut self, value: f32) {
self.sink.set_volume(value)
}
fn paused(&self) -> bool {
self.sink.is_paused()
}
fn playing(&self) -> bool {
!self.paused() && !self.stopped()
}
fn elapsed(&self) -> time::Duration {
self.state.elapsed()
}
fn set_query_interval(&mut self, t: time::Duration) {
self.state.set_query_interval(t)
}
}
impl fmt::Debug for Source {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<Audio source: {self:p}>")
}
}
pub struct SpatialSource {
sink: rodio::SpatialPlayer,
state: SourceState,
}
impl SpatialSource {
pub fn new(ctx: &impl Has<AudioContext>, path: impl AsRef<path::Path>) -> GameResult<Self> {
let audio = ctx.retrieve();
let data = SoundData::new(&audio.fs, path.as_ref())?;
Self::from_data(audio, data)
}
pub fn from_data(audio: &impl Has<AudioContext>, data: SoundData) -> GameResult<Self> {
let audio = audio.retrieve();
let state = SourceState::new(data);
let sink = rodio::SpatialPlayer::connect_new(
audio.stream.mixer(),
[0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
);
Ok(SpatialSource { sink, state })
}
}
impl SoundSource for SpatialSource {
fn play_later(&self) {
self.sink.append(self.state.to_source());
}
fn play_detached(self) {
self.play();
self.sink.detach();
}
fn set_repeat(&mut self, repeat: bool) {
self.state.set_repeat(repeat)
}
fn set_fade_in(&mut self, dur: time::Duration) {
self.state.set_fade_in(dur)
}
fn set_start(&mut self, dur: time::Duration) {
self.state.set_start(dur)
}
fn set_pitch(&mut self, ratio: f32) {
self.state.set_pitch(ratio)
}
fn repeat(&self) -> bool {
self.state.repeat()
}
fn pause(&self) {
self.sink.pause()
}
fn resume(&self) {
self.sink.play()
}
fn stop(&self) {
self.state.play_time.store(0, Ordering::SeqCst);
self.sink.clear();
}
fn stopped(&self) -> bool {
self.sink.empty()
}
fn volume(&self) -> f32 {
self.sink.volume()
}
fn set_volume(&mut self, value: f32) {
self.sink.set_volume(value)
}
fn paused(&self) -> bool {
self.sink.is_paused()
}
fn playing(&self) -> bool {
!self.paused() && !self.stopped()
}
fn elapsed(&self) -> time::Duration {
self.state.elapsed()
}
fn set_query_interval(&mut self, t: time::Duration) {
self.state.set_query_interval(t)
}
}
impl SpatialSource {
pub fn set_position<P>(&self, pos: P)
where
P: Into<mint::Point3<f32>>,
{
self.sink.set_emitter_position(pos.into().into());
}
pub fn set_ears<P>(&self, left: P, right: P)
where
P: Into<mint::Point3<f32>>,
{
self.sink.set_left_ear_position(left.into().into());
self.sink.set_right_ear_position(right.into().into());
}
}
impl fmt::Debug for SpatialSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<Spatial audio source: {self:p}>")
}
}