summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/sound
diff options
context:
space:
mode:
Diffstat (limited to 'src/osd/modules/sound')
-rw-r--r--src/osd/modules/sound/coreaudio_sound.cpp244
-rw-r--r--src/osd/modules/sound/direct_sound.cpp459
-rw-r--r--src/osd/modules/sound/js_sound.cpp32
-rw-r--r--src/osd/modules/sound/js_sound.js38
-rw-r--r--src/osd/modules/sound/none.cpp23
-rw-r--r--src/osd/modules/sound/pa_sound.cpp89
-rw-r--r--src/osd/modules/sound/pipewire_sound.cpp823
-rw-r--r--src/osd/modules/sound/pulse_sound.cpp533
-rw-r--r--src/osd/modules/sound/sdl_sound.cpp531
-rw-r--r--src/osd/modules/sound/sound_module.cpp61
-rw-r--r--src/osd/modules/sound/sound_module.h73
-rw-r--r--src/osd/modules/sound/xaudio2_sound.cpp180
12 files changed, 2125 insertions, 961 deletions
diff --git a/src/osd/modules/sound/coreaudio_sound.cpp b/src/osd/modules/sound/coreaudio_sound.cpp
index d43d47a114f..7e9814af117 100644
--- a/src/osd/modules/sound/coreaudio_sound.cpp
+++ b/src/osd/modules/sound/coreaudio_sound.cpp
@@ -8,10 +8,11 @@
#include "sound_module.h"
#include "modules/osdmodule.h"
-#include "modules/lib/osdobj_common.h"
#ifdef SDLMAME_MACOSX
+#include "modules/lib/osdobj_common.h"
+
#include <AvailabilityMacros.h>
#include <AudioToolbox/AudioToolbox.h>
#include <AudioUnit/AudioUnit.h>
@@ -19,19 +20,14 @@
#include <CoreFoundation/CoreFoundation.h>
#include <CoreServices/CoreServices.h>
-#include <string.h>
-
-
-#ifdef MAC_OS_X_VERSION_MAX_ALLOWED
-
-#if MAC_OS_X_VERSION_MAX_ALLOWED < 1060
+#include <memory>
+#include <new>
+#include <cstring>
-typedef ComponentDescription AudioComponentDescription;
-#endif // MAC_OS_X_VERSION_MAX_ALLOWED < 1060
-
-#endif // MAC_OS_X_VERSION_MAX_ALLOWED
+namespace osd {
+namespace {
class sound_coreaudio : public osd_module, public sound_module
{
@@ -41,14 +37,15 @@ public:
sound_module(),
m_graph(nullptr),
m_node_count(0),
+ m_sample_rate(0),
+ m_audio_latency(0),
m_sample_bytes(0),
m_headroom(0),
m_buffer_size(0),
- m_buffer(nullptr),
+ m_buffer(),
m_playpos(0),
m_writepos(0),
m_in_underrun(false),
- m_scale(128),
m_overflows(0),
m_underflows(0)
{
@@ -57,13 +54,12 @@ public:
{
}
- virtual int init(osd_options const &options) override;
+ virtual int init(osd_interface &osd, osd_options const &options) override;
virtual void exit() override;
// sound_module
- virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override;
- virtual void set_mastervolume(int attenuation) override;
+ virtual void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override;
private:
struct node_detail
@@ -81,18 +77,10 @@ private:
EFFECT_COUNT_MAX = 10
};
- uint32_t clamped_latency() const { return unsigned(std::max(std::min(m_audio_latency, int(LATENCY_MAX)), int(LATENCY_MIN))); }
+ uint32_t clamped_latency() const { return unsigned(std::clamp<int>(m_audio_latency, LATENCY_MIN, LATENCY_MAX)); }
uint32_t buffer_avail() const { return ((m_writepos >= m_playpos) ? m_buffer_size : 0) + m_playpos - m_writepos; }
uint32_t buffer_used() const { return ((m_playpos > m_writepos) ? m_buffer_size : 0) + m_writepos - m_playpos; }
- void copy_scaled(void *dst, void const *src, uint32_t bytes) const
- {
- bytes /= sizeof(int16_t);
- int16_t const *s = (int16_t const *)src;
- for (int16_t *d = (int16_t *)dst; bytes > 0; bytes--, s++, d++)
- *d = (*s * m_scale) >> 7;
- }
-
bool create_graph(osd_options const &options);
bool add_output(char const *name);
bool add_device_output(char const *name);
@@ -125,8 +113,8 @@ private:
}
bool get_output_device_id(char const *name, AudioDeviceID &id) const;
- char *get_device_uid(AudioDeviceID id) const;
- char *get_device_name(AudioDeviceID id) const;
+ std::unique_ptr<char []> get_device_uid(AudioDeviceID id) const;
+ std::unique_ptr<char []> get_device_name(AudioDeviceID id) const;
UInt32 get_output_stream_count(
AudioDeviceID id,
char const *uid,
@@ -141,17 +129,14 @@ private:
CFPropertyListRef &class_info) const;
CFPropertyListRef load_property_list(char const *name) const;
- char *convert_cfstring_to_utf8(CFStringRef str) const
+ std::unique_ptr<char []> convert_cfstring_to_utf8(CFStringRef str) const
{
CFIndex const len = CFStringGetMaximumSizeForEncoding(
CFStringGetLength(str),
kCFStringEncodingUTF8);
- char *const result = global_alloc_array_clear<char>(len + 1);
- if (!CFStringGetCString(str, result, len + 1, kCFStringEncodingUTF8))
- {
- global_free_array(result);
- return nullptr;
- }
+ std::unique_ptr<char []> result = std::make_unique<char []>(len + 1);
+ if (!CFStringGetCString(str, result.get(), len + 1, kCFStringEncodingUTF8))
+ result.reset();
return result;
}
@@ -174,25 +159,28 @@ private:
unsigned m_node_count;
node_detail m_node_details[EFFECT_COUNT_MAX + 2];
- uint32_t m_sample_bytes;
- uint32_t m_headroom;
- uint32_t m_buffer_size;
- int8_t *m_buffer;
- uint32_t m_playpos;
- uint32_t m_writepos;
+ int m_sample_rate;
+ int m_audio_latency;
+ uint32_t m_sample_bytes;
+ uint32_t m_headroom;
+ uint32_t m_buffer_size;
+ std::unique_ptr<int8_t []> m_buffer;
+ uint32_t m_playpos;
+ uint32_t m_writepos;
bool m_in_underrun;
- int32_t m_scale;
unsigned m_overflows;
unsigned m_underflows;
};
-int sound_coreaudio::init(const osd_options &options)
+int sound_coreaudio::init(osd_interface &osd, const osd_options &options)
{
OSStatus err;
// Don't bother with any of this if sound is disabled
- if (sample_rate() == 0)
+ m_sample_rate = options.sample_rate();
+ m_audio_latency = options.audio_latency();
+ if (m_sample_rate == 0)
return 0;
// Create the output graph
@@ -202,7 +190,7 @@ int sound_coreaudio::init(const osd_options &options)
// Set audio stream format for two-channel native-endian 16-bit packed linear PCM
AudioStreamBasicDescription format;
- format.mSampleRate = sample_rate();
+ format.mSampleRate = m_sample_rate;
format.mFormatID = kAudioFormatLinearPCM;
format.mFormatFlags = kAudioFormatFlagsNativeEndian
| kLinearPCMFormatFlagIsSignedInteger
@@ -227,10 +215,13 @@ int sound_coreaudio::init(const osd_options &options)
m_sample_bytes = format.mBytesPerFrame;
// Allocate buffer
- m_headroom = m_sample_bytes * (clamped_latency() * sample_rate() / 40);
- m_buffer_size = m_sample_bytes * std::max<uint32_t>(sample_rate() * (clamped_latency() + 3) / 40, 256U);
- m_buffer = global_alloc_array_clear<int8_t>(m_buffer_size);
- if (!m_buffer)
+ m_headroom = m_sample_bytes * (clamped_latency() * m_sample_rate / 40);
+ m_buffer_size = m_sample_bytes * std::max<uint32_t>(m_sample_rate * (clamped_latency() + 3) / 40, 256U);
+ try
+ {
+ m_buffer = std::make_unique<int8_t []>(m_buffer_size);
+ }
+ catch (std::bad_alloc const &)
{
osd_printf_error("Could not allocate stream buffer\n");
goto close_graph_and_return_error;
@@ -238,7 +229,6 @@ int sound_coreaudio::init(const osd_options &options)
m_playpos = 0;
m_writepos = m_headroom;
m_in_underrun = false;
- m_scale = 128;
m_overflows = m_underflows = 0;
// Initialise and start
@@ -246,23 +236,21 @@ int sound_coreaudio::init(const osd_options &options)
if (noErr != err)
{
osd_printf_error("Could not initialize AudioUnit graph (%ld)\n", (long)err);
- goto free_buffer_and_return_error;
+ goto close_graph_and_return_error;
}
err = AUGraphStart(m_graph);
if (noErr != err)
{
osd_printf_error("Could not start AudioUnit graph (%ld)\n", (long)err);
AUGraphUninitialize(m_graph);
- goto free_buffer_and_return_error;
+ goto close_graph_and_return_error;
}
osd_printf_verbose("Audio: End initialization\n");
return 0;
-free_buffer_and_return_error:
- global_free_array(m_buffer);
- m_buffer_size = 0;
- m_buffer = nullptr;
close_graph_and_return_error:
+ m_buffer_size = 0;
+ m_buffer.reset();
AUGraphClose(m_graph);
DisposeAUGraph(m_graph);
m_graph = nullptr;
@@ -283,20 +271,16 @@ void sound_coreaudio::exit()
m_graph = nullptr;
m_node_count = 0;
}
- if (m_buffer)
- {
- global_free_array(m_buffer);
- m_buffer = nullptr;
- }
+ m_buffer.reset();
if (m_overflows || m_underflows)
osd_printf_verbose("Sound buffer: overflows=%u underflows=%u\n", m_overflows, m_underflows);
osd_printf_verbose("Audio: End deinitialization\n");
}
-void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame)
+void sound_coreaudio::stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame)
{
- if ((sample_rate() == 0) || !m_buffer)
+ if ((m_sample_rate == 0) || !m_buffer)
return;
uint32_t const bytes_this_frame = samples_this_frame * m_sample_bytes;
@@ -307,7 +291,7 @@ void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buff
}
uint32_t const chunk = std::min(m_buffer_size - m_writepos, bytes_this_frame);
- memcpy(m_buffer + m_writepos, (int8_t *)buffer, chunk);
+ memcpy(&m_buffer[m_writepos], (int8_t *)buffer, chunk);
m_writepos += chunk;
if (m_writepos >= m_buffer_size)
m_writepos = 0;
@@ -316,19 +300,12 @@ void sound_coreaudio::update_audio_stream(bool is_throttled, int16_t const *buff
{
assert(0U == m_writepos);
assert(m_playpos > (bytes_this_frame - chunk));
- memcpy(m_buffer, (int8_t *)buffer + chunk, bytes_this_frame - chunk);
+ memcpy(&m_buffer[0], (int8_t *)buffer + chunk, bytes_this_frame - chunk);
m_writepos += bytes_this_frame - chunk;
}
}
-void sound_coreaudio::set_mastervolume(int attenuation)
-{
- int const clamped_attenuation = std::max(std::min(attenuation, 0), -32);
- m_scale = (-32 == clamped_attenuation) ? 0 : (int32_t)(pow(10.0, clamped_attenuation / 20.0) * 128);
-}
-
-
bool sound_coreaudio::create_graph(osd_options const &options)
{
OSStatus err;
@@ -648,7 +625,7 @@ bool sound_coreaudio::get_output_device_id(
return false;
}
property_size /= sizeof(AudioDeviceID);
- AudioDeviceID *const devices = global_alloc_array_clear<AudioDeviceID>(property_size);
+ std::unique_ptr<AudioDeviceID []> const devices = std::make_unique<AudioDeviceID []>(property_size);
property_size *= sizeof(AudioDeviceID);
err = AudioObjectGetPropertyData(
kAudioObjectSystemObject,
@@ -656,20 +633,19 @@ bool sound_coreaudio::get_output_device_id(
0,
nullptr,
&property_size,
- devices);
+ devices.get());
UInt32 const device_count = property_size / sizeof(AudioDeviceID);
if (noErr != err)
{
osd_printf_error("Error getting audio device list (%ld)\n", (long)err);
- global_free_array(devices);
return false;
}
for (UInt32 i = 0; device_count > i; i++)
{
- char *const device_uid = get_device_uid(devices[i]);
- char *const device_name = get_device_name(devices[i]);
- if ((nullptr == device_uid) && (nullptr == device_name))
+ std::unique_ptr<char []> const device_uid = get_device_uid(devices[i]);
+ std::unique_ptr<char []> const device_name = get_device_name(devices[i]);
+ if (!device_uid && !device_name)
{
osd_printf_warning(
"Could not get UID or name for device %lu - skipping\n",
@@ -679,52 +655,46 @@ bool sound_coreaudio::get_output_device_id(
UInt32 const streams = get_output_stream_count(
devices[i],
- device_uid,
- device_name);
+ device_uid.get(),
+ device_name.get());
if (1U > streams)
{
osd_printf_verbose(
"No output streams found for device %s (%s) - skipping\n",
- (nullptr != device_name) ? device_name : "<anonymous>",
- (nullptr != device_uid) ? device_uid : "<unknown>");
- if (nullptr != device_uid) global_free_array(device_uid);
- if (nullptr != device_name) global_free_array(device_name);
+ device_name ? device_name.get() : "<anonymous>",
+ device_uid ? device_uid.get() : "<unknown>");
continue;
}
- for (std::size_t j = strlen(device_uid); (0 < j) && (' ' == device_uid[j - 1]); j--)
+ for (std::size_t j = strlen(device_uid.get()); (0 < j) && (' ' == device_uid[j - 1]); j--)
device_uid[j - 1] = '\0';
- for (std::size_t j = strlen(device_name); (0 < j) && (' ' == device_name[j - 1]); j--)
+ for (std::size_t j = strlen(device_name.get()); (0 < j) && (' ' == device_name[j - 1]); j--)
device_name[j - 1] = '\0';
- bool const matched_uid = (nullptr != device_uid) && !strcmp(name, device_uid);
- bool const matched_name = (nullptr != device_name) && !strcmp(name, device_name);
+ bool const matched_uid = device_uid && !strcmp(name, device_uid.get());
+ bool const matched_name = device_name && !strcmp(name, device_name.get());
if (matched_uid || matched_name)
{
osd_printf_verbose(
"Matched device %s (%s) with %lu output stream(s)\n",
- (nullptr != device_name) ? device_name : "<anonymous>",
- (nullptr != device_uid) ? device_uid : "<unknown>",
+ device_name ? device_name.get() : "<anonymous>",
+ device_uid ? device_uid.get() : "<unknown>",
(unsigned long)streams);
}
- global_free_array(device_uid);
- global_free_array(device_name);
if (matched_uid || matched_name)
{
id = devices[i];
- global_free_array(devices);
return true;
}
}
osd_printf_verbose("No audio output devices match %s\n", name);
- global_free_array(devices);
return false;
}
-char *sound_coreaudio::get_device_uid(AudioDeviceID id) const
+std::unique_ptr<char []> sound_coreaudio::get_device_uid(AudioDeviceID id) const
{
AudioObjectPropertyAddress const uid_addr = {
kAudioDevicePropertyDeviceUID,
@@ -747,9 +717,9 @@ char *sound_coreaudio::get_device_uid(AudioDeviceID id) const
(long)err);
return nullptr;
}
- char *const result = convert_cfstring_to_utf8(device_uid);
+ std::unique_ptr<char []> result = convert_cfstring_to_utf8(device_uid);
CFRelease(device_uid);
- if (nullptr == result)
+ if (!result)
{
osd_printf_warning(
"Error converting UID for audio device %lu to UTF-8\n",
@@ -759,7 +729,7 @@ char *sound_coreaudio::get_device_uid(AudioDeviceID id) const
}
-char *sound_coreaudio::get_device_name(AudioDeviceID id) const
+std::unique_ptr<char []> sound_coreaudio::get_device_name(AudioDeviceID id) const
{
AudioObjectPropertyAddress const name_addr = {
kAudioDevicePropertyDeviceNameCFString,
@@ -782,9 +752,9 @@ char *sound_coreaudio::get_device_name(AudioDeviceID id) const
(long)err);
return nullptr;
}
- char *const result = convert_cfstring_to_utf8(device_name);
+ std::unique_ptr<char []> result = convert_cfstring_to_utf8(device_name);
CFRelease(device_name);
- if (nullptr == result)
+ if (!result)
{
osd_printf_warning(
"Error converting name for audio device %lu to UTF-8\n",
@@ -913,51 +883,48 @@ CFPropertyListRef sound_coreaudio::load_property_list(char const *name) const
(UInt8 const *)name,
strlen(name),
false);
- if (nullptr == url)
- {
+ if (!url)
return nullptr;
- }
- CFDataRef data = nullptr;
- SInt32 err;
- Boolean const status = CFURLCreateDataAndPropertiesFromResource(
- nullptr,
- url,
- &data,
- nullptr,
- nullptr,
- &err);
+ CFReadStreamRef const stream = CFReadStreamCreateWithFile(nullptr, url);
CFRelease(url);
- if (!status)
+ if (!stream)
{
- osd_printf_error(
- "Error reading data from %s (%ld)\n",
- name,
- (long)err);
- if (nullptr != data) CFRelease(data);
+ osd_printf_error("Error opening file %s\n", name);
+ return nullptr;
+ }
+ if (!CFReadStreamOpen(stream))
+ {
+ CFRelease(stream);
+ osd_printf_error("Error opening file %s\n", name);
return nullptr;
}
- CFStringRef msg = nullptr;
- CFPropertyListRef const result = CFPropertyListCreateFromXMLData(
+ CFErrorRef msg = nullptr;
+ CFPropertyListRef const result = CFPropertyListCreateWithStream(
nullptr,
- data,
+ stream,
+ 0,
kCFPropertyListImmutable,
+ nullptr,
&msg);
- CFRelease(data);
- if ((nullptr == result) || (nullptr != msg))
- {
- char *buf = (nullptr != msg) ? convert_cfstring_to_utf8(msg) : nullptr;
- if (nullptr != msg)
+ CFReadStreamClose(stream);
+ CFRelease(stream);
+ if (!result || msg)
+ {
+ CFStringRef const desc = msg ? CFErrorCopyDescription(msg) : nullptr;
+ std::unique_ptr<char []> const buf = desc ? convert_cfstring_to_utf8(desc) : nullptr;
+ if (desc)
+ CFRelease(desc);
+ if (msg)
CFRelease(msg);
- if (nullptr != buf)
+ if (buf)
{
osd_printf_error(
"Error creating property list from %s: %s\n",
name,
- buf);
- global_free_array(buf);
+ buf.get());
}
else
{
@@ -965,7 +932,8 @@ CFPropertyListRef sound_coreaudio::load_property_list(char const *name) const
"Error creating property list from %s\n",
name);
}
- if (nullptr != result) CFRelease(result);
+ if (result)
+ CFRelease(result);
return nullptr;
}
@@ -997,7 +965,7 @@ OSStatus sound_coreaudio::render(
}
uint32_t const chunk = std::min(m_buffer_size - m_playpos, number_bytes);
- copy_scaled((int8_t *)data->mBuffers[0].mData, m_buffer + m_playpos, chunk);
+ memcpy((int8_t *)data->mBuffers[0].mData, &m_buffer[m_playpos], chunk);
m_playpos += chunk;
if (m_playpos >= m_buffer_size)
m_playpos = 0;
@@ -1006,7 +974,7 @@ OSStatus sound_coreaudio::render(
{
assert(0U == m_playpos);
assert(m_writepos >= (number_bytes - chunk));
- copy_scaled((int8_t *)data->mBuffers[0].mData + chunk, m_buffer, number_bytes - chunk);
+ memcpy((int8_t *)data->mBuffers[0].mData + chunk, &m_buffer[0], number_bytes - chunk);
m_playpos += number_bytes - chunk;
}
@@ -1025,8 +993,14 @@ OSStatus sound_coreaudio::render_callback(
return ((sound_coreaudio *)refcon)->render(action_flags, timestamp, bus_number, number_frames, data);
}
-#else /* SDLMAME_MACOSX */
- MODULE_NOT_SUPPORTED(sound_coreaudio, OSD_SOUND_PROVIDER, "coreaudio")
-#endif
+} // anonymous namespace
+
+} // namespace osd
+
+#else // SDLMAME_MACOSX
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_coreaudio, OSD_SOUND_PROVIDER, "coreaudio") } }
+
+#endif // SDLMAME_MACOSX
-MODULE_DEFINITION(SOUND_COREAUDIO, sound_coreaudio)
+MODULE_DEFINITION(SOUND_COREAUDIO, osd::sound_coreaudio)
diff --git a/src/osd/modules/sound/direct_sound.cpp b/src/osd/modules/sound/direct_sound.cpp
index 4d6c8c26c50..7417def934c 100644
--- a/src/osd/modules/sound/direct_sound.cpp
+++ b/src/osd/modules/sound/direct_sound.cpp
@@ -11,236 +11,235 @@
#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32)
-// standard windows headers
-#include <windows.h>
-#include <mmsystem.h>
-
-// undef WINNT for dsound.h to prevent duplicate definition
-#undef WINNT
-#include <dsound.h>
-#undef interface
-
// MAME headers
-#include "emu.h"
-#include "osdepend.h"
#include "emuopts.h"
+// osd headers
+#include "modules/lib/osdobj_common.h"
+#include "osdepend.h"
+#include "osdcore.h"
+
#ifdef SDLMAME_WIN32
-#include "../../sdl/osdsdl.h"
+#include "sdl/window.h"
#include <SDL2/SDL_syswm.h>
-#include "../../sdl/window.h"
#else
#include "winmain.h"
#include "window.h"
#endif
+
#include <utility>
+// standard windows headers
+
+#include <windows.h>
+
+#include <mmreg.h>
+#include <mmsystem.h>
+
+#include <dsound.h>
+
+#include <wrl/client.h>
+
+
//============================================================
// DEBUGGING
//============================================================
#define LOG_SOUND 0
-#define LOG(x) do { if (LOG_SOUND) osd_printf_verbose x; } while(0)
+#define LOG(...) do { if (LOG_SOUND) osd_printf_verbose(__VA_ARGS__); } while(0)
-class sound_direct_sound : public osd_module, public sound_module
+namespace osd {
+
+namespace {
+
+class buffer_base
{
public:
+ explicit operator bool() const { return bool(m_buffer); }
- sound_direct_sound() :
- osd_module(OSD_SOUND_PROVIDER, "dsound"),
- sound_module(),
- m_dsound(nullptr),
- m_bytes_per_sample(0),
- m_primary_buffer(),
- m_stream_buffer(),
- m_stream_buffer_in(0),
- m_buffer_underflows(0),
- m_buffer_overflows(0)
- {
- }
- virtual ~sound_direct_sound() { }
+ unsigned long release() { return m_buffer.Reset(); }
- virtual int init(osd_options const &options) override;
- virtual void exit() override;
+protected:
+ Microsoft::WRL::ComPtr<IDirectSoundBuffer> m_buffer;
+};
- // sound_module
- virtual void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override;
- virtual void set_mastervolume(int attenuation) override;
-private:
- class buffer
+class primary_buffer : public buffer_base
+{
+public:
+ HRESULT create(LPDIRECTSOUND dsound)
{
- public:
- buffer() : m_buffer(nullptr) { }
- ~buffer() { release(); }
+ assert(!m_buffer);
+ DSBUFFERDESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.dwSize = sizeof(desc);
+ desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2;
+ desc.lpwfxFormat = nullptr;
+ return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr);
+ }
- ULONG release()
- {
- ULONG const result = m_buffer ? m_buffer->Release() : 0;
- m_buffer = nullptr;
- return result;
- }
+ HRESULT get_format(WAVEFORMATEX &format) const
+ {
+ assert(m_buffer);
+ return m_buffer->GetFormat(&format, sizeof(format), nullptr);
+ }
- operator bool() const { return m_buffer; }
+ HRESULT set_format(WAVEFORMATEX const &format) const
+ {
+ assert(m_buffer);
+ return m_buffer->SetFormat(&format);
+ }
+};
- protected:
- LPDIRECTSOUNDBUFFER m_buffer;
- };
- class primary_buffer : public buffer
+class stream_buffer : public buffer_base
+{
+public:
+ HRESULT create(LPDIRECTSOUND dsound, DWORD size, WAVEFORMATEX &format)
{
- public:
- HRESULT create(LPDIRECTSOUND dsound)
- {
- assert(!m_buffer);
- DSBUFFERDESC desc;
- memset(&desc, 0, sizeof(desc));
- desc.dwSize = sizeof(desc);
- desc.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_GETCURRENTPOSITION2;
- desc.lpwfxFormat = nullptr;
- return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr);
- }
+ assert(!m_buffer);
+ DSBUFFERDESC desc;
+ memset(&desc, 0, sizeof(desc));
+ desc.dwSize = sizeof(desc);
+ desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ desc.dwBufferBytes = size;
+ desc.lpwfxFormat = &format;
+ m_size = size;
+ return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr);
+ }
- HRESULT get_format(WAVEFORMATEX &format) const
- {
- assert(m_buffer);
- return m_buffer->GetFormat(&format, sizeof(format), nullptr);
- }
- HRESULT set_format(WAVEFORMATEX const &format) const
+ HRESULT play_looping() const
+ {
+ assert(m_buffer);
+ return m_buffer->Play(0, 0, DSBPLAY_LOOPING);
+ }
+ HRESULT stop() const
+ {
+ assert(m_buffer);
+ return m_buffer->Stop();
+ }
+ HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos) const
+ {
+ assert(m_buffer);
+ return m_buffer->GetCurrentPosition(&play_pos, &write_pos);
+ }
+ HRESULT copy_data(DWORD cursor, DWORD bytes, void const *data)
+ {
+ HRESULT result = lock(cursor, bytes);
+ if (DS_OK != result)
+ return result;
+
+ assert(m_bytes1);
+ assert((m_locked1 + m_locked2) >= bytes);
+ memcpy(m_bytes1, data, std::min(m_locked1, bytes));
+ if (m_locked1 < bytes)
{
- assert(m_buffer);
- return m_buffer->SetFormat(&format);
+ assert(m_bytes2);
+ memcpy(m_bytes2, (uint8_t const *)data + m_locked1, bytes - m_locked1);
}
- };
- class stream_buffer : public buffer
+ unlock();
+ return DS_OK;
+ }
+ HRESULT clear()
{
- public:
- stream_buffer() : m_size(0), m_bytes1(nullptr), m_bytes2(nullptr), m_locked1(0), m_locked2(0) { }
+ HRESULT result = lock_all();
+ if (DS_OK != result)
+ return result;
- HRESULT create(LPDIRECTSOUND dsound, DWORD size, WAVEFORMATEX &format)
- {
- assert(!m_buffer);
- DSBUFFERDESC desc;
- memset(&desc, 0, sizeof(desc));
- desc.dwSize = sizeof(desc);
- desc.dwFlags = DSBCAPS_CTRLVOLUME | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
- desc.dwBufferBytes = size;
- desc.lpwfxFormat = &format;
- m_size = size;
- return dsound->CreateSoundBuffer(&desc, &m_buffer, nullptr);
- }
+ assert(m_bytes1);
+ assert(!m_bytes2);
+ assert(m_size == m_locked1);
+ assert(0U == m_locked2);
+ memset(m_bytes1, 0, m_locked1);
- HRESULT play_looping() const
- {
- assert(m_buffer);
- return m_buffer->Play(0, 0, DSBPLAY_LOOPING);
- }
- HRESULT stop() const
- {
- assert(m_buffer);
- return m_buffer->Stop();
- }
- HRESULT set_volume(LONG volume) const
- {
- assert(m_buffer);
- return m_buffer->SetVolume(volume);
- }
- HRESULT set_min_volume() { return set_volume(DSBVOLUME_MIN); }
+ unlock();
+ return DS_OK;
+ }
- HRESULT get_current_positions(DWORD &play_pos, DWORD &write_pos) const
- {
- assert(m_buffer);
- return m_buffer->GetCurrentPosition(&play_pos, &write_pos);
- }
- HRESULT copy_data(DWORD cursor, DWORD bytes, void const *data)
- {
- HRESULT result = lock(cursor, bytes);
- if (DS_OK != result)
- return result;
-
- assert(m_bytes1);
- assert((m_locked1 + m_locked2) >= bytes);
- memcpy(m_bytes1, data, std::min(m_locked1, bytes));
- if (m_locked1 < bytes)
- {
- assert(m_bytes2);
- memcpy(m_bytes2, (uint8_t const *)data + m_locked1, bytes - m_locked1);
- }
-
- unlock();
- return DS_OK;
- }
- HRESULT clear()
- {
- HRESULT result = lock_all();
- if (DS_OK != result)
- return result;
-
- assert(m_bytes1);
- assert(!m_bytes2);
- assert(m_size == m_locked1);
- assert(0U == m_locked2);
- memset(m_bytes1, 0, m_locked1);
-
- unlock();
- return DS_OK;
- }
+ DWORD size() const { return m_size; }
- DWORD size() const { return m_size; }
+private:
+ HRESULT lock(DWORD cursor, DWORD bytes)
+ {
+ assert(cursor < m_size);
+ assert(bytes <= m_size);
+ assert(m_buffer);
+ assert(!m_bytes1);
+ return m_buffer->Lock(
+ cursor, bytes,
+ &m_bytes1,
+ &m_locked1,
+ &m_bytes2,
+ &m_locked2,
+ 0);
+ }
+ HRESULT lock_all() { return lock(0, m_size); }
+ HRESULT unlock()
+ {
+ assert(m_buffer);
+ assert(m_bytes1);
+ HRESULT const result = m_buffer->Unlock(
+ m_bytes1,
+ m_locked1,
+ m_bytes2,
+ m_locked2);
+ m_bytes1 = m_bytes2 = nullptr;
+ m_locked1 = m_locked2 = 0;
+ return result;
+ }
- protected:
- HRESULT lock(DWORD cursor, DWORD bytes)
- {
- assert(cursor < m_size);
- assert(bytes <= m_size);
- assert(m_buffer);
- assert(!m_bytes1);
- return m_buffer->Lock(
- cursor, bytes,
- &m_bytes1,
- &m_locked1,
- &m_bytes2,
- &m_locked2,
- 0);
- }
- HRESULT lock_all() { return lock(0, m_size); }
- HRESULT unlock()
- {
- assert(m_buffer);
- assert(m_bytes1);
- HRESULT const result = m_buffer->Unlock(
- m_bytes1,
- m_locked1,
- m_bytes2,
- m_locked2);
- m_bytes1 = m_bytes2 = nullptr;
- m_locked1 = m_locked2 = 0;
- return result;
- }
+ DWORD m_size = 0;
+ void *m_bytes1 = nullptr, *m_bytes2 = nullptr;
+ DWORD m_locked1 = 0, m_locked2 = 0;
+};
- DWORD m_size;
- void *m_bytes1, *m_bytes2;
- DWORD m_locked1, m_locked2;
- };
+class sound_direct_sound : public osd_module, public sound_module
+{
+public:
+ sound_direct_sound() :
+ osd_module(OSD_SOUND_PROVIDER, "dsound"),
+ sound_module(),
+ m_sample_rate(0),
+ m_audio_latency(0),
+ m_bytes_per_sample(0),
+ m_primary_buffer(),
+ m_stream_buffer(),
+ m_stream_buffer_in(0),
+ m_buffer_underflows(0),
+ m_buffer_overflows(0)
+ {
+ }
+
+ virtual int init(osd_interface &osd, osd_options const &options) override;
+ virtual void exit() override;
+
+ // sound_module
+ virtual void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override;
+
+private:
HRESULT dsound_init();
void dsound_kill();
HRESULT create_buffers(DWORD size, WAVEFORMATEX &format);
void destroy_buffers();
// DirectSound objects
- LPDIRECTSOUND m_dsound;
+ Microsoft::WRL::ComPtr<IDirectSound> m_dsound;
+
+ // configuration
+ int m_sample_rate;
+ int m_audio_latency;
// descriptors and formats
- uint32_t m_bytes_per_sample;
+ uint32_t m_bytes_per_sample;
// sound buffers
primary_buffer m_primary_buffer;
stream_buffer m_stream_buffer;
- uint32_t m_stream_buffer_in;
+ uint32_t m_stream_buffer_in;
// buffer over/underflow counts
unsigned m_buffer_underflows;
@@ -252,12 +251,16 @@ private:
// init
//============================================================
-int sound_direct_sound::init(osd_options const &options)
+int sound_direct_sound::init(osd_interface &osd, osd_options const &options)
{
- // attempt to initialize directsound
- // don't make it fatal if we can't -- we'll just run without sound
- dsound_init();
+ m_sample_rate = options.sample_rate();
+ m_audio_latency = options.audio_latency();
m_buffer_underflows = m_buffer_overflows = 0;
+
+ // attempt to initialize DirectSound
+ if (dsound_init() != DS_OK)
+ return -1;
+
return 0;
}
@@ -281,16 +284,16 @@ void sound_direct_sound::exit()
m_buffer_underflows);
}
- LOG(("Sound buffer: overflows=%u underflows=%u\n", m_buffer_overflows, m_buffer_underflows));
+ LOG("Sound buffer: overflows=%u underflows=%u\n", m_buffer_overflows, m_buffer_underflows);
}
//============================================================
-// update_audio_stream
+// stream_sink_update
//============================================================
-void sound_direct_sound::update_audio_stream(
- bool is_throttled,
+void sound_direct_sound::stream_sink_update(
+ uint32_t,
int16_t const *buffer,
int samples_this_frame)
{
@@ -307,7 +310,7 @@ void sound_direct_sound::update_audio_stream(
if (DS_OK != result)
return;
-//DWORD orig_write = write_position;
+ //DWORD orig_write = write_position;
// normalize the write position so it is always after the play position
if (write_position < play_position)
write_position += m_stream_buffer.size();
@@ -323,7 +326,7 @@ void sound_direct_sound::update_audio_stream(
// if we're between play and write positions, then bump forward, but only in full chunks
while (stream_in < write_position)
{
-//printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
+ //printf("Underflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
m_buffer_underflows++;
stream_in += bytes_this_frame;
}
@@ -331,7 +334,7 @@ void sound_direct_sound::update_audio_stream(
// if we're going to overlap the play position, just skip this chunk
if ((stream_in + bytes_this_frame) > (play_position + m_stream_buffer.size()))
{
-//printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
+ //printf("Overflow: PP=%d WP=%d(%d) SI=%d(%d) BTF=%d\n", (int)play_position, (int)write_position, (int)orig_write, (int)stream_in, (int)m_stream_buffer_in, (int)bytes_this_frame);
m_buffer_overflows++;
return;
}
@@ -353,26 +356,6 @@ void sound_direct_sound::update_audio_stream(
//============================================================
-// set_mastervolume
-//============================================================
-
-void sound_direct_sound::set_mastervolume(int attenuation)
-{
- // clamp the attenuation to 0-32 range
- attenuation = std::max(std::min(attenuation, 0), -32);
-
- // set the master volume
- if (m_stream_buffer)
- {
- if (-32 == attenuation)
- m_stream_buffer.set_min_volume();
- else
- m_stream_buffer.set_volume(100 * attenuation);
- }
-}
-
-
-//============================================================
// dsound_init
//============================================================
@@ -385,7 +368,7 @@ HRESULT sound_direct_sound::dsound_init()
result = DirectSoundCreate(nullptr, &m_dsound, nullptr);
if (result != DS_OK)
{
- osd_printf_error("Error creating DirectSound: %08x\n", (unsigned)result);
+ osd_printf_error("Error creating DirectSound: %08x\n", result);
goto error;
}
@@ -395,7 +378,7 @@ HRESULT sound_direct_sound::dsound_init()
result = m_dsound->GetCaps(&dsound_caps);
if (result != DS_OK)
{
- osd_printf_error("Error getting DirectSound capabilities: %08x\n", (unsigned)result);
+ osd_printf_error("Error getting DirectSound capabilities: %08x\n", result);
goto error;
}
@@ -404,34 +387,40 @@ HRESULT sound_direct_sound::dsound_init()
#ifdef SDLMAME_WIN32
SDL_SysWMinfo wminfo;
SDL_VERSION(&wminfo.version);
- SDL_GetWindowWMInfo(std::dynamic_pointer_cast<sdl_window_info>(osd_common_t::s_window_list.front())->platform_window(), &wminfo);
+ if (!SDL_GetWindowWMInfo(dynamic_cast<sdl_window_info &>(*osd_common_t::window_list().front()).platform_window(), &wminfo))
+ {
+ result = DSERR_UNSUPPORTED; // just so it has something to return
+ goto error;
+ }
HWND const window = wminfo.info.win.window;
#else // SDLMAME_WIN32
- HWND const window = std::static_pointer_cast<win_window_info>(osd_common_t::s_window_list.front())->platform_window();
+ HWND const window = dynamic_cast<win_window_info &>(*osd_common_t::window_list().front()).platform_window();
#endif // SDLMAME_WIN32
result = m_dsound->SetCooperativeLevel(window, DSSCL_PRIORITY);
}
if (result != DS_OK)
{
- osd_printf_error("Error setting DirectSound cooperative level: %08x\n", (unsigned)result);
+ osd_printf_error("Error setting DirectSound cooperative level: %08x\n", result);
goto error;
}
{
// make a format description for what we want
WAVEFORMATEX stream_format;
- stream_format.wBitsPerSample = 16;
stream_format.wFormatTag = WAVE_FORMAT_PCM;
stream_format.nChannels = 2;
- stream_format.nSamplesPerSec = sample_rate();
+ stream_format.nSamplesPerSec = m_sample_rate;
+ stream_format.wBitsPerSample = 16;
stream_format.nBlockAlign = stream_format.wBitsPerSample * stream_format.nChannels / 8;
stream_format.nAvgBytesPerSec = stream_format.nSamplesPerSec * stream_format.nBlockAlign;
+ stream_format.cbSize = 0;
// compute the buffer size based on the output sample rate
- DWORD stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * m_audio_latency / 10;
+ int audio_latency = std::max(m_audio_latency, 1);
+ DWORD stream_buffer_size = stream_format.nSamplesPerSec * stream_format.nBlockAlign * audio_latency / 10;
stream_buffer_size = std::max(DWORD(1024), (stream_buffer_size / 1024) * 1024);
- LOG(("stream_buffer_size = %u\n", (unsigned)stream_buffer_size));
+ LOG("stream_buffer_size = %u\n", stream_buffer_size);
// create the buffers
m_bytes_per_sample = stream_format.nBlockAlign;
@@ -445,7 +434,7 @@ HRESULT sound_direct_sound::dsound_init()
result = m_stream_buffer.play_looping();
if (result != DS_OK)
{
- osd_printf_error("Error playing: %08x\n", (uint32_t)result);
+ osd_printf_error("Error playing: %08x\n", result);
goto error;
}
return DS_OK;
@@ -465,9 +454,7 @@ error:
void sound_direct_sound::dsound_kill()
{
// release the object
- if (m_dsound)
- m_dsound->Release();
- m_dsound = nullptr;
+ m_dsound.Reset();
}
@@ -483,10 +470,10 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format)
HRESULT result;
// create the primary buffer
- result = m_primary_buffer.create(m_dsound);
+ result = m_primary_buffer.create(m_dsound.Get());
if (result != DS_OK)
{
- osd_printf_error("Error creating primary DirectSound buffer: %08x\n", (unsigned)result);
+ osd_printf_error("Error creating primary DirectSound buffer: %08x\n", result);
goto error;
}
@@ -494,7 +481,7 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format)
result = m_primary_buffer.set_format(format);
if (result != DS_OK)
{
- osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", (unsigned)result);
+ osd_printf_error("Error setting primary DirectSound buffer format: %08x\n", result);
goto error;
}
@@ -503,20 +490,20 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format)
result = m_primary_buffer.get_format(primary_format);
if (result != DS_OK)
{
- osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", (unsigned)result);
+ osd_printf_error("Error getting primary DirectSound buffer format: %08x\n", result);
goto error;
}
osd_printf_verbose(
"DirectSound: Primary buffer: %d Hz, %d bits, %d channels\n",
- (int)primary_format.nSamplesPerSec,
- (int)primary_format.wBitsPerSample,
- (int)primary_format.nChannels);
+ primary_format.nSamplesPerSec,
+ primary_format.wBitsPerSample,
+ primary_format.nChannels);
// create the stream buffer
- result = m_stream_buffer.create(m_dsound, size, format);
+ result = m_stream_buffer.create(m_dsound.Get(), size, format);
if (result != DS_OK)
{
- osd_printf_error("Error creating DirectSound stream buffer: %08x\n", (unsigned)result);
+ osd_printf_error("Error creating DirectSound stream buffer: %08x\n", result);
goto error;
}
@@ -524,7 +511,7 @@ HRESULT sound_direct_sound::create_buffers(DWORD size, WAVEFORMATEX &format)
result = m_stream_buffer.clear();
if (result != DS_OK)
{
- osd_printf_error("Error locking DirectSound stream buffer: %08x\n", (unsigned)result);
+ osd_printf_error("Error locking DirectSound stream buffer: %08x\n", result);
goto error;
}
@@ -541,7 +528,7 @@ error:
// destroy_buffers
//============================================================
-void sound_direct_sound::destroy_buffers(void)
+void sound_direct_sound::destroy_buffers()
{
// stop any playback
if (m_stream_buffer)
@@ -554,8 +541,16 @@ void sound_direct_sound::destroy_buffers(void)
m_primary_buffer.release();
}
+} // anonymous namespace
+
+} // namespace osd
+
+
#else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32)
- MODULE_NOT_SUPPORTED(sound_direct_sound, OSD_SOUND_PROVIDER, "dsound")
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_direct_sound, OSD_SOUND_PROVIDER, "dsound") } }
+
#endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32)
-MODULE_DEFINITION(SOUND_DSOUND, sound_direct_sound)
+
+MODULE_DEFINITION(SOUND_DSOUND, osd::sound_direct_sound)
diff --git a/src/osd/modules/sound/js_sound.cpp b/src/osd/modules/sound/js_sound.cpp
index afcf6ec8f2b..1473ef9de2a 100644
--- a/src/osd/modules/sound/js_sound.cpp
+++ b/src/osd/modules/sound/js_sound.cpp
@@ -11,7 +11,7 @@
#include "sound_module.h"
#include "modules/osdmodule.h"
-#if (defined(SDLMAME_EMSCRIPTEN))
+#if defined(SDLMAME_EMSCRIPTEN)
#include "emscripten.h"
@@ -19,36 +19,30 @@ class sound_js : public osd_module, public sound_module
{
public:
- sound_js()
- : osd_module(OSD_SOUND_PROVIDER, "js"), sound_module()
+ sound_js() : osd_module(OSD_SOUND_PROVIDER, "js"), sound_module()
{
}
virtual ~sound_js() { }
- virtual int init(const osd_options &options) { return 0; }
+ virtual int init(osd_interface &osd, const osd_options &options) { return 0; }
virtual void exit() { }
// sound_module
- virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame)
+ virtual void stream_sink_update(uint32_t, const int16_t *buffer, int samples_this_frame)
{
- EM_ASM_ARGS({
- // Forward audio stream update on to JS backend implementation.
- jsmame_update_audio_stream($0, $1);
- }, (unsigned int)buffer, samples_this_frame);
+ EM_ASM_ARGS(
+ {
+ // Forward audio stream update on to JS backend implementation.
+ jsmame_steam_update($1, $2);
+ },
+ (unsigned int)buffer,
+ samples_this_frame);
}
- virtual void set_mastervolume(int attenuation)
- {
- EM_ASM_ARGS({
- // Forward volume update on to JS backend implementation.
- jsmame_set_mastervolume($0);
- }, attenuation);
- }
-
};
-#else /* SDLMAME_UNIX */
+#else // SDLMAME_EMSCRIPTEN
MODULE_NOT_SUPPORTED(sound_js, OSD_SOUND_PROVIDER, "js")
-#endif
+#endif // SDLMAME_EMSCRIPTEN
MODULE_DEFINITION(SOUND_JS, sound_js)
diff --git a/src/osd/modules/sound/js_sound.js b/src/osd/modules/sound/js_sound.js
index 5cbcc380c1b..25f19231ae9 100644
--- a/src/osd/modules/sound/js_sound.js
+++ b/src/osd/modules/sound/js_sound.js
@@ -98,30 +98,7 @@ function disconnect_old_event() {
eventNode = null;
};
-function set_mastervolume (
- // even though it's 'attenuation' the value is negative, so...
- attenuation_in_decibels
-) {
- lazy_init();
- if (!context) return;
-
- // http://stackoverflow.com/questions/22604500/web-audio-api-working-with-decibels
- // seemingly incorrect/broken. figures. welcome to Web Audio
- // var gain_web_audio = 1.0 - Math.pow(10, 10 / attenuation_in_decibels);
-
- // HACK: Max attenuation in JSMESS appears to be 32.
- // Hit ' then left/right arrow to test.
- // FIXME: This is linear instead of log10 scale.
- var gain_web_audio = 1.0 + (+attenuation_in_decibels / +32);
- if (gain_web_audio < +0)
- gain_web_audio = +0;
- else if (gain_web_audio > +1)
- gain_web_audio = +1;
-
- gain_node.gain.value = gain_web_audio;
-};
-
-function update_audio_stream (
+function stream_sink_update (
pBuffer, // pointer into emscripten heap. int16 samples
samples_this_frame // int. number of samples at pBuffer address.
) {
@@ -173,10 +150,11 @@ function tick (event) {
start = 0;
}
}
- //Pad with silence if we're underrunning:
+ //Pad with latest if we're underrunning:
+ var idx = (index == 0 ? bufferSize : index) - 1;
while (index < 4096) {
- buffers[0][index] = 0;
- buffers[1][index++] = 0;
+ buffers[0][index] = buffers[0][idx];
+ buffers[1][index++] = buffers[1][idx];
}
//Deep inside the bowels of vendors bugs,
//we're using watchdog for a firefox bug,
@@ -206,14 +184,12 @@ function sample_count() {
}
return {
- set_mastervolume: set_mastervolume,
- update_audio_stream: update_audio_stream,
+ stream_sink_update: stream_sink_update,
get_context: get_context,
sample_count: sample_count
};
})();
-window.jsmame_set_mastervolume = jsmame_web_audio.set_mastervolume;
-window.jsmame_update_audio_stream = jsmame_web_audio.update_audio_stream;
+window.jsmame_stream_sink_update = jsmame_web_audio.stream_sink_update;
window.jsmame_sample_count = jsmame_web_audio.sample_count;
diff --git a/src/osd/modules/sound/none.cpp b/src/osd/modules/sound/none.cpp
index 685cdc884c2..bdb219636d0 100644
--- a/src/osd/modules/sound/none.cpp
+++ b/src/osd/modules/sound/none.cpp
@@ -2,32 +2,35 @@
// copyright-holders:Miodrag Milanovic
/***************************************************************************
- none.c
+ none.cpp
Dummy sound interface.
*******************************************************************c********/
#include "sound_module.h"
+
#include "modules/osdmodule.h"
+
+namespace osd {
+
+namespace {
+
class sound_none : public osd_module, public sound_module
{
public:
- sound_none()
- : osd_module(OSD_SOUND_PROVIDER, "none"), sound_module()
+ sound_none() : osd_module(OSD_SOUND_PROVIDER, "none")
{
}
virtual ~sound_none() { }
- virtual int init(const osd_options &options) override { return 0; }
+ virtual int init(osd_interface &osd, const osd_options &options) override { return 0; }
virtual void exit() override { }
+};
- // sound_module
-
- virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override { }
- virtual void set_mastervolume(int attenuation) override { }
+} // anonymous namespace
-};
+} // namespace osd
-MODULE_DEFINITION(SOUND_NONE, sound_none)
+MODULE_DEFINITION(SOUND_NONE, osd::sound_none)
diff --git a/src/osd/modules/sound/pa_sound.cpp b/src/osd/modules/sound/pa_sound.cpp
index 41645318672..68200a9c3f1 100644
--- a/src/osd/modules/sound/pa_sound.cpp
+++ b/src/osd/modules/sound/pa_sound.cpp
@@ -9,44 +9,50 @@
*******************************************************************c********/
#include "sound_module.h"
+
#include "modules/osdmodule.h"
#ifndef NO_USE_PORTAUDIO
-#include <portaudio.h>
#include "modules/lib/osdobj_common.h"
+#include "osdcore.h"
-#include <iostream>
-#include <fstream>
-#include <sstream>
+#include <portaudio.h>
+
+#include <algorithm>
#include <atomic>
-#include <cmath>
#include <climits>
-#include <algorithm>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <sstream>
-#ifdef WIN32
+#ifdef _WIN32
#include "pa_win_wasapi.h"
#endif
+
+namespace osd {
+
+namespace {
+
#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()
+ sound_pa() : osd_module(OSD_SOUND_PROVIDER, "portaudio")
{
}
virtual ~sound_pa() { }
- virtual int init(osd_options const &options) override;
+ virtual int init(osd_interface &osd, osd_options const &options) override;
virtual void exit() override;
// sound_module
- virtual void update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame) override;
- virtual void set_mastervolume(int attenuation) override;
+ virtual void stream_sink_update(uint32_t, const s16 *buffer, int samples_this_frame) override;
private:
// Lock free SPSC ring buffer
@@ -73,14 +79,14 @@ private:
writepos.store((writepos + n) % size);
}
- int write(const T* src, int n, int attenuation) {
+ int write(const T* src, int n) {
n = std::min<int>(n, size - reserve - count());
if (writepos + n > size) {
- att_memcpy(buf + writepos, src, sizeof(T) * (size - writepos), attenuation);
- att_memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos)), attenuation);
+ memcpy(buf + writepos, src, sizeof(T) * (size - writepos));
+ memcpy(buf, src + (size - writepos), sizeof(T) * (n - (size - writepos)));
} else {
- att_memcpy(buf + writepos, src, sizeof(T) * n, attenuation);
+ memcpy(buf + writepos, src, sizeof(T) * n);
}
increment_writepos(n);
@@ -121,13 +127,6 @@ private:
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;
- }
};
enum
@@ -150,7 +149,8 @@ private:
PaStream* m_pa_stream;
PaError err;
- int m_attenuation;
+ int m_sample_rate;
+ int m_audio_latency;
audio_buffer<s16>* m_ab;
@@ -170,8 +170,12 @@ private:
#endif
};
-int sound_pa::init(osd_options const &options)
+int sound_pa::init(osd_interface &osd, osd_options const &options)
{
+ m_sample_rate = options.sample_rate();
+ if (!m_sample_rate)
+ return 0;
+
PaStreamParameters stream_params;
const PaStreamInfo* stream_info;
const PaHostApiInfo* api_info;
@@ -180,10 +184,6 @@ int sound_pa::init(osd_options const &options)
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;
@@ -192,7 +192,7 @@ int sound_pa::init(osd_options const &options)
m_skip_threshold_ticks = 0;
m_osd_tps = osd_ticks_per_second();
m_buffer_min_ct = INT_MAX;
- m_audio_latency = std::min<int>(std::max<int>(m_audio_latency, LATENCY_MIN), LATENCY_MAX);
+ m_audio_latency = std::clamp<int>(options.audio_latency(), LATENCY_MIN, LATENCY_MAX);
try {
m_ab = new audio_buffer<s16>(m_sample_rate, 2);
@@ -216,7 +216,7 @@ int sound_pa::init(osd_options const &options)
// 0 = use default
stream_params.suggestedLatency = options.pa_latency() ? options.pa_latency() : device_info->defaultLowOutputLatency;
-#ifdef WIN32
+#ifdef _WIN32
PaWasapiStreamInfo wasapi_stream_info;
// if requested latency is less than 20 ms, we need to use exclusive mode
@@ -364,7 +364,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples)
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) {
+ if (adjust / 2 > m_sample_rate / 500) {
m_ab->increment_playpos(adjust);
m_has_overflowed = true;
}
@@ -379,7 +379,7 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples)
m_ab->read(output_buffer, buf_ct);
std::memset(output_buffer + buf_ct, 0, (number_of_samples - buf_ct) * sizeof(s16));
- // if update_audio_stream has been called, note the underflow
+ // if stream_sink_update has been called, note the underflow
if (m_osd_ticks)
m_has_underflowed = true;
@@ -389,9 +389,9 @@ int sound_pa::callback(s16* output_buffer, size_t number_of_samples)
return paContinue;
}
-void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int samples_this_frame)
+void sound_pa::stream_sink_update(uint32_t, const s16 *buffer, int samples_this_frame)
{
- if (!sample_rate())
+ if (!m_sample_rate)
return;
#if LOG_BUFCNT
@@ -413,20 +413,15 @@ void sound_pa::update_audio_stream(bool is_throttled, const s16 *buffer, int sam
m_has_overflowed = false;
}
- m_ab->write(buffer, samples_this_frame * 2, m_attenuation);
+ m_ab->write(buffer, samples_this_frame * 2);
// for determining buffer overflows, take the sample here instead of in the callback
m_osd_ticks = osd_ticks();
}
-void sound_pa::set_mastervolume(int attenuation)
-{
- m_attenuation = attenuation;
-}
-
void sound_pa::exit()
{
- if (!sample_rate())
+ if (!m_sample_rate)
return;
#if LOG_BUFCNT
@@ -456,8 +451,14 @@ void sound_pa::exit()
osd_printf_verbose("Sound: overflows=%d underflows=%d\n", m_overflows, m_underflows);
}
+} // anonymous namespace
+
+} // namespace osd
+
#else
- MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio")
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_pa, OSD_SOUND_PROVIDER, "portaudio") } }
+
#endif
-MODULE_DEFINITION(SOUND_PORTAUDIO, sound_pa)
+MODULE_DEFINITION(SOUND_PORTAUDIO, osd::sound_pa)
diff --git a/src/osd/modules/sound/pipewire_sound.cpp b/src/osd/modules/sound/pipewire_sound.cpp
new file mode 100644
index 00000000000..8bf5b8e0afa
--- /dev/null
+++ b/src/osd/modules/sound/pipewire_sound.cpp
@@ -0,0 +1,823 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/***************************************************************************
+
+ piepewire_sound.c
+
+ PipeWire interface.
+
+***************************************************************************/
+
+#include "sound_module.h"
+#include "modules/osdmodule.h"
+
+#ifndef NO_USE_PIPEWIRE
+
+#define GNU_SOURCE
+#include "modules/lib/osdobj_common.h"
+
+#include <pipewire/pipewire.h>
+#include <pipewire/extensions/metadata.h>
+#include <spa/debug/pod.h>
+#include <spa/debug/dict.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/raw-utils.h>
+#include <rapidjson/document.h>
+
+#include <map>
+
+class sound_pipewire : public osd_module, public sound_module
+{
+public:
+ sound_pipewire()
+ : osd_module(OSD_SOUND_PROVIDER, "pipewire"), sound_module()
+ {
+ }
+ virtual ~sound_pipewire() { }
+
+ virtual int init(osd_interface &osd, osd_options const &options) override;
+ virtual void exit() override;
+
+ virtual bool external_per_channel_volume() override { return true; }
+ virtual bool split_streams_per_source() override { return true; }
+
+ virtual uint32_t get_generation() override;
+ virtual osd::audio_info get_information() override;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override;
+ virtual void stream_close(uint32_t id) override;
+ virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override;
+ virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) override;
+
+private:
+ struct position_info {
+ uint32_t m_position;
+ std::array<double, 3> m_coords;
+ };
+
+ static const position_info position_infos[];
+
+ static const char *const typenames[];
+ enum { AREC, APLAY };
+
+ struct node_info {
+ sound_pipewire *m_wire;
+ uint32_t m_id, m_osdid;
+ int m_type;
+ std::string m_serial;
+ std::string m_name;
+ std::string m_text_id;
+
+ // Audio node info
+ uint32_t m_sinks, m_sources;
+ std::vector<uint32_t> m_position_codes;
+ std::vector<std::string> m_port_names;
+ std::vector<std::array<double, 3>> m_positions;
+
+ osd::audio_rate_range m_rate;
+ bool m_has_s16;
+ bool m_has_iec958;
+
+ pw_node *m_node;
+ spa_hook m_node_listener;
+
+ node_info(sound_pipewire *wire, uint32_t id, uint32_t osdid, int type, std::string serial, std::string name, std::string text_id) : m_wire(wire), m_id(id), m_osdid(osdid), m_type(type), m_serial(serial), m_name(name), m_text_id(text_id), m_sinks(0), m_sources(0), m_rate{0, 0, 0}, m_has_s16(false), m_has_iec958(false), m_node(nullptr) {
+ spa_zero(m_node_listener);
+ }
+ };
+
+ struct stream_info {
+ sound_pipewire *m_wire;
+ bool m_is_output;
+ uint32_t m_osdid;
+ uint32_t m_node_id;
+ node_info *m_node;
+ uint32_t m_channels;
+ pw_stream *m_stream;
+ std::vector<float> m_volumes;
+ abuffer m_buffer;
+
+ stream_info(sound_pipewire *wire, bool is_output, uint32_t osdid, uint32_t channels) : m_wire(wire), m_is_output(is_output), m_osdid(osdid), m_channels(channels), m_stream(nullptr), m_buffer(channels) {}
+ };
+
+ static const pw_core_events core_events;
+ static const pw_registry_events registry_events;
+ static const pw_node_events node_events;
+ static const pw_metadata_events default_events;
+ static const pw_stream_events stream_sink_events;
+ static const pw_stream_events stream_source_events;
+
+ std::map<uint32_t, node_info> m_nodes;
+ std::map<uint32_t, uint32_t> m_node_osdid_to_id;
+
+ std::map<uint32_t, stream_info> m_streams;
+
+ pw_thread_loop *m_loop;
+ pw_context *m_context;
+ pw_core *m_core;
+ spa_hook m_core_listener;
+ pw_registry *m_registry;
+ spa_hook m_registry_listener;
+ pw_metadata *m_default;
+ spa_hook m_default_listener;
+
+ std::string m_default_audio_sink;
+ std::string m_default_audio_source;
+
+ uint32_t m_node_current_id, m_stream_current_id;
+ uint32_t m_generation;
+ bool m_wait_sync, m_wait_stream;
+
+ void sync();
+
+ void core_event_done(uint32_t id, int seq);
+ static void s_core_event_done(void *data, uint32_t id, int seq);
+
+ void register_node(uint32_t id, const spa_dict *props);
+ void register_port(uint32_t id, const spa_dict *props);
+ void register_link(uint32_t id, const spa_dict *props);
+ void register_default_metadata(uint32_t id);
+ void register_metadata(uint32_t id, const spa_dict *props);
+ void registry_event_global(uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props);
+ static void s_registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props);
+
+ void registry_event_global_remove(uint32_t id);
+ static void s_registry_event_global_remove(void *data, uint32_t id);
+
+ void node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param);
+ static void s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param);
+
+ int default_event_property(uint32_t subject, const char *key, const char *type, const char *value);
+ static int s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value);
+
+ void stream_sink_event_process(stream_info *stream);
+ static void s_stream_sink_event_process(void *data);
+
+ void stream_source_event_process(stream_info *stream);
+ static void s_stream_source_event_process(void *data);
+
+ void stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param);
+ static void s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param);
+};
+
+// Try to more or less map to speaker.h positions
+
+const sound_pipewire::position_info sound_pipewire::position_infos[] = {
+ { SPA_AUDIO_CHANNEL_MONO, { 0.0, 0.0, 1.0 } },
+ { SPA_AUDIO_CHANNEL_FL, { -0.2, 0.0, 1.0 } },
+ { SPA_AUDIO_CHANNEL_FR, { 0.2, 0.0, 1.0 } },
+ { SPA_AUDIO_CHANNEL_FC, { 0.0, 0.0, 1.0 } },
+ { SPA_AUDIO_CHANNEL_LFE, { 0.0, -0.5, 1.0 } },
+ { SPA_AUDIO_CHANNEL_RL, { -0.2, 0.0, -0.5 } },
+ { SPA_AUDIO_CHANNEL_RR, { 0.2, 0.0, -0.5 } },
+ { SPA_AUDIO_CHANNEL_RC, { 0.0, 0.0, -0.5 } },
+ { SPA_AUDIO_CHANNEL_UNKNOWN, { 0.0, 0.0, 0.0 } }
+};
+
+
+const char *const sound_pipewire::typenames[] = {
+ "Audio recorder", "Speaker"
+};
+
+const pw_core_events sound_pipewire::core_events = {
+ PW_VERSION_CORE_EVENTS,
+ nullptr, // info
+ s_core_event_done,
+ nullptr, // ping
+ nullptr, // error
+ nullptr, // remove_id
+ nullptr, // bound_id
+ nullptr, // add_mem
+ nullptr, // remove_mem
+ nullptr // bound_props
+};
+
+const pw_registry_events sound_pipewire::registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ s_registry_event_global,
+ s_registry_event_global_remove
+};
+
+const pw_node_events sound_pipewire::node_events = {
+ PW_VERSION_NODE_EVENTS,
+ nullptr, // info
+ s_node_event_param
+};
+
+const pw_metadata_events sound_pipewire::default_events = {
+ PW_VERSION_METADATA_EVENTS,
+ s_default_event_property
+};
+
+const pw_stream_events sound_pipewire::stream_sink_events = {
+ PW_VERSION_STREAM_EVENTS,
+ nullptr, // destroy
+ nullptr, // state changed
+ nullptr, // control info
+ nullptr, // io changed
+ s_stream_event_param_changed,
+ nullptr, // add buffer
+ nullptr, // remove buffer
+ s_stream_sink_event_process,
+ nullptr, // drained
+ nullptr, // command
+ nullptr // trigger done
+};
+
+const pw_stream_events sound_pipewire::stream_source_events = {
+ PW_VERSION_STREAM_EVENTS,
+ nullptr, // destroy
+ nullptr, // state changed
+ nullptr, // control info
+ nullptr, // io changed
+ s_stream_event_param_changed,
+ nullptr, // add buffer
+ nullptr, // remove buffer
+ s_stream_source_event_process,
+ nullptr, // drained
+ nullptr, // command
+ nullptr // trigger done
+};
+
+void sound_pipewire::register_node(uint32_t id, const spa_dict *props)
+{
+ const spa_dict_item *cls = spa_dict_lookup_item(props, PW_KEY_MEDIA_CLASS);
+ const spa_dict_item *desc = spa_dict_lookup_item(props, PW_KEY_NODE_DESCRIPTION);
+ const spa_dict_item *name = spa_dict_lookup_item(props, PW_KEY_NODE_NAME);
+ const spa_dict_item *serial = spa_dict_lookup_item(props, PW_KEY_OBJECT_SERIAL);
+ if(!cls)
+ return;
+ int type;
+ if(!strcmp(cls->value, "Audio/Source"))
+ type = AREC;
+ else if(!strcmp(cls->value, "Audio/Sink"))
+ type = APLAY;
+ else
+ return;
+
+ m_node_osdid_to_id[m_node_current_id] = id;
+ auto &node = m_nodes.emplace(id, node_info(this, id, m_node_current_id++, type, serial->value, desc ? desc->value : "?", name ? name->value : "?")).first->second;
+
+ // printf("node %03x: %s %s %s | %s\n", node.m_id, serial->value, typenames[node.m_type], node.m_name.c_str(), node.m_text_id.c_str());
+
+ node.m_node = (pw_node *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0);
+ pw_node_add_listener(node.m_node, &node.m_node_listener, &node_events, &node);
+ pw_node_enum_params(node.m_node, 0, 3, 0, 0xffffffff, nullptr);
+ m_generation++;
+}
+
+void sound_pipewire::register_port(uint32_t id, const spa_dict *props)
+{
+ uint32_t node = strtol(spa_dict_lookup_item(props, PW_KEY_NODE_ID)->value, nullptr, 10);
+ auto ind = m_nodes.find(node);
+ if(ind == m_nodes.end())
+ return;
+
+ const spa_dict_item *channel = spa_dict_lookup_item(props, PW_KEY_AUDIO_CHANNEL);
+ const spa_dict_item *dir = spa_dict_lookup_item(props, PW_KEY_PORT_DIRECTION);
+ bool is_in = dir && !strcmp(dir->value, "in") ;
+ uint32_t index = strtol(spa_dict_lookup_item(props, PW_KEY_PORT_ID)->value, nullptr, 10);
+
+ if(is_in && ind->second.m_sinks <= index)
+ ind->second.m_sinks = index+1;
+ if(!is_in && ind->second.m_sources <= index)
+ ind->second.m_sources = index+1;
+
+ auto &port_names = ind->second.m_port_names;
+ if(port_names.size() <= index)
+ port_names.resize(index+1);
+
+ if(is_in || port_names[index].empty())
+ port_names[index] = channel ? channel->value : "?";
+
+ m_generation++;
+ // printf("port %03x.%d %03x: %s\n", node, index, id, port_names[index].c_str());
+}
+
+void sound_pipewire::register_link(uint32_t id, const spa_dict *props)
+{
+ const spa_dict_item *input = spa_dict_lookup_item(props, PW_KEY_LINK_INPUT_NODE);
+ const spa_dict_item *output = spa_dict_lookup_item(props, PW_KEY_LINK_OUTPUT_NODE);
+ if(!input || !output)
+ return;
+
+ uint32_t input_id = strtol(input->value, nullptr, 10);
+ uint32_t output_id = strtol(output->value, nullptr, 10);
+
+ for(auto &si : m_streams) {
+ stream_info &stream = si.second;
+ if(stream.m_is_output && stream.m_node_id == output_id && (stream.m_node && stream.m_node->m_id != input_id)) {
+ auto ni = m_nodes.find(input_id);
+ if(ni != m_nodes.end()) {
+ stream.m_node = &ni->second;
+ m_generation ++;
+ return;
+ }
+ }
+ if(!stream.m_is_output && stream.m_node_id == input_id && (stream.m_node && stream.m_node->m_id != output_id)) {
+ auto ni = m_nodes.find(output_id);
+ if(ni != m_nodes.end()) {
+ stream.m_node = &ni->second;
+ m_generation ++;
+ return;
+ }
+ }
+ }
+}
+
+void sound_pipewire::register_default_metadata(uint32_t id)
+{
+ m_default = (pw_metadata *)pw_registry_bind(m_registry, id, PW_TYPE_INTERFACE_Metadata, PW_VERSION_METADATA, 0);
+ pw_metadata_add_listener(m_default, &m_default_listener, &default_events, this);
+}
+
+void sound_pipewire::register_metadata(uint32_t id, const spa_dict *props)
+{
+ const spa_dict_item *mn = spa_dict_lookup_item(props, PW_KEY_METADATA_NAME);
+ if(mn && !strcmp(mn->value, "default"))
+ register_default_metadata(id);
+}
+
+void sound_pipewire::registry_event_global(uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const spa_dict *props)
+{
+ if(!strcmp(type, PW_TYPE_INTERFACE_Node))
+ register_node(id, props);
+ else if(!strcmp(type, PW_TYPE_INTERFACE_Port))
+ register_port(id, props);
+ else if(!strcmp(type, PW_TYPE_INTERFACE_Metadata))
+ register_metadata(id, props);
+ else if(!strcmp(type, PW_TYPE_INTERFACE_Link))
+ register_link(id, props);
+ else {
+ // printf("type %03x %s\n", id, type);
+ }
+}
+
+void sound_pipewire::s_registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const spa_dict *props)
+{
+ ((sound_pipewire *)data)->registry_event_global(id, permissions, type, version, props);
+}
+
+void sound_pipewire::registry_event_global_remove(uint32_t id)
+{
+ auto ind = m_nodes.find(id);
+ if(ind == m_nodes.end())
+ return;
+
+ for(auto &istream : m_streams)
+ if(istream.second.m_node == &ind->second)
+ istream.second.m_node = nullptr;
+ m_nodes.erase(ind);
+ m_generation++;
+}
+
+void sound_pipewire::s_registry_event_global_remove(void *data, uint32_t id)
+{
+ ((sound_pipewire *)data)->registry_event_global_remove(id);
+}
+
+void sound_pipewire::node_event_param(node_info *node, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param)
+{
+ if(id == SPA_PARAM_EnumFormat) {
+ const spa_pod_prop *subtype = spa_pod_find_prop(param, nullptr, SPA_FORMAT_mediaSubtype);
+ if(subtype) {
+ uint32_t sval;
+ if(!spa_pod_get_id(&subtype->value, &sval)) {
+ if(sval == SPA_MEDIA_SUBTYPE_raw) {
+ const spa_pod_prop *format = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_format);
+ const spa_pod_prop *rate = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate);
+ const spa_pod_prop *position = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position);
+
+ if(format) {
+ uint32_t *entry;
+ SPA_POD_CHOICE_FOREACH((spa_pod_choice *)(&format->value), entry) {
+ if(*entry == SPA_AUDIO_FORMAT_S16)
+ node->m_has_s16 = true;
+ }
+ }
+ if(rate) {
+ if(rate->value.type == SPA_TYPE_Choice) {
+ struct spa_pod_choice_body *b = &((spa_pod_choice *)(&rate->value))->body;
+ uint32_t *choices = (uint32_t *)(b+1);
+ node->m_rate.m_default_rate = choices[0];
+ if(b->type == SPA_CHOICE_Range) {
+ node->m_rate.m_min_rate = choices[1];
+ node->m_rate.m_max_rate = choices[2];
+ } else {
+ node->m_rate.m_min_rate = node->m_rate.m_default_rate;
+ node->m_rate.m_max_rate = node->m_rate.m_default_rate;
+ }
+ }
+ }
+
+ if(position) {
+ uint32_t *entry;
+ node->m_position_codes.clear();
+ node->m_positions.clear();
+ SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&position->value), entry) {
+ node->m_position_codes.push_back(*entry);
+ for(uint32_t i = 0;; i++) {
+ if((position_infos[i].m_position == *entry) || (position_infos[i].m_position == SPA_AUDIO_CHANNEL_UNKNOWN)) {
+ node->m_positions.push_back(position_infos[i].m_coords);
+ break;
+ }
+ }
+ }
+ }
+ } else if(sval == SPA_MEDIA_SUBTYPE_iec958)
+ node->m_has_iec958 = true;
+ }
+ }
+ m_generation++;
+
+ } else
+ spa_debug_pod(2, nullptr, param);
+}
+
+void sound_pipewire::s_node_event_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param)
+{
+ node_info *n = (node_info *)data;
+ n->m_wire->node_event_param(n, seq, id, index, next, param);
+}
+
+int sound_pipewire::default_event_property(uint32_t subject, const char *key, const char *type, const char *value)
+{
+ if(!value)
+ return 0;
+ std::string val = value;
+ if(!strcmp(type, "Spa:String:JSON")) {
+ rapidjson::Document json;
+ json.Parse(value);
+ if(json.IsObject() && json.HasMember("name") && json["name"].IsString())
+ val = json["name"].GetString();
+ } else
+ val = value;
+
+ if(!strcmp(key, "default.audio.sink"))
+ m_default_audio_sink = val;
+
+ else if(!strcmp(key, "default.audio.source"))
+ m_default_audio_source = val;
+
+ return 0;
+}
+
+int sound_pipewire::s_default_event_property(void *data, uint32_t subject, const char *key, const char *type, const char *value)
+{
+ return ((sound_pipewire *)data)->default_event_property(subject, key, type, value);
+}
+
+int sound_pipewire::init(osd_interface &osd, osd_options const &options)
+{
+ spa_zero(m_core_listener);
+ spa_zero(m_registry_listener);
+ spa_zero(m_default_listener);
+
+ m_node_current_id = 1;
+ m_stream_current_id = 1;
+ m_generation = 1;
+
+ m_wait_sync = false;
+ m_wait_stream = false;
+
+ pw_init(nullptr, nullptr);
+ m_loop = pw_thread_loop_new(nullptr, nullptr);
+ m_context = pw_context_new(pw_thread_loop_get_loop(m_loop), nullptr, 0);
+ m_core = pw_context_connect(m_context, nullptr, 0);
+
+ if(!m_core)
+ return 1;
+
+ pw_core_add_listener(m_core, &m_core_listener, &core_events, this);
+
+ m_registry = pw_core_get_registry(m_core, PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(m_registry, &m_registry_listener, &registry_events, this);
+
+ pw_thread_loop_start(m_loop);
+
+ // The first sync ensures that the initial information request is
+ // completed, the second that the subsequent ones (parameters
+ // retrieval) are completed too.
+ sync();
+ sync();
+
+ return 0;
+}
+
+void sound_pipewire::core_event_done(uint32_t id, int seq)
+{
+ m_wait_sync = false;
+ pw_thread_loop_signal(m_loop, false);
+}
+
+void sound_pipewire::s_core_event_done(void *data, uint32_t id, int seq)
+{
+ ((sound_pipewire *)data)->core_event_done(id, seq);
+}
+
+void sound_pipewire::sync()
+{
+ pw_thread_loop_lock(m_loop);
+ m_wait_sync = true;
+ pw_core_sync(m_core, PW_ID_CORE, 0);
+ while(m_wait_sync)
+ pw_thread_loop_wait(m_loop);
+ pw_thread_loop_unlock(m_loop);
+}
+
+void sound_pipewire::exit()
+{
+ pw_thread_loop_stop(m_loop);
+ for(const auto &si : m_streams)
+ pw_stream_destroy(si.second.m_stream);
+ for(const auto &ni : m_nodes)
+ pw_proxy_destroy((pw_proxy *)ni.second.m_node);
+ pw_proxy_destroy((pw_proxy *)m_default);
+ pw_proxy_destroy((pw_proxy *)m_registry);
+ pw_core_disconnect(m_core);
+ pw_context_destroy(m_context);
+ pw_thread_loop_destroy(m_loop);
+ pw_deinit();
+}
+
+uint32_t sound_pipewire::get_generation()
+{
+ pw_thread_loop_lock(m_loop);
+ uint32_t result = m_generation;
+ pw_thread_loop_unlock(m_loop);
+ return result;
+}
+
+osd::audio_info sound_pipewire::get_information()
+{
+ osd::audio_info result;
+ pw_thread_loop_lock(m_loop);
+ result.m_nodes.resize(m_nodes.size());
+ result.m_default_sink = 0;
+ result.m_default_source = 0;
+ result.m_generation = m_generation;
+ uint32_t node = 0;
+ for(auto &inode : m_nodes) {
+ result.m_nodes[node].m_name = inode.second.m_name;
+ result.m_nodes[node].m_id = inode.second.m_osdid;
+ result.m_nodes[node].m_rate = inode.second.m_rate;
+ result.m_nodes[node].m_sinks = inode.second.m_sinks;
+ result.m_nodes[node].m_sources = inode.second.m_sources;
+ result.m_nodes[node].m_port_names = inode.second.m_port_names;
+ result.m_nodes[node].m_port_positions = inode.second.m_positions;
+
+ if(inode.second.m_text_id == m_default_audio_sink)
+ result.m_default_sink = inode.second.m_osdid;
+ if(inode.second.m_text_id == m_default_audio_source)
+ result.m_default_source = inode.second.m_osdid;
+ node ++;
+ }
+
+ for(auto &istream : m_streams)
+ if(istream.second.m_node)
+ result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_node->m_osdid, istream.second.m_volumes });
+
+ pw_thread_loop_unlock(m_loop);
+ return result;
+}
+
+void sound_pipewire::stream_sink_event_process(stream_info *stream)
+{
+ pw_buffer *buffer = pw_stream_dequeue_buffer(stream->m_stream);
+ if(!buffer)
+ return;
+
+ spa_buffer *sbuf = buffer->buffer;
+ stream->m_buffer.get((int16_t *)(sbuf->datas[0].data), buffer->requested);
+
+ sbuf->datas[0].chunk->offset = 0;
+ sbuf->datas[0].chunk->stride = stream->m_channels * 2;
+ sbuf->datas[0].chunk->size = buffer->requested * stream->m_channels * 2;
+
+ pw_stream_queue_buffer(stream->m_stream, buffer);
+}
+
+void sound_pipewire::s_stream_sink_event_process(void *data)
+{
+ stream_info *info = (stream_info *)(data);
+ info->m_wire->stream_sink_event_process(info);
+}
+
+void sound_pipewire::stream_source_event_process(stream_info *stream)
+{
+ pw_buffer *buffer = pw_stream_dequeue_buffer(stream->m_stream);
+ if(!buffer)
+ return;
+
+ spa_buffer *sbuf = buffer->buffer;
+ stream->m_buffer.push((int16_t *)(sbuf->datas[0].data), sbuf->datas[0].chunk->size / stream->m_channels / 2);
+ pw_stream_queue_buffer(stream->m_stream, buffer);
+}
+
+void sound_pipewire::s_stream_source_event_process(void *data)
+{
+ stream_info *info = (stream_info *)(data);
+ info->m_wire->stream_source_event_process(info);
+}
+
+void sound_pipewire::stream_event_param_changed(stream_info *stream, uint32_t id, const spa_pod *param)
+{
+ if(id == SPA_PARAM_Props) {
+ const spa_pod_prop *vols = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
+ if(vols) {
+ bool initial = stream->m_volumes.empty();
+ stream->m_volumes.clear();
+ float *entry;
+ SPA_POD_ARRAY_FOREACH((spa_pod_array *)(&vols->value), entry) {
+ stream->m_volumes.push_back(osd::linear_to_db(*entry));
+ }
+ if(!stream->m_volumes.empty()) {
+ if(initial) {
+ m_wait_stream = false;
+ pw_thread_loop_signal(m_loop, false);
+ } else
+ m_generation++;
+ }
+ }
+ }
+}
+
+void sound_pipewire::s_stream_event_param_changed(void *data, uint32_t id, const spa_pod *param)
+{
+ stream_info *info = (stream_info *)(data);
+ info->m_wire->stream_event_param_changed(info, id, param);
+}
+
+uint32_t sound_pipewire::stream_sink_open(uint32_t node, std::string name, uint32_t rate)
+{
+ pw_thread_loop_lock(m_loop);
+ auto ni = m_node_osdid_to_id.find(node);
+ if(ni == m_node_osdid_to_id.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return 0;
+ }
+ node_info &snode = m_nodes.find(ni->second)->second;
+
+ uint32_t id = m_stream_current_id++;
+ auto &stream = m_streams.emplace(id, stream_info(this, true, id, snode.m_sinks)).first->second;
+
+ stream.m_stream = pw_stream_new_simple(pw_thread_loop_get_loop(m_loop),
+ name.c_str(),
+ pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Game",
+ PW_KEY_TARGET_OBJECT, snode.m_serial.c_str(),
+ nullptr),
+ &stream_sink_events,
+ &stream);
+ stream.m_node = &snode;
+
+ const spa_pod *params;
+ spa_audio_info_raw format = {
+ SPA_AUDIO_FORMAT_S16,
+ 0,
+ rate,
+ stream.m_channels
+ };
+ for(uint32_t i=0; i != snode.m_sinks; i++)
+ format.position[i] = snode.m_position_codes[i];
+
+ uint8_t buffer[1024];
+ spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &format);
+
+ pw_stream_connect(stream.m_stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
+ &params, 1);
+
+ m_wait_stream = true;
+ while(m_wait_stream)
+ pw_thread_loop_wait(m_loop);
+
+ stream.m_node_id = pw_stream_get_node_id(stream.m_stream);
+ pw_thread_loop_unlock(m_loop);
+
+ return id;
+}
+
+uint32_t sound_pipewire::stream_source_open(uint32_t node, std::string name, uint32_t rate)
+{
+ pw_thread_loop_lock(m_loop);
+ auto ni = m_node_osdid_to_id.find(node);
+ if(ni == m_node_osdid_to_id.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return 0;
+ }
+ node_info &snode = m_nodes.find(ni->second)->second;
+
+ uint32_t id = m_stream_current_id++;
+ auto &stream = m_streams.emplace(id, stream_info(this, false, id, snode.m_sources)).first->second;
+
+ stream.m_stream = pw_stream_new_simple(pw_thread_loop_get_loop(m_loop),
+ name.c_str(),
+ pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Record",
+ PW_KEY_MEDIA_ROLE, "Game",
+ PW_KEY_TARGET_OBJECT, snode.m_serial.c_str(),
+ nullptr),
+ &stream_source_events,
+ &stream);
+ stream.m_node = &snode;
+
+ const spa_pod *params;
+ spa_audio_info_raw format = {
+ SPA_AUDIO_FORMAT_S16,
+ 0,
+ rate,
+ stream.m_channels
+ };
+ for(uint32_t i=0; i != snode.m_sources; i++)
+ format.position[i] = snode.m_position_codes[i];
+
+ uint8_t buffer[1024];
+ spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ params = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &format);
+
+ pw_stream_connect(stream.m_stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ pw_stream_flags(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
+ &params, 1);
+
+ m_wait_stream = true;
+ while(m_wait_stream)
+ pw_thread_loop_wait(m_loop);
+
+ stream.m_node_id = pw_stream_get_node_id(stream.m_stream);
+ pw_thread_loop_unlock(m_loop);
+
+ return id;
+}
+
+void sound_pipewire::stream_set_volumes(uint32_t id, const std::vector<float> &db)
+{
+ pw_thread_loop_lock(m_loop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return;
+ }
+ stream_info &stream = si->second;
+ stream.m_volumes = db;
+ std::vector<float> linear;
+ for(float db1 : db)
+ linear.push_back(osd::db_to_linear(db1));
+ pw_stream_set_control(stream.m_stream, SPA_PROP_channelVolumes, linear.size(), linear.data(), 0);
+ pw_thread_loop_unlock(m_loop);
+}
+
+void sound_pipewire::stream_close(uint32_t id)
+{
+ pw_thread_loop_lock(m_loop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return;
+ }
+ stream_info &stream = si->second;
+ pw_stream_destroy(stream.m_stream);
+ m_streams.erase(si);
+ pw_thread_loop_unlock(m_loop);
+}
+
+void sound_pipewire::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame)
+{
+ pw_thread_loop_lock(m_loop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return;
+ }
+ si->second.m_buffer.push(buffer, samples_this_frame);
+ pw_thread_loop_unlock(m_loop);
+}
+
+void sound_pipewire::stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame)
+{
+ pw_thread_loop_lock(m_loop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pw_thread_loop_unlock(m_loop);
+ return;
+ }
+ si->second.m_buffer.get(buffer, samples_this_frame);
+ pw_thread_loop_unlock(m_loop);
+}
+
+#else
+ MODULE_NOT_SUPPORTED(sound_pipewire, OSD_SOUND_PROVIDER, "pipewire")
+#endif
+
+MODULE_DEFINITION(SOUND_PIPEWIRE, sound_pipewire)
diff --git a/src/osd/modules/sound/pulse_sound.cpp b/src/osd/modules/sound/pulse_sound.cpp
new file mode 100644
index 00000000000..820ca149c39
--- /dev/null
+++ b/src/osd/modules/sound/pulse_sound.cpp
@@ -0,0 +1,533 @@
+// license:BSD-3-Clause
+// copyright-holders:Olivier Galibert
+/***************************************************************************
+
+ pulse_sound.c
+
+ PulseAudio interface.
+
+*******************************************************************c********/
+
+#include "sound_module.h"
+#include "modules/osdmodule.h"
+
+#ifndef NO_USE_PULSEAUDIO
+
+#define GNU_SOURCE
+
+#include <pulse/pulseaudio.h>
+#include <map>
+
+#include "modules/lib/osdobj_common.h"
+
+class sound_pulse : public osd_module, public sound_module
+{
+public:
+ sound_pulse()
+ : osd_module(OSD_SOUND_PROVIDER, "pulse"), sound_module()
+ {
+ }
+ virtual ~sound_pulse() { }
+
+ virtual int init(osd_interface &osd, osd_options const &options) override;
+ virtual void exit() override;
+
+ virtual bool external_per_channel_volume() override { return false; }
+ virtual bool split_streams_per_source() override { return true; }
+
+ virtual uint32_t get_generation() override;
+ virtual osd::audio_info get_information() override;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) override;
+ virtual void stream_close(uint32_t id) override;
+ virtual void stream_sink_update(uint32_t, const int16_t *buffer, int samples_this_frame) override;
+
+private:
+ struct position_info {
+ pa_channel_position_t m_position;
+ std::array<double, 3> m_coords;
+ };
+
+ static const position_info position_infos[];
+ static const char *const typenames[];
+ enum { AREC, APLAY };
+
+ struct node_info {
+ sound_pulse *m_pulse;
+ uint32_t m_id, m_osdid;
+ int m_type;
+ std::string m_name, m_desc;
+
+ // Audio node info
+ std::vector<pa_channel_position_t> m_position_codes;
+ std::vector<std::string> m_position_names;
+ std::vector<std::array<double, 3>> m_positions;
+ uint32_t m_sink_port_count, m_source_port_count;
+
+ osd::audio_rate_range m_rate;
+
+ node_info(sound_pulse *pulse, uint32_t id, uint32_t osdid, int type, std::string name, std::string desc) : m_pulse(pulse), m_id(id), m_osdid(osdid), m_type(type), m_name(name), m_desc(desc), m_sink_port_count(0), m_source_port_count(0), m_rate{0, 0, 0} {
+ }
+ };
+
+ struct stream_info {
+ sound_pulse *m_pulse;
+ uint32_t m_osdid;
+ uint32_t m_pulse_id;
+ node_info *m_target_node;
+ uint32_t m_channels;
+ pa_stream *m_stream;
+ std::vector<float> m_volumes;
+ abuffer m_buffer;
+
+ stream_info(sound_pulse *pulse, uint32_t osdid, uint32_t channels) : m_pulse(pulse), m_osdid(osdid), m_pulse_id(0), m_channels(channels), m_stream(nullptr), m_volumes(channels), m_buffer(channels) {}
+ };
+
+ std::map<uint32_t, node_info> m_nodes;
+ std::map<uint32_t, uint32_t> m_node_osdid_to_id;
+
+ std::map<uint32_t, stream_info> m_streams;
+ std::map<uint32_t, uint32_t> m_stream_pulse_id_to_osdid;
+
+ pa_threaded_mainloop *m_mainloop;
+ pa_context *m_context;
+ uint32_t m_node_current_id, m_stream_current_id;
+ uint32_t m_generation;
+ bool m_wait_stream, m_wait_init;
+
+ std::string m_default_audio_sink;
+ std::string m_default_audio_source;
+
+ static void i_server_info(pa_context *, const pa_server_info *i, void *self);
+ void server_info(const pa_server_info *i);
+ static void i_context_notify(pa_context *, void *self);
+ void context_notify();
+ static void i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self);
+ void context_subscribe(pa_subscription_event_type_t t, uint32_t idx);
+ static void i_stream_notify(pa_stream *, void *self);
+ void stream_notify(stream_info *stream);
+ static void i_stream_write_request(pa_stream *, size_t size, void *self);
+ void stream_write_request(stream_info *stream, size_t size);
+ static void i_source_info(pa_context *, const pa_source_info *i, int eol, void *self);
+ void source_info(const pa_source_info *i, int eol);
+ static void i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self);
+ void sink_info_new(const pa_sink_info *i, int eol);
+ static void i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self);
+ void sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol);
+
+ void generic_error(const char *msg);
+ void generic_pa_error(const char *msg, int err);
+};
+
+// Try to more or less map to speaker.h positions
+
+const sound_pulse::position_info sound_pulse::position_infos[] = {
+ { PA_CHANNEL_POSITION_MONO, { 0.0, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_LEFT, { -0.2, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_RIGHT, { 0.2, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_FRONT_CENTER, { 0.0, 0.0, 1.0 } },
+ { PA_CHANNEL_POSITION_LFE, { 0.0, -0.5, 1.0 } },
+ { PA_CHANNEL_POSITION_REAR_LEFT, { -0.2, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_REAR_RIGHT, { 0.2, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_REAR_CENTER, { 0.0, 0.0, -0.5 } },
+ { PA_CHANNEL_POSITION_MAX, { 0.0, 0.0, 0.0 } }
+};
+
+
+const char *const sound_pulse::typenames[] = {
+ "Audio recorder", "Speaker"
+};
+
+void sound_pulse::generic_error(const char *msg)
+{
+ perror(msg);
+ ::exit(1);
+}
+
+void sound_pulse::generic_pa_error(const char *msg, int err)
+{
+ fprintf(stderr, "%s: %s\n", msg, pa_strerror(err));
+ ::exit(1);
+}
+
+void sound_pulse::context_notify()
+{
+ pa_context_state state = pa_context_get_state(m_context);
+ if(state == PA_CONTEXT_READY) {
+ pa_context_subscribe(m_context, PA_SUBSCRIPTION_MASK_ALL, nullptr, this);
+ pa_context_get_sink_info_list(m_context, i_sink_info_new, (void *)this);
+
+ } else if(state == PA_CONTEXT_FAILED || state == PA_CONTEXT_TERMINATED) {
+ m_generation = 0x80000000;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
+ }
+}
+
+void sound_pulse::i_context_notify(pa_context *, void *self)
+{
+ static_cast<sound_pulse *>(self)->context_notify();
+}
+
+void sound_pulse::stream_notify(stream_info *stream)
+{
+ pa_stream_state state = pa_stream_get_state(stream->m_stream);
+
+ if(state == PA_STREAM_READY || state == PA_STREAM_FAILED || state == PA_STREAM_TERMINATED) {
+ m_wait_stream = false;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
+ }
+}
+
+void sound_pulse::i_stream_notify(pa_stream *, void *self)
+{
+ stream_info *si = static_cast<stream_info *>(self);
+ si->m_pulse->stream_notify(si);
+}
+
+void sound_pulse::stream_write_request(stream_info *stream, size_t size)
+{
+ // This is called with the thread locked
+ while(size) {
+ void *buffer;
+ size_t bsize = size;
+ int err = pa_stream_begin_write(stream->m_stream, &buffer, &bsize);
+ if(err)
+ generic_pa_error("stream begin write", err);
+ uint32_t frames = bsize/2/stream->m_channels;
+ uint32_t bytes = frames*2*stream->m_channels;
+ stream->m_buffer.get((int16_t *)buffer, frames);
+ err = pa_stream_write(stream->m_stream, buffer, bytes, nullptr, 0, PA_SEEK_RELATIVE);
+ if(err)
+ generic_pa_error("stream write", err);
+ size -= bytes;
+ }
+}
+
+void sound_pulse::i_stream_write_request(pa_stream *, size_t size, void *self)
+{
+ stream_info *si = static_cast<stream_info *>(self);
+ si->m_pulse->stream_write_request(si, size);
+}
+
+
+void sound_pulse::server_info(const pa_server_info *i)
+{
+ m_default_audio_sink = i->default_sink_name;
+ m_default_audio_source = i->default_source_name;
+ m_generation++;
+ if(m_wait_init) {
+ m_wait_init = false;
+ pa_threaded_mainloop_signal(m_mainloop, 0);
+ }
+}
+
+void sound_pulse::i_server_info(pa_context *, const pa_server_info *i, void *self)
+{
+ static_cast<sound_pulse *>(self)->server_info(i);
+}
+
+void sound_pulse::source_info(const pa_source_info *i, int eol)
+{
+ if(eol) {
+ if(m_wait_init)
+ pa_context_get_server_info(m_context, i_server_info, (void *)this);
+ return;
+ }
+ auto ni = m_nodes.find(i->index);
+ if(ni != m_nodes.end()) {
+ // Add the monitoring sources to the node
+ ni->second.m_source_port_count = i->channel_map.channels;
+ return;
+ }
+
+ m_node_osdid_to_id[m_node_current_id] = i->index;
+ auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, AREC, i->name, i->description)).first->second;
+
+ node.m_source_port_count = i->channel_map.channels;
+ for(int chan=0; chan != i->channel_map.channels; chan++) {
+ pa_channel_position_t pos = i->channel_map.map[chan];
+ node.m_position_codes.push_back(pos);
+ node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos));
+ for(uint32_t j = 0;; j++) {
+ if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) {
+ node.m_positions.push_back(position_infos[j].m_coords);
+ break;
+ }
+ }
+ }
+}
+
+void sound_pulse::i_source_info(pa_context *, const pa_source_info *i, int eol, void *self)
+{
+ static_cast<sound_pulse *>(self)->source_info(i, eol);
+}
+
+void sound_pulse::sink_info_new(const pa_sink_info *i, int eol)
+{
+ if(eol) {
+ if(m_wait_init)
+ pa_context_get_source_info_list(m_context, i_source_info, (void *)this);
+ return;
+ }
+
+ m_node_osdid_to_id[m_node_current_id] = i->index;
+ auto &node = m_nodes.emplace(i->index, node_info(this, i->index, m_node_current_id++, APLAY, i->name, i->description)).first->second;
+
+ node.m_sink_port_count = i->channel_map.channels;
+ for(int chan=0; chan != i->channel_map.channels; chan++) {
+ pa_channel_position_t pos = i->channel_map.map[chan];
+ node.m_position_codes.push_back(pos);
+ node.m_position_names.push_back(pa_channel_position_to_pretty_string(pos));
+ for(uint32_t j = 0;; j++) {
+ if((position_infos[j].m_position == pos) || (position_infos[j].m_position == PA_CHANNEL_POSITION_MAX)) {
+ node.m_positions.push_back(position_infos[j].m_coords);
+ break;
+ }
+ }
+ }
+ m_generation++;
+}
+
+void sound_pulse::i_sink_info_new(pa_context *, const pa_sink_info *i, int eol, void *self)
+{
+ static_cast<sound_pulse *>(self)->sink_info_new(i, eol);
+}
+
+
+void sound_pulse::sink_input_info_change(stream_info *stream, const pa_sink_input_info *i, int eol)
+{
+ if(eol)
+ return;
+
+ auto ni = m_nodes.find(i->sink);
+ if(ni != m_nodes.end())
+ stream->m_target_node = &ni->second;
+
+ for(uint32_t port = 0; port != stream->m_channels; port++)
+ stream->m_volumes[port] = pa_sw_volume_to_dB(i->volume.values[port]);
+
+ m_generation++;
+}
+
+void sound_pulse::i_sink_input_info_change(pa_context *, const pa_sink_input_info *i, int eol, void *self)
+{
+ stream_info *stream = static_cast<stream_info *>(self);
+ stream->m_pulse->sink_input_info_change(stream, i, eol);
+}
+
+void sound_pulse::context_subscribe(pa_subscription_event_type_t t, uint32_t idx)
+{
+ // This is called with the thread locked
+ switch(int(t)) {
+ case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SINK:
+ case PA_SUBSCRIPTION_EVENT_REMOVE | PA_SUBSCRIPTION_EVENT_SOURCE: {
+ auto si = m_nodes.find(idx);
+ if(si == m_nodes.end())
+ break;
+ for(auto &istream : m_streams)
+ if(istream.second.m_target_node == &si->second)
+ istream.second.m_target_node = nullptr;
+ m_nodes.erase(si);
+ m_generation++;
+ break;
+ }
+
+ case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SINK:
+ pa_context_get_sink_info_by_index(m_context, idx, i_sink_info_new, this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_NEW | PA_SUBSCRIPTION_EVENT_SOURCE:
+ pa_context_get_source_info_by_index(m_context, idx, i_source_info, this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SERVER:
+ pa_context_get_server_info(m_context, i_server_info, (void *)this);
+ break;
+
+ case PA_SUBSCRIPTION_EVENT_CHANGE | PA_SUBSCRIPTION_EVENT_SINK_INPUT: {
+ auto si1 = m_stream_pulse_id_to_osdid.find(idx);
+ if(si1 == m_stream_pulse_id_to_osdid.end())
+ break;
+ auto si = m_streams.find(si1->second);
+ if(si == m_streams.end())
+ break;
+ pa_context_get_sink_input_info(m_context, idx, i_sink_input_info_change, (void *)&si->second);
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void sound_pulse::i_context_subscribe(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void *self)
+{
+ static_cast<sound_pulse *>(self)->context_subscribe(t, idx);
+}
+
+
+
+int sound_pulse::init(osd_interface &osd, osd_options const &options)
+{
+ m_node_current_id = 1;
+ m_stream_current_id = 1;
+ m_generation = 0;
+ m_wait_stream = false;
+ m_wait_init = true;
+
+ m_mainloop = pa_threaded_mainloop_new();
+ m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "MAME");
+ pa_context_set_state_callback(m_context, i_context_notify, this);
+ pa_context_set_subscribe_callback(m_context, i_context_subscribe, this);
+ pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+ pa_threaded_mainloop_start(m_mainloop);
+
+ pa_threaded_mainloop_lock(m_mainloop);
+ while(m_wait_init)
+ pa_threaded_mainloop_wait(m_mainloop);
+ pa_threaded_mainloop_unlock(m_mainloop);
+ if(m_generation >= 0x80000000)
+ return 1;
+
+ return 0;
+}
+
+uint32_t sound_pulse::get_generation()
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ uint32_t result = m_generation;
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return result;
+}
+
+osd::audio_info sound_pulse::get_information()
+{
+ osd::audio_info result;
+ pa_threaded_mainloop_lock(m_mainloop);
+ result.m_nodes.resize(m_nodes.size());
+ result.m_default_sink = 0;
+ result.m_default_source = 0;
+ result.m_generation = m_generation;
+ uint32_t node = 0;
+ for(auto &inode : m_nodes) {
+ result.m_nodes[node].m_name = inode.second.m_desc;
+ result.m_nodes[node].m_id = inode.second.m_osdid;
+ result.m_nodes[node].m_rate = inode.second.m_rate;
+ result.m_nodes[node].m_sinks = inode.second.m_sink_port_count;
+ result.m_nodes[node].m_sources = inode.second.m_source_port_count;
+ result.m_nodes[node].m_port_names = inode.second.m_position_names;
+ result.m_nodes[node].m_port_positions = inode.second.m_positions;
+
+ if(inode.second.m_name == m_default_audio_sink)
+ result.m_default_sink = inode.second.m_osdid;
+ if(inode.second.m_name == m_default_audio_source)
+ result.m_default_source = inode.second.m_osdid;
+ node ++;
+ }
+
+ for(auto &istream : m_streams)
+ if(istream.second.m_target_node)
+ result.m_streams.emplace_back(osd::audio_info::stream_info { istream.second.m_osdid, istream.second.m_target_node->m_osdid, istream.second.m_volumes });
+
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return result;
+}
+
+uint32_t sound_pulse::stream_sink_open(uint32_t node, std::string name, uint32_t rate)
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto ni = m_node_osdid_to_id.find(node);
+ if(ni == m_node_osdid_to_id.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return 0;
+ }
+ node_info &snode = m_nodes.find(ni->second)->second;
+
+ uint32_t id = m_stream_current_id++;
+ auto &stream = m_streams.emplace(id, stream_info(this, id, snode.m_sink_port_count)).first->second;
+
+ pa_sample_spec ss;
+#ifdef LSB_FIRST
+ ss.format = PA_SAMPLE_S16LE;
+#else
+ ss.format = PA_SAMPLE_S16BE;
+#endif
+ ss.rate = rate;
+ ss.channels = stream.m_channels;
+ stream.m_stream = pa_stream_new(m_context, name.c_str(), &ss, nullptr);
+ pa_stream_set_state_callback(stream.m_stream, i_stream_notify, &stream);
+ pa_stream_set_write_callback(stream.m_stream, i_stream_write_request, &stream);
+
+ pa_buffer_attr battr;
+ battr.fragsize = uint32_t(-1);
+ battr.maxlength = 1024;
+ battr.minreq = uint32_t(-1);
+ battr.prebuf = uint32_t(-1);
+ battr.tlength = uint32_t(-1);
+
+ int err = pa_stream_connect_playback(stream.m_stream, snode.m_name.c_str(), &battr, pa_stream_flags_t(PA_STREAM_ADJUST_LATENCY|PA_STREAM_START_UNMUTED), nullptr, nullptr);
+ if(err)
+ generic_pa_error("stream connect playback", err);
+
+ stream.m_target_node = &snode;
+
+ m_wait_stream = true;
+ while(m_wait_stream)
+ pa_threaded_mainloop_wait(m_mainloop);
+
+ stream.m_pulse_id = pa_stream_get_index(stream.m_stream);
+ m_stream_pulse_id_to_osdid[stream.m_pulse_id] = id;
+
+ pa_threaded_mainloop_unlock(m_mainloop);
+
+ return id;
+}
+
+void sound_pulse::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame)
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return;
+ }
+ si->second.m_buffer.push(buffer, samples_this_frame);
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+void sound_pulse::stream_set_volumes(uint32_t id, const std::vector<float> &db)
+{
+}
+
+void sound_pulse::stream_close(uint32_t id)
+{
+ pa_threaded_mainloop_lock(m_mainloop);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end()) {
+ pa_threaded_mainloop_unlock(m_mainloop);
+ return;
+ }
+ stream_info &stream = si->second;
+ pa_stream_set_state_callback(stream.m_stream, nullptr, &stream);
+ pa_stream_set_write_callback(stream.m_stream, nullptr, &stream);
+ pa_stream_disconnect(stream.m_stream);
+ m_streams.erase(si);
+ pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+void sound_pulse::exit()
+{
+ for(const auto &si : m_streams) {
+ pa_stream_disconnect(si.second.m_stream);
+ pa_stream_unref(si.second.m_stream);
+ }
+
+ pa_context_unref(m_context);
+ pa_threaded_mainloop_free(m_mainloop);
+}
+
+#else
+ MODULE_NOT_SUPPORTED(sound_pulse, OSD_SOUND_PROVIDER, "pulse")
+#endif
+
+MODULE_DEFINITION(SOUND_PULSEAUDIO, sound_pulse)
diff --git a/src/osd/modules/sound/sdl_sound.cpp b/src/osd/modules/sound/sdl_sound.cpp
index 5ff8f2e6821..35c11eae86d 100644
--- a/src/osd/modules/sound/sdl_sound.cpp
+++ b/src/osd/modules/sound/sdl_sound.cpp
@@ -9,458 +9,231 @@
//============================================================
#include "sound_module.h"
+
#include "modules/osdmodule.h"
#if (defined(OSD_SDL) || defined(USE_SDL_SOUND))
+#include "modules/lib/osdobj_common.h"
+#include "osdcore.h"
+
// standard sdl header
#include <SDL2/SDL.h>
-// MAME headers
-#include "emu.h"
-#include "emuopts.h"
-
-#include "../../sdl/osdsdl.h"
-
#include <algorithm>
+#include <cmath>
#include <fstream>
#include <memory>
+#include <map>
-//============================================================
-// DEBUGGING
-//============================================================
-#define LOG_SOUND 0
+namespace osd {
-//============================================================
-// CLASS
-//============================================================
+namespace {
class sound_sdl : public osd_module, public sound_module
{
public:
-
- // number of samples per SDL callback
- static const int SDL_XFER_SAMPLES = 512;
-
sound_sdl() :
- osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module(),
- stream_in_initialized(0),
- attenuation(0), buf_locked(0), stream_buffer(nullptr), stream_buffer_size(0), buffer_underflows(0), buffer_overflows(0)
-{
- sdl_xfer_samples = SDL_XFER_SAMPLES;
+ osd_module(OSD_SOUND_PROVIDER, "sdl"), sound_module()
+ {
}
+
virtual ~sound_sdl() { }
- virtual int init(const osd_options &options) override;
+ virtual int init(osd_interface &osd, const osd_options &options) override;
virtual void exit() override;
- // sound_module
+ virtual bool external_per_channel_volume() override { return false; }
+ virtual bool split_streams_per_source() override { return false; }
- virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) override;
- virtual void set_mastervolume(int attenuation) override;
+ virtual uint32_t get_generation() override;
+ virtual osd::audio_info get_information() override;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) override;
+ virtual void stream_close(uint32_t id) override;
+ virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) override;
private:
- class ring_buffer
- {
- public:
- ring_buffer(size_t size);
-
- size_t data_size() const { return (tail - head + buffer_size) % buffer_size; }
- size_t free_size() const { return (head - tail - 1 + buffer_size) % buffer_size; }
- int append(const void *data, size_t size);
- int pop(void *data, size_t size);
-
- private:
- std::unique_ptr<int8_t []> const buffer;
- size_t const buffer_size;
- int head = 0, tail = 0;
+ struct device_info {
+ std::string m_name;
+ int m_freq;
+ uint8_t m_channels;
+ device_info(const char *name, int freq, uint8_t channels) : m_name(name), m_freq(freq), m_channels(channels) {}
};
- static void sdl_callback(void *userdata, Uint8 *stream, int len);
-
- void lock_buffer();
- void unlock_buffer();
- void attenuate(int16_t *data, int bytes);
- void copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy);
- int sdl_create_buffers();
- void sdl_destroy_buffers();
-
- int sdl_xfer_samples;
- int stream_in_initialized;
- int attenuation;
+ struct stream_info {
+ uint32_t m_id;
+ SDL_AudioDeviceID m_sdl_id;
+ abuffer m_buffer;
+ stream_info(uint32_t id, uint8_t channels) : m_id(id), m_sdl_id(0), m_buffer(channels) {}
+ };
- int buf_locked;
- std::unique_ptr<ring_buffer> stream_buffer;
- uint32_t stream_buffer_size;
+ std::vector<device_info> m_devices;
+ uint32_t m_default_sink;
+ uint32_t m_stream_next_id;
+ std::map<uint32_t, std::unique_ptr<stream_info>> m_streams;
- // diagnostics
- int buffer_underflows;
- int buffer_overflows;
- std::unique_ptr<std::ofstream> sound_log;
+ static void sink_callback(void *userdata, Uint8 *stream, int len);
};
-
-//============================================================
-// PARAMETERS
-//============================================================
-
-// maximum audio latency
-#define MAX_AUDIO_LATENCY 5
-
-//============================================================
-// ring_buffer - constructor
-//============================================================
-
-sound_sdl::ring_buffer::ring_buffer(size_t size)
- : buffer(std::make_unique<int8_t []>(size + 1)), buffer_size(size + 1)
-{
- // A size+1 bytes buffer is allocated because it can never be full.
- // Otherwise the case head == tail couldn't be distinguished between a
- // full buffer and an empty buffer.
- std::fill_n(buffer.get(), size + 1, 0);
-}
-
//============================================================
-// ring_buffer::append
+// sound_sdl::init
//============================================================
-int sound_sdl::ring_buffer::append(const void *data, size_t size)
+int sound_sdl::init(osd_interface &osd, const osd_options &options)
{
- if (free_size() < size)
- return -1;
+ m_stream_next_id = 1;
- int8_t const *const data8 = reinterpret_cast<int8_t const *>(data);
- size_t sz = buffer_size - tail;
- if (size <= sz)
- sz = size;
- else
- std::copy_n(&data8[sz], size - sz, &buffer[0]);
-
- std::copy_n(data8, sz, &buffer[tail]);
- tail = (tail + size) % buffer_size;
-
- return 0;
-}
-
-//============================================================
-// ring_buffer::pop
-//============================================================
-
-int sound_sdl::ring_buffer::pop(void *data, size_t size)
-{
- if (data_size() < size)
+ if(SDL_InitSubSystem(SDL_INIT_AUDIO)) {
+ osd_printf_error("Could not initialize SDL %s\n", SDL_GetError());
return -1;
-
- int8_t *const data8 = reinterpret_cast<int8_t *>(data);
- size_t sz = buffer_size - head;
- if (size <= sz)
- sz = size;
- else
- {
- std::copy_n(&buffer[0], size - sz, &data8[sz]);
- std::fill_n(&buffer[0], size - sz, 0);
}
- std::copy_n(&buffer[head], sz, data8);
- std::fill_n(&buffer[head], sz, 0);
- head = (head + size) % buffer_size;
-
+ osd_printf_verbose("Audio: Start initialization\n");
+ char const *const audio_driver = SDL_GetCurrentAudioDriver();
+ osd_printf_verbose("Audio: Driver is %s\n", audio_driver ? audio_driver : "not initialized");
+
+ // Capture is not implemented in SDL2, and the enumeration
+ // interface is different in SDL3
+ int dev_count = SDL_GetNumAudioDevices(0);
+ for(int i=0; i != dev_count; i++) {
+ SDL_AudioSpec spec;
+ const char *name = SDL_GetAudioDeviceName(i, 0);
+ int err = SDL_GetAudioDeviceSpec(i, 0, &spec);
+ if(!err)
+ m_devices.emplace_back(name, spec.freq, spec.channels);
+ }
+ char *def_name;
+ SDL_AudioSpec def_spec;
+ if(!SDL_GetDefaultAudioInfo(&def_name, &def_spec, 0)) {
+ uint32_t idx;
+ for(idx = 0; idx != m_devices.size() && m_devices[idx].m_name != def_name; idx++);
+ if(idx == m_devices.size())
+ m_devices.emplace_back(def_name, def_spec.freq, def_spec.channels);
+ m_default_sink = idx+1;
+ SDL_free(def_name);
+ } else
+ m_default_sink = 0;
return 0;
}
-//============================================================
-// sound_sdl - destructor
-//============================================================
-
-//============================================================
-// lock_buffer
-//============================================================
-void sound_sdl::lock_buffer()
+void sound_sdl::exit()
{
- if (!buf_locked)
- SDL_LockAudio();
- buf_locked++;
-
- if (LOG_SOUND)
- *sound_log << "locking\n";
+ SDL_QuitSubSystem(SDL_INIT_AUDIO);
}
-//============================================================
-// unlock_buffer
-//============================================================
-void sound_sdl::unlock_buffer()
+uint32_t sound_sdl::get_generation()
{
- buf_locked--;
- if (!buf_locked)
- SDL_UnlockAudio();
-
- if (LOG_SOUND)
- *sound_log << "unlocking\n";
-
+ // sdl2 is not dynamic w.r.t devices
+ return 1;
}
-//============================================================
-// Apply attenuation
-//============================================================
+osd::audio_info sound_sdl::get_information()
+{
+ enum { FL, FR, FC, LFE, BL, BR, BC, SL, SR };
+ static const char *const posname[9] = { "FL", "FR", "FC", "LFE", "BL", "BR", "BC", "SL", "SR" };
+
+ static std::array<double, 3> pos3d[9] = {
+ { -0.2, 0.0, 1.0 },
+ { 0.2, 0.0, 1.0 },
+ { 0.0, 0.0, 1.0 },
+ { 0.0, -0.5, 1.0 },
+ { -0.2, 0.0, -0.5 },
+ { 0.2, 0.0, -0.5 },
+ { 0.0, 0.0, -0.5 },
+ { -0.2, 0.0, 0.0 },
+ { 0.2, 0.0, 0.0 },
+ };
+
+ static const uint32_t positions[8][8] = {
+ { FC },
+ { FL, FR },
+ { FL, FR, LFE },
+ { FL, FR, BL, BR },
+ { FL, FR, LFE, BL, BR },
+ { FL, FR, FC, LFE, BL, BR },
+ { FL, FR, FC, LFE, BC, SL, SR },
+ { FL, FR, FC, LFE, BL, BR, SL, SR }
+ };
-void sound_sdl::attenuate(int16_t *data, int bytes_to_copy)
-{
- int level = (int) (pow(10.0, (double) attenuation / 20.0) * 128.0);
- int count = bytes_to_copy / sizeof(*data);
- while (count > 0)
- {
- *data = (*data * level) >> 7; /* / 128 */
- data++;
- count--;
+ osd::audio_info result;
+ result.m_nodes.resize(m_devices.size());
+ result.m_default_sink = m_default_sink;
+ result.m_default_source = 0;
+ result.m_generation = 1;
+ for(uint32_t node = 0; node != m_devices.size(); node++) {
+ result.m_nodes[node].m_name = m_devices[node].m_name;
+ result.m_nodes[node].m_id = node + 1;
+ uint32_t freq = m_devices[node].m_freq;
+ result.m_nodes[node].m_rate = audio_rate_range{ freq, freq, freq };
+ result.m_nodes[node].m_sinks = m_devices[node].m_channels;
+ for(uint32_t port = 0; port != m_devices[node].m_channels; port++) {
+ uint32_t pos = positions[m_devices[node].m_channels-1][port];
+ result.m_nodes[node].m_port_names.push_back(posname[pos]);
+ result.m_nodes[node].m_port_positions.push_back(pos3d[pos]);
+ }
}
+ return result;
}
-//============================================================
-// copy_sample_data
-//============================================================
-
-void sound_sdl::copy_sample_data(bool is_throttled, const int16_t *data, int bytes_to_copy)
+uint32_t sound_sdl::stream_sink_open(uint32_t node, std::string name, uint32_t rate)
{
- lock_buffer();
- int const err = stream_buffer->append(data, bytes_to_copy);
- unlock_buffer();
-
- if (LOG_SOUND && err)
- *sound_log << "Late detection of overflow. This shouldn't happen.\n";
-}
+ device_info &dev = m_devices[node-1];
+ std::unique_ptr<stream_info> stream = std::make_unique<stream_info>(m_stream_next_id ++, dev.m_channels);
+ SDL_AudioSpec dspec, ospec;
+ dspec.freq = rate;
+ dspec.format = AUDIO_S16SYS;
+ dspec.channels = dev.m_channels;
+ dspec.samples = 512;
+ dspec.callback = sink_callback;
+ dspec.userdata = stream.get();
-//============================================================
-// update_audio_stream
-//============================================================
-
-void sound_sdl::update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame)
-{
- // if nothing to do, don't do it
- if (sample_rate() == 0 || !stream_buffer)
- return;
-
-
- if (!stream_in_initialized)
- {
- // Fill in some zeros to prevent an initial buffer underflow
- int8_t zero = 0;
- size_t zsize = stream_buffer->free_size() / 2;
- while (zsize--)
- stream_buffer->append(&zero, 1);
-
- // start playing
- SDL_PauseAudio(0);
- stream_in_initialized = 1;
- }
-
- size_t bytes_this_frame = samples_this_frame * sizeof(*buffer) * 2;
- size_t free_size = stream_buffer->free_size();
- size_t data_size = stream_buffer->data_size();
-
- if (stream_buffer->free_size() < bytes_this_frame) {
- if (LOG_SOUND)
- util::stream_format(*sound_log, "Overflow: DS=%u FS=%u BTF=%u\n", data_size, free_size, bytes_this_frame);
- buffer_overflows++;
- return;
- }
-
- copy_sample_data(is_throttled, buffer, bytes_this_frame);
-
- size_t nfree_size = stream_buffer->free_size();
- size_t ndata_size = stream_buffer->data_size();
- if (LOG_SOUND)
- util::stream_format(*sound_log, "Appended data: DS=%u(%u) FS=%u(%u) BTF=%u\n", data_size, ndata_size, free_size, nfree_size, bytes_this_frame);
-}
-
-
-
-//============================================================
-// set_mastervolume
-//============================================================
-
-void sound_sdl::set_mastervolume(int _attenuation)
-{
- // clamp the attenuation to 0-32 range
- attenuation = std::max(std::min(_attenuation, 0), -32);
-
- if (stream_in_initialized)
- {
- if (attenuation == -32)
- SDL_PauseAudio(1);
- else
- SDL_PauseAudio(0);
- }
+ stream->m_sdl_id = SDL_OpenAudioDevice(dev.m_name.c_str(), 0, &dspec, &ospec, 0);
+ if(!stream->m_sdl_id)
+ return 0;
+ SDL_PauseAudioDevice(stream->m_sdl_id, 0);
+ uint32_t id = stream->m_id;
+ m_streams[stream->m_id] = std::move(stream);
+ return id;
}
-//============================================================
-// sdl_callback
-//============================================================
-void sound_sdl::sdl_callback(void *userdata, Uint8 *stream, int len)
+void sound_sdl::stream_close(uint32_t id)
{
- sound_sdl *thiz = reinterpret_cast<sound_sdl *>(userdata);
- size_t const free_size = thiz->stream_buffer->free_size();
- size_t const data_size = thiz->stream_buffer->data_size();
-
- if (data_size < len)
- {
- thiz->buffer_underflows++;
- if (LOG_SOUND)
- util::stream_format(*thiz->sound_log, "Underflow at sdl_callback: DS=%u FS=%u Len=%d\n", data_size, free_size, len);
-
- // Maybe read whatever is left in the stream_buffer anyway?
- memset(stream, 0, len);
+ auto si = m_streams.find(id);
+ if(si == m_streams.end())
return;
- }
-
- int err = thiz->stream_buffer->pop((void *)stream, len);
- if (LOG_SOUND && err)
- *thiz->sound_log << "Late detection of underflow. This shouldn't happen.\n";
-
- thiz->attenuate((int16_t *)stream, len);
-
- if (LOG_SOUND)
- util::stream_format(*thiz->sound_log, "callback: xfer DS=%u FS=%u Len=%d\n", data_size, free_size, len);
-}
-
-
-//============================================================
-// sound_sdl::init
-//============================================================
-
-int sound_sdl::init(const osd_options &options)
-{
- int n_channels = 2;
- int audio_latency;
- SDL_AudioSpec aspec, obtained;
- char audio_driver[16] = "";
-
- if (LOG_SOUND)
- sound_log = std::make_unique<std::ofstream>(SDLMAME_SOUND_LOG);
-
- // skip if sound disabled
- if (sample_rate() != 0)
- {
- if (SDL_InitSubSystem(SDL_INIT_AUDIO))
- {
- osd_printf_error("Could not initialize SDL %s\n", SDL_GetError());
- return -1;
- }
-
- osd_printf_verbose("Audio: Start initialization\n");
- strncpy(audio_driver, SDL_GetCurrentAudioDriver(), sizeof(audio_driver));
- osd_printf_verbose("Audio: Driver is %s\n", audio_driver);
-
- sdl_xfer_samples = SDL_XFER_SAMPLES;
- stream_in_initialized = 0;
-
- // set up the audio specs
- aspec.freq = sample_rate();
- aspec.format = AUDIO_S16SYS; // keep endian independent
- aspec.channels = n_channels;
- aspec.samples = sdl_xfer_samples;
- aspec.callback = sdl_callback;
- aspec.userdata = this;
-
- if (SDL_OpenAudio(&aspec, &obtained) < 0)
- goto cant_start_audio;
-
- osd_printf_verbose("Audio: frequency: %d, channels: %d, samples: %d\n",
- obtained.freq, obtained.channels, obtained.samples);
-
- sdl_xfer_samples = obtained.samples;
-
- // pin audio latency
- audio_latency = std::max(std::min(m_audio_latency, MAX_AUDIO_LATENCY), 1);
-
- // compute the buffer sizes
- stream_buffer_size = (sample_rate() * 2 * sizeof(int16_t) * (2 + audio_latency)) / 30;
- stream_buffer_size = (stream_buffer_size / 1024) * 1024;
- if (stream_buffer_size < 1024)
- stream_buffer_size = 1024;
-
- // create the buffers
- if (sdl_create_buffers())
- goto cant_create_buffers;
-
- // set the startup volume
- set_mastervolume(attenuation);
- osd_printf_verbose("Audio: End initialization\n");
- return 0;
-
- // error handling
- cant_create_buffers:
- cant_start_audio:
- osd_printf_verbose("Audio: Initialization failed. SDL error: %s\n", SDL_GetError());
-
- return -1;
- }
-
- return 0;
+ SDL_CloseAudioDevice(si->second->m_sdl_id);
+ m_streams.erase(si);
}
-
-
-//============================================================
-// sdl_kill
-//============================================================
-
-void sound_sdl::exit()
+void sound_sdl::stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame)
{
- // if nothing to do, don't do it
- if (sample_rate() == 0)
+ auto si = m_streams.find(id);
+ if(si == m_streams.end())
return;
-
- osd_printf_verbose("sdl_kill: closing audio\n");
- SDL_CloseAudio();
-
- SDL_QuitSubSystem(SDL_INIT_AUDIO);
-
- // kill the buffers
- sdl_destroy_buffers();
-
- // print out over/underflow stats
- if (buffer_overflows || buffer_underflows)
- osd_printf_verbose("Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
-
- if (LOG_SOUND)
- {
- util::stream_format(*sound_log, "Sound buffer: overflows=%d underflows=%d\n", buffer_overflows, buffer_underflows);
- sound_log.reset();
- }
+ stream_info *stream = si->second.get();
+ SDL_LockAudioDevice(stream->m_sdl_id);
+ stream->m_buffer.push(buffer, samples_this_frame);
+ SDL_UnlockAudioDevice(stream->m_sdl_id);
}
-
-
-//============================================================
-// dsound_create_buffers
-//============================================================
-
-int sound_sdl::sdl_create_buffers()
+void sound_sdl::sink_callback(void *userdata, uint8_t *data, int len)
{
- osd_printf_verbose("sdl_create_buffers: creating stream buffer of %u bytes\n", stream_buffer_size);
-
- stream_buffer = std::make_unique<ring_buffer>(stream_buffer_size);
- buf_locked = 0;
- return 0;
+ stream_info *stream = reinterpret_cast<stream_info *>(userdata);
+ stream->m_buffer.get((int16_t *)data, len / 2 / stream->m_buffer.channels());
}
-//============================================================
-// sdl_destroy_buffers
-//============================================================
+} // anonymous namespace
+
+} // namespace osd
-void sound_sdl::sdl_destroy_buffers()
-{
- // release the buffer
- stream_buffer.reset();
-}
+#else // (defined(OSD_SDL) || defined(USE_SDL_SOUND))
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl") } }
-#else /* SDLMAME_UNIX */
- MODULE_NOT_SUPPORTED(sound_sdl, OSD_SOUND_PROVIDER, "sdl")
#endif
-MODULE_DEFINITION(SOUND_SDL, sound_sdl)
+MODULE_DEFINITION(SOUND_SDL, osd::sound_sdl)
diff --git a/src/osd/modules/sound/sound_module.cpp b/src/osd/modules/sound/sound_module.cpp
new file mode 100644
index 00000000000..5dc13eb3246
--- /dev/null
+++ b/src/osd/modules/sound/sound_module.cpp
@@ -0,0 +1,61 @@
+// license:BSD-3-Clause
+// copyright-holders:O. Galibert
+
+
+#include "emu.h"
+#include "sound_module.h"
+
+void sound_module::abuffer::get(int16_t *data, uint32_t samples)
+{
+ uint32_t pos = 0;
+ while(pos != samples) {
+ if(m_buffers.empty()) {
+ while(pos != samples) {
+ memcpy(data, m_last_sample.data(), m_channels*2);
+ data += m_channels;
+ pos ++;
+ }
+ break;
+ }
+
+ auto &buf = m_buffers.front();
+ if(buf.m_data.empty()) {
+ m_buffers.erase(m_buffers.begin());
+ continue;
+ }
+
+ uint32_t avail = buf.m_data.size() / m_channels - buf.m_cpos;
+ if(avail > samples - pos) {
+ avail = samples - pos;
+ memcpy(data, buf.m_data.data() + buf.m_cpos * m_channels, avail * 2 * m_channels);
+ buf.m_cpos += avail;
+ break;
+ }
+
+ memcpy(data, buf.m_data.data() + buf.m_cpos * m_channels, avail * 2 * m_channels);
+ m_buffers.erase(m_buffers.begin());
+ pos += avail;
+ data += avail * m_channels;
+ }
+}
+
+void sound_module::abuffer::push(const int16_t *data, uint32_t samples)
+{
+ m_buffers.resize(m_buffers.size() + 1);
+ auto &buf = m_buffers.back();
+ buf.m_cpos = 0;
+ buf.m_data.resize(samples * m_channels);
+ memcpy(buf.m_data.data(), data, samples * 2 * m_channels);
+ memcpy(m_last_sample.data(), data + (samples-1) * m_channels, 2 * m_channels);
+
+ if(m_buffers.size() > 10)
+ // If there are way too many buffers, drop some so only 10 are left (roughly 0.2s)
+ m_buffers.erase(m_buffers.begin(), m_buffers.begin() + m_buffers.size() - 10);
+
+ else if(m_buffers.size() >= 5)
+ // If there are too many buffers, remove five samples per buffer
+ // to slowly resync to reduce latency (4 seconds to
+ // compensate one buffer, roughly)
+ buf.m_cpos = 5;
+}
+
diff --git a/src/osd/modules/sound/sound_module.h b/src/osd/modules/sound/sound_module.h
index d9f597ad0d9..2498db84107 100644
--- a/src/osd/modules/sound/sound_module.h
+++ b/src/osd/modules/sound/sound_module.h
@@ -4,33 +4,76 @@
* sound_module.h
*
*/
+#ifndef MAME_OSD_SOUND_SOUND_MODULE_H
+#define MAME_OSD_SOUND_SOUND_MODULE_H
-#ifndef SOUND_MODULE_H_
-#define SOUND_MODULE_H_
+#pragma once
-#include "osdepend.h"
-#include "modules/osdmodule.h"
+#include <osdepend.h>
-//============================================================
-// CONSTANTS
-//============================================================
+#include <cstdint>
+#include <array>
+#include <vector>
+#include <string>
#define OSD_SOUND_PROVIDER "sound"
class sound_module
{
public:
- sound_module() : m_sample_rate(0), m_audio_latency(1) { }
+ virtual ~sound_module() = default;
- virtual ~sound_module() { }
+ virtual uint32_t get_generation() { return 1; }
+ virtual osd::audio_info get_information() {
+ osd::audio_info result;
+ result.m_generation = 1;
+ result.m_default_sink = 1;
+ result.m_default_source = 0;
+ result.m_nodes.resize(1);
+ result.m_nodes[0].m_name = "";
+ result.m_nodes[0].m_id = 1;
+ result.m_nodes[0].m_rate.m_default_rate = 0; // Magic value meaning "use configured sample rate"
+ result.m_nodes[0].m_rate.m_min_rate = 0;
+ result.m_nodes[0].m_rate.m_max_rate = 0;
+ result.m_nodes[0].m_sinks = 2;
+ result.m_nodes[0].m_sources = 0;
+ result.m_nodes[0].m_port_names.push_back("L");
+ result.m_nodes[0].m_port_names.push_back("R");
+ result.m_nodes[0].m_port_positions.emplace_back(std::array<double, 3>({ -0.2, 0.0, 1.0 }));
+ result.m_nodes[0].m_port_positions.emplace_back(std::array<double, 3>({ 0.2, 0.0, 1.0 }));
+ result.m_streams.resize(1);
+ result.m_streams[0].m_id = 1;
+ result.m_streams[0].m_node = 1;
+ return result;
+ }
+ virtual bool external_per_channel_volume() { return false; }
+ virtual bool split_streams_per_source() { return false; }
- virtual void update_audio_stream(bool is_throttled, const int16_t *buffer, int samples_this_frame) = 0;
- virtual void set_mastervolume(int attenuation) = 0;
+ virtual uint32_t stream_sink_open(uint32_t node, std::string name, uint32_t rate) { return 1; }
+ virtual uint32_t stream_source_open(uint32_t node, std::string name, uint32_t rate) { return 0; }
+ virtual void stream_set_volumes(uint32_t id, const std::vector<float> &db) {}
+ virtual void stream_close(uint32_t id) {}
+ virtual void stream_sink_update(uint32_t id, const int16_t *buffer, int samples_this_frame) {}
+ virtual void stream_source_update(uint32_t id, int16_t *buffer, int samples_this_frame) {}
- int sample_rate() const { return m_sample_rate; }
+protected:
+ class abuffer {
+ public:
+ abuffer(uint32_t channels) : m_channels(channels), m_last_sample(channels, 0) {}
+ void get(int16_t *data, uint32_t samples);
+ void push(const int16_t *data, uint32_t samples);
+ uint32_t channels() const { return m_channels; }
- int m_sample_rate;
- int m_audio_latency;
+ private:
+ struct buffer {
+ uint32_t m_cpos;
+ std::vector<int16_t> m_data;
+ };
+
+ uint32_t m_channels;
+ std::vector<int16_t> m_last_sample;
+ std::vector<buffer> m_buffers;
+ };
};
-#endif /* FONT_MODULE_H_ */
+#endif // MAME_OSD_SOUND_SOUND_MODULE_H
diff --git a/src/osd/modules/sound/xaudio2_sound.cpp b/src/osd/modules/sound/xaudio2_sound.cpp
index 1d9e1036f9a..a865f2527fe 100644
--- a/src/osd/modules/sound/xaudio2_sound.cpp
+++ b/src/osd/modules/sound/xaudio2_sound.cpp
@@ -7,9 +7,24 @@
//====================================================================
#include "sound_module.h"
+
#include "modules/osdmodule.h"
-#if defined(OSD_WINDOWS) || defined(OSD_UWP)
+#if defined(OSD_WINDOWS) | defined(SDLMAME_WIN32)
+
+// OSD headers
+#include "modules/lib/osdlib.h"
+#include "modules/lib/osdobj_common.h"
+#include "osdcore.h"
+#include "osdepend.h"
+#include "windows/winutil.h"
+
+// stdlib includes
+#include <algorithm>
+#include <chrono>
+#include <mutex>
+#include <queue>
+#include <thread>
// standard windows headers
#include <windows.h>
@@ -19,21 +34,10 @@
// XAudio2 include
#include <xaudio2.h>
-#undef interface
-// stdlib includes
-#include <mutex>
-#include <thread>
-#include <queue>
-#include <chrono>
+namespace osd {
-// MAME headers
-#include "emu.h"
-#include "osdepend.h"
-
-#include "winutil.h"
-
-#include "modules/lib/osdlib.h"
+namespace {
//============================================================
// Constants
@@ -180,29 +184,6 @@ public:
// The main class for the XAudio2 sound module implementation
class sound_xaudio2 : public osd_module, public sound_module, public IXAudio2VoiceCallback
{
-private:
- Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2;
- mastering_voice_ptr m_masterVoice;
- src_voice_ptr m_sourceVoice;
- DWORD m_sample_bytes;
- std::unique_ptr<BYTE[]> m_buffer;
- DWORD m_buffer_size;
- DWORD m_buffer_count;
- DWORD m_writepos;
- std::mutex m_buffer_lock;
- HANDLE m_hEventBufferCompleted;
- HANDLE m_hEventDataAvailable;
- HANDLE m_hEventExiting;
- std::thread m_audioThread;
- std::queue<xaudio2_buffer> m_queue;
- std::unique_ptr<bufferpool> m_buffer_pool;
- uint32_t m_overflows;
- uint32_t m_underflows;
- BOOL m_in_underflow;
- BOOL m_initialized;
- OSD_DYNAMIC_API(xaudio2, "XAudio2_9.dll", "XAudio2_8.dll");
- OSD_DYNAMIC_API_FN(xaudio2, HRESULT, WINAPI, XAudio2Create, IXAudio2 **, uint32_t, XAUDIO2_PROCESSOR);
-
public:
sound_xaudio2() :
osd_module(OSD_SOUND_PROVIDER, "xaudio2"),
@@ -210,6 +191,8 @@ public:
m_xAudio2(nullptr),
m_masterVoice(nullptr),
m_sourceVoice(nullptr),
+ m_sample_rate(0),
+ m_audio_latency(0),
m_sample_bytes(0),
m_buffer(nullptr),
m_buffer_size(0),
@@ -226,26 +209,23 @@ public:
{
}
- virtual ~sound_xaudio2() { }
-
bool probe() override;
- int init(osd_options const &options) override;
+ int init(osd_interface &osd, osd_options const &options) override;
void exit() override;
// sound_module
- void update_audio_stream(bool is_throttled, int16_t const *buffer, int samples_this_frame) override;
- void set_mastervolume(int attenuation) override;
+ void stream_sink_update(uint32_t, int16_t const *buffer, int samples_this_frame) override;
+private:
// Xaudio callbacks
- void STDAPICALLTYPE OnVoiceProcessingPassStart(uint32_t bytes_required) override;
- void STDAPICALLTYPE OnVoiceProcessingPassEnd() override {}
- void STDAPICALLTYPE OnStreamEnd() override {}
- void STDAPICALLTYPE OnBufferStart(void* pBufferContext) override {}
- void STDAPICALLTYPE OnLoopEnd(void* pBufferContext) override {}
- void STDAPICALLTYPE OnVoiceError(void* pBufferContext, HRESULT error) override {}
- void STDAPICALLTYPE OnBufferEnd(void *pBufferContext) override;
+ void STDAPICALLTYPE OnVoiceProcessingPassStart(uint32_t bytes_required) noexcept override;
+ void STDAPICALLTYPE OnVoiceProcessingPassEnd() noexcept override {}
+ void STDAPICALLTYPE OnStreamEnd() noexcept override {}
+ void STDAPICALLTYPE OnBufferStart(void* pBufferContext) noexcept override {}
+ void STDAPICALLTYPE OnLoopEnd(void* pBufferContext) noexcept override {}
+ void STDAPICALLTYPE OnVoiceError(void* pBufferContext, HRESULT error) noexcept override {}
+ void STDAPICALLTYPE OnBufferEnd(void *pBufferContext) noexcept override;
-private:
void create_buffers(const WAVEFORMATEX &format);
HRESULT create_voices(const WAVEFORMATEX &format);
void process_audio();
@@ -253,6 +233,31 @@ private:
void submit_needed();
void roll_buffer();
BOOL submit_next_queued();
+
+ Microsoft::WRL::ComPtr<IXAudio2> m_xAudio2;
+ mastering_voice_ptr m_masterVoice;
+ src_voice_ptr m_sourceVoice;
+ int m_sample_rate;
+ int m_audio_latency;
+ DWORD m_sample_bytes;
+ std::unique_ptr<BYTE[]> m_buffer;
+ DWORD m_buffer_size;
+ DWORD m_buffer_count;
+ DWORD m_writepos;
+ std::mutex m_buffer_lock;
+ HANDLE m_hEventBufferCompleted;
+ HANDLE m_hEventDataAvailable;
+ HANDLE m_hEventExiting;
+ std::thread m_audioThread;
+ std::queue<xaudio2_buffer> m_queue;
+ std::unique_ptr<bufferpool> m_buffer_pool;
+ uint32_t m_overflows;
+ uint32_t m_underflows;
+ BOOL m_in_underflow;
+ BOOL m_initialized;
+
+ OSD_DYNAMIC_API(xaudio2, "XAudio2_9.dll", "XAudio2_8.dll");
+ OSD_DYNAMIC_API_FN(xaudio2, HRESULT, WINAPI, XAudio2Create, IXAudio2 **, uint32_t, XAUDIO2_PROCESSOR);
};
//============================================================
@@ -268,14 +273,9 @@ bool sound_xaudio2::probe()
// init
//============================================================
-int sound_xaudio2::init(osd_options const &options)
+int sound_xaudio2::init(osd_interface &osd, osd_options const &options)
{
- HRESULT result;
- WAVEFORMATEX format = {0};
- auto init_start = std::chrono::system_clock::now();
- std::chrono::milliseconds init_time;
-
- CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ auto const init_start = std::chrono::system_clock::now();
// Make sure our XAudio2Create entrypoint is bound
if (!OSD_DYNAMIC_API_TEST(XAudio2Create))
@@ -284,6 +284,13 @@ int sound_xaudio2::init(osd_options const &options)
return 1;
}
+ HRESULT result;
+ std::chrono::milliseconds init_time;
+ WAVEFORMATEX format = { 0 };
+
+ m_sample_rate = options.sample_rate();
+ m_audio_latency = options.audio_latency();
+
// Create the IXAudio2 object
HR_GOERR(OSD_DYNAMIC_CALL(XAudio2Create, m_xAudio2.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR));
@@ -291,7 +298,7 @@ int sound_xaudio2::init(osd_options const &options)
format.wBitsPerSample = 16;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 2;
- format.nSamplesPerSec = sample_rate();
+ format.nSamplesPerSec = m_sample_rate;
format.nBlockAlign = format.wBitsPerSample * format.nChannels / 8;
format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
@@ -310,10 +317,10 @@ int sound_xaudio2::init(osd_options const &options)
HR_GOERR(m_sourceVoice->Start());
// Start the thread listening
- m_audioThread = std::thread([](sound_xaudio2* self) { self->process_audio(); }, this);
+ m_audioThread = std::thread([] (sound_xaudio2 *self) { self->process_audio(); }, this);
init_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - init_start);
- osd_printf_verbose("Sound: XAudio2 initialized. %d ms.\n", static_cast<int>(init_time.count()));
+ osd_printf_verbose("Sound: XAudio2 initialized. %d ms.\n", init_time.count());
m_initialized = TRUE;
return 0;
@@ -368,15 +375,15 @@ void sound_xaudio2::exit()
}
//============================================================
-// update_audio_stream
+// stream_sink_update
//============================================================
-void sound_xaudio2::update_audio_stream(
- bool is_throttled,
+void sound_xaudio2::stream_sink_update(
+ uint32_t,
int16_t const *buffer,
int samples_this_frame)
{
- if (!m_initialized || sample_rate() == 0 || !m_buffer)
+ if (!m_initialized || m_sample_rate == 0 || !m_buffer)
return;
uint32_t const bytes_this_frame = samples_this_frame * m_sample_bytes;
@@ -406,37 +413,11 @@ void sound_xaudio2::update_audio_stream(
}
//============================================================
-// set_mastervolume
-//============================================================
-
-void sound_xaudio2::set_mastervolume(int attenuation)
-{
- if (!m_initialized)
- return;
-
- assert(m_sourceVoice);
-
- HRESULT result;
-
- // clamp the attenuation to 0-32 range
- attenuation = std::max(std::min(attenuation, 0), -32);
-
- // Ranges from 1.0 to XAUDIO2_MAX_VOLUME_LEVEL indicate additional gain
- // Ranges from 0 to 1.0 indicate a reduced volume level
- // 0 indicates silence
- // We only support a reduction from 1.0, so we generate values in the range 0.0 to 1.0
- float scaledVolume = (32.0f + attenuation) / 32.0f;
-
- // set the master volume
- HR_RETV(m_sourceVoice->SetVolume(scaledVolume));
-}
-
-//============================================================
// IXAudio2VoiceCallback::OnBufferEnd
//============================================================
// The XAudio2 voice callback triggered when a buffer finishes playing
-void sound_xaudio2::OnBufferEnd(void *pBufferContext)
+void sound_xaudio2::OnBufferEnd(void *pBufferContext) noexcept
{
BYTE* completed_buffer = static_cast<BYTE*>(pBufferContext);
if (completed_buffer != nullptr)
@@ -453,7 +434,7 @@ void sound_xaudio2::OnBufferEnd(void *pBufferContext)
//============================================================
// The XAudio2 voice callback triggered on every pass
-void sound_xaudio2::OnVoiceProcessingPassStart(uint32_t bytes_required)
+void sound_xaudio2::OnVoiceProcessingPassStart(uint32_t bytes_required) noexcept
{
if (bytes_required == 0)
{
@@ -479,7 +460,8 @@ void sound_xaudio2::create_buffers(const WAVEFORMATEX &format)
{
// Compute the buffer size
// buffer size is equal to the bytes we need to hold in memory per X tenths of a second where X is audio_latency
- float audio_latency_in_seconds = m_audio_latency / 10.0f;
+ int audio_latency = std::max(m_audio_latency, 1);
+ float audio_latency_in_seconds = audio_latency / 10.0f;
uint32_t format_bytes_per_second = format.nSamplesPerSec * format.nBlockAlign;
uint32_t total_buffer_size = format_bytes_per_second * audio_latency_in_seconds * RESAMPLE_TOLERANCE;
@@ -526,7 +508,7 @@ HRESULT sound_xaudio2::create_voices(const WAVEFORMATEX &format)
m_xAudio2->CreateMasteringVoice(
&temp_master_voice,
format.nChannels,
- sample_rate()));
+ m_sample_rate));
m_masterVoice = mastering_voice_ptr(temp_master_voice);
@@ -693,9 +675,15 @@ void sound_xaudio2::roll_buffer()
}
}
+} // anonymous namespace
+
+} // namespace osd
+
#else
-MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2")
+
+namespace osd { namespace { MODULE_NOT_SUPPORTED(sound_xaudio2, OSD_SOUND_PROVIDER, "xaudio2") } }
+
#endif
-MODULE_DEFINITION(SOUND_XAUDIO2, sound_xaudio2)
+MODULE_DEFINITION(SOUND_XAUDIO2, osd::sound_xaudio2)