Mercurial > foo_out_sdl
view foo_out_sdl.cc @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | e9bb126753e7 |
| children |
line wrap: on
line source
#include <algorithm> #include <cstdlib> #include <cstdio> #include <cstdarg> #include <cmath> #include <atomic> #include "SDL3/SDL.h" #include "foobar2000/helpers/foobar2000+atl.h" #include "foobar2000/SDK/core_api.h" #include "foobar2000/SDK/output.h" #if audio_sample_size == 32 # define OUTSDL_USE_NATIVE_F32 1 # define OUTSDL_FORMAT SDL_AUDIO_F32 #else # define OUTSDL_FORMAT SDL_AUDIO_S32 #endif /* ----------------------------------------------------------------------------------------- */ /* logf: printf-like interface for printing into the console * a.k.a. "paper hates std::stringstream" * doing it this way results in a smaller binary as well */ enum class logf_level { info, error, }; static int vlogf(logf_level level, const char* format, std::va_list ap) { char buf[1024]; int r; r = std::vsnprintf(buf, sizeof(buf), format, ap); if (r < 0) return -1; switch (level) { case logf_level::info: console::info(buf); break; case logf_level::error: console::error(buf); break; } return r; } static int logf(logf_level level, const char* format, ...) { std::va_list ap; int r; va_start(ap, format); r = vlogf(level, format, ap); va_end(ap); return r; } /* ----------------------------------------------------------------------------------------- */ class OutputSDL : public output_v4 { public: OutputSDL(const GUID& p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth); ~OutputSDL(); virtual double get_latency() override; virtual void process_samples(const audio_chunk &p_chunk) override; virtual void update(bool &p_ready) override; virtual void pause(bool p_state) override; virtual void flush() override; virtual void force_play() override; virtual void volume_set(double p_val) override; virtual bool is_progressing() override; virtual size_t update_v2() override; static GUID g_get_guid() { return { 0x90033ef5, 0x6703, 0x44a0, { 0x98, 0x47, 0x53, 0x99, 0x1, 0x53, 0x3b, 0xd6 } }; } static void g_enum_devices(output_device_enum_callback &p_callback); static bool g_advanced_settings_query() { return false; } static bool g_needs_bitdepth_config() { return false; } static bool g_needs_dither_config() { return false; } static bool g_needs_device_list_prefixes() { return true; } static bool g_supports_multiple_streams() { return false; } static bool g_is_high_latency() { return true; } static uint32_t g_extra_flags() { return 0; } static void g_advanced_settings_popup(HWND p_parent, POINT p_menupoint) {} static const char* g_get_name() { return "SDL"; } private: // ---------- Static members -------------- // These are for loading SDL, as well as // converting to/from GUIDs. static HMODULE sdl_; static GUID DevIDtoGUID(SDL_AudioDeviceID id); static SDL_AudioDeviceID GUIDtoDevID(const GUID &id); static bool LoadSDL(); static bool IsSDLLoaded(); static void UnloadSDL(); // ---------------------------------------- static void SDLCALL AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int additional_amount, int total_amount); void StreamCallback(int amount); void ReinitStream(unsigned int channels, unsigned int freq); // the audio stream itself SDL_AudioStream *stream_; #ifndef OUTSDL_USE_NATIVE_F32 // buffer to convert f64 -> f32 std::vector<float> buffer_; #endif // the current spec of the audio stream SDL_AudioSpec spec_; std::atomic<int> needed_; #define FUNC(type, x, params, callparams) static decltype(&SDL_##x) sdl3_##x; #include "foo_out_sdl_funcs.h" }; HMODULE OutputSDL::sdl_ = nullptr; #define FUNC(type, x, params, callparams) decltype(&SDL_##x) OutputSDL::sdl3_##x = nullptr; #include "foo_out_sdl_funcs.h" OutputSDL::OutputSDL(const GUID &p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth) : stream_(nullptr) #ifndef OUTSDL_USE_NATIVE_F32 /* sane default size */ , buffer_(8096) #endif { // uhhhh needed_.store(0); if (!LoadSDL()) return; // uh oh // increment subsystem counter // hope this succeeds!!! if (!sdl3_InitSubSystem(SDL_INIT_AUDIO)) logf(logf_level::error, sdl3_GetError()); /* make a guess */ spec_.format = OUTSDL_FORMAT; spec_.channels = 2; /* Stereo is most likely. Who cares about surround? :) */ spec_.freq = 44100; /* guess CD quality */ // TODO supply output devices stream_ = sdl3_OpenAudioDeviceStream(GUIDtoDevID(p_device), &spec_, OutputSDL::AudioStreamCallback, this); if (!stream_) logf(logf_level::error, "%s", sdl3_GetError()); } OutputSDL::~OutputSDL() { if (!IsSDLLoaded()) return; /* nothing to do */ sdl3_DestroyAudioStream(stream_); sdl3_QuitSubSystem(SDL_INIT_AUDIO); UnloadSDL(); } void OutputSDL::StreamCallback(int total_amount) { /* Don't need to do anything when additional_amount == 0 */ if (!total_amount) return; needed_ += total_amount; } void SDLCALL OutputSDL::AudioStreamCallback(void *userdata, SDL_AudioStream *stream, int a, int t) { // Simply forwards to the main stream callback OutputSDL *This = reinterpret_cast<OutputSDL *>(userdata); // assert(This->stream_ == stream); // Seems to work fine ? This->StreamCallback(t); } void OutputSDL::ReinitStream(unsigned int channels, unsigned int freq) { /* Fast path; no change * This is the common case. Most music is 44.1khz and stereo. */ if (spec_.channels == channels && spec_.freq == freq) return; spec_.format = OUTSDL_FORMAT; spec_.channels = channels; spec_.freq = freq; logf(logf_level::info, "SDL: setting freq and channels %d, %d", channels, freq); // tell SDL about our change if (!sdl3_SetAudioStreamFormat(stream_, &spec_, nullptr)) logf(logf_level::error, sdl3_GetError()); } double OutputSDL::get_latency() { // ??? I don't know return 0.016; } void OutputSDL::process_samples(const audio_chunk &p_chunk) { // Reinitialize stream with possibly new values for channels and frequency ReinitStream(p_chunk.get_channels(), p_chunk.get_srate()); /* NOTE this is only actually tested with stereo */ #ifdef OUTSDL_USE_NATIVE_F32 t_size sz = p_chunk.get_data_size() * sizeof(float); /* audio_sample is 32-bit floating point; SDL can use this directly */ if (!sdl3_PutAudioStreamData(stream_, p_chunk.get_data(), sz)) logf(logf_level::error, "SDL: %s", sdl3_GetError()); needed_ -= sz; #else /* Expand the buffer if necessary */ t_size sz; sz = p_chunk.get_data_size(); if (sz > buffer_.size()) buffer_.resize(sz); float *buf = buffer_.data(); /* Convert to f32 */ audio_math::convert(p_chunk.get_data(), buf, sz); needed_ -= sz * sizeof(float); /* Add f32 audio to stream */ while (sz > 0) { /* no possible loss of data here; we cap size_t to int */ int to = static_cast<int>((std::min)(sz, INT_MIN / sizeof(float))); sdl3_PutAudioStreamData(stream_, buffer_.data(), to * sizeof(float)); sz -= to; buf += to; } #endif } void OutputSDL::update(bool &p_ready) { p_ready = update_v2() > 0; } size_t OutputSDL::update_v2() { return (std::max)(needed_.load(), 0) / (int)sizeof(float); } void OutputSDL::pause(bool p_state) { logf(logf_level::info, "pause? %d", (int)p_state); bool v; if (p_state) { v = sdl3_PauseAudioStreamDevice(stream_); } else { v = sdl3_ResumeAudioStreamDevice(stream_); } if (!v) logf(logf_level::error, "pause: %s", sdl3_GetError()); } // . these are easy void OutputSDL::flush() { if (!sdl3_ClearAudioStream(stream_)) logf(logf_level::error, "flush: %s", sdl3_GetError()); } void OutputSDL::force_play() { if (!sdl3_FlushAudioStream(stream_)) logf(logf_level::error, "force_play: %s", sdl3_GetError()); } void OutputSDL::volume_set(double p_val) { /* fb2k provides this as dB for some reason, so convert it back * to linear which is what normal programs use. */ double p_linear = std::pow(10.0, p_val / 10.0); if (!sdl3_SetAudioStreamGain(stream_, /* shut up msvc */static_cast<float>(p_linear))) logf(logf_level::error, "volume_set: %s", sdl3_GetError()); } /* conversion to/from GUID and SDL_AudioDeviceID */ GUID OutputSDL::DevIDtoGUID(SDL_AudioDeviceID id) { GUID g = {0}; g.Data1 = id; return g; } SDL_AudioDeviceID OutputSDL::GUIDtoDevID(const GUID &g) { return g.Data1; } void OutputSDL::g_enum_devices(output_device_enum_callback& p_callback) { int i, count; if (!LoadSDL()) return; /* uh oh */ SDL_AudioDeviceID *devs = sdl3_GetAudioPlaybackDevices(&count); for (i = 0; i < count; i++) { const char *name = sdl3_GetAudioDeviceName(devs[i]); if (!name) continue; p_callback.on_device(DevIDtoGUID(devs[i]), name, std::strlen(name)); } // Add default device too for brevity p_callback.on_device(DevIDtoGUID(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK), "default", 7); } bool OutputSDL::LoadSDL(void) { if (IsSDLLoaded()) return true; // Unload any failed attempts UnloadSDL(); sdl_ = LoadLibrary("SDL3.dll"); if (!sdl_) { logf(logf_level::error, "Failed to load SDL3.dll!"); return false; } #define FUNC(type, x, params, callparams) \ sdl3_##x = (decltype(&SDL_##x))GetProcAddress(sdl_, "SDL_" #x); #include "foo_out_sdl_funcs.h" return IsSDLLoaded(); } bool OutputSDL::IsSDLLoaded() { return (sdl_ #define FUNC(type, x, params, callparams) && (!!sdl3_##x) #include "foo_out_sdl_funcs.h" ); } void OutputSDL::UnloadSDL() { // kill off everything if (sdl_) { FreeLibrary(sdl_); sdl_ = nullptr; } #define FUNC(type, x, params, callparams) sdl3_##x = nullptr; #include "foo_out_sdl_funcs.h" } bool OutputSDL::is_progressing() { return true; // ? } static output_factory_t<OutputSDL> g_output_sdl_factory; //////////////////////////////////////////////////////////////////////////////// const char *about = "Copyright (c) paper <paper@tflc.us>, 2026\n"; // very beta ;) DECLARE_COMPONENT_VERSION("SDL output", "0.1", about); VALIDATE_COMPONENT_FILENAME("foo_out_sdl.dll");
