• Audio
  • BELAMINI Music Player

Yeah essentially, but right now it seems to be mixing sections of the songs together.

yup, that's what it does: it plays several audio files at once. If what you need is to play audio files one after the other, start from here.

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?

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?

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.

6 days later

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.

/*
 ____  _____ _        _
| __ )| ____| |      / \
|  _ \|  _| | |     / _ \
| |_) | |___| |___ / ___ \
|____/|_____|_____/_/   \_\
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 ***`

    ainithit

    // 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.

    ainithit

     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?

    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?