#![crate_name = "ao"]
#![doc(html_root_url = "http://rustdoc.taricorp.net/ao/ao/")]
#![crate_type = "lib"]
#![deny(dead_code, missing_docs)]
extern crate libc;
use libc::{c_int, c_char};
use std::error::Error;
use std::ffi::{CStr, CString};
use std::fmt;
use std::io;
use std::marker::PhantomData;
use std::mem::size_of;
use std::path::Path;
use std::str;
use std::sync::atomic::{AtomicBool, Ordering, ATOMIC_BOOL_INIT};
use std::ptr;
#[allow(non_camel_case_types, dead_code)]
mod ffi;
pub mod auto;
pub type AoResult<T> = Result<T, AoError>;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum AoError {
NoDriver = ffi::AO_ENODRIVER as isize,
NotFile = ffi::AO_ENOTFILE as isize,
NotLive = ffi::AO_ENOTLIVE as isize,
BadOption = ffi::AO_EBADOPTION as isize,
OpenDevice = ffi::AO_EOPENDEVICE as isize,
OpenFile = ffi::AO_EOPENFILE as isize,
FileExists = ffi::AO_EFILEEXISTS as isize,
BadFormat = ffi::AO_EBADFORMAT as isize,
Unknown = ffi::AO_EFAIL as isize,
}
impl AoError {
fn from_errno() -> AoError {
match io::Error::last_os_error().raw_os_error().unwrap() as c_int {
ffi::AO_ENODRIVER => AoError::NoDriver,
ffi::AO_ENOTFILE => AoError::NotFile,
ffi::AO_ENOTLIVE => AoError::NotLive,
ffi::AO_EBADOPTION => AoError::BadOption,
ffi::AO_EOPENDEVICE => AoError::OpenDevice,
ffi::AO_EOPENFILE => AoError::OpenFile,
ffi::AO_EFILEEXISTS => AoError::FileExists,
ffi::AO_EBADFORMAT => AoError::BadFormat,
_ => AoError::Unknown
}
}
}
impl Error for AoError {
fn description(&self) -> &str {
match *self {
AoError::NoDriver => "No such driver",
AoError::NotFile => "Driver is not a file output device",
AoError::NotLive => "Driver is not a live output device",
AoError::BadOption => "A valid option key has an invalid value",
AoError::OpenDevice => "Cannot open the output device",
AoError::OpenFile => "Cannot open the output file",
AoError::FileExists => "File for output already exists",
AoError::BadFormat => "Requested stream format is not supported",
AoError::Unknown => "Unknown error"
}
}
}
impl fmt::Display for AoError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
f.write_str(self.description())
}
}
pub trait Sample : Copy {
fn channels(&self) -> usize;
}
macro_rules! sample_impl(
($t:ty) => (
#[inline]
impl Sample for $t {
fn channels(&self) -> usize { 1 }
}
);
(channels $w:expr) => (
#[inline]
impl<S: Sample> Sample for [S; $w] {
fn channels(&self) -> usize { $w }
}
)
);
sample_impl!(i8);
sample_impl!(i16);
sample_impl!(i32);
sample_impl!(channels 2);
pub struct SampleFormat<T, S> {
pub sample_rate: usize,
pub channels: usize,
pub byte_order: Endianness,
pub matrix: Option<S>,
marker: PhantomData<T>
}
impl<T: Sample, S: AsRef<str>> SampleFormat<T, S> {
pub fn new(sample_rate: usize, channels: usize, byte_order: Endianness,
matrix: Option<S>) -> SampleFormat<T, S> {
SampleFormat {
sample_rate: sample_rate,
channels: channels,
byte_order: byte_order,
matrix: matrix,
marker: PhantomData
}
}
fn with_native<F, U>(&self, f: F) -> U
where F: FnOnce(*const ffi::ao_sample_format) -> U {
let sample_size = size_of::<T>() * 8;
let matrix: Option<CString> = match self.matrix {
None => None,
Some(ref s) => CString::new(s.as_ref()).ok()
};
let native = ffi::ao_sample_format {
bits: sample_size as c_int,
rate: self.sample_rate as c_int,
channels: self.channels as c_int,
byte_format: self.byte_order as c_int,
matrix: matrix.map_or(ptr::null(), |cs| cs.as_ptr())
};
f(&native as *const _)
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum Endianness {
Little = ffi::AO_FMT_LITTLE as isize,
Big = ffi::AO_FMT_BIG as isize,
Native = ffi::AO_FMT_NATIVE as isize,
}
pub struct AO;
static mut FFI_INITIALIZED: AtomicBool = ATOMIC_BOOL_INIT;
impl AO {
pub fn init() -> AO {
unsafe {
if FFI_INITIALIZED.compare_and_swap(false, true, Ordering::AcqRel) {
panic!("Attempted multiple instantiation of ao::AO")
}
ffi::ao_initialize();
};
AO
}
pub fn get_driver<'a>(&'a self, name: &str) -> Option<Driver<'a>> {
let id = if name != "" {
let cs = match CString::new(name) {
Ok(s) => s,
Err(_) => return None
};
unsafe {
ffi::ao_driver_id(cs.as_ptr())
}
} else {
unsafe {
ffi::ao_default_driver_id()
}
};
if id == -1 {
None
} else {
Some(Driver {
id: id,
marker: PhantomData
})
}
}
}
impl Drop for AO {
fn drop(&mut self) {
unsafe {
ffi::ao_shutdown();
FFI_INITIALIZED.store(false, Ordering::Release);
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum DriverType {
Live,
File
}
impl DriverType {
fn from_c_int(n: c_int) -> DriverType {
match n {
ffi::AO_TYPE_FILE => DriverType::File,
ffi::AO_TYPE_LIVE => DriverType::Live,
n => panic!("Invalid AO_TYPE_*: {}", n)
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct DriverInfo<'a> {
pub flavor: DriverType,
pub name: &'a str,
pub short_name: &'a str,
pub comment: Option<&'a str>,
}
pub struct Driver<'a> {
id: c_int,
marker: PhantomData<&'a ()>
}
impl<'a> Driver<'a> {
pub fn get_info(& self) -> Option<DriverInfo<'a>> {
let id = self.id;
unsafe fn sstr<'z>(s: *const c_char) -> &'z str {
str::from_utf8(CStr::from_ptr(s).to_bytes()).unwrap()
}
unsafe {
let info = ffi::ao_driver_info(id);
if info.is_null() {
None
} else {
let ref info = *info;
Some(DriverInfo {
name: sstr(info.name),
short_name: sstr(info.short_name),
comment: if info.comment.is_null() {
None
} else {
Some(sstr(info.comment))
},
flavor: DriverType::from_c_int(info.flavor),
})
}
}
}
pub fn open_live<T: Sample, S: AsRef<str>>(&self,
format: &SampleFormat<T, S>) -> AoResult<Device<'a, T>> {
let handle = format.with_native(|f| unsafe {
ffi::ao_open_live(self.id, f, ptr::null())
});
Device::<'a, T>::init(handle)
}
pub fn open_file<T: Sample, S: AsRef<str>>(&self,
format: &SampleFormat<T, S>, file: &Path,
overwrite: bool) -> AoResult<Device<'a, T>> {
let c_path = match file.to_str() {
Some(s) => match CString::new(s) {
Ok(s) => s,
Err(_) => return Err(AoError::OpenFile)
},
None => return Err(AoError::OpenFile)
};
let handle = format.with_native(|f| {
unsafe {
ffi::ao_open_file(self.id, c_path.as_ptr(), overwrite as c_int, f, ptr::null())
}
});
Device::<'a, T>::init(handle)
}
}
pub struct Device<'a, S> {
id: *mut ffi::ao_device,
m0: PhantomData<&'a ()>,
m1: PhantomData<S>
}
impl<'a, S: Sample> Device<'a, S> {
fn init(handle: *mut ffi::ao_device) -> AoResult<Device<'a, S>> {
if handle.is_null() {
Err(AoError::from_errno())
} else {
Ok(Device {
id: handle,
m0: PhantomData,
m1: PhantomData
})
}
}
pub fn play(&self, samples: &[S]) {
unsafe {
let len = samples.len() * size_of::<S>();
ffi::ao_play(self.id, samples.as_ptr() as *const i8, len as u32);
}
}
}
impl<'a, S> Drop for Device<'a, S> {
fn drop(&mut self) {
unsafe {
ffi::ao_close(self.id);
}
}
}
#[test]
#[should_panic]
#[allow(unused_variables)]
fn test_multiple_instantiation() {
let lib = AO::init();
let lib2 = AO::init();
}