From 804c2e221c002e1e3d206e78df38c64fedd456d5 Mon Sep 17 00:00:00 2001 From: Nikolai Rodionov Date: Thu, 27 Nov 2025 22:58:33 +0100 Subject: [PATCH] Add a dummy jack example Signed-off-by: Nikolai Rodionov --- Cargo.lock | 23 +++++++++ Cargo.toml | 2 +- examples/jack-sine/Cargo.toml | 8 +++ examples/jack-sine/README.md | 2 + examples/jack-sine/src/main.rs | 94 ++++++++++++++++++++++++++++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 examples/jack-sine/Cargo.toml create mode 100644 examples/jack-sine/README.md create mode 100644 examples/jack-sine/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index f989b10..997ffa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "engine" version = "0.1.0" @@ -59,6 +74,14 @@ dependencies = [ "log", ] +[[package]] +name = "jack-sine" +version = "0.1.0" +dependencies = [ + "crossbeam-channel", + "jack", +] + [[package]] name = "jack-sys" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index b0894bb..9d1460c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] resolver = "3" -members = ["engine", "lib"] +members = ["engine", "examples/jack-sine", "lib"] [workspace.dependencies] diff --git a/examples/jack-sine/Cargo.toml b/examples/jack-sine/Cargo.toml new file mode 100644 index 0000000..a068714 --- /dev/null +++ b/examples/jack-sine/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jack-sine" +version = "0.1.0" +edition = "2024" + +[dependencies] +crossbeam-channel = "0.5.15" +jack = "0.13.3" diff --git a/examples/jack-sine/README.md b/examples/jack-sine/README.md new file mode 100644 index 0000000..a9c5041 --- /dev/null +++ b/examples/jack-sine/README.md @@ -0,0 +1,2 @@ +Set this on mac ❯ export DYLD_FALLBACK_LIBRARY_PATH="$(brew --prefix jack)/lib:${DYLD_FALLBACK_LIBRARY_PATH:-}" + diff --git a/examples/jack-sine/src/main.rs b/examples/jack-sine/src/main.rs new file mode 100644 index 0000000..cee0003 --- /dev/null +++ b/examples/jack-sine/src/main.rs @@ -0,0 +1,94 @@ +//! Sine wave generator with frequency configuration exposed through standard +//! input. + +use crossbeam_channel::bounded; +use std::io; +use std::str::FromStr; + +fn main() { + // 1. open a client + let (client, _status) = + jack::Client::new("rust_jack_sine", jack::ClientOptions::default()).unwrap(); + + // 2. register port + let out_port = client + .register_port("sine_out", jack::AudioOut::default()) + .unwrap(); + + // 3. define process callback handler + let (tx, rx) = bounded(1_000_000); + struct State { + out_port: jack::Port, + rx: crossbeam_channel::Receiver, + 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 = state.out_port.as_mut_slice(ps); + + // Check frequency requests + 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 = state.frequency * state.time * 2.0 * std::f64::consts::PI; + let y = x.sin(); + *v = y as f32; + 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. + let active_client = client.activate_async((), process).unwrap(); + active_client + .as_client() + .connect_ports_by_name("rust_jack_sine:sine_out", "system:playback_1") + .unwrap(); + active_client + .as_client() + .connect_ports_by_name("rust_jack_sine:sine_out", "system:playback_2") + .unwrap(); + // processing starts here + + // 5. wait or do some processing while your handler is running in real time. + println!("Enter an integer value to change the frequency of the sine wave."); + while let Some(f) = read_freq() { + tx.send(f).unwrap(); + } + + // 6. Optional deactivate. Not required since active_client will deactivate on + // drop, though explicit deactivate may help you identify errors in + // deactivate. + if let Err(err) = active_client.deactivate() { + eprintln!("JACK exited with error: {err}"); + }; +} + +/// Attempt to read a frequency from standard in. Will block until there is +/// user input. `None` is returned if there was an error reading from standard +/// in, or the retrieved string wasn't a compatible u16 integer. +fn read_freq() -> Option { + let mut user_input = String::new(); + match io::stdin().read_line(&mut user_input) { + Ok(_) => u16::from_str(user_input.trim()).ok().map(|n| n as f64), + Err(_) => None, + } +}