bela-sys 0.8.0

Raw FFI bindings to the Bela core API (libbela) for Bela Gem
Documentation

bela-sys

Raw FFI bindings to the Bela core API (libbela) for Bela Gem on PocketBeagle 2 (aarch64-unknown-linux-gnu).

Vendored headers

vendor/bela/ contains Bela.h, GPIOcontrol.h and Utilities.h (the include closure of wrapper.h) plus the upstream LICENSE. The provenance is recorded in vendor/bela/SOURCE. These files are LGPL 3.0 (see vendor/bela/LICENSE); the rest of the crate is MIT OR Apache-2.0.

vendor/ne10/ is a second tree and a different kind of thing: it holds NE10_dsp.h and NE10_types.h from the board, and nothing generates from them. src/ne10.rs declares NE10's four FFT functions by hand, so those two headers are only the baseline cargo xtask check-vendor --board diffs a board against, together with the library identity in vendor/ne10/SOURCE — a build id and a hash, because every build of the library calls itself libNE10.so.10. Wiring them into a bindgen run later would be a change of purpose rather than a tidy-up. abi/ne10_abi.c is the same check from the other side, at build time and without a board: it asserts that the headers still describe what src/ne10.rs declares. NE10 is BSD-3-Clause, whose notice is at the top of each of those headers. See docs/fft.md.

The headers are taken from the board, not from BelaPlatform/Bela: the Bela Gem image ships Bela 1.18.0, which is newer than any published branch (see docs/board-facts.md).

scripts/update-vendor.sh --board          # from root@bela.local
scripts/update-vendor.sh <branch|commit>  # from upstream git

Because the pin follows a board image rather than a released version, a new image moves the headers on the board without changing anything here. After updating one, compare the two:

cargo xtask check-vendor --board          # against root@bela.local

It reports the BELA_*_VERSION macros on both sides and diffs every vendored file against the board's copy, exiting non-zero on drift — which is the signal to re-run the update script and regenerate the bindings below. It needs a board, so CI cannot run it.

Why vendored files instead of a git submodule

  • The include closure is three files (~70 KB); a submodule would drag in the whole upstream repository (IDE, examples, PRU firmware, history) for every clone and CI run.
  • src/bindings.rs is committed, and vendoring keeps "these headers" and "the bindings generated from them" atomic in one commit — a submodule can drift ahead of the generated code, and its bumps show up as opaque hash changes instead of reviewable header diffs.
  • The pin tracks the exact Bela version shipped on the board, which does not correspond to any published upstream commit. File copies can come from anywhere; a submodule can only point at upstream commits.
  • A plain git clone always builds — no --recursive, no submodule initialisation failure modes.

The trade-off is that provenance rests on scripts/update-vendor.sh recording the source in vendor/bela/SOURCE, rather than on git itself.

Regenerating the bindings

src/bindings.rs is generated but committed, so building this crate requires neither libclang nor an aarch64 sysroot. Regenerate it after updating the vendored headers:

cargo xtask bindgen --sysroot <dir>   # or set BELA_SYSROOT

The sysroot is the one synced from the board (see docs/cross-compile.md); bindgen needs it for the libc headers Bela.h includes.

The MIDI shim

shim/midi.h and shim/midi.cpp are a C surface over Bela's Midi class, which is C++ and lives in libbelaextra. They are the one part of this crate that is neither generated nor a declaration of Bela's own C API: Bela ships a partial C surface in libraries/Midi/Midi_c.h, and the shim is that file written again with output, port listing, and error reporting that distinguishes a port that opened from one that did not. docs/midi.md records why, and what the class does on the audio thread.

build.rs compiles it with the cc crate when the sysroot carries Bela's libraries/Midi sources, and skips it — with a warning — when it does not, so that a check without a sysroot still works. The compiler comes from BELA_CXX, or from BELA_CC when that ends in gcc, and the archiver follows the compiler's name; see docs/cross-compile.md. That choice is the one piece of the build script with more than one answer, so it lives in shim_compiler.rs and is tested from src/lib.rscargo test does not build a build script.

The class is LGPL 3.0, like the vendored headers; the shim reaches it by linking libbelaextra.so dynamically.

Linking

build.rs emits the link flags for libbela on device targets: the library search paths from docs/board-facts.md (prefixed with BELA_SYSROOT when cross-compiling) and -lbelaextra -lbela plus the C++ runtime and transitive dependencies (seasocks, evl, stdc++) that Rust does not link on its own.

The order of those two matters: libbelaextra.so needs symbols from libbela.so without naming it in its own DT_NEEDED, and rustc links with --as-needed, which drops a libbela that appears before the library needing it.

On non-device targets it emits nothing, so host builds and cargo check/clippy for the target work without a sysroot.

Sysroot-specific compiler-driver arguments

A device link also needs --sysroot, -B and -Wl,-rpath-link arguments derived from BELA_SYSROOT — see docs/cross-compile.md for what each is for. build.rs publishes them as links metadata (links = "bela") rather than adding them to its own link line, because they belong to the final link — the application's — not to this crate's. bela (links = "bela_relay") relays them to its own dependents, since links metadata reaches only an immediate dependent and an application is not one of bela-sys's.

An application does not read this crate's metadata directly — see the bela README for the DEP_BELA_RELAY_LINK_ARGS_* an application's own build.rs reads, and docs/cross-compile.md for the direct linker = setting that replaces scripts/aarch64-bela-linker.sh for anyone depending on the published crates.