cpal 0.0.21

Cross-platform audio playing library in pure Rust.
extern crate libc;
extern crate winapi;
extern crate ole32;

use std::{slice, mem, ptr};
use std::marker::PhantomData;

// TODO: determine if should be NoSend or not
pub struct Voice {
    audio_client: *mut winapi::IAudioClient,
    render_client: *mut winapi::IAudioRenderClient,
    max_frames_in_buffer: winapi::UINT32,
    num_channels: winapi::WORD,
    bytes_per_frame: winapi::WORD,
    samples_per_second: winapi::DWORD,
    bits_per_sample: winapi::WORD,
    playing: bool,
}

pub struct Buffer<'a, T: 'a> {
    render_client: *mut winapi::IAudioRenderClient,
    buffer_data: *mut T,
    buffer_len: usize,
    frames: winapi::UINT32,
    marker: PhantomData<&'a mut T>,
}

impl Voice {
    pub fn new() -> Voice {
        init().unwrap()
    }

    pub fn get_channels(&self) -> ::ChannelsCount {
        self.num_channels as ::ChannelsCount
    }

    pub fn get_samples_rate(&self) -> ::SamplesRate {
        ::SamplesRate(self.samples_per_second as u32)
    }

    pub fn get_samples_format(&self) -> ::SampleFormat {
        match self.bits_per_sample {
            16 => ::SampleFormat::U16,
            _ => unimplemented!(),
        }
    }

    pub fn append_data<'a, T>(&'a mut self, max_elements: usize) -> Buffer<'a, T> {
        unsafe {
            loop {
                // 
                let frames_available = {
                    let mut padding = mem::uninitialized();
                    let hresult = (*self.audio_client).GetCurrentPadding(&mut padding);
                    check_result(hresult).unwrap();
                    self.max_frames_in_buffer - padding
                };

                if frames_available == 0 {
                    // TODO: 
                    ::std::thread::sleep_ms(1);
                    continue;
                }

                let frames_available = ::std::cmp::min(frames_available,
                                                       max_elements as u32 * mem::size_of::<T>() as u32 /
                                                       self.bytes_per_frame as u32);
                assert!(frames_available != 0);

                // loading buffer
                let (buffer_data, buffer_len) = {
                    let mut buffer: *mut winapi::BYTE = mem::uninitialized();
                    let hresult = (*self.render_client).GetBuffer(frames_available,
                                    &mut buffer as *mut *mut libc::c_uchar);
                    check_result(hresult).unwrap();
                    assert!(!buffer.is_null());

                    (buffer as *mut T,
                     frames_available as usize * self.bytes_per_frame as usize
                          / mem::size_of::<T>())
                };

                let buffer = Buffer {
                    render_client: self.render_client,
                    buffer_data: buffer_data,
                    buffer_len: buffer_len,
                    frames: frames_available,
                    marker: PhantomData,
                };

                return buffer;
            }
        }
    }

    pub fn play(&mut self) {
        if !self.playing {
            unsafe {
                let hresult = (*self.audio_client).Start();
                check_result(hresult).unwrap();
            }
        }

        self.playing = true;
    }

    pub fn pause(&mut self) {
        if self.playing {
            unsafe {
                let hresult = (*self.audio_client).Stop();
                check_result(hresult).unwrap();
            }
        }

        self.playing = false;
    }
}

unsafe impl Send for Voice {}
unsafe impl Sync for Voice {}

impl Drop for Voice {
    fn drop(&mut self) {
        unsafe {
            (*self.render_client).Release();
            (*self.audio_client).Release();
        }
    }
}

impl<'a, T> Buffer<'a, T> {
    pub fn get_buffer<'b>(&'b mut self) -> &'b mut [T] {
        unsafe {
            slice::from_raw_parts_mut(self.buffer_data, self.buffer_len)
        }
    }

    pub fn finish(self) {
        // releasing buffer
        unsafe {
            let hresult = (*self.render_client).ReleaseBuffer(self.frames as u32, 0);
            check_result(hresult).unwrap();
        };
    }
}

fn init() -> Result<Voice, String> {
    // FIXME: release everything
    unsafe {
        try!(check_result(ole32::CoInitializeEx(::std::ptr::null_mut(), 0)));

        // building the devices enumerator object
        let enumerator = {
            let mut enumerator: *mut winapi::IMMDeviceEnumerator = ::std::mem::uninitialized();
            
            let hresult = ole32::CoCreateInstance(&winapi::CLSID_MMDeviceEnumerator,
                                                   ptr::null_mut(), winapi::CLSCTX_ALL,
                                                   &winapi::IID_IMMDeviceEnumerator,
                                                   mem::transmute(&mut enumerator));

            try!(check_result(hresult));
            &mut *enumerator
        };

        // getting the default end-point
        let device = {
            let mut device: *mut winapi::IMMDevice = mem::uninitialized();
            let hresult = enumerator.GetDefaultAudioEndpoint(winapi::EDataFlow::eRender, winapi::ERole::eConsole,
                            mem::transmute(&mut device));
            try!(check_result(hresult));
            &mut *device
        };

        // activating in order to get a `IAudioClient`
        let audio_client: &mut winapi::IAudioClient = {
            let mut audio_client: *mut winapi::IAudioClient = mem::uninitialized();
            let hresult = device.Activate(&winapi::IID_IAudioClient, winapi::CLSCTX_ALL,
                            ptr::null_mut(), mem::transmute(&mut audio_client));
            try!(check_result(hresult));
            &mut *audio_client
        };

        // computing the format and initializing the device
        let format = {
            let format_attempt = winapi::WAVEFORMATEX {
                wFormatTag: 1,      // WAVE_FORMAT_PCM ; TODO: replace by constant
                nChannels: 2,
                nSamplesPerSec: 44100,
                nAvgBytesPerSec: 2 * 44100 * 2,
                nBlockAlign: (2 * 16) / 8,
                wBitsPerSample: 16,
                cbSize: 0,
            };

            let mut format_ptr: *mut winapi::WAVEFORMATEX = mem::uninitialized();
            let hresult = audio_client.IsFormatSupported(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
                            &format_attempt, &mut format_ptr);
            try!(check_result(hresult));

            let format = if format_ptr.is_null() {
                &format_attempt
            } else {
                &*format_ptr
            };

            let format_copy = ptr::read(format);

            let hresult = audio_client.Initialize(winapi::AUDCLNT_SHAREMODE::AUDCLNT_SHAREMODE_SHARED,
                            0, 10000000, 0, format, ptr::null());

            if !format_ptr.is_null() {
                ole32::CoTaskMemFree(format_ptr as *mut libc::c_void);
            }

            try!(check_result(hresult));

            format_copy
        };

        // 
        let max_frames_in_buffer = {
            let mut max_frames_in_buffer = mem::uninitialized();
            let hresult = audio_client.GetBufferSize(&mut max_frames_in_buffer);
            try!(check_result(hresult));
            max_frames_in_buffer
        };

        // 
        let render_client = {
            let mut render_client: *mut winapi::IAudioRenderClient = mem::uninitialized();
            let hresult = audio_client.GetService(&winapi::IID_IAudioRenderClient,
                            mem::transmute(&mut render_client));
            try!(check_result(hresult));
            &mut *render_client
        };

        Ok(Voice {
            audio_client: audio_client,
            render_client: render_client,
            max_frames_in_buffer: max_frames_in_buffer,
            num_channels: format.nChannels,
            bytes_per_frame: format.nBlockAlign,
            samples_per_second: format.nSamplesPerSec,
            bits_per_sample: format.wBitsPerSample,
            playing: false,
        })
    }
}

fn check_result(result: winapi::HRESULT) -> Result<(), String> {
    if result < 0 {
        return Err(format!("Error in winapi call"));        // TODO: 
    }

    Ok(())
}