// Audio.cpp - Windows Media Foundation MP3 decoding #include "audio/Audio.h" #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #include #include #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfreadwrite.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "ole32.lib") using Microsoft::WRL::ComPtr; #ifdef max #undef max #endif #ifdef min #undef min #endif #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& outPCM, int& outRate, int& outCh){ outPCM.clear(); outRate=44100; outCh=2; ComPtr 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 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 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 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 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; } // Ensure the device is running so SFX can be heard even before music starts SDL_ResumeAudioStreamDevice(audioStream); return true; } void Audio::start(){ if(!ensureStream()) return; // If no track is selected yet, try to select one now (in case tracks loaded after initial start) if(current < 0) { nextTrack(); } SDL_ResumeAudioStreamDevice(audioStream); playing = true; } void Audio::toggleMute(){ muted=!muted; } void Audio::nextTrack(){ if(tracks.empty()) { current = -1; return; } // Try every track once to find a decodable one int start = current; for(size_t i=0;i 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;i32767) 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 lock(sfxMutex); for(size_t si=0; si32767) 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& 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 volBuf(pcm.size()); for(size_t i=0;i32767) 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 out(bytes/2); SDL_GetAudioStreamData(cvt, out.data(), bytes); std::lock_guard 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(userdata)->feed(want, stream); } void Audio::addTrackAsync(const std::string& path) { std::lock_guard lock(pendingTracksMutex); pendingTracks.push_back(path); } void Audio::startBackgroundLoading() { // If a previous loading thread exists but has finished, join it so we can start anew if (loadingThread.joinable()) { if (loadingComplete) { loadingThread.join(); } else { // Already running return; } } 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 while (true) { std::string path; { std::lock_guard lock(pendingTracksMutex); if (pendingTracks.empty()) break; path = std::move(pendingTracks.front()); pendingTracks.erase(pendingTracks.begin()); } 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 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 lock(pendingTracksMutex); pendingTracks.clear(); } playing=false; #ifdef _WIN32 if(mfStarted){ MFShutdown(); mfStarted=false; } #endif } // IAudioSystem interface implementation void Audio::playSound(const std::string& name) { // This is a simplified implementation - in a full implementation, // you would load sound effects by name from assets // For now, we'll just trigger a generic sound effect // In practice, this would load a sound file and play it via playSfx } void Audio::playMusic(const std::string& name) { // This is a simplified implementation - in a full implementation, // you would load music tracks by name // For now, we'll just start the current playlist if (!tracks.empty() && !playing) { start(); } } void Audio::stopMusic() { playing = false; } void Audio::setMasterVolume(float volume) { m_masterVolume = std::max(0.0f, std::min(1.0f, volume)); } void Audio::setMusicVolume(float volume) { m_musicVolume = std::max(0.0f, std::min(1.0f, volume)); } void Audio::setSoundVolume(float volume) { m_sfxVolume = std::max(0.0f, std::min(1.0f, volume)); } bool Audio::isMusicPlaying() const { return playing; }