bela 0.2.0

Safe Rust API for real-time audio on Bela Gem
Documentation

bela

Safe Rust API for real-time audio on Bela Gem (PocketBeagle 2, aarch64-unknown-linux-gnu), built on the raw FFI bindings in bela-sys.

User code implements the BelaApplication trait — an unsafe trait, because implementing it is a promise that render is real-time safe — and hands an instance to Bela::run:

use bela::{Bela, BelaApplication, Context, Settings};

struct Passthrough;

unsafe impl BelaApplication for Passthrough {
    fn render(&mut self, context: &mut Context) {
        let channels = context
            .audio_in_channels()
            .min(context.audio_out_channels());
        for frame in 0..context.audio_frames() {
            for channel in 0..channels {
                let sample = context.audio_read(frame, channel);
                context.audio_write(frame, channel, sample);
            }
        }
    }
}

fn main() -> Result<(), bela::Error> {
    Bela::run(Passthrough, &Settings::new())
}

Work that must not happen in render — file and network I/O, expensive calculations, anything that allocates or blocks — goes into an AuxiliaryTask, which render triggers with a real-time safe schedule() call. The callback owns its state and shares with render through atomics or a lock-free queue; see examples/aux_task.rs.

Debugging output from the audio thread goes through rt_println!, which formats into a fixed-size stack buffer and hands it to Bela's real-time print function — println! allocates and blocks, and is forbidden in render:

rt_println!("{} blocks, {} underruns", blocks, context.underrun_count());

Whether render fits within its block deadline is answered by Settings::cpu_monitoring, which makes Context::cpu_usage report how much of each block the audio thread uses, and by CpuTimer, which measures one section of render at a time; see examples/cpu.rs. Without them the first sign of running out of headroom is a dropout.

A built binary stays reconfigurable through Bela's standard command-line options — --period, --verbose, --use-analog and the rest, the same set every other way of writing a Bela program accepts. Bela::run_with_args applies them on top of Settings, so the application keeps its own defaults, and print_usage prints the list for a --help of your own:

fn main() -> Result<(), bela::Error> {
    let settings = Settings::new().period_size(32);
    Bela::run_with_args(Passthrough, &settings, std::env::args_os())
}

Options of the program's own are parsed by the program, which hands on what is left; see examples/command_line.rs.

See examples/ for runnable versions and the repository README for project status and cross-compilation instructions.