fixes for mac music

This commit is contained in:
2025-11-30 15:19:14 +01:00
parent da32b5be1a
commit 7f07036f07
24 changed files with 236 additions and 172 deletions

View File

@ -558,16 +558,20 @@ bool ApplicationManager::initializeGame() {
Audio::instance().init();
// Discover available tracks (up to 100) and queue for background loading
m_totalTracks = 0;
std::vector<std::string> trackPaths;
trackPaths.reserve(100);
for (int i = 1; i <= 100; ++i) {
char buf[128];
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
// Use simple file existence check via std::filesystem
if (std::filesystem::exists(buf)) {
Audio::instance().addTrackAsync(buf);
++m_totalTracks;
} else {
char base[128];
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
if (path.empty()) {
break;
}
trackPaths.push_back(path);
}
m_totalTracks = static_cast<int>(trackPaths.size());
for (const auto& path : trackPaths) {
Audio::instance().addTrackAsync(path);
}
if (m_totalTracks > 0) {
Audio::instance().startBackgroundLoading();

View File

@ -208,27 +208,14 @@ bool AssetManager::loadSoundEffectWithFallback(const std::string& id, const std:
return false;
}
// Try WAV first, then MP3 fallback (matching main.cpp pattern)
std::string wavPath = "assets/music/" + baseName + ".wav";
std::string mp3Path = "assets/music/" + baseName + ".mp3";
// Check WAV first
if (fileExists(wavPath)) {
if (m_soundSystem->loadSound(id, wavPath)) {
logInfo("Loaded sound effect: " + id + " from " + wavPath + " (WAV)");
return true;
}
const std::string basePath = "assets/music/" + baseName;
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
if (!resolved.empty() && m_soundSystem->loadSound(id, resolved)) {
logInfo("Loaded sound effect: " + id + " from " + resolved);
return true;
}
// Fallback to MP3
if (fileExists(mp3Path)) {
if (m_soundSystem->loadSound(id, mp3Path)) {
logInfo("Loaded sound effect: " + id + " from " + mp3Path + " (MP3 fallback)");
return true;
}
}
setError("Failed to load sound effect: " + id + " (tried both WAV and MP3)");
setError("Failed to load sound effect: " + id + " (no supported audio extension found)");
return false;
}

View File

@ -15,6 +15,7 @@
#include <cstdlib>
#include <memory>
#include <filesystem>
#include <thread>
#include "audio/Audio.h"
#include "audio/SoundEffect.h"
@ -682,11 +683,11 @@ int main(int, char **)
}
SDL_SetRenderVSync(renderer, 1);
#if defined(__APPLE__)
// On macOS bundles launched from Finder start in /, so re-root relative paths.
if (const char* basePathRaw = SDL_GetBasePath()) {
std::filesystem::path exeDir(basePathRaw);
SDL_free(const_cast<char*>(basePathRaw));
AssetPath::setBasePath(exeDir.string());
#if defined(__APPLE__)
// On macOS bundles launched from Finder start in /, so re-root relative paths.
std::error_code ec;
std::filesystem::current_path(exeDir, ec);
if (ec) {
@ -694,12 +695,13 @@ int main(int, char **)
"Failed to set working directory to %s: %s",
exeDir.string().c_str(), ec.message().c_str());
}
#endif
SDL_free(const_cast<char*>(basePathRaw));
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetBasePath() failed; asset lookups rely on current directory: %s",
SDL_GetError());
}
#endif
FontAtlas font;
font.init("FreeSans.ttf", 24);
@ -709,10 +711,15 @@ int main(int, char **)
pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16);
ScoreManager scores;
// Load scores asynchronously to prevent startup hang due to network request
std::thread([&scores]() {
// Load scores asynchronously but keep the worker alive until shutdown to avoid lifetime issues
std::jthread scoreLoader([&scores]() {
scores.load();
}).detach();
});
const auto ensureScoresLoaded = [&]() {
if (scoreLoader.joinable()) {
scoreLoader.join();
}
};
Starfield starfield;
starfield.init(200, LOGICAL_W, LOGICAL_H);
Starfield3D starfield3D;
@ -771,9 +778,19 @@ int main(int, char **)
// Initialize sound effects system
SoundEffectManager::instance().init();
// Load sound effects
SoundEffectManager::instance().loadSound("clear_line", "assets/music/clear_line.wav");
auto loadAudioAsset = [](const std::string& basePath, const std::string& id) {
std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
if (resolved.empty()) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Missing audio asset for %s (base %s)", id.c_str(), basePath.c_str());
return;
}
if (!SoundEffectManager::instance().loadSound(id, resolved)) {
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load %s from %s", id.c_str(), resolved.c_str());
}
};
loadAudioAsset("assets/music/clear_line", "clear_line");
// Load voice lines for line clears using WAV files (with MP3 fallback)
std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"};
@ -789,49 +806,25 @@ int main(int, char **)
appendVoices(tripleSounds);
appendVoices(tetrisSounds);
// Helper function to load sound with WAV/MP3 fallback and file existence check
auto loadSoundWithFallback = [&](const std::string& id, const std::string& baseName) {
std::string wavPath = "assets/music/" + baseName + ".wav";
std::string mp3Path = "assets/music/" + baseName + ".mp3";
// Check if WAV file exists first
SDL_IOStream* wavFile = SDL_IOFromFile(wavPath.c_str(), "rb");
if (wavFile) {
SDL_CloseIO(wavFile);
if (SoundEffectManager::instance().loadSound(id, wavPath)) {
(void)0;
return;
}
}
// Fallback to MP3 if WAV doesn't exist or fails to load
SDL_IOStream* mp3File = SDL_IOFromFile(mp3Path.c_str(), "rb");
if (mp3File) {
SDL_CloseIO(mp3File);
if (SoundEffectManager::instance().loadSound(id, mp3Path)) {
(void)0;
return;
}
}
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to load sound: %s (tried both WAV and MP3)", id.c_str());
auto loadVoice = [&](const std::string& id, const std::string& baseName) {
loadAudioAsset("assets/music/" + baseName, id);
};
loadSoundWithFallback("nice_combo", "nice_combo");
loadSoundWithFallback("you_fire", "you_fire");
loadSoundWithFallback("well_played", "well_played");
loadSoundWithFallback("keep_that_ryhtm", "keep_that_ryhtm");
loadSoundWithFallback("great_move", "great_move");
loadSoundWithFallback("smooth_clear", "smooth_clear");
loadSoundWithFallback("impressive", "impressive");
loadSoundWithFallback("triple_strike", "triple_strike");
loadSoundWithFallback("amazing", "amazing");
loadSoundWithFallback("you_re_unstoppable", "you_re_unstoppable");
loadSoundWithFallback("boom_tetris", "boom_tetris");
loadSoundWithFallback("wonderful", "wonderful");
loadSoundWithFallback("lets_go", "lets_go"); // For level up
loadSoundWithFallback("hard_drop", "hard_drop_001");
loadSoundWithFallback("new_level", "new_level");
loadVoice("nice_combo", "nice_combo");
loadVoice("you_fire", "you_fire");
loadVoice("well_played", "well_played");
loadVoice("keep_that_ryhtm", "keep_that_ryhtm");
loadVoice("great_move", "great_move");
loadVoice("smooth_clear", "smooth_clear");
loadVoice("impressive", "impressive");
loadVoice("triple_strike", "triple_strike");
loadVoice("amazing", "amazing");
loadVoice("you_re_unstoppable", "you_re_unstoppable");
loadVoice("boom_tetris", "boom_tetris");
loadVoice("wonderful", "wonderful");
loadVoice("lets_go", "lets_go");
loadVoice("hard_drop", "hard_drop_001");
loadVoice("new_level", "new_level");
bool suppressLineVoiceForLevelUp = false;
@ -1122,6 +1115,7 @@ int main(int, char **)
playerName.pop_back();
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
if (playerName.empty()) playerName = "PLAYER";
ensureScoresLoaded();
scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName);
Settings::instance().setPlayerName(playerName);
isNewHighScore = false;
@ -1379,6 +1373,7 @@ int main(int, char **)
SDL_StartTextInput(window);
} else {
isNewHighScore = false;
ensureScoresLoaded();
scores.submit(game.score(), game.lines(), game.level(), game.elapsed());
}
state = AppState::GameOver;
@ -1396,25 +1391,21 @@ int main(int, char **)
// Count actual music files first
totalTracks = 0;
for (int i = 1; i <= 100; ++i) { // Check up to 100 files
char buf[64];
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
// Check if file exists
SDL_IOStream* file = SDL_IOFromFile(buf, "rb");
if (file) {
SDL_CloseIO(file);
totalTracks++;
} else {
break; // No more consecutive files
std::vector<std::string> trackPaths;
trackPaths.reserve(100);
for (int i = 1; i <= 100; ++i) {
char base[64];
std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
if (path.empty()) {
break;
}
trackPaths.push_back(path);
}
// Add all found tracks to the background loading queue
for (int i = 1; i <= totalTracks; ++i) {
char buf[64];
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
Audio::instance().addTrackAsync(buf);
totalTracks = static_cast<int>(trackPaths.size());
for (const auto& track : trackPaths) {
Audio::instance().addTrackAsync(track);
}
// Start background loading thread
@ -1470,7 +1461,12 @@ int main(int, char **)
static bool menuTrackLoaded = false;
if (!menuTrackLoaded) {
std::thread([]() {
Audio::instance().setMenuTrack("assets/music/Every Block You Take.mp3");
std::string menuTrack = AssetPath::resolveWithExtensions("assets/music/Every Block You Take", { ".mp3" });
if (!menuTrack.empty()) {
Audio::instance().setMenuTrack(menuTrack);
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Menu track not found (Every Block You Take)");
}
}).detach();
menuTrackLoaded = true;
}
@ -1804,6 +1800,7 @@ int main(int, char **)
// 4. Draw Text
// 4. Draw Text
// Title
ensureScoresLoaded();
bool realHighScore = scores.isHighScore(game.score());
const char* title = realHighScore ? "NEW HIGH SCORE!" : "GAME OVER";
int tW=0, tH=0; pixelFont.measure(title, 2.0f, tW, tH);

View File

@ -2,17 +2,27 @@
#include <array>
#include <string>
#include <filesystem>
#include <initializer_list>
#include <SDL3/SDL.h>
namespace AssetPath {
inline bool fileExists(const std::string& path) {
if (path.empty()) {
inline std::string& baseDirectory() {
static std::string base;
return base;
}
inline void setBasePath(std::string path) {
baseDirectory() = std::move(path);
}
inline bool tryOpenFile(const std::string& candidate) {
if (candidate.empty()) {
return false;
}
SDL_IOStream* file = SDL_IOFromFile(path.c_str(), "rb");
SDL_IOStream* file = SDL_IOFromFile(candidate.c_str(), "rb");
if (file) {
SDL_CloseIO(file);
return true;
@ -20,13 +30,58 @@ inline bool fileExists(const std::string& path) {
return false;
}
inline bool fileExists(const std::string& path) {
if (path.empty()) {
return false;
}
if (tryOpenFile(path)) {
return true;
}
std::filesystem::path p(path);
if (!p.is_absolute()) {
const std::string& base = baseDirectory();
if (!base.empty()) {
std::filesystem::path combined = std::filesystem::path(base) / p;
if (tryOpenFile(combined.string())) {
return true;
}
}
}
return false;
}
inline std::string resolveWithBase(const std::string& path) {
if (path.empty()) {
return path;
}
if (tryOpenFile(path)) {
return path;
}
std::filesystem::path p(path);
if (!p.is_absolute()) {
const std::string& base = baseDirectory();
if (!base.empty()) {
std::filesystem::path combined = std::filesystem::path(base) / p;
std::string combinedStr = combined.string();
if (tryOpenFile(combinedStr)) {
return combinedStr;
}
}
}
return path;
}
inline std::string resolveImagePath(const std::string& originalPath) {
if (originalPath.empty()) {
return originalPath;
}
if (fileExists(originalPath)) {
return originalPath;
if (auto resolved = resolveWithBase(originalPath); resolved != originalPath || fileExists(resolved)) {
return resolved;
}
const std::size_t dot = originalPath.find_last_of('.');
@ -45,12 +100,41 @@ inline std::string resolveImagePath(const std::string& originalPath) {
if (candidate == originalPath) {
continue;
}
if (fileExists(candidate)) {
return candidate;
std::string resolvedCandidate = resolveWithBase(candidate);
if (resolvedCandidate != candidate || fileExists(resolvedCandidate)) {
return resolvedCandidate;
}
}
return originalPath;
}
inline std::string resolveWithExtensions(const std::string& basePathWithoutExt, std::initializer_list<const char*> extensions) {
for (const char* ext : extensions) {
std::string candidate = basePathWithoutExt + ext;
std::string resolved = resolveWithBase(candidate);
if (resolved != candidate || fileExists(resolved)) {
return resolved;
}
}
return {};
}
inline std::string resolveAudioPath(const std::string& basePathWithoutExt) {
static constexpr std::array<const char*, 4> AUDIO_EXTENSIONS = {
".ogg",
".flac",
".wav",
".mp3"
};
for (const char* ext : AUDIO_EXTENSIONS) {
std::string candidate = basePathWithoutExt + ext;
std::string resolved = resolveWithBase(candidate);
if (resolved != candidate || fileExists(resolved)) {
return resolved;
}
}
return {};
}
} // namespace AssetPath