Quickstart
JACK Background
The jack crate provides Rust bindings to the JACK
API. Typically, a JACK server is started and clients connect to it to gain
access to audio and midi inputs and outputs, along with synchronization
mechanisms and APIs.

The JACK server is responsible for:
- Discovering and exposing audio and midi devices.
- Synchronizing audio and midi data.
- Managing the processing graph.
JACK clients are responsible for:
- Registering themselves with JACK.
- Registering callbacks to provide audio/midi data to the JACK server.
JACk Server
There are two Linux implementations tested with the jack crate.
- JACK2 - The primary implementation of JACK. Can use realtime scheduling and
alsa under the hood to provide the best latency. The JACK2 server may be
started through the
jackdCLI orqjackctlGUI. - Pipewire - The most commonly used audio & video stream server for Linux. May
not provide the best latency, but is very convenient to use. Pipewire itself
has its own API, but it also exposes a JACK server.
pw-jackis often used to patch in Pipewire's JACK implementation. For example, you can run your Rust JACK app with:pw-jack cargo run.
JACK Clients
This is where the jack crate comes in. Once a JACK server is running on the
system, you can run your client to produce audio. Here is a simple jack
program that can take inputs and forward them to outputs.
fn main() { // 1. Create client let (client, _status) = jack::Client::new("rust_jack_simple", jack::ClientOptions::default()).unwrap(); // 2. Register ports. They will be used in a callback that will be // called when new data is available. let in_a: jack::Port<jack::AudioIn> = client .register_port("rust_in_l", jack::AudioIn::default()) .unwrap(); let in_b: jack::Port<jack::AudioIn> = client .register_port("rust_in_r", jack::AudioIn::default()) .unwrap(); let mut out_a: jack::Port<jack::AudioOut> = client .register_port("rust_out_l", jack::AudioOut::default()) .unwrap(); let mut out_b: jack::Port<jack::AudioOut> = client .register_port("rust_out_r", jack::AudioOut::default()) .unwrap(); let process_callback = move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control { let out_a_p = out_a.as_mut_slice(ps); let out_b_p = out_b.as_mut_slice(ps); let in_a_p = in_a.as_slice(ps); let in_b_p = in_b.as_slice(ps); out_a_p.clone_from_slice(in_a_p); out_b_p.clone_from_slice(in_b_p); jack::Control::Continue }; let process = jack::contrib::ClosureProcessHandler::new(process_callback); // 3. Activate the client, which starts the processing. let active_client = client.activate_async((), process).unwrap(); // 4. Wait for user input to quit println!("Press enter/return to quit..."); let mut user_input = String::new(); io::stdin().read_line(&mut user_input).ok(); // 5. Not needed as the async client will cease processing on `drop`. if let Err(err) = active_client.deactivate() { eprintln!("JACK exited with error: {err}"); } }
Connecting Ports
- Run the JACK client using the Pipewire JACK server.
pw-jack cargo run - View the JACK processing graph. This can be done by using the
qjackctlGUI and clicking Graphs.pw-jack qjackctl - Connect the ports as you see fit! In the below, a webcam microphone is
connected to the speakers. Warning, do not try this at home! Connecting a
microphone input to a speaker output may produce a terrible echo.

Features
The Rust features for the jack crate are defined in
https://github.com/RustAudio/rust-jack/blob/main/Cargo.toml. To see the
documentation for Rust features in general, see the Rust
Book.
Disabling Default Features
The jack crate ships with a reasonable set of default features. To enable just
a subset of features, set default-features to false and select only the
desired features.
jack = { version = "..", default-features = false, features = ["log"] }
log
Default: Yes
If the log crate should be used to handle JACK
logging. Requires setting up a logging implementation to make messages
available.
dynamic_loading
Default: Yes
Load libjack at runtime as opposed to the standard dynamic linking. This is
preferred as it allows pw-jack to intercept the loading at runtime to provide
the Pipewire JACK server implementation.
Logging
JACK can communicate info and error messages. By default, the log
crate is hooked up to output
messages. However, other logging methods can be used with the
set_logger function.
No Logging
Logging from jack can be disabled entirely by setting the logger to None.
#![allow(unused)] fn main() { jack::set_logger(jack::LoggerType::None); }
Log Crate (default)
The log crate is the default logger if the log feature is enabled, which is
enabled by default. The log crate provides a facade for logging; it provides
macros to perform logging, but another mechanism or crate is required to
actually perform the logging.
In the example below, we use the env_logger
crate to display logging for info and
error severity level messages.
#![allow(unused)] fn main() { env_logger::builder().filter(None, log::LevelFilter::Info).init(); // JACK may log things to `info!` or `error!`. let (client, _status) = jack::Client::new("rust_jack_simple", jack::ClientOptions::default()).unwrap(); }
Stdio
If the log feature is not enabled, then jack will log info messages to
stdout and error messages to stderr. These usually show up in the terminal.
#![allow(unused)] fn main() { jack::set_logger(jack::LoggerType::Stdio); }
Custom
jack::LoggerType::Custom can be used to set a custom logger. Here is
stdout/stderr implemented as a custom logger:
fn main() { jack::set_logger(jack::LoggerType::Custom{info: stdout_handler, error: stderr_handler}); ... } unsafe extern "C" fn stdout_handler(msg: *const libc::c_char) { let res = std::panic::catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() { Ok(msg) => println!("{}", msg), Err(err) => println!("failed to log to JACK info: {:?}", err), }); if let Err(err) = res { eprintln!("{err:?}"); std::mem::forget(err); // Prevent from rethrowing panic. } } unsafe extern "C" fn stderr_handler(msg: *const libc::c_char) { let res = std::panic::catch_unwind(|| match std::ffi::CStr::from_ptr(msg).to_str() { Ok(msg) => eprintln!("{}", msg), Err(err) => eprintln!("failed to log to JACK error: {:?}", err), }); if let Err(err) = res { eprintln!("{err:?}"); std::mem::forget(err); // Prevent from rethrowing panic. } }
Contrib
jack::contrib contains convenient but optional utilities.
Closure Callbacks
Closure callbacks allow you to define functionality inline.
Process Closure
Audio and midi processing can be defined through closures. This involves:
- Creating a closure that captures the appropriate state (including JACK ports) and
- Activating it within a client.
#![allow(unused)] fn main() { // 1. Create the client. let (client, _status) = jack::Client::new("silence", jack::ClientOptions::default()).unwrap(); // 2. Define the state. let mut output = client.register_port("out", jack::AudioOut::default()); let silence_value = 0.0; // 3. Define the closure. Use `move` to capture the required state. let process_callback = move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control { output.as_mut_slice(ps).fill(silence_value); jack::Control::Continue }; // 4. Start processing. let process = jack::contrib::ClosureProcessHandler::new(process_callback); let active_client = client.activate_async((), process).unwrap(); }
State + Process Closure + Buffer Closure
jack::contrib::ClosureProcessHandler also allows defining a buffer size
callback that can share state with the process callback. The buffer size
callback is useful as it allows the handler to adapt to any changes in the
buffer size.
#![allow(unused)] fn main() { // 1. Create the client. let (client, _status) = jack::Client::new("silence", jack::ClientOptions::default()).unwrap(); // 2. Define the state. struct State { silence: Vec<f32>, output: jack::Port<jack::AudioOut>, } let state = State { silence: Vec::new(), output: client .register_port("out", jack::AudioOut::default()) .unwrap(), }; // 3. Define the state and closure. let process_callback = |state: &mut State, _: &jack::Client, ps: &jack::ProcessScope| { state .output .as_mut_slice(ps) .copy_from_slice(state.silence.as_slice()); jack::Control::Continue }; let buffer_callback = |state: &mut State, _: &jack::Client, len: jack::Frames| { state.silence = vec![0f32; len as usize]; jack::Control::Continue }; // 4. Start processing. let process = jack::contrib::ClosureProcessHandler::with_state(state, process_callback, buffer_callback); let active_client = client.activate_async((), process).unwrap(); }