-
Notifications
You must be signed in to change notification settings - Fork 139
Puredata and C
The Bela Wiki has moved to learn.bela.io. This material has been superseded. Click here for the maintained version.
Note: this page is for advanced users looking at how to combine C++ and Pd on Bela. The basic guide to using Pd on Bela is here.
When running Puredata patches on Bela, these are combined under the hood with some C++ code which provides a wrapper for the heavy
or libpd
code.
By adding your own C++ files, you can override the default behaviour and combine C++ and Pd in your project.
When no custom C++ files are provided, the linker uses the core/default_main.cpp
to provide a default main()
function to your program and core/default_libpd_render.cpp
(in the case of libpd
) or scripts/hvresources/render.cpp
(in the case of heavy
) files to provide default versions of the setup()
, render()
, cleanup()
functions to your program.
Both wrappers take care of similar issues, using the libpd
API or the heavy
API as needed.
In the setup()
function, they enable message passing between the Pd patch and the C++ code, preparing for the Bela digital I/O, enabling MIDI devices, enabling the Bela scope.
In render()
they prepare the audio buffers to be sent to the processing routine, convert digital I/O to audio channels as needed, parse incoming MIDI messages and pass them on to the processing engine.
The processing routine is libpd_process_sys()
for libpd
and hv_processInline()
for heavy
.
After the routine returns, the wrapper copies the output values from the output buffer of the routine to the output buffers of Bela, turns any digital channels processed at audio rate back into the Bela digital I/O format, and sends data to the scope channels as needed.
Last, cleanup()
de-allocates the memory and any resources allocated in setup()
.
All of these provide the default behaviour for Pd patches on Bela, as referred to in our example code and the dedicated wiki page.
In order to add custom functionalities to the Pd wrapper, or modify the existing ones, you can provide your own C++ wrapper to replace the default one:
-
libpd
: if you provide.c
or.cpp
files in the project folder which contains your_main.pd
file, these will be compiled. If they contain a definition of therender(BelaContext*, void*)
function, then thecore/default_libpd_render.cpp
file will not be used and your files will be used instead. -
heavy
: if you provide.c
or.cpp
files in theheavy/
folder within the folder that contains your_main.pd
file, then thescripts/hvresources/render.cpp
file will not be used during the build and your files will be used instead.
An example project which is structured this way is examples/PureData/customRender/
.
You are strongly encouraged to refer to the documentation for the libpd
API or the heavy
API and to the default wrappers provided by the Bela code as a starting point for your own customized wrappers.
Using a custom wrapper, you can combine Pd and C++ code with bidirectional audio and message communication between the two, you can make more flexible mappings between Pd and the Bela I/Os and scope, optimize CPU usage, add support for I2C sensors or custom network communication within Pd and much more.
In the examples/PureData/custom-render/
project, messages are passed from Pd to the C++ wrapper to control an oscillator which is then used to do some post-processing on the audio outputs generated from Pd.
A custom render()
function could do some pre-processing on the audio. analog and digital inputs before they are passed to the processing routine, or some post-processing on the signals generated by the processing routine, so that implementing a C++->Pd->C++ audio-rate communication is pretty straightforward.
Implementing a Pd->C++->Pd audio-rate communication is a bit more complicated and it would typically involve a 1-block extra latency. One possible approach is a follows:
after the call to the processing routine, you would copy in a buffer the data for those channels that need to be fed back into Pd; at the next call to render()
, you would apply the C++ processing to those data and place them into one of the buffers to be passed to the processing routine, so that they can be accessed through an [adc~]
object.
Other workable solutions exist, including using multiple independent Pd patches, joined together by the wrapper.
This is already supported by heavy
(though not through the Bela scripts) by instantiating multiple contexts, and will be introduced in the 0.48 release of libpd
.
The code in the default wrappers was written to give convenient access to the wide feature set of Bela from a Pd patch without having to write any C++ code.
As a natural result of this, the efficiency of the code is not optimal, and some use cases could save a significative amount of CPU by re-factoring the code in the wrapper.
For instance, scope channels are available by default on channels [dac~ 27 28 29 30]
.
This means that the patch needs to have at least 26 output channels if you plan to use the scope, adding some overhead in resource usage.
A custom render.cpp
file could allow to use any arbitrary channel for logging data to the scope, lowering the channel count of the Pd patch and reducing the CPU usage.