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
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5 -6
View File
@@ -1,14 +1,14 @@
@echo off @echo off
echo Converting MP3 files to WAV using Windows Media Player... echo Convert MP3 files to OGG (preferred) for cross-platform playback...
echo. echo.
REM Check if we have access to Windows Media Format SDK REM Check if we have access to Windows Media Format SDK
set MUSIC_DIR=assets\music set MUSIC_DIR=assets\music
REM List of MP3 files to convert REM List of MP3 files to convert
set FILES=amazing.mp3 boom_tetris.mp3 great_move.mp3 impressive.mp3 keep_that_ryhtm.mp3 lets_go.mp3 nice_combo.mp3 smooth_clear.mp3 triple_strike.mp3 well_played.mp3 wonderful.mp3 you_fire.mp3 you_re_unstoppable.mp3 set FILES=amazing.mp3 boom_tetris.mp3 great_move.mp3 impressive.mp3 keep_that_ryhtm.mp3 lets_go.mp3 nice_combo.mp3 smooth_clear.mp3 triple_strike.mp3 well_played.mp3 wonderful.mp3 you_fire.mp3 you_re_unstoppable.mp3 hard_drop_001.mp3 new_level.mp3
echo Please convert these MP3 files to WAV format manually: echo Please convert these MP3 files to OGG Vorbis manually (or run convert_to_ogg.ps1 on Windows):
echo. echo.
for %%f in (%FILES%) do ( for %%f in (%FILES%) do (
echo - %MUSIC_DIR%\%%f echo - %MUSIC_DIR%\%%f
@@ -17,13 +17,12 @@ for %%f in (%FILES%) do (
echo. echo.
echo Recommended settings for conversion: echo Recommended settings for conversion:
echo - Sample Rate: 44100 Hz echo - Sample Rate: 44100 Hz
echo - Bit Depth: 16-bit
echo - Channels: Stereo (2) echo - Channels: Stereo (2)
echo - Format: PCM WAV echo - Use OGG Vorbis quality ~4 (or convert to FLAC if you prefer lossless)
echo. echo.
echo You can use: echo You can use:
echo - Audacity (free): https://www.audacityteam.org/ echo - Audacity (free): https://www.audacityteam.org/
echo - VLC Media Player (free): Media ^> Convert/Save echo - VLC Media Player (free): Media ^> Convert/Save
echo - Any audio converter software echo - ffmpeg (CLI): ffmpeg -i input.mp3 -c:a libvorbis -qscale:a 4 output.ogg
echo. echo.
pause pause
+45
View File
@@ -0,0 +1,45 @@
# Convert MP3 sound effects to OGG Vorbis format for cross-platform playback
# Requires ffmpeg (https://ffmpeg.org/). OGG keeps files small while SDL's decoders
# work everywhere the game ships.
$musicDir = "assets\music"
$sourceFiles = @(
"amazing.mp3",
"boom_tetris.mp3",
"great_move.mp3",
"impressive.mp3",
"keep_that_ryhtm.mp3",
"lets_go.mp3",
"nice_combo.mp3",
"smooth_clear.mp3",
"triple_strike.mp3",
"well_played.mp3",
"wonderful.mp3",
"you_fire.mp3",
"you_re_unstoppable.mp3",
"hard_drop_001.mp3",
"new_level.mp3",
"Every Block You Take.mp3"
)
if (!(Get-Command ffmpeg -ErrorAction SilentlyContinue)) {
Write-Host "ffmpeg is required. Install via https://ffmpeg.org/ or winget install Gyan.FFmpeg" -ForegroundColor Red
exit 1
}
Write-Host "Converting MP3 sound effects to OGG..." -ForegroundColor Cyan
foreach ($file in $sourceFiles) {
$src = Join-Path $musicDir $file
if (!(Test-Path $src)) {
Write-Host "Skipping (missing): $src" -ForegroundColor Yellow
continue
}
$ogg = ($file -replace ".mp3$", ".ogg")
$dest = Join-Path $musicDir $ogg
Write-Host "-> $ogg" -ForegroundColor Green
& ffmpeg -y -i $src -c:a libvorbis -qscale:a 4 $dest > $null 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to convert $file" -ForegroundColor Red
}
}
Write-Host "Conversion complete." -ForegroundColor Green
+8 -60
View File
@@ -1,63 +1,11 @@
# Convert MP3 sound effects to WAV format # Deprecated shim: point developers to the OGG conversion workflow
# This script converts all MP3 sound effect files to WAV for better compatibility Write-Host "convert_to_wav.ps1 is deprecated." -ForegroundColor Yellow
Write-Host "Use convert_to_ogg.ps1 for generating assets with OGG Vorbis." -ForegroundColor Yellow
$musicDir = "assets\music" $oggScript = Join-Path $PSScriptRoot "convert_to_ogg.ps1"
$mp3Files = @( if (Test-Path $oggScript) {
"amazing.mp3", & $oggScript
"boom_tetris.mp3",
"great_move.mp3",
"impressive.mp3",
"keep_that_ryhtm.mp3",
"lets_go.mp3",
"nice_combo.mp3",
"smooth_clear.mp3",
"triple_strike.mp3",
"well_played.mp3",
"wonderful.mp3",
"you_fire.mp3",
"you_re_unstoppable.mp3"
)
Write-Host "Converting MP3 sound effects to WAV format..." -ForegroundColor Green
foreach ($mp3File in $mp3Files) {
$mp3Path = Join-Path $musicDir $mp3File
$wavFile = $mp3File -replace "\.mp3$", ".wav"
$wavPath = Join-Path $musicDir $wavFile
if (Test-Path $mp3Path) {
Write-Host "Converting $mp3File to $wavFile..." -ForegroundColor Yellow
# Try ffmpeg first (most common)
$ffmpegResult = $null
try {
$ffmpegResult = & ffmpeg -i $mp3Path -acodec pcm_s16le -ar 44100 -ac 2 $wavPath -y 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "✓ Successfully converted $mp3File" -ForegroundColor Green
continue
}
} catch {
# FFmpeg not available, try other methods
}
# Try Windows Media Format SDK (if available)
try {
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName Microsoft.VisualBasic
# Use Windows built-in audio conversion
$shell = New-Object -ComObject Shell.Application
# This is a fallback method - may not work on all systems
Write-Host "⚠ FFmpeg not found. Please install FFmpeg or convert manually." -ForegroundColor Red
} catch {
Write-Host "⚠ Could not convert $mp3File automatically." -ForegroundColor Red
}
} else { } else {
Write-Host "⚠ File not found: $mp3Path" -ForegroundColor Red Write-Host "Missing convert_to_ogg.ps1" -ForegroundColor Red
exit 1
} }
}
Write-Host "`nConversion complete! If FFmpeg was not found, please:" -ForegroundColor Cyan
Write-Host "1. Install FFmpeg: https://ffmpeg.org/download.html" -ForegroundColor White
Write-Host "2. Or use an audio converter like Audacity" -ForegroundColor White
Write-Host "3. Convert to: 44.1kHz, 16-bit, Stereo WAV" -ForegroundColor White
+1 -1
View File
@@ -12,7 +12,7 @@ Sound=1
SmoothScroll=1 SmoothScroll=1
[Player] [Player]
Name=GREGOR Name=PLAYER
[Debug] [Debug]
Enabled=1 Enabled=1
+11 -7
View File
@@ -558,16 +558,20 @@ bool ApplicationManager::initializeGame() {
Audio::instance().init(); Audio::instance().init();
// Discover available tracks (up to 100) and queue for background loading // Discover available tracks (up to 100) and queue for background loading
m_totalTracks = 0; m_totalTracks = 0;
std::vector<std::string> trackPaths;
trackPaths.reserve(100);
for (int i = 1; i <= 100; ++i) { for (int i = 1; i <= 100; ++i) {
char buf[128]; char base[128];
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i); std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
// Use simple file existence check via std::filesystem std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
if (std::filesystem::exists(buf)) { if (path.empty()) {
Audio::instance().addTrackAsync(buf);
++m_totalTracks;
} else {
break; 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) { if (m_totalTracks > 0) {
Audio::instance().startBackgroundLoading(); Audio::instance().startBackgroundLoading();
+5 -18
View File
@@ -208,27 +208,14 @@ bool AssetManager::loadSoundEffectWithFallback(const std::string& id, const std:
return false; return false;
} }
// Try WAV first, then MP3 fallback (matching main.cpp pattern) const std::string basePath = "assets/music/" + baseName;
std::string wavPath = "assets/music/" + baseName + ".wav"; std::string resolved = AssetPath::resolveWithExtensions(basePath, { ".wav", ".mp3" });
std::string mp3Path = "assets/music/" + baseName + ".mp3"; if (!resolved.empty() && m_soundSystem->loadSound(id, resolved)) {
logInfo("Loaded sound effect: " + id + " from " + resolved);
// Check WAV first
if (fileExists(wavPath)) {
if (m_soundSystem->loadSound(id, wavPath)) {
logInfo("Loaded sound effect: " + id + " from " + wavPath + " (WAV)");
return true; return true;
} }
}
// Fallback to MP3 setError("Failed to load sound effect: " + id + " (no supported audio extension found)");
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)");
return false; return false;
} }
+64 -67
View File
@@ -15,6 +15,7 @@
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <filesystem> #include <filesystem>
#include <thread>
#include "audio/Audio.h" #include "audio/Audio.h"
#include "audio/SoundEffect.h" #include "audio/SoundEffect.h"
@@ -682,11 +683,11 @@ int main(int, char **)
} }
SDL_SetRenderVSync(renderer, 1); 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()) { if (const char* basePathRaw = SDL_GetBasePath()) {
std::filesystem::path exeDir(basePathRaw); 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::error_code ec;
std::filesystem::current_path(exeDir, ec); std::filesystem::current_path(exeDir, ec);
if (ec) { if (ec) {
@@ -694,12 +695,13 @@ int main(int, char **)
"Failed to set working directory to %s: %s", "Failed to set working directory to %s: %s",
exeDir.string().c_str(), ec.message().c_str()); exeDir.string().c_str(), ec.message().c_str());
} }
#endif
SDL_free(const_cast<char*>(basePathRaw));
} else { } else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
"SDL_GetBasePath() failed; asset lookups rely on current directory: %s", "SDL_GetBasePath() failed; asset lookups rely on current directory: %s",
SDL_GetError()); SDL_GetError());
} }
#endif
FontAtlas font; FontAtlas font;
font.init("FreeSans.ttf", 24); font.init("FreeSans.ttf", 24);
@@ -709,10 +711,15 @@ int main(int, char **)
pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16); pixelFont.init("assets/fonts/PressStart2P-Regular.ttf", 16);
ScoreManager scores; ScoreManager scores;
// Load scores asynchronously to prevent startup hang due to network request // Load scores asynchronously but keep the worker alive until shutdown to avoid lifetime issues
std::thread([&scores]() { std::jthread scoreLoader([&scores]() {
scores.load(); scores.load();
}).detach(); });
const auto ensureScoresLoaded = [&]() {
if (scoreLoader.joinable()) {
scoreLoader.join();
}
};
Starfield starfield; Starfield starfield;
starfield.init(200, LOGICAL_W, LOGICAL_H); starfield.init(200, LOGICAL_W, LOGICAL_H);
Starfield3D starfield3D; Starfield3D starfield3D;
@@ -772,8 +779,18 @@ int main(int, char **)
// Initialize sound effects system // Initialize sound effects system
SoundEffectManager::instance().init(); SoundEffectManager::instance().init();
// Load sound effects auto loadAudioAsset = [](const std::string& basePath, const std::string& id) {
SoundEffectManager::instance().loadSound("clear_line", "assets/music/clear_line.wav"); 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) // Load voice lines for line clears using WAV files (with MP3 fallback)
std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"}; std::vector<std::string> singleSounds = {"well_played", "smooth_clear", "great_move"};
@@ -789,49 +806,25 @@ int main(int, char **)
appendVoices(tripleSounds); appendVoices(tripleSounds);
appendVoices(tetrisSounds); appendVoices(tetrisSounds);
// Helper function to load sound with WAV/MP3 fallback and file existence check auto loadVoice = [&](const std::string& id, const std::string& baseName) {
auto loadSoundWithFallback = [&](const std::string& id, const std::string& baseName) { loadAudioAsset("assets/music/" + baseName, id);
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());
}; };
loadSoundWithFallback("nice_combo", "nice_combo"); loadVoice("nice_combo", "nice_combo");
loadSoundWithFallback("you_fire", "you_fire"); loadVoice("you_fire", "you_fire");
loadSoundWithFallback("well_played", "well_played"); loadVoice("well_played", "well_played");
loadSoundWithFallback("keep_that_ryhtm", "keep_that_ryhtm"); loadVoice("keep_that_ryhtm", "keep_that_ryhtm");
loadSoundWithFallback("great_move", "great_move"); loadVoice("great_move", "great_move");
loadSoundWithFallback("smooth_clear", "smooth_clear"); loadVoice("smooth_clear", "smooth_clear");
loadSoundWithFallback("impressive", "impressive"); loadVoice("impressive", "impressive");
loadSoundWithFallback("triple_strike", "triple_strike"); loadVoice("triple_strike", "triple_strike");
loadSoundWithFallback("amazing", "amazing"); loadVoice("amazing", "amazing");
loadSoundWithFallback("you_re_unstoppable", "you_re_unstoppable"); loadVoice("you_re_unstoppable", "you_re_unstoppable");
loadSoundWithFallback("boom_tetris", "boom_tetris"); loadVoice("boom_tetris", "boom_tetris");
loadSoundWithFallback("wonderful", "wonderful"); loadVoice("wonderful", "wonderful");
loadSoundWithFallback("lets_go", "lets_go"); // For level up loadVoice("lets_go", "lets_go");
loadSoundWithFallback("hard_drop", "hard_drop_001"); loadVoice("hard_drop", "hard_drop_001");
loadSoundWithFallback("new_level", "new_level"); loadVoice("new_level", "new_level");
bool suppressLineVoiceForLevelUp = false; bool suppressLineVoiceForLevelUp = false;
@@ -1122,6 +1115,7 @@ int main(int, char **)
playerName.pop_back(); playerName.pop_back();
} else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) { } else if (e.key.scancode == SDL_SCANCODE_RETURN || e.key.scancode == SDL_SCANCODE_KP_ENTER) {
if (playerName.empty()) playerName = "PLAYER"; if (playerName.empty()) playerName = "PLAYER";
ensureScoresLoaded();
scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName); scores.submit(game.score(), game.lines(), game.level(), game.elapsed(), playerName);
Settings::instance().setPlayerName(playerName); Settings::instance().setPlayerName(playerName);
isNewHighScore = false; isNewHighScore = false;
@@ -1379,6 +1373,7 @@ int main(int, char **)
SDL_StartTextInput(window); SDL_StartTextInput(window);
} else { } else {
isNewHighScore = false; isNewHighScore = false;
ensureScoresLoaded();
scores.submit(game.score(), game.lines(), game.level(), game.elapsed()); scores.submit(game.score(), game.lines(), game.level(), game.elapsed());
} }
state = AppState::GameOver; state = AppState::GameOver;
@@ -1396,25 +1391,21 @@ int main(int, char **)
// Count actual music files first // Count actual music files first
totalTracks = 0; totalTracks = 0;
for (int i = 1; i <= 100; ++i) { // Check up to 100 files std::vector<std::string> trackPaths;
char buf[64]; trackPaths.reserve(100);
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i); for (int i = 1; i <= 100; ++i) {
char base[64];
// Check if file exists std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
SDL_IOStream* file = SDL_IOFromFile(buf, "rb"); std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
if (file) { if (path.empty()) {
SDL_CloseIO(file); break;
totalTracks++;
} else {
break; // No more consecutive files
} }
trackPaths.push_back(path);
} }
totalTracks = static_cast<int>(trackPaths.size());
// Add all found tracks to the background loading queue for (const auto& track : trackPaths) {
for (int i = 1; i <= totalTracks; ++i) { Audio::instance().addTrackAsync(track);
char buf[64];
std::snprintf(buf, sizeof(buf), "assets/music/music%03d.mp3", i);
Audio::instance().addTrackAsync(buf);
} }
// Start background loading thread // Start background loading thread
@@ -1470,7 +1461,12 @@ int main(int, char **)
static bool menuTrackLoaded = false; static bool menuTrackLoaded = false;
if (!menuTrackLoaded) { if (!menuTrackLoaded) {
std::thread([]() { 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(); }).detach();
menuTrackLoaded = true; menuTrackLoaded = true;
} }
@@ -1804,6 +1800,7 @@ int main(int, char **)
// 4. Draw Text // 4. Draw Text
// 4. Draw Text // 4. Draw Text
// Title // Title
ensureScoresLoaded();
bool realHighScore = scores.isHighScore(game.score()); bool realHighScore = scores.isHighScore(game.score());
const char* title = realHighScore ? "NEW HIGH SCORE!" : "GAME OVER"; const char* title = realHighScore ? "NEW HIGH SCORE!" : "GAME OVER";
int tW=0, tH=0; pixelFont.measure(title, 2.0f, tW, tH); int tW=0, tH=0; pixelFont.measure(title, 2.0f, tW, tH);
+92 -8
View File
@@ -2,17 +2,27 @@
#include <array> #include <array>
#include <string> #include <string>
#include <filesystem>
#include <initializer_list>
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
namespace AssetPath { namespace AssetPath {
inline bool fileExists(const std::string& path) { inline std::string& baseDirectory() {
if (path.empty()) { static std::string base;
return false; return base;
} }
SDL_IOStream* file = SDL_IOFromFile(path.c_str(), "rb"); 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(candidate.c_str(), "rb");
if (file) { if (file) {
SDL_CloseIO(file); SDL_CloseIO(file);
return true; return true;
@@ -20,13 +30,58 @@ inline bool fileExists(const std::string& path) {
return false; 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) { inline std::string resolveImagePath(const std::string& originalPath) {
if (originalPath.empty()) { if (originalPath.empty()) {
return originalPath; return originalPath;
} }
if (fileExists(originalPath)) { if (auto resolved = resolveWithBase(originalPath); resolved != originalPath || fileExists(resolved)) {
return originalPath; return resolved;
} }
const std::size_t dot = originalPath.find_last_of('.'); const std::size_t dot = originalPath.find_last_of('.');
@@ -45,12 +100,41 @@ inline std::string resolveImagePath(const std::string& originalPath) {
if (candidate == originalPath) { if (candidate == originalPath) {
continue; continue;
} }
if (fileExists(candidate)) { std::string resolvedCandidate = resolveWithBase(candidate);
return candidate; if (resolvedCandidate != candidate || fileExists(resolvedCandidate)) {
return resolvedCandidate;
} }
} }
return originalPath; 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 } // namespace AssetPath