#[macro_use]
extern crate simple_error;
use std::error::Error;
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::{c_char, c_uchar, c_ushort, c_int, c_ulong, c_void};
use std::path::Path;
#[repr(C)]
struct WM_Info {
copyright: *const c_char,
current_sample: c_ulong,
approx_total_samples: c_ulong,
total_midi_time: c_ulong,
mixer_options: c_ushort,
}
extern "C" {
fn WildMidi_Init(cfg: *const c_char, rate: c_ushort, flags: c_ushort) -> c_int;
fn WildMidi_Open(path: *const c_char) -> *const c_void;
fn WildMidi_OpenBuffer(data: *const c_uchar, size: c_ulong) -> *const c_void;
fn WildMidi_MasterVolume(volume: c_uchar) -> c_int;
fn WildMidi_Shutdown();
fn WildMidi_Close(ptr: *const c_void) -> c_int;
fn WildMidi_FastSeek(ptr: *const c_void, pos: c_ushort) -> c_int;
fn WildMidi_GetOutput(ptr: *const c_void, buf: *mut c_uchar, len: c_ulong) -> c_int;
fn WildMidi_GetInfo(ptr: *const c_void) -> *const WM_Info;
}
pub struct Player;
impl Player {
fn locate_cfg() -> Option<&'static str> {
let paths = vec![
"/etc/wildmidi/wildmidi.cfg",
"/etc/wildmidi.cfg"
];
for path in paths.iter() {
if Path::new(path).exists() {
return Some(path);
}
}
None
}
pub fn new(rate: u16) -> Result<Player, Box<Error>> {
let cfg = match Player::locate_cfg() {
Some(cfg) => cfg,
None => bail!("No valid configuration file found"),
};
Player::with_cfg(cfg, rate)
}
pub fn with_cfg(cfg: &str, rate: u16) -> Result<Player, Box<Error>> {
let cfg = CString::new(cfg)?;
unsafe {
if WildMidi_Init(cfg.as_ptr(), rate, 0) != 0 {
bail!("Couldn't initialize WildMidi.");
}
}
Ok(Player { })
}
pub fn volume(&mut self, volume: u8) -> Result<(), Box<Error>> {
unsafe {
if WildMidi_MasterVolume(volume) != 0 {
bail!("Couldn't set volume.");
}
}
Ok(())
}
pub fn load(&self, data: &[u8]) -> Result<Midi, Box<Error>> {
unsafe {
let len = data.len() as c_ulong;
let ptr = WildMidi_OpenBuffer(data.as_ptr(), len);
if !ptr.is_null() {
return Ok(Midi::new(ptr));
}
}
bail!("Failed to open Midi file.")
}
pub fn load_file(&self, path: &str) -> Result<Midi, Box<Error>> {
if !Path::new(path).exists() {
bail!("File does not exist");
}
let path = CString::new(path)?;
unsafe {
let ptr = WildMidi_Open(path.as_ptr());
if !ptr.is_null() {
return Ok(Midi::new(ptr));
}
}
bail!("Failed to open Midi file.")
}
}
impl Drop for Player {
fn drop(&mut self) {
unsafe {
WildMidi_Shutdown();
}
}
}
pub struct Midi {
ptr: *const c_void,
}
impl Midi {
fn new(ptr: *const c_void) -> Midi {
Midi { ptr }
}
pub fn play(&mut self, len: usize) -> Vec<u8> {
let mut vec = vec![0;len];
unsafe {
let buf = vec.as_mut_ptr();
let handle = self.ptr;
let read = WildMidi_GetOutput(handle, buf, len as c_ulong) as usize;
if read < len {
vec.resize(read, 0);
}
}
vec
}
pub fn seek(&mut self, pos: u32) {
unsafe {
WildMidi_FastSeek(self.ptr, pos as c_ushort);
}
}
pub fn copyright(&self) -> Option<String> {
unsafe {
let ptr = WildMidi_GetInfo(self.ptr);
if (*ptr).copyright.is_null() {
None
} else {
if let Ok(str) = CStr::from_ptr((*ptr).copyright).to_str() {
Some(String::from(str))
} else {
None
}
}
}
}
pub fn current_sample(&self) -> usize {
unsafe {
let ptr = WildMidi_GetInfo(self.ptr);
(*ptr).current_sample as usize
}
}
pub fn total_samples(&self) -> usize {
unsafe {
let ptr = WildMidi_GetInfo(self.ptr);
(*ptr).approx_total_samples as usize
}
}
pub fn total_time(&self) -> usize {
unsafe {
let ptr = WildMidi_GetInfo(self.ptr);
(*ptr).total_midi_time as usize
}
}
}
impl Drop for Midi {
fn drop(&mut self) {
unsafe {
WildMidi_Close(self.ptr);
}
}
}
#[cfg(test)]
mod player_tests {
use ::*;
#[test]
fn create() {
if let Err(e) = Player::new(44100) {
panic!("{}", e);
}
}
#[test]
fn invalid_rate() {
if let Ok(_) = Player::new(0) {
panic!("Allowed player to be created with invalid rate.");
}
}
}