- Edited
Hmm you can only have one Bela program running at one time, because only one program can access the audio, digital and analog channels at one time. This is enforced transparently by the Makefile
(which the IDE is a front-end for): in the Makefile
the stop
target is a pre-requisite of run
, so the IDE would call make run PROJECT=projectname
, which in turn implies make stop
. The recipe for the stop
target is:
stop:
ifeq ($(DEBIAN_VERSION),stretch)
$(AT) systemctl stop bela_startup || true
endif
$(AT) PID=`grep $(BELA_AUDIO_THREAD_NAME) $(XENOMAI_STAT_PATH) | cut -d " " -f 5 | sed s/\s//g`; if [ -z $$PID ]; then [ $(QUIET) = true ] || echo "No process to kill"; else [ $(QUIET) = true ] || echo "Killing old Bela process $$PID"; kill -2 $$PID; sleep 0.2; kill -9 $$PID 2> /dev/null; fi; screen -X -S $(SCREEN_NAME) quit > /dev/null; exit 0;
# take care of stale sclang / scsynth processes
ifeq ($(PROJECT_TYPE),sc)
#if we are about to start a sc project, these killall should be synchronous, otherwise they may kill they newly-spawn sclang process
$(AT) killall scsynth 2>/dev/null; killall sclang 2>/dev/null; true
else
#otherwise, it safe if they are asynchronous (faster). The Bela program will still be able to start as the
# audio thread has been killed above
$(AT) killall scsynth 2>/dev/null& killall sclang 2>/dev/null& true
endif
that is lot of stuff to say that stop
does the following:
- grep $(BELA_AUDIO_THREAD_NAME) $(XENOMAI_STAT_PATH) | cut -d " " -f 5 | sed s/\s//g
:
search for the PID of the currently running Bela program by looking at /proc/xenomai/sched/stat
and finding the PID of the Bela audio thread.
- kill -2 $$PID
: kill that program, sleep 0.2;
wait a bit, kill -9 $$PID
kill it harder. screen -X -S $(SCREEN_NAME) quit
: also exit any screen
that we may have been running a program on (in case it is planning to restart automatically).
- killall scsynth 2>/dev/null; killall sclang 2>/dev/null; true
sometimes scsynth
or sclang
do not die properly. This makes sure they do.
The above means that, if you are running a project from the IDE, all the above will kill any existing Bela project which is already generating/processing audio (that is any project which is already running render()
. Additionally, a similar check for an already running Bela program is performed in Bela_initAudio()
in core/RTAudio.cpp
, so that if a Bela audio thread is already running, the program will refuse to run and quit immediately.
So, from the lengthy explanation above you will have gathered that you cannot run the audio thread (that is: the render()
function) for two Bela programs at the same time. SuperCollider of course has its own implementation of the render()
function, so this also means you cannot run a full C++ Bela project and SuperCollider at the same time.
The good news
Untested - this will very likely (let's say 90%) work
However, I think the below solution will work for you, assuming, as you say, that "the I2C program doesn't need to do any audio processing".
You have to write your C++ Bela program that runs the I2C-to-OSC conversion in such a way that it never calls the render()
function. This is done by adding an infinite loop at the end of setup()
. For instance, this would do it:
while(!gShouldStop)
usleep(100000);
return false;
This would gently check every 100ms whether the exit flag is set, and then exit cleanly when that is the case (e.g.: if you run this in a terminal, this will happen when you hit ctrl-C). (Note the "return false
" which will make sure that the audio does not attempt to start once you exit the loop).
However, this is not very useful for you, as you would have to rewrite most of your code (I am thinking of this one).
What I suggest instead is that in a similar loop you would add those instructions that you currently have in render()
and use the argument to usleep()
to time it similarly to what render()
would do: you would not have to count samples as you currently do, just sleep accordingly. For instance, the bottom of setup()
could look like this:
....
while(!gShouldStop){
Bela_scheduleAuxiliaryTask(i2cTask);
while (oscServer.messageWaiting()){
int count = parseMessage(oscServer.popMessage());
oscClient.queueMessage(oscClient.newMessage.to("/osc-acknowledge").add(count).add(4.2f).add(std::string("OSC message received")).end());
}
usleep(1.f/sampleFreq * 1000000);
}
return false;
The above minimizes the changes to your existing code. However, the while (oscServer.messageWaiting()){...}
block seems to be simply a leftover from the example code so you can probably get rid of that?
You should then be able to run the above program stand-alone, and while that is running, you can start supercollider. However, you would not be able to run this after scsynth
has started (remember the check that takes place in Bela_initAudio()
!).
The better news
Try the above first, and once you get the above working, be happy: it is a good workaround to minimize your work to port your code quickly. However, it comes with the disadvantage of not being able to run it once scsynth
has started. This cannot be avoided as you are using the Bela API and the call to Bela_initAudio()
is what triggers that check. There are better ways of doing it.
Note that I think both the above and the below solutions would require you to run the Bela program from outside the Bela IDE (for instance: a terminal, a screen
or a systemd
script).
One "proper" way of doing it
Untested - this most likely (let's say 80%) works.
This way allows you to use the Bela building toolchain , and potentially the Bela IDE (at least for test runs, but I don;t think it would work for running this in one tab and a supercollider project in the other).
Define a int main()
function in your render.cpp
file. Bela by default uses the main()
function provided in core/default_main.cpp
, unless you provide your own. Here you can define your own main()
function and paste into it all the code from your setup()
function (once you amended it as above). Also you will need a couple of includes and defininginterrupt_handler()
see the template below
// add this code to your `render.cpp`, then strip out
#include <Bela.h>
#include <signal.h>
// Handle Ctrl-C by requesting that the audio rendering stop
void interrupt_handler(int var)
{
gShouldStop = true;
}
int main(int argc, char *argv[])
{
// Set up interrupt handler to catch Control-C and SIGTERM
signal(SIGINT, interrupt_handler);
signal(SIGTERM, interrupt_handler);
// Run until told to stop
/***
paste here all of the content of your (edited as above) setup() function.
***/
Bela_deleteAllAuxiliaryTasks();
}
You see? No Bela-specific calls here, except for Bela_deleteAllAuxiliaryTasks()
, which is probably useless. Most importantly, no call to Bela_initAudio()
which is the one that would fail.
Another proper way of doing it
Untested - this probably (let's say 60%) works
You will have by now realized that you do not need any Bela-specific stuff to run your I2C-to-OSC code. What you are using are libraries (I2c, OSC) that are provided by Bela, but that have nothing to do with the real-time audio core of it. This means that your project does not have to be a Bela project. You could be using pthread
s instead of Bela's AuxiliaryTask
s , which are a wrapper for Xenomai's real-time threads. While we have not yet figure out the best way of disentangling "libraries" from core Bela code, you could write your code so that it is just another C++ program on your computer, for which you run a build and link command. The above main()
example is already a good step in this direction, however it is still sort of a hack to build the project as a Bela project: you are simply leveraging the Bela build toolchain. An alternative would be to get your render.cpp
file in its final form from the. One "proper" way of doing it
bit and compile it stand-alone, linking against libbela
and libbelaextra
:
g++ render.cpp -o render.o -I/root/Bela/include
g++ render.o -o i2c-to-osc -L/root/Bela/lib -lbela -lbelaextra