Skip to content

Commit

Permalink
Merge branch 'main' into testing
Browse files Browse the repository at this point in the history
  • Loading branch information
wmedrano committed Sep 14, 2024
2 parents af91abf + 2923d0f commit 099e264
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 41 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@ jobs:
run: cargo build --verbose --no-default-features
- name: Build (metadata)
run: cargo build --verbose --no-default-features --features metadata
- name: Build (examples)
run: cargo build --verbose --examples
- name: Run Tests
run: cargo nextest run --all-features
- name: Run Doc Tests
run: cargo doc && cargo test --doc
79 changes: 79 additions & 0 deletions docs/contrib/closure_callbacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
layout: page
title: Closure Callbacks
parent: Contrib
permalink: /closure-callbacks
nav_order: 1
---

# Closure Callbacks

Closure callbacks allow you to define functionality inline.

## Process Closure

The typical use case for a process closure involves creating a closure that
contains captures the required state and then activating it.

```rust
// 1. Create the client.
let (client, _status) =
jack::Client::new("silence", jack::ClientOptions::NO_START_SERVER).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.

```rust
// 1. Create the client.
let (client, _status) =
jack::Client::new("silence", jack::ClientOptions::NO_START_SERVER).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();
```
10 changes: 10 additions & 0 deletions docs/contrib/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
layout: page
title: Contrib
permalink: /contrib
nav_order: 3
---

# Contrib

`jack::contrib` contains convenient but optional utilities.
2 changes: 1 addition & 1 deletion docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fn main() {
out_b_p.clone_from_slice(in_b_p);
jack::Control::Continue
};
let process = jack::ClosureProcessHandler::new(process_callback);
let process = jack::contrib::ClosureProcessHandler::new(process_callback);

// 3. Activate the client, which starts the processing.
let active_client = client.activate_async((), process).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/playback_capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn main() {
out_b_p.clone_from_slice(in_b_p);
jack::Control::Continue
};
let process = jack::ClosureProcessHandler::new(process_callback);
let process = jack::contrib::ClosureProcessHandler::new(process_callback);

// Activate the client, which starts the processing.
let active_client = client.activate_async(Notifications, process).unwrap();
Expand Down
2 changes: 1 addition & 1 deletion examples/show_midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ fn main() {

// Activate
let active_client = client
.activate_async((), jack::ClosureProcessHandler::new(cback))
.activate_async((), jack::contrib::ClosureProcessHandler::new(cback))
.unwrap();

// Spawn a non-real-time thread that prints out the midi messages we get.
Expand Down
37 changes: 24 additions & 13 deletions examples/sine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,38 +11,49 @@ fn main() {
jack::Client::new("rust_jack_sine", jack::ClientOptions::NO_START_SERVER).unwrap();

// 2. register port
let mut out_port = client
let out_port = client
.register_port("sine_out", jack::AudioOut::default())
.unwrap();

// 3. define process callback handler
let mut frequency = 220.0;
let sample_rate = client.sample_rate();
let frame_t = 1.0 / sample_rate as f64;
let mut time = 0.0;
let (tx, rx) = bounded(1_000_000);
let process = jack::ClosureProcessHandler::new(
move |_: &jack::Client, ps: &jack::ProcessScope| -> jack::Control {
struct State {
out_port: jack::Port<jack::AudioOut>,
rx: crossbeam_channel::Receiver<f64>,
frequency: f64,
frame_t: f64,
time: f64,
}
let process = jack::contrib::ClosureProcessHandler::with_state(
State {
out_port,
rx,
frequency: 220.0,
frame_t: 1.0 / client.sample_rate() as f64,
time: 0.0,
},
|state, _, ps| -> jack::Control {
// Get output buffer
let out = out_port.as_mut_slice(ps);
let out = state.out_port.as_mut_slice(ps);

// Check frequency requests
while let Ok(f) = rx.try_recv() {
time = 0.0;
frequency = f;
while let Ok(f) = state.rx.try_recv() {
state.time = 0.0;
state.frequency = f;
}

// Write output
for v in out.iter_mut() {
let x = frequency * time * 2.0 * std::f64::consts::PI;
let x = state.frequency * state.time * 2.0 * std::f64::consts::PI;
let y = x.sin();
*v = y as f32;
time += frame_t;
state.time += state.frame_t;
}

// Continue as normal
jack::Control::Continue
},
move |_, _, _| jack::Control::Continue,
);

// 4. Activate the client. Also connect the ports to the system audio.
Expand Down
2 changes: 1 addition & 1 deletion src/client/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::Error;
/// // Create a client and a handler
/// let (client, _status) =
/// jack::Client::new("my_client", jack::ClientOptions::NO_START_SERVER).unwrap();
/// let process_handler = jack::ClosureProcessHandler::new(
/// let process_handler = jack::contrib::ClosureProcessHandler::new(
/// move |_: &jack::Client, _: &jack::ProcessScope| jack::Control::Continue,
/// );
///
Expand Down
2 changes: 1 addition & 1 deletion src/client/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub trait NotificationHandler: Send {
/// pipe so that the rest of the application knows that the JACK client thread has shut down.
///
/// # Safety
/// See https://man7.org/linux/man-pages/man7/signal-safety.7.html for details about
/// See <https://man7.org/linux/man-pages/man7/signal-safety.7.html> for details about
/// what is legal in an async-signal-safe callback.
unsafe fn shutdown(&mut self, _status: ClientStatus, _reason: &str) {}

Expand Down
23 changes: 2 additions & 21 deletions src/client/handler_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,5 @@ impl ProcessHandler for () {

/// Wrap a closure that can handle the `process` callback. This is called every time data from ports
/// is available from JACK.
pub struct ClosureProcessHandler<F: 'static + Send + FnMut(&Client, &ProcessScope) -> Control> {
pub process_fn: F,
}

impl<F> ClosureProcessHandler<F>
where
F: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
{
pub fn new(f: F) -> ClosureProcessHandler<F> {
ClosureProcessHandler { process_fn: f }
}
}

impl<F> ProcessHandler for ClosureProcessHandler<F>
where
F: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
{
fn process(&mut self, c: &Client, ps: &ProcessScope) -> Control {
(self.process_fn)(c, ps)
}
}
#[deprecated = "Prefer using jack::contrib::ClosureProcessHandler directly."]
pub type ClosureProcessHandler<F> = crate::contrib::ClosureProcessHandler<(), F>;
1 change: 1 addition & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ pub use self::client_options::ClientOptions;
pub use self::client_status::ClientStatus;
pub use self::common::CLIENT_NAME_SIZE;

#[allow(deprecated)]
pub use self::handler_impls::ClosureProcessHandler;
116 changes: 116 additions & 0 deletions src/contrib/closure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::{Client, Control, Frames, ProcessHandler, ProcessScope};

/// Wrap a closure that can handle the `process` callback. This is called every time data from ports
/// is available from JACK.
pub struct ClosureProcessHandler<T, F> {
pub state: T,
pub callbacks: F,
}

impl<ProcessCallback> ClosureProcessHandler<(), ProcessCallback>
where
ProcessCallback: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
{
/// Create a new `jack::ProcessHandler` with the given process callback.
///
/// ```rust
/// // Run one cycle of processing
/// let mut has_run = false;
/// let handler = jack::contrib::ClosureProcessHandler::new(move |_client, _process_scope| {
/// if has_run {
/// jack::Control::Quit
/// } else {
/// has_run = true;
/// jack::Control::Continue
/// }
/// });
/// ```
pub fn new(process_callback: ProcessCallback) -> Self {
ClosureProcessHandler {
state: (),
callbacks: process_callback,
}
}
}

impl<ProcessCallback> ProcessHandler for ClosureProcessHandler<(), ProcessCallback>
where
ProcessCallback: 'static + Send + FnMut(&Client, &ProcessScope) -> Control,
{
fn process(&mut self, c: &Client, ps: &ProcessScope) -> Control {
(self.callbacks)(c, ps)
}
}

pub struct ProcessCallbacks<ProcessCallback, BufferCallback> {
process: ProcessCallback,
buffer: BufferCallback,
}

impl<T, ProcessCallback, BufferCallback>
ClosureProcessHandler<T, ProcessCallbacks<ProcessCallback, BufferCallback>>
where
T: Send,
ProcessCallback: 'static + Send + FnMut(&mut T, &Client, &ProcessScope) -> Control,
BufferCallback: 'static + Send + FnMut(&mut T, &Client, Frames) -> Control,
{
/// Create a new `jack::ProcessHandler` with some state.
///
/// ```rust
/// // 1. Create the client.
/// let (client, _status) = jack::Client::new("silence", jack::ClientOptions::NO_START_SERVER).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| -> jack::Control {
/// 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| -> jack::Control {
/// 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();
/// ```
pub fn with_state(
state: T,
process_callback: ProcessCallback,
buffer_callback: BufferCallback,
) -> Self {
ClosureProcessHandler {
state,
callbacks: ProcessCallbacks {
process: process_callback,
buffer: buffer_callback,
},
}
}
}

impl<T, ProcessCallback, BufferCallback> ProcessHandler
for ClosureProcessHandler<T, ProcessCallbacks<ProcessCallback, BufferCallback>>
where
T: Send,
ProcessCallback: 'static + Send + FnMut(&mut T, &Client, &ProcessScope) -> Control,
BufferCallback: 'static + Send + FnMut(&mut T, &Client, Frames) -> Control,
{
fn process(&mut self, c: &Client, ps: &ProcessScope) -> Control {
(self.callbacks.process)(&mut self.state, c, ps)
}

fn buffer_size(&mut self, c: &Client, size: Frames) -> Control {
(self.callbacks.buffer)(&mut self.state, c, size)
}
}
13 changes: 11 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@
//! callback. For example, `Port<AudioIn>::as_mut_slice` returns a audio buffer that can be written
//! to.

#[allow(deprecated)]
pub use crate::client::ClosureProcessHandler;
pub use crate::client::{
AsyncClient, Client, ClientOptions, ClientStatus, ClosureProcessHandler, CycleTimes,
InternalClientID, NotificationHandler, ProcessHandler, ProcessScope, CLIENT_NAME_SIZE,
AsyncClient, Client, ClientOptions, ClientStatus, CycleTimes, InternalClientID,
NotificationHandler, ProcessHandler, ProcessScope, CLIENT_NAME_SIZE,
};
pub use crate::jack_enums::{Control, Error, LatencyType};
pub use crate::logging::{set_logger, LoggerType};
Expand Down Expand Up @@ -68,6 +70,13 @@ mod properties;
mod ringbuffer;
mod transport;

/// A collection of useful but optional functionality.
pub mod contrib {
mod closure;

pub use closure::ClosureProcessHandler;
}

#[cfg(test)]
mod tests;

Expand Down

0 comments on commit 099e264

Please sign in to comment.