Files
spacetris/src/audio/SoundEffect.cpp
Gregor Klevze 2afaea7fd3
Some checks failed
Build and Package Tetris / build-windows (push) Has been cancelled
Build and Package Tetris / build-linux (push) Has been cancelled
Updated game structure
2025-08-16 12:10:19 +02:00

319 lines
10 KiB
C++

// 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;
}