Hi!

I just wrote a c++ program reading input from an 9-DOF sensor breakout board via I2c on the Bela (https://forum.bela.io/d/442-i2c-accelerometer-class/6). Now I need to run it (sending data over OSC) at the same time as I'm running SuperCollider (currently using the remote-control example and connecting to it via another computer). However, as soon as I run the SuperCollider code the I2C program quits (even if I run it with the buil_project.sh script with the -b flag). How do I get them both running simultaneously? (The I2C program doesn't need to do any audio processing)

Cheers,
Erik

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 pthreads instead of Bela's AuxiliaryTasks , 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

Thank you for your extensive reply!

Using the first method, the IDE still stops the IMU program when I run the SC remote example.

I'm now trying the second method (creating a main() function and only doing the things I need). A few problems I've run into:
rt_printf results in a segfault (error 139). Solved by changing it to printf.
OSCClient.setup creates an auxillary task which triggers the error:
Error: You should call Bela_initAudio() before calling Bela_createAuxiliaryTask()
Segmentation fault
make: *** [runide] Error 139

If I comment out all the OSC stuff it works like a charm (debugging through printing to terminal). However, I kind of need the OSC stuff. Is there an obvious and easy solution to this? I guess I could use another C++ OSC library otherwise, or using some other means of sending the the data to SuperCollider (writing a UGen like the AnalogIn one?).

    ErikNatanael Using the first method, the IDE still stops the IMU program when I run the SC remote example.

    Yes that is expected: if the IMU program is started from the IDE, the IDE will kill it when you start another one. However, you should be able to run that from the terminal and then it should not be killed when you start supercollider.

    ErikNatanael rt_printf results in a segfault (error 139). Solved by changing it to printf.
    ErikNatanael Error: You should call Bela_initAudio() before calling Bela_createAuxiliaryTask()

    I think these are both due to the fact that you need to cal xenomai_init()as done in RTAudio.cpp:Bela_initAudio():

    // at the top:
    #include <xenomai/init.h>
    
    //then in main():
            int argc = 0;
            char *const *argv;
            xenomai_init(&argc, &argv);
            gXenomaiInited = 1;

    ErikNatanael I guess I could use another C++ OSC library

    yes, however the point here was about allowing you to re-use your existing code without looking for extra stuff. In practice, you could actually use the OscPkt class used under-the-hood by Bela's OscXXX classes (just #include "oscpkt.hh" if you want to go that route), but hopefully the above should work for you.

    ErikNatanael (writing a UGen like the AnalogIn one?)

    Right, that would be the real proper way of doing this! However, I2c has really nothing to do with audio, so I kind of think that a stand-alone program would be more useful and easier to program, debug and maintain than a supercollider uGen (and I have never made one of these myself, btw). Ultimately, the overhead of sending/parsing ~100 OSC messages per second should be negligible.

    Aha, great! I had to add extern int gXenomaiInited; as well above my main function. Now the I2C/IMU program seems to work if I run it from the command line. But as soon as I do so the IDE hangs and I almost always get a
    You have been disconnected from the Bela IDE and any more changes you make will not be saved. Please check your USB connection and reboot your BeagleBone error. If I stop the program with CTRL+C the IDE comes back to life almost immediately. The reason is probably that CPU load is almost at 100%.

    Looking at top, the IMU process occupies between 30% and 50% of the CPU and node occupies another 45%. Stopping the IMU program predictably lets the CPU use return to a normal 1% load. (This is without printing anything to the terminal). Commenting out everything in the readIMU function makes no difference to the CPU load. What makes me suspicious is that node uses so much CPU, as if the two processes are working against eachother in some kind of loop.

    I updated the code in the repository to its current state: https://github.com/Nathnainiel/Bela-Adafruit_IMU
    Any ideas to what might be hogging the cpu and how I avoid it?

    Look at these lines: https://github.com/Nathnainiel/Bela-Adafruit_IMU/blob/master/render.cpp#L126-L128 : you are missing braces around the indented code, so that you effectively have a busy loop:

    	while(!gShouldStop)
    		Bela_scheduleAuxiliaryTask(i2cTask);

    this by itself uses lots of CPU on the main thread. Additionally, the i2cTask being scheduled so often will also use lots of CPU. Ultimately this (almost) hangs the board, and the IDE does not get refreshed quickly enough, so that it thinks the board got disconnected.

    Because of the way Xenomai works, the CPU used by the Xenomai task i2cTask will not show up in top: the CPU time spent by a Xenomai's task in "Xenomai mode" will be listed in /proc/xenomai/sched/stat/. The total amount here is "invisible" to Linux, so that if you have a Xenomai task that uses 90% of the CPU, what Linux's top will show as "50%" is actually only 50% of the 10% not used by Xenomai. The CPU used by your project's executable, as displayed by top will be the sum of the CPU used by the busy loop in main() and the time spent in "Linux mode (e.g.: doing I2c or network I/O)" by the Xenomai task.

    Ahh, such a simple mistake! Thank you for your help! I updated the repository again, and added instructions for using the program.

    It seems like I can start the IMU program either before or after I start SC, but then I can't stop the IDE until I kill the IMU program (which isn't a problem, I'll need to make a safe shutdown script anyways). Just leaving it here for the future.

    I take it you are starting the IMU program from the console at the bottom of the IDE? That is not really meant for long-running programs, as you do not have a way to interact with (and stop) them once they are started.

    I'm starting it from a terminal logged in via ssh, but it seems to have been a one off. I can start and stop stuff through the GUI however I want while the IMU program is running now.

    2 years later

    Based on this thread, I have been experimenting with making a C++ application that runs 'in the background': first compiling it in the Bela IDE, then stopping it and running it via ssh. This application in my case translates incoming OSC messages into SSD1306 OLED commands.

    This approach works well for incoming OSC messages from PD running on my computer (192.168.7.1), but when I send the same messages (at the same rate) from PD running on the Bela, there is a significant and steadily rising latency. It seems like the Bela does not like to have its networking facilities being utilized by a C++ and PD application at the same time?

    Any pointers about this greatly appreciated - I hope to be able to leave the display app as a separate C++ app, so it can also be used for SC. I have not tested if the same type of apparent congestion occurs when sending OSC messages from an SC app to the C++ app.

    My (very work-in-progress) code for this app is here: https://github.com/crosswick/OSC2OLED4Bela

    Easiest way to test the performance is to send a float between 0 and 1 to address /param1 on port 7562, which will result in a horizontal bar being filled from left to right.

      crosswick there is a significant and steadily rising latency

      This seems to indicate that you are sending messages faster than they can be processed. Therefore they stack up in the queue causing the latency.

      crosswick It seems like the Bela does not like to have its networking facilities being utilized by a C++ and PD application at the same time?

      This wouldn't be a problem. However, if both programs are intensively using the CPU, so that they cannot process incoming messages fast enough, then this explains the increasing latency.

      How often are you sending messages?

        giuliomoro I'm sending messages from a slider, rate-limited by a [cyclone/speedlim 100], so if I'm correct that's 10 times per second.

        I'm not sure it would be a CPU issue... if I send these messages from my laptop, the display latency is fine. It seems that only when both PD and C++ are accessing the network facilities that the escalating latency occurs.

        Hmm maybe I could try a number of different priority settings for the Auxiliary Task in the C++ app...

        what is the overall CPU usage on the board? (as displayed by the IDE)

        7 days later

        I've done another test that simply scales a potentiometer input to 0-1 and sends this over OSC to the OLED driving app, again rate-limited by a [cyclone/speedlim 100]; the PD patch alone reads about 16% CPU; when the OLED app is running it's about 20% CPU. In this case the significant and escalating latency is there.

        Then, when running the exact same PD patch on my laptop and sending the value of an Hslider through the same speedlim over the USB network connection, the display is updating fine. So my best guess is still that there is some type of conflict that arises when a C++ and PD app are running concurrently on the Bela, both using network.

        Perhaps I can test something else to investigate?

        Try the following: run a different Bela program on the board, which uses the same CPU but does not use the network, and run the OSC/OLED app on the side. When you send updates from the host, does the OLED update as expected or is the latency still high and increasing?

        Alright, I can confirm that with the same CPU load (caused by the same PD patch but not connected to network), the display does react without latency to incoming OSC messages from my laptop.

        Ok, can you please link to all the programs and patches needed to test this on my end?

        The OLED app is still the same, see https://github.com/crosswick/OSC2OLED4Bela

        Here's the little PD patch that scales the input of a potentiometer and sends a 0-1 float value to /param1 on 192.168.7.2, port 7562:

        #N canvas 988 690 450 530 10;
        #X obj 143 86 snapshot~;
        #X obj 110 19 loadbang;
        #X obj 110 46 metro 100;
        #X obj 143 115 - 0.43;
        #X obj 143 136 / -0.124;
        #X obj 60 266 oscformat param1;
        #X obj 60 287 list prepend send;
        #X obj 60 308 list trim;
        #X obj 60 435 netsend -u -b;
        #X msg 96 366 connect 192.168.7.2 7562;
        #X obj 96 340 loadbang;
        #X obj 144 204 hsl 128 15 0 1 0 0 empty empty empty -2 -8 0 10 -262144
        -1 -1 4000 1;
        #X msg 142 396 disconnect;
        #X obj 60 242 cyclone/speedlim 100;
        #X obj 218 30 adc~ 5;
        #X connect 0 0 3 0;
        #X connect 1 0 2 0;
        #X connect 2 0 0 0;
        #X connect 3 0 4 0;
        #X connect 4 0 13 0;
        #X connect 5 0 6 0;
        #X connect 6 0 7 0;
        #X connect 7 0 8 0;
        #X connect 9 0 8 0;
        #X connect 10 0 9 0;
        #X connect 11 0 13 0;
        #X connect 12 0 8 0;
        #X connect 13 0 5 0;
        #X connect 14 0 0 0;