Skip to content

Commit 3fdafa1

Browse files
committed
0.2: add more handlers, fix panic unsoundness
- Not all the handlers are added yet. This is because I got bored. - Panicking is now safe (still not a good idea, but safe) to do in callbacks. Yay!
1 parent a85a6a3 commit 3fdafa1

File tree

4 files changed

+223
-18
lines changed

4 files changed

+223
-18
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqa-jack"
3-
version = "0.1.1"
3+
version = "0.2.0"
44
description = "JACK bindings for Rust (part of the SQA project)"
55
documentation = "https://docs.rs/sqa-jack"
66
repository = "https://github.com/eeeeeta/sqa-jack"

src/handler.rs

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use *;
2+
use std::panic::{AssertUnwindSafe, catch_unwind};
23

34
/// Context for some callbacks.
45
pub struct JackCallbackContext {
@@ -33,6 +34,10 @@ pub enum JackControl {
3334
/// Stop processing.
3435
Stop = -1
3536
}
37+
/// Trait for an object that implements JACK callbacks.
38+
///
39+
/// Most of the default implementations return `JackControl::Continue` - however, **process() does not** - you
40+
/// must explicitly override this behaviour if you want to specify nothing for `process()`.
3641
pub trait JackHandler: Send {
3742
/// This function is called by the engine any time there is work to be done.
3843
/// Return `JackControl::Stop` to stop processing, otherwise return
@@ -53,34 +58,119 @@ pub trait JackHandler: Send {
5358
fn process(&mut self, _ctx: &JackCallbackContext) -> JackControl {
5459
JackControl::Stop
5560
}
61+
/// This is called whenever the size of the the buffer that will be passed to the
62+
/// `process()` function is about to change.
63+
/// Clients that depend on knowing the buffer size must implement this callback.
64+
fn buffer_size(&mut self, _new_size: jack_nframes_t) -> JackControl { JackControl::Continue }
65+
/// Called whenever the system sample rate changes.
66+
///
67+
/// Given that the JACK API exposes no way to change the sample rate, the library author
68+
/// would like you to know that this is a decidedly rare occurence. Still, it's worth
69+
/// being prepared ;)
70+
fn sample_rate(&mut self, _new_rate: jack_nframes_t) -> JackControl { JackControl::Continue }
71+
/// Called just once after the creation of the thread in which all other callbacks are
72+
/// handled.
73+
fn thread_init(&mut self) { }
74+
/// To be called if and when the JACK server shuts down the client thread.
75+
fn shutdown(&mut self, _status: JackStatus, _reason: &str) { }
76+
/// Called whenever a client is registered or unregistered.
77+
///
78+
/// Use the `registered` argument to determine which it is.
79+
fn client_registered(&mut self, _name: &str, _registered: bool) { }
80+
/// Called when an XRUN (over- or under- run) occurs.
81+
fn xrun(&mut self) -> JackControl { JackControl::Continue }
5682
}
83+
/*
84+
/// Called whenever a port is registered or unregistered.
85+
///
86+
/// Use the `registered` argument to determine which it is.
87+
fn port_registered(&mut self, _port: JackPort, _registered: bool) { }
88+
/// Called whenever a port is renamed.
89+
fn port_renamed(&mut self, _port: JackPort, _old_name: &str, _new_name: &str) { }
90+
/// Called whenever ports are connected or disconnected.
91+
///
92+
/// Use the `connected` argument to determine which it is.
93+
fn port_connected(&mut self, _from: JackPort, _to: JackPort, _connected: bool) { }
94+
/// Called whenever the processing graph is reordered.
95+
fn graph_reorder(&mut self) -> JackControl { JackControl::Continue }
96+
/// Called when the JACK server starts or stops freewheeling.
97+
///
98+
/// Use the `freewheel` argument to determine which it is.
99+
fn freewheel(&mut self, _freewheel: bool) { }
100+
*/
57101

58102
impl<F> JackHandler for F where F: FnMut(&JackCallbackContext) -> JackControl + Send + 'static {
59103
fn process(&mut self, ctx: &JackCallbackContext) -> JackControl {
60104
self(ctx)
61105
}
62106
}
107+
unsafe extern "C" fn buffer_size_callback<T>(frames: jack_nframes_t, user: *mut libc::c_void) -> libc::c_int where T: JackHandler {
108+
let callbacks = &mut *(user as *mut T);
109+
catch_unwind(AssertUnwindSafe(|| {
110+
callbacks.buffer_size(frames) as _
111+
})).unwrap_or(-1)
112+
}
113+
unsafe extern "C" fn sample_rate_callback<T>(frames: jack_nframes_t, user: *mut libc::c_void) -> libc::c_int where T: JackHandler {
114+
let callbacks = &mut *(user as *mut T);
115+
catch_unwind(AssertUnwindSafe(|| {
116+
callbacks.sample_rate(frames) as _
117+
})).unwrap_or(-1)
118+
}
119+
unsafe extern "C" fn client_registration_callback<T>(name: *const libc::c_char, register: libc::c_int, user: *mut libc::c_void) where T: JackHandler {
120+
let callbacks = &mut *(user as *mut T);
121+
let name = CStr::from_ptr(name);
122+
let _ = catch_unwind(AssertUnwindSafe(|| {
123+
callbacks.client_registered(&name.to_string_lossy(), register != 0)
124+
}));
63125

64-
extern "C" fn process_callback<T>(nframes: jack_nframes_t, user: *mut libc::c_void) -> i32 where T: JackHandler {
65-
unsafe {
66-
let callbacks = &mut *(user as *mut T);
67-
let ctx = JackCallbackContext {
68-
nframes: nframes
69-
};
70-
callbacks.process(&ctx) as i32
71-
}
72126
}
127+
unsafe extern "C" fn info_shutdown_callback<T>(code: jack_status_t, reason: *const libc::c_char, user: *mut libc::c_void) where T: JackHandler {
128+
let callbacks = &mut *(user as *mut T);
129+
let code = JackStatus::from_bits_truncate(code);
130+
let reason = CStr::from_ptr(reason);
131+
let _ = catch_unwind(AssertUnwindSafe(|| {
132+
callbacks.shutdown(code, &reason.to_string_lossy())
133+
}));
73134

135+
}
136+
unsafe extern "C" fn thread_init_callback<T>(user: *mut libc::c_void) where T: JackHandler {
137+
let callbacks = &mut *(user as *mut T);
138+
let _ = catch_unwind(AssertUnwindSafe(|| {
139+
callbacks.thread_init()
140+
}));
141+
}
142+
unsafe extern "C" fn process_callback<T>(nframes: jack_nframes_t, user: *mut libc::c_void) -> libc::c_int where T: JackHandler {
143+
let callbacks = &mut *(user as *mut T);
144+
let ctx = JackCallbackContext {
145+
nframes: nframes
146+
};
147+
catch_unwind(AssertUnwindSafe(|| {
148+
callbacks.process(&ctx) as _
149+
})).unwrap_or(-1)
150+
}
151+
unsafe extern "C" fn xrun_callback<T>(user: *mut libc::c_void) -> libc::c_int where T: JackHandler {
152+
let callbacks = &mut *(user as *mut T);
153+
catch_unwind(AssertUnwindSafe(|| {
154+
callbacks.xrun() as _
155+
})).unwrap_or(-1)
156+
}
74157
pub fn set_handler<F>(conn: &mut JackConnection<Deactivated>, handler: F) -> JackResult<()> where F: JackHandler {
75158
let user_ptr = Box::into_raw(Box::new(handler));
76159
let user_ptr = user_ptr as *mut libc::c_void;
77-
let code = unsafe {
78-
jack_set_process_callback(conn.handle, Some(process_callback::<F>), user_ptr)
79-
};
80-
if code != 0 {
81-
Err(ErrorKind::UnknownErrorCode("set_process_callback()", code))?
82-
}
83-
else {
84-
Ok(())
160+
unsafe {
161+
let code = jack_set_process_callback(conn.handle, Some(process_callback::<F>), user_ptr);
162+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - process", code))? }
163+
let code = jack_set_thread_init_callback(conn.handle, Some(thread_init_callback::<F>), user_ptr);
164+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - thread_init", code))? }
165+
let code = jack_set_buffer_size_callback(conn.handle, Some(buffer_size_callback::<F>), user_ptr);
166+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - buffer_size", code))? }
167+
let code = jack_set_sample_rate_callback(conn.handle, Some(sample_rate_callback::<F>), user_ptr);
168+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - sample_rate", code))? }
169+
let code = jack_set_xrun_callback(conn.handle, Some(xrun_callback::<F>), user_ptr);
170+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - xrun", code))? }
171+
let code = jack_set_client_registration_callback(conn.handle, Some(client_registration_callback::<F>), user_ptr);
172+
if code != 0 { Err(ErrorKind::UnknownErrorCode("set_process_callback() - client_registration", code))? }
173+
jack_on_info_shutdown(conn.handle, Some(info_shutdown_callback::<F>), user_ptr);
85174
}
175+
Ok(())
86176
}

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ pub mod errors;
1010
pub mod handler;
1111
pub mod port;
1212

13+
#[cfg(test)]
14+
mod tests;
15+
1316
use jack_sys::*;
1417
use std::ffi::{CString, CStr};
1518
use std::marker::PhantomData;
@@ -23,7 +26,7 @@ bitflags! {
2326
/// Status of an operation.
2427
///
2528
/// See `STATUS_*` constants for possible values.
26-
pub flags JackStatus: libc::c_uint {
29+
pub flags JackStatus: jack_status_t {
2730
/// Overall operation failed.
2831
const STATUS_FAILURE = jack_sys::JackFailure,
2932
/// The operation contained an invalid or unsupported option.

src/tests.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//! Tests
2+
use *;
3+
use std::thread;
4+
use std::sync::atomic::AtomicBool;
5+
use std::sync::atomic::Ordering::*;
6+
use std::sync::Arc;
7+
8+
#[test]
9+
fn panicking() {
10+
let i_witnessed_a_panic = Arc::new(AtomicBool::new(false));
11+
fn run(iwp: Arc<AtomicBool>) -> JackResult<()> {
12+
let mut conn = JackConnection::connect("Testing", None)?;
13+
let mut first = false;
14+
conn.set_handler(move |_: &JackCallbackContext| {
15+
if !first {
16+
first = true;
17+
}
18+
else {
19+
// uh-oh, JACK called us again after panicking!
20+
iwp.store(true, Relaxed);
21+
}
22+
panic!("Hi, I'm a closure, and I'm a panic-a-holic.");
23+
})?;
24+
let _ = match conn.activate() {
25+
Ok(nc) => nc,
26+
Err((_, err)) => return Err(err)
27+
};
28+
thread::sleep(::std::time::Duration::new(2, 0));
29+
Ok(())
30+
}
31+
run(i_witnessed_a_panic.clone()).unwrap();
32+
assert_eq!(i_witnessed_a_panic.load(Relaxed), false);
33+
}
34+
#[test]
35+
fn thread_init() {
36+
let atomic = Arc::new(AtomicBool::new(false));
37+
struct Special(Arc<AtomicBool>);
38+
impl JackHandler for Special {
39+
fn thread_init(&mut self) {
40+
self.0.store(true, Relaxed)
41+
}
42+
}
43+
fn run(atomic: Arc<AtomicBool>) -> JackResult<()> {
44+
let mut conn = JackConnection::connect("Testing", None)?;
45+
let special = Special(atomic);
46+
conn.set_handler(special)?;
47+
let _ = match conn.activate() {
48+
Ok(nc) => nc,
49+
Err((_, err)) => return Err(err)
50+
};
51+
thread::sleep(::std::time::Duration::new(2, 0));
52+
Ok(())
53+
}
54+
run(atomic.clone()).unwrap();
55+
assert_eq!(atomic.load(Relaxed), true);
56+
}
57+
#[test]
58+
fn sawtooth() {
59+
struct Sawtooth {
60+
out1: JackPort,
61+
out2: JackPort,
62+
left_saw: f32,
63+
right_saw: f32,
64+
xrun: Arc<AtomicBool>
65+
}
66+
impl JackHandler for Sawtooth {
67+
fn process(&mut self, ctx: &JackCallbackContext) -> JackControl {
68+
let out1 = ctx.get_port_buffer(&self.out1).unwrap();
69+
let out2 = ctx.get_port_buffer(&self.out2).unwrap();
70+
for (out1, out2) in out1.iter_mut().zip(out2.iter_mut()) {
71+
*out1 = self.left_saw * 0.1;
72+
*out2 = self.right_saw * 0.1;
73+
self.left_saw += 0.01;
74+
if self.left_saw >= 1.0 { self.left_saw -= 2.0; }
75+
self.right_saw += 0.03;
76+
if self.right_saw >= 1.0 { self.right_saw -= 2.0; }
77+
}
78+
JackControl::Continue
79+
}
80+
fn xrun(&mut self) -> JackControl {
81+
self.xrun.store(true, Relaxed);
82+
JackControl::Continue
83+
}
84+
}
85+
fn run(b: Arc<AtomicBool>) -> JackResult<()> {
86+
let mut conn = JackConnection::connect("Very Annoying Sawtooth Generator", None)?;
87+
let out1 = conn.register_port("output_1", PORT_IS_OUTPUT)?;
88+
let out2 = conn.register_port("output_2", PORT_IS_OUTPUT)?;
89+
let data = Sawtooth {
90+
out1: out1,
91+
out2: out2,
92+
left_saw: 0.0,
93+
right_saw: 0.0,
94+
xrun: b
95+
};
96+
conn.set_handler(data)?;
97+
let mut conn = match conn.activate() {
98+
Ok(nc) => nc,
99+
Err((_, err)) => return Err(err)
100+
};
101+
let ports = conn.get_ports(None, None, Some(PORT_IS_INPUT | PORT_IS_PHYSICAL))?;
102+
if ports.len() >= 2 {
103+
conn.connect_ports(&out1, &ports[0])?;
104+
conn.connect_ports(&out2, &ports[1])?;
105+
}
106+
thread::sleep(::std::time::Duration::new(2, 0));
107+
Ok(())
108+
}
109+
let atomic = Arc::new(AtomicBool::new(false));
110+
run(atomic.clone()).unwrap();
111+
assert_eq!(atomic.load(Relaxed), false);
112+
}

0 commit comments

Comments
 (0)