thank you, the code you supplied seems to be able to play the first file but then it tries to continuously open both at the same time so the next files don't play. how should i edit it?
BELAMINI Music Player
- Edited
It works for me with the supplied files: it plays through all eight of them in a sequence. What lines do you get printed in the console?
- Edited
Playing from reader: 0
Playing from reader: 1
Playing from reader: 0
Playing from reader: 1
Playing from reader: 0
Playing from reader: 1
Playing from reader: 0
Playing from reader: 1
Playing from reader: 0
...
Did you edit the gFilenames
variable to point to your audio files? Can you show it? Do you get any error at startup?
when i changed the files to .wav it works. does bela not support .flac?
They should work, I have tried with one flac exported from Audacity with level 5 and 16 bits and it works just fine.
okay. thank you. also, as a follow up, how should i adapt the code to integrate a set of buttons (play/pause, replay, and skip) utilizing the gpio pins (58,57,59) and pull-down buttons?
I assume you are using the BelaMini and so these pins are D1, D2, D3?
In that case, you will want to read the button values repeatedly and detect a low-high transition, debounce that and use it to control the reproduction. One issue when introducing replay and skip is that you may want to also revisit the logic for loading the next audio file, as the code assumes that they are being played in sequence in a pre-established order.
- Edited
/*
____ _____ _ _
| __ )| ____| | / \
| _ \| _| | | / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/ \_\
http://bela.io
*/
/**
\example Audio/file-player/render.cpp
Playback of several large wav files
---------------------------
This example shows how to play audio files from a playlist sequentially.
It uses the AudioFileReader class, which loads data from the audio file into
memory in the background, while the user can request data safely from the
RT-thread via the AudioFileReader::getSamples() method.
We want to start playing back the next file as soon as the current one
finishes, so we need to start preloading the next one ahead of time. This is
done by using two AudioFileReader objects, where at any time one, whose index
is `gCurrReader`, will be playing back the current file while the other one
starts preloading the next file.
*/
#include <Bela.h>
#include <libraries/AudioFile/AudioFile.h>
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <thread>
#include <atomic>
std::vector<AudioFileReader> gReaders(2);
std::vector<std::string> gFilenames = {
"05._Toy_Ballerina.wav",
"sample-4.wav",
// "number2.wav",
// "number3.wav",
// "number4.wav",
// "number5.wav",
// "number6.wav",
// "number7.wav",
};
size_t gLoadingFile;
size_t gCurrReader;
std::vector<float> gSamples;
size_t gFrameCount = 0;
AuxiliaryTask gStartLoadingFileTask;
// defining button GPIO
#define PLAY_PAUSE_BUTTON_PIN "/sys/class/gpio/gpio59/value";
#define SKIP_BUTTON_PIN "/sys/class/gpio/gpio58/value";
#define REPLAY_BUTTON_PIN "/sys/class/gpio/gpio57/value";
std::atomic<bool> gPauseFlag(false); // Flag to indicate whether the song is paused
// Function to read the play/pause button state asynchronously
void readPlayPauseButtonState() {
std::ifstream playPauseButton("/sys/class/gpio/gpio59/value");
if (!playPauseButton.is_open()) {
std::cerr << "Failed to open play/pause button GPIO file." << std::endl;
return;
}
std::string play_state;
while (true) {
// Read the play/pause button state
std::getline(playPauseButton, play_state);
std::cout << "Play state: " << play_state << std::endl;
// Check the state and enter the while loop if paused
if (play_state == "1") {
std::cout << "Play state: " << play_state << std::endl;
// Toggle the pause flag
gPauseFlag.store(!gPauseFlag.load(std::memory_order_relaxed), std::memory_order_relaxed);
}
playPauseButton.clear(); // Clear any error flags
playPauseButton.seekg(0, std::ios::beg); // Move the get pointer to the beginning of the file
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Adjust sleep duration as needed
}
// Close the file
playPauseButton.close();
}
std::thread buttonThread(readPlayPauseButtonState);
// creats global variable for input stream and initializes
// std::ifstream playPauseButton(PLAY_PAUSE_BUTTON_PIN);
// std::ifstream skipButton(SKIP_BUTTON_PIN);
// std::ifstream replayButton(REPLAY_BUTTON_PIN);
void loadNextFile(void*)
{
// start preloading the next file
gLoadingFile = (gLoadingFile + 1) % gFilenames.size();
size_t nextReader = (gCurrReader + 1) % gReaders.size();
gReaders[nextReader].setup(gFilenames[gLoadingFile], 16384);
rt_printf("Opening file [%d] %s in reader %d\n", gLoadingFile, gFilenames[gLoadingFile].c_str(), nextReader);
}
bool setup(BelaContext *context, void *userData)
{
// create a task to load files
gStartLoadingFileTask = Bela_createAuxiliaryTask(loadNextFile, 1, "loadNextFile");
if(!gStartLoadingFileTask) {
fprintf(stderr, "Error creating file loading task\n");
return false;
}
gLoadingFile = -1;
gCurrReader = -1;
// open the first file
loadNextFile(NULL);
gCurrReader = 0;
// open the second file
loadNextFile(NULL);
gSamples.reserve(context->audioFrames * gReaders[gCurrReader].getChannels());
std::string line;
return true;
}
std::atomic<unsigned int> gFrameIndex(0);
void render(BelaContext *context, void *userData) {
AudioFileReader& reader = gReaders[gCurrReader];
// this call may allocate memory if getChannels() is larger than for
// all previous files
gSamples.resize(context->audioFrames * reader.getChannels());
reader.getSamples(gSamples);
unsigned int n = gFrameIndex.load(std::memory_order_relaxed);
for(; n < context->audioFrames; )
{
std::cout << "Current frame index: " << n << std::endl;
float out;
// Check if the song is paused
if (gPauseFlag.load(std::memory_order_relaxed)) {
// If paused, skip audio processing
gFrameIndex.fetch_sub(1, std::memory_order_relaxed);
n = gFrameIndex.load(std::memory_order_relaxed);
continue;
} else {
gFrameIndex.fetch_add(1, std::memory_order_relaxed);
n = gFrameIndex.load(std::memory_order_relaxed);
}
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);
}
// count samples played back for the existing file
gFrameCount++;
if(gFrameCount >= reader.getLength())
{
// reached end of file
gFrameCount = 0;
// start playing the file we preloaded
gCurrReader = (gCurrReader + 1) % gReaders.size();
reader.getSamples(gSamples);
rt_printf("Playing from reader: %d\n", gCurrReader);
// start loading next file in a real-time safe way
Bela_scheduleAuxiliaryTask(gStartLoadingFileTask);
}
}
}
void cleanup(BelaContext *context, void *userData)
{
buttonThread.join();
}
This is what I currently have but the music doesn't seem to play anymore and I get the warning
- *** Error in
/root/Bela/projects/yesnt/yesnt': corrupted size vs. prev_size: 0x00773f18 ***`
// Check if the song is paused if (gPauseFlag.load(std::memory_order_relaxed)) { // If paused, skip audio processing gFrameIndex.fetch_sub(1, std::memory_order_relaxed); n = gFrameIndex.load(std::memory_order_relaxed); continue; } else { gFrameIndex.fetch_add(1, std::memory_order_relaxed); n = gFrameIndex.load(std::memory_order_relaxed); }
what's all this stuff about? Why do you need atomics for the gFrameIndex
variable that is only used by one thread?
Also, n
needs to be strictly 0 <= n < context->audioFrames
. Any call to audioWrite(context, n, c, value)
with n
outside this range would cause memory corruption and in your code I see n
being incremented or decremented without taking into account these limits, hence your memory corruption errors. I am genuinely surprised you don't get a segmentation fault within a few milliseconds of starting the program and instead only get a corruption message at the end of it, but maybe I don't understand fully.
while (true)
That means the thread would never exit and so the join()
in cleanup()
would hang. It should instead check for the return of the Bela_shouldStop()
call that would inform it that it should exit the loop, e.g.:
while(!Bela_shouldStop())
{
...
}
Why are you reading the GPIO pins values via the sysfs interface instead of using Bela's API? Where did you learn about that?
- Edited
void render(BelaContext *context, void *userData) {
AudioFileReader& reader = gReaders[gCurrReader];
// this call may allocate memory if getChannels() is larger than for
// all previous files
gSamples.resize(context->audioFrames * reader.getChannels());
reader.getSamples(gSamples);
// while ( gFrameIndex.load(std::memory_order_relaxed) < context->audioFrames)
// {
for (int n = 0; n < context->audioFrames; ) {
// std::cout << "Current frame index: " << gFrameIndex.load(std::memory_order_relaxed) << std::endl;
float out;
// gFrameIndex.fetch_add(1, std::memory_order_relaxed);
// // Check if the song is paused
// if (gPauseFlag.load(std::memory_order_relaxed)) {
// // If paused, skip audio processing
// gFrameIndex.fetch_sub(1, std::memory_order_relaxed);
// continue;
// }
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() && !gPauseFlag.load(std::memory_order_relaxed)) {
out = gSamples[n * reader.getChannels() + c];
}
audioWrite(context, n, c, out);
}
if (!gPauseFlag.load(std::memory_order_relaxed)) {
// count samples played back for the existing file
gFrameCount++;
n++;
}
if(gFrameCount >= reader.getLength())
{
// reached end of file
gFrameCount = 0;
// start playing the file we preloaded
gCurrReader = (gCurrReader + 1) % gReaders.size();
reader.getSamples(gSamples);
rt_printf("Playing from reader: %d\n", gCurrReader);
// start loading next file in a real-time safe way
Bela_scheduleAuxiliaryTask(gStartLoadingFileTask);
}
}
}
the reason why I have to use an atomic integer for n is because when I use n++, i get this error -->
Xenomai/cobalt: watchdog triggered
Makefile:613: recipe for target 'runide' failed
CPU time limit exceeded
make: *** [runide] Error 152
ainithit the reason why I have to use an atomic integer for n is because when I use n++, i get this error -->
No, the reason why you get that is because your loop is infinite when the player is paused. That's not how real-time audio programming works: you should not just wait forever in a tight loop until a flag is set . Rather, you should process each frame in the block one at a time. If it's not paused, send one audio sample to the output of the current frame, if it's paused send zeros to the output of the frame. In your code, if gPauseFlag
is set n
doesn't get incremented and so the render()
function doesn't return and it occupies the CPU continuously for over 3 seconds, which is when the watchdog kills your program. This class may be relevant.
Why do you makegPauseFlag
atomic anyhow?