alisomay/libpd-rs

Safe rust abstractions over libpd-sys

Miller Puckette in the 1990s for creating interactive computer music and multimedia works

libpd-rs

Safe rust abstractions over libpd-sys.

Pure Data (Pd) is a visual programming language developed by Miller Puckette in the 1990s for creating interactive computer music and multimedia works. While Puckette is the main author of the program, Pd is an open-source project with a large developer base working on new extensions. It is released under BSD-3-Clause.

Though pd is designed as a desktop application, libpd is an open source project which exposes it as a C library opening the possibility to embed the functionality of pd to any platform which C can compile to.

libpd-rs aims to bring libpd to the Rust ecosystem. It aims to expose the full functionality of libpd with some extra additions such as bundling commonly used externals and addition of extra functionality for increased ease of use.

It is thoroughly documented, well tested and enriched with various examples to get you started right away.

Now let's make some sound! 🔔


Add the following dependencies to your Cargo.toml:

[dependencies]
libpd-rs = "0.1.9"
cpal = "0.13"

Paste the code into your main.rs:

⚠️ Warning ⚠️: This example will produce audio, so please keep your volume at a reasonable level for safety.

use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use libpd_rs::convenience::PdGlobal;

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 = PdGlobal::init_and_configure(0, output_channels, sample_rate)?;

    // Let's evaluate a pd patch.
    // We could have opened a `.pd` file also.
    // This patch would play a sine wave at 440hz.
    pd.eval_patch(
        r#"
    #N canvas 577 549 158 168 12;
    #X obj 23 116 dac~;
    #X obj 23 17 osc~ 440;
    #X obj 23 66 *~ 0.1;
    #X obj 81 67 *~ 0.1;
    #X connect 1 0 2 0;
    #X connect 1 0 3 0;
    #X connect 2 0 0 0;
    #X connect 3 0 0 1;
        "#,
    )?;

    // 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 = libpd_rs::convenience::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);

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

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

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

    // Wait a bit for listening..
    std::thread::sleep(std::time::Duration::from_secs(5));

    // Turn audio processing off
    pd.activate_audio(false)?;

    // Pause the stream
    output_stream.pause()?;

    // Close the patch
    pd.close_patch()?;

    // Leave
    Ok(())
}

This is just the tip of the iceberg about what you can do with libpd.

The patch we had just evaluated would look like this in pd desktop application:

Running the examples and tests

After cloning the repository, in the repository root run:

cargo run --example <name of the example>

e.g.

cargo run --example with_nannou

Please check the README on examples for more information.

For the tests, you may run cargo test directly.

Next steps

Please check the examples and tests directory if you're learning better when reading code.

Or if you would like to dive in to documentation please go ahead.

Resources

  • Pure Data
    • https://puredata.info/
    • https://forum.pdpatchrepo.info/
    • http://www.pd-tutorial.com/
    • https://www.worldscientific.com/worldscibooks/10.1142/6277
    • https://mitpress.mit.edu/books/designing-sound
    • https://www.soundonsound.com/techniques/pure-data-introduction
    • collection of resources in modwiggler
    • collection of resources in reddit
    • a guide to writing pd externals in C
  • libpd
  • Audio in Rust
    • https://github.com/kfrncs/awesome-rust-audio
    • https://github.com/RustAudio
    • https://www.theaudioprogrammer.com/discord

Road map

  • Multi hooks support
  • Multi instance support
  • Support for Android and IOS
  • Enrich examples with nice patches and add also examples with bevy and nannou.

Support

  • Desktop

    • macOS:
      • x86_64
      • aarch64
    • linux:
      • x86_64
      • aarch64
    • windows:
      • msvc
        • x86_64
        • aarch64 (not tested but should work)
      • gnu
        • x86_64 (not tested but should work)
        • aarch64 (not tested but should work)
  • Mobile

    • iOS (not yet but will be addressed)
    • Android (not yet but will be addressed)
  • Web (not yet but will be addressed)

List of bundled externals

The way to add externals to libpd is to compile and statically link them.

libpd-rs will be bundling some of the essential and commonly used externals in pure data. This list will be growing as we add more externals.

If you have ideas please consider writing an answer to this post.

  • moog~
  • freeverb~

Contributing

  • Be friendly and productive
  • Follow common practice open source contribution culture
  • Rust code of conduct applies

Thank you 🙏

Similar projects

  • https://github.com/x37v/puredata-rust
  • https://github.com/wavejumper/pd-external-rs
  • https://github.com/x37v/puredata-rust/tree/HEAD/pd-sys

Last words

Generative or algorithmic music is a powerful tool for exploration, pumps up creativity and goes very well together with traditional music making approaches also.

Making apps which produce meaningful sound is difficult, I wish that this crate would ease your way on doing that and make complicated audio ideas in apps accessible to more people.

Issues

Collection of the latest Issues

alisomay

alisomay

Comment Icon0

There are some not very intuitive behaviour how open_patch reacts to relative paths and single file names.

A quick re-visit is needed.

alisomay

alisomay

documentation
Comment Icon2
  • Which badges should I use?
  • Should there be a logo?
  • How should the README be organized to look also nice in crates.io?
alisomay

alisomay

enhancement
Comment Icon0

I'd like to add multiple examples in an examples folder for people to start fast and easy.

Some ideas are,

  • Sine (hello world) ✅
  • Messaging with pd
  • Writing and reading from arrays
  • Usage of some less commonly used functions
  • Embedding to nannou ✅
  • Embedding to Bevy
  • Examples with specifically with cpal
  • Examples with other cross platform audio libraries.

I'd like most of the examples to contain nice music. Also I would like that all of them to produce audio.

Information - Updated Jul 25, 2022

Stars: 40
Forks: 0
Issues: 4

Repositories & Extras

ears is a simple library to play sounds and music in Rust

ears is a simple library to play sounds and music in MSYS2 according to the instructions

ears is a simple library to play sounds and music in Rust

A vector graphics renderer using OpenGL with a Rust &amp; C API

A Rust example can be found in examples/quickstart

A vector graphics renderer using OpenGL with a Rust &amp; C API

Drew's Foundation bindings for Rust

This library binds (some subset of) Apple Foundation to Rust

Drew's Foundation bindings for Rust

WASM Game Of Life

This is the code I have written as a result of following the exercises in the Rust WASM book which can be found recommended way...

WASM Game Of Life

Pure rust building block for distributed systems

The objective of bifrost is to build a solid foundation for distributed systems in rust

Pure rust building block for distributed systems

Rulox Interpreter

Rust implementation of the Lox language interpreter found in the

Rulox Interpreter

Overdamped Langevin dynamics with periodic boundary conditions in Rust

This code is meant to be run on very small systems (N &lt; 100) due to the lack of internal neighbor data

Overdamped Langevin dynamics with periodic boundary conditions in Rust

A Rust library for safely extracting JSON fields while also checking data bounds

Obtain an SQL schema from the Database being used (it should contain the table creation SQL)

A Rust library for safely extracting JSON fields while also checking data bounds

A fork of the Rust implementation of FROST: Flexible Round-Optimised Schnorr Threshold signatures by Chelsea...

A fork of the Rust implementation of and adapted to support additional Identifiable Cheating Entity property and Static group keys

A fork of the Rust implementation of FROST: Flexible Round-Optimised Schnorr Threshold signatures by Chelsea...
Facebook Instagram Twitter GitHub Dribbble
Privacy