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
}