summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--makefile5
-rw-r--r--scripts/src/3rdparty.lua304
-rw-r--r--scripts/src/main.lua10
-rw-r--r--scripts/src/osd/modules.lua25
-rw-r--r--src/osd/modules/lib/osdobj_common.cpp10
-rw-r--r--src/osd/modules/lib/osdobj_common.h9
-rw-r--r--src/osd/modules/sound/pa_sound.cpp438
7 files changed, 652 insertions, 149 deletions
diff --git a/makefile b/makefile
index 1c3c47b793b..8df072c7b42 100644
--- a/makefile
+++ b/makefile
@@ -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)