Hello everyone
I am trying to figure out good strategies for flexibly allocating buffers in c++ in UGens and I am very interested in hearing your perspectives. Any advice is appreciated!
My problem is I want to create a/some headerfiles at the root of my ugen repo containing common algorithms that I want to combine in different ways inside my ugens, eg. allpass and comb. Some of these rely on buffer allocation of course and I would like to handle this in a sleek and flexible way of course but it is not so straight forward I find. The problem though is that if you do this in a headerfile, then the class in that header file needs access to the ugen’s World and function table it seems. Eg. a constructor like this in a header file:
// Constructor
AllPass_Base(World * parent_world, float sampleRate, float maxdelaylength) {
m_samplerate = sampleRate;
// Calculate size of buffers
m_bufsize = NEXTPOWEROFTWO(m_samplerate * maxdelaylength);
m_mask = m_bufsize - 1; // Used for wrapping purposes
// Allocate buffers
m_delaybuffer = (float *)RTAlloc(parent_world, m_bufsize * sizeof(float));
m_historybuffer = (float *)RTAlloc(parent_world, m_bufsize * sizeof(float));
// Zero out newly made buffers
memset(m_delaybuffer, 0, m_bufsize * sizeof(float));
memset(m_historybuffer, 0, m_bufsize * sizeof(float));
};
This leads to compilation errors complaining use of undeclared identifier 'ft'
.
using RTAlloc in a constructor
What I have done so far is this, where I allocate the buffer in a self contained UGen class in the more canonical way. This seems to be the classic strategy which I have so far used in my humble UGens and it works nicely by itself but the problem, as mentioned above, is how to abstract this in a reusable way in other cpp classes:
// Irate
maxdelay = in0(3); // Seconds
bufsize = NEXTPOWEROFTWO((float)sampleRate() * maxdelay);
// Don't forget to free this in the destructor
delaybuffer = (float *)RTAlloc(mWorld, bufsize * sizeof(float));
historybuffer = (float *)RTAlloc(mWorld, bufsize * sizeof(float));
// This check makes sure that RTAlloc succeeded. (It might fail if there's
// not enough memory.) If you don't do this check properly then YOU CAN
// CRASH THE SERVER! A lot of ugens in core and sc3-plugins fail to do this.
// Don't follow their example.
if (delaybuffer == NULL || historybuffer == NULL) {
mCalcFunc = make_calc_function<MKAllpass, &MKAllpass::clear>();
// Clear outputs of the ugen, just in case
clear(1);
if (mWorld->mVerbosity > -2) {
Print("Failed to allocate memory for MKAllpass ugen.\n");
}
return;
}
// Fill the buffer with zeros.
memset(delaybuffer, 0, bufsize * sizeof(float));
memset(historybuffer, 0, bufsize * sizeof(float));
Using STL containers ?
And alternative to this is to use STL containers. This was recommended to me by a colleague, but then the SuperCollider docs explicitly warn against this:
Memory Allocation
Do not allocate memory from the OS via malloc / free or new/ delete. Instead you should use the real-time memory allocator via RTAlloc / RTFree.
STL Containers
It is generally not recommended to use STL containers, since they internally allocate memory. The only way the STL containers can be used is by providing an Allocator, which maps to the allocating functions of the server.
The NHHall way
Then there’s a slightly different strategy which is seen in the NHHall
ugen. This is pretty close to what I would like to do