I’ve been trying to implement a solid fixed sample rate class for running a synth engine at a selected srate for and have so far failed to deal with the buffer glitches. I’ve tried with juce interpolators as well as r8brain and I just don’t seem to be able to get my head around it. With the juce interpolators I would expect it should always give back the correctly interpolated samples of what are passed in as the output sample size is the size factor. It seems it’s essential to add at least an extra sample to the input to avoid calamity, so I’d expect an extra sample would be enough for the divisional samples. But, it just doesn’t seem to work that way. The more extra samples provided the worse the glitches, using the zeros at some points.
Anyway here is what I have at the moment:
const int inputSamples = buffer.getNumSamples();
const int numChannels = buffer.getNumChannels();
const int estimatedResampledSamples = (int)(inputSamples / targetRatio)+1;
juce::AudioBuffer<float> resampledBuffer(numChannels, estimatedResampledSamples);
resampledBuffer.clear();
synth->renderNextBlock(resampledBuffer, scaledMidi, 0, estimatedResampledSamples);
// Upsample to output sample rate
for (int chan = 0; chan < numChannels; ++chan) {
const float* inPtr = resampledBuffer.getReadPointer(chan);
float* outPtr = buffer.getWritePointer(chan);
outputResampler[chan]->process(outputRatio, inPtr, outPtr, buffer.getNumSamples());
}
From my experiments with r8brain, I got even more lost, I did get it working sort of but the latency at lower sample rates was extreme and I still didn’t manage to solve the glitches. Presumably it needs to work around some kind of FIFO buffer, dividing into sub blocks. I really thought doing this would be fairly straightforward but it has me beat.
Incidentally, I am more interested in running my synth engine at lower sample rates, although want both options. Going up in sample rate from the actual plug-in rate using the above code doesn’t have any glitches, just lower rates. I’ve also tried with Linear, but it still has the same issue as Lagrange.
OK, I’ve finally got my head around it, it needed to use a FIFO buffer, for anyone looking in the future this is what I have. The fifo is just a float vector. In prepare, the fifo and interpolators are reset, this also gets called when the resample frequency is changed using scopelock. It can probably be improved but I’m just relieved to get it working finally for now.
const int inputSamples = buffer.getNumSamples();
const int numChannels = buffer.getNumChannels();
int estimatedResampledSamples = (int)(inputSamples / targetRatio)+2;
int fifoSize = (int)fifoInput[0].size();
int toProcessSize = estimatedResampledSamples - fifoSize;
juce::AudioBuffer<float> resampledBuffer(numChannels, toProcessSize);
resampledBuffer.clear();
synth->renderNextBlock(resampledBuffer, scaledMidi, 0, toProcessSize);
for(int i=0; i < resampledBuffer.getNumSamples(); i++) {
fifoInput[0].push_back({resampledBuffer.getSample(0, i)});
fifoInput[1].push_back({resampledBuffer.getSample(1, i)});
}
for (int chan = 0; chan < numChannels; ++chan) {
const float* inPtr = fifoInput[chan].data();
float* outPtr = buffer.getWritePointer(chan);
auto usedSamples = outputResampler[chan]->process(outputRatio, inPtr, outPtr, inputSamples, estimatedResampledSamples, estimatedResampledSamples);
fifoInput[chan].erase(fifoInput[chan].begin(), fifoInput[chan].begin()+usedSamples);
}
r8brain has a function getInputRequiredForOutput. This gives you the number of samples you need to feed into process in order to get back a specific number of output samples. These values are relative to the creation of the r8brain CDSPResampler object.
1 Like
Thanks, it is something I will revisit at some point, now I’ve used and understand using a FIFO with Lagrange, r8brain makes a bit more sense to me. Aside from the glitches, it was the latency that put me off, but presumably using a FIFO people are working with smaller sub buffers and running a loop?
The other issue is the need to convert between float & doubles, I guess I could use a simple double based wrapper to return doubles from the synth engine on the fly, but then they have to be converted back to floats for the processor out after the post synth resampling. It’s a shame it doesn’t have a template for floats.
1 Like
IIRC, r8brain isn’t suitable for realtime/streamed sample rate conversion. It’s aimed at high-quality and speed without prioritising latency.
1 Like
R8brain can work in real-time, it just doesn’t hold state well for you, so you have to process in batches that result in integer in and integer out. So lots of buffering around it.