Part of a larger restructure, added MusicPlayer which is the main controller and a structure for the song queue

This commit is contained in:
Crylia
2024-03-26 12:03:41 +01:00
parent a2872f6479
commit 9023766c51
67 changed files with 1218 additions and 262 deletions

View File

@@ -0,0 +1,47 @@
#pragma once
#include <iostream>
template<typename T>
class SongHistory {
private:
struct Node {
T data;
Node* next;
};
Node* topNode = nullptr;
public:
SongHistory( ) { }
~SongHistory( ) {
while (!isEmpty( ))
pop( );
}
void push(const T& value) {
Node* newNode = new Node;
newNode->data = value;
newNode->next = topNode;
topNode = newNode;
}
void pop( ) {
if (!isEmpty( )) {
Node* tmp = topNode;
topNode = topNode->next;
delete tmp;
}
else
std::cerr << "Stack empty, cannot pop" << std::endl;
}
T top( ) {
if (!isEmpty( ))
return topNode->data;
else
return nullptr;
}
bool isEmpty( ) const { return topNode == nullptr; }
};

View File

@@ -0,0 +1,186 @@
#include "ConditionalCircularLinkedList.h"
ConditionalCircularLinkedList::ConditionalCircularLinkedList( ) { }
void ConditionalCircularLinkedList::Append(Song* song) {
Node* newNode = new Node(song);
if (head == nullptr) {
head = newNode;
// Only link back when its supposed to
if (isLinked)
head->next = head;
current = head;
}
else {
Node* tmp = head;
while (tmp->next != head)
tmp = tmp->next;
tmp->next = newNode;
// Again make sure to only link when supposed to
if (isLinked)
newNode->next = head;
}
}
void ConditionalCircularLinkedList::SetListMode(bool isLooping) {
if (head == nullptr) {
std::cout << "WARN: List is empty, cannot set mode" << std::endl;
return;
}
Node* tmp = head;
while (tmp->next != head)
tmp = tmp->next;
if (!isLooping)
tmp->next = nullptr;
else
tmp->next = head;
}
void ConditionalCircularLinkedList::Clear( ) {
while (head != nullptr) {
Node* tmp = head;
head = head->next;
delete tmp;
delete current;
}
}
void ConditionalCircularLinkedList::Advance( ) {
if (current != nullptr)
current = current->next;
}
Song* ConditionalCircularLinkedList::GetNext( ) {
if (current != nullptr)
return current->data;
}
void ConditionalCircularLinkedList::AddSongBefore(Song* newSong, Song* addSongBefore) {
if (head == nullptr) return;
Node* newNode = new Node(newSong);
Node* current = head;
Node* prev = nullptr;
do {
if (current->data == addSongBefore) {
if (prev == nullptr) {
newNode->next = head;
head = newNode;
Node* lastNode = head;
while (lastNode->next != head)
lastNode = lastNode->next;
if (isLinked)
lastNode->next = head;
}
else {
prev->next = newNode;
newNode->next = current;
}
return;
}
prev = current;
current = current->next;
} while (current != head);
}
void ConditionalCircularLinkedList::AddSongAfter(Song* newSong, Song* addSongAfter) {
if (head == nullptr) return;
Node* newNode = new Node(newSong);
Node* current = head;
do {
if (current->data == addSongAfter) {
newNode->next = current->next;
current->next = newNode;
return;
}
current = current->next;
} while (current != head);
}
Song* ConditionalCircularLinkedList::RemoveSong(Song* removeSong) {
if (head == nullptr) return nullptr;
Node* current = head;
Node* prev = nullptr;
do {
if (current->data == removeSong) {
if (prev == nullptr) {
head = current->next;
Node* lastNode = head;
while (lastNode->next != current)
lastNode = lastNode->next;
if (isLinked)
lastNode->next = head;
Song* deletedSong = current->data;
delete current;
return deletedSong;
}
else {
prev->next = current->next;
Song* deletedSong = current->data;
delete current;
return deletedSong;
}
}
prev = current;
current = current->next;
} while (current != head);
}
bool ConditionalCircularLinkedList::IsEmpty( ) {
return current == nullptr;
}
void ConditionalCircularLinkedList::Shuffle( ) {
int cnt = 0;
Node* tmp = head;
while (tmp != nullptr) {
cnt++;
tmp = tmp->next;
}
Node** nodes = new Node * [cnt];
tmp = head;
for (int i = 0; i < cnt; i++) {
nodes[i] = tmp;
tmp = tmp->next;
}
for (int i = cnt; i > 0; i--) {
int j = rand( ) % (i + 1);
Node* tmp = nodes[i];
nodes[i] = nodes[j];
nodes[j] = tmp;
}
head = nodes[0];
for (int i = 0; i < cnt - 1; i++)
nodes[i]->next = nodes[i + 1];
nodes[cnt - 1]->next = nullptr;
delete[] nodes;
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <iostream>
#include "../../song/song.h"
class ConditionalCircularLinkedList {
private:
struct Node {
Node* next;
Song* data;
Node(Song* song) : data(song), next(nullptr) { }
};
Node* head;
// Variable to keep the current position that we seek
Node* current;
// Variable to either link or unlink the list
bool isLinked;
public:
ConditionalCircularLinkedList( );
ConditionalCircularLinkedList(bool isLinked) : head(nullptr), isLinked(isLinked) { }
/**
* @brief Append a new song to the queue
*
* @param song
*/
void Append(Song* song);
/**
* @brief Clear the entire list so its brand spanking new.
*
*/
void Clear( );
/**
* @brief Depending on if the queue is supposed to be looping, this will link
* up the end of the list, with its beginning to its a circular list.
* If its not supposed to loop then we break the link and it will be a normal list.
* @param isLooping Will set the list to circular or normal mode
*/
void SetListMode(bool isLooping);
/**
* @brief Advance the circular list by one, this means setting the "current" pointer
* to the next element
*
*/
void Advance( );
/**
* @brief Get the "current" pointer's data
*
* @return Song*
*/
Song* GetNext( );
void AddSongBefore(Song* newSong, Song* addSongBefore);
void AddSongAfter(Song* newSong, Song* addSongAfter);
Song* RemoveSong(Song* removeSong);
bool IsEmpty( );
void Shuffle( );
};

View File

@@ -0,0 +1,94 @@
#include "SongQueue.h"
SongQueue::SongQueue( ) {
queue = new ConditionalCircularLinkedList( );
// Its never going to be linked, I'm just too lazy to make a new implementation thats almost the same
priorityQueue = new ConditionalCircularLinkedList(false);
}
SongQueue::~SongQueue( ) { }
void SongQueue::PopulateQueue(const std::list<Song*>& songList) {
for (Song* song : songList) {
queue->Append(song);
}
}
void SongQueue::Next( ) {
if (priorityQueue->IsEmpty( )) {
topSong = queue->GetNext( );
queue->Advance( );
return;
}
topSong = priorityQueue->GetNext( );
priorityQueue->Advance( );
}
void SongQueue::JumpToSong(Song* song, bool fromPrioQueue) {
if (fromPrioQueue) {
if (priorityQueue->IsEmpty( ))
return;
while (priorityQueue->GetNext( ) != song) {
priorityQueue->Advance( );
topSong = song;
}
}
if (queue->IsEmpty( ))
return;
while (queue->GetNext( ) != song) {
queue->Advance( );
topSong = song;
}
}
void SongQueue::AddToPriorityQueue(Song* song) {
priorityQueue->Append(song);
}
bool SongQueue::IsEmpty( ) {
return queue->IsEmpty( ) && priorityQueue->IsEmpty( );
}
Song* SongQueue::Top( ) { return topSong; }
void SongQueue::SetTop(Song* song) { topSong = song; }
void SongQueue::RemoveSongFromPriorityQueue(Song* song) {
if (priorityQueue->IsEmpty( ))
return;
priorityQueue->RemoveSong(song);
}
void SongQueue::MoveSongInPriorityQueue(Song* songToMove, Song* otherSong, bool beforeElseAfter) {
if (priorityQueue->IsEmpty( ))
return;
if (beforeElseAfter)
priorityQueue->AddSongBefore(songToMove, otherSong);
else
priorityQueue->AddSongAfter(songToMove, otherSong);
}
void SongQueue::ShufflePlaylist( ) {
//First backup the original list state
queue_original = new ConditionalCircularLinkedList(queue);
queue->Shuffle( );
}
void SongQueue::RestorePlaylist( ) {
if (queue_original == nullptr) return;
queue = new ConditionalCircularLinkedList(queue_original);
}
void SongQueue::LinkQueue(bool link) {
queue->SetListMode(link);
}

View File

@@ -0,0 +1,61 @@
#pragma once
#include <iostream>
#include <list>
#include "../song/song.h"
#include "ConditionalCircularLinkedList/ConditionalCircularLinkedList.h"
class SongQueue {
private:
// The currently playing song
Song* topSong;
//Regular song queue, can be unlinked for non looping playlists
ConditionalCircularLinkedList* queue;
//Copy of queue to faster restore when unshuffling
ConditionalCircularLinkedList* queue_original;
//The name is confusing, but its because the queue itself has priority over the normal one
ConditionalCircularLinkedList* priorityQueue;
void setQueue( );
public:
SongQueue( );
~SongQueue( );
void PopulateQueue(const std::list<Song*>& songlist);
/**
* @brief Will put the next song from the correct sublist as the topSong.
* This will also remove the song from the queue as its not needed anymore.
*
*/
void Next( );
/**
* @brief Will put the given song from the priority or normal queue as the top song and remove it
* from the list
*
* @param song
*/
void JumpToSong(Song* song, bool fromPrioQueue);
void AddToPriorityQueue(Song* song);
bool IsEmpty( );
Song* Top( );
void SetTop(Song* song);
void RemoveSongFromPriorityQueue(Song* song);
void MoveSongInPriorityQueue(Song* songToMove, Song* otherSong, bool beforeElseAfter);
void ShufflePlaylist( );
void RestorePlaylist( );
void LinkQueue(bool link);
};

View File

@@ -1,6 +1,6 @@
#include "audio.h"
Audio::Audio(const std::string path) :path(path) {
Audio::Audio( ) {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
std::cerr << "SDL initialization failed: " << SDL_GetError( ) << std::endl;
return;
@@ -11,14 +11,6 @@ Audio::Audio(const std::string path) :path(path) {
SDL_Quit( );
return;
}
music = Mix_LoadMUS(path.c_str( ));
if (!music) {
std::cerr << "Failed to load MP3 file: " << Mix_GetError( ) << std::endl;
Mix_CloseAudio( );
SDL_Quit( );
return;
}
};
Audio::~Audio( ) {
@@ -34,6 +26,22 @@ void Audio::StartMusic( ) {
}
}
void Audio::StopMusic( ) {
Mix_HaltMusic( );
}
void Audio::PlaySong(const std::string path) {
this->path = path;
music = Mix_LoadMUS(path.c_str( ));
if (!music) {
std::cerr << "Failed to load MP3 file: " << Mix_GetError( ) << std::endl;
Mix_CloseAudio( );
SDL_Quit( );
return;
}
}
void Audio::PauseMusic( ) {
if (Mix_PlayingMusic( ) == 1) {
Mix_PauseMusic( );

View File

@@ -14,16 +14,16 @@ extern "C" {
class Audio {
public:
static Audio& getInstance(const std::string path) {
static Audio instance(path);
static Audio& getInstance( ) {
static Audio instance;
return instance;
}
private:
Audio(const std::string path);
Audio( );
const std::string path;
const std::string artist;
const std::string album;
std::string path;
std::string artist;
std::string album;
Mix_Music* music;
@@ -43,6 +43,9 @@ public:
QPixmap GetAlbumCover( );
void PlaySong(const std::string path);
void StopMusic( );
void StartMusic( );
void PauseMusic( );
void ResumeMusic( );

36
src/core/song/song.cpp Normal file
View File

@@ -0,0 +1,36 @@
#include "song.h"
Song::Song(
std::string title,
std::string album,
std::string artist,
std::string codec,
std::string comment,
std::string copyright,
std::string publisher,
std::string genre,
std::string encoded_by,
std::string date,
std::string language,
std::string albumCoverPath,
std::string path,
int length,
std::string discoveredPath
) : title(title),
album(album),
artist(artist),
codec(codec),
comment(comment),
copyright(copyright),
publisher(publisher),
genre(genre),
encoded_by(encoded_by),
date(date),
language(language),
albumCoverPath(albumCoverPath),
path(path),
length(length) {
}
Song::~Song( ) { }

54
src/core/song/song.h Normal file
View File

@@ -0,0 +1,54 @@
#pragma once
#include <string>
class Song {
private:
// Song information
const std::string& title;
const std::string& album;
const std::string& artist;
const std::string& codec;
const std::string& comment;
const std::string& copyright;
const std::string& publisher;
const std::string& genre;
const std::string& encoded_by;
const std::string& date;
const std::string& language;
const std::string& albumCoverPath;
const std::string& path;
const int& length;
// Our own Metadata
bool favorite = false;
int playCount = 0;
std::string discovered;
public:
Song(
std::string title,
std::string album,
std::string artist,
std::string codec,
std::string comment,
std::string copyright,
std::string publisher,
std::string genre,
std::string encoded_by,
std::string date,
std::string language,
std::string albumCoverPath,
std::string path,
int length,
std::string discoveredPath
);
~Song( );
void SetFavorite(bool fav) { favorite = fav; }
void IncrementPlayCount( ) { playCount++; }
std::string GetPath( ) { return path; };
};