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 and hands an instance
to Bela::run:
use ;
;
One or four threads, one application model
Bela can render a block on all four of a Bela Gem's cores, and it does
so by calling render on every thread at once, for the same block,
over the same buffers — it partitions nothing itself. BelaApplication
is shaped for that, and a single render thread is the same shape with
one of everything:
- the application is shared as
&selfwhile rendering, so whateverrendermutates lives in aRenderState, one per thread, built bycreate_render_statebefore audio starts; RenderContextreads the whole block but writes onlyaudio_frame_range(), and the ranges tile the block exactly;render_preandrender_postbracket the parallel section on the main audio thread, with the whole block and every state to themselves — where per-block preparation and mixing down belong.
Settings::thread_count chooses how many threads; nothing else about
an application changes with it. See
examples/parallel.rs, which measures that the
work really was divided, and
docs/multithreaded-rendering.md
for what Bela does and how it was measured.
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!;
Whether render fits within its block deadline is answered by
Settings::cpu_monitoring, which makes BlockContext::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:
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.
Downstream setup
Building a device binary needs three compiler-driver arguments derived
from the Bela sysroot (--sysroot, -B, -Wl,-rpath-link; see
docs/cross-compile.md for what each is
for). bela-sys publishes them and this crate relays them, because
links metadata reaches only an immediate dependent
— an application depending on bela is not one of bela-sys's. An
application therefore needs its own small build.rs to turn what
bela relayed into link arguments for its own binary:
// build.rs
and .cargo/config.toml names the compiler driver directly:
[]
= "aarch64-unknown-linux-gnu-gcc" # or aarch64-linux-gnu-gcc, gcc, ...
No file to copy from this repository, and no executable bit to
preserve. See docs/cross-compile.md for
compiler installation and the toolchain rules bela-sys uses to build
its MIDI shim with a compiler matching this linker.