use std::fmt;
use std::io;
use std::io::Read;
use std::mem;
use std::path;
use std::time;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use mint;
use rodio;
use crate::context::Context;
use crate::error::GameError;
use crate::error::GameResult;
use crate::filesystem;
pub trait AudioContext {
fn device(&self) -> &rodio::Device;
}
pub struct RodioAudioContext {
device: rodio::Device,
}
impl RodioAudioContext {
pub fn new() -> GameResult<Self> {
let device = rodio::default_output_device().ok_or_else(|| {
GameError::AudioError(String::from(
"Could not initialize sound system using default output device (for some reason)",
))
})?;
Ok(Self { device })
}
}
impl AudioContext for RodioAudioContext {
fn device(&self) -> &rodio::Device {
&self.device
}
}
impl fmt::Debug for RodioAudioContext {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<RodioAudioContext: {:p}>", self)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NullAudioContext;
impl AudioContext for NullAudioContext {
fn device(&self) -> &rodio::Device {
panic!("Audio module disabled")
}
}
#[derive(Clone, Debug)]
pub struct SoundData(Arc<[u8]>);
impl SoundData {
pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
let path = path.as_ref();
let file = &mut filesystem::open(context, path)?;
SoundData::from_read(file)
}
pub fn from_bytes(data: &[u8]) -> Self {
SoundData(Arc::from(data))
}
pub fn from_read<R>(reader: &mut R) -> GameResult<Self>
where
R: Read,
{
let mut buffer = Vec::new();
let _ = reader.read_to_end(&mut buffer)?;
Ok(SoundData::from(buffer))
}
pub fn can_play(&self) -> bool {
let cursor = io::Cursor::new(self.clone());
rodio::Decoder::new(cursor).is_ok()
}
}
impl From<Arc<[u8]>> for SoundData {
#[inline]
fn from(arc: Arc<[u8]>) -> Self {
SoundData(arc)
}
}
impl From<Vec<u8>> for SoundData {
fn from(v: Vec<u8>) -> Self {
SoundData(Arc::from(v))
}
}
impl From<Box<[u8]>> for SoundData {
fn from(b: Box<[u8]>) -> Self {
SoundData(Arc::from(b))
}
}
impl AsRef<[u8]> for SoundData {
#[inline]
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
pub trait SoundSource {
#[inline(always)]
fn play(&mut self) -> GameResult {
self.stop();
self.play_later()
}
fn play_later(&self) -> GameResult;
fn play_detached(&mut self) -> GameResult;
fn set_repeat(&mut self, repeat: bool);
fn set_fade_in(&mut self, dur: time::Duration);
fn set_pitch(&mut self, ratio: f32);
fn repeat(&self) -> bool;
fn pause(&self);
fn resume(&self);
fn stop(&mut 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)]
pub struct SourceState {
data: io::Cursor<SoundData>,
repeat: bool,
fade_in: time::Duration,
speed: f32,
query_interval: time::Duration,
play_time: Arc<AtomicUsize>,
}
impl SourceState {
pub fn new(cursor: io::Cursor<SoundData>) -> Self {
SourceState {
data: cursor,
repeat: false,
fade_in: time::Duration::from_millis(0),
speed: 1.0,
query_interval: time::Duration::from_millis(100),
play_time: Arc::new(AtomicUsize::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_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::SeqCst);
time::Duration::from_micros(t as u64)
}
pub fn set_query_interval(&mut self, t: time::Duration) {
self.query_interval = t;
}
}
pub struct Source {
sink: rodio::Sink,
state: SourceState,
}
impl Source {
pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
let path = path.as_ref();
let data = SoundData::new(context, path)?;
Source::from_data(context, data)
}
pub fn from_data(context: &mut Context, data: SoundData) -> GameResult<Self> {
if !data.can_play() {
return Err(GameError::AudioError(
"Could not decode the given audio data".to_string(),
));
}
let sink = rodio::Sink::new(&context.audio_context.device());
let cursor = io::Cursor::new(data);
Ok(Source {
sink: sink,
state: SourceState::new(cursor),
})
}
}
impl SoundSource for Source {
fn play_later(&self) -> GameResult {
use rodio::Source;
let cursor = self.state.data.clone();
let counter = self.state.play_time.clone();
let period_mus = self.state.query_interval.as_secs() as usize * 1_000_000
+ self.state.query_interval.subsec_micros() as usize;
if self.state.repeat {
let sound = rodio::Decoder::new(cursor)?
.repeat_infinite()
.speed(self.state.speed)
.fade_in(self.state.fade_in)
.periodic_access(self.state.query_interval, move |_| {
let _ = counter.fetch_add(period_mus, Ordering::SeqCst);
});
self.sink.append(sound);
} else {
let sound = rodio::Decoder::new(cursor)?
.speed(self.state.speed)
.fade_in(self.state.fade_in)
.periodic_access(self.state.query_interval, move |_| {
let _ = counter.fetch_add(period_mus, Ordering::SeqCst);
});
self.sink.append(sound);
}
Ok(())
}
fn play_detached(&mut self) -> GameResult {
self.stop();
self.play_later()?;
let device = rodio::default_output_device().unwrap();
let new_sink = rodio::Sink::new(&device);
let old_sink = mem::replace(&mut self.sink, new_sink);
old_sink.detach();
Ok(())
}
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_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(&mut self) {
let volume = self.volume();
let device = rodio::default_output_device().unwrap();
self.sink = rodio::Sink::new(&device);
self.state.play_time.store(0, Ordering::SeqCst);
self.set_volume(volume);
}
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: {:p}>", self)
}
}
pub struct SpatialSource {
sink: rodio::SpatialSink,
state: SourceState,
left_ear: mint::Point3<f32>,
right_ear: mint::Point3<f32>,
emitter_position: mint::Point3<f32>,
}
impl SpatialSource {
pub fn new<P: AsRef<path::Path>>(context: &mut Context, path: P) -> GameResult<Self> {
let path = path.as_ref();
let data = SoundData::new(context, path)?;
SpatialSource::from_data(context, data)
}
pub fn from_data(context: &mut Context, data: SoundData) -> GameResult<Self> {
if !data.can_play() {
return Err(GameError::AudioError(
"Could not decode the given audio data".to_string(),
));
}
let sink = rodio::SpatialSink::new(
&context.audio_context.device(),
[0.0, 0.0, 0.0],
[-1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
);
let cursor = io::Cursor::new(data);
Ok(SpatialSource {
sink,
state: SourceState::new(cursor),
left_ear: [-1.0, 0.0, 0.0].into(),
right_ear: [1.0, 0.0, 0.0].into(),
emitter_position: [0.0, 0.0, 0.0].into(),
})
}
}
impl SoundSource for SpatialSource {
fn play_later(&self) -> GameResult {
use rodio::Source;
let cursor = self.state.data.clone();
let counter = self.state.play_time.clone();
let period_mus = self.state.query_interval.as_secs() as usize * 1_000_000
+ self.state.query_interval.subsec_micros() as usize;
if self.state.repeat {
let sound = rodio::Decoder::new(cursor)?
.repeat_infinite()
.speed(self.state.speed)
.fade_in(self.state.fade_in)
.periodic_access(self.state.query_interval, move |_| {
let _ = counter.fetch_add(period_mus, Ordering::SeqCst);
});
self.sink.append(sound);
} else {
let sound = rodio::Decoder::new(cursor)?
.speed(self.state.speed)
.fade_in(self.state.fade_in)
.periodic_access(self.state.query_interval, move |_| {
let _ = counter.fetch_add(period_mus, Ordering::SeqCst);
});
self.sink.append(sound);
}
Ok(())
}
fn play_detached(&mut self) -> GameResult {
self.stop();
self.play_later()?;
let device = rodio::default_output_device().unwrap();
let new_sink = rodio::SpatialSink::new(
&device,
self.emitter_position.into(),
self.left_ear.into(),
self.right_ear.into(),
);
let old_sink = mem::replace(&mut self.sink, new_sink);
old_sink.detach();
Ok(())
}
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_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(&mut self) {
let volume = self.volume();
let device = rodio::default_output_device().unwrap();
self.sink = rodio::SpatialSink::new(
&device,
self.emitter_position.into(),
self.left_ear.into(),
self.right_ear.into(),
);
self.state.play_time.store(0, Ordering::SeqCst);
self.set_volume(volume);
}
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>(&mut self, pos: P)
where
P: Into<mint::Point3<f32>>,
{
self.emitter_position = pos.into();
self.sink.set_emitter_position(self.emitter_position.into());
}
pub fn set_ears<P>(&mut self, left: P, right: P)
where
P: Into<mint::Point3<f32>>,
{
self.left_ear = left.into();
self.right_ear = right.into();
self.sink.set_left_ear_position(self.left_ear.into());
self.sink.set_right_ear_position(self.right_ear.into());
}
}
impl fmt::Debug for SpatialSource {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<Spatial audio source: {:p}>", self)
}
}