Hello!
I have a cpp render that manipulates a realtime incoming mic input by adding a convolved reverb of varying kinds, delaying the reverberated audio output from no delay to some, and changing the volume of the dry input.
I initially wrote this for a GUI for testing, and it worked fine. However, I need it to work with physical analogue inputs as the idea is that someone will manipulate these sliders with a VR headset on.
By commenting out parts, I've isolated the issue to one line, and it is where I use the map function for my delay.
"float delay = map(input1, 0, 3.3 / 4.096, 0, 0.24);"
If I instead just set this to a value, there is no crackling at all. I have another analogue input changing the volume of the dry signal and that does not cause crackling so it is just something to do with my convolution or with my delay.
I would appreciate any advice as I am new to using cpp and have been using the cpp Bela tutorials and am now stumped.
Here is the working version:
void render(BelaContext *context, void *userData)
{
// Access the sliders specifying the index we obtained when creating then
int room = (int)gGuiController.getSliderValue(gRoomSlider);
int maxBlocks = (int)gGuiController.getSliderValue(gMaxBlocksSlider);
float sparsity = gGuiController.getSliderValue(gSparsitySlider);
//read the delay in seconds and convert to samples
float delay = gGuiController.getSliderValue(gDelaySlider);
int delayInSamples = delay * context->audioSampleRate;
//calculate read pointer based on write pointer
gReadPointer = (gWritePointer - delayInSamples + gDelayBuffer.size()) % gDelayBuffer.size();
float inBuf[context->audioFrames]; // the buffer to be processed by the convolver
for(unsigned int n = 0; n < context->audioFrames; n++) {
float in = 0;
if(gProcessInput) {
in = audioRead(context, n, gInputChannel);
}
inBuf[n] = in;
}
float outBuf[context->audioFrames];
for (unsigned int n = 0; n < context->audioFrames; n++)
{
float in = inBuf[n];
float out = outBuf[n];
// ... convolve it ...
switch (room)
{
case 0: //IR1
out = zlConvolverA.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
case 1: //IR2
out = zlConvolverB.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
case 2: //IR3
out = zlConvolverC.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
}
float wet = gGuiController.getSliderValue(gWetSlider);
float dry = gGuiController.getSliderValue(gDrySlider);
//this is the dry signal. we dont want to circular buffer this ever as
//we have outBuf which is the convolved signal which we are delaying.
in = in * dry;
//Overwrite the buffer at the write pointer
//Do this first so we can have the read and write pointers be equal
gDelayBuffer[gWritePointer] = out;
//read output from write pointer (oldest sample)
float outDelay = gDelayBuffer[gReadPointer];
//increment and wrap both pointers
gWritePointer++;
if(gWritePointer >= gDelayBuffer.size())
gWritePointer = 0;
gReadPointer++;
if(gReadPointer >= gDelayBuffer.size())
gReadPointer = 0;
outDelay = outDelay * wet;
for(unsigned int channel = 0; channel < context->audioOutChannels; ++channel)
audioWrite(context, n, channel, outDelay + in);
}
}
And what I'm doing with the analogue inputs:
void render(BelaContext *context, void *userData)
{
// Access the sliders specifying the index we obtained when creating then
int room = (int)gGuiController.getSliderValue(gRoomSlider);
int maxBlocks = (int)gGuiController.getSliderValue(gMaxBlocksSlider);
float sparsity = gGuiController.getSliderValue(gSparsitySlider);
//read the delay in seconds and convert to samples
// float delay = gGuiController.getSliderValue(gDelaySlider);
// int delayInSamples = delay * context->audioSampleRate;
//calculate read pointer based on write pointer
//gReadPointer = (gWritePointer - delayInSamples + gDelayBuffer.size()) % gDelayBuffer.size();
float inBuf[context->audioFrames]; // the buffer to be processed by the convolver
for(unsigned int n = 0; n < context->audioFrames; n++) {
float in = 0;
if(gProcessInput) {
in = audioRead(context, n, gInputChannel);
}
inBuf[n] = in;
}
float outBuf[context->audioFrames];
for (unsigned int n = 0; n < context->audioFrames; n++)
{
// Declare ADC inputs from potentiometers
float input0 = analogRead(context, n/2, 0);
float input1 = analogRead(context, n/2, 1);
// rt_printf("input0: %f\n", input0);
// Map ADC inputs to useful value range based on usage
float amplitudeDB = map(input0, 0, 3.3 / 4.096, -40, -6);
float delay = .19; //map(input1, 0, 3.3 / 4.096, 0, 0.24);
// rt_printf("amplitudeDB: %f\n", amplitudeDB);
// Further value adjustment
float dry = powf(10.0, amplitudeDB / 20); //convert dB to linear amplitude
// rt_printf("dry: %f\n", dry);
int delayInSamples = delay * context->audioSampleRate;
gReadPointer = (gWritePointer - delayInSamples + gDelayBuffer.size()) % gDelayBuffer.size();
float in = inBuf[n];
float out = outBuf[n];
// ... convolve it ...
switch (room)
{
case 0: //IR1
out = zlConvolverA.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
case 1: //IR2
out = zlConvolverB.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
case 2: //IR3
out = zlConvolverC.process(in, 1.0, 1.0, maxBlocks, sparsity);
break;
}
float wet = gGuiController.getSliderValue(gWetSlider);
//float dry = gGuiController.getSliderValue(gDrySlider);
//this is the dry signal. we dont want to circular buffer this ever as
//we have outBuf which is the convolved signal which we are delaying.
in = in * dry;
//Overwrite the buffer at the write pointer
//Do this first so we can have the read and write pointers be equal
gDelayBuffer[gWritePointer] = out;
//read output from write pointer (oldest sample)
float outDelay = gDelayBuffer[gReadPointer];
//increment and wrap both pointers
gWritePointer++;
if(gWritePointer >= gDelayBuffer.size())
gWritePointer = 0;
gReadPointer++;
if(gReadPointer >= gDelayBuffer.size())
gReadPointer = 0;
outDelay = outDelay * wet;
for(unsigned int channel = 0; channel < context->audioOutChannels; ++channel)
audioWrite(context, n, channel, outDelay + in);
}
}
Thank you!