Skip to content

Commit

Permalink
Pass &mut to handlers (#61)
Browse files Browse the repository at this point in the history
* Separates the `JackHandler` trait into `NotificationHandler` and `ProcessHandler`, both of which pass themselves through `&mut` to their functions.
  • Loading branch information
wmedrano committed Feb 21, 2017
1 parent 220a7ba commit a8989ff
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 289 deletions.
31 changes: 23 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
# Rust JACK

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://goo.gl/j4uiqg)

[![crates.io](https://img.shields.io/crates/v/jack.svg)](https://crates.io/crates/jack)
[![docs.rs](https://docs.rs/jack/badge.svg)](https://docs.rs/jack/)
[![crates.io](https://img.shields.io/crates/v/jack.svg)](https://goo.gl/ZCHble)
[![docs.rs](https://docs.rs/jack/badge.svg)](https://goo.gl/spnykb)

[![Build Status](https://travis-ci.org/wmedrano/rust-jack.svg?branch=master)](https://travis-ci.org/wmedrano/rust-jack)
[![Coverage Status](https://coveralls.io/repos/github/wmedrano/rust-jack/badge.svg?branch=master)](https://coveralls.io/github/wmedrano/rust-jack?branch=master)
[![Build Status](https://travis-ci.org/wmedrano/rust-jack.svg?branch=master)](https://goo.gl/KCqB7F)
[![Coverage Status](https://coveralls.io/repos/github/wmedrano/rust-jack/badge.svg?branch=master)](https://goo.gl/KCqB7F)


Nice Rust bindings for
[JACK Audio Connection Kit](http://www.jackaudio.org/)
[JACK Audio Connection Kit](https://goo.gl/jYMGO8)

[Documentation for Master](https://wmedrano.github.io/rust-jack/jack/index.html)
[Documentation for Master](https://goo.gl/xvXezZ)

Check out the `examples` directory for usage.

## Crates

### Stable
```toml
[dependencies]
jack = "0.4.0"
```

### Master
```toml
[dependencies]
jack = { git = "https://github.com/wmedrano/rust-jack.git" }
```

Check out the `examples` directory.

## Completeness

Expand Down
93 changes: 91 additions & 2 deletions examples/playback_capture.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,96 @@
//! Takes 2 audio inputs and outputs them to 2 audio outputs.
//! All JACK notifications are also printed out.
extern crate jack;
use jack::prelude as j;
use std::io;

struct Notifications;

impl j::NotificationHandler for Notifications {
fn thread_init(&self, _: &j::Client) {
println!("JACK: thread init");
}

fn shutdown(&mut self, status: j::ClientStatus, reason: &str) {
println!("JACK: shutdown with status {:?} because \"{}\"",
status,
reason);
}

fn freewheel(&mut self, _: &j::Client, is_enabled: bool) {
println!("JACK: freewheel mode is {}",
if is_enabled { "on" } else { "of" });
}

fn buffer_size(&mut self, _: &j::Client, sz: j::JackFrames) -> j::JackControl {
println!("JACK: buffer size changed to {}", sz);
j::JackControl::Continue
}

fn sample_rate(&mut self, _: &j::Client, srate: j::JackFrames) -> j::JackControl {
println!("JACK: sample rate changed to {}", srate);
j::JackControl::Continue
}

fn client_registration(&mut self, _: &j::Client, name: &str, is_reg: bool) {
println!("JACK: {} client with name \"{}\"",
if is_reg { "registered" } else { "unregistered" },
name);
}

fn port_registration(&mut self, _: &j::Client, port_id: j::JackPortId, is_reg: bool) {
println!("JACK: {} port with id {}",
if is_reg { "registered" } else { "unregistered" },
port_id);
}

fn port_rename(&mut self,
_: &j::Client,
port_id: j::JackPortId,
old_name: &str,
new_name: &str)
-> j::JackControl {
println!("JACK: port with id {} renamed from {} to {}",
port_id,
old_name,
new_name);
j::JackControl::Continue
}

fn ports_connected(&mut self,
_: &j::Client,
port_id_a: j::JackPortId,
port_id_b: j::JackPortId,
are_connected: bool) {
println!("JACK: ports with id {} and {} are {}",
port_id_a,
port_id_b,
if are_connected {
"connected"
} else {
"disconnected"
});
}

fn graph_reorder(&mut self, _: &j::Client) -> j::JackControl {
println!("JACK: graph reordered");
j::JackControl::Continue
}

fn xrun(&mut self, _: &j::Client) -> j::JackControl {
println!("JACK: xrun occurred");
j::JackControl::Continue
}

fn latency(&mut self, _: &j::Client, mode: j::LatencyType) {
println!("JACK: {} latency has changed",
match mode {
j::LatencyType::Capture => "capture",
j::LatencyType::Playback => "playback",
});
}
}

fn main() {
// Create client
let (client, _status) = j::Client::new("rust_jack_simple", j::client_options::NO_START_SERVER)
Expand All @@ -23,9 +111,10 @@ fn main() {
out_b_p.clone_from_slice(&in_b_p);
j::JackControl::Continue
};
let process = j::ProcessHandler::new(process_callback);
let process = j::ClosureProcessHandler::new(process_callback);

// Activate the client, which starts the processing.
let active_client = j::AsyncClient::new(client, process).unwrap();
let active_client = j::AsyncClient::new(client, Notifications, process).unwrap();

// Wait for user input to quit
println!("Press enter/return to quit...");
Expand Down
6 changes: 3 additions & 3 deletions examples/show_midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
extern crate jack;
use std::io;
use jack::prelude::{AsyncClient, Client, JackControl, MidiInPort, MidiInSpec, MidiOutPort,
MidiOutSpec, ProcessHandler, ProcessScope, RawMidi, client_options};
MidiOutSpec, ClosureProcessHandler, ProcessScope, RawMidi, client_options};

fn main() {
// open client
Expand Down Expand Up @@ -36,8 +36,8 @@ fn main() {
};

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

// wait
println!("Press any key to quit");
Expand Down
8 changes: 4 additions & 4 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, JackControl, ProcessHandler, ProcessScope,
AsyncClient, client_options};
use jack::prelude::{AudioOutPort, AudioOutSpec, Client, JackControl, ClosureProcessHandler,
ProcessScope, AsyncClient, client_options};


/// Attempt to read a frequency from standard in. Will block until there is user input. `None` is
Expand All @@ -31,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 |_: &Client, ps: &ProcessScope| -> JackControl {
let process = ClosureProcessHandler::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 @@ -55,7 +55,7 @@ fn main() {
});

// 4. activate the client
let active_client = AsyncClient::new(client, 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
69 changes: 30 additions & 39 deletions src/client/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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};
pub use super::callbacks::{NotificationHandler, ProcessHandler};

/// A JACK client that is processing data asynchronously, in real-time.
///
Expand All @@ -18,31 +18,33 @@ pub use super::callbacks::{JackHandler, ProcessHandler};
///
/// // 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);
/// let process_handler = j::ClosureProcessHandler::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();
/// let active_client = j::AsyncClient::new(client, (), process_handler).unwrap();
/// ```
#[derive(Debug)]
pub struct AsyncClient<JH: JackHandler> {
pub struct AsyncClient<N: NotificationHandler, P: ProcessHandler> {
client: Client,
handler: *mut (JH, *mut j::jack_client_t),
handler: *mut (N, P, *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`.
impl<N, P> AsyncClient<N, P> where N: NotificationHandler, P: ProcessHandler{
/// Tell the JACK server that the program is ready to start processing audio. JACK will call the
/// methods specified by the `NotificationHandler` and `ProcessHandler` objects.
///
/// 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> {
pub fn new(client: Client, notification_handler: N, process_handler: P) -> 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()));
let handler_ptr = try!(register_callbacks(notification_handler, process_handler, client.as_ptr()));
sleep_on_test();
if handler_ptr.is_null() {
Err(JackErr::CallbackRegistrationError)
Expand Down Expand Up @@ -77,47 +79,35 @@ impl<JH: JackHandler> AsyncClient<JH> {
///
/// 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> {
pub fn deactivate(self) -> Result<(Client, N, P), 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();
let (client_ptr, handler_ptr) = (self.client.as_ptr(), self.handler);
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
// deactivate
sleep_on_test();
let callback_res = clear_callbacks(client.as_ptr());
if j::jack_deactivate(client_ptr) != 0 {
return Err(JackErr::ClientDeactivationError);
}

// clear the callbacks
sleep_on_test();
try!(clear_callbacks(client_ptr));

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)
}
}
// done, take ownership of pointer
let handler_box = Box::from_raw(handler_ptr);
let handler_tuple = *handler_box;
let (n_handler, p_handler, _client_ptr) = handler_tuple;
Ok((Client::from_raw(client_ptr), n_handler, p_handler))
}
}
}

impl<JH: JackHandler> Deref for AsyncClient<JH> {
impl<N, P> Deref for AsyncClient<N, P>
where N: NotificationHandler, P: ProcessHandler{
type Target = Client;

fn deref(&self) -> &Self::Target {
Expand All @@ -126,7 +116,8 @@ impl<JH: JackHandler> Deref for AsyncClient<JH> {
}

/// Closes the client.
impl<JH: JackHandler> Drop for AsyncClient<JH> {
impl<N, P> Drop for AsyncClient<N, P>
where N: NotificationHandler, P: ProcessHandler {
fn drop(&mut self) {
let _ = *CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
unsafe {
Expand Down
1 change: 1 addition & 0 deletions src/client/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ impl Client {
source_port: &Port<A>,
destination_port: &Port<B>)
-> Result<(), JackErr> {
let _ = *CREATE_OR_DESTROY_CLIENT_MUTEX.lock().unwrap();
self.connect_ports_by_name(source_port.name(), destination_port.name())
}

Expand Down
Loading

0 comments on commit a8989ff

Please sign in to comment.