// SoundEffect.cpp - Implementation of sound effects system #include "SoundEffect.h" #include #include "audio/Audio.h" #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; #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& 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 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 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 tempData; 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 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(); 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& 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; }