Hello,

In my learning journey, while fiddling around with the "oscillator_bank" example, I wanted to make a simple adjustment that would change the pitch every 1 or 2 seconds. So I extracted the freq setting code into a function like this:

void setFreq(float f, float inc){
	for(int n = 0; n < gNumOscillators; n++) {
		// Update the frequencies to a regular spread, plus a small amount of randomness
		// to avoid weird phase effects
		float randScale = 0.99 + .02 * (float)random() / (float)RAND_MAX;
		float newFreq = f * randScale;

		// For efficiency, frequency is expressed in change in wavetable position per sample, not Hz or radians
		osc.setFrequency(n, f); // use newFreq instead for Bela's random thing
		f += inc;
	}
}

And within render I added this:

int time = (int)(context->audioFramesElapsed) / 44000;
		time = time % 2;
		if (time != glastTime){
			//cout << "!seconds";
			glastTime = time;
			//setFreq(500 * (time + 2), 0);
		}

Sound was like I expected, but this lead to some block dropout warnings while running the code, so I assume I have to put it in another thread? What is the best resource around here for getting and idea of good thread practice? Hopefully some simple examples, since a lot of complex code will quickly blur the intent for me.

Thanks,
Søren

EDIT: is best practice to just always put everything non-audio related into auxiliary tasks? If that is the case, how do you go about providing an auxiliary task with a BelaContent object ?

    Assuming that your

    			//setFreq(500 * (time + 2), 0);

    is actually

    			setFreq(500 * (time + 2), 0);

    , here is your problem: you are doing an infrequent but expensive computation in the audio thread while your CPU is already pretty busy, thus causing glitches.

    Setting the frequency of a few hundred oscillators at once can be pretty expensive (especially considering it contains a call to random()). So let's say that on average your audio thread takes 70% of the CPU time: on that one audio callback where you are updating the frequency, you may be using more CPU time than the you have available ( > 100%) and therefore glitch. Note that you would not see such spike in the CPU usage from the CPU meter, because that shows the average CPU usage, but you are getting dropouts on a peak of usage.

    By updating the frequencies in a separate thread, you guarantee that no such glitch occurs, as the CPU time spent to update the frequencies can take the time needed without hanging a single audio callback.

    This is a very timely question: the talk I gave today at the ADC explains this problem in detail and I hope you will find it useful (starts at 5h32m23s):

    soeren EDIT: is best practice to just always put everything non-audio related into auxiliary tasks? If that is the case,

    In principle yes, it is good to put everything non-audio related into an aux task. However, some audio-related but non time-critical stuff (like in your case) can go in a separate thread, too. Hopefully the last part of the talk clarifies that.

    soeren how do you go about providing an auxiliary task with a BelaContent object ?

    You don't! A BelaContext* is only valid in those threads that call setup(), render() and cleanup(), and only for the lifetime of the function itself. This means that you should not make a copy of a BelaContext* to use later or to pass it to another thread. If you want to access elements of the BelaContext* from an AuxiliaryTask (e.g.: for getting an input value or writing an output value), then you should do so by reading/writing a shared resource (e.g.: a global variable) from one thread and reading it from the other. For instance, if you want to write to an analog out a value computed in an auxiliary task you would do:

    float gAnalogOutValue = 0;
    int gAnalogOutChannel = 3;
    void auxiliaryTaskCallback(void*)
    {
      gAnalogOutValue = random() / (float)RAND_MAX;
    }
    void render(BelaContext* context)
    {
    // ...
      analogWrite(context, n, gAnalogOutChannel, gAnalogOutValue);
    // ...
    }

      giuliomoro amazing amount of help, thank you so much! will study this closely now. And yes, the function call was not ment to be commented out.

      Just one follow up question: we do agree, that putting something in an aux task equals putting it into a different thread - right?

      EDIT: one more question if anyone sees this: how do you go about making an auxiliary task that takes argument(s) ? Not the Belacontext, I get that, but basic variables. All the Bela examples just have (void*), and when I tried implementing a float argument (both in prototype and implementation) I get a complaint that the Bela_createAuxiliaryTask has "no matching function". Or that's simply not possible and you have to use global variables like you already mentioned @giuliomoro ?

        soeren how do you go about making an auxiliary task that takes argument(s

        AuxiliaryTask Bela_createAuxiliaryTask(void(*)(void *) callback, int priority, const char * name, void * arg )	

        The last argument (optional) passed to Bela_createAuxiliaryTask() is a void* that is then passed as the only argument to the function.
        See the docs.

        If you want to pass an argument other than a void* then you should pass it (or a pointer to it) and then cast it accordingly. For instance the below code:

        void myCallback(void* arg)
        {
          float param = *((float*)arg);
          print("param: %f\n", param); 
        }
        
        bool setup(BelaContext* context)
        {
          float param = 123;
          AuxiliaryTask task = Bela_createAuxiliaryTask(myCallback, myPriority, myName, (void*)&param);
          Bela_scheduleAuxiliaryTask(task);
        }

        should print 123 (untested).

        Just one follow up question: we do agree, that putting something in an aux task equals putting it into a different thread - right?

        Yes it does. An AuxiliaryTask is a different thread.

        soeren All the Bela examples just have (void*),

        That's because none of them is making use of the parameter passed to the callback as they rely on global variables instead.