Skip to content

Commit d8ba4b0

Browse files
authored
Merge pull request #22 from alisomay/multi-instance
Multi Instance Support
2 parents b7b4baf + 0c2321b commit d8ba4b0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2965
-1876
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- uses: actions/checkout@v3
2222
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
2323
- run: sudo apt install libasound2-dev && sudo apt install alsa-utils
24-
- run: cargo build --verbose
24+
- run: cargo build --verbose --release
2525
- run: cargo test -- --nocapture
2626

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

3737
- name: Archive code coverage results
38-
uses: actions/upload-artifact@v1
38+
uses: actions/upload-artifact@v4
3939
with:
4040
name: code-coverage-report
4141
path: cobertura.xml
@@ -52,7 +52,7 @@ jobs:
5252
steps:
5353
- uses: actions/checkout@v3
5454
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
55-
- run: cargo build --verbose
55+
- run: cargo build --verbose --release
5656
- run: cargo test -- --nocapture
5757

5858
build_and_test_windows:
@@ -67,5 +67,5 @@ jobs:
6767
steps:
6868
- uses: actions/checkout@v3
6969
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
70-
- run: cargo build --verbose
70+
- run: cargo build --verbose --release
7171
- run: cargo test -- --nocapture

Cargo.toml

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,43 @@
11
[package]
22
name = "libpd-rs"
3-
version = "0.1.10"
3+
version = "0.2.0"
44
authors = ["alisomay <[email protected]>"]
55
edition = "2021"
66
license = "BSD-3-Clause"
77
description = "Safe rust abstractions over libpd"
88
readme = "README.md"
99
homepage = "https://github.com/alisomay/libpd-rs"
1010
repository = "https://github.com/alisomay/libpd-rs"
11-
documentation = "https://docs.rs/libpd-rs/0.1.9/libpd_rs/#"
11+
documentation = "https://docs.rs/libpd-rs/latest/libpd_rs/#"
1212
keywords = ["puredata", "libpd", "audio", "midi", "bindings"]
1313
categories = ["multimedia"]
14-
exclude = [
15-
"tests/*",
16-
"assets/favicon/*",
17-
"assets/logo_*"
18-
]
14+
exclude = ["tests/*", "assets/favicon/*", "assets/logo_*"]
15+
16+
[lib]
17+
name = "libpd_rs"
18+
path = "src/lib.rs"
19+
test = true
20+
doctest = true
21+
bench = false
22+
doc = true
23+
edition = "2021"
24+
crate-type = ["lib"]
1925

2026
[dependencies]
21-
libpd-sys = "0.2"
22-
thiserror = "1.0.30"
27+
# libpd-sys = "0.3"
28+
libpd-sys = { git = "https://github.com/alisomay/libpd-sys.git", branch = "main" }
29+
thiserror = "2"
2330
libffi = "3.0.0"
2431
tempfile = "3.3.0"
2532
embed-doc-image = "0.1.4"
2633

2734
[dev-dependencies]
28-
cpal = "0.15.2"
35+
cpal = "0.15.3"
2936
sys-info = "0.9.1"
30-
nannou = "0.18"
31-
nannou_audio = "0.18"
37+
nannou = "0.19"
38+
nannou_audio = "0.19"
3239
rand = "0.8.5"
40+
serial_test = "3"
3341

3442
# For local development,
3543
# [patch.crates-io]
@@ -38,16 +46,3 @@ rand = "0.8.5"
3846
# For local development,
3947
# [patch."https://github.com/alisomay/libpd-sys"]
4048
# libpd-sys = { path = "../libpd-sys" }
41-
42-
[lib]
43-
name = "libpd_rs" # The name of the target.
44-
path = "src/lib.rs" # The source file of the target.
45-
test = true # Is tested by default.
46-
doctest = true # Documentation examples are tested by default.
47-
bench = false # Is benchmarked by default.
48-
doc = true # Is documented by default.
49-
proc-macro = false # Set to `true` for a proc-macro library.
50-
edition = "2021" # The edition of the target.
51-
crate-type = ["lib"] # The crate types to generate.
52-
53-

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
123123
// Here if we had an input buffer we could have modified it to do pre-processing.
124124

125125
// Process audio, advance internal scheduler.
126-
libpd_rs::process::process_float(ticks, &[], data);
126+
libpd_rs::functions::process::process_float(ticks, &[], data);
127127

128128
// Here we could have done post processing after pd processed our output buffer in place.
129129
},

examples/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ This one is the exact same program which is provided in the main `README.md` fil
1414
cargo run --example simple
1515
```
1616

17+
## `communicate`
18+
19+
This one is the exact same program which is provided in the docs as the second example.
20+
21+
```sh
22+
cargo run --example communicate
23+
```
24+
1725
## `with_nannou`
1826

1927
[nannou](https://github.com/nannou-org/nannou) is a fantastic creative coding framework for Rust.

examples/communicate.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
2+
use libpd_rs::{
3+
functions::{receive::on_float, send::send_list_to, util::calculate_ticks},
4+
Pd,
5+
};
6+
use sys_info::loadavg;
7+
8+
fn main() -> Result<(), Box<dyn std::error::Error>> {
9+
// Initialize cpal
10+
// This could have been another cross platform audio library
11+
// basically anything which gets you the audio callback of the os.
12+
let host = cpal::default_host();
13+
14+
// Currently we're only going to output to the default device
15+
let device = host.default_output_device().unwrap();
16+
17+
// Using the default config
18+
let config = device.default_output_config()?;
19+
20+
// Let's get the default configuration from the audio driver.
21+
let sample_rate = config.sample_rate().0 as i32;
22+
let output_channels = config.channels() as i32;
23+
24+
// Initialize libpd with that configuration,
25+
// with no input channels since we're not going to use them.
26+
let mut pd = Pd::init_and_configure(0, output_channels, sample_rate)?;
27+
let ctx = pd.audio_context();
28+
29+
// Let's evaluate another pd patch.
30+
// We could have opened a `.pd` file also.
31+
pd.eval_patch(
32+
r#"
33+
#N canvas 832 310 625 448 12;
34+
#X obj 18 27 r cpu_load;
35+
#X obj 55 394 s response;
36+
#X obj 13 261 *~;
37+
#X obj 112 240 vline~;
38+
#X obj 118 62 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1
39+
-1;
40+
#X obj 14 395 dac~;
41+
#X obj 50 299 sig~;
42+
#X floatatom 50 268 5 0 0 0 - - -;
43+
#X obj 13 228 phasor~ 120;
44+
#X obj 139 61 metro 2000;
45+
#X obj 139 38 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1
46+
1;
47+
#X obj 18 52 unpack f f;
48+
#X obj 14 362 *~ 2;
49+
#X obj 14 336 vcf~ 12;
50+
#X obj 139 12 loadbang;
51+
#X msg 118 86 1 8 \, 0 0 10;
52+
#X obj 149 197 expr (480 + 80) * ($f1 - 8) / (4 - 16) + 480;
53+
#X obj 29 128 * 20;
54+
#X obj 167 273 expr (520 + 120) * ($f1 - 5) / (12 - 5) + 120;
55+
#X connect 0 0 11 0;
56+
#X connect 2 0 13 0;
57+
#X connect 3 0 2 1;
58+
#X connect 4 0 15 0;
59+
#X connect 6 0 13 1;
60+
#X connect 7 0 6 0;
61+
#X connect 8 0 2 0;
62+
#X connect 9 0 15 0;
63+
#X connect 10 0 9 0;
64+
#X connect 11 0 16 0;
65+
#X connect 11 0 18 0;
66+
#X connect 11 1 17 0;
67+
#X connect 12 0 5 0;
68+
#X connect 12 0 5 1;
69+
#X connect 13 0 12 0;
70+
#X connect 14 0 10 0;
71+
#X connect 15 0 3 0;
72+
#X connect 16 0 9 1;
73+
#X connect 17 0 1 0;
74+
#X connect 17 0 13 2;
75+
#X connect 18 0 7 0;
76+
"#,
77+
)?;
78+
79+
// Here we are registering a listener (hook in libpd lingo) for
80+
// float values which are received from the pd patch.
81+
on_float(|source, value| {
82+
if source == "response" {
83+
print!("\r");
84+
print!("Pd says that the q value of the vcf~ is: {value}");
85+
}
86+
});
87+
88+
// Pd can send data to many different endpoints at a time.
89+
// This is why we need to declare our subscription to one or more first.
90+
// In this case we're subscribing to one, but it could have been many,
91+
pd.subscribe_to("response")?;
92+
93+
// Build the audio stream.
94+
let output_stream = device.build_output_stream(
95+
&config.into(),
96+
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
97+
// Provide the ticks to advance per iteration for the internal scheduler.
98+
let ticks = calculate_ticks(output_channels, data.len() as i32);
99+
100+
// Here if we had an input buffer
101+
// we could have modified it to do pre-processing.
102+
103+
// To receive messages from the pd patch we need to read the ring buffers
104+
// filled by the pd patch repeatedly to check if there are messages there.
105+
// Audio callback is a nice place to do that.
106+
ctx.receive_messages_from_pd();
107+
108+
// Process audio, advance internal scheduler.
109+
ctx.process_float(ticks, &[], data);
110+
111+
// Here we could have done post processing after
112+
// pd processed our output buffer in place.
113+
},
114+
|err| eprintln!("an error occurred on stream: {}", err),
115+
None,
116+
)?;
117+
118+
// Turn audio processing on
119+
pd.activate_audio(true)?;
120+
121+
// Run the stream
122+
output_stream.play()?;
123+
124+
// This program does not terminate.
125+
// You would need to explicitly quit it.
126+
loop {
127+
// We sample in 2 hz.
128+
std::thread::sleep(std::time::Duration::from_millis(500));
129+
130+
// Read the average load of the cpu.
131+
let load = loadavg()?;
132+
133+
let one_minute_cpu_load_average = load.one;
134+
let five_minutes_cpu_load_average = load.five;
135+
136+
// Lists are one of the types we can send to pd.
137+
// Although pd allows for heterogeneous lists,
138+
// even if we're not using them heterogeneously in this example,
139+
// we still need to send it as a list of Atoms.
140+
141+
// Atom is an encapsulating type in pd to unify
142+
// various types of data together under the same umbrella.
143+
// Check out `libpd_rs::types` module for more details.
144+
145+
// Atoms have From trait implemented for them for
146+
// floats and strings.
147+
send_list_to(
148+
"cpu_load",
149+
&[
150+
one_minute_cpu_load_average.into(),
151+
five_minutes_cpu_load_average.into(),
152+
],
153+
)?;
154+
}
155+
}

examples/simple.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
2-
use libpd_rs::convenience::PdGlobal;
2+
use libpd_rs::Pd;
33

44
fn main() -> Result<(), Box<dyn std::error::Error>> {
55
// Initialize cpal
@@ -19,7 +19,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
1919

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

2425
// Let's evaluate a pd patch.
2526
// We could have opened a `.pd` file also.
@@ -43,12 +44,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
4344
&config.into(),
4445
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
4546
// Provide the ticks to advance per iteration for the internal scheduler.
46-
let ticks = libpd_rs::convenience::calculate_ticks(output_channels, data.len() as i32);
47+
let ticks =
48+
libpd_rs::functions::util::calculate_ticks(output_channels, data.len() as i32);
4749

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

5052
// Process audio, advance internal scheduler.
51-
libpd_rs::process::process_float(ticks, &[], data);
53+
ctx.process_float(ticks, &[], data);
5254

5355
// Here we could have done post processing after pd processed our output buffer in place.
5456
},

examples/with_nannou/bubble.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ impl Bubble {
140140
}
141141

142142
/// Transforms the voice message of the bubble to send to pure data.
143-
pub fn pack_message(&self) -> Vec<libpd_rs::types::Atom> {
143+
pub fn pack_message(&self) -> Vec<libpd_rs::atom::Atom> {
144144
self.state
145145
.message
146146
.into_iter()
@@ -212,8 +212,10 @@ impl Bubble {
212212

213213
// Collision with the floor!
214214
if distance_to_floor < self.properties.r * 2.0 {
215+
model.pd.set_as_current();
215216
// On collision we tell the right voice to play with the right parameters in pd.
216-
libpd_rs::send::send_list_to("bubble_collision", &self.pack_message()).unwrap();
217+
libpd_rs::functions::send::send_list_to("bubble_collision", &self.pack_message())
218+
.unwrap();
217219

218220
// Physics
219221
self.properties.dy = -self.properties.dy;

0 commit comments

Comments
 (0)