From ea74af146d2eca31a85c1d321cd9d58eea7d443d Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Wed, 10 Dec 2025 18:24:48 +0100 Subject: [PATCH] mac fix --- CMakeLists.txt | 4 ++ src/audio/Audio.cpp | 100 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 85 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e22b56..78b9383 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,10 @@ target_link_libraries(tetris PRIVATE SDL3::SDL3 SDL3_ttf::SDL3_ttf SDL3_image::S if (WIN32) target_link_libraries(tetris PRIVATE mfplat mfreadwrite mfuuid) endif() +if(APPLE) + # Needed for MP3 decoding via AudioToolbox on macOS + target_link_libraries(tetris PRIVATE "-framework AudioToolbox" "-framework CoreFoundation") +endif() # Include production build configuration include(cmake/ProductionBuild.cmake) diff --git a/src/audio/Audio.cpp b/src/audio/Audio.cpp index ca666d0..da80838 100644 --- a/src/audio/Audio.cpp +++ b/src/audio/Audio.cpp @@ -26,6 +26,9 @@ using Microsoft::WRL::ComPtr; #ifdef min #undef min #endif +#elif defined(__APPLE__) +#include +#include #endif Audio& Audio::instance(){ static Audio inst; return inst; } @@ -36,7 +39,7 @@ bool Audio::init(){ if(outSpec.freq!=0) return true; outSpec.format=SDL_AUDIO_S1 #endif return true; } -#ifdef _WIN32 +#if defined(_WIN32) static bool decodeMP3(const std::string& path, std::vector& outPCM, int& outRate, int& outCh){ outPCM.clear(); outRate=44100; outCh=2; ComPtr reader; @@ -47,15 +50,85 @@ static bool decodeMP3(const std::string& path, std::vector& outPCM, int 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(); } +#elif defined(__APPLE__) +// Decode MP3 files using macOS AudioToolbox so music works on Apple builds. +static bool decodeMP3(const std::string& path, std::vector& outPCM, int& outRate, int& outCh){ + outPCM.clear(); + outRate = 44100; + outCh = 2; + + CFURLRef url = CFURLCreateFromFileSystemRepresentation(nullptr, reinterpret_cast(path.c_str()), path.size(), false); + if (!url) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to create URL for %s", path.c_str()); + return false; + } + + ExtAudioFileRef audioFile = nullptr; + OSStatus status = ExtAudioFileOpenURL(url, &audioFile); + CFRelease(url); + if (status != noErr || !audioFile) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] ExtAudioFileOpenURL failed (%d) for %s", static_cast(status), path.c_str()); + return false; + } + + AudioStreamBasicDescription clientFormat{}; + clientFormat.mSampleRate = 44100.0; + clientFormat.mFormatID = kAudioFormatLinearPCM; + clientFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked; + clientFormat.mBitsPerChannel = 16; + clientFormat.mChannelsPerFrame = 2; + clientFormat.mFramesPerPacket = 1; + clientFormat.mBytesPerFrame = (clientFormat.mBitsPerChannel / 8) * clientFormat.mChannelsPerFrame; + clientFormat.mBytesPerPacket = clientFormat.mBytesPerFrame * clientFormat.mFramesPerPacket; + + status = ExtAudioFileSetProperty(audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(clientFormat), &clientFormat); + if (status != noErr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to set client format (%d) for %s", static_cast(status), path.c_str()); + ExtAudioFileDispose(audioFile); + return false; + } + + const UInt32 framesPerBuffer = 4096; + std::vector buffer(framesPerBuffer * clientFormat.mChannelsPerFrame); + while (true) { + AudioBufferList abl{}; + abl.mNumberBuffers = 1; + abl.mBuffers[0].mNumberChannels = clientFormat.mChannelsPerFrame; + abl.mBuffers[0].mDataByteSize = framesPerBuffer * clientFormat.mBytesPerFrame; + abl.mBuffers[0].mData = buffer.data(); + + UInt32 framesToRead = framesPerBuffer; + status = ExtAudioFileRead(audioFile, &framesToRead, &abl); + if (status != noErr) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] ExtAudioFileRead failed (%d) for %s", static_cast(status), path.c_str()); + ExtAudioFileDispose(audioFile); + return false; + } + + if (framesToRead == 0) { + break; // EOF + } + + size_t samplesRead = static_cast(framesToRead) * clientFormat.mChannelsPerFrame; + outPCM.insert(outPCM.end(), buffer.data(), buffer.data() + samplesRead); + } + + ExtAudioFileDispose(audioFile); + outRate = static_cast(clientFormat.mSampleRate); + outCh = static_cast(clientFormat.mChannelsPerFrame); + return !outPCM.empty(); +} +#else +static bool decodeMP3(const std::string& path, std::vector& outPCM, int& outRate, int& outCh){ + (void)outPCM; (void)outRate; (void)outCh; (void)path; + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported on this platform: %s", path.c_str()); + return false; +} #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)); } + 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()); + tracks.push_back(std::move(t)); } void Audio::shuffle(){ std::lock_guard lock(tracksMutex); @@ -252,15 +325,11 @@ void Audio::backgroundLoadingThread() { } AudioTrack t; t.path = path; -#ifdef _WIN32 - if (mfInitialized && decodeMP3(path, t.pcm, t.rate, t.channels)) { + 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 // Thread-safe addition to tracks if (loadingAbort.load()) { @@ -311,18 +380,11 @@ int Audio::getLoadedTrackCount() const { void Audio::setMenuTrack(const std::string& path) { menuTrack.path = path; -#ifdef _WIN32 - // Ensure MF is started (might be redundant if init called, but safe) - if(!mfStarted){ if(FAILED(MFStartup(MF_VERSION))) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MFStartup failed"); } else mfStarted=true; } - if (decodeMP3(path, menuTrack.pcm, menuTrack.rate, menuTrack.channels)) { menuTrack.ok = true; } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] Failed to decode menu track %s", path.c_str()); } -#else - SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "[Audio] MP3 unsupported (stub): %s", path.c_str()); -#endif } void Audio::playMenuMusic() {