The first mistake in the code above is having omitted the "for" loop so you are only processing 1/NFrames samples of the input. See below pass-through example to see how sample-by-sample processing is done in Bela.
for(unsigned int n = 0; n < context->audioFrames; n++) {
float out_l = 0;
float out_r = 0;
// Read audio inputs
out_l = audioRead(context,n,0);
out_r = audioRead(context,n,1);
// do something with out_l
// do something with out_r
// Write the sample into the output buffer -- done!
audioWrite(context, n, 0, out_l);
audioWrite(context, n, 1, out_r);
}
Usually an audio delay is implemented using a circular buffer. If you're not familiar with the term, it's nothing more than an array much like "array1[]" above. The difference is a "read" and "write" pointer are used, where "read" always stays "delay" samples behind "write", and "write" overwrites samples that are older than what "read" is getting.
Here is a brief example for how you might manage something like this in the render() loop.
unsigned int gMaxDelaySamples = 44100; //buffer can store up to 1 second delay.
//Separate function needed
//to re-initialize gReadPointer to a larger delay
//(for example if changing delay
//due to ADC reading from a pot)
unsigned int gDelaySamples = unsigned int(0.25*gMaxDelaySamples); //1/4 second delay
unsigned int gWritePointer = gDelaySamples;
unsigned int gReadPointer = 0;
float gCircbuf[gMaxDelaySamples];
bool setup(BelaContext *context, void *userData)
{
gWritePointer = gDelaySamples;
gReadPointer = 0;
// There is always a chance 1rst second will output noise on startup,
// so initializing gCircbuf to all silence is a good practice
for(unsigned int i = 0; i < gMaxDelaySamples; i++)
{
gCircbuf[i] = 0.0;
}
return true;
}
void render(BelaContext *context, void *userData)
{
float in_l = 0;
float in_r = 0;
// Notice loop over all audioFrames contained in input buffer is size configured
// for number of frames per period
for(unsigned int n = 0; n < context->audioFrames; n++)
{
// Read audio inputs
in_l = audioRead(context,n,0);
in_r = audioRead(context,n,1);
// To simplify the example, let's process as mono
float input = in_l + in_r;
// Then write it to the circular buffer
gCircbuf[gWritePointer] = input;
// Then read the circular buffer gDelaySamples behind gWritePointer
float output = 0.5*gCircbuf[gReadPointer];
if(++gReadPointer >= gMaxDelaySamples)
gReadPointer = 0;
if(++gWritePointer >= gMaxDelaySamples)
gWritePointer = 0;
// Write the sample into the output buffer -- done!
audioWrite(context, n, 0, output); // Left
audioWrite(context, n, 1, output); // Right
}
} //render
The following example has more to it than a simple delay, but if you are eventually trying to figure out how to make an echo effect, this example will help give ideas how to do this:
https://github.com/BelaPlatform/Bela/blob/master/examples/Audio/delay/render.cpp
The example is more of a natural echo effect, but it contains the basic principle of a circular buffer. You will notice in the BelaPlatform example, subtraction of number of delay samples and modulo division are used rather than tracking two pointers separately. The principle is the same in that the output is being taking from the buffer a certain number of samples behind where the buffer is being written. The write pointer only overwrites old samples that have already been copied to the output.