Skip to content

Commit

Permalink
Merge pull request #22 from alisomay/multi-instance
Browse files Browse the repository at this point in the history
Multi Instance Support
  • Loading branch information
alisomay authored Nov 28, 2024
2 parents b7b4baf + 0c2321b commit d8ba4b0
Show file tree
Hide file tree
Showing 46 changed files with 2,965 additions and 1,876 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- uses: actions/checkout@v3
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: sudo apt install libasound2-dev && sudo apt install alsa-utils
- run: cargo build --verbose
- run: cargo build --verbose --release
- run: cargo test -- --nocapture

- uses: actions-rs/[email protected]
Expand All @@ -35,7 +35,7 @@ jobs:
token: ${{secrets.CODECOV_TOKEN}}

- name: Archive code coverage results
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: code-coverage-report
path: cobertura.xml
Expand All @@ -52,7 +52,7 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo build --verbose --release
- run: cargo test -- --nocapture

build_and_test_windows:
Expand All @@ -67,5 +67,5 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo build --verbose --release
- run: cargo test -- --nocapture
45 changes: 20 additions & 25 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
[package]
name = "libpd-rs"
version = "0.1.10"
version = "0.2.0"
authors = ["alisomay <[email protected]>"]
edition = "2021"
license = "BSD-3-Clause"
description = "Safe rust abstractions over libpd"
readme = "README.md"
homepage = "https://github.com/alisomay/libpd-rs"
repository = "https://github.com/alisomay/libpd-rs"
documentation = "https://docs.rs/libpd-rs/0.1.9/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"
path = "src/lib.rs"
test = true
doctest = true
bench = false
doc = true
edition = "2021"
crate-type = ["lib"]

[dependencies]
libpd-sys = "0.2"
thiserror = "1.0.30"
# 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.18"
nannou_audio = "0.18"
nannou = "0.19"
nannou_audio = "0.19"
rand = "0.8.5"
serial_test = "3"

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

[lib]
name = "libpd_rs" # The name of the target.
path = "src/lib.rs" # The source file of the target.
test = true # Is tested by default.
doctest = true # Documentation examples are tested by default.
bench = false # Is benchmarked by default.
doc = true # Is documented by default.
proc-macro = false # Set to `true` for a proc-macro library.
edition = "2021" # The edition of the target.
crate-type = ["lib"] # The crate types to generate.


2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,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::process::process_float(ticks, &[], data);
libpd_rs::functions::process::process_float(ticks, &[], data);

// Here we could have done post processing after pd processed our output buffer in place.
},
Expand Down
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(),
],
)?;
}
}
10 changes: 6 additions & 4 deletions examples/simple.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use libpd_rs::convenience::PdGlobal;
use libpd_rs::Pd;

fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize cpal
Expand All @@ -19,7 +19,8 @@ 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 = PdGlobal::init_and_configure(0, output_channels, sample_rate)?;
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 All @@ -43,12 +44,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
&config.into(),
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
// Provide the ticks to advance per iteration for the internal scheduler.
let ticks = libpd_rs::convenience::calculate_ticks(output_channels, data.len() as i32);
let ticks =
libpd_rs::functions::util::calculate_ticks(output_channels, data.len() as i32);

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

// Process audio, advance internal scheduler.
libpd_rs::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
6 changes: 4 additions & 2 deletions 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,8 +212,10 @@ 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::send::send_list_to("bubble_collision", &self.pack_message()).unwrap();
libpd_rs::functions::send::send_list_to("bubble_collision", &self.pack_message())
.unwrap();

// Physics
self.properties.dy = -self.properties.dy;
Expand Down
Loading

0 comments on commit d8ba4b0

Please sign in to comment.