I’m working on a module that’s a set of 8 samplers triggered by a sequencer. When the sequencer advances during recording, the input is recorded into the next sampler, and during playback each sampler plays out it’s recording sequentially. There’s a global pitch control that affects the playback speed/pitch of all the samples.
The way the sampling works is that the input is copied into a buffer (a vector of floats). If the pitch control is in it’s default position, then that input buffer is copied directly to an output buffer, which is iterated through and sent to the output. If some pitch control is applied, I’m using dsp::SampleRateConverter to copy the input buffer to the output buffer at a different sample rate to achieve the pitch shifting.
The problem I’m having is that when I change the pitch parameter from pitching the buffer up to pitching it down, the playback of the lower pitch only seems to work for the number of samples that the preceding higher pitch had - thus the output is abruptly cut short. The confusing thing is, this only happens for the first time the buffer is played at that lower pitch - the next time the sequencer comes around it plays all of the samples without cutting the end off.
The relevant code can be found here, and I’ve uploaded a video here showing the problem in action.
Anyone have any ideas?
Hi! I’ve tried to read the code but couldn’t find a culprit right away. I’ll try to take another look after work
Beside that, I think it would be better not to call vector operations that allocate and deallocate memory in the audio loop (like push_back or shrink_to_fit), it could cause delays, see:
Maybe it would be better to have a maximum sample size and use RingBuffers as the data structure?
Fixed it! Turns out all I needed to do move the line that resizes the outBuffer vector to before the sample rate converter process, ensuring there’s enough room to put all the samples into. Just reserving the space doesn’t actually make the vector the right size, now it seems to be working pretty well.
Thanks for the advice, I’ve used vectors before so I’m somewhat comfortable with them but yes I’m sure a RingBuffer is a better structure to use. It’s been behaving fine running the sequencer at LFO rates but when the sequencer gets up near audio rates it just can’t handle doing the conversion:
The article looks interesting, I’m definitely fumbling in the dark when it comes to audio programming so any pointers on good practice are appreciated!
@nolente is not saying not to use vectors, that’s ok (depending on what you are doing), but you mustn’t resize the vectors in your Module::process() call. If you do that, you will be guaranteeing that anyone using your module will get random clicks and pops when they use rack.
Please don’t do that. Enough modules that aren’t coded correctly and it’s “death by a thousand paper cuts” for rack users. They won’t know which modules are the culprits. They will just say “rack always clicks and pops when I use it.”
Here is a paper or writing efficient plugins that also talks about the well-known fact that you shouldn’t allocate memory in your audio code: SquinkyVCV/efficient-plugins.md at main · squinkylabs/SquinkyVCV · GitHub