Writing a Music Player Using SDL2: Part 1

Time has flown by without a blog post, and it's about dang time to release a little something new. Over this post-free time, I have learned several things worthy of sharing. For starters, everyone loves music and it's mission critical for most people to have a music player so they can jam out when working. So let's make one!

Why Make a Music Player When There's So Many Good Ones?

I have over 7 terabytes of music, and every time I've attempted to load my entire playlist of music into popular apps, I wind up cranking an i9-9900K with 64 GB of RAM to nearly 100% usage and cause my system to freeze. Due to this, I figured I would write a simple, lightweight, terminal-based program that would provide the flexibility needed to listen to music without any extra bloat, and could be extensible with bash scripts using something like ncurses. Let me introduce you to what I have accomplished so far for this program!

The data.h File

Below is a full source code listing with the file data.h, which contains info necessary for playback and playlists. Pardon the text wrapping! :)

#ifndef _DATA_H
#define _DATA_H

// This contains all necessary data to power the core application,
// from playlist management to playing/pausing songs.

// C++ and C stdlib
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <bits/stdc++.h>

// LibSDL2 headers for music playback
#include "SDL2/SDL.h"
#include "SDL2/SDL_mixer.h"

// Since this is C++, we want to add the std namespace
// To our code to simplify what we write.
using namespace std;

// Add the basic ability to enqueue and dequeue songs.
// Allows us to play more than one song.
struct Queue { 
    stack<char *> songs; 
  
    // Enqueue an item to the queue 
    void enQueue(char * x) { 
        songs.push(x); 
    } 
  
    // Dequeue an item from the queue 
    int deQueue(void) {
	    if (songs.empty()) {
		    cout << "Playlist is empty!" << endl;
		    exit(0);
	    }
	    
	    // pop an item from the songs stack
	    int x = songs.top();
	    songs.pop();
	    
	    // if stack becomes empty, return 
	    // the popped item.
	    if (songs.empty()) {
		    return x;
	    }
	    
	    int item = deQueue(); //recursive call
	    songs.push(x); //push popped item back to stack 
	    
	    return item; //return result of deQueue() call 
    } 
};

// This is responsible for all music playback and controls.
typedef class sdl2tools {
	private:
		Mix_Music *music;
	public:
		Queue NowPlaying;

               //Play the music by initializing SDL2 and loading
               //the actual tune.
		void play(char * Path) {
                	 int result = 0;
	                int flags = MIX_INIT_MP3;
                
                        //If loading SDL2 fails, exit program
                        //and return an error.
	                if (SDL_Init(SDL_INIT_AUDIO) < 0) {
	                        cout << "Failed to init SDL" << endl;
	                        exit(1);
	                }
                
                        //If the audio mixer fails to load, exit
                        //program and return related error.
	                if (flags != (result = Mix_Init(flags))) {
        	                cout << "Could not initialize mixer. Error: " << result << endl;
                	        cout << "Mix_Init: " << Mix_GetError() << endl;
                        	exit(1);
                	}

                       //Actually load and play the music.
	               Mix_OpenAudio(22050, AUDIO_S16SYS, 2, 640);
			music = Mix_LoadMUS(Path);
			Mix_PlayMusic(music, 1)
	        }

               //Since the second argument of Mix_PlayMusic() from
               //SDL2's API mandates an audio file as first
               //argument and a boolean "is playing" status as #2,
               //setting the second argument to 0 pauses music.
		void pause(void) {
			Mix_PlayMusic(music, 0);
		}

               //Resume music.
		void resume(void) {
			Mix_PlayMusic(music, 1);
		}

               //Stop playing music by quitting SDL2 cleanly.
		void stop(void) {
			while (!SDL_QuitRequested()) {
				SDL_Delay(250);
			}

			Mix_FreeMusic(music);
			SDL_Quit();
		}

               //Skip currently playing song. Dependent on having
               //a song to skip to.
		void skip(void) {
			music.stop();
			NowPlaying.deQueue();
			music.play(path);
		}

}sdl2tools;

#endif

In this custom header file, we start by defining the core data structures that will make up our ability to have playlists, using the struct Queue. This will be need to be used in our main program with multithreading so we can run the music player in the background with a “playlist”.

We then go on to define the sdl2tools class, which contains all the code needed to actually play our music and manipulate the audio stream, from playing to pausing or even skipping songs. With this done, we wrap up writing our header file and save it. With this prototype code done, it's now of prime importance to introduce a main.cpp file where multithreading, command-line arguments, and playing music in the background will be implemented. On top of this, a help menu is necessary to explain to users how everything works... and maybe introduce an easter egg or two :)

Until next time!

Liked This Content? Check Out Our Discord Community and Become an email subscriber!

This source code is MIT Licensed in it's own repository by the owner.