Understanding Real Time
Bela is a real-time system. This page explains what “real-time” means, and what this means for you.
Defining “real-time” and “hard real-time” systems
In computing, “real-time” refers to any system that guarantees response within a specified time frame. This is called the “real-time constraint”. Bela’s real-time constraint is processing audio frames at audio rate, which is 44,100 frames per second.
There are different types of real-time systems, often called soft, firm, or hard. A firm or soft real-time system can miss a few deadlines, but performance will degrade if too many deadlines are missed. A hard real-time system is one that is subject to stringent deadlines, and must meet those deadlines in order to function. Bela is a hard real-time system, meaning that it must process audio frames at audio rate in order to function.
Though Bela is powerful enough to meet these time constraints under most conditions, this doesn’t mean that you can do any amount of processing on Bela and it will always hit deadlines. For example, if you want to apply filters or audio effects that require a significant amount of calculation (such as phase vocoders), you may need to consider how to optimise your code to achieve the performnace you want.
Timing and I/O in Bela
All I/O in Bela – audio, analog and digital – is synchronised to the same master clock. This means that audio and sensor data is always strongly aligned with no latency or jitter. It also means that programming conventions for handling I/O are somewhat different from other platforms.
Functions in a Bela project
Every Bela project must declare three functions:
setup(BelaContext *context, void *userData) runs once at the beginning of the program, before any audio or sensor processing has taken place. It is analogous to the
setup() function in the Arduino environment. Your code here should allocate any needed memory and initialise program state.
render(BelaContext *context, void *userData) runs regularly, in a loop as long as the program is running. It is called by the Bela system. Your code within
render() should process one buffer worth of audio, analog and digital data, using the information stored in the
context data structure. For further information, see [[ Example projects and tutorials ]].
cleanup(BelaContext *context, void *userData) runs once at the end of the program, after audio and sensor processing has finished. Use it to clean up resources allocated in
Working with I/O
All audio processing, as well as all analog and digital I/O, should be handled from
render(). Every call to
render() represents a small slice of time:
On Bela, analog and digital I/Os are sampled at a constant rate, regardless of whether you use the samples or not. All I/O is held within the
context data structure. For example, if the buffer size is 4 audio frames and you are using 8 analog channels, then the structure will contain:
context->audioInis an array of 4 frames * 2 channels = 8 audio input samples. These samples were read by the hardware before
context->audioOutis an array of 4 frames * 2 channels = 8 audio output samples which your program should write. They will be sent to the hardware after
context->analogInis an array of 2 frames * 8 channels = 16 analog input samples. (2 frames because analog I/O runs at half the sample rate of audio I/O.) These samples were read by the hardware before
context->analogOutis an array of 2 frames * 8 channels = 16 analog output samples which your program should write.
context->digitalis an array of 4 frames. Digital frames are encoded as 32-bit words where bits 31-16 are the input or output value and bits 15-0 set the direction. While these can be set directly using bitwise operators, it is recommended that you use I/O functions below.
This is a contrast to Arduino and similar environments. For example, calling
analogRead() on Arduino would cause the analog pin to be read at the moment the function runs. The rest of the code will stop until the analog to digital conversion has finished and
analogRead() returns. On Bela, the analog input data is already present in the buffer. Calling
analogRead() will retrieve the sample from the buffer. Your code does not need to wait for the conversion to happen.
Understanding the “frame” argument
All the I/O functions in Bela (
analogWrite(), etc.) take an argument for which frame to read or write the I/O pin. The frame indicates exactly what time the read/write should take place. The number of frames in any given call to
render() depends on the Bela buffer size (which can be changed on the settings menu in the IDE or with the
- p command line argument). By default, the buffer size is 8 analog frames or 16 audio frames. Valid frame numbers will range from 0 to
(context->analogFrames - 1) for analog,
(context->digitalFrames - 1) for digital and
(context->audioFrames - 1) for audio.
Remember when calling
digitalWrite() that the value will not be updated immediately upon returning from the function. Instead, the output will be buffered and will change when the designated frame arrives.
Bela provides a number of I/O functions. Your files should contain
#include <Utilities.h> in order to use these functions. See the code docs for detailed usage.
audioRead– read an audio input
audioWrite– write a digital output
digitalRead– read a digital pin
digitalWrite– write a digital pin, and hold the value going forward
digitalWriteOnce– write a digital pin for one frame only
pinMode– change a digital pin to an input or output and maintain this going forward
pinModeOnce– change a digital pin to an input or output for one frame only
analogRead– read an analog pin
analogWrite– write an analog pin, and (depending on system setting) hold the value going forward
analogWriteOnce– write an analog pin for one frame only
int digitalRead(BelaContext *context, int frame, int channel)
This function works like
digitalRead() on Arduino, but it takes two extra arguments. The first argument is the
context data structure which is passed in to
render(). This is needed because
context holds all the references to the I/O buffers. The second argument is the frame (i.e. the time) at which to read the pin. The third argument,
channel, is the pin to read, similar to the Arduino function. The value of the pin (
LOW) is returned.
Analog input and output pins are provided on dedicated headers, labelled “IN” and “OUT” respectively.
Digital input/output pins are available on P8 and P9 connectors. The correspondency between physical pins and the digital pin numbers used in the software is outlined below.
Refer to the interactive diagram to find your way around the available pins on the cape: http://bela.io/belaDiagram/ .
Pin Digital in/out number P8_07 0 P8_08 1 P8_09 2 P8_10 3 P8_11 4 P8_12 5 P9_12 6 P9_14 7 P8_15 8 P8_16 9 P9_16 10 P8_18 11 P8_27 12 P8_28 13 P8_29 14 P8_30 15