Skip to content

Instantly share code, notes, and snippets.

@tomaka
Last active August 12, 2016 14:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomaka/5b0b0c77d826ec9ec9ac7f257c9b738c to your computer and use it in GitHub Desktop.
Save tomaka/5b0b0c77d826ec9ec9ac7f257c9b738c to your computer and use it in GitHub Desktop.
extern crate coreaudio;
extern crate libc;
use std::sync::mpsc::{channel, Sender, Receiver};
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::cell::RefCell;
use std::mem;
use std::cmp;
use std::marker::PhantomData;
use futures::Poll;
use futures::Task;
use futures::TaskHandle;
use futures::stream::Stream;
use CreationError;
use Format;
use FormatsEnumerationError;
use SampleFormat;
use SamplesRate;
use ChannelPosition;
use UnknownTypeBuffer;
mod enumerate;
pub use self::enumerate::{EndpointsIterator,
SupportedFormatsIterator,
get_default_endpoint};
use self::coreaudio::audio_unit::{AudioUnit, IOType};
use self::coreaudio::audio_unit::render_callback::{self, data};
#[derive(Clone, PartialEq, Eq)]
pub struct Endpoint;
impl Endpoint {
pub fn get_supported_formats_list(&self)
-> Result<SupportedFormatsIterator, FormatsEnumerationError>
{
Ok(vec!(Format {
channels: vec![ChannelPosition::FrontLeft, ChannelPosition::FrontRight],
samples_rate: SamplesRate(44100),
data_type: SampleFormat::F32
}).into_iter())
}
pub fn get_name(&self) -> String {
"Default AudioUnit Endpoint".to_string()
}
}
pub struct EventLoop;
impl EventLoop {
#[inline]
pub fn new() -> EventLoop { EventLoop }
#[inline]
pub fn run(&self) { loop {} }
}
pub struct Buffer<T> {
args: render_callback::Args<data::NonInterleaved<f32>>,
buffer: Vec<f32>,
}
impl<T> Buffer<T> {
#[inline]
pub fn get_buffer(&mut self) -> &mut [T] {
&mut self.samples[..]
}
#[inline]
pub fn len(&self) -> usize {
self.samples.len()
}
#[inline]
pub fn finish(self) {
// TODO: At the moment this assumes the Vec<T> is a Vec<f32>.
// Need to add T: Sample and use Sample::to_vec_f32.
let num_channels = args.data.channels().count();
for (i, frame) in self.buffer.chunks(num_channels).enumerate() {
for (channel, sample) in self.args.data.channels_mut().zip(frame.iter()) {
channel[i] = *sample;
}
}
}
}
type NumChannels = usize;
type NumFrames = usize;
pub struct Voice;
#[allow(dead_code)] // the audio_unit will be dropped if we don't hold it.
pub struct SamplesStream {
audio_unit: AudioUnit,
inner: Arc<Mutex<SamplesStreamInner>>,
}
// TODO: shouldn't be necessary
unsafe impl Sync for SamplesStream {}
unsafe impl Send for SamplesStream {}
struct SamplesStreamInner {
scheduled_task: Option<TaskHandle>,
current_callback: Option<render_callback::Args<data::NonInterleaved<f32>>>,
}
impl Stream for SamplesStream {
type Item = UnknownTypeBuffer;
type Error = ();
fn poll(&mut self, _: &mut Task) -> Poll<Option<Self::Item>, Self::Error> {
let mut inner = self.inner.lock().unwrap();
// There are two possibilites: either we're answering a callback of coreaudio and we return
// a buffer, or we're not answering a callback and we return that we're not ready.
let current_callback = match inner.current_callback.take() {
Some(c) => c,
None => return Poll::NotReady
};
let buffer_len = args.num_frames * args.data.channels().count();
let buffer = Buffer {
args: current_callback,
buffer: vec![0; buffer_len],
};
Poll::Ok(UnknownTypeBuffer::F32(::Buffer { target: Some(buffer) }))
}
fn schedule(&mut self, task: &mut Task) {
*self.inner.lock().unwrap().scheduled_task = Some(task.handle().clone());
}
}
impl Voice {
pub fn new(_: &Endpoint, _: &Format, _: &EventLoop)
-> Result<(Voice, SamplesStream), CreationError>
{
let inner = Arc::new(SamplesStreamInner {
scheduled_task: Mutex::new(None),
current_callback: Mutex::new(None),
});
let audio_unit = match AudioUnit::new(IOType::HalOutput) {
Ok(au) => au,
Err(_) => return Err(CreationError::DeviceNotAvailable) // TODO: correct error
};
// TODO: iOS uses integer and fixed-point data
{
let inner = inner.clone();
let ret = audio_unit.set_render_callback(move |args| {
// This callback is entered whenever the coreaudio engine needs to be fed data.
// Store the callback argument in the `SamplesStreamInner` and return the task
// that we're supposed to notify.
let scheduled = {
let mut inner = inner.lock().unwrap();
assert!(inner.current_callback.is_none());
*inner.current_callback = Some(args);
inner.scheduled_task.take()
};
// It is important that `inner` is unlocked here.
if let Some(scheduled) = scheduled {
// Calling `notify()` should immediately call `poll()` on the `SamplesStream`,
// which will use the data we stored in `current_callback`.
scheduled.notify();
}
// TODO: what should happen if the callback wasn't processed? in other word, what
// if the user didn't register any handler or did a stupid thing in the
// handler (like mem::forgetting the buffer)?
Ok(())
});
match ret {
Ok(_) => (),
Err(_) => return Err(CreationError::DeviceNotAvailable) // TODO: correct error
};
}
match audio_unit.start() {
Ok(_) => (),
Err(_) => return Err(CreationError::DeviceNotAvailable) // TODO: correct error
};
let samples_stream = SamplesStream {
audio_unit: audio_unit,
inner: inner,
};
Ok((Voice, samples_stream))
}
#[inline]
pub fn play(&mut self) {
// implicitly playing
}
#[inline]
pub fn pause(&mut self) {
unimplemented!()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment