diff options
-rw-r--r-- | makefile | 5 | ||||
-rw-r--r-- | scripts/src/3rdparty.lua | 304 | ||||
-rw-r--r-- | scripts/src/main.lua | 10 | ||||
-rw-r--r-- | scripts/src/osd/modules.lua | 25 | ||||
-rw-r--r-- | src/osd/modules/lib/osdobj_common.cpp | 10 | ||||
-rw-r--r-- | src/osd/modules/lib/osdobj_common.h | 9 | ||||
-rw-r--r-- | src/osd/modules/sound/pa_sound.cpp | 438 |
7 files changed, 652 insertions, 149 deletions
@@ -31,6 +31,7 @@ # SDL_INI_PATH = .;$HOME/.mame/;ini; # SDL2_MULTIAPI = 1 # NO_USE_MIDI = 1 +# NO_USE_PORTAUDIO = 1 # DONT_USE_NETWORK = 1 # USE_QTDEBUG = 1 # NO_X11 = 1 @@ -644,6 +645,10 @@ ifdef NO_USE_MIDI PARAMS += --NO_USE_MIDI='$(NO_USE_MIDI)' endif +ifdef NO_USE_PORTAUDIO +PARAMS += --NO_USE_PORTAUDIO='$(NO_USE_PORTAUDIO)' +endif + ifdef USE_QTDEBUG PARAMS += --USE_QTDEBUG='$(USE_QTDEBUG)' endif diff --git a/scripts/src/3rdparty.lua b/scripts/src/3rdparty.lua index 211cd7c5fd8..081f8692f66 100644 --- a/scripts/src/3rdparty.lua +++ b/scripts/src/3rdparty.lua @@ -902,155 +902,161 @@ end -------------------------------------------------- -- PortAudio library objects -------------------------------------------------- --- ---if not _OPTIONS["with-system-portaudio"] then ---project "portaudio" --- uuid "0755c5f5-eccf-47f3-98a9-df67018a94d4" --- kind "StaticLib" --- --- configuration { "vs*" } --- buildoptions { --- "/wd4245", -- warning C4245: 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch --- "/wd4244", -- warning C4244: 'argument' : conversion from 'xxx' to 'xxx', possible loss of data --- "/wd4100", -- warning C4100: 'xxx' : unreferenced formal parameter --- "/wd4389", -- warning C4389: 'operator' : signed/unsigned mismatch --- "/wd4189", -- warning C4189: 'xxx' : local variable is initialized but not referenced --- "/wd4127", -- warning C4127: conditional expression is constant --- } ---if _OPTIONS["vs"]=="intel-15" then --- buildoptions { --- "/Qwd869", -- remark #869: parameter "xxx" was never referenced --- "/Qwd1478", -- warning #1478: function "xxx" (declared at line yyy of "zzz") was declared deprecated --- "/Qwd2544", -- message #2544: empty dependent statement in if-statement --- "/Qwd1879", -- warning #1879: unimplemented pragma ignored --- } ---end --- configuration { "vs201*" } --- buildoptions { --- "/wd4456", -- warning C4456: declaration of 'xxx' hides previous local declaration --- } --- --- configuration { "gmake or ninja" } --- buildoptions_c { --- "-Wno-strict-prototypes", --- "-Wno-bad-function-cast", --- "-Wno-undef", --- "-Wno-missing-braces", --- "-Wno-unused-variable", --- "-Wno-unused-value", --- "-Wno-unused-function", --- "-Wno-unknown-pragmas", --- } --- --- local version = str_to_version(_OPTIONS["gcc_version"]) --- if (_OPTIONS["gcc"]~=nil) then --- if string.find(_OPTIONS["gcc"], "clang") or string.find(_OPTIONS["gcc"], "android") then --- buildoptions_c { --- "-Wno-unknown-warning-option", --- "-Wno-absolute-value", --- "-Wno-unused-but-set-variable", --- "-Wno-maybe-uninitialized", --- "-Wno-sometimes-uninitialized", --- } --- else --- if (version >= 40600) then --- buildoptions_c { --- "-Wno-unused-but-set-variable", --- "-Wno-maybe-uninitialized", --- "-Wno-sometimes-uninitialized", --- } --- end --- end --- end --- configuration { "vs*" } --- buildoptions { --- "/wd4204", -- warning C4204: nonstandard extension used : non-constant aggregate initializer --- "/wd4701", -- warning C4701: potentially uninitialized local variable 'xxx' used --- } --- --- configuration { } --- --- includedirs { --- MAME_DIR .. "3rdparty/portaudio/include", --- MAME_DIR .. "3rdparty/portaudio/src/common", --- } --- --- files { --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_allocation.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_converters.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_cpuload.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_dither.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_debugprint.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_front.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_process.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_stream.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_trace.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/skeleton/pa_hostapi_skeleton.c", --- } --- --- if _OPTIONS["targetos"]=="windows" then --- defines { --- "PA_USE_DS=1", --- "PA_USE_WDMKS=1", --- "PA_USE_WMME=1", --- } --- includedirs { --- MAME_DIR .. "3rdparty/portaudio/src/os/win", --- } --- --- configuration { } --- files { --- MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_util.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_waveformat.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_hostapis.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_wdmks_utils.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_coinitialize.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_ringbuffer.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c", --- } --- --- end --- if _OPTIONS["targetos"]=="linux" then --- defines { --- "PA_USE_ALSA=1", --- "PA_USE_OSS=1", --- "HAVE_LINUX_SOUNDCARD_H", --- } --- includedirs { --- MAME_DIR .. "3rdparty/portaudio/src/os/unix", --- } --- files { --- MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_util.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/alsa/pa_linux_alsa.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/oss/pa_unix_oss.c", --- } --- end --- if _OPTIONS["targetos"]=="macosx" then --- defines { --- "PA_USE_COREAUDIO=1", --- } --- includedirs { --- MAME_DIR .. "3rdparty/portaudio/src/os/unix", --- } --- files { --- MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c", --- MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_util.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c", --- MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c", --- MAME_DIR .. "3rdparty/portaudio/src/common/pa_ringbuffer.c", --- } --- end --- ---else ---links { --- ext_lib("portaudio"), ---} ---end +if _OPTIONS["NO_USE_PORTAUDIO"]~="1" then +if not _OPTIONS["with-system-portaudio"] then +project "portaudio" + uuid "0755c5f5-eccf-47f3-98a9-df67018a94d4" + kind "StaticLib" + + configuration { "vs*" } + buildoptions { + "/wd4245", -- warning C4245: 'conversion' : conversion from 'type1' to 'type2', signed/unsigned mismatch + "/wd4244", -- warning C4244: 'argument' : conversion from 'xxx' to 'xxx', possible loss of data + "/wd4100", -- warning C4100: 'xxx' : unreferenced formal parameter + "/wd4389", -- warning C4389: 'operator' : signed/unsigned mismatch + "/wd4189", -- warning C4189: 'xxx' : local variable is initialized but not referenced + "/wd4127", -- warning C4127: conditional expression is constant + } + if _OPTIONS["vs"]=="intel-15" then + buildoptions { + "/Qwd869", -- remark #869: parameter "xxx" was never referenced + "/Qwd1478", -- warning #1478: function "xxx" (declared at line yyy of "zzz") was declared deprecated + "/Qwd2544", -- message #2544: empty dependent statement in if-statement + "/Qwd1879", -- warning #1879: unimplemented pragma ignored + } + end + configuration { "vs2015*" } + buildoptions { + "/wd4456", -- warning C4456: declaration of 'xxx' hides previous local declaration + } + + configuration { "gmake or ninja" } + buildoptions_c { + "-Wno-strict-prototypes", + "-Wno-bad-function-cast", + "-Wno-undef", + "-Wno-missing-braces", + "-Wno-unused-variable", + "-Wno-unused-value", + "-Wno-unused-function", + "-Wno-unknown-pragmas", + } + + local version = str_to_version(_OPTIONS["gcc_version"]) + if (_OPTIONS["gcc"]~=nil) then + if string.find(_OPTIONS["gcc"], "clang") or string.find(_OPTIONS["gcc"], "android") then + buildoptions_c { + "-Wno-unknown-warning-option", + "-Wno-absolute-value", + "-Wno-unused-but-set-variable", + "-Wno-maybe-uninitialized", + "-Wno-sometimes-uninitialized", + } + else + if (version >= 40600) then + buildoptions_c { + "-Wno-unused-but-set-variable", + "-Wno-maybe-uninitialized", + "-Wno-sometimes-uninitialized", + "-w", + "-Wno-incompatible-pointer-types-discards-qualifiers", + } + end + end + end + configuration { "vs*" } + buildoptions { + "/wd4204", -- warning C4204: nonstandard extension used : non-constant aggregate initializer + "/wd4701", -- warning C4701: potentially uninitialized local variable 'xxx' used + } + + configuration { } + + includedirs { + MAME_DIR .. "3rdparty/portaudio/include", + MAME_DIR .. "3rdparty/portaudio/src/common", + } + + files { + MAME_DIR .. "3rdparty/portaudio/src/common/pa_allocation.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_converters.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_cpuload.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_dither.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_debugprint.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_front.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_process.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_stream.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_trace.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/skeleton/pa_hostapi_skeleton.c", + } + + if _OPTIONS["targetos"]=="windows" then + defines { + "PA_USE_DS=1", + "PA_USE_WASAPI=1", + "PA_USE_WDMKS=1", + "PA_USE_WMME=1", + } + includedirs { + MAME_DIR .. "3rdparty/portaudio/src/os/win", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/wasapi/mingw-include", + } + + configuration { } + files { + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_util.c", + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_waveformat.c", + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_hostapis.c", + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_coinitialize.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/dsound/pa_win_ds_dynlink.c", + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_hostapis.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/wasapi/pa_win_wasapi.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_ringbuffer.c", + } + + end + if _OPTIONS["targetos"]=="linux" then + defines { + "PA_USE_ALSA=1", + "PA_USE_OSS=1", + "HAVE_LINUX_SOUNDCARD_H", + } + includedirs { + MAME_DIR .. "3rdparty/portaudio/src/os/unix", + } + files { + MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c", + MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_util.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/alsa/pa_linux_alsa.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/oss/pa_unix_oss.c", + } + end + if _OPTIONS["targetos"]=="macosx" then + defines { + "PA_USE_COREAUDIO=1", + } + includedirs { + MAME_DIR .. "3rdparty/portaudio/src/os/unix", + } + files { + MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c", + MAME_DIR .. "3rdparty/portaudio/src/os/unix/pa_unix_util.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core_utilities.c", + MAME_DIR .. "3rdparty/portaudio/src/hostapi/coreaudio/pa_mac_core_blocking.c", + MAME_DIR .. "3rdparty/portaudio/src/common/pa_ringbuffer.c", + } + end + +else +links { + ext_lib("portaudio"), +} +end +end -------------------------------------------------- -- SDL2 library diff --git a/scripts/src/main.lua b/scripts/src/main.lua index 22bef465c18..0a92b1fed57 100644 --- a/scripts/src/main.lua +++ b/scripts/src/main.lua @@ -284,6 +284,16 @@ if (STANDALONE~=true) then } end + if _OPTIONS["NO_USE_PORTAUDIO"]~="1" then + links { + ext_lib("portaudio"), + } + if _OPTIONS["targetos"]=="windows" then + links { + "setupapi", + } + end + end if _OPTIONS["NO_USE_MIDI"]~="1" then links { ext_lib("portmidi"), diff --git a/scripts/src/osd/modules.lua b/scripts/src/osd/modules.lua index a863cd360b9..ce9977d8dd6 100644 --- a/scripts/src/osd/modules.lua +++ b/scripts/src/osd/modules.lua @@ -80,6 +80,7 @@ function osdmodulesbuild() MAME_DIR .. "src/osd/modules/midi/none.cpp", MAME_DIR .. "src/osd/modules/sound/js_sound.cpp", MAME_DIR .. "src/osd/modules/sound/direct_sound.cpp", + MAME_DIR .. "src/osd/modules/sound/pa_sound.cpp", MAME_DIR .. "src/osd/modules/sound/coreaudio_sound.cpp", MAME_DIR .. "src/osd/modules/sound/sdl_sound.cpp", MAME_DIR .. "src/osd/modules/sound/xaudio2_sound.cpp", @@ -122,6 +123,7 @@ function osdmodulesbuild() includedirs { MAME_DIR .. "3rdparty/winpcap/Include", MAME_DIR .. "3rdparty/compat/mingw", + MAME_DIR .. "3rdparty/portaudio/include", } includedirs { @@ -215,6 +217,12 @@ function osdmodulesbuild() MAME_DIR .. "3rdparty/rapidjson/include", } + if _OPTIONS["NO_USE_PORTAUDIO"]=="1" then + defines { + "NO_USE_PORTAUDIO", + } + end + if _OPTIONS["NO_USE_MIDI"]=="1" then defines { "NO_USE_MIDI", @@ -494,6 +502,23 @@ if not _OPTIONS["NO_USE_MIDI"] then end newoption { + trigger = "NO_USE_PORTAUDIO", + description = "Disable PortAudio interface", + allowed = { + { "0", "Enable PortAudio" }, + { "1", "Disable PortAudio" }, + }, +} + +if not _OPTIONS["NO_USE_PORTAUDIO"] then + if _OPTIONS["targetos"]=="windows" or _OPTIONS["targetos"]=="linux" or _OPTIONS["targetos"]=="macosx" then + _OPTIONS["NO_USE_PORTAUDIO"] = "0" + else + _OPTIONS["NO_USE_PORTAUDIO"] = "1" + end +end + +newoption { trigger = "MODERN_WIN_API", description = "Use Modern Windows APIs", allowed = { diff --git a/src/osd/modules/lib/osdobj_common.cpp b/src/osd/modules/lib/osdobj_common.cpp index d462e946587..dcca53b4361 100644 --- a/src/osd/modules/lib/osdobj_common.cpp +++ b/src/osd/modules/lib/osdobj_common.cpp @@ -127,6 +127,13 @@ const options_entry osd_options::s_option_entries[] = { OSDOPTION_SOUND, OSDOPTVAL_AUTO, OPTION_STRING, "sound output method: " }, { OSDOPTION_AUDIO_LATENCY "(1-5)", "2", OPTION_INTEGER, "set audio latency (increase to reduce glitches, decrease for responsiveness)" }, +#ifndef NO_USE_PORTAUDIO + { nullptr, nullptr, OPTION_HEADER, "PORTAUDIO OPTIONS" }, + { OSDOPTION_PA_API, OSDOPTVAL_NONE, OPTION_STRING, "PortAudio API" }, + { OSDOPTION_PA_DEVICE, OSDOPTVAL_NONE, OPTION_STRING, "PortAudio device" }, + { OSDOPTION_PA_LATENCY "(0.001-0.25)", "0", OPTION_FLOAT, "suggested latency in seconds, 0 for default" }, +#endif + #ifdef SDLMAME_MACOSX { nullptr, nullptr, OPTION_HEADER, "CoreAudio-SPECIFIC OPTIONS" }, { OSDOPTION_AUDIO_OUTPUT, OSDOPTVAL_AUTO, OPTION_STRING, "Audio output device" }, @@ -213,6 +220,9 @@ void osd_common_t::register_options() REGISTER_MODULE(m_mod_man, SOUND_COREAUDIO); REGISTER_MODULE(m_mod_man, SOUND_JS); REGISTER_MODULE(m_mod_man, SOUND_SDL); +#ifndef NO_USE_PORTAUDIO + REGISTER_MODULE(m_mod_man, SOUND_PORTAUDIO); +#endif REGISTER_MODULE(m_mod_man, SOUND_NONE); REGISTER_MODULE(m_mod_man, MONITOR_SDL); diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index 5ae94d7a699..df8096840cf 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -73,6 +73,10 @@ #define OSDOPTION_SOUND "sound" #define OSDOPTION_AUDIO_LATENCY "audio_latency" +#define OSDOPTION_PA_API "pa_api" +#define OSDOPTION_PA_DEVICE "pa_device" +#define OSDOPTION_PA_LATENCY "pa_latency" + #define OSDOPTION_AUDIO_OUTPUT "audio_output" #define OSDOPTION_AUDIO_EFFECT "audio_effect" @@ -160,6 +164,11 @@ public: const char *bgfx_shadow_mask() const { return value(OSDOPTION_BGFX_SHADOW_MASK); } const char *bgfx_avi_name() const { return value(OSDOPTION_BGFX_AVI_NAME); } + // PortAudio options + const char *pa_api() const { return value(OSDOPTION_PA_API); } + const char *pa_device() const { return value(OSDOPTION_PA_DEVICE); } + const float pa_latency() const { return float_value(OSDOPTION_PA_LATENCY); } + private: static const options_entry s_option_entries[]; }; diff --git a/src/osd/modules/sound/pa_sound.cpp b/src/osd/modules/sound/pa_sound.cpp new file mode 100644 index 00000000000..1295097d132 --- /dev/null +++ b/src/osd/modules/sound/pa_sound.cpp @@ -0,0 +1,438 @@ +// license:BSD-3-Clause +// copyright-holders:intealls, R.Belmont +/*************************************************************************** + + pa_sound.c + + PortAudio interface. + +*******************************************************************c********/ + +#include "sound_module.h" +#include "modules/osdmodule.h" + +#ifndef NO_USE_PORTAUDIO + +#include "portaudio/include/portaudio.h" +#include "modules/lib/osdobj_common.h" + +#include <iostream> +#include <fstream> +#include <sstream> +#include <atomic> +#include <cmath> +#include <climits> +#include <algorithm> + +#ifdef WIN32 +#include "pa_win_wasapi.h" +#endif + +#define LOG_FILE "pa.log" +#define LOG_BUFCNT 0 + +class sound_pa : public osd_module, public sound_module +{ +public: + sound_pa() + : osd_module(OSD_SOUND_PROVIDER, "portaudio"), sound_module() + { + } + virtual ~sound_pa() { } + + virtual int init(osd_options const &options); + virtual void exit(); + + // sound_module + + virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame); + virtual void set_mastervolume(int attenuation); + +private: + /* Lock free SPSC ring buffer */ + template <typename T> + struct audio_buffer { + T* buf; + int size; + int reserve; + std::atomic<int> rd_pos, wr_pos; + + audio_buffer(int size, int reserve) : size(size), reserve(reserve) { + rd_pos = wr_pos = 0; + buf = new T[size](); + } + + ~audio_buffer() { delete[] buf; } + + int count() { + int diff; + diff = wr_pos - rd_pos; + diff = diff < 0 ? size + diff : diff; + diff -= reserve; + return diff < 0 ? 0 : diff; + } + + void increment_wrpos(int n) { + wr_pos = (wr_pos + n) % size; + } + + int write(const T* src, int n, int attenuation) { + n = std::min<int>(n, size - count()); + + if (wr_pos + n > size) { + att_memcpy(buf + wr_pos, src, sizeof(T) * (size - wr_pos), attenuation); + att_memcpy(buf, src + (size - wr_pos), sizeof(T) * (n - (size - wr_pos)), attenuation); + } else { + att_memcpy(buf + wr_pos, src, sizeof(T) * n, attenuation); + } + + increment_wrpos(n); + + return n; + } + + void increment_rdpos(int n) { + rd_pos = (rd_pos + n) % size; + } + + int read(T* dst, int n) { + n = std::min<int>(n, count()); + + if (rd_pos + n > size) { + std::memcpy(dst, buf + rd_pos, sizeof(T) * (size - rd_pos)); + std::memcpy(dst + (size - rd_pos), buf, sizeof(T) * (n - (size - rd_pos))); + } else { + std::memcpy(dst, buf + rd_pos, sizeof(T) * n); + } + + increment_rdpos(n); + + return n; + } + + int clear(int n) { + n = std::min<int>(n, size - count()); + + if (wr_pos + n > size) { + std::memset(buf + wr_pos, 0, sizeof(T) * (size - wr_pos)); + std::memset(buf, 0, sizeof(T) * (n - (size - wr_pos))); + } else { + std::memset(buf + wr_pos, 0, sizeof(T) * n); + } + + increment_wrpos(n); + + return n; + } + + void att_memcpy(T* dest, const T* data, int n, int attenuation) { + int level = powf(10.0, attenuation / 20.0) * 32768; + n /= sizeof(T); + while (n--) + *dest++ = (*data++ * level) >> 15; + } + }; + + int callback(s16* output_buffer, size_t number_of_frames); + static int _callback(const void*, + void *output_buffer, + unsigned long number_of_frames, + const PaStreamCallbackTimeInfo*, + PaStreamCallbackFlags, + void *arg) { return static_cast<sound_pa*> (arg)-> + callback((s16*) output_buffer, number_of_frames * 2); } + + PaDeviceIndex list_get_devidx(const char* api_str, const char* device_str); + + PaStream* m_pa_stream; + PaError err; + + int m_attenuation; + + audio_buffer<s16>* m_ab; + + std::atomic<bool> m_has_underflowed; + std::atomic<bool> m_has_overflowed; + unsigned m_underflows; + unsigned m_overflows; + + int m_skip_threshold; // this many samples in the buffer ~1 second in a row count as an overflow + osd_ticks_t m_osd_ticks; + osd_ticks_t m_skip_threshold_ticks; + osd_ticks_t m_osd_tps; + int m_buffer_min_ct; + +#if LOG_BUFCNT + std::stringstream m_log; +#endif +}; + +int sound_pa::init(osd_options const &options) +{ + PaStreamParameters stream_params; + const PaStreamInfo* stream_info; + const PaHostApiInfo* api_info; + const PaDeviceInfo* device_info; + + unsigned long frames_per_callback = paFramesPerBufferUnspecified; + double callback_interval; + + if (!sample_rate()) + return 0; + + m_attenuation = options.volume(); + m_underflows = 0; + m_overflows = 0; + m_has_overflowed = false; + m_has_underflowed = false; + m_skip_threshold_ticks = 0; + + try { + m_ab = new audio_buffer<s16>(m_sample_rate, 2); + } catch (std::bad_alloc&) { + osd_printf_verbose("PortAudio: Unable to allocate audio buffer, sound is disabled\n"); + goto error; + } + + m_osd_tps = osd_ticks_per_second(); + + err = Pa_Initialize(); + + if (err != paNoError) goto pa_error; + + stream_params.device = list_get_devidx(options.pa_api(), options.pa_device()); + + stream_params.channelCount = 2; + stream_params.sampleFormat = paInt16; + stream_params.hostApiSpecificStreamInfo = NULL; + + device_info = Pa_GetDeviceInfo(stream_params.device); + + // 0 = use default + stream_params.suggestedLatency = options.pa_latency() ? options.pa_latency() : device_info->defaultLowOutputLatency; + +#ifdef WIN32 + PaWasapiStreamInfo wasapi_stream_info; + + // if requested latency is less than 20 ms, we need to use exclusive mode + if (Pa_GetHostApiInfo(device_info->hostApi)->type == paWASAPI && stream_params.suggestedLatency < 0.020) + { + wasapi_stream_info.size = sizeof(PaWasapiStreamInfo); + wasapi_stream_info.hostApiType = paWASAPI; + wasapi_stream_info.flags = paWinWasapiExclusive; + wasapi_stream_info.version = 1; + + stream_params.hostApiSpecificStreamInfo = &wasapi_stream_info; + + // for latencies lower than ~16 ms, we need to use event mode + if (stream_params.suggestedLatency < 0.016) + { + // only way to control output latency with event mode + frames_per_callback = stream_params.suggestedLatency * m_sample_rate; + + // needed for event mode to work + stream_params.suggestedLatency = 0; + } + } +#endif + + err = Pa_OpenStream(&m_pa_stream, + NULL, + &stream_params, + m_sample_rate, + frames_per_callback, + paClipOff, + _callback, + this); + + if (err != paNoError) goto pa_error; + + stream_info = Pa_GetStreamInfo(m_pa_stream); + api_info = Pa_GetHostApiInfo(device_info->hostApi); + + // in milliseconds + callback_interval = static_cast<double>(stream_info->outputLatency) * 1000.0; + + // clamp to a probable figure + callback_interval = std::min<double>(callback_interval, 20.0); + + // set the best guess callback interval to allowed count, each audio_latency step > 1 adds 20 ms + m_skip_threshold = ((std::max<double>(callback_interval, 10.0) + (m_audio_latency - 1) * 20.0) / 1000.0) * m_sample_rate * 2 + 0.5f; + + osd_printf_verbose("PortAudio: Using device \"%s\" on API \"%s\"\n", device_info->name, api_info->name); + osd_printf_verbose("PortAudio: Sample rate is %0.0f Hz, device output latency is %0.2f ms\n", + stream_info->sampleRate, stream_info->outputLatency * 1000.0); + osd_printf_verbose("PortAudio: Allowed additional buffering latency is %0.2f ms/%d frames\n", + (m_skip_threshold / 2.0) / (m_sample_rate / 1000.0), m_skip_threshold / 2); + + err = Pa_StartStream(m_pa_stream); + + if (err != paNoError) goto pa_error; + + return 0; + +pa_error: + osd_printf_verbose("PortAudio error: %s\n", Pa_GetErrorText(err)); + Pa_Terminate(); +error: + m_sample_rate = 0; + return -1; +} + +PaDeviceIndex sound_pa::list_get_devidx(const char* api_str, const char* device_str) +{ + PaDeviceIndex selected_devidx = -1; + + for (PaHostApiIndex api_idx = 0; api_idx < Pa_GetHostApiCount(); api_idx++) + { + const PaHostApiInfo *api_info = Pa_GetHostApiInfo(api_idx); + + osd_printf_verbose("PortAudio: API %s has %d devices\n", api_info->name, api_info->deviceCount); + + for (int api_devidx = 0; api_devidx < api_info->deviceCount; api_devidx++) + { + PaDeviceIndex devidx = Pa_HostApiDeviceIndexToDeviceIndex(api_idx, api_devidx); + const PaDeviceInfo *device_info = Pa_GetDeviceInfo(devidx); + + // specified API and device is found + if (!strcmp(api_str, api_info->name) && !strcmp(device_str, device_info->name)) + selected_devidx = devidx; + + // if specified device cannot be found, use the default device of the specified API + if (!strcmp(api_str, api_info->name) && api_devidx == api_info->deviceCount - 1 && selected_devidx == -1) + selected_devidx = api_info->defaultOutputDevice; + + osd_printf_verbose("PortAudio: %s: \"%s\"%s\n", + api_info->name, device_info->name, api_info->defaultOutputDevice == devidx ? " (default)" : ""); + } + } + + if (selected_devidx < 0) + { + osd_printf_verbose("PortAudio: Unable to find specified API or device or none set, reverting to default\n"); + return Pa_GetDefaultOutputDevice(); + } + + return selected_devidx; +} + +int sound_pa::callback(s16* output_buffer, size_t number_of_samples) +{ + int buf_ct = m_ab->count(); + + if (buf_ct >= number_of_samples) + { + m_ab->read(output_buffer, number_of_samples); + + // keep track of the minimum buffer count, skip samples adaptively to respect the audio_latency setting + buf_ct -= number_of_samples; + + if (buf_ct < m_buffer_min_ct) + m_buffer_min_ct = buf_ct; + + // if we are below the threshold, reset the counter + if (buf_ct < m_skip_threshold) + m_skip_threshold_ticks = m_osd_ticks; + + // if we have been above the set threshold for ~1 second, skip forward + if (m_osd_ticks - m_skip_threshold_ticks > m_osd_tps) + { + int adjust = m_buffer_min_ct - m_skip_threshold / 2; + + // if adjustment is less than two milliseconds, don't bother + if (adjust / 2 > sample_rate() / 500) { + m_ab->increment_rdpos(adjust); + m_has_overflowed = true; + } + + m_skip_threshold_ticks = m_osd_ticks; + m_buffer_min_ct = INT_MAX; + } + } + else + { + m_ab->read(output_buffer, buf_ct); + std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16)); + + // rd_pos == wr_pos only happens when buffer hasn't received any samples, + // i.e. before update_audio_stream has been called + if (m_ab->rd_pos != m_ab->wr_pos) + m_has_underflowed = true; + + m_skip_threshold_ticks = m_osd_ticks; + } + + return paContinue; +} + +void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) +{ + if (!sample_rate()) + return; + + // for determining buffer overflows, take the sample here instead of in the callback + m_osd_ticks = osd_ticks(); + +#if LOG_BUFCNT + if (m_log.good()) + m_log << m_ab->count() << std::endl; +#endif + + if (m_has_underflowed) + { + m_underflows++; + // add some silence to prevent immediate underflows + m_ab->clear(m_skip_threshold / 4); + m_has_underflowed = false; + } + + if (m_has_overflowed) + { + m_overflows++; + m_has_overflowed = false; + } + + m_ab->write(buffer, samples_this_frame * 2, m_attenuation); +} + +void sound_pa::set_mastervolume(int attenuation) +{ + m_attenuation = attenuation; +} + +void sound_pa::exit() +{ + if (!sample_rate()) + return; + +#if LOG_BUFCNT + if (m_log.good()) + { + std::ofstream m_logfile(LOG_FILE); + + if (m_logfile.is_open()) { + m_logfile << m_log.str(); + m_logfile.close(); + } else { + osd_printf_verbose("PortAudio: Could not write log.\n"); + } + } +#endif + + Pa_StopStream(m_pa_stream); + err = Pa_Terminate(); + + if (err != paNoError) + osd_printf_verbose("PortAudio error: %s\n", Pa_GetErrorText(err)); + + delete m_ab; + + if (m_overflows || m_underflows) + osd_printf_verbose("Sound: overflows=%d underflows=%d\n", m_overflows, m_underflows); +} + +#else + MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") +#endif + +MODULE_DEFINITION(SOUND_PORTAUDIO, sound_pa) |