Skip to content

Commit

Permalink
Adapt examples
Browse files Browse the repository at this point in the history
  • Loading branch information
alisomay committed Nov 28, 2024
1 parent 12edec6 commit 28b0a62
Show file tree
Hide file tree
Showing 30 changed files with 767 additions and 344 deletions.
18 changes: 6 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libpd-rs"
version = "0.3.0"
version = "0.2.0"
authors = ["alisomay <[email protected]>"]
edition = "2021"
license = "BSD-3-Clause"
Expand All @@ -11,11 +11,7 @@ repository = "https://github.com/alisomay/libpd-rs"
documentation = "https://docs.rs/libpd-rs/latest/libpd_rs/#"
keywords = ["puredata", "libpd", "audio", "midi", "bindings"]
categories = ["multimedia"]
exclude = [
"tests/*",
"assets/favicon/*",
"assets/logo_*"
]
exclude = ["tests/*", "assets/favicon/*", "assets/logo_*"]

[lib]
name = "libpd_rs"
Expand All @@ -28,18 +24,20 @@ edition = "2021"
crate-type = ["lib"]

[dependencies]
libpd-sys = "0.3"
# libpd-sys = "0.3"
libpd-sys = { git = "https://github.com/alisomay/libpd-sys.git", branch = "main" }
thiserror = "2"
libffi = "3.0.0"
tempfile = "3.3.0"
embed-doc-image = "0.1.4"

[dev-dependencies]
cpal = "0.15.2"
cpal = "0.15.3"
sys-info = "0.9.1"
nannou = "0.19"
nannou_audio = "0.19"
rand = "0.8.5"
serial_test = "3"

# For local development,
# [patch.crates-io]
Expand All @@ -48,7 +46,3 @@ rand = "0.8.5"
# For local development,
# [patch."https://github.com/alisomay/libpd-sys"]
# libpd-sys = { path = "../libpd-sys" }




8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ This one is the exact same program which is provided in the main `README.md` fil
cargo run --example simple
```

## `communicate`

This one is the exact same program which is provided in the docs as the second example.

```sh
cargo run --example communicate
```

## `with_nannou`

[nannou](https://github.com/nannou-org/nannou) is a fantastic creative coding framework for Rust.
Expand Down
155 changes: 155 additions & 0 deletions examples/communicate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use libpd_rs::{
functions::{receive::on_float, send::send_list_to, util::calculate_ticks},
Pd,
};
use sys_info::loadavg;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize cpal
// This could have been another cross platform audio library
// basically anything which gets you the audio callback of the os.
let host = cpal::default_host();

// Currently we're only going to output to the default device
let device = host.default_output_device().unwrap();

// Using the default config
let config = device.default_output_config()?;

// Let's get the default configuration from the audio driver.
let sample_rate = config.sample_rate().0 as i32;
let output_channels = config.channels() as i32;

// Initialize libpd with that configuration,
// with no input channels since we're not going to use them.
let mut pd = Pd::init_and_configure(0, output_channels, sample_rate)?;
let ctx = pd.audio_context();

// Let's evaluate another pd patch.
// We could have opened a `.pd` file also.
pd.eval_patch(
r#"
#N canvas 832 310 625 448 12;
#X obj 18 27 r cpu_load;
#X obj 55 394 s response;
#X obj 13 261 *~;
#X obj 112 240 vline~;
#X obj 118 62 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
-1;
#X obj 14 395 dac~;
#X obj 50 299 sig~;
#X floatatom 50 268 5 0 0 0 - - -;
#X obj 13 228 phasor~ 120;
#X obj 139 61 metro 2000;
#X obj 139 38 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1
1;
#X obj 18 52 unpack f f;
#X obj 14 362 *~ 2;
#X obj 14 336 vcf~ 12;
#X obj 139 12 loadbang;
#X msg 118 86 1 8 \, 0 0 10;
#X obj 149 197 expr (480 + 80) * ($f1 - 8) / (4 - 16) + 480;
#X obj 29 128 * 20;
#X obj 167 273 expr (520 + 120) * ($f1 - 5) / (12 - 5) + 120;
#X connect 0 0 11 0;
#X connect 2 0 13 0;
#X connect 3 0 2 1;
#X connect 4 0 15 0;
#X connect 6 0 13 1;
#X connect 7 0 6 0;
#X connect 8 0 2 0;
#X connect 9 0 15 0;
#X connect 10 0 9 0;
#X connect 11 0 16 0;
#X connect 11 0 18 0;
#X connect 11 1 17 0;
#X connect 12 0 5 0;
#X connect 12 0 5 1;
#X connect 13 0 12 0;
#X connect 14 0 10 0;
#X connect 15 0 3 0;
#X connect 16 0 9 1;
#X connect 17 0 1 0;
#X connect 17 0 13 2;
#X connect 18 0 7 0;
"#,
)?;

// Here we are registering a listener (hook in libpd lingo) for
// float values which are received from the pd patch.
on_float(|source, value| {
if source == "response" {
print!("\r");
print!("Pd says that the q value of the vcf~ is: {value}");
}
});

// Pd can send data to many different endpoints at a time.
// This is why we need to declare our subscription to one or more first.
// In this case we're subscribing to one, but it could have been many,
pd.subscribe_to("response")?;

// Build the audio stream.
let output_stream = device.build_output_stream(
&config.into(),
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
// Provide the ticks to advance per iteration for the internal scheduler.
let ticks = calculate_ticks(output_channels, data.len() as i32);

// Here if we had an input buffer
// we could have modified it to do pre-processing.

// To receive messages from the pd patch we need to read the ring buffers
// filled by the pd patch repeatedly to check if there are messages there.
// Audio callback is a nice place to do that.
ctx.receive_messages_from_pd();

// Process audio, advance internal scheduler.
ctx.process_float(ticks, &[], data);

// Here we could have done post processing after
// pd processed our output buffer in place.
},
|err| eprintln!("an error occurred on stream: {}", err),
None,
)?;

// Turn audio processing on
pd.activate_audio(true)?;

// Run the stream
output_stream.play()?;

// This program does not terminate.
// You would need to explicitly quit it.
loop {
// We sample in 2 hz.
std::thread::sleep(std::time::Duration::from_millis(500));

// Read the average load of the cpu.
let load = loadavg()?;

let one_minute_cpu_load_average = load.one;
let five_minutes_cpu_load_average = load.five;

// Lists are one of the types we can send to pd.
// Although pd allows for heterogeneous lists,
// even if we're not using them heterogeneously in this example,
// we still need to send it as a list of Atoms.

// Atom is an encapsulating type in pd to unify
// various types of data together under the same umbrella.
// Check out `libpd_rs::types` module for more details.

// Atoms have From trait implemented for them for
// floats and strings.
send_list_to(
"cpu_load",
&[
one_minute_cpu_load_average.into(),
five_minutes_cpu_load_average.into(),
],
)?;
}
}
3 changes: 2 additions & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize libpd with that configuration,
// with no input channels since we're not going to use them.
let mut pd = Pd::init_and_configure(0, output_channels, sample_rate)?;
let ctx = pd.audio_context();

// Let's evaluate a pd patch.
// We could have opened a `.pd` file also.
Expand Down Expand Up @@ -49,7 +50,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Here if we had an input buffer we could have modified it to do pre-processing.

// Process audio, advance internal scheduler.
libpd_rs::functions::process::process_float(ticks, &[], data);
ctx.process_float(ticks, &[], data);

// Here we could have done post processing after pd processed our output buffer in place.
},
Expand Down
3 changes: 2 additions & 1 deletion examples/with_nannou/bubble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl Bubble {
}

/// Transforms the voice message of the bubble to send to pure data.
pub fn pack_message(&self) -> Vec<libpd_rs::types::Atom> {
pub fn pack_message(&self) -> Vec<libpd_rs::atom::Atom> {
self.state
.message
.into_iter()
Expand Down Expand Up @@ -212,6 +212,7 @@ impl Bubble {

// Collision with the floor!
if distance_to_floor < self.properties.r * 2.0 {
model.pd.set_as_current();
// On collision we tell the right voice to play with the right parameters in pd.
libpd_rs::functions::send::send_list_to("bubble_collision", &self.pack_message())
.unwrap();
Expand Down
16 changes: 11 additions & 5 deletions examples/with_nannou/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod bubble;

use bubble::Bubble;
use libpd_rs::PdAudioContext;
use nannou::prelude::*;
use nannou_audio as audio;
use nannou_audio::Buffer;
Expand All @@ -15,7 +16,7 @@ fn main() {
// This data structure will be shared across nannou functions.
pub struct Model {
pd: libpd_rs::Pd,
output_stream: audio::Stream<()>,
output_stream: audio::Stream<PdAudioContext>,
gravity: f32,
bubbles: RefCell<Vec<Bubble>>,
bubble_count: usize,
Expand Down Expand Up @@ -50,9 +51,13 @@ fn model(app: &App) -> Model {
// .into_iter()
// .find(|d| d.name().unwrap() == "BlackHole 16ch");

let pd = libpd_rs::Pd::init_and_configure(0, channels as i32, sample_rate as i32).unwrap();
pd.set_as_current();
let audio_ctx = pd.audio_context();

// Start the stream registering our audio callback.
let output_stream = audio_host
.new_output_stream(())
.new_output_stream(audio_ctx)
// Uncomment to pick another audio device.
// .device(device.unwrap())
.channels(channels)
Expand All @@ -69,7 +74,7 @@ fn model(app: &App) -> Model {
// This data structure will be shared across nannou functions.
let mut model = Model {
// Initialize pd
pd: libpd_rs::Pd::init_and_configure(0, channels as i32, sample_rate as i32).unwrap(),
pd,
output_stream,
gravity: 0.8,
bubbles: RefCell::new(vec![]),
Expand Down Expand Up @@ -138,15 +143,16 @@ impl Model {

// This is where we process audio.
// We hand over all tasks to our pd patch!
fn audio_callback(_: &mut (), buffer: &mut Buffer) {
fn audio_callback(pd_audio_context: &mut PdAudioContext, buffer: &mut Buffer) {
let ticks =
libpd_rs::functions::util::calculate_ticks(buffer.channels() as i32, buffer.len() as i32);
libpd_rs::functions::process::process_float(ticks, &[], buffer);
pd_audio_context.process_float(ticks, &[], buffer);
}

// This is where we draw repeatedly!
fn view(app: &App, model: &Model, frame: Frame) {
// Let's poll pd messages here, for every frame.
model.pd.set_as_current();
libpd_rs::functions::receive::receive_messages_from_pd();

let background_color = nannou::color::srgb8(238, 108, 77);
Expand Down
Loading

0 comments on commit 28b0a62

Please sign in to comment.