the file player example is designed to read files sequentially, preloading the next file before the current one ends (that's key to a gapless transition from one file to the next). As you see, it uses two AudioFileReader objects, one for the current file and one for the next file. The loadNextFile() function is called asynchronously to start preloading the next file. As the process of starting the preloading is itself not audio thread-safe, it is done in a dedicated thread (managed by the AuxiliaryTask API).
The AudioFileReader class preloads chunks of audio from disk in a separate thread, so they are ready for the audio thread to read and copy to the output. If you need to loop a given file, you can actually just call setLoop(true) on an AudioFileReader object and don't need two of them.
This is a simpler single-file example, which you should expand to the four voices:
#include <Bela.h>
#include <libraries/AudioFile/AudioFile.h>
#include <vector>
#include <string>
AudioFileReader reader;
std::vector<float> gSamples;
bool setup(BelaContext *context, void *userData)
{
reader.setup("number0.wav", 16384);
reader.setLoop(true);
gSamples.reserve(context->audioFrames * reader.getChannels());
return true;
}
void render(BelaContext *context, void *userData) {
gSamples.resize(context->audioFrames * reader.getChannels());
reader.getSamples(gSamples);
for(unsigned int n = 0; n < context->audioFrames; ++n)
{
float out = 0;
for(unsigned int c = 0; c < context->audioOutChannels; ++c)
{
// write each channel from the audio file to the
// output channels. If there are more output channels
// than channels in the file, copy the file's last
// channel to all remaining outputs
if(c < reader.getChannels())
out = gSamples[n * reader.getChannels() + c];
audioWrite(context, n, c, out);
}
}
}
void cleanup(BelaContext *context, void *userData)
{
}