Added music player, next is the sound effects and loading music depending on the scene

This commit is contained in:
Ethan 2025-03-28 11:30:07 -04:00
parent 268e6d6ebf
commit 1b6f5cff5b
10 changed files with 368 additions and 2 deletions

BIN
Resources/music/bright.ogg Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -9,6 +9,7 @@ pkg_check_modules(LuaJIT REQUIRED IMPORTED_TARGET GLOBAL luajit)
find_package(sol2 REQUIRED) find_package(sol2 REQUIRED)
find_package(tinyxml2 REQUIRED) find_package(tinyxml2 REQUIRED)
find_package(Freetype REQUIRED) find_package(Freetype REQUIRED)
find_package(OpenAL REQUIRED)
find_package(glm CONFIG REQUIRED) find_package(glm CONFIG REQUIRED)
@ -32,6 +33,9 @@ find_package(glm CONFIG REQUIRED)
add_executable (YuppleMayham add_executable (YuppleMayham
"src/main.cpp" "src/main.cpp"
"src/thirdparty/glad.c" "src/thirdparty/glad.c"
"src/sound/engine.cpp"
"src/sound/audiostream.cpp"
"include/thirdparty/stb_vorbis.c"
"src/utility/data/font_data.c" "src/utility/data/font_data.c"
"src/utility/ftfont.cpp" "src/utility/ftfont.cpp"
"src/graphics/sprite.cpp" "src/graphics/sprite.cpp"
@ -98,6 +102,6 @@ endif()
target_include_directories(YuppleMayham PRIVATE "${PROJECT_SOURCE_DIR}/YuppleMayham/include" ${LuaJIT_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIR_ft2build}) target_include_directories(YuppleMayham PRIVATE "${PROJECT_SOURCE_DIR}/YuppleMayham/include" ${LuaJIT_INCLUDE_DIRS} ${FREETYPE_INCLUDE_DIR_ft2build})
target_link_libraries(YuppleMayham SDL2::SDL2main SDL2::SDL2 SDL2_image::SDL2_image glm::glm-header-only sol2 tinyxml2 freetype ${LuaJIT_LINK_LIBRARIES}) target_link_libraries(YuppleMayham SDL2::SDL2main SDL2::SDL2 SDL2_image::SDL2_image openal glm::glm-header-only sol2 tinyxml2 freetype ${LuaJIT_LINK_LIBRARIES})
# TODO: Add tests and install targets if needed. # TODO: Add tests and install targets if needed.

View file

@ -10,6 +10,7 @@ class Scene;
class Text; class Text;
class ResourceManager; class ResourceManager;
class Renderer; class Renderer;
class AudioEngine;
class GLWindow; class GLWindow;
enum { enum {
@ -38,7 +39,7 @@ public:
const unsigned getWindowWidth() const; const unsigned getWindowWidth() const;
const unsigned getWindowHeight() const; const unsigned getWindowHeight() const;
void quit() { game_state = GAME_QUITTING; } void quit();
~Game(); ~Game();
private: private:
@ -51,6 +52,7 @@ private:
std::shared_ptr<ResourceManager> resourceManager; std::shared_ptr<ResourceManager> resourceManager;
std::shared_ptr<Renderer> renderer; std::shared_ptr<Renderer> renderer;
std::shared_ptr<Text> textHandler; std::shared_ptr<Text> textHandler;
std::shared_ptr<AudioEngine> audioEngine;
std::shared_ptr<EventManager> globalEventManager; std::shared_ptr<EventManager> globalEventManager;
}; };

View file

@ -0,0 +1,63 @@
#ifndef _H_AUDIO_STREAM_H
#define _H_AUDIO_STREAM_H
#define STB_VORBIS_HEADER_ONLY
#include "thirdparty/stb_vorbis.c"
#include "utility/logger.h"
#include <AL/al.h>
#include <AL/alc.h>
#include <string>
#include <queue>
#include <mutex>
#include <thread>
namespace AUDIO {
constexpr size_t CHUNK_SIZE = 4096;
constexpr int SAMPLE_RATE = 44100;
}
class AudioStream
{
public:
AudioStream();
bool PlayStream();
void AddToQueue(std::string songName);
void PauseStream() { paused = !paused; }
void kill() { stopThread = true; }
~AudioStream();
private:
std::queue<std::string> songNames;
std::mutex mutex;
std::atomic<bool> stopThread;
std::atomic<bool> paused;
std::string CurStreamName();
bool eof;
std::thread streamThread;
int loadInitalChunk();
int loadNextChunk();
int loadChunk(ALuint buffer);
void popQueue();
void cycleQueue();
bool openFile(std::string fileName);
void stream();
short *data;
stb_vorbis *file;
stb_vorbis_info info;
ALuint buffers[4];
ALuint source;
ALint state;
ALint format;
size_t last_buffer;
};
#endif // _H_AUDIO_STREAM_H

View file

@ -0,0 +1,34 @@
#ifndef _H_AUDIO_ENGINE_H
#define _H_AUDIO_ENGINE_H
#include <AL/al.h>
#include <AL/alc.h>
#include <memory>
#include "utility/events.h"
#include "sound/audiostream.h"
#include "utility/resourcemanager.h"
#include "utility/logger.h"
class AudioEngine
{
public:
AudioEngine(std::weak_ptr<ResourceManager> _resource);
void hookEventManager(std::weak_ptr<EventManager> _events);
void pushMusic(std::string _songName);
void playMusic();
void pauseMusic();
void killMusic();
~AudioEngine();
private:
std::unique_ptr<AudioStream> musicPlayer;
std::weak_ptr<EventManager> globalEventManager;
std::weak_ptr<ResourceManager> resourceManager;
ALCdevice *device;
ALCcontext *context;
};
#endif // _H_AUDIO_ENGINE_H

View file

@ -13,6 +13,8 @@
#include "graphics/glwindow.h" #include "graphics/glwindow.h"
#include "sound/engine.h"
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <memory> #include <memory>
#include <utility/events.h> #include <utility/events.h>
@ -54,6 +56,14 @@ bool Game::init()
globalEventManager = std::make_shared<EventManager>(); globalEventManager = std::make_shared<EventManager>();
resourceManager = std::make_shared<ResourceManager>(); resourceManager = std::make_shared<ResourceManager>();
renderer = std::make_shared<Renderer>(resourceManager); renderer = std::make_shared<Renderer>(resourceManager);
audioEngine = std::make_shared<AudioEngine>(resourceManager);
audioEngine->hookEventManager(globalEventManager);
/* Testing */
audioEngine->pushMusic("music/short_song.ogg");
audioEngine->pushMusic("music/bright.ogg");
audioEngine->pushMusic("music/main_song.ogg");
audioEngine->playMusic();
/* */
renderer->hookEventManager(globalEventManager); renderer->hookEventManager(globalEventManager);
textHandler = std::make_shared<Text>(); textHandler = std::make_shared<Text>();
if (!textHandler->loadFonts("fonts")) if (!textHandler->loadFonts("fonts"))
@ -108,6 +118,14 @@ void Game::render()
window->swap(); window->swap();
} }
void Game::quit()
{
game_state = GAME_QUITTING;
if (audioEngine) {
audioEngine->killMusic();
}
}
Game::~Game() Game::~Game()
{ {
resourceManager->clearResources(); resourceManager->clearResources();

View file

@ -0,0 +1,193 @@
#include "sound/audiostream.h"
#include <AL/al.h>
#include <thread>
AudioStream::AudioStream()
{
alGenBuffers(4, buffers);
alGenSources(1, &source);
data = (short *)std::malloc(AUDIO::CHUNK_SIZE * sizeof(short) * 2); // Make space for stereo even if we are playing mono
stopThread = false;
eof = true;
paused = true;
streamThread = std::thread(&AudioStream::stream, this);
streamThread.detach();
}
std::string AudioStream::CurStreamName()
{
std::lock_guard<std::mutex> lock(mutex);
return songNames.front();
}
bool AudioStream::PlayStream()
{
if (CurStreamName().empty()) {
LOG(WARN, "Song queue empty!", NULL);
return false;
}
LOG(INFO, "Current song is: {}", CurStreamName());
return (paused = true);
}
void AudioStream::AddToQueue(std::string songName)
{
std::lock_guard<std::mutex> lock(mutex);
LOG(INFO, "Adding song {} to queue", songName);
songNames.push(songName);
}
bool AudioStream::openFile(std::string fileName)
{
int error = 0;
file = stb_vorbis_open_filename(CurStreamName().c_str(), &error, NULL);
if (file == nullptr || error != STBVorbisError::VORBIS__no_error) {
LOG(ERROR, "Failed to open file: '{}' error code: {}", CurStreamName(), error);
return false;
}
info = stb_vorbis_get_info(file);
if (info.sample_rate != AUDIO::SAMPLE_RATE) {
LOG(ERROR, "Failed to open file: '{}', make sure to convert sample rate to 44100!", CurStreamName());
stb_vorbis_close(file);
return false;
}
stb_vorbis_seek_start(file);
std::free(data);
data = (short *)std::malloc(AUDIO::CHUNK_SIZE * sizeof(short) * info.channels);
return true;
}
int AudioStream::loadChunk(ALuint buffer)
{
int sample = 0;
if (file == NULL) {
return sample;
}
if (info.channels == 0) {
return sample;
}
sample = stb_vorbis_get_samples_short_interleaved(file, info.channels, data, AUDIO::CHUNK_SIZE);
if (sample == 0)
return sample;
format = info.channels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
//LOG(INFO, "Buffer id {}, Format {}, data size {}, sample rate {},\nfilename {}",
// buffer, format == AL_FORMAT_MONO16 ? "MONO" : "STEREO", sample * info.channels * sizeof(short), info.sample_rate, songNames.front());
alBufferData(buffer, format, data, sample * info.channels * sizeof(short), info.sample_rate);
return sample;
}
int AudioStream::loadInitalChunk()
{
int samples = 0;
if (!openFile(CurStreamName())) {
return samples;
}
for (int i = 0; i < 4; i++) {
samples = loadChunk(buffers[i]);
if (samples == 0) {
LOG(ERROR, "Music file {} too small!", CurStreamName());
break;
}
}
return samples;
}
int AudioStream::loadNextChunk()
{
int samples = loadChunk(buffers[last_buffer]);
return samples;
}
void AudioStream::popQueue()
{
std::lock_guard<std::mutex> lock(mutex);
if (songNames.empty())
return;
songNames.pop();
}
void AudioStream::cycleQueue()
{
std::lock_guard<std::mutex> lock(mutex);
LOG(INFO, "Cycling queue", NULL);
if (songNames.empty())
return;
std::string buffer = songNames.front();
songNames.pop();
songNames.push(buffer);
LOG(INFO, "New front: {}", songNames.front());
}
void AudioStream::stream()
{
int processed = 0;
int queued = 0;
bool inital = true;
std::this_thread::sleep_for(std::chrono::seconds(1));
//LOG(INFO, "Call from in audio thread!", NULL);
//LOG(INFO, "Top of queue is: {}", CurStreamName());
paused = false;
while (!stopThread) {
if (songNames.empty() || paused) {
LOG(INFO, "Paused or queue empty", NULL);
continue;
}
alGetSourcei(source, AL_SOURCE_STATE, &state);
if (inital) {
int samples = loadInitalChunk();
if (samples == 0) {
LOG(ERROR, "Music file '{}' either failed to load or not big enough!", CurStreamName());
popQueue();
continue;
}
alSourceQueueBuffers(source, 4, buffers);
alSourcePlay(source);
alGetSourcei(source, AL_SOURCE_STATE, &state);
inital = false;
eof = false;
}
// If we are at the end of file, the and stream queue has a song in it
// Load in the inital buffers
if (state != AL_PLAYING) {
alSourcePlay(source);
}
while (state == AL_PLAYING) {
if (paused) {
alSourcePause(source);
}
alGetSourcei(source, AL_SOURCE_STATE, &state);
alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
while (processed != 0) {
alSourceUnqueueBuffers(source, 1, &buffers[last_buffer]);
int samples = loadNextChunk();
if (samples == 0) {
LOG(INFO, "Hit the end of the song!", NULL);
cycleQueue();
stb_vorbis_close(file);
while (!openFile(CurStreamName())) {
LOG(ERROR, "Failed to open next in stream! {}", CurStreamName());
popQueue();
}
} else {
}
//LOG(INFO, "Buffer index: {}", last_buffer);
alSourceQueueBuffers(source, 1, &buffers[last_buffer]);
last_buffer = (last_buffer + 1) % 4;
processed--;
}
}
}
}
AudioStream::~AudioStream()
{
std::free(data);
alDeleteBuffers(4, buffers);
alDeleteSources(1, &source);
}

View file

@ -0,0 +1,52 @@
#include "sound/engine.h"
#include <AL/al.h>
#include <AL/alc.h>
AudioEngine::AudioEngine(std::weak_ptr<ResourceManager> _resource)
{
// Open the default device for now
if ((device = alcOpenDevice(NULL)) == NULL) {
LOG(ERROR, "Failed to open default device {}", alGetError());
return;
}
context = alcCreateContext(device, NULL);
alcMakeContextCurrent(context);
musicPlayer = std::make_unique<AudioStream>();
resourceManager = _resource;
alGetError();
}
void AudioEngine::hookEventManager(std::weak_ptr<EventManager> _events)
{
globalEventManager = _events;
}
void AudioEngine::pushMusic(std::string _songName)
{
LOG(INFO, "Loading song {}", _songName);
musicPlayer->AddToQueue(_songName);
}
void AudioEngine::playMusic()
{
LOG(INFO, "Playing stream", NULL);
musicPlayer->PlayStream();
}
void AudioEngine::pauseMusic()
{
musicPlayer->PauseStream();
}
void AudioEngine::killMusic()
{
musicPlayer->kill();
}
AudioEngine::~AudioEngine()
{
musicPlayer->kill();
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);
}