Expand description
Safe Rust API for real-time audio on Bela Gem.
Built on top of the raw FFI bindings in bela_sys. User code
implements the BelaApplication trait and hands an instance to
Bela::run:
use bela::{BelaApplication, RenderContext, SetupContext, ThreadInfo};
struct Passthrough;
impl BelaApplication for Passthrough {
type RenderState = ();
fn create_render_state(&mut self, _thread: ThreadInfo, _context: &SetupContext) {}
fn render(&self, _state: &mut (), context: &mut RenderContext) {
let channels = context
.audio_in_channels()
.min(context.audio_out_channels());
// This thread's share of the block; with one render thread,
// all of it.
for frame in context.audio_frame_range() {
for channel in 0..channels {
let sample = context.audio_read(frame, channel);
context.audio_write(frame, channel, sample);
}
}
}
}Bela::run(Passthrough, &Settings::new().period_size(64)) then runs
it until something asks it to stop. That line is not part of the
example above because Bela only exists on the device target, and
this doc test is compiled on the host — where the application is,
which is the part that has to keep up with the trait.
examples/passthrough.rs is the whole program, and the rest of
examples/ covers the crate a piece at a time.
§One application model, one or four threads
Bela can render a block on several threads at once — a Bela Gem has
four cores — and it does so by calling render on all of them
simultaneously, for the same block, over the same buffers. Nothing
is partitioned on the C side.
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; RenderContextreads the whole block but writes only this thread’saudio_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 render 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. What Bela actually does, and
how it was measured, is in docs/multithreaded-rendering.md.
§Everything else
Work that must not happen in render — file and network I/O,
expensive calculations, anything that allocates or blocks — belongs
in an AuxiliaryTask, which render triggers with a real-time
safe schedule call.
MIDI arrives through MidiInput, opened in setup with a name
from midi_ports and read once per block from
render_pre: taking a message is a
ring read, and what it changes is what render then plays. The
messages are MidiMessage, and what they carry — Note,
Velocity, MidiChannel and the rest — are types of their own
rather than bytes, because a note and a velocity are two numbers in
the same range.
Sending goes through MidiOutput, which hands each render thread
a MidiSender to keep in its render state. Sending queues a
message and schedules a task; the writing to Bela happens on that
task’s thread rather than on the audio thread, for reasons
docs/midi.md sets out. What has no block left to be sent in — a
closing all-notes-off — goes through
MidiOutput::send from
cleanup.
Spectra come from RealFft, a plan of one FftLength built in
setup — where a failure can still be reported — and moved into
the render state, one per thread. Transforming allocates nothing:
forward writes FftBins a render callback
can read, and inverse takes them back to
samples with the scaling already applied. On a Bela Gem a
1024-point transform costs about 10 µs, which docs/fft.md
measures alongside why the shortest length this crate offers is 8.
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.
Whether rendering 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 at a
time. Without them the first sign of running out of headroom is a
dropout, after the fact.
The codec’s own volume controls — the line out level, the headphone
level and the gain of the preamplifier ahead of the ADC — are set
through the Bela handle, with
set_line_out_level and its siblings.
They can be set before audio starts as well as while it runs, which
is what Bela::until_stopped leaves room for.
A built binary stays reconfigurable through Bela’s standard
command-line options — --period, --verbose, --use-analog and
the rest, the same ones 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.
Being reconfigurable from outside means being given configurations
the program was not written for, so an application that needs
particular ones says so in
validate_settings. It is
asked about the ResolvedSettings — everything applied, the
command line included — before the audio system is built, and what
it refuses comes back as Error::SettingsRefused with the
process untouched. That is the only place an application can
decline: setup runs inside
Bela_initAudio with the hardware already up, and refusing from
there leaves the process unable to build another audio system.
One corner of the C core API has no safe accessors here on purpose:
the Multiplexer Capelet, multiplexerAnalogRead and
multiplexerChannelForFrame. The Capelet is an accessory for the
original Bela cape and cannot be attached to a Gem, so what a
reading means — which Capelet pin it came from — cannot be checked
on the board this crate is measured against. docs/board-facts.md
records what a Gem does with the multiplexer settings regardless.
What the program is running on is Board::detect and
Version::running — the board libbela says it found, and the
version of the library it found it with. Both answer before there is
an audio system, so a program built and measured against one board
can say so and decline rather than fail partway through bringing one
up, and an examples/board_info run is the first thing to ask for
from anyone reporting a problem.
Bela itself calls into libbela and therefore only exists when
compiling for the device target (aarch64-unknown-linux-gnu); the
rest of the crate — BelaApplication, the contexts, Settings
— is target-independent and unit-tested on the host.
Binaries should set panic = "abort" in their release profile: a
panic crossing the audio callback boundary aborts the process either
way, and abort avoids shipping unwinding machinery.
Re-exports§
pub use bela_sys;
Macros§
- rt_
print - Prints to the Bela console in a real-time safe way, with
format!-style arguments and no trailing newline. - rt_
println - Prints to the Bela console in a real-time safe way, with
format!-style arguments and a trailing newline.
Structs§
- Auxiliary
Task - A task that runs a callback on a lower-priority thread when the audio thread asks it to.
- Bela
- Owns an initialised Bela audio system and the application driven by it.
- Block
Context - What
render_preandrender_postsee: the whole block, with nothing else running. - Cleanup
Context - What
cleanupsees: the same audio configurationSetupContextdescribed, after the audio thread has been joined. - Control
Value - What a controller moved to.
- Controller
- Which controller a
ControlChangeis about — 1 is the modulation wheel, 7 volume, 64 the sustain pedal. - CpuSection
- Measures for as long as it is alive; see
CpuTimer::measure. - CpuTimer
- Measures a section of
renderchosen by the application. - CpuUsage
- A reading of the CPU monitoring counters.
- FftBin
- One frequency bin: a complex number, single precision.
- FftLength
- A transform length: a power of two from
MINtoMAX. - Midi
Channel - Which of the sixteen MIDI channels a message arrived on.
- Midi
Input - A MIDI port opened for input.
- Midi
Output - A MIDI port opened for output.
- Midi
Sender - One render thread’s end of the output queue.
- Note
- Which key a note message is about, where 60 is middle C.
- Paired
Io - A single borrow over one domain’s whole-block input and this view’s output range.
- Pitch
Bend - Where the pitch wheel is: fourteen bits, 0 to 16383.
- Pressure
- How hard a key or a channel is being pressed after the note started.
- Priority
- A real-time priority Bela accepts for an auxiliary task: 0 to 99.
- Program
- Which sound a
ProgramChangeselects. - RealFft
- A real-to-complex FFT and its inverse, for one transform length.
- Render
Context - What
rendersees: the whole block to read, this thread’s share of it to write. - Resolved
Settings - The settings an audio system is about to be built with, as
validate_settingssees them. - Settings
- Overrides applied on top of Bela’s default initialisation settings.
- Setup
Context - What
setupandcreate_render_statesee: the audio configuration, before any audio has been rendered. - Thread
Info - Which render thread a
RenderStateis being made for. - Velocity
- How hard a key was pressed or released.
- Version
- A Bela version: major, minor and bugfix.
Enums§
- Board
- A board, as libbela’s
BelaHwnames it. - Channel
- Which channel a level or gain applies to.
- Detect
Mode - How
Board::detectshould go about finding out. - Error
- Errors returned by the Bela audio system lifecycle.
- Midi
Message - A MIDI message, as Bela’s parser hands it over.
- PinMode
- Direction of a digital (GPIO) pin. All pins begin as inputs, which
is what
PinMode::default()is.
Constants§
- MAX_
DECIBELS - Largest magnitude, in decibels, a level or gain may have.
- MAX_
MONITORED_ PERIOD_ SIZE - Largest period size for which libbela runs
renderon the same thread that updates the monitoring counters. - MESSAGE_
CAPACITY - Maximum length in bytes of a single message, excluding the terminator.
Traits§
- Bela
Application - A Bela application: user code driven by the audio system callbacks.
- Callback
Context - Proof that the caller is inside one of the Bela callbacks.
Functions§
- constrain
- Clips
xto the rangemin_val..max_val. - map
- Linearly rescales
xfrom the rangein_min..in_maxtoout_min..out_max. Values outside the input range are extrapolated. - midi_
ports - Every MIDI port ALSA reports, by the name that opens it.
- print_
args - Prints formatted arguments in a real-time safe way, without a trailing newline.
- print_
usage - Prints Bela’s standard options to standard error.
- println_
args - Prints formatted arguments in a real-time safe way, followed by a newline.
- request_
stop - Requests that the audio system stop.
- stop_
requested - Whether a stop has been requested by the stop button, IDE, or
request_stop.