Buffer initialization

Hi !

This may be a newbie question (gotta say I’m not brightest when it comes to memory allocation).

I have a recuring problem with the plug-in I develop when it comes to buffer.

Sometimes, when I open a patch with a module that has been previously added, its buffer seems to be full of garbage data. I suppose it’s a problem of memory allocation, the module looking at the same memory adress that it use to.

My buffer are in a RingBuffer class, the buffer itself is a std::vector. When the constructor of the object is called, it fills the buffer with zero just to be safe. Does that mean that the constructor isn’t called on patch load ?

The ringbuffer is declared as a module member variable, something like :

struct MyModule{

MyModule(){
configParam
...
}

noi::RingBuffer ring_buffer = noi::RingBuffer(maxlenght);

void process(){
}
};

It seems dirty, what is the rule of thumb for this kind of thing ? How are the datas stored in a saved patch ?

Have a nice day !

that will call your constructor when a module is made indeed. (Idiomatically I would be more likely to do not::RingBuffer ring_buffer{maxLength}; inline but that’s minor).

What’s the constructor of RingBuffer do? If you put a print in there do you see it? Does it confuse capacity and length somehow? those are all things i’d check if i had your code and got your bug report.

Parameters are automatically saved and restored by VCV. Your other Module member variables are saved only if you override Module::dataToJson and Module::dataFromJson to write and read them.

As for what’s happening with the ring buffer, you haven’t shared enough code here to tell what’s going on. You said “My buffer are in a RingBuffer class, the buffer itself is a std::vector”. However, the rack::dsp::RingBuffer does not use an std::vector, so this ring buffer must be yours or some other library. To tell more, we’d need to see that RingBuffer’s implementation, and more of how you’re using it.

Using std::vector could be a problem, depending on exactly how you use it, because it can grow dynamically which means a memory allocation, which should be avoided in process.

1 Like

Thanks a lot for your answers !

The size of the vector is set at construction, then I adjust the gap between the read and write index to set the wanted delay. I added some print to the buffer construtor, it seems to be called. I also added a print for a float in the buffer to check for any aberrant value, but the bug didn’t appear since then, despite my numerous attempt at closing and relaunching vcv.

I’ll add info if it ever comes back… If you have the time to check the ringbuffer code for any blatant error, I’ll gladly take any advices, will it be on the ringbuffer concept or on general cpp.

I didn’t notice an obvious problem offhand, but then you’ve changed the code since you had an issue, and I didn’t see where you were actually using that ring buffer in a module.

A few things I did notice:

  • your plugin has multiple ring buffer implementations. If you’re running into issues with the implementation, then perhaps consider using a common ring buffer so that you only have one to fix.
  • your plugin hard-codes the sample rate to 48000, but the Rack sample rate is variable and can change at any time (i.e. implement Module::onSampleRateChanged). This probably means your modules sound bad at rates other than 48k.
  • inputs, outputs, and lights missing config* to give them a name (tooltip).
  • probable over-use of inline. Generally speaking, compilers today are much better at optimizing code than you are, and depending on the compiler and the options given, using inline may reduce the compiler’s ability to generate the best code.
1 Like

A ring buffer really needs a good unit test suite. I had one in a commercial product that only failed like 1 time in a million. Luckily the test failed so I could fix it before it was released.

Thanks a lot for those advices !

The inline stuff was a workaround that allows me to resolve multiple declaration error, I now understand that it was a misuse of the .cpp and .hpp file. Also the onSampleRateChange(e) is now properly overrided for setting my sample rate and I deleted the additional ringbuffer (which was a test). Learned a bunch of things on the way, your help is really appreciated

Just to be clear on the purpose of all of this, obviously the right answer is to use a proper ringBuffer made by someone who is more competent than me and that has been thoroughly tested, the point here is to learn and discover funny sound that wouldn’t be possible without diving deep and making mistake in the core of the process

I probably jumped to soon to what I think was the problem, overviewing a lot of things (sorry for that, I still need to learn to ask proper coding question) The RingBuffer is contained in a Reverb, the link below is the link to the module.cpp, the reverb itself is in reverb.cpp, it uses filters (combs and allpasses) that are in filter.cpp, those filters use the buffers in buffer.cpp :

Any advices on how to perform this kind of test ? I’m curious, may I ask you what was the problem with your own ring buffer ?

1 Like

Rack provides a number of buffers that are available for modules to use: RingBuffer, DoubleRingBuffer, and AppleRingBuffer. One of these is probably a good candidate for your usage.

The RingBuffer is a threadsafe queue. I’m using it to offload work (that can allocate) from the audio thread to a separate thread. The other buffers aren’t threadsafe, but your code is all running just in the audio thread.

See include\dsp\ringbuffer.hpp.

Your ring buffer looks like it is implementing a linear interpolated fractional delay line. If you want a pretty well battle tested fractional delay line which can do linear, zoh, and a small window sinc interpolation and are happy with gpl3 https://github.com/surge-synthesizer/sst-basic-blocks/blob/main/include/sst/basic-blocks/dsp/SSESincDelayLine.h might help. It’s pretty well tested and exercised, and of course I know by saying that someone will find a bug, but that’s good :slight_smile:

Your problem sounds like some point in your network is not initialized. I think you see and understand when initializers run so it really just is looking at each of your elements and seeing if it is not initialized. In reverb tank model it will indeed explode bad data at startup and then fade out.

Hope that helps! Good luck and have fun

1 Like

The commercial one? It had a race condition so that when the producer and the consumer are on different threads every now and then i think the producer would overwrite some memory,

That particular test was difficult. Made one thread that would wire increasing integers in bursts. The consumer thread would just keep reading and verify it’s always one more than the last.