Skip to content

Commit

Permalink
Change Element datatype to be u32.
Browse files Browse the repository at this point in the history
As i understand it, an audio unit can have several inputs and several
outputs, and an 'element' is just an index of one of those.
(https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html).

Therefore, it's should be possible, for example, to have several render
callbacks for a single audio unit. An example would be a crossfade unit
with 2 inputs: it'll have 2 elements in its input scope and 1 in output
scope, and it'll require either two render callbacks (one for each input),
or two upstream audio units.

This changes Element to be just a number and adds explicit element
parameter to all the places where it hasn't been present before
(i.e. setting callbacks and input/output stream formats).

I also had to change handling of render callbacks a bit, since there can
now be more than one of them for a single audio unit.

This relates to the issue RustAudio#60 and PR RustAudio#47.
  • Loading branch information
dmski committed May 12, 2019
1 parent e175d64 commit 28c7c72
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 40 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ homepage = "https://github.com/RustAudio/coreaudio-rs"
[lib]
name = "coreaudio"

[[bin]]
name = "example-sine"
path = "examples/sine.rs"

[features]
default = ["audio_toolbox", "audio_unit", "core_audio", "open_al", "core_midi"]
audio_toolbox = ["coreaudio-sys/audio_toolbox"]
Expand Down
14 changes: 7 additions & 7 deletions examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,26 @@ fn run() -> Result<(), coreaudio::Error> {
.map(|phase| (phase * PI * 2.0).sin() as f32 * 0.15);

// Construct an Output audio unit that delivers audio to the default output device.
let mut audio_unit = try!(AudioUnit::new(IOType::DefaultOutput));
let mut audio_unit = AudioUnit::new(IOType::DefaultOutput)?;

let stream_format = try!(audio_unit.output_stream_format());
let stream_format = audio_unit.output_stream_format(0)?;
println!("{:#?}", &stream_format);

// For this example, our sine wave expects `f32` data.
assert!(SampleFormat::F32 == stream_format.sample_format);
assert_eq!(SampleFormat::F32, stream_format.sample_format);

type Args = render_callback::Args<data::NonInterleaved<f32>>;
try!(audio_unit.set_render_callback(move |args| {
audio_unit.set_render_callback(move |args| {
let Args { num_frames, mut data, .. } = args;
for i in 0..num_frames {
let sample = samples.next().unwrap();
for channel in data.channels_mut() {
for mut channel in data.channels_mut() {
channel[i] = sample;
}
}
Ok(())
}));
try!(audio_unit.start());
}, 0)?;
audio_unit.start()?;

std::thread::sleep(std::time::Duration::from_millis(3000));

Expand Down
47 changes: 25 additions & 22 deletions src/audio_unit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub use self::types::{
MixerType,
MusicDeviceType,
};
use std::collections::HashMap;


pub mod audio_format;
Expand All @@ -62,22 +63,17 @@ pub enum Scope {
LayerItem = 7,
}

/// Represents the **Input** and **Output** **Element**s.
///
/// These are used when specifying which **Element** we're setting the properties of.
#[derive(Copy, Clone, Debug)]
pub enum Element {
Output = 0,
Input = 1,
}
/// These are used when specifying which **Element** (bus) we're setting the properties of.
/// [The anatomy of an AudioUnit](https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html#//apple_ref/doc/uid/TP40003278-CH12-SW11)
type Element = u32;


/// A rust representation of the sys::AudioUnit, including a pointer to the current rendering callback.
///
/// Find the original Audio Unit Programming Guide [here](https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html).
pub struct AudioUnit {
instance: sys::AudioUnit,
maybe_render_callback: Option<*mut render_callback::InputProcFnWrapper>,
registered_render_callbacks: HashMap<u32, *mut render_callback::InputProcFnWrapper>,
maybe_input_callback: Option<InputCallback>,
}

Expand Down Expand Up @@ -165,7 +161,7 @@ impl AudioUnit {
try_os_status!(sys::AudioUnitInitialize(instance));
Ok(AudioUnit {
instance: instance,
maybe_render_callback: None,
registered_render_callbacks: HashMap::new(),
maybe_input_callback: None,
})
}
Expand Down Expand Up @@ -229,15 +225,15 @@ impl AudioUnit {
/// Set the **AudioUnit**'s sample rate.
///
/// **Available** in iOS 2.0 and later.
pub fn set_sample_rate(&mut self, sample_rate: f64) -> Result<(), Error> {
pub fn set_sample_rate(&mut self, element: u32, sample_rate: f64) -> Result<(), Error> {
let id = sys::kAudioUnitProperty_SampleRate;
self.set_property(id, Scope::Input, Element::Output, Some(&sample_rate))
self.set_property(id, Scope::Input, element, Some(&sample_rate))
}

/// Get the **AudioUnit**'s sample rate.
pub fn sample_rate(&self) -> Result<f64, Error> {
pub fn sample_rate(&self, element: Element) -> Result<f64, Error> {
let id = sys::kAudioUnitProperty_SampleRate;
self.get_property(id, Scope::Input, Element::Output)
self.get_property(id, Scope::Input, element)
}

/// Sets the current **StreamFormat** for the AudioUnit.
Expand All @@ -258,27 +254,28 @@ impl AudioUnit {
&mut self,
stream_format: StreamFormat,
scope: Scope,
element: Element,
) -> Result<(), Error> {
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = stream_format.to_asbd();
self.set_property(id, scope, Element::Output, Some(&asbd))
self.set_property(id, scope, element, Some(&asbd))
}

/// Return the current Stream Format for the AudioUnit.
pub fn stream_format(&self, scope: Scope) -> Result<StreamFormat, Error> {
pub fn stream_format(&self, scope: Scope, element: Element) -> Result<StreamFormat, Error> {
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = try!(self.get_property(id, scope, Element::Output));
let asbd = self.get_property(id, scope, element)?;
StreamFormat::from_asbd(asbd)
}

/// Return the current output Stream Format for the AudioUnit.
pub fn output_stream_format(&self) -> Result<StreamFormat, Error> {
self.stream_format(Scope::Output)
pub fn output_stream_format(&self, element: Element) -> Result<StreamFormat, Error> {
self.stream_format(Scope::Output, element)
}

/// Return the current input Stream Format for the AudioUnit.
pub fn input_stream_format(&self) -> Result<StreamFormat, Error> {
self.stream_format(Scope::Input)
pub fn input_stream_format(&self, element: Element) -> Result<StreamFormat, Error> {
self.stream_format(Scope::Input, element)
}
}

Expand All @@ -298,7 +295,13 @@ impl Drop for AudioUnit {
self.stop().ok();
error::Error::from_os_status(sys::AudioUnitUninitialize(self.instance)).ok();

self.free_render_callback();
let elements: Vec<u32> = self.registered_render_callbacks
.iter()
.map(|(k, _v)| { *k })
.collect();
for e in elements {
self.free_render_callback(e);
}
self.free_input_callback();
}
}
Expand Down
23 changes: 12 additions & 11 deletions src/audio_unit/render_callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,15 +390,15 @@ pub mod action_flags {

impl AudioUnit {
/// Pass a render callback (aka "Input Procedure") to the **AudioUnit**.
pub fn set_render_callback<F, D>(&mut self, mut f: F) -> Result<(), Error>
pub fn set_render_callback<F, D>(&mut self, mut f: F, element: Element) -> Result<(), Error>
where
F: FnMut(Args<D>) -> Result<(), ()> + 'static,
D: Data,
{
// First, we'll retrieve the stream format so that we can ensure that the given callback
// format matches the audio unit's format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = try!(self.get_property(id, Scope::Output, Element::Output));
let asbd = self.get_property(id, Scope::Output, element)?;
let stream_format = super::StreamFormat::from_asbd(asbd)?;

// If the stream format does not match, return an error indicating this.
Expand Down Expand Up @@ -453,12 +453,12 @@ impl AudioUnit {
self.set_property(
sys::kAudioUnitProperty_SetRenderCallback,
Scope::Input,
Element::Output,
element,
Some(&render_callback),
)?;

self.free_render_callback();
self.maybe_render_callback = Some(input_proc_fn_wrapper_ptr as *mut InputProcFnWrapper);
self.free_render_callback(element);
self.registered_render_callbacks.insert(element, input_proc_fn_wrapper_ptr as *mut InputProcFnWrapper);
Ok(())
}

Expand All @@ -471,7 +471,7 @@ impl AudioUnit {
// First, we'll retrieve the stream format so that we can ensure that the given callback
// format matches the audio unit's format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = self.get_property(id, Scope::Input, Element::Input)?;
let asbd = self.get_property(id, Scope::Input, 1)?;
let stream_format = super::StreamFormat::from_asbd(asbd)?;

// If the stream format does not match, return an error indicating this.
Expand All @@ -483,7 +483,7 @@ impl AudioUnit {
//
// First, get the current buffer size for pre-allocating the `AudioBuffer`s.
let id = sys::kAudioDevicePropertyBufferFrameSize;
let mut buffer_frame_size: u32 = self.get_property(id, Scope::Global, Element::Output)?;
let mut buffer_frame_size: u32 = self.get_property(id, Scope::Global, 0)?; // Always 0 bus for Scope::Global
let mut data: Vec<u8> = vec![];
let sample_bytes = stream_format.sample_format.size_in_bytes();
let n_channels = stream_format.channels_per_frame;
Expand Down Expand Up @@ -525,7 +525,7 @@ impl AudioUnit {
unsafe {
// Retrieve the up-to-date stream format.
let id = sys::kAudioUnitProperty_StreamFormat;
let asbd = match super::get_property(audio_unit, id, Scope::Input, Element::Output) {
let asbd = match super::get_property(audio_unit, id, Scope::Input, in_bus_number) {
Err(err) => return err.to_os_status(),
Ok(asbd) => asbd,
};
Expand Down Expand Up @@ -607,7 +607,7 @@ impl AudioUnit {
self.set_property(
sys::kAudioOutputUnitProperty_SetInputCallback,
Scope::Global,
Element::Output,
0,
Some(&render_callback),
)?;

Expand All @@ -622,8 +622,9 @@ impl AudioUnit {

/// Retrieves ownership over the render callback and returns it where it can be re-used or
/// safely dropped.
pub fn free_render_callback(&mut self) -> Option<Box<InputProcFnWrapper>> {
if let Some(callback) = self.maybe_render_callback.take() {
pub fn free_render_callback(&mut self, element: Element) -> Option<Box<InputProcFnWrapper>> {
let maybe_callback = self.registered_render_callbacks.remove(&element);
if let Some(callback) = maybe_callback {
// Here, we transfer ownership of the callback back to the current scope so that it
// is dropped and cleaned up. Without this line, we would leak the Boxed callback.
let callback: Box<InputProcFnWrapper> = unsafe { Box::from_raw(callback) };
Expand Down

0 comments on commit 28c7c72

Please sign in to comment.