Updated game structure
Some checks failed
Build and Package Tetris / build-windows (push) Has been cancelled
Build and Package Tetris / build-linux (push) Has been cancelled

This commit is contained in:
2025-08-16 12:10:19 +02:00
parent 71648fbaeb
commit 2afaea7fd3
36 changed files with 665 additions and 382 deletions

252
src/audio/Audio.cpp Normal file
View File

@ -0,0 +1,252 @@
// Audio.cpp - Windows Media Foundation MP3 decoding
#include "audio/Audio.h"
#include <SDL3/SDL.h>
#include <cstdio>
#include <algorithm>
#include <fstream>
#include <cstring>
#include <vector>
#include <chrono>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <objbase.h>
#include <wrl/client.h>
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "ole32.lib")
using Microsoft::WRL::ComPtr;
#endif
Audio& Audio::instance(){ static Audio inst; return inst; }
bool Audio::init(){ if(outSpec.freq!=0) return true; outSpec.format=SDL_AUDIO_S16; outSpec.channels=outChannels; outSpec.freq=outRate;
#ifdef _WIN32
if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MFStartup failed"); } else mfStarted=true; }
#endif
return true; }
#ifdef _WIN32
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
outPCM.clear(); outRate=44100; outCh=2;
ComPtr<IMFSourceReader> reader;
wchar_t wpath[MAX_PATH]; int wlen = MultiByteToWideChar(CP_UTF8,0,path.c_str(),-1,wpath,MAX_PATH); if(!wlen) return false;
if(FAILED(MFCreateSourceReaderFromURL(wpath,nullptr,&reader))) return false;
// Request PCM output
ComPtr<IMFMediaType> outType; MFCreateMediaType(&outType); outType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); outType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); outType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2); outType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100); outType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 4); outType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 44100*4); outType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16); outType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK, 3); reader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, outType.Get());
reader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
while(true){ DWORD flags=0; ComPtr<IMFSample> sample; if(FAILED(reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,0,nullptr,&flags,nullptr,&sample))) break; if(flags & MF_SOURCE_READERF_ENDOFSTREAM) break; if(!sample) continue; ComPtr<IMFMediaBuffer> buffer; if(FAILED(sample->ConvertToContiguousBuffer(&buffer))) continue; BYTE* data=nullptr; DWORD maxLen=0, curLen=0; if(SUCCEEDED(buffer->Lock(&data,&maxLen,&curLen)) && curLen){ size_t samples = curLen/2; size_t oldSz = outPCM.size(); outPCM.resize(oldSz + samples); std::memcpy(outPCM.data()+oldSz, data, curLen); } if(data) buffer->Unlock(); }
outRate=44100; outCh=2; return !outPCM.empty(); }
#endif
void Audio::addTrack(const std::string& path){ AudioTrack t; t.path=path;
#ifdef _WIN32
if(decodeMP3(path, t.pcm, t.rate, t.channels)) t.ok=true; else SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
#else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform (stub): %s", path.c_str());
#endif
tracks.push_back(std::move(t)); }
void Audio::shuffle(){
std::lock_guard<std::mutex> lock(tracksMutex);
std::shuffle(tracks.begin(), tracks.end(), rng);
}
bool Audio::ensureStream(){
if(audioStream) return true;
audioStream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &outSpec, &Audio::streamCallback, this);
if(!audioStream){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] SDL_OpenAudioDeviceStream failed: %s", SDL_GetError());
return false;
}
return true;
}
void Audio::start(){ if(!ensureStream()) return; if(!playing){ current=-1; nextTrack(); SDL_ResumeAudioStreamDevice(audioStream); playing=true; } }
void Audio::toggleMute(){ muted=!muted; }
void Audio::nextTrack(){ if(tracks.empty()) return; for(size_t i=0;i<tracks.size(); ++i){ current = (current + 1) % (int)tracks.size(); if(tracks[current].ok){ tracks[current].cursor=0; return; } } current=-1; }
void Audio::feed(Uint32 bytesWanted, SDL_AudioStream* stream){
if(bytesWanted==0) return;
// Prepare a buffer of int16 samples for the output device
const size_t outSamples = bytesWanted / sizeof(int16_t);
std::vector<int16_t> mix(outSamples, 0);
// 1) Mix music into buffer (if not muted)
if(!muted && current >= 0){
size_t cursorBytes = 0;
while(cursorBytes < bytesWanted){
if(current < 0) break;
auto &trk = tracks[current];
size_t samplesAvail = trk.pcm.size() - trk.cursor; // samples (int16)
if(samplesAvail == 0){ nextTrack(); if(current < 0) break; continue; }
size_t samplesNeeded = (bytesWanted - cursorBytes) / sizeof(int16_t);
size_t toCopy = (samplesAvail < samplesNeeded) ? samplesAvail : samplesNeeded;
if(toCopy == 0) break;
// Mix add with clamp
size_t startSample = cursorBytes / sizeof(int16_t);
for(size_t i=0;i<toCopy;++i){
int v = (int)mix[startSample+i] + (int)trk.pcm[trk.cursor+i];
if(v>32767) v=32767; if(v<-32768) v=-32768; mix[startSample+i] = (int16_t)v;
}
trk.cursor += toCopy;
cursorBytes += (Uint32)(toCopy * sizeof(int16_t));
if(trk.cursor >= trk.pcm.size()) nextTrack();
}
}
// 2) Mix active SFX
{
std::lock_guard<std::mutex> lock(sfxMutex);
for(size_t si=0; si<activeSfx.size(); ){
auto &s = activeSfx[si];
size_t samplesAvail = s.pcm.size() - s.cursor;
if(samplesAvail == 0){ activeSfx.erase(activeSfx.begin()+si); continue; }
size_t toCopy = (samplesAvail < outSamples) ? samplesAvail : outSamples;
for(size_t i=0;i<toCopy;++i){
int v = (int)mix[i] + (int)s.pcm[s.cursor+i];
if(v>32767) v=32767; if(v<-32768) v=-32768; mix[i] = (int16_t)v;
}
s.cursor += toCopy;
++si;
}
}
// Submit mixed audio
if(!mix.empty()) SDL_PutAudioStreamData(stream, mix.data(), (int)bytesWanted);
}
void Audio::playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume){
if(pcm.empty()) return;
if(!ensureStream()) return;
// Convert input to device format (S16, stereo, 44100)
SDL_AudioSpec src{}; src.format=SDL_AUDIO_S16; src.channels=(Uint8)channels; src.freq=rate;
SDL_AudioSpec dst{}; dst.format=SDL_AUDIO_S16; dst.channels=(Uint8)outChannels; dst.freq=outRate;
SDL_AudioStream* cvt = SDL_CreateAudioStream(&src, &dst);
if(!cvt) return;
// Apply volume while copying into a temp buffer
std::vector<int16_t> volBuf(pcm.size());
for(size_t i=0;i<pcm.size();++i){
int v = (int)(pcm[i] * volume);
if(v>32767) v=32767; if(v<-32768) v=-32768; volBuf[i]=(int16_t)v;
}
SDL_PutAudioStreamData(cvt, volBuf.data(), (int)(volBuf.size()*sizeof(int16_t)));
SDL_FlushAudioStream(cvt);
int bytes = SDL_GetAudioStreamAvailable(cvt);
if(bytes>0){
std::vector<int16_t> out(bytes/2);
SDL_GetAudioStreamData(cvt, out.data(), bytes);
std::lock_guard<std::mutex> lock(sfxMutex);
activeSfx.push_back(SfxPlay{ std::move(out), 0 });
}
SDL_DestroyAudioStream(cvt);
}
void SDLCALL Audio::streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total){ Uint32 want = additional>0 ? (Uint32)additional : (Uint32)total; if(!want) want=4096; reinterpret_cast<Audio*>(userdata)->feed(want, stream); }
void Audio::addTrackAsync(const std::string& path) {
std::lock_guard<std::mutex> lock(pendingTracksMutex);
pendingTracks.push_back(path);
}
void Audio::startBackgroundLoading() {
if (loadingThread.joinable()) return; // Already running
loadingComplete = false;
loadedCount = 0;
loadingThread = std::thread(&Audio::backgroundLoadingThread, this);
}
void Audio::backgroundLoadingThread() {
#ifdef _WIN32
// Initialize COM and MF for this thread
HRESULT hrCom = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
HRESULT hrMF = MFStartup(MF_VERSION);
bool mfInitialized = SUCCEEDED(hrMF);
if (!mfInitialized) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to initialize MF on background thread");
}
#endif
// Copy pending tracks to avoid holding the mutex during processing
std::vector<std::string> tracksToProcess;
{
std::lock_guard<std::mutex> lock(pendingTracksMutex);
tracksToProcess = pendingTracks;
}
for (const std::string& path : tracksToProcess) {
AudioTrack t;
t.path = path;
#ifdef _WIN32
if (mfInitialized && decodeMP3(path, t.pcm, t.rate, t.channels)) {
t.ok = true;
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode %s", path.c_str());
}
#else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform (stub): %s", path.c_str());
#endif
// Thread-safe addition to tracks
{
std::lock_guard<std::mutex> lock(tracksMutex);
tracks.push_back(std::move(t));
}
loadedCount++;
// Small delay to prevent overwhelming the system
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
#ifdef _WIN32
// Cleanup MF and COM for this thread
if (mfInitialized) {
MFShutdown();
}
if (SUCCEEDED(hrCom)) {
CoUninitialize();
}
#endif
loadingComplete = true;
}
void Audio::waitForLoadingComplete() {
if (loadingThread.joinable()) {
loadingThread.join();
}
}
bool Audio::isLoadingComplete() const {
return loadingComplete;
}
int Audio::getLoadedTrackCount() const {
return loadedCount;
}
void Audio::shutdown(){
// Stop background loading thread first
if (loadingThread.joinable()) {
loadingThread.join();
}
if(audioStream){ SDL_DestroyAudioStream(audioStream); audioStream=nullptr; }
tracks.clear();
{
std::lock_guard<std::mutex> lock(pendingTracksMutex);
pendingTracks.clear();
}
playing=false;
#ifdef _WIN32
if(mfStarted){ MFShutdown(); mfStarted=false; }
#endif
}

60
src/audio/Audio.h Normal file
View File

@ -0,0 +1,60 @@
// Audio.h - MP3 playlist playback (Windows Media Foundation backend) + SDL3 stream
#pragma once
#include <SDL3/SDL.h>
#include <vector>
#include <string>
#include <random>
#include <cstdint>
#include <thread>
#include <mutex>
#include <atomic>
struct AudioTrack {
std::string path;
std::vector<int16_t> pcm;
int channels = 2;
int rate = 44100;
size_t cursor = 0;
bool ok = false;
};
class Audio {
public:
static Audio& instance();
bool init(); // initialize backend (MF on Windows)
void addTrack(const std::string& path); // decode MP3 -> PCM16 stereo 44100
void addTrackAsync(const std::string& path); // add track for background loading
void startBackgroundLoading(); // start background thread for loading
void waitForLoadingComplete(); // wait for all tracks to finish loading
bool isLoadingComplete() const; // check if background loading is done
int getLoadedTrackCount() const; // get number of tracks loaded so far
void shuffle(); // randomize order
void start(); // begin playback
void toggleMute();
// Queue a sound effect to mix over the music (pcm can be mono/stereo, any rate; will be converted)
void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume);
void shutdown();
private:
Audio()=default; ~Audio()=default; Audio(const Audio&)=delete; Audio& operator=(const Audio&)=delete;
static void SDLCALL streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total);
void feed(Uint32 bytesWanted, SDL_AudioStream* stream);
void nextTrack();
bool ensureStream();
void backgroundLoadingThread(); // background thread function
std::vector<AudioTrack> tracks; int current=-1; bool playing=false; bool muted=false; std::mt19937 rng{std::random_device{}()};
SDL_AudioStream* audioStream=nullptr; SDL_AudioSpec outSpec{}; int outChannels=2; int outRate=44100; bool mfStarted=false;
// Threading support
std::vector<std::string> pendingTracks;
std::thread loadingThread;
std::mutex tracksMutex;
std::mutex pendingTracksMutex;
std::atomic<bool> loadingComplete{false};
std::atomic<int> loadedCount{0};
// SFX mixing support
struct SfxPlay { std::vector<int16_t> pcm; size_t cursor=0; };
std::vector<SfxPlay> activeSfx;
std::mutex sfxMutex;
};

22
src/audio/MenuWrappers.h Normal file
View File

@ -0,0 +1,22 @@
// MenuWrappers.h - function prototypes for menu helper wrappers implemented in main.cpp
#pragma once
#include <SDL3/SDL.h>
#include <string>
class FontAtlas;
// Draw fireworks using the provided blocks texture (may be nullptr)
void menu_drawFireworks(SDL_Renderer* renderer, SDL_Texture* blocksTex);
void menu_updateFireworks(double frameMs);
double menu_getLogoAnimCounter();
int menu_getHoveredButton();
void menu_drawEnhancedButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
const std::string& label, bool isHovered, bool isSelected);
void menu_drawMenuButton(SDL_Renderer* renderer, FontAtlas& font, float cx, float cy, float w, float h,
const std::string& label, SDL_Color bgColor, SDL_Color borderColor);
void menu_drawLevelSelectionPopup(SDL_Renderer* renderer, FontAtlas& font, SDL_Texture* bgTex, int selectedLevel);
void menu_drawSettingsPopup(SDL_Renderer* renderer, FontAtlas& font, bool musicEnabled);

318
src/audio/SoundEffect.cpp Normal file
View File

@ -0,0 +1,318 @@
// SoundEffect.cpp - Implementation of sound effects system
#include "SoundEffect.h"
#include <SDL3/SDL.h>
#include "audio/Audio.h"
#include <cstdio>
#include <algorithm>
#include <random>
#include <cstring>
#include <memory>
#ifdef _WIN32
#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <objbase.h>
#include <wrl/client.h>
#pragma comment(lib, "mfplat.lib")
#pragma comment(lib, "mfreadwrite.lib")
#pragma comment(lib, "mfuuid.lib")
#pragma comment(lib, "ole32.lib")
using Microsoft::WRL::ComPtr;
#endif
// SoundEffect implementation
bool SoundEffect::load(const std::string& filePath) {
// Determine file type by extension
std::string extension = filePath.substr(filePath.find_last_of('.') + 1);
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
bool success = false;
if (extension == "wav") {
success = loadWAV(filePath);
} else if (extension == "mp3") {
success = loadMP3(filePath);
} else {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Unsupported file format: %s", extension.c_str());
return false;
}
if (!success) {
return false;
}
loaded = true;
//std::printf("[SoundEffect] Loaded: %s (%d channels, %d Hz, %zu samples)\n", filePath.c_str(), channels, sampleRate, pcmData.size());
return true;
}
void SoundEffect::play(float volume) {
if (!loaded || pcmData.empty()) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Cannot play - loaded=%d, pcmData.size()=%zu", loaded, pcmData.size());
return;
}
//std::printf("[SoundEffect] Playing sound with %zu samples at volume %.2f\n", pcmData.size(), volume);
// Calculate final volume
float finalVolume = defaultVolume * volume;
finalVolume = (std::max)(0.0f, (std::min)(1.0f, finalVolume));
// Use the simple audio player to play this sound
SimpleAudioPlayer::instance().playSound(pcmData, channels, sampleRate, finalVolume);
}
void SoundEffect::setVolume(float volume) {
defaultVolume = (std::max)(0.0f, (std::min)(1.0f, volume));
}
// SimpleAudioPlayer implementation
SimpleAudioPlayer& SimpleAudioPlayer::instance() {
static SimpleAudioPlayer inst;
return inst;
}
bool SimpleAudioPlayer::init() {
if (initialized) {
return true;
}
initialized = true;
//std::printf("[SimpleAudioPlayer] Initialized\n");
return true;
}
void SimpleAudioPlayer::shutdown() {
initialized = false;
//std::printf("[SimpleAudioPlayer] Shut down\n");
}
void SimpleAudioPlayer::playSound(const std::vector<int16_t>& pcmData, int channels, int sampleRate, float volume) {
if (!initialized || pcmData.empty()) {
return;
}
// Route through shared Audio mixer so SFX always play over music
Audio::instance().playSfx(pcmData, channels, sampleRate, volume);
}
bool SoundEffect::loadWAV(const std::string& filePath) {
SDL_AudioSpec wavSpec;
Uint8* wavBuffer;
Uint32 wavLength;
if (!SDL_LoadWAV(filePath.c_str(), &wavSpec, &wavBuffer, &wavLength)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to load WAV file %s: %s",
filePath.c_str(), SDL_GetError());
return false;
}
// Store audio format info
channels = wavSpec.channels;
sampleRate = wavSpec.freq;
// Convert to 16-bit signed if needed
if (wavSpec.format == SDL_AUDIO_S16) {
// Already in the right format
size_t samples = wavLength / sizeof(int16_t);
pcmData.resize(samples);
std::memcpy(pcmData.data(), wavBuffer, wavLength);
} else {
// Need to convert format
SDL_AudioSpec srcSpec = wavSpec;
SDL_AudioSpec dstSpec = wavSpec;
dstSpec.format = SDL_AUDIO_S16;
SDL_AudioStream* converter = SDL_CreateAudioStream(&srcSpec, &dstSpec);
if (converter) {
SDL_PutAudioStreamData(converter, wavBuffer, wavLength);
SDL_FlushAudioStream(converter);
int convertedLength = SDL_GetAudioStreamAvailable(converter);
if (convertedLength > 0) {
pcmData.resize(convertedLength / sizeof(int16_t));
SDL_GetAudioStreamData(converter, pcmData.data(), convertedLength);
}
SDL_DestroyAudioStream(converter);
}
}
SDL_free(wavBuffer);
return !pcmData.empty();
}
bool SoundEffect::loadMP3(const std::string& filePath) {
#ifdef _WIN32
static bool mfInitialized = false;
if (!mfInitialized) {
if (FAILED(MFStartup(MF_VERSION))) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] MFStartup failed");
return false;
}
mfInitialized = true;
}
ComPtr<IMFSourceReader> reader;
wchar_t wpath[MAX_PATH];
int wlen = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, wpath, MAX_PATH);
if (!wlen) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to convert path to wide char");
return false;
}
if (FAILED(MFCreateSourceReaderFromURL(wpath, nullptr, &reader))) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] Failed to create source reader for %s", filePath.c_str());
return false;
}
// Request PCM output
ComPtr<IMFMediaType> outType;
MFCreateMediaType(&outType);
outType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
outType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
outType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, 2);
outType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, 44100);
outType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 4);
outType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 44100 * 4);
outType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, 16);
outType->SetUINT32(MF_MT_AUDIO_CHANNEL_MASK, 3);
reader->SetCurrentMediaType((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, outType.Get());
reader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
// Read all samples
std::vector<int16_t> tempData;
while (true) {
DWORD flags = 0;
ComPtr<IMFSample> sample;
if (FAILED(reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, nullptr, &flags, nullptr, &sample))) {
break;
}
if (flags & MF_SOURCE_READERF_ENDOFSTREAM) {
break;
}
if (!sample) {
continue;
}
ComPtr<IMFMediaBuffer> buffer;
if (FAILED(sample->ConvertToContiguousBuffer(&buffer))) {
continue;
}
BYTE* data = nullptr;
DWORD maxLen = 0, curLen = 0;
if (SUCCEEDED(buffer->Lock(&data, &maxLen, &curLen)) && curLen) {
size_t samples = curLen / 2;
size_t oldSize = tempData.size();
tempData.resize(oldSize + samples);
std::memcpy(tempData.data() + oldSize, data, curLen);
}
if (data) {
buffer->Unlock();
}
}
if (tempData.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] No audio data decoded from %s", filePath.c_str());
return false;
}
pcmData = std::move(tempData);
channels = 2;
sampleRate = 44100;
return true;
#else
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[SoundEffect] MP3 support not available on this platform");
return false;
#endif
}
// SoundEffectManager implementation
SoundEffectManager& SoundEffectManager::instance() {
static SoundEffectManager inst;
return inst;
}
bool SoundEffectManager::init() {
if (initialized) {
return true;
}
// Initialize the simple audio player
SimpleAudioPlayer::instance().init();
initialized = true;
//std::printf("[SoundEffectManager] Initialized\n");
return true;
}
void SoundEffectManager::shutdown() {
soundEffects.clear();
SimpleAudioPlayer::instance().shutdown();
initialized = false;
//std::printf("[SoundEffectManager] Shut down\n");
}
bool SoundEffectManager::loadSound(const std::string& id, const std::string& filePath) {
if (!initialized) {
std::fprintf(stderr, "[SoundEffectManager] Not initialized\n");
return false;
}
auto soundEffect = std::make_unique<SoundEffect>();
if (!soundEffect->load(filePath)) {
std::fprintf(stderr, "[SoundEffectManager] Failed to load sound: %s\n", filePath.c_str());
return false;
}
// Remove existing sound with the same ID
soundEffects.erase(
std::remove_if(soundEffects.begin(), soundEffects.end(),
[&id](const auto& pair) { return pair.first == id; }),
soundEffects.end());
soundEffects.emplace_back(id, std::move(soundEffect));
//std::printf("[SoundEffectManager] Loaded sound '%s' from %s\n", id.c_str(), filePath.c_str());
return true;
}
void SoundEffectManager::playSound(const std::string& id, float volume) {
if (!enabled || !initialized) {
return;
}
auto it = std::find_if(soundEffects.begin(), soundEffects.end(),
[&id](const auto& pair) { return pair.first == id; });
if (it != soundEffects.end()) {
it->second->play(volume * masterVolume);
} else {
std::fprintf(stderr, "[SoundEffectManager] Sound not found: %s\n", id.c_str());
}
}
void SoundEffectManager::playRandomSound(const std::vector<std::string>& soundIds, float volume) {
if (!enabled || !initialized || soundIds.empty()) {
return;
}
static std::random_device rd;
static std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, soundIds.size() - 1);
const std::string& selectedId = soundIds[dis(gen)];
playSound(selectedId, volume);
}
void SoundEffectManager::setMasterVolume(float volume) {
masterVolume = (std::max)(0.0f, (std::min)(1.0f, volume));
}
void SoundEffectManager::setEnabled(bool enabled_) {
enabled = enabled_;
}
bool SoundEffectManager::isEnabled() const {
return enabled;
}

94
src/audio/SoundEffect.h Normal file
View File

@ -0,0 +1,94 @@
// SoundEffect.h - Single sound effect player using SDL3 audio
#pragma once
#include <SDL3/SDL.h>
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
class SoundEffect {
public:
SoundEffect() = default;
~SoundEffect() = default;
// Load a sound effect from file (WAV or MP3)
bool load(const std::string& filePath);
// Play the sound effect
void play(float volume = 1.0f);
// Set default volume for this sound effect
void setVolume(float volume);
// Get PCM data for mixing
const std::vector<int16_t>& getPCMData() const { return pcmData; }
int getChannels() const { return channels; }
int getSampleRate() const { return sampleRate; }
bool isLoaded() const { return loaded; }
private:
std::vector<int16_t> pcmData;
int channels = 2;
int sampleRate = 44100;
bool loaded = false;
float defaultVolume = 1.0f;
bool loadWAV(const std::string& filePath);
bool loadMP3(const std::string& filePath);
};
// Simple audio player for immediate playback
class SimpleAudioPlayer {
public:
static SimpleAudioPlayer& instance();
bool init();
void shutdown();
// Play a sound effect immediately
void playSound(const std::vector<int16_t>& pcmData, int channels, int sampleRate, float volume = 1.0f);
private:
SimpleAudioPlayer() = default;
~SimpleAudioPlayer() = default;
SimpleAudioPlayer(const SimpleAudioPlayer&) = delete;
SimpleAudioPlayer& operator=(const SimpleAudioPlayer&) = delete;
bool initialized = false;
};
// Helper class to manage multiple sound effects
class SoundEffectManager {
public:
static SoundEffectManager& instance();
bool init();
void shutdown();
// Load a sound effect and assign it an ID
bool loadSound(const std::string& id, const std::string& filePath);
// Play a sound effect by ID
void playSound(const std::string& id, float volume = 1.0f);
// Play a random sound from a group
void playRandomSound(const std::vector<std::string>& soundIds, float volume = 1.0f);
// Set master volume for all sound effects
void setMasterVolume(float volume);
// Enable/disable sound effects
void setEnabled(bool enabled);
bool isEnabled() const;
private:
SoundEffectManager() = default;
~SoundEffectManager() = default;
SoundEffectManager(const SoundEffectManager&) = delete;
SoundEffectManager& operator=(const SoundEffectManager&) = delete;
std::vector<std::pair<std::string, std::unique_ptr<SoundEffect>>> soundEffects;
float masterVolume = 1.0f;
bool enabled = true;
bool initialized = false;
};