diff --git a/CMakeLists.txt b/CMakeLists.txt index 7bf4a4b..85eab0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,14 +107,16 @@ if (NOT MSVC) target_compile_options(boo PRIVATE -Wno-narrowing) endif() -find_package(IPP) -if (IPP_FOUND) - target_compile_definitions(boo PUBLIC -DINTEL_IPP=1) - target_include_directories(boo PUBLIC ${IPP_INCLUDE_DIRS}) - target_link_libraries(boo PUBLIC ${IPP_LIBRARIES}) - message(STATUS "Building with IPP support") -else() - message(WARNING "IPP not found; skipping support") +if (NOT NINTENDO_SWITCH) + find_package(IPP) + if (IPP_FOUND) + target_compile_definitions(boo PUBLIC -DINTEL_IPP=1) + target_include_directories(boo PUBLIC ${IPP_INCLUDE_DIRS}) + target_link_libraries(boo PUBLIC ${IPP_LIBRARIES}) + message(STATUS "Building with IPP support") + else() + message(WARNING "IPP not found; skipping support") + endif () endif () set(_EXTRA_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}) @@ -131,7 +133,16 @@ if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64 set(AudioMatrix_SRC lib/audiodev/AudioMatrixSSE.cpp) endif() -if(WINDOWS_STORE) +option(USE_SDL2_AUDIO "Use SDL2 audio backend" ON) +if (USE_SDL2_AUDIO) + if (NOT TARGET SDL2::SDL2-static) + find_package(SDL2 REQUIRED) + endif () + target_sources(boo PRIVATE + ${AudioMatrix_SRC} + lib/audiodev/SDL2.cpp) + target_link_libraries(boo PRIVATE SDL2::SDL2-static) +elseif (WINDOWS_STORE) target_sources(boo PRIVATE ${AudioMatrix_SRC} lib/audiodev/WASAPI.cpp @@ -260,12 +271,10 @@ else(NOT GEKKO) endif() -if(NOT NX) - target_link_libraries(boo - PUBLIC - soxr - ) -endif() +target_link_libraries(boo + PUBLIC + soxr +) target_link_libraries(boo PUBLIC logvisor OptickCore) target_include_directories(boo diff --git a/include/boo/audiodev/IAudioVoiceEngine.hpp b/include/boo/audiodev/IAudioVoiceEngine.hpp index b0b69c7..682bb07 100644 --- a/include/boo/audiodev/IAudioVoiceEngine.hpp +++ b/include/boo/audiodev/IAudioVoiceEngine.hpp @@ -100,6 +100,18 @@ struct IAudioVoiceEngine { /** Get canonical count of frames for each 5ms output block */ virtual size_t get5MsFrames() const = 0; + + /** Stops the async audio pump */ + virtual void stopPump() const {} + + /** Starts the async audio pump */ + virtual void startPump() const {} + + /** Lock the async audio pump for updates */ + virtual void lockPump() const {} + + /** Unlock the async audio pump */ + virtual void unlockPump() const {} }; /** Construct host platform's voice engine */ diff --git a/lib/audiodev/SDL2.cpp b/lib/audiodev/SDL2.cpp new file mode 100644 index 0000000..7cbd820 --- /dev/null +++ b/lib/audiodev/SDL2.cpp @@ -0,0 +1,151 @@ +#include "lib/audiodev/AudioVoiceEngine.hpp" +#include "logvisor/logvisor.hpp" + +#include +#include + +#include + +#ifdef __GNUC__ +[[noreturn]] inline __attribute__((always_inline)) void unreachable() { __builtin_unreachable(); } +#elif defined(_MSC_VER) +[[noreturn]] __forceinline void unreachable() { __assume(false); } +#else +#error Unknown compiler +#endif + +namespace boo { +static logvisor::Module Log("boo::SDL2AudioVoiceEngine"); + +static void SDLAudioCallback(void* userdata, Uint8* stream, int len); + +static inline soxr_datatype_t _toSoxrFormat(SDL_AudioFormat format) { + switch (format) { + case AUDIO_F32SYS: + return SOXR_FLOAT32_I; + case AUDIO_S32SYS: + return SOXR_INT32_I; + case AUDIO_S16SYS: + return SOXR_INT16_I; + default: + Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), format); + unreachable(); + } +} + +static inline unsigned int _bitsPerSample(SDL_AudioFormat format) { + switch (format) { + case AUDIO_F32SYS: + case AUDIO_S32SYS: + return 32; + case AUDIO_S16SYS: + return 16; + default: + Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), format); + unreachable(); + } +} + +struct SDL2AudioVoiceEngine : BaseAudioVoiceEngine { + SDL_AudioDeviceID dev = 0; + + bool _openAudioDevice(const char* name) { + if (dev != 0) { + SDL_CloseAudioDevice(dev); + } + SDL_AudioSpec desired; + SDL_AudioSpec obtained; + SDL_zero(desired); + SDL_zero(obtained); + desired.freq = 48000; + desired.format = AUDIO_F32SYS; + desired.channels = 2; + desired.samples = 800; // 16.67ms @ 48000hz + desired.callback = &SDLAudioCallback; + desired.userdata = this; + dev = SDL_OpenAudioDevice(name, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); + if (dev == 0) { + Log.report(logvisor::Error, FMT_STRING("Failed to open audio device: {}"), SDL_GetError()); + return false; + } + m_mixInfo.m_sampleRate = obtained.freq; + m_mixInfo.m_sampleFormat = _toSoxrFormat(obtained.format); + m_mixInfo.m_bitsPerSample = _bitsPerSample(obtained.format); + m_mixInfo.m_channels = AudioChannelSet::Stereo; // TODO + m_mixInfo.m_periodFrames = obtained.samples; + m_5msFrames = m_mixInfo.m_sampleRate * 5 / 1000; + return true; + } + + SDL2AudioVoiceEngine(const char* uniqueName, const char* friendlyName) { + Log.report(logvisor::Info, FMT_STRING("Using audio driver {}"), SDL_GetCurrentAudioDriver()); + SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, friendlyName); + _openAudioDevice(nullptr); + } + + ~SDL2AudioVoiceEngine() override { SDL_CloseAudioDevice(dev); } + + std::string getCurrentAudioOutput() const override { + // TODO + return ""; + } + + bool setCurrentAudioOutput(const char* name) override { return _openAudioDevice(name); } + + std::vector> enumerateAudioOutputs() const override { + int numDevices = SDL_GetNumAudioDevices(0); + if (numDevices == -1) { + return {}; + } + std::vector> ret; + for (int i = 0; i < numDevices; ++i) { + ret.emplace_back(SDL_GetAudioDeviceName(i, 0), std::string{}); + } + return ret; + } + + // Called from SDL audio thread; synchronization is handled via lockPump/unlockPump + void _asyncPumpAndMixVoices(Uint8* stream, int len) { + const auto channels = m_mixInfo.m_channelMap.m_channelCount; + switch (m_mixInfo.m_sampleFormat) { + case SOXR_INT16_I: + _pumpAndMixVoices(len / (sizeof(int16_t) * channels), reinterpret_cast(stream)); + break; + case SOXR_INT32_I: + _pumpAndMixVoices(len / (sizeof(int32_t) * channels), reinterpret_cast(stream)); + break; + case SOXR_FLOAT32_I: + _pumpAndMixVoices(len / (sizeof(float) * channels), reinterpret_cast(stream)); + break; + default: + Log.report(logvisor::Fatal, FMT_STRING("Unhandled audio format {}"), m_mixInfo.m_sampleFormat); + unreachable(); + } + } + + void stopPump() const override { SDL_PauseAudioDevice(dev, 1); } + void startPump() const override { SDL_PauseAudioDevice(dev, 0); } + void lockPump() const override { SDL_LockAudioDevice(dev); } + void unlockPump() const override { SDL_UnlockAudioDevice(dev); } + + // MIDI unsupported + std::vector> enumerateMIDIInputs() const override { return {}; } + bool supportsVirtualMIDIIn() const override { return false; } + std::unique_ptr newVirtualMIDIIn(ReceiveFunctor&& receiver) override { return {}; } + std::unique_ptr newVirtualMIDIOut() override { return {}; } + std::unique_ptr newVirtualMIDIInOut(ReceiveFunctor&& receiver) override { return {}; } + std::unique_ptr newRealMIDIIn(const char* name, ReceiveFunctor&& receiver) override { return {}; } + std::unique_ptr newRealMIDIOut(const char* name) override { return {}; } + std::unique_ptr newRealMIDIInOut(const char* name, ReceiveFunctor&& receiver) override { return {}; } + bool useMIDILock() const override { return false; } +}; + +static void SDLAudioCallback(void* userdata, Uint8* stream, int len) { + auto* engine = static_cast(userdata); + engine->_asyncPumpAndMixVoices(stream, len); +} + +std::unique_ptr NewAudioVoiceEngine(const char* uniqueName, const char* friendlyName) { + return std::make_unique(uniqueName, friendlyName); +} +} // namespace boo