Skip to content

Commit

Permalink
Separate PortSpec and Port wrapping datatypes
Browse files Browse the repository at this point in the history
Previously, a port is registered and associated with a specific datatype that ends up wrapping it. The wrapping was done by: `my_registered_port.data(...)`.

Changed it so that ports are now registered with a `PortSpec` while wrappers can be created independent.

EX:
``` rust
/// I allow safe access to port data.
pub struct MySafeWrapper {port: &Port<ThePortSpec>, ps: &ProcessScope};
```
  • Loading branch information
wmedrano authored Dec 29, 2016
1 parent 0ebd255 commit c706e0d
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 259 deletions.
46 changes: 18 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,34 @@ Check out the `examples` directory.

* The general workflow for a JACK application is to start up a JACK daemon and connect the client to it. [qjackctl](http://qjackctl.sourceforge.net/) is a convinient way to configure and bring up a JACK server through a GUI.

## Running Tests
## Testing

Testing is a little awkward to setup since it relies on a JACK server.

### Setting Up JACK Dummy Server
Testing expects there to be an available JACK server running at a sample rate of
44.1kHz and a buffer size of 1024 samples.

```bash
$ ./dummy_jack_server.sh
```

which runs

```bash
$ jackd -r -ddummy -r44100 -p1024 & # Start the dummy JACK server
```

Testing expects there to be an available JACK server running at a sample rate of
44.1kHz and a buffer size of 1024 samples.

#### Possible Issues

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 of `DEFAULT_SLEEP_TIME` in `test.rs`.

Another case can be that libjack is broke. Try switching between libjack and
Another case is that libjack may be broken.
Try switching between libjack and
libjack2 (they have the same API and libjack2 isn't necessarily newer than
libjack), or using a different version.

Expand All @@ -53,27 +61,9 @@ $ cargo test
* `jack_on_shutdown` has been removed, uses only `jack_on_info_shutdown`.
* Rust enums vs C enums
* Rust bitflags vs C integers used as flags
* deprecated Jack functions are not used/implemented in Rust bindings


## Progress

Sections based on the
[main page](http://jackaudio.org/files/docs/html/index.html) sections on the
Jack API.

### TODO
* Top priority: MIDI!!!
* Managing and determining latency
* Transport and Timebase control
* The non-callback API (possibly skip)
* Reading and writing MIDI data
* Session API for clients.
* managing support for newer/older versions of JACK
* the API for starting and controlling a JACK server
* * Metadata API.

### Other TODOS
* make safer
* better error reporting
* better testing
* deprecated JACK functions are not used/implemented in Rust bindings


## C JACK API

[Main Page](http://jackaudio.org/files/docs/html/index.html)
4 changes: 4 additions & 0 deletions dummy_jack_server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
# Start the dummy JACK server

jackd -r -ddummy -r44100 -p1024
16 changes: 10 additions & 6 deletions examples/playback_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,17 @@ fn main() {

// Register ports, that will be used in a callback that will be
// called when new data is available.
let mut in_a: jack::AudioInPort = client.register_port("rust_in_l").unwrap();
let mut in_b: jack::AudioInPort = client.register_port("rust_in_r").unwrap();
let mut out_a: jack::AudioOutPort = client.register_port("rust_out_l").unwrap();
let mut out_b: jack::AudioOutPort = client.register_port("rust_out_r").unwrap();
let in_a = client.register_port("rust_in_l", jack::AudioInSpec).unwrap();
let in_b = client.register_port("rust_in_r", jack::AudioInSpec).unwrap();
let mut out_a = client.register_port("rust_out_l", jack::AudioOutSpec).unwrap();
let mut out_b = client.register_port("rust_out_r", jack::AudioOutSpec).unwrap();
let process_callback = move |ps: &jack::ProcessScope| -> jack::JackControl {
out_a.data(ps).buffer().clone_from_slice(in_a.data(ps).buffer());
out_b.data(ps).buffer().clone_from_slice(in_b.data(ps).buffer());
let mut out_a_p = jack::AudioOutPort::new(&mut out_a, ps);
let mut out_b_p = jack::AudioOutPort::new(&mut out_b, ps);
let in_a_p = jack::AudioInPort::new(&in_a, ps);
let in_b_p = jack::AudioInPort::new(&in_b, ps);
out_a_p.clone_from_slice(&in_a_p);
out_b_p.clone_from_slice(&in_b_p);
jack::JackControl::Continue
};
// Activate the client, which starts the processing.
Expand Down
27 changes: 14 additions & 13 deletions examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ extern crate jack;
use std::io;
use std::str::FromStr;
use std::sync::mpsc::{Sender, Receiver, channel};
use jack::JackClient;
use jack::{client_options, AudioOutPort, AudioOutSpec, Client, JackClient, JackHandler,
JackControl, Port, ProcessScope};


pub struct SinWave<'a> {
pub struct SinWave {
frame_t: f64,
frequency: f64,
out_port: jack::AudioOutPort<'a>,
out_port: Port<AudioOutSpec>,
time: f64,
receiver: Receiver<f64>,
sender: Sender<f64>,
}

impl<'a> SinWave<'a> {
pub fn new(out_port: jack::AudioOutPort<'a>, freq: f64, sample_rate: f64) -> Self {
impl SinWave {
pub fn new(out_port: Port<AudioOutSpec>, freq: f64, sample_rate: f64) -> Self {
let (tx, rx) = channel();
SinWave {
frame_t: 1.0 / sample_rate,
Expand All @@ -32,11 +33,11 @@ impl<'a> SinWave<'a> {
}
}

impl<'a> jack::JackHandler for SinWave<'a> {
fn process(&mut self, process_scope: &jack::ProcessScope) -> jack::JackControl {
impl JackHandler for SinWave {
fn process(&mut self, process_scope: &ProcessScope) -> JackControl {
// Get output buffer
let mut out_data = self.out_port.data(&process_scope);
let out: &mut [f32] = out_data.buffer();
let mut out_p = AudioOutPort::new(&mut self.out_port, process_scope);
let out: &mut [f32] = &mut out_p;

// Check frequency requests
while let Ok(f) = self.receiver.try_recv() {
Expand All @@ -53,7 +54,7 @@ impl<'a> jack::JackHandler for SinWave<'a> {
}

// Continue as normal
jack::JackControl::Continue
JackControl::Continue
}
}

Expand All @@ -66,10 +67,10 @@ fn read_freq() -> Option<f64> {
}

fn main() {
let (mut client, _status) =
jack::Client::open("rust_jack_sine", jack::client_options::NO_START_SERVER).unwrap();
let (mut client, _status) = Client::open("rust_jack_sine", client_options::NO_START_SERVER)
.unwrap();

let out_port = client.register_port("sine_out").unwrap();
let out_port = client.register_port("sine_out", AudioOutSpec).unwrap();
let app = SinWave::new(out_port, 220.0, client.sample_rate() as f64);
let freq_request = app.frequency_requester();
let active_client = client.activate(app).unwrap();
Expand Down
6 changes: 4 additions & 2 deletions src/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ pub struct ProcessScope {
}

impl ProcessScope {
pub unsafe fn new(n_frames: u32, client_ptr: *mut j::jack_client_t) -> Self {
/// Create a `ProcessScope` for the client with the given pointer
/// and the specified amount of frames.
pub unsafe fn from_raw(n_frames: u32, client_ptr: *mut j::jack_client_t) -> Self {
ProcessScope {
n_frames: n_frames,
client_ptr: client_ptr,
Expand Down Expand Up @@ -200,7 +202,7 @@ unsafe extern "C" fn shutdown<T: JackHandler>(code: j::jack_status_t,

unsafe extern "C" fn process<T: JackHandler>(n_frames: u32, data: *mut c_void) -> i32 {
let obj: &mut (T, *mut j::jack_client_t) = handler_and_ptr_from_void(data);
let scope = ProcessScope::new(n_frames, obj.1);
let scope = ProcessScope::from_raw(n_frames, obj.1);
obj.0.process(&scope).to_ffi()
}

Expand Down
34 changes: 20 additions & 14 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::{ffi, ptr};
use jack_sys as j;

use std::mem;
use std::{ffi, ptr};

use callbacks::{JackHandler, register_callbacks, clear_callbacks};
use jack_enums::*;
use jack_flags::port_flags::PortFlags;
use jack_flags::client_options::ClientOptions;
use jack_flags::client_status::{ClientStatus, UNKNOWN_ERROR};
use port::{Port, PortData, UnownedPort};
use jack_flags::port_flags::PortFlags;
use jack_utils::collect_strs;
use callbacks::{JackHandler, register_callbacks, clear_callbacks};
use std::mem;
use port::{Port, PortSpec, UnownedPort};
use port;

/// The maximum length of the JACK client name string. Unlike the "C" JACK
/// API, this does not take into account the final `NULL` character and
Expand Down Expand Up @@ -176,7 +179,7 @@ pub unsafe trait JackClient: Sized {
if pp.is_null() {
None
} else {
Some(unsafe { Port::from_raw(self.client_ptr(), pp) })
Some(unsafe { Port::from_raw(port::Unowned {}, self.client_ptr(), pp) })
}
}

Expand All @@ -187,7 +190,7 @@ pub unsafe trait JackClient: Sized {
if pp.is_null() {
None
} else {
Some(unsafe { Port::from_raw(self.client_ptr(), pp) })
Some(unsafe { Port::from_raw(port::Unowned {}, self.client_ptr(), pp) })
}
}

Expand Down Expand Up @@ -257,7 +260,7 @@ pub unsafe trait JackClient: Sized {
}

/// Returns `true` if the port `port` belongs to this client.
fn is_mine<PD: PortData>(&self, port: &Port<PD>) -> bool {
fn is_mine<PS: PortSpec>(&self, port: &Port<PS>) -> bool {
match unsafe { j::jack_port_is_mine(self.client_ptr(), port.port_ptr()) } {
1 => true,
_ => false,
Expand Down Expand Up @@ -359,11 +362,14 @@ impl Client {
///
/// `buffer_size` - Must be `Some(n)` if this is not a built-in
/// `port_type`. Otherwise, it is ignored.
pub fn register_port<PD: PortData>(&mut self, port_name: &str) -> Result<Port<PD>, JackErr> {
pub fn register_port<PS: PortSpec>(&mut self,
port_name: &str,
port_spec: PS)
-> Result<Port<PS>, JackErr> {
let port_name_c = ffi::CString::new(port_name).unwrap();
let port_type_c = ffi::CString::new(PD::jack_port_type()).unwrap();
let port_flags = PD::jack_flags().bits() as u64;
let buffer_size = PD::jack_buffer_size() as u64;
let port_type_c = ffi::CString::new(port_spec.jack_port_type()).unwrap();
let port_flags = port_spec.jack_flags().bits() as u64;
let buffer_size = port_spec.jack_buffer_size() as u64;
let pp = unsafe {
j::jack_port_register(self.client,
port_name_c.as_ptr(),
Expand All @@ -374,7 +380,7 @@ impl Client {
if pp.is_null() {
Err(JackErr::PortRegistrationError(port_name.to_string()))
} else {
Ok(unsafe { Port::from_raw(self.client_ptr(), pp) })
Ok(unsafe { Port::from_raw(port_spec, self.client_ptr(), pp) })
}
}

Expand Down Expand Up @@ -487,7 +493,7 @@ impl Client {

/// Remove the port from the client, disconnecting any existing connections.
/// The port must have been created with this client.
pub fn unregister_port<PD: PortData>(&mut self, _port: Port<PD>) -> Result<(), JackErr> {
pub fn unregister_port<PS: PortSpec>(&mut self, _port: Port<PS>) -> Result<(), JackErr> {
unimplemented!();
// port.unregister(self)
}
Expand Down
58 changes: 36 additions & 22 deletions src/info.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
use std::io::{Write, stderr};
use std::ffi;
use std::sync::{Once, ONCE_INIT};
use jack_sys as j;

fn to_stdout(msg: &str) {
println!("{}", msg);
}

fn to_stderr(msg: &str) {
writeln!(&mut stderr(), "{}", msg).unwrap();
}
fn to_nothing(_: &str) {}

static mut info_fn: fn(&str) = to_stdout;
static mut error_fn: fn(&str) = to_stderr;
static mut INFO_FN: fn(&str) = to_nothing;
static mut ERROR_FN: fn(&str) = to_nothing;

unsafe extern "C" fn error_wrapper(msg: *const i8) {
let msg = ffi::CStr::from_ptr(msg).to_str().unwrap();
error_fn(msg);
ERROR_FN(msg);
}

unsafe extern "C" fn info_wrapper(msg: *const i8) {
let msg = ffi::CStr::from_ptr(msg).to_str().unwrap();
info_fn(msg)
INFO_FN(msg)
}

static IS_INFO_CALLBACK_SET: Once = ONCE_INIT;
/// Set the global JACK info callback.
///
/// # Example
/// ```rust
/// fn info_log(msg: &str) {
/// println!("{}", msg);
/// }
/// jack::set_info_callback(info_log);
/// ```
pub fn set_info_callback(info: fn(&str)) {
unsafe {
INFO_FN = info;
}
IS_INFO_CALLBACK_SET.call_once(|| unsafe { j::jack_set_info_function(Some(info_wrapper)) })
}

static ARE_CALLBACKS_SET: Once = ONCE_INIT;
/// TODO: Provide better API for this functionality
pub fn set_info_callbacks(info: Option<fn(&str)>, error: Option<fn(&str)>) {
static IS_ERROR_CALLBACK_SET: Once = ONCE_INIT;
/// Set the global JACK error callback.
///
/// # Example
/// ```rust
/// fn error_log(msg: &str) {
/// println!("{}", msg);
/// }
/// jack::set_error_callback(error_log);
/// ```
pub fn set_error_callback(error: fn(&str)) {
unsafe {
info_fn = info.unwrap_or(to_stdout);
error_fn = error.unwrap_or(to_stderr);
};
ARE_CALLBACKS_SET.call_once(|| unsafe {
j::jack_set_error_function(Some(error_wrapper));
j::jack_set_info_function(Some(info_wrapper));
});
ERROR_FN = error;
}
IS_ERROR_CALLBACK_SET.call_once(|| unsafe { j::jack_set_error_function(Some(error_wrapper)) })
}
Loading

0 comments on commit c706e0d

Please sign in to comment.