Skip to content

Commit

Permalink
Refactor (#58)
Browse files Browse the repository at this point in the history
Refactor

ActiveClient -> AsyncClient

Decouples Client and AsyncClient dependency.
Now, AsyncClient is a layer above Client, while Client has no dep on AsyncClient.
  • Loading branch information
wmedrano committed Feb 10, 2017
1 parent 36f394d commit 220a7ba
Show file tree
Hide file tree
Showing 19 changed files with 523 additions and 417 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ $ cargo kcov
If the tests are failing, a possible gotcha may be timing issues.

1. Rust runs tests in parallel, it may be possible that the JACK server is not keeping up. Set the environment variable `RUST_TEST_THREADS` to 1.
2. Increase the value used by `sleep_on_test` in `client_impls.rs`.
2. Increase the value used by `sleep_on_test` in `client/common.rs`.

Another case is that libjack may be broken on your setup. Try switching between
libjack and libjack2 (they have the same API and libjack2 isn't necessarily
Expand Down
7 changes: 3 additions & 4 deletions examples/playback_capture.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
//! Takes 2 audio inputs and outputs them to 2 audio outputs.
extern crate jack;
use jack::prelude as j;
use jack::traits::*;
use std::io;

fn main() {
// Create client
let (client, _status) = j::Client::open("rust_jack_simple", j::client_options::NO_START_SERVER)
let (client, _status) = j::Client::new("rust_jack_simple", j::client_options::NO_START_SERVER)
.unwrap();

// Register ports. They will be used in a callback that will be
Expand All @@ -15,7 +14,7 @@ fn main() {
let in_b = client.register_port("rust_in_r", j::AudioInSpec::default()).unwrap();
let mut out_a = client.register_port("rust_out_l", j::AudioOutSpec::default()).unwrap();
let mut out_b = client.register_port("rust_out_r", j::AudioOutSpec::default()).unwrap();
let process_callback = move |_: &j::WeakClient, ps: &j::ProcessScope| -> jack::JackControl {
let process_callback = move |_: &j::Client, ps: &j::ProcessScope| -> j::JackControl {
let mut out_a_p = j::AudioOutPort::new(&mut out_a, ps);
let mut out_b_p = j::AudioOutPort::new(&mut out_b, ps);
let in_a_p = j::AudioInPort::new(&in_a, ps);
Expand All @@ -26,7 +25,7 @@ fn main() {
};
let process = j::ProcessHandler::new(process_callback);
// Activate the client, which starts the processing.
let active_client = client.activate(process).unwrap();
let active_client = j::AsyncClient::new(client, process).unwrap();

// Wait for user input to quit
println!("Press enter/return to quit...");
Expand Down
10 changes: 5 additions & 5 deletions examples/show_midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
//! Note On and Off event, once every cycle, on the output port.
extern crate jack;
use std::io;
use jack::prelude::{Client, JackClient, JackControl, MidiInPort, MidiInSpec, MidiOutPort,
MidiOutSpec, ProcessHandler, ProcessScope, RawMidi, WeakClient, client_options};
use jack::prelude::{AsyncClient, Client, JackControl, MidiInPort, MidiInSpec, MidiOutPort,
MidiOutSpec, ProcessHandler, ProcessScope, RawMidi, client_options};

fn main() {
// open client
let (client, _status) = Client::open("rust_jack_show_midi", client_options::NO_START_SERVER)
let (client, _status) = Client::new("rust_jack_show_midi", client_options::NO_START_SERVER)
.unwrap();

// process logic
let mut maker = client.register_port("rust_midi_maker", MidiOutSpec::default()).unwrap();
let shower = client.register_port("rust_midi_shower", MidiInSpec::default()).unwrap();
let cback = move |_: &WeakClient, ps: &ProcessScope| -> JackControl {
let cback = move |_: &Client, ps: &ProcessScope| -> JackControl {
let show_p = MidiInPort::new(&shower, ps);
for e in show_p.iter() {
println!("{:?}", e);
Expand All @@ -37,7 +37,7 @@ fn main() {

// activate
let process = ProcessHandler::new(cback);
let active_client = client.activate(process).unwrap();
let active_client = AsyncClient::new(client, process).unwrap();

// wait
println!("Press any key to quit");
Expand Down
11 changes: 5 additions & 6 deletions examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ extern crate jack;
use std::io;
use std::str::FromStr;
use std::sync::mpsc::channel;
use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackClient, JackControl, ProcessHandler,
ProcessScope, WeakClient, client_options};
use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackControl, ProcessHandler, ProcessScope,
AsyncClient, client_options};


/// Attempt to read a frequency from standard in. Will block until there is user input. `None` is
Expand All @@ -20,8 +20,7 @@ fn read_freq() -> Option<f64> {

fn main() {
// 1. open a client
let (client, _status) = Client::open("rust_jack_sine", client_options::NO_START_SERVER)
.unwrap();
let (client, _status) = Client::new("rust_jack_sine", client_options::NO_START_SERVER).unwrap();

// 2. register port
let mut out_port = client.register_port("sine_out", AudioOutSpec::default()).unwrap();
Expand All @@ -32,7 +31,7 @@ fn main() {
let frame_t = 1.0 / sample_rate as f64;
let mut time = 0.0;
let (tx, rx) = channel();
let process = ProcessHandler::new(move |_: &WeakClient, ps: &ProcessScope| -> JackControl {
let process = ProcessHandler::new(move |_: &Client, ps: &ProcessScope| -> JackControl {
// Get output buffer
let mut out_p = AudioOutPort::new(&mut out_port, ps);
let out: &mut [f32] = &mut out_p;
Expand All @@ -56,7 +55,7 @@ fn main() {
});

// 4. activate the client
let active_client = client.activate(process).unwrap();
let active_client = AsyncClient::new(client, process).unwrap();
// processing starts here

// 5. wait or do some processing while your handler is running in real time.
Expand Down
144 changes: 144 additions & 0 deletions src/client/async_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use std::mem;
use std::ops::Deref;

use jack_sys as j;

use client::base::Client;
use client::common::{CREATE_OR_DESTROY_CLIENT_MUTEX, sleep_on_test};
use jack_enums::*;
use super::callbacks::{clear_callbacks, register_callbacks};

pub use super::callbacks::{JackHandler, ProcessHandler};

/// A JACK client that is processing data asynchronously, in real-time.
///
/// # Example
/// ```
/// use jack::prelude as j;
///
/// // Create a client and a handler
/// let (client, _status) = j::Client::new("my_client", j::client_options::NO_START_SERVER).unwrap();
/// let process_handler = j::ProcessHandler::new(move |_: &j::Client, _: &j::ProcessScope| j::JackControl::Continue);
///
/// // An active async client is created, `client` is consumed.
/// let active_client = j::AsyncClient::new(client, process_handler).unwrap();
/// ```
#[derive(Debug)]
pub struct AsyncClient<JH: JackHandler> {
client: Client,
handler: *mut (JH, *mut j::jack_client_t),
}

impl<JH: JackHandler> AsyncClient<JH> {
/// Tell the JACK server that the program is ready to start processing
/// audio. JACK will call the methods specified by the `JackHandler` trait, from `handler`.
///
/// On failure, either `Err(JackErr::CallbackRegistrationError)` or
/// `Err(JackErr::ClientActivationError)` is returned.
///
/// `handler` is consumed, but it is returned when `Client::deactivate` is
/// called.
pub fn new(client: Client, handler: JH) -> Result<Self, JackErr> {
let _ = *CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
unsafe {
sleep_on_test();
let handler_ptr = try!(register_callbacks(handler, client.as_ptr()));
sleep_on_test();
if handler_ptr.is_null() {
Err(JackErr::CallbackRegistrationError)
} else {
let res = j::jack_activate(client.as_ptr());
for _ in 0..4 {
sleep_on_test();
}
match res {
0 => {
Ok(AsyncClient {
client: client,
handler: handler_ptr,
})
}

_ => {
drop(Box::from_raw(handler_ptr));
Err(JackErr::ClientActivationError)
}
}
}
}
}


/// Tell the JACK server to remove this client from the process graph. Also, disconnect all
/// ports belonging to it since inactive clients have no port connections.
///
/// The `handler` that was used for `Client::activate` is returned on success. Its state may
/// have changed due to JACK calling its methods.
///
/// In the case of error, the `Client` is destroyed because its state is unknown, and it is
/// therefore unsafe to continue using.
pub fn deactivate(self) -> Result<(Client, JH), JackErr> {
let _ = *CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
unsafe {
// Collect contents, cleanup will be manual, instead of automatic as we don't want to
// drop our inner client, since it may still be open.
let (client_ptr, handler) = (self.client.as_ptr(), self.handler);
let client = Client::from_raw(client_ptr);

// Deactivate, but not close, the client
sleep_on_test();
mem::forget(self); // we're deactivating now, so no need to do it on drop
let res = match j::jack_deactivate(client.as_ptr()) {
// We own the handler post-deactivation
0 => Ok(Box::from_raw(handler)),

// We may still own the handler here, but it's not safe to say
// without more information about the error condition
_ => Err(JackErr::ClientDeactivationError),
};

// Clear the callbacks
sleep_on_test();
let callback_res = clear_callbacks(client.as_ptr());
sleep_on_test();

match (res, callback_res) {
(Ok(handler_ptr), Ok(())) => {
let (handler, _) = *handler_ptr;
Ok((client, handler))
}
(Err(err), _) | (_, Err(err)) => {
// We've invalidated the client, so it must be closed
drop(client);
Err(err)
}
}
}
}
}

impl<JH: JackHandler> Deref for AsyncClient<JH> {
type Target = Client;

fn deref(&self) -> &Self::Target {
&self.client
}
}

/// Closes the client.
impl<JH: JackHandler> Drop for AsyncClient<JH> {
fn drop(&mut self) {
let _ = *CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
unsafe {
// Deactivate the handler
sleep_on_test();
j::jack_deactivate(self.client.as_ptr()); // result doesn't matter
sleep_on_test();

// Drop the handler
drop(Box::from_raw(self.handler));

// The client will close itself on drop
}
}
}

0 comments on commit 220a7ba

Please sign in to comment.