A MultiOutUGen is a unique beast. You have to tell it the number of output channels in sclang. Then the UGen’s outbuf is auto-generated based on the number of output channels and the blockSize. You don’t need to allocate the buffer yourself in the server code. It is allocated for you.
I just dealt with this in my RTNeuralUGen code. That might be a good place to look, though I’m sure it isn’t the best place.
So using UGen will allocate one buffer automatically for one output channel and MultiOutUGen will allocate multiple buffers for multiple output channels automatically.
What i dont understand is:
In your .sc file you either have to inherit your new class from UGen or MultiOutUGen, right?
When you create the buffers manually like its done in the RNBO example you would also have to inherit from UGen to make one output channel available in SC or MultiOutUGen to make several output channels available in SC, right?
Isnt this redundant, the buffers are created automatically when using UGen or MultiOutUGen and you additionally allocate them by yourself?
I think I already commented about this in this thread, but nobody picked up on it.
SC’s native output buffer mechanism is the macro ZXP(). If you’re using ZXP in the inner loop to write directly to the output buffer, then there’s no need to allocate more storage.
In the rnbo saw example, all the juice is happening in rnboObj.process(input, 0, buf, 1, bufsize);. Probably Cycling 74’s code isn’t embedding ZXP in the process() function. My guess is that they’re avoiding the problem of rnbo’s output interface vs SC’s output interface by writing first into a buffer that they control (the RTAlloc calls), and then in each control cycle, copying from that buffer into SC’s output buffer.
It would be redundant if rnbo were writing ZXP into their inner loop, but there may be technical reasons why they can’t do that. The RTAlloc’ed buffers would be a workaround for that.
I could be wrong, of course, but I think this is pretty close.
These loop macros are legacy and should not be used in new code!
I had a quick look at the RNBO example. Here’s what’s going on:
While they direclty pass the UGen inputs to the RNBO process function, they use a dedicated buffer for the output. That buffer is allocated in the constructor and freed in the destructor.
I think they are doing this to avoid the buffer aliasing problem. (Input and output buffers may point to the same buffer, so you must not write to the output before reading all inputs). In Pd – and likely Max/MSP as well – you can only avoid this problem by allocating extra input or output buffers.
They use a hardcoded size of 512 for the individual channel buffers instead of querying the Server buffer size (as they do RnboSaw::next)
prepareToProcess() is a setup function, but they call it in the next() method for every audio block. I think this should be called only once in the constructor
hey, in my first comment i have been thankful for both of your replies. My assumptions on the redundancy have been partly because of the direct writing to the output stage how you have described it. Im very happy for all the sugesstions.
Finally, I’m wondering if a RNBO object can be safely created on the realtime thread. I’m pretty sure that some objects require dynamic memory allocation in the setup function.
Now, in most audio programs (Max/MSP, Pd, all DAWS, etc.) ugens/plugins are never initialized in a realtime context, so you don’t have to worry about the setup function. However, in SC, UGens are created on the audio thread and all initialization must be realtime safe. There are ways to defer UGen initialization to the NRT thread (like I do in VSTPlugin), but it’s a bit complex.
I admit they didnt bother to test it.
But this comment was from two years ago. In the meantime i have raised two issues on github. When you run the current example the sound is at least how you would expect it. Before the fix the number of samples which have been written to the output have also been hardcoded with 512 (thats the default max blocksize) which have been replaced with blocksize(). The reason for not beeing able to change the freq is, because its not implemented.
Long story short: forget about the implementation?
When you run the current example the sound is at least how you would expect it.
Interesting. Is this with the following code?
This would be very surprising because the still call prepareToProcess in the next function which I’m pretty sure is wrong. Compare this with the C++ example where prepareToProcess is only called once in the beginning: How to Include RNBO in Your C++ Project | Cycling '74
Yes, i ran both examples before and after the “fix”.
The initial version created a proper saw wave when you booted the server with a blocksize of 512.
Both versions are normalized between -0.5 and 0.5 though, haha
Im pretty sure in the current example code are some mistakes. I would like to figure them out and know if a proper implementation would be possible without getting overly complex.
As I said, prepareToProcess() should very likely be called in the constructor, not in the next function.
Also, they should cache the parameter index in the constructor in a member variable, so we can also update the parameter in the next function. Ideally, you should cache the last value of in0 and only set the parameter if the new value of in0 has changed.
Again, I doubt that the feeling that RNBO objects are not generally realtime safe. I would need to have a look at the actual generated C++ code.
If RNBO_USE_FLOAT32 is defined, the extra buffer wouldn’t be necessary and you could directly pass the UGen output buffesr to the process function. (If the plugin uses audio inputs and outputs, you would need to disable buffer aliasing.)
i currently have to get familiar with alot of fundamentals in C++ and dont know how much time this will take, but i have dedicated countless hours the last two or three years on figuring out and then developing different attempts of audio rate sequencing and microsound in gen~ and would be really happy to share that with the SC community via RNBO export.
I could make an attempt to put prepareToProcess() in the constructor and get rid of the manually allocated buffers. What i dont understand is why buf isnt declared as a variable with a certain variable type in the constructor (but if we want to get rid of it anyway, maybe not that important right now).
Where and how should i define RNBO_USE_FLOAT32 and set the kUnitDef_CantAliasInputsToOutputs flag?
Should i put one of those definitions:
What i dont understand is why buf isnt declared as a variable with a certain variable type in the constructor
buf is a member variable, but it’s not immediately apparent. (That’s why we typically add a prefix or postfix to member variables, e.g. mVariable, m_variable, _variable or variable_.)
Where and how should i define RNBO_USE_FLOAT32 and set the
I think that must be an option when exporting the RNBO code. Maybe it is already defined. Either way, I would recommend to add the following check somewhere at the top:
#ifndef RNBO_USE_FLOAT32
#error "RNBO_USE_FLOAT32 must be defined!"
#endif
It took me a second to see what you were saying here. The issue is not using RTAlloc to allocate the buffer. Lots of UGens do this. The issue is whether the RNBO object is itself realtime safe on instantiation. Correct?
Exactly! Some effects certainly require dynamic memory allocation in their setup function. Again, this is not a problem in traditional audio apps, such as DAWs, as the initialization is never done on the audio thread.
However, I haven’t done any research on RNBO’s realtime safety guarantees. Maybe the prepareToProcessis realtime safe. Someone should find out