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);
// ...
}