From c706e0d9b336efa039ed610c43c19a1fa581ea5f Mon Sep 17 00:00:00 2001 From: Will Date: Thu, 29 Dec 2016 08:00:19 -0800 Subject: [PATCH] Separate PortSpec and Port wrapping datatypes 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, ps: &ProcessScope}; ``` --- README.md | 46 ++++------ dummy_jack_server.sh | 4 + examples/playback_capture.rs | 16 ++-- examples/sine.rs | 27 +++--- src/callbacks.rs | 6 +- src/client.rs | 34 +++++--- src/info.rs | 58 ++++++++----- src/jack_flags/client_options.rs | 47 ++++++----- src/jack_flags/client_status.rs | 73 ++++++++-------- src/jack_flags/mod.rs | 2 +- src/jack_flags/port_flags.rs | 42 ++++----- src/lib.rs | 51 +++++++++-- src/port.rs | 59 ++++++------- src/port_impls.rs | 141 +++++++++++++++++++------------ 14 files changed, 347 insertions(+), 259 deletions(-) create mode 100755 dummy_jack_server.sh diff --git a/README.md b/README.md index 83a28bbe3..b45990b24 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,25 @@ 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. @@ -35,7 +42,8 @@ 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. @@ -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) diff --git a/dummy_jack_server.sh b/dummy_jack_server.sh new file mode 100755 index 000000000..1355d2e17 --- /dev/null +++ b/dummy_jack_server.sh @@ -0,0 +1,4 @@ +#!/bin/sh +# Start the dummy JACK server + +jackd -r -ddummy -r44100 -p1024 diff --git a/examples/playback_capture.rs b/examples/playback_capture.rs index 2e450b1c2..6c451c442 100644 --- a/examples/playback_capture.rs +++ b/examples/playback_capture.rs @@ -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. diff --git a/examples/sine.rs b/examples/sine.rs index 950d0cf23..fd7049d84 100644 --- a/examples/sine.rs +++ b/examples/sine.rs @@ -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, time: f64, receiver: Receiver, sender: Sender, } -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, freq: f64, sample_rate: f64) -> Self { let (tx, rx) = channel(); SinWave { frame_t: 1.0 / sample_rate, @@ -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() { @@ -53,7 +54,7 @@ impl<'a> jack::JackHandler for SinWave<'a> { } // Continue as normal - jack::JackControl::Continue + JackControl::Continue } } @@ -66,10 +67,10 @@ fn read_freq() -> Option { } 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(); diff --git a/src/callbacks.rs b/src/callbacks.rs index 8cc83646e..bfad31b47 100644 --- a/src/callbacks.rs +++ b/src/callbacks.rs @@ -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, @@ -200,7 +202,7 @@ unsafe extern "C" fn shutdown(code: j::jack_status_t, unsafe extern "C" fn process(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() } diff --git a/src/client.rs b/src/client.rs index 48ec69554..03936fc59 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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 @@ -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) }) } } @@ -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) }) } } @@ -257,7 +260,7 @@ pub unsafe trait JackClient: Sized { } /// Returns `true` if the port `port` belongs to this client. - fn is_mine(&self, port: &Port) -> bool { + fn is_mine(&self, port: &Port) -> bool { match unsafe { j::jack_port_is_mine(self.client_ptr(), port.port_ptr()) } { 1 => true, _ => false, @@ -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(&mut self, port_name: &str) -> Result, JackErr> { + pub fn register_port(&mut self, + port_name: &str, + port_spec: PS) + -> Result, 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(), @@ -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) }) } } @@ -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(&mut self, _port: Port) -> Result<(), JackErr> { + pub fn unregister_port(&mut self, _port: Port) -> Result<(), JackErr> { unimplemented!(); // port.unregister(self) } diff --git a/src/info.rs b/src/info.rs index 17997012b..20cdf69d7 100644 --- a/src/info.rs +++ b/src/info.rs @@ -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, error: Option) { +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)) }) } diff --git a/src/jack_flags/client_options.rs b/src/jack_flags/client_options.rs index 0fd7ec4ac..6e80d4bc1 100644 --- a/src/jack_flags/client_options.rs +++ b/src/jack_flags/client_options.rs @@ -2,35 +2,38 @@ use jack_sys as j; bitflags! { /// Option flags for opening a JACK client. - /// - /// * `NULL_OPTION` - Equivalent to `ClientOptions::empty()` - /// - /// * `NO_START_SERVER`: Do not automatically start the JACK server when it - /// is not already running. This option is always selected if - /// `$JACK_NO_START_SERVER` is defined in the calling process - /// environment. - /// - /// * `USE_EXACT_NAME`: Use the exact client name requested. Otherwise, - /// JACK automatically generates a unique one if needed. - /// - /// * `SERVER_NAME`: Open with optional `server_name` parameter. TODO: - /// implement - /// - /// * `LOAD_NAME`: Load internal client from optional `load_name`, - /// otherwise use the `client_name`. TODO implement - /// - /// * `LOAD_INIT`: Pass optional `load_init` to `jack_initialize()` - /// entry point of an internal client. TODO: implement - /// - /// * `SESSION_ID`: Pass a SessionID token. This allows the session - /// manager to identify the client again. pub flags ClientOptions: u32 { + /// Equivalent to `ClientOptions::empty()` const NULL_OPTION = j::JackNullOption, + + /// Do not automatically start the JACK server when it is not + /// already running. This option is always selected if + /// `$JACK_NO_START_SERVER` is defined in the calling process + /// environment. const NO_START_SERVER = j::JackNoStartServer, + + /// Use the exact client name requested. Otherwise, JACK + /// automatically generates a unique one if needed. const USE_EXACT_NAME = j::JackUseExactName, + + /// Open with optional `server_name` parameter. + /// + /// TODO: implement const SERVER_NAME = j::JackServerName, + + /// Load internal client from optional `load_name`, otherwise use the `client_name`. + /// + /// TODO: wmedrano const LOAD_NAME = j::JackLoadName, + + /// Pass optional `load_init` to `jack_initialize()` entry + /// point of an internal client. + /// + /// TODO: implement const LOAD_INIT = j::JackLoadInit, + + /// Pass a SessionID token. This allows the session manager to + /// identify the client again. const SESSION_ID = j::JackSessionID, } } diff --git a/src/jack_flags/client_status.rs b/src/jack_flags/client_status.rs index 813608b0e..a39a4963c 100644 --- a/src/jack_flags/client_status.rs +++ b/src/jack_flags/client_status.rs @@ -2,58 +2,57 @@ use jack_sys as j; bitflags! { /// Status flags for JACK clients. - /// - /// * `FAILURE` - Overall operation failed. - /// - /// * `INVALID_OPTION` - The operation contained an invalid or unsupported - /// option. - /// - /// * `NAME_NOT_UNIQUE` - The desired client name was not unique. With the - /// `USE_EXACT_NAME` option this situation is fatal. Otherwise, the name was - /// modified by appending a dash and a two-digit number in the range "-01" - /// to "-99". `Client::name()` will return the exact string that was - /// used. If the specified client_name plus these extra characters would be - /// too long, the open fails instead. - /// - /// * `SERVER_STARTED` - The JACK server was started as a result of this - /// operation. Otherwise, it was running already. In either case the caller - /// is now connected to jackd, so there is no race condition. When the - /// server shuts down, the client will find out. - /// - /// * `SERVER_FAILED` - Unable to connect to the JACK server. - /// - /// * `SERVER_ERROR` - Communication error with the JACK server. - /// - /// * `NO_SUCH_CLIENT` - Requested client does not exist. - /// - /// * `LOAD_FAILURE` - Unable to load internal client - /// - /// * `INIT_FAILURE` - Unable to initialize client - /// - /// * `SHM_FAILURE` - Unable to access shared memory - /// - /// * `VERSION_ERROR` - Client's protocol version does not match - /// - /// * `BACKEND_ERROR` - No documentation found. TODO: dig deeper - /// - /// * `CLIENT_ZOZMBIE` - No documentation found. TODO: dig deeper - /// - /// * `UNKNOWN_ERROR` - Not part of JACK and shouldn't occur ever. /// File an issue if you can get it to appear. pub flags ClientStatus: u32 { + /// Overall operation failed. const FAILURE = j::JackFailure, + + /// The operation contained an invalid or unsupported option. const INVALID_OPTION = j::JackInvalidOption, + + /// The desired client name was not unique. With the + /// `USE_EXACT_NAME` option this situation is + /// fatal. Otherwise, the name was modified by appending a + /// dash and a two-digit number in the range "-01" to + /// "-99". `Client::name()` will return the exact string that + /// was used. If the specified client_name plus these extra + /// characters would be too long, the open fails instead. const NAME_NOT_UNIQUE = j::JackNameNotUnique, + /// The JACK server was started as a result of this + /// operation. Otherwise, it was running already. In either + /// case the caller is now connected to jackd, so there is no + /// race condition. When the server shuts down, the client + /// will find out. const SERVER_STARTED = j::JackServerStarted, + + /// Unable to connect to the JACK server. const SERVER_FAILED = j::JackServerFailed, + + /// Communication error with the JACK server. const SERVER_ERROR = j::JackServerError, + + /// Requested client does not exist. const NO_SUCH_CLIENT = j::JackNoSuchClient, + + /// Unable to load internal client const LOAD_FAILURE = j::JackLoadFailure, + + /// Unable to initialize client const INIT_FAILURE = j::JackInitFailure, + + /// Unable to access shared memory const SHM_FAILURE = j::JackShmFailure, + + /// Client's protocol version does not match const VERSION_ERROR = j::JackVersionError, + + /// No documentation found. TODO: dig deeper const BACKEND_ERROR = j::JackBackendError, + + /// No documentation found. TODO: dig deeper const CLIENT_ZOMBIE = j::JackClientZombie, + + /// An error unknown to JACK occurred. const UNKNOWN_ERROR = 0x2000, // TODO: don't use this } } diff --git a/src/jack_flags/mod.rs b/src/jack_flags/mod.rs index 220f47b05..b2fad945c 100644 --- a/src/jack_flags/mod.rs +++ b/src/jack_flags/mod.rs @@ -6,7 +6,7 @@ pub mod client_options; pub mod client_status; /// Contains `PortFlags` flags which can be used when implementing the -/// `PortData` trait. +/// `PortSpec` trait. pub mod port_flags; pub use client_options::ClientOptions; diff --git a/src/jack_flags/port_flags.rs b/src/jack_flags/port_flags.rs index c99cf7ba1..043c0304b 100644 --- a/src/jack_flags/port_flags.rs +++ b/src/jack_flags/port_flags.rs @@ -2,32 +2,34 @@ use jack_sys as j; bitflags! { /// Flags for specifying port options. - /// - /// * `IS_INPUT` - The port can receive data. - /// - /// * `IS_OUTPUT` - Data can be read from the port. - /// - /// * `IS_PHYSICAL` - Port corresponds to some kind of physical I/O - /// connector. - /// - /// * `CAN_MONITOR` - A call to `jack_port_request_monitor()` makes - /// sense. TODO: implement. Precisely what this means it dependent on the - /// client. A typical result of it being called with `true` as the second - /// argument is that data that would be available from an output port (with - /// `IS_PHYSICAL` set) is sent to a physical output connector as well, so - /// that it can be heard/seen/whatever. - /// - /// * `IS_TERMINAL` - For an input port, the data received by the port will - /// not be passed on or made available at any other port. For output, the - /// data available at the port does not originate from any other port. Audio - /// synthesizers, I/O hardware interface clients, HDR systems are examples - /// of clients that would set this flag for their ports. pub flags PortFlags: u32 { + /// Same as having no flags. const NO_PORT_FLAGS = 0, + + /// The port can receive data. const IS_INPUT = j::JackPortIsInput, + + /// Data can be read from the port. const IS_OUTPUT = j::JackPortIsOutput, + + /// Port corresponds to some kind of physical I/O connector. const IS_PHYSICAL = j::JackPortIsPhysical, + + /// A call to `jack_port_request_monitor()` makes sense. TODO: + /// implement. Precisely what this means it dependent on the + /// client. A typical result of it being called with `true` as + /// the second argument is that data that would be available + /// from an output port (with `IS_PHYSICAL` set) is sent to a + /// physical output connector as well, so that it can be + /// heard/seen/whatever. const CAN_MONITOR = j::JackPortCanMonitor, + + /// For an input port, the data received by the port will not + /// be passed on or made available at any other port. For + /// output, the data available at the port does not originate + /// from any other port. Audio synthesizers, I/O hardware + /// interface clients, HDR systems are examples of clients + /// that would set this flag for their ports. const IS_TERMINAL = j::JackPortIsTerminal, } } diff --git a/src/lib.rs b/src/lib.rs index 5ba4a4a53..1b06b1bc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,40 @@ +//! ```rust +//! extern crate jack; +//! use std::io; +//! +//! fn main() { +//! // Create client +//! let (mut client, _status) = +//! jack::Client::open("rust_jack_simple", +//! jack::client_options::NO_START_SERVER) +//! .unwrap(); +//! +//! // Register ports, that will be used in a callback when new data is available. +//! 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 { +//! 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. +//! let active_client = client.activate(process_callback).unwrap(); +//! +//! // Wait for user input +//! println!("Press enter/return to quit..."); +//! let mut user_input = String::new(); +//! io::stdin().read_line(&mut user_input).ok(); +//! +//! active_client.deactivate().unwrap(); +//! } +//! ``` #[macro_use] extern crate bitflags; extern crate jack_sys; @@ -17,21 +54,17 @@ mod port_impls; pub use callbacks::{ProcessScope, JackHandler}; pub use client::CLIENT_NAME_SIZE; pub use client::{JackClient, Client, ActiveClient, CycleTimes}; -pub use info::set_info_callbacks; +pub use info::{set_info_callback, set_error_callback}; pub use jack_enums::{JackControl, JackErr}; pub use jack_flags::{ClientOptions, client_options}; pub use jack_flags::{ClientStatus, client_status}; pub use jack_flags::{PortFlags, port_flags}; pub use port::{PORT_NAME_SIZE, PORT_TYPE_SIZE}; -pub use port::{Port, PortData, Unowned, UnownedPort}; -pub use port_impls::{AudioIn, AudioOut}; - -/// An endpoint to read JACK 32bit floating point audio. -pub type AudioInPort<'a> = Port>; - -/// An endpoint to output JACK 32bit floating point audio. -pub type AudioOutPort<'a> = Port>; +pub use port::{Port, PortSpec, Unowned, UnownedPort}; +pub use port_impls::{AudioInSpec, AudioInPort, AudioOutSpec, AudioOutPort}; +/// Return JACK's current system time in microseconds, using the JACK +/// clock source. pub fn get_time() -> u64 { unsafe { jack_sys::jack_get_time() } } diff --git a/src/port.rs b/src/port.rs index 33f043a0e..db899e554 100644 --- a/src/port.rs +++ b/src/port.rs @@ -1,9 +1,10 @@ +use libc; + use std::marker::Sized; use std::ffi; use jack_flags::port_flags::PortFlags; use jack_sys as j; use jack_enums::JackErr; -use callbacks::ProcessScope; lazy_static! { /// The maximum string length for port names. @@ -15,19 +16,16 @@ lazy_static! { /// Represents the data of a Port within a `JackHandler::process` /// callback. -pub unsafe trait PortData: Sized { - /// Used by `Port::data()`. - unsafe fn from_ptr(ptr: *mut ::libc::c_void, nframes: u32) -> Self; - +pub unsafe trait PortSpec: Sized { /// String used by JACK upon port creation to identify the port /// type. - fn jack_port_type() -> &'static str; + fn jack_port_type(&self) -> &'static str; /// Flags used by jack upon port creation. - fn jack_flags() -> PortFlags; + fn jack_flags(&self) -> PortFlags; /// Size used by jack upon port creation. - fn jack_buffer_size() -> u64; + fn jack_buffer_size(&self) -> u64; } /// An endpoint to interact with JACK data streams, for audio, midi, @@ -37,25 +35,15 @@ pub unsafe trait PortData: Sized { /// but it should be possible to create a client without the need for /// calling `unsafe` `Port` methods. #[derive(Debug)] -pub struct Port { - port_data: Option, +pub struct Port { + spec: PS, client_ptr: *mut j::jack_client_t, port_ptr: *mut j::jack_port_t, } -unsafe impl Send for Port {} - -impl Port { - pub fn data(&mut self, ps: &ProcessScope) -> &mut PD { - assert!(self.client_ptr == ps.client_ptr(), - "Port data may only be obtained for within the process of the client that \ - created it."); - let n = ps.n_frames(); - let ptr = unsafe { j::jack_port_get_buffer(self.port_ptr(), n) }; - self.port_data = Some(unsafe { PD::from_ptr(ptr, n) }); - self.port_data.as_mut().unwrap() - } +unsafe impl Send for Port {} +impl Port { /// Returns the full name of the port, including the "client_name:" prefix. pub fn name<'a>(&'a self) -> &'a str { unsafe { ffi::CStr::from_ptr(j::jack_port_name(self.port_ptr)).to_str().unwrap() } @@ -196,6 +184,9 @@ impl Port { /// Remove the port from the client, disconnecting any existing connections. /// The port must have been created with the provided client. pub fn unregister(self) -> Result<(), JackErr> { + if self.client_ptr.is_null() { + return Ok(()); + }; let res = unsafe { j::jack_port_unregister(self.client_ptr, self.port_ptr) }; match res { 0 => Ok(()), @@ -204,11 +195,12 @@ impl Port { } /// Create a Port from raw JACK pointers. - pub unsafe fn from_raw(client_ptr: *mut j::jack_client_t, + pub unsafe fn from_raw(spec: PS, + client_ptr: *mut j::jack_client_t, port_ptr: *mut j::jack_port_t) -> Self { Port { - port_data: None, + spec: spec, port_ptr: port_ptr, client_ptr: client_ptr, } @@ -221,28 +213,31 @@ impl Port { pub unsafe fn port_ptr(&self) -> *mut j::jack_port_t { self.port_ptr } + + pub unsafe fn buffer(&self, n_frames: u32) -> *mut libc::c_void { + j::jack_port_get_buffer(self.port_ptr, n_frames) + } } /// Port that holds no data from JACK, though it can be used for /// obtaining information about external ports. #[derive(Debug)] pub struct Unowned; -pub type UnownedPort = Port; -unsafe impl PortData for Unowned { - unsafe fn from_ptr(_ptr: *mut ::libc::c_void, _nframes: u32) -> Self { - Unowned {} - } +/// Port that holds no data from Jack, though it can be used to query +/// information. +pub type UnownedPort = Port; - fn jack_port_type() -> &'static str { +unsafe impl PortSpec for Unowned { + fn jack_port_type(&self) -> &'static str { unreachable!() } - fn jack_flags() -> PortFlags { + fn jack_flags(&self) -> PortFlags { unreachable!() } - fn jack_buffer_size() -> u64 { + fn jack_buffer_size(&self) -> u64 { unreachable!() } } diff --git a/src/port_impls.rs b/src/port_impls.rs index 657e3b455..31813cdb6 100644 --- a/src/port_impls.rs +++ b/src/port_impls.rs @@ -1,67 +1,45 @@ use std::slice; +use std::ops::{Deref, DerefMut}; + use jack_flags::port_flags::{IS_INPUT, IS_OUTPUT, PortFlags}; -use port::PortData; +use port::{Port, PortSpec}; +use callbacks::ProcessScope; -/// `AudioIn` implements the `PortData` trait which, defines an +/// `AudioInSpec` implements the `PortSpec` trait which, defines an /// endpoint for JACK. In this case, it is a readable 32 bit floating /// point buffer for audio. /// -/// `AudioIn::buffer()` is used to gain access the buffer. -#[derive(Debug)] -pub struct AudioIn<'a> { - buff: &'a [f32], -} +/// `AudioInSpec::buffer()` is used to gain access the buffer. +#[derive(Debug, Default)] +pub struct AudioInSpec; -/// `AudioOut` implements the `PortData` trait, which defines an +/// `AudioOutSpec` implements the `PortSpec` trait, which defines an /// endpoint for JACK. In this case, it is a mutable 32 bit floating /// point buffer for audio. /// -/// `AudioOut::buffer()` is used to gain access the buffer. -#[derive(Debug)] -pub struct AudioOut<'a> { - buff: &'a mut [f32], -} +/// `AudioOutSpec::buffer()` is used to gain access the buffer. +#[derive(Debug, Default)] +pub struct AudioOutSpec; -unsafe impl<'a> PortData for AudioOut<'a> { - /// Create an AudioOut instance from a buffer pointer and frame - /// count. This is mostly used by `Port` within a - /// `process` scope. - /// - /// # Arguments - /// - /// * `ptr` - buffer pointer to underlying data. - /// - /// * `nframes` - the size of the buffer. - unsafe fn from_ptr(ptr: *mut ::libc::c_void, nframes: u32) -> Self { - let len = nframes as usize; - let buff = slice::from_raw_parts_mut(ptr as *mut f32, len); - AudioOut { buff: buff } - } - fn jack_port_type() -> &'static str { +unsafe impl<'a> PortSpec for AudioOutSpec { + fn jack_port_type(&self) -> &'static str { "32 bit float mono audio" } - fn jack_flags() -> PortFlags { + fn jack_flags(&self) -> PortFlags { IS_OUTPUT } - fn jack_buffer_size() -> u64 { + fn jack_buffer_size(&self) -> u64 { // Not needed for built in types according to JACK api 0 } } -impl<'a> AudioOut<'a> { - /// Retrieve the underlying buffer for reading. - pub fn buffer(&mut self) -> &mut [f32] { - return self.buff; - } -} - -unsafe impl<'a> PortData for AudioIn<'a> { - /// Create an AudioIn instance from a buffer pointer and frame - /// count. This is mostly used by `Port` within a +unsafe impl PortSpec for AudioInSpec { + /// Create an AudioInSpec instance from a buffer pointer and frame + /// count. This is mostly used by `Port` within a /// `process` scope. /// /// # Arguments @@ -69,29 +47,86 @@ unsafe impl<'a> PortData for AudioIn<'a> { /// * `ptr` - buffer pointer to underlying data. /// /// * `nframes` - the size of the buffer. - unsafe fn from_ptr(ptr: *mut ::libc::c_void, nframes: u32) -> Self { - let len = nframes as usize; - let buff = slice::from_raw_parts(ptr as *const f32, len); - AudioIn { buff: buff } - } - fn jack_port_type() -> &'static str { + fn jack_port_type(&self) -> &'static str { "32 bit float mono audio" } - fn jack_flags() -> PortFlags { + fn jack_flags(&self) -> PortFlags { IS_INPUT } - fn jack_buffer_size() -> u64 { + fn jack_buffer_size(&self) -> u64 { // Not needed for built in types according to JACK api 0 } } -impl<'a> AudioIn<'a> { - /// Retrieve the underlying buffer for writing purposes. - pub fn buffer(&self) -> &[f32] { - self.buff +/// Safetly wrap a `Port`. Can deref into a `&mut[f32]`. +pub struct AudioOutPort<'a> { + _port: &'a mut Port, + buffer: &'a mut [f32], +} + +impl<'a> AudioOutPort<'a> { + /// Wrap a `Port` within a process scope of a client + /// that registered the port. Panics if the port does not belong + /// to the client that created the process. + pub fn new(port: &'a mut Port, ps: &'a ProcessScope) -> Self { + unsafe { assert_eq!(port.client_ptr(), ps.client_ptr()) }; + let buff = unsafe { + slice::from_raw_parts_mut(port.buffer(ps.n_frames()) as *mut f32, + ps.n_frames() as usize) + }; + AudioOutPort { + _port: port, + buffer: buff, + } + } +} + +impl<'a> Deref for AudioOutPort<'a> { + type Target = [f32]; + + fn deref(&self) -> &[f32] { + self.buffer + } +} + +impl<'a> DerefMut for AudioOutPort<'a> { + fn deref_mut(&mut self) -> &mut [f32] { + self.buffer + } +} + + +/// Safetly wrap a `Port`. Derefs into a `&[f32]`. +pub struct AudioInPort<'a> { + _port: &'a Port, + buffer: &'a [f32], +} + +impl<'a> AudioInPort<'a> { + /// Wrap a `Port` within a process scope of a client + /// that registered the port. Panics if the port does not belong + /// to the client that created the process. + pub fn new(port: &'a Port, ps: &'a ProcessScope) -> Self { + unsafe { assert_eq!(port.client_ptr(), ps.client_ptr()) }; + let buff = unsafe { + slice::from_raw_parts(port.buffer(ps.n_frames()) as *const f32, + ps.n_frames() as usize) + }; + AudioInPort { + _port: port, + buffer: buff, + } + } +} + +impl<'a> Deref for AudioInPort<'a> { + type Target = [f32]; + + fn deref(&self) -> &[f32] { + self.buffer } }