aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/src/openmpt
diff options
context:
space:
mode:
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/src/openmpt')
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp57
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp74
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp460
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp31
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp34
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp47
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp91
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp84
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp116
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp175
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp58
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp43
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp92
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp52
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp101
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp132
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp45
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp754
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp262
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp394
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp78
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp391
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp175
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp640
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp1405
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp251
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp457
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp209
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp262
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp83
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp572
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp72
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp504
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp167
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp1081
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp121
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp503
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp74
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp475
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp80
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp779
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp104
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp664
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp219
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp707
-rw-r--r--Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp105
46 files changed, 13280 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp
new file mode 100644
index 00000000..feab6041
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/all/BuildSettings.hpp
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+
+
+#include "mpt/base/detect_compiler.hpp"
+#include "mpt/base/detect_os.hpp"
+#include "mpt/base/detect_quirks.hpp"
+
+
+
+#if defined(MODPLUG_TRACKER) || defined(LIBOPENMPT_BUILD)
+#include "BuildSettings.h"
+#else
+
+
+
+#include "mpt/base/namespace.hpp"
+
+
+
+#ifndef OPENMPT_NAMESPACE
+#define OPENMPT_NAMESPACE OpenMPT
+#endif
+
+#ifndef OPENMPT_NAMESPACE_BEGIN
+#define OPENMPT_NAMESPACE_BEGIN \
+ namespace OPENMPT_NAMESPACE \
+ { \
+ inline namespace MPT_INLINE_NS \
+ {
+#endif
+#ifndef OPENMPT_NAMESPACE_END
+#define OPENMPT_NAMESPACE_END \
+ } \
+ }
+#endif
+
+
+
+#ifdef __cplusplus
+OPENMPT_NAMESPACE_BEGIN
+namespace mpt
+{
+#ifndef MPT_NO_NAMESPACE
+using namespace ::mpt;
+#endif
+} // namespace mpt
+OPENMPT_NAMESPACE_END
+#endif
+
+
+
+#endif
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp
new file mode 100644
index 00000000..eb177e0a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Endian.hpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/memory.hpp"
+#include "mpt/endian/floatingpoint.hpp"
+#include "mpt/endian/integer.hpp"
+#include "openmpt/base/Types.hpp"
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using int64le = mpt::packed<int64, mpt::LittleEndian_tag>;
+using int32le = mpt::packed<int32, mpt::LittleEndian_tag>;
+using int16le = mpt::packed<int16, mpt::LittleEndian_tag>;
+using int8le = mpt::packed<int8, mpt::LittleEndian_tag>;
+using uint64le = mpt::packed<uint64, mpt::LittleEndian_tag>;
+using uint32le = mpt::packed<uint32, mpt::LittleEndian_tag>;
+using uint16le = mpt::packed<uint16, mpt::LittleEndian_tag>;
+using uint8le = mpt::packed<uint8, mpt::LittleEndian_tag>;
+
+using int64be = mpt::packed<int64, mpt::BigEndian_tag>;
+using int32be = mpt::packed<int32, mpt::BigEndian_tag>;
+using int16be = mpt::packed<int16, mpt::BigEndian_tag>;
+using int8be = mpt::packed<int8, mpt::BigEndian_tag>;
+using uint64be = mpt::packed<uint64, mpt::BigEndian_tag>;
+using uint32be = mpt::packed<uint32, mpt::BigEndian_tag>;
+using uint16be = mpt::packed<uint16, mpt::BigEndian_tag>;
+using uint8be = mpt::packed<uint8, mpt::BigEndian_tag>;
+
+
+
+using IEEE754binary32LE = mpt::IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32LE;
+using IEEE754binary32BE = mpt::IEEE754binary_types<mpt::float_traits<float32>::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32BE;
+using IEEE754binary64LE = mpt::IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64LE;
+using IEEE754binary64BE = mpt::IEEE754binary_types<mpt::float_traits<float64>::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64BE;
+
+
+// unaligned
+
+using float32le = mpt::IEEE754binary32EmulatedLE;
+using float32be = mpt::IEEE754binary32EmulatedBE;
+using float64le = mpt::IEEE754binary64EmulatedLE;
+using float64be = mpt::IEEE754binary64EmulatedBE;
+
+
+// potentially aligned
+
+using float32le_fast = mpt::IEEE754binary32LE;
+using float32be_fast = mpt::IEEE754binary32BE;
+using float64le_fast = mpt::IEEE754binary64LE;
+using float64be_fast = mpt::IEEE754binary64BE;
+
+
+
+#define MPT_BINARY_STRUCT(type, size) \
+ constexpr bool declare_binary_safe(const type &) \
+ { \
+ return true; \
+ } \
+ static_assert(mpt::check_binary_size<type>(size)); \
+ /**/
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp
new file mode 100644
index 00000000..3f741e43
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/FlagSet.hpp
@@ -0,0 +1,460 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+/*
+ * Originally based on <http://stackoverflow.com/questions/4226960/type-safer-bitflags-in-c>.
+ * Rewritten to be standard-conforming.
+ */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Type-safe wrapper around an enum, that can represent all enum values and bitwise compositions thereof.
+// Conversions to and from plain integers as well as conversions to the base enum are always explicit.
+template <typename enum_t>
+// cppcheck-suppress copyCtorAndEqOperator
+class enum_value_type
+{
+public:
+ using enum_type = enum_t;
+ using value_type = enum_value_type;
+ using store_type = typename std::make_unsigned<enum_t>::type;
+
+private:
+ store_type bits;
+
+public:
+ MPT_CONSTEXPRINLINE enum_value_type() noexcept
+ : bits(0)
+ {
+ }
+ MPT_CONSTEXPRINLINE enum_value_type(const enum_value_type &x) noexcept
+ : bits(x.bits)
+ {
+ }
+ MPT_CONSTEXPRINLINE enum_value_type(enum_type x) noexcept
+ : bits(static_cast<store_type>(x))
+ {
+ }
+
+private:
+ explicit MPT_CONSTEXPRINLINE enum_value_type(store_type x) noexcept
+ : bits(x)
+ {
+ } // private in order to prevent accidental conversions. use from_bits.
+ MPT_CONSTEXPRINLINE operator store_type() const noexcept { return bits; } // private in order to prevent accidental conversions. use as_bits.
+public:
+ static MPT_CONSTEXPRINLINE enum_value_type from_bits(store_type bits) noexcept { return value_type(bits); }
+ MPT_CONSTEXPRINLINE enum_type as_enum() const noexcept { return static_cast<enum_t>(bits); }
+ MPT_CONSTEXPRINLINE store_type as_bits() const noexcept { return bits; }
+
+public:
+ MPT_CONSTEXPRINLINE operator bool() const noexcept { return bits != store_type(); }
+ MPT_CONSTEXPRINLINE bool operator!() const noexcept { return bits == store_type(); }
+
+ MPT_CONSTEXPRINLINE const enum_value_type operator~() const noexcept { return enum_value_type(~bits); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_value_type a, enum_value_type b) noexcept { return a.bits == b.bits; }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_value_type a, enum_value_type b) noexcept { return a.bits != b.bits; }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_value_type a, enum_t b) noexcept { return a == enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_value_type a, enum_t b) noexcept { return a != enum_value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) == b; }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) != b; }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits | b.bits); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits & b.bits); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_value_type a, enum_value_type b) noexcept { return enum_value_type(a.bits ^ b.bits); }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_value_type a, enum_t b) noexcept { return a | enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_value_type a, enum_t b) noexcept { return a & enum_value_type(b); }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_value_type a, enum_t b) noexcept { return a ^ enum_value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator|(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) | b; }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator&(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) & b; }
+ friend MPT_CONSTEXPRINLINE const enum_value_type operator^(enum_t a, enum_value_type b) noexcept { return enum_value_type(a) ^ b; }
+
+ MPT_CONSTEXPRINLINE enum_value_type &operator|=(enum_value_type b) noexcept
+ {
+ *this = *this | b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator&=(enum_value_type b) noexcept
+ {
+ *this = *this & b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator^=(enum_value_type b) noexcept
+ {
+ *this = *this ^ b;
+ return *this;
+ }
+
+ MPT_CONSTEXPRINLINE enum_value_type &operator|=(enum_t b) noexcept
+ {
+ *this = *this | b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator&=(enum_t b) noexcept
+ {
+ *this = *this & b;
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE enum_value_type &operator^=(enum_t b) noexcept
+ {
+ *this = *this ^ b;
+ return *this;
+ }
+};
+
+
+// Type-safe enum wrapper that allows type-safe bitwise testing.
+template <typename enum_t>
+class Enum
+{
+public:
+ using self_type = Enum;
+ using enum_type = enum_t;
+ using value_type = enum_value_type<enum_t>;
+ using store_type = typename value_type::store_type;
+
+private:
+ enum_type value;
+
+public:
+ explicit MPT_CONSTEXPRINLINE Enum(enum_type val) noexcept
+ : value(val)
+ {
+ }
+ MPT_CONSTEXPRINLINE operator enum_type() const noexcept { return value; }
+ MPT_CONSTEXPRINLINE Enum &operator=(enum_type val) noexcept
+ {
+ value = val;
+ return *this;
+ }
+
+public:
+ MPT_CONSTEXPRINLINE const value_type operator~() const { return ~value_type(value); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, value_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, value_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(value_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(value_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, enum_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, enum_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_type a, self_type b) noexcept { return value_type(a) == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_type a, self_type b) noexcept { return value_type(a) != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, value_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, value_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, value_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(value_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(value_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(value_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, enum_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, enum_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, enum_type b) noexcept { return value_type(a) ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(enum_type a, self_type b) noexcept { return value_type(a) | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(enum_type a, self_type b) noexcept { return value_type(a) & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(enum_type a, self_type b) noexcept { return value_type(a) ^ value_type(b); }
+};
+
+
+template <typename enum_t, typename store_t = typename enum_value_type<enum_t>::store_type>
+// cppcheck-suppress copyCtorAndEqOperator
+class FlagSet
+{
+public:
+ using self_type = FlagSet;
+ using enum_type = enum_t;
+ using value_type = enum_value_type<enum_t>;
+ using store_type = store_t;
+
+private:
+ // support truncated store_type ... :
+ store_type bits_;
+ static MPT_CONSTEXPRINLINE store_type store_from_value(value_type bits) noexcept { return static_cast<store_type>(bits.as_bits()); }
+ static MPT_CONSTEXPRINLINE value_type value_from_store(store_type bits) noexcept { return value_type::from_bits(static_cast<typename value_type::store_type>(bits)); }
+
+ MPT_CONSTEXPRINLINE FlagSet &store(value_type bits) noexcept
+ {
+ bits_ = store_from_value(bits);
+ return *this;
+ }
+ MPT_CONSTEXPRINLINE value_type load() const noexcept { return value_from_store(bits_); }
+
+public:
+ // Default constructor (no flags set)
+ MPT_CONSTEXPRINLINE FlagSet() noexcept
+ : bits_(store_from_value(value_type()))
+ {
+ }
+
+ // Copy constructor
+ MPT_CONSTEXPRINLINE FlagSet(const FlagSet &flags) noexcept
+ : bits_(flags.bits_)
+ {
+ }
+
+ // Value constructor
+ MPT_CONSTEXPRINLINE FlagSet(value_type flags) noexcept
+ : bits_(store_from_value(value_type(flags)))
+ {
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet(enum_type flag) noexcept
+ : bits_(store_from_value(value_type(flag)))
+ {
+ }
+
+ explicit MPT_CONSTEXPRINLINE FlagSet(store_type flags) noexcept
+ : bits_(store_from_value(value_type::from_bits(flags)))
+ {
+ }
+
+ MPT_CONSTEXPRINLINE explicit operator bool() const noexcept
+ {
+ return load();
+ }
+ MPT_CONSTEXPRINLINE explicit operator store_type() const noexcept
+ {
+ return load().as_bits();
+ }
+
+ MPT_CONSTEXPRINLINE value_type value() const noexcept
+ {
+ return load();
+ }
+
+ MPT_CONSTEXPRINLINE operator value_type() const noexcept
+ {
+ return load();
+ }
+
+ // Test if one or more flags are set. Returns true if at least one of the given flags is set.
+ MPT_CONSTEXPRINLINE bool operator[](value_type flags) const noexcept
+ {
+ return test(flags);
+ }
+
+ // Set one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &set(value_type flags) noexcept
+ {
+ return store(load() | flags);
+ }
+
+ // Set or clear one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &set(value_type flags, bool val) noexcept
+ {
+ return store((val ? (load() | flags) : (load() & ~flags)));
+ }
+
+ // Clear or flags.
+ MPT_CONSTEXPRINLINE FlagSet &reset() noexcept
+ {
+ return store(value_type());
+ }
+
+ // Clear one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &reset(value_type flags) noexcept
+ {
+ return store(load() & ~flags);
+ }
+
+ // Toggle all flags.
+ MPT_CONSTEXPRINLINE FlagSet &flip() noexcept
+ {
+ return store(~load());
+ }
+
+ // Toggle one or more flags.
+ MPT_CONSTEXPRINLINE FlagSet &flip(value_type flags) noexcept
+ {
+ return store(load() ^ flags);
+ }
+
+ // Returns the size of the flag set in bytes
+ MPT_CONSTEXPRINLINE std::size_t size() const noexcept
+ {
+ return sizeof(store_type);
+ }
+
+ // Returns the size of the flag set in bits
+ MPT_CONSTEXPRINLINE std::size_t size_bits() const noexcept
+ {
+ return size() * 8;
+ }
+
+ // Test if one or more flags are set. Returns true if at least one of the given flags is set.
+ MPT_CONSTEXPRINLINE bool test(value_type flags) const noexcept
+ {
+ return (load() & flags);
+ }
+
+ // Test if all specified flags are set.
+ MPT_CONSTEXPRINLINE bool test_all(value_type flags) const noexcept
+ {
+ return (load() & flags) == flags;
+ }
+
+ // Test if any but the specified flags are set.
+ MPT_CONSTEXPRINLINE bool test_any_except(value_type flags) const noexcept
+ {
+ return (load() & ~flags);
+ }
+
+ // Test if any flag is set.
+ MPT_CONSTEXPRINLINE bool any() const noexcept
+ {
+ return load();
+ }
+
+ // Test if no flags are set.
+ MPT_CONSTEXPRINLINE bool none() const noexcept
+ {
+ return !load();
+ }
+
+ MPT_CONSTEXPRINLINE store_type GetRaw() const noexcept
+ {
+ return bits_;
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &SetRaw(store_type flags) noexcept
+ {
+ bits_ = flags;
+ return *this;
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(value_type flags) noexcept
+ {
+ return store(flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(enum_type flag) noexcept
+ {
+ return store(flag);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator=(FlagSet flags) noexcept
+ {
+ return store(flags.load());
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator&=(value_type flags) noexcept
+ {
+ return store(load() & flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator|=(value_type flags) noexcept
+ {
+ return store(load() | flags);
+ }
+
+ MPT_CONSTEXPRINLINE FlagSet &operator^=(value_type flags) noexcept
+ {
+ return store(load() ^ flags);
+ }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, self_type b) noexcept { return a.load() == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, self_type b) noexcept { return a.load() != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, value_type b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, value_type b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(value_type a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(value_type a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, enum_type b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, enum_type b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(enum_type a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(enum_type a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(self_type a, Enum<enum_type> b) noexcept { return a.load() == value_type(b); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(self_type a, Enum<enum_type> b) noexcept { return a.load() != value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(Enum<enum_type> a, self_type b) noexcept { return value_type(a) == b.load(); }
+ friend MPT_CONSTEXPRINLINE bool operator!=(Enum<enum_type> a, self_type b) noexcept { return value_type(a) != b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, self_type b) noexcept { return a.load() | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, self_type b) noexcept { return a.load() & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, self_type b) noexcept { return a.load() ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, value_type b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, value_type b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, value_type b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(value_type a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(value_type a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(value_type a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, enum_type b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, enum_type b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, enum_type b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(enum_type a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(enum_type a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(enum_type a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(self_type a, Enum<enum_type> b) noexcept { return a.load() | value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(self_type a, Enum<enum_type> b) noexcept { return a.load() & value_type(b); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(self_type a, Enum<enum_type> b) noexcept { return a.load() ^ value_type(b); }
+
+ friend MPT_CONSTEXPRINLINE const value_type operator|(Enum<enum_type> a, self_type b) noexcept { return value_type(a) | b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator&(Enum<enum_type> a, self_type b) noexcept { return value_type(a) & b.load(); }
+ friend MPT_CONSTEXPRINLINE const value_type operator^(Enum<enum_type> a, self_type b) noexcept { return value_type(a) ^ b.load(); }
+};
+
+
+// Declare typesafe logical operators for enum_t
+#define MPT_DECLARE_ENUM(enum_t) \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator|(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) | enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator&(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) & enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator^(enum_t a, enum_t b) noexcept \
+ { \
+ return enum_value_type<enum_t>(a) ^ enum_value_type<enum_t>(b); \
+ } \
+ MPT_CONSTEXPRINLINE enum_value_type<enum_t> operator~(enum_t a) noexcept \
+ { \
+ return ~enum_value_type<enum_t>(a); \
+ } \
+/**/
+
+// backwards compatibility
+#define DECLARE_FLAGSET MPT_DECLARE_ENUM
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp
new file mode 100644
index 00000000..6fcbb707
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Int24.hpp
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/endian/int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <limits>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using uint24 = mpt::uint24;
+static_assert(sizeof(uint24) == 3);
+inline constexpr uint32 uint24_min = std::numeric_limits<uint24>::min();
+inline constexpr uint32 uint24_max = std::numeric_limits<uint24>::max();
+using int24 = mpt::int24;
+static_assert(sizeof(int24) == 3);
+inline constexpr int32 int24_min = std::numeric_limits<int24>::min();
+inline constexpr int32 int24_max = std::numeric_limits<int24>::max();
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp
new file mode 100644
index 00000000..b4def28a
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/base/Types.hpp
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/integer.hpp"
+#include "mpt/base/floatingpoint.hpp"
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+using int8 = mpt::int8;
+using int16 = mpt::int16;
+using int32 = mpt::int32;
+using int64 = mpt::int64;
+using uint8 = mpt::uint8;
+using uint16 = mpt::uint16;
+using uint32 = mpt::uint32;
+using uint64 = mpt::uint64;
+
+using nativefloat = mpt::nativefloat;
+using float32 = mpt::float32;
+using float64 = mpt::float64;
+using namespace mpt::float_literals;
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp
new file mode 100644
index 00000000..af5f0741
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/logging/Logger.hpp
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/source_location.hpp"
+#include "mpt/string/types.hpp"
+
+OPENMPT_NAMESPACE_BEGIN
+
+enum LogLevel
+{
+ LogDebug = 5,
+ LogInformation = 4,
+ LogNotification = 3,
+ LogWarning = 2,
+ LogError = 1
+};
+
+class ILogger
+{
+protected:
+ virtual ~ILogger() = default;
+
+public:
+ virtual bool IsLevelActive(LogLevel level) const noexcept = 0;
+ // facility: ASCII
+ virtual bool IsFacilityActive(const char *facility) const noexcept = 0;
+ // facility: ASCII
+ virtual void SendLogMessage(const mpt::source_location &loc, LogLevel level, const char *facility, const mpt::ustring &text) const = 0;
+};
+
+#define MPT_LOG(logger, level, facility, text) \
+ do \
+ { \
+ if((logger).IsLevelActive((level))) \
+ { \
+ if((logger).IsFacilityActive((facility))) \
+ { \
+ (logger).SendLogMessage(MPT_SOURCE_LOCATION_CURRENT(), (level), (facility), (text)); \
+ } \
+ } \
+ } while(0)
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp
new file mode 100644
index 00000000..c075f9cf
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/random/ModPlug.hpp
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <limits>
+#include <type_traits>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace mpt
+{
+
+
+namespace rng
+{
+
+
+template <typename Tstate, typename Tvalue, Tstate x1, Tstate x2, Tstate x3, Tstate x4, int rol1, int rol2>
+class modplug
+{
+public:
+ typedef Tstate state_type;
+ typedef Tvalue result_type;
+
+private:
+ state_type state1;
+ state_type state2;
+
+public:
+ template <typename Trng>
+ explicit inline modplug(Trng &rd)
+ : state1(mpt::random<state_type>(rd))
+ , state2(mpt::random<state_type>(rd))
+ {
+ }
+ explicit inline modplug(state_type seed1, state_type seed2)
+ : state1(seed1)
+ , state2(seed2)
+ {
+ }
+
+public:
+ static MPT_CONSTEXPRINLINE result_type min()
+ {
+ return static_cast<result_type>(0);
+ }
+ static MPT_CONSTEXPRINLINE result_type max()
+ {
+ return std::numeric_limits<result_type>::max();
+ }
+ static MPT_CONSTEXPRINLINE int result_bits()
+ {
+ static_assert(std::is_integral<result_type>::value);
+ static_assert(std::is_unsigned<result_type>::value);
+ return std::numeric_limits<result_type>::digits;
+ }
+ inline result_type operator()()
+ {
+ state_type a = state1;
+ state_type b = state2;
+ a = mpt::rotl(a, rol1);
+ a ^= x1;
+ a += x2 + (b * x3);
+ b += mpt::rotl(a, rol2) * x4;
+ state1 = a;
+ state2 = b;
+ result_type result = static_cast<result_type>(b);
+ return result;
+ }
+};
+
+typedef modplug<uint32, uint32, 0x10204080u, 0x78649E7Du, 4, 5, 1, 16> modplug_dither;
+
+
+} // namespace rng
+
+
+} // namespace mpt
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp
new file mode 100644
index 00000000..cf5995b2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Copy.hpp
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/audio/span.hpp"
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+
+#include <cassert>
+#include <cstddef>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudio(TBufOut buf_out, TBufIn buf_in)
+{
+ assert(buf_in.size_frames() == buf_out.size_frames());
+ assert(buf_in.size_channels() == buf_out.size_channels());
+ std::size_t countFrames = std::min(buf_in.size_frames(), buf_out.size_frames());
+ std::size_t channels = std::min(buf_in.size_channels(), buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudio(TBufOut buf_out, TBufIn buf_in, std::size_t countFrames)
+{
+ assert(countFrames <= buf_in.size_frames());
+ assert(countFrames <= buf_out.size_frames());
+ assert(buf_in.size_channels() == buf_out.size_channels());
+ std::size_t channels = std::min(buf_in.size_channels(), buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+template <typename TBufOut, typename TBufIn>
+void CopyAudioChannels(TBufOut buf_out, TBufIn buf_in, std::size_t channels, std::size_t countFrames)
+{
+ assert(countFrames <= buf_in.size_frames());
+ assert(countFrames <= buf_out.size_frames());
+ assert(channels <= buf_in.size_channels());
+ assert(channels <= buf_out.size_channels());
+ for(std::size_t frame = 0; frame < countFrames; ++frame)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ buf_out(channel, frame) = SC::sample_cast<typename TBufOut::sample_type>(buf_in(channel, frame));
+ }
+ }
+}
+
+
+// Copy numChannels interleaved sample streams.
+template <typename Tin, typename Tout>
+void CopyAudioChannelsInterleaved(Tout *MPT_RESTRICT outBuf, const Tin *MPT_RESTRICT inBuf, std::size_t numChannels, std::size_t countFrames)
+{
+ CopyAudio(mpt::audio_span_interleaved<Tout>(outBuf, numChannels, countFrames), mpt::audio_span_interleaved<const Tin>(inBuf, numChannels, countFrames));
+}
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp
new file mode 100644
index 00000000..1dd8de37
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/CopyMix.hpp
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+
+#include "mpt/audio/span.hpp"
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/SampleClip.hpp"
+#include "openmpt/soundbase/SampleClipFixedPoint.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+#include "openmpt/soundbase/SampleConvertFixedPoint.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+template <int fractionalBits, bool clipOutput, typename TOutBuf, typename TInBuf, typename Tdither>
+void ConvertBufferMixInternalFixedToBuffer(TOutBuf outBuf, TInBuf inBuf, Tdither &dither, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ constexpr int ditherBits = SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).IsInt()
+ ? SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).GetBitsPerSample()
+ : 0;
+ SC::ClipFixed<int32, fractionalBits, clipOutput> clip;
+ SC::ConvertFixedPoint<TOutSample, TInSample, fractionalBits> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(clip(dither.template process<ditherBits>(channel, inBuf(channel, i))));
+ }
+ }
+}
+
+
+template <int fractionalBits, typename TOutBuf, typename TInBuf>
+void ConvertBufferToBufferMixInternalFixed(TOutBuf outBuf, TInBuf inBuf, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ SC::ConvertToFixedPoint<TOutSample, TInSample, fractionalBits> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(inBuf(channel, i));
+ }
+ }
+}
+
+
+template <bool clipOutput, typename TOutBuf, typename TInBuf, typename Tdither>
+void ConvertBufferMixInternalToBuffer(TOutBuf outBuf, TInBuf inBuf, Tdither &dither, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ constexpr int ditherBits = SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).IsInt()
+ ? SampleFormat(SampleFormatTraits<TOutSample>::sampleFormat()).GetBitsPerSample()
+ : 0;
+ SC::Clip<TInSample, clipOutput> clip;
+ SC::Convert<TOutSample, TInSample> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(clip(dither.template process<ditherBits>(channel, inBuf(channel, i))));
+ }
+ }
+}
+
+
+template <typename TOutBuf, typename TInBuf>
+void ConvertBufferToBufferMixInternal(TOutBuf outBuf, TInBuf inBuf, std::size_t channels, std::size_t count)
+{
+ using TOutSample = typename std::remove_const<typename TOutBuf::sample_type>::type;
+ using TInSample = typename std::remove_const<typename TInBuf::sample_type>::type;
+ assert(inBuf.size_channels() >= channels);
+ assert(outBuf.size_channels() >= channels);
+ assert(inBuf.size_frames() >= count);
+ assert(outBuf.size_frames() >= count);
+ SC::Convert<TOutSample, TInSample> conv;
+ for(std::size_t i = 0; i < count; ++i)
+ {
+ for(std::size_t channel = 0; channel < channels; ++channel)
+ {
+ outBuf(channel, i) = conv(inBuf(channel, i));
+ }
+ }
+}
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp
new file mode 100644
index 00000000..2ba7153c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/Dither.hpp
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/engine.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+
+#include <vector>
+#include <variant>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tdither>
+class MultiChannelDither
+{
+private:
+ std::vector<Tdither> DitherChannels;
+ typename Tdither::prng_type prng;
+
+public:
+ template <typename Trd>
+ MultiChannelDither(Trd &rd, std::size_t channels)
+ : DitherChannels(channels)
+ , prng(Tdither::prng_init(rd))
+ {
+ return;
+ }
+ void Reset()
+ {
+ for(std::size_t channel = 0; channel < DitherChannels.size(); ++channel)
+ {
+ DitherChannels[channel] = Tdither{};
+ }
+ }
+ std::size_t GetChannels() const
+ {
+ return DitherChannels.size();
+ }
+ template <uint32 targetbits>
+ MPT_FORCEINLINE MixSampleInt process(std::size_t channel, MixSampleInt sample)
+ {
+ return DitherChannels[channel].template process<targetbits>(sample, prng);
+ }
+ template <uint32 targetbits>
+ MPT_FORCEINLINE MixSampleFloat process(std::size_t channel, MixSampleFloat sample)
+ {
+ return DitherChannels[channel].template process<targetbits>(sample, prng);
+ }
+};
+
+
+template <typename AvailableDithers, typename DitherNames, std::size_t defaultChannels, std::size_t defaultDither, std::size_t noDither, typename seeding_random_engine = mpt::good_engine>
+class Dithers
+ : public DitherNames
+{
+
+public:
+ static constexpr std::size_t NoDither = noDither;
+ static constexpr std::size_t DefaultDither = defaultDither;
+ static constexpr std::size_t DefaultChannels = defaultChannels;
+
+private:
+ seeding_random_engine m_PRNG;
+ AvailableDithers m_Dithers;
+
+public:
+ template <typename Trd>
+ Dithers(Trd &rd, std::size_t mode = defaultDither, std::size_t channels = defaultChannels)
+ : m_PRNG(mpt::make_prng<seeding_random_engine>(rd))
+ , m_Dithers(std::in_place_index<defaultDither>, m_PRNG, channels)
+ {
+ SetMode(mode, channels);
+ }
+
+ AvailableDithers &Variant()
+ {
+ return m_Dithers;
+ }
+
+ static std::size_t GetNumDithers()
+ {
+ return std::variant_size<AvailableDithers>::value;
+ }
+
+ static std::size_t GetDefaultDither()
+ {
+ return defaultDither;
+ }
+
+ static std::size_t GetNoDither()
+ {
+ return noDither;
+ }
+
+private:
+ template <std::size_t i = 0>
+ void set_mode(std::size_t mode, std::size_t channels)
+ {
+ if constexpr(i < std::variant_size<AvailableDithers>::value)
+ {
+ if(mode == i)
+ {
+ m_Dithers.template emplace<i>(m_PRNG, channels);
+ } else
+ {
+ this->template set_mode<i + 1>(mode, channels);
+ }
+ } else
+ {
+ MPT_UNUSED(mode);
+ m_Dithers.template emplace<defaultDither>(m_PRNG, channels);
+ }
+ }
+
+public:
+ void SetMode(std::size_t mode, std::size_t channels)
+ {
+ if((mode == GetMode()) && (channels == GetChannels()))
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ return;
+ }
+ set_mode(mode, channels);
+ }
+ void SetMode(std::size_t mode)
+ {
+ if(mode == GetMode())
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ return;
+ }
+ set_mode(mode, GetChannels());
+ }
+ void SetChannels(std::size_t channels)
+ {
+ if(channels == GetChannels())
+ {
+ return;
+ }
+ set_mode(GetMode(), channels);
+ }
+ void Reset()
+ {
+ std::visit([](auto &dither)
+ { return dither.Reset(); },
+ m_Dithers);
+ }
+ std::size_t GetMode() const
+ {
+ return m_Dithers.index();
+ }
+ std::size_t GetChannels() const
+ {
+ return std::visit([](auto &dither)
+ { return dither.GetChannels(); },
+ m_Dithers);
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp
new file mode 100644
index 00000000..fcca4f45
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherModPlug.hpp
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/random/ModPlug.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/MixSampleConvert.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+struct Dither_ModPlug
+{
+public:
+ using prng_type = mpt::rng::modplug_dither;
+ template <typename Trd>
+ static prng_type prng_init(Trd &)
+ {
+ return prng_type{0, 0};
+ }
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &rng)
+ {
+ if constexpr(targetbits == 0)
+ {
+ MPT_UNREFERENCED_PARAMETER(rng);
+ return sample;
+ } else if constexpr(targetbits + MixSampleIntTraits::mix_headroom_bits + 1 >= 32)
+ {
+ MPT_UNREFERENCED_PARAMETER(rng);
+ return sample;
+ } else
+ {
+ sample += mpt::rshift_signed(static_cast<int32>(mpt::random<uint32>(rng)), (targetbits + MixSampleIntTraits::mix_headroom_bits + 1));
+ return sample;
+ }
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng)
+ {
+ return mix_sample_cast<MixSampleFloat>(process<targetbits>(mix_sample_cast<MixSampleInt>(sample), prng));
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp
new file mode 100644
index 00000000..98700c04
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherNone.hpp
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+struct Dither_None
+{
+public:
+ using prng_type = struct
+ {
+ };
+ template <typename Trd>
+ static prng_type prng_init(Trd &)
+ {
+ return prng_type{};
+ }
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &)
+ {
+ return sample;
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &)
+ {
+ return sample;
+ }
+};
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp
new file mode 100644
index 00000000..5a60ef52
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/DitherSimple.hpp
@@ -0,0 +1,92 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/random/engine.hpp"
+#include "mpt/random/default_engines.hpp"
+#include "mpt/random/random.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/MixSampleConvert.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <int ditherdepth = 1, bool triangular = false, bool shaped = true>
+struct Dither_SimpleImpl
+{
+public:
+ using prng_type = mpt::fast_engine;
+ template <typename Trd>
+ static prng_type prng_init(Trd &rd)
+ {
+ return mpt::make_prng<prng_type>(rd);
+ }
+
+private:
+ int32 error = 0;
+
+public:
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleInt process(MixSampleInt sample, Trng &prng)
+ {
+ if constexpr(targetbits == 0)
+ {
+ MPT_UNREFERENCED_PARAMETER(prng);
+ return sample;
+ } else
+ {
+ static_assert(sizeof(MixSampleInt) == 4);
+ constexpr int rshift = (32 - targetbits) - MixSampleIntTraits::mix_headroom_bits;
+ if constexpr(rshift <= 1)
+ {
+ MPT_UNREFERENCED_PARAMETER(prng);
+ // nothing to dither
+ return sample;
+ } else
+ {
+ constexpr int rshiftpositive = (rshift > 1) ? rshift : 1; // work-around warnings about negative shift with C++14 compilers
+ constexpr int round_mask = ~((1 << rshiftpositive) - 1);
+ constexpr int round_offset = 1 << (rshiftpositive - 1);
+ constexpr int noise_bits = rshiftpositive + (ditherdepth - 1);
+ constexpr int noise_bias = (1 << (noise_bits - 1));
+ int32 e = error;
+ unsigned int unoise = 0;
+ if constexpr(triangular)
+ {
+ unoise = (mpt::random<unsigned int>(prng, noise_bits) + mpt::random<unsigned int>(prng, noise_bits)) >> 1;
+ } else
+ {
+ unoise = mpt::random<unsigned int>(prng, noise_bits);
+ }
+ int noise = static_cast<int>(unoise) - noise_bias; // un-bias
+ int val = sample;
+ if constexpr(shaped)
+ {
+ val += (e >> 1);
+ }
+ int rounded = (val + noise + round_offset) & round_mask;
+ e = val - rounded;
+ sample = rounded;
+ error = e;
+ return sample;
+ }
+ }
+ }
+ template <uint32 targetbits, typename Trng>
+ MPT_FORCEINLINE MixSampleFloat process(MixSampleFloat sample, Trng &prng)
+ {
+ return mix_sample_cast<MixSampleFloat>(process<targetbits>(mix_sample_cast<MixSampleInt>(sample), prng));
+ }
+};
+
+using Dither_Simple = Dither_SimpleImpl<>;
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp
new file mode 100644
index 00000000..2188d3b2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSample.hpp
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/audio/sample.hpp"
+#include "mpt/base/floatingpoint.hpp"
+
+#include <type_traits>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tsample, std::size_t MIX_HEADROOM_BITS, std::size_t FILTER_HEADROOM_BITS>
+struct FixedPointSampleTraits
+{
+ static_assert(std::is_integral<Tsample>::value);
+ static_assert(std::is_signed<Tsample>::value);
+ static_assert((sizeof(Tsample) * 8u) - 1 > MIX_HEADROOM_BITS);
+ static_assert((sizeof(Tsample) * 8u) - 1 > FILTER_HEADROOM_BITS);
+ using sample_type = Tsample;
+ enum class sample_type_strong : sample_type
+ {
+ };
+ static constexpr int mix_headroom_bits = static_cast<int>(MIX_HEADROOM_BITS);
+ static constexpr int mix_precision_bits = static_cast<int>((sizeof(Tsample) * 8) - MIX_HEADROOM_BITS); // including sign bit
+ static constexpr int mix_fractional_bits = static_cast<int>((sizeof(Tsample) * 8) - 1 - MIX_HEADROOM_BITS); // excluding sign bit
+ static constexpr sample_type mix_clip_max = ((sample_type(1) << mix_fractional_bits) - sample_type(1));
+ static constexpr sample_type mix_clip_min = -((sample_type(1) << mix_fractional_bits) - sample_type(1));
+ static constexpr int filter_headroom_bits = static_cast<int>(FILTER_HEADROOM_BITS);
+ static constexpr int filter_precision_bits = static_cast<int>((sizeof(Tsample) * 8) - FILTER_HEADROOM_BITS); // including sign bit
+ static constexpr int filter_fractional_bits = static_cast<int>((sizeof(Tsample) * 8) - 1 - FILTER_HEADROOM_BITS); // excluding sign bit
+ template <typename Tfloat>
+ static constexpr Tfloat mix_scale = static_cast<Tfloat>(sample_type(1) << mix_fractional_bits);
+};
+
+using MixSampleIntTraits = FixedPointSampleTraits<int32, 4, 8>;
+
+using MixSampleInt = MixSampleIntTraits::sample_type;
+using MixSampleFloat = mpt::audio_sample_float;
+
+using MixSample = std::conditional<mpt::float_traits<nativefloat>::is_hard, MixSampleFloat, MixSampleInt>::type;
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp
new file mode 100644
index 00000000..f34f3e06
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/MixSampleConvert.hpp
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/soundbase/MixSample.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+#include "openmpt/soundbase/SampleConvertFixedPoint.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+template <typename Tdst, typename Tsrc>
+struct ConvertMixSample;
+
+template <>
+struct ConvertMixSample<MixSampleInt, MixSampleInt>
+{
+ MPT_FORCEINLINE MixSampleInt conv(MixSampleInt src)
+ {
+ return src;
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleFloat, MixSampleFloat>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(MixSampleFloat src)
+ {
+ return src;
+ }
+};
+
+template <typename Tsrc>
+struct ConvertMixSample<MixSampleInt, Tsrc>
+{
+ MPT_FORCEINLINE MixSampleInt conv(Tsrc src)
+ {
+ return SC::ConvertToFixedPoint<MixSampleInt, Tsrc, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <typename Tdst>
+struct ConvertMixSample<Tdst, MixSampleInt>
+{
+ MPT_FORCEINLINE Tdst conv(MixSampleInt src)
+ {
+ return SC::ConvertFixedPoint<Tdst, MixSampleInt, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <typename Tsrc>
+struct ConvertMixSample<MixSampleFloat, Tsrc>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(Tsrc src)
+ {
+ return SC::Convert<MixSampleFloat, Tsrc>{}(src);
+ }
+};
+
+template <typename Tdst>
+struct ConvertMixSample<Tdst, MixSampleFloat>
+{
+ MPT_FORCEINLINE Tdst conv(MixSampleFloat src)
+ {
+ return SC::Convert<Tdst, MixSampleFloat>{}(src);
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleInt, MixSampleFloat>
+{
+ MPT_FORCEINLINE MixSampleInt conv(MixSampleFloat src)
+ {
+ return SC::ConvertToFixedPoint<MixSampleInt, MixSampleFloat, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+template <>
+struct ConvertMixSample<MixSampleFloat, MixSampleInt>
+{
+ MPT_FORCEINLINE MixSampleFloat conv(MixSampleInt src)
+ {
+ return SC::ConvertFixedPoint<MixSampleFloat, MixSampleInt, MixSampleIntTraits::mix_fractional_bits>{}(src);
+ }
+};
+
+
+template <typename Tdst, typename Tsrc>
+MPT_FORCEINLINE Tdst mix_sample_cast(Tsrc src)
+{
+ return ConvertMixSample<Tdst, Tsrc>{}.conv(src);
+}
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp
new file mode 100644
index 00000000..733d61de
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClip.hpp
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tsample, bool clipOutput>
+struct Clip;
+
+template <bool clipOutput>
+struct Clip<uint8, clipOutput>
+{
+ using input_t = uint8;
+ using output_t = uint8;
+ MPT_FORCEINLINE uint8 operator()(uint8 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int8, clipOutput>
+{
+ using input_t = int8;
+ using output_t = int8;
+ MPT_FORCEINLINE int8 operator()(int8 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int16, clipOutput>
+{
+ using input_t = int16;
+ using output_t = int16;
+ MPT_FORCEINLINE int16 operator()(int16 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int24, clipOutput>
+{
+ using input_t = int24;
+ using output_t = int24;
+ MPT_FORCEINLINE int24 operator()(int24 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int32, clipOutput>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE int32 operator()(int32 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<int64, clipOutput>
+{
+ using input_t = int64;
+ using output_t = int64;
+ MPT_FORCEINLINE int64 operator()(int64 val)
+ {
+ return val;
+ }
+};
+
+template <bool clipOutput>
+struct Clip<float, clipOutput>
+{
+ using input_t = float;
+ using output_t = float;
+ MPT_FORCEINLINE float operator()(float val)
+ {
+ if constexpr(clipOutput)
+ {
+ if(val < -1.0f) val = -1.0f;
+ if(val > 1.0f) val = 1.0f;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+template <bool clipOutput>
+struct Clip<double, clipOutput>
+{
+ using input_t = double;
+ using output_t = double;
+ MPT_FORCEINLINE double operator()(double val)
+ {
+ if constexpr(clipOutput)
+ {
+ if(val < -1.0) val = -1.0;
+ if(val > 1.0) val = 1.0;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp
new file mode 100644
index 00000000..fdf51dd5
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleClipFixedPoint.hpp
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tfixed, int fractionalBits, bool clipOutput>
+struct ClipFixed
+{
+ using input_t = Tfixed;
+ using output_t = Tfixed;
+ MPT_FORCEINLINE Tfixed operator()(Tfixed val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ if constexpr(clipOutput)
+ {
+ constexpr Tfixed clip_max = (Tfixed(1) << fractionalBits) - Tfixed(1);
+ constexpr Tfixed clip_min = Tfixed(0) - (Tfixed(1) << fractionalBits);
+ if(val < clip_min) val = clip_min;
+ if(val > clip_max) val = clip_max;
+ return val;
+ } else
+ {
+ return val;
+ }
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp
new file mode 100644
index 00000000..d10e25b1
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvert.hpp
@@ -0,0 +1,754 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+#include <limits>
+#include <type_traits>
+
+#include <cmath>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+
+#if MPT_COMPILER_MSVC
+template <typename Tfloat>
+MPT_FORCEINLINE Tfloat fastround(Tfloat x)
+{
+ static_assert(std::is_floating_point<Tfloat>::value);
+ return std::floor(x + static_cast<Tfloat>(0.5));
+}
+#else
+template <typename Tfloat>
+MPT_FORCEINLINE Tfloat fastround(Tfloat x)
+{
+ static_assert(std::is_floating_point<Tfloat>::value);
+ return mpt::round(x);
+}
+#endif
+
+
+
+// Shift input_t down by shift and saturate to output_t.
+template <typename Tdst, typename Tsrc, int shift>
+struct ConvertShift
+{
+ using input_t = Tsrc;
+ using output_t = Tdst;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::saturate_cast<output_t>(mpt::rshift_signed(val, shift));
+ }
+};
+
+
+
+// Shift input_t up by shift and saturate to output_t.
+template <typename Tdst, typename Tsrc, int shift>
+struct ConvertShiftUp
+{
+ using input_t = Tsrc;
+ using output_t = Tdst;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::saturate_cast<output_t>(mpt::lshift_signed(val, shift));
+ }
+};
+
+
+
+
+// Every sample conversion functor has to typedef its input_t and output_t.
+// The input_t argument is taken by value because we only deal with per-single-sample conversions here.
+
+
+// straight forward type conversions, clamping when converting from floating point.
+template <typename Tdst, typename Tsrc>
+struct Convert;
+
+template <typename Tid>
+struct Convert<Tid, Tid>
+{
+ using input_t = Tid;
+ using output_t = Tid;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val;
+ }
+};
+
+template <>
+struct Convert<uint8, int8>
+{
+ using input_t = int8;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(val + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int16>
+{
+ using input_t = int16;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 8)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int24>
+{
+ using input_t = int24;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(static_cast<int>(val), 16)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int32>
+{
+ using input_t = int32;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 24)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, int64>
+{
+ using input_t = int64;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<uint8>(static_cast<int8>(mpt::rshift_signed(val, 56)) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, float32>
+{
+ using input_t = float32;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 128.0f;
+ return static_cast<uint8>(mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))) + 0x80);
+ }
+};
+
+template <>
+struct Convert<uint8, double>
+{
+ using input_t = double;
+ using output_t = uint8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 128.0;
+ return static_cast<uint8>(mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val))) + 0x80);
+ }
+};
+
+template <>
+struct Convert<int8, uint8>
+{
+ using input_t = uint8;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(static_cast<int>(val) - 0x80);
+ }
+};
+
+template <>
+struct Convert<int8, int16>
+{
+ using input_t = int16;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int8, int24>
+{
+ using input_t = int24;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(static_cast<int>(val), 16));
+ }
+};
+
+template <>
+struct Convert<int8, int32>
+{
+ using input_t = int32;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 24));
+ }
+};
+
+template <>
+struct Convert<int8, int64>
+{
+ using input_t = int64;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int8>(mpt::rshift_signed(val, 56));
+ }
+};
+
+template <>
+struct Convert<int8, float32>
+{
+ using input_t = float32;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 128.0f;
+ return mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int8, double>
+{
+ using input_t = double;
+ using output_t = int8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 128.0;
+ return mpt::saturate_cast<int8>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int16, uint8>
+{
+ using input_t = uint8;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 8));
+ }
+};
+
+template <>
+struct Convert<int16, int8>
+{
+ using input_t = int8;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::lshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int16, int24>
+{
+ using input_t = int24;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(static_cast<int>(val), 8));
+ }
+};
+
+template <>
+struct Convert<int16, int32>
+{
+ using input_t = int32;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int16, int64>
+{
+ using input_t = int64;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int16>(mpt::rshift_signed(val, 48));
+ }
+};
+
+template <>
+struct Convert<int16, float32>
+{
+ using input_t = float32;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 32768.0f;
+ return mpt::saturate_cast<int16>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int16, double>
+{
+ using input_t = double;
+ using output_t = int16;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 32768.0;
+ return mpt::saturate_cast<int16>(static_cast<int>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int24, uint8>
+{
+ using input_t = uint8;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 16));
+ }
+};
+
+template <>
+struct Convert<int24, int8>
+{
+ using input_t = int8;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int24, int16>
+{
+ using input_t = int16;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::lshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int24, int32>
+{
+ using input_t = int32;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::rshift_signed(val, 8));
+ }
+};
+
+template <>
+struct Convert<int24, int64>
+{
+ using input_t = int64;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int24>(mpt::rshift_signed(val, 40));
+ }
+};
+
+template <>
+struct Convert<int24, float32>
+{
+ using input_t = float32;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 2147483648.0f;
+ return static_cast<int24>(mpt::rshift_signed(mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))), 8));
+ }
+};
+
+template <>
+struct Convert<int24, double>
+{
+ using input_t = double;
+ using output_t = int24;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 2147483648.0;
+ return static_cast<int24>(mpt::rshift_signed(mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val))), 8));
+ }
+};
+
+template <>
+struct Convert<int32, uint8>
+{
+ using input_t = uint8;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(static_cast<int>(val) - 0x80, 24));
+ }
+};
+
+template <>
+struct Convert<int32, int8>
+{
+ using input_t = int8;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(val, 24));
+ }
+};
+
+template <>
+struct Convert<int32, int16>
+{
+ using input_t = int16;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(val, 16));
+ }
+};
+
+template <>
+struct Convert<int32, int24>
+{
+ using input_t = int24;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::lshift_signed(static_cast<int>(val), 8));
+ }
+};
+
+template <>
+struct Convert<int32, int64>
+{
+ using input_t = int64;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<int32>(mpt::rshift_signed(val, 32));
+ }
+};
+
+template <>
+struct Convert<int32, float32>
+{
+ using input_t = float32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= 2147483648.0f;
+ return mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int32, double>
+{
+ using input_t = double;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= 2147483648.0;
+ return mpt::saturate_cast<int32>(static_cast<int64>(SC::fastround(val)));
+ }
+};
+
+template <>
+struct Convert<int64, uint8>
+{
+ using input_t = uint8;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val) - 0x80, 56);
+ }
+};
+
+template <>
+struct Convert<int64, int8>
+{
+ using input_t = int8;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 56);
+ }
+};
+
+template <>
+struct Convert<int64, int16>
+{
+ using input_t = int16;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 48);
+ }
+};
+
+template <>
+struct Convert<int64, int24>
+{
+ using input_t = int24;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 40);
+ }
+};
+
+template <>
+struct Convert<int64, int32>
+{
+ using input_t = int32;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return mpt::lshift_signed(static_cast<int64>(val), 32);
+ }
+};
+
+template <>
+struct Convert<int64, float32>
+{
+ using input_t = float32;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = mpt::safe_clamp(val, -1.0f, 1.0f);
+ val *= static_cast<float>(uint64(1) << 63);
+ return mpt::saturate_cast<int64>(SC::fastround(val));
+ }
+};
+
+template <>
+struct Convert<int64, double>
+{
+ using input_t = double;
+ using output_t = int64;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ val = std::clamp(val, -1.0, 1.0);
+ val *= static_cast<double>(uint64(1) << 63);
+ return mpt::saturate_cast<int64>(SC::fastround(val));
+ }
+};
+
+template <>
+struct Convert<float32, uint8>
+{
+ using input_t = uint8;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return (static_cast<int>(val) - 0x80) * (1.0f / static_cast<float32>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<float32, int8>
+{
+ using input_t = int8;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<float32, int16>
+{
+ using input_t = int16;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 15));
+ }
+};
+
+template <>
+struct Convert<float32, int24>
+{
+ using input_t = int24;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 23));
+ }
+};
+
+template <>
+struct Convert<float32, int32>
+{
+ using input_t = int32;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<unsigned int>(1) << 31));
+ }
+};
+
+template <>
+struct Convert<float32, int64>
+{
+ using input_t = int64;
+ using output_t = float32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0f / static_cast<float>(static_cast<uint64>(1) << 63));
+ }
+};
+
+template <>
+struct Convert<double, uint8>
+{
+ using input_t = uint8;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return (static_cast<int>(val) - 0x80) * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<double, int8>
+{
+ using input_t = int8;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 7));
+ }
+};
+
+template <>
+struct Convert<double, int16>
+{
+ using input_t = int16;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 15));
+ }
+};
+
+template <>
+struct Convert<double, int24>
+{
+ using input_t = int24;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 23));
+ }
+};
+
+template <>
+struct Convert<double, int32>
+{
+ using input_t = int32;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<unsigned int>(1) << 31));
+ }
+};
+
+template <>
+struct Convert<double, int64>
+{
+ using input_t = int64;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return val * (1.0 / static_cast<double>(static_cast<uint64>(1) << 63));
+ }
+};
+
+template <>
+struct Convert<double, float>
+{
+ using input_t = float;
+ using output_t = double;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<double>(val);
+ }
+};
+
+template <>
+struct Convert<float, double>
+{
+ using input_t = double;
+ using output_t = float;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ return static_cast<float>(val);
+ }
+};
+
+
+
+template <typename Tdst, typename Tsrc>
+MPT_FORCEINLINE Tdst sample_cast(Tsrc src)
+{
+ return SC::Convert<Tdst, Tsrc>{}(src);
+}
+
+
+
+} // namespace SC
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp
new file mode 100644
index 00000000..65b5d912
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleConvertFixedPoint.hpp
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/arithmetic_shift.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/math.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleConvert.hpp"
+
+#include <algorithm>
+#include <limits>
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+template <typename Tdst, typename Tsrc, int fractionalBits>
+struct ConvertFixedPoint;
+
+template <int fractionalBits>
+struct ConvertFixedPoint<uint8, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = uint8;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int8>::min()) val = std::numeric_limits<int8>::min();
+ if(val > std::numeric_limits<int8>::max()) val = std::numeric_limits<int8>::max();
+ return static_cast<uint8>(val + 0x80); // unsigned
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int8, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int8;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int8>::min()) val = std::numeric_limits<int8>::min();
+ if(val > std::numeric_limits<int8>::max()) val = std::numeric_limits<int8>::max();
+ return static_cast<int8>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int16, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int16;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int16>::min()) val = std::numeric_limits<int16>::min();
+ if(val > std::numeric_limits<int16>::max()) val = std::numeric_limits<int16>::max();
+ return static_cast<int16>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int24, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int24;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(output_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ val = mpt::rshift_signed((val + (1 << (shiftBits - 1))), shiftBits); // round
+ if(val < std::numeric_limits<int24>::min()) val = std::numeric_limits<int24>::min();
+ if(val > std::numeric_limits<int24>::max()) val = std::numeric_limits<int24>::max();
+ return static_cast<int24>(val);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<int32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return static_cast<int32>(std::clamp(val, static_cast<int32>(-((1 << fractionalBits) - 1)), static_cast<int32>(1 << fractionalBits) - 1)) << (sizeof(input_t) * 8 - 1 - fractionalBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<float32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = float32;
+ const float factor;
+ MPT_FORCEINLINE ConvertFixedPoint()
+ : factor(1.0f / static_cast<float>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return val * factor;
+ }
+};
+
+template <int fractionalBits>
+struct ConvertFixedPoint<float64, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = float64;
+ const double factor;
+ MPT_FORCEINLINE ConvertFixedPoint()
+ : factor(1.0 / static_cast<double>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ return val * factor;
+ }
+};
+
+
+template <typename Tdst, typename Tsrc, int fractionalBits>
+struct ConvertToFixedPoint;
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, uint8, fractionalBits>
+{
+ using input_t = uint8;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(static_cast<int>(val) - 0x80), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int8, fractionalBits>
+{
+ using input_t = int8;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int16, fractionalBits>
+{
+ using input_t = int16;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int24, fractionalBits>
+{
+ using input_t = int24;
+ using output_t = int32;
+ static constexpr int shiftBits = fractionalBits + 1 - sizeof(input_t) * 8;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ static_assert(shiftBits >= 1);
+ return mpt::lshift_signed(static_cast<output_t>(val), shiftBits);
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, int32, fractionalBits>
+{
+ using input_t = int32;
+ using output_t = int32;
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(output_t) * 8 - 1);
+ return mpt::rshift_signed(static_cast<output_t>(val), (sizeof(input_t) * 8 - 1 - fractionalBits));
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, float32, fractionalBits>
+{
+ using input_t = float32;
+ using output_t = int32;
+ const float factor;
+ MPT_FORCEINLINE ConvertToFixedPoint()
+ : factor(static_cast<float>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ val = mpt::sanitize_nan(val);
+ return mpt::saturate_cast<output_t>(SC::fastround(val * factor));
+ }
+};
+
+template <int fractionalBits>
+struct ConvertToFixedPoint<int32, float64, fractionalBits>
+{
+ using input_t = float64;
+ using output_t = int32;
+ const double factor;
+ MPT_FORCEINLINE ConvertToFixedPoint()
+ : factor(static_cast<double>(1 << fractionalBits))
+ {
+ return;
+ }
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1);
+ val = mpt::sanitize_nan(val);
+ return mpt::saturate_cast<output_t>(SC::fastround(val * factor));
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp
new file mode 100644
index 00000000..164004b0
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleDecode.hpp
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/floatingpoint.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "openmpt/base/Endian.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <algorithm>
+
+#include <cmath>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+// Byte offsets, from lowest significant to highest significant byte (for various functor template parameters)
+#define littleEndian64 0, 1, 2, 3, 4, 5, 6, 7
+#define littleEndian32 0, 1, 2, 3
+#define littleEndian24 0, 1, 2
+#define littleEndian16 0, 1
+
+#define bigEndian64 7, 6, 5, 4, 3, 2, 1, 0
+#define bigEndian32 3, 2, 1, 0
+#define bigEndian24 2, 1, 0
+#define bigEndian16 1, 0
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+// Every sample decoding functor has to typedef its input_t and output_t
+// and has to provide a static constexpr input_inc member
+// which describes by how many input_t elements inBuf has to be incremented between invocations.
+// input_inc is normally 1 except when decoding e.g. bigger sample values
+// from multiple std::byte values.
+
+
+struct DecodeInt8
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return mpt::byte_cast<int8>(*inBuf);
+ }
+};
+
+struct DecodeUint8
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return static_cast<int8>(static_cast<int>(mpt::byte_cast<uint8>(*inBuf)) - 128);
+ }
+};
+
+struct DecodeInt8Delta
+{
+ using input_t = std::byte;
+ using output_t = int8;
+ static constexpr std::size_t input_inc = 1;
+ uint8 delta;
+ DecodeInt8Delta()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(*inBuf);
+ return static_cast<int8>(delta);
+ }
+};
+
+struct DecodeInt16uLaw
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 1;
+ // clang-format off
+ static constexpr std::array<int16, 256> uLawTable =
+ {
+ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+ -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+ -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+ -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+ -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+ -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+ -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+ -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+ -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+ -1372, -1308, -1244, -1180, -1116, -1052, -988, -924,
+ -876, -844, -812, -780, -748, -716, -684, -652,
+ -620, -588, -556, -524, -492, -460, -428, -396,
+ -372, -356, -340, -324, -308, -292, -276, -260,
+ -244, -228, -212, -196, -180, -164, -148, -132,
+ -120, -112, -104, -96, -88, -80, -72, -64,
+ -56, -48, -40, -32, -24, -16, -8, -1,
+ 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+ 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+ 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+ 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316,
+ 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140,
+ 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092,
+ 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004,
+ 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980,
+ 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436,
+ 1372, 1308, 1244, 1180, 1116, 1052, 988, 924,
+ 876, 844, 812, 780, 748, 716, 684, 652,
+ 620, 588, 556, 524, 492, 460, 428, 396,
+ 372, 356, 340, 324, 308, 292, 276, 260,
+ 244, 228, 212, 196, 180, 164, 148, 132,
+ 120, 112, 104, 96, 88, 80, 72, 64,
+ 56, 48, 40, 32, 24, 16, 8, 0
+ };
+ // clang-format on
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return uLawTable[mpt::byte_cast<uint8>(*inBuf)];
+ }
+};
+
+struct DecodeInt16ALaw
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 1;
+ // clang-format off
+ static constexpr std::array<int16, 256> ALawTable =
+ {
+ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+ -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+ -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+ -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+ -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+ -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+ -11008,-10496,-12032,-11520, -8960, -8448, -9984, -9472,
+ -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+ -344, -328, -376, -360, -280, -264, -312, -296,
+ -472, -456, -504, -488, -408, -392, -440, -424,
+ -88, -72, -120, -104, -24, -8, -56, -40,
+ -216, -200, -248, -232, -152, -136, -184, -168,
+ -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+ -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+ -688, -656, -752, -720, -560, -528, -624, -592,
+ -944, -912, -1008, -976, -816, -784, -880, -848,
+ 5504, 5248, 6016, 5760, 4480, 4224, 4992, 4736,
+ 7552, 7296, 8064, 7808, 6528, 6272, 7040, 6784,
+ 2752, 2624, 3008, 2880, 2240, 2112, 2496, 2368,
+ 3776, 3648, 4032, 3904, 3264, 3136, 3520, 3392,
+ 22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+ 30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+ 11008, 10496, 12032, 11520, 8960, 8448, 9984, 9472,
+ 15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+ 344, 328, 376, 360, 280, 264, 312, 296,
+ 472, 456, 504, 488, 408, 392, 440, 424,
+ 88, 72, 120, 104, 24, 8, 56, 40,
+ 216, 200, 248, 232, 152, 136, 184, 168,
+ 1376, 1312, 1504, 1440, 1120, 1056, 1248, 1184,
+ 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696,
+ 688, 656, 752, 720, 560, 528, 624, 592,
+ 944, 912, 1008, 976, 816, 784, 880, 848
+ };
+ // clang-format on
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return ALawTable[mpt::byte_cast<uint8>(*inBuf)];
+ }
+};
+
+template <uint16 offset, std::size_t loByteIndex, std::size_t hiByteIndex>
+struct DecodeInt16
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (mpt::byte_cast<uint8>(inBuf[loByteIndex]) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 8)) - offset;
+ }
+};
+
+template <std::size_t loByteIndex, std::size_t hiByteIndex>
+struct DecodeInt16Delta
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ uint16 delta;
+ DecodeInt16Delta()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(inBuf[loByteIndex]) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 8);
+ return static_cast<int16>(delta);
+ }
+};
+
+struct DecodeInt16Delta8
+{
+ using input_t = std::byte;
+ using output_t = int16;
+ static constexpr std::size_t input_inc = 2;
+ uint16 delta;
+ DecodeInt16Delta8()
+ : delta(0)
+ {
+ }
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ delta += mpt::byte_cast<uint8>(inBuf[0]);
+ int16 result = delta & 0xFF;
+ delta += mpt::byte_cast<uint8>(inBuf[1]);
+ result |= (delta << 8);
+ return result;
+ }
+};
+
+template <uint32 offset, std::size_t loByteIndex, std::size_t midByteIndex, std::size_t hiByteIndex>
+struct DecodeInt24
+{
+ using input_t = std::byte;
+ using output_t = int32;
+ static constexpr std::size_t input_inc = 3;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return ((mpt::byte_cast<uint8>(inBuf[loByteIndex]) << 8) | (mpt::byte_cast<uint8>(inBuf[midByteIndex]) << 16) | (mpt::byte_cast<uint8>(inBuf[hiByteIndex]) << 24)) - offset;
+ }
+};
+
+template <uint32 offset, std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeInt32
+{
+ using input_t = std::byte;
+ using output_t = int32;
+ static constexpr std::size_t input_inc = 4;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (mpt::byte_cast<uint8>(inBuf[loLoByteIndex]) | (mpt::byte_cast<uint8>(inBuf[loHiByteIndex]) << 8) | (mpt::byte_cast<uint8>(inBuf[hiLoByteIndex]) << 16) | (mpt::byte_cast<uint8>(inBuf[hiHiByteIndex]) << 24)) - offset;
+ }
+};
+
+template <uint64 offset, std::size_t b0, std::size_t b1, std::size_t b2, std::size_t b3, std::size_t b4, std::size_t b5, std::size_t b6, std::size_t b7>
+struct DecodeInt64
+{
+ using input_t = std::byte;
+ using output_t = int64;
+ static constexpr std::size_t input_inc = 8;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return (uint64(0)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b0])) << 0)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b1])) << 8)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b2])) << 16)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b3])) << 24)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b4])) << 32)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b5])) << 40)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b6])) << 48)
+ | (static_cast<uint64>(mpt::byte_cast<uint8>(inBuf[b7])) << 56))
+ - offset;
+ }
+};
+
+template <std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeFloat32
+{
+ using input_t = std::byte;
+ using output_t = float32;
+ static constexpr std::size_t input_inc = 4;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float32 val = IEEE754binary32LE(inBuf[loLoByteIndex], inBuf[loHiByteIndex], inBuf[hiLoByteIndex], inBuf[hiHiByteIndex]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0f)
+ {
+ val = 1.0f;
+ } else
+ {
+ val = -1.0f;
+ }
+ }
+ return val;
+ }
+};
+
+template <std::size_t loLoByteIndex, std::size_t loHiByteIndex, std::size_t hiLoByteIndex, std::size_t hiHiByteIndex>
+struct DecodeScaledFloat32
+{
+ using input_t = std::byte;
+ using output_t = float32;
+ static constexpr std::size_t input_inc = 4;
+ float factor;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float32 val = IEEE754binary32LE(inBuf[loLoByteIndex], inBuf[loHiByteIndex], inBuf[hiLoByteIndex], inBuf[hiHiByteIndex]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0f)
+ {
+ val = 1.0f;
+ } else
+ {
+ val = -1.0f;
+ }
+ }
+ return factor * val;
+ }
+ MPT_FORCEINLINE DecodeScaledFloat32(float scaleFactor)
+ : factor(scaleFactor)
+ {
+ return;
+ }
+};
+
+template <std::size_t b0, std::size_t b1, std::size_t b2, std::size_t b3, std::size_t b4, std::size_t b5, std::size_t b6, std::size_t b7>
+struct DecodeFloat64
+{
+ using input_t = std::byte;
+ using output_t = float64;
+ static constexpr std::size_t input_inc = 8;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ float64 val = IEEE754binary64LE(inBuf[b0], inBuf[b1], inBuf[b2], inBuf[b3], inBuf[b4], inBuf[b5], inBuf[b6], inBuf[b7]);
+ val = mpt::sanitize_nan(val);
+ if(std::isinf(val))
+ {
+ if(val >= 0.0)
+ {
+ val = 1.0;
+ } else
+ {
+ val = -1.0;
+ }
+ }
+ return val;
+ }
+};
+
+template <typename Tsample>
+struct DecodeIdentity
+{
+ using input_t = Tsample;
+ using output_t = Tsample;
+ static constexpr std::size_t input_inc = 1;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return *inBuf;
+ }
+};
+
+
+// Reads sample data with Func and passes it directly to Func2.
+// Func1::output_t and Func2::input_t must be identical
+template <typename Func2, typename Func1>
+struct ConversionChain
+{
+ using input_t = typename Func1::input_t;
+ using output_t = typename Func2::output_t;
+ static constexpr std::size_t input_inc = Func1::input_inc;
+ Func1 func1;
+ Func2 func2;
+ MPT_FORCEINLINE output_t operator()(const input_t *inBuf)
+ {
+ return func2(func1(inBuf));
+ }
+ MPT_FORCEINLINE ConversionChain(Func2 f2 = Func2(), Func1 f1 = Func1())
+ : func1(f1)
+ , func2(f2)
+ {
+ return;
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp
new file mode 100644
index 00000000..83792428
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleEncode.hpp
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/memory.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <algorithm>
+
+#include <cmath>
+#include <cstddef>
+#include <cstdlib>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SC
+{ // SC = _S_ample_C_onversion
+
+
+struct EncodeuLaw
+{
+ using input_t = int16;
+ using output_t = std::byte;
+ static constexpr uint8 exp_table[17] = {0, 7 << 4, 6 << 4, 5 << 4, 4 << 4, 3 << 4, 2 << 4, 1 << 4, 0 << 4, 0, 0, 0, 0, 0, 0, 0, 0};
+ static constexpr uint8 mant_table[17] = {0, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3};
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ uint16 x = static_cast<uint16>(val);
+ uint8 out = (x >> 8) & 0x80;
+ uint32 abs = x & 0x7fff;
+ if(x & 0x8000)
+ {
+ abs ^= 0x7fff;
+ abs += 1;
+ }
+ x = static_cast<uint16>(std::clamp(static_cast<uint32>(abs + (33 << 2)), static_cast<uint32>(0), static_cast<uint32>(0x7fff)));
+ int index = mpt::countl_zero(x);
+ out |= exp_table[index];
+ out |= (x >> mant_table[index]) & 0x0f;
+ out ^= 0xff;
+ return mpt::byte_cast<std::byte>(out);
+ }
+};
+
+
+struct EncodeALaw
+{
+ using input_t = int16;
+ using output_t = std::byte;
+ static constexpr uint8 exp_table[17] = {0, 7 << 4, 6 << 4, 5 << 4, 4 << 4, 3 << 4, 2 << 4, 1 << 4, 0 << 4, 0, 0, 0, 0, 0, 0, 0, 0};
+ static constexpr uint8 mant_table[17] = {0, 10, 9, 8, 7, 6, 5, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4};
+ MPT_FORCEINLINE output_t operator()(input_t val)
+ {
+ int16 sx = std::clamp(val, static_cast<int16>(-32767), static_cast<int16>(32767));
+ uint16 x = static_cast<uint16>(sx);
+ uint8 out = ((x & 0x8000) ^ 0x8000) >> 8;
+ x = static_cast<uint16>(std::abs(sx));
+ int index = mpt::countl_zero(x);
+ out |= exp_table[index];
+ out |= (x >> mant_table[index]) & 0x0f;
+ out ^= 0x55;
+ return mpt::byte_cast<std::byte>(out);
+ }
+};
+
+
+} // namespace SC
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp
new file mode 100644
index 00000000..2ba2b18b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/soundbase/SampleFormat.hpp
@@ -0,0 +1,391 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/utility.hpp"
+#include "openmpt/base/Int24.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <type_traits>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+class SampleFormat
+{
+public:
+ enum class Enum : uint8
+ {
+ Unsigned8 = 9, // do not change value (for compatibility with old configuration settings)
+ Int8 = 8, // do not change value (for compatibility with old configuration settings)
+ Int16 = 16, // do not change value (for compatibility with old configuration settings)
+ Int24 = 24, // do not change value (for compatibility with old configuration settings)
+ Int32 = 32, // do not change value (for compatibility with old configuration settings)
+ Float32 = 32 + 128, // do not change value (for compatibility with old configuration settings)
+ Float64 = 64 + 128, // do not change value (for compatibility with old configuration settings)
+ Invalid = 0
+ };
+ static constexpr SampleFormat::Enum Unsigned8 = SampleFormat::Enum::Unsigned8;
+ static constexpr SampleFormat::Enum Int8 = SampleFormat::Enum::Int8;
+ static constexpr SampleFormat::Enum Int16 = SampleFormat::Enum::Int16;
+ static constexpr SampleFormat::Enum Int24 = SampleFormat::Enum::Int24;
+ static constexpr SampleFormat::Enum Int32 = SampleFormat::Enum::Int32;
+ static constexpr SampleFormat::Enum Float32 = SampleFormat::Enum::Float32;
+ static constexpr SampleFormat::Enum Float64 = SampleFormat::Enum::Float64;
+ static constexpr SampleFormat::Enum Invalid = SampleFormat::Enum::Invalid;
+
+private:
+ SampleFormat::Enum value;
+
+ template <typename T>
+ static MPT_CONSTEXPRINLINE SampleFormat::Enum Sanitize(T x) noexcept
+ {
+ using uT = typename std::make_unsigned<T>::type;
+ uT val = static_cast<uT>(x);
+ if(!val)
+ {
+ return SampleFormat::Enum::Invalid;
+ }
+ if(val == static_cast<uT>(-8))
+ {
+ val = 8 + 1;
+ }
+ // float|64|32|16|8|?|?|unsigned
+ val &= 0b1'1111'00'1;
+ auto is_float = [](uT val) -> bool
+ {
+ return (val & 0b1'0000'00'0) ? true : false;
+ };
+ auto is_unsigned = [](uT val) -> bool
+ {
+ return (val & 0b0'0000'00'1) ? true : false;
+ };
+ auto has_size = [](uT val) -> bool
+ {
+ return (val & 0b0'1111'00'0) ? true : false;
+ };
+ auto unique_size = [](uT val) -> bool
+ {
+ val &= 0b0'1111'00'0;
+ if(val == 0b0'0001'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0010'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0011'00'0)
+ {
+ return true;
+ } else if(val == 0b0'0100'00'0)
+ {
+ return true;
+ } else if(val == 0b0'1000'00'0)
+ {
+ return true;
+ } else
+ {
+ return false;
+ }
+ };
+ auto get_size = [](uT val) -> std::size_t
+ {
+ val &= 0b0'1111'00'0;
+ if(val == 0b0'0001'00'0)
+ {
+ return 1;
+ } else if(val == 0b0'0010'00'0)
+ {
+ return 2;
+ } else if(val == 0b0'0011'00'0)
+ {
+ return 3;
+ } else if(val == 0b0'0100'00'0)
+ {
+ return 4;
+ } else if(val == 0b0'1000'00'0)
+ {
+ return 8;
+ } else
+ {
+ return 2; // default to 16bit
+ }
+ };
+ if(!has_size(val))
+ {
+ if(is_unsigned(val) && is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Invalid);
+ } else if(is_unsigned(val))
+ {
+ val = mpt::to_underlying(Enum::Unsigned8);
+ } else if(is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Float32);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Invalid);
+ }
+ } else if(!unique_size(val))
+ {
+ // order of size check matters
+ if((val & 0b0'0011'00'0) == 0b0'0011'00'0)
+ {
+ val = mpt::to_underlying(Enum::Int24);
+ } else if(val & 0b0'0010'00'0)
+ {
+ val = mpt::to_underlying(Enum::Int16);
+ } else if(val & 0b0'0100'00'0)
+ {
+ if(is_float(val))
+ {
+ val = mpt::to_underlying(Enum::Float32);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Int32);
+ }
+ } else if(val & 0b0'1000'00'0)
+ {
+ val = mpt::to_underlying(Enum::Float64);
+ } else if(val & 0b0'0001'00'0)
+ {
+ if(is_unsigned(val))
+ {
+ val = mpt::to_underlying(Enum::Unsigned8);
+ } else
+ {
+ val = mpt::to_underlying(Enum::Int8);
+ }
+ }
+ } else
+ {
+ if(is_unsigned(val) && (get_size(val) > 1))
+ {
+ val &= ~0b0'0000'00'1; // remove unsigned
+ }
+ if(is_float(val) && (get_size(val) < 4))
+ {
+ val &= ~0b1'0000'00'0; // remove float
+ }
+ if(!is_float(val) && (get_size(val) == 8))
+ {
+ val |= 0b1'0000'00'0; // add float
+ }
+ }
+ return static_cast<SampleFormat::Enum>(val);
+ }
+
+public:
+ MPT_CONSTEXPRINLINE SampleFormat() noexcept
+ : value(SampleFormat::Invalid)
+ {
+ }
+
+ MPT_CONSTEXPRINLINE SampleFormat(SampleFormat::Enum v) noexcept
+ : value(Sanitize(v))
+ {
+ }
+
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat &a, const SampleFormat &b) noexcept
+ {
+ return a.value == b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat &a, const SampleFormat &b) noexcept
+ {
+ return a.value != b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat::Enum &a, const SampleFormat &b) noexcept
+ {
+ return a == b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat::Enum &a, const SampleFormat &b) noexcept
+ {
+ return a != b.value;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator==(const SampleFormat &a, const SampleFormat::Enum &b) noexcept
+ {
+ return a.value == b;
+ }
+ friend MPT_CONSTEXPRINLINE bool operator!=(const SampleFormat &a, const SampleFormat::Enum &b) noexcept
+ {
+ return a.value != b;
+ }
+
+ MPT_CONSTEXPRINLINE bool IsValid() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8)
+ || (value == SampleFormat::Int8)
+ || (value == SampleFormat::Int16)
+ || (value == SampleFormat::Int24)
+ || (value == SampleFormat::Int32)
+ || (value == SampleFormat::Float32)
+ || (value == SampleFormat::Float64);
+ }
+
+ MPT_CONSTEXPRINLINE bool IsUnsigned() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8);
+ }
+ MPT_CONSTEXPRINLINE bool IsFloat() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Float32)
+ || (value == SampleFormat::Float64);
+ }
+ MPT_CONSTEXPRINLINE bool IsInt() const noexcept
+ {
+ return false
+ || (value == SampleFormat::Unsigned8)
+ || (value == SampleFormat::Int8)
+ || (value == SampleFormat::Int16)
+ || (value == SampleFormat::Int24)
+ || (value == SampleFormat::Int32);
+ }
+ MPT_CONSTEXPRINLINE uint8 GetSampleSize() const noexcept
+ {
+ return !IsValid() ? 0
+ : (value == SampleFormat::Unsigned8) ? 1
+ : (value == SampleFormat::Int8) ? 1
+ : (value == SampleFormat::Int16) ? 2
+ : (value == SampleFormat::Int24) ? 3
+ : (value == SampleFormat::Int32) ? 4
+ : (value == SampleFormat::Float32) ? 4
+ : (value == SampleFormat::Float64) ? 8
+ : 0;
+ }
+ MPT_CONSTEXPRINLINE uint8 GetBitsPerSample() const noexcept
+ {
+ return !IsValid() ? 0
+ : (value == SampleFormat::Unsigned8) ? 8
+ : (value == SampleFormat::Int8) ? 8
+ : (value == SampleFormat::Int16) ? 16
+ : (value == SampleFormat::Int24) ? 24
+ : (value == SampleFormat::Int32) ? 32
+ : (value == SampleFormat::Float32) ? 32
+ : (value == SampleFormat::Float64) ? 64
+ : 0;
+ }
+
+ MPT_CONSTEXPRINLINE operator SampleFormat::Enum() const noexcept
+ {
+ return value;
+ }
+ explicit MPT_CONSTEXPRINLINE operator std::underlying_type<SampleFormat::Enum>::type() const noexcept
+ {
+ return mpt::to_underlying(value);
+ }
+
+ // backward compatibility, conversion to/from integers
+ static MPT_CONSTEXPRINLINE SampleFormat FromInt(int x) noexcept
+ {
+ return SampleFormat(Sanitize(x));
+ }
+ static MPT_CONSTEXPRINLINE int ToInt(SampleFormat x) noexcept
+ {
+ return mpt::to_underlying(x.value);
+ }
+ MPT_CONSTEXPRINLINE int AsInt() const noexcept
+ {
+ return mpt::to_underlying(value);
+ }
+};
+
+
+template <typename Container>
+Container AllSampleFormats()
+{
+ return {SampleFormat::Float64, SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Int8, SampleFormat::Unsigned8};
+}
+
+template <typename Container>
+Container DefaultSampleFormats()
+{
+ return {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Int8};
+}
+
+template <typename Tsample>
+struct SampleFormatTraits;
+template <>
+struct SampleFormatTraits<uint8>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Unsigned8; }
+};
+template <>
+struct SampleFormatTraits<int8>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int8; }
+};
+template <>
+struct SampleFormatTraits<int16>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int16; }
+};
+template <>
+struct SampleFormatTraits<int24>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int24; }
+};
+template <>
+struct SampleFormatTraits<int32>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Int32; }
+};
+template <>
+struct SampleFormatTraits<float>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Float32; }
+};
+template <>
+struct SampleFormatTraits<double>
+{
+ static MPT_CONSTEXPRINLINE SampleFormat sampleFormat() { return SampleFormat::Float64; }
+};
+
+template <SampleFormat::Enum sampleFormat>
+struct SampleFormatToType;
+template <>
+struct SampleFormatToType<SampleFormat::Unsigned8>
+{
+ typedef uint8 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int8>
+{
+ typedef int8 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int16>
+{
+ typedef int16 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int24>
+{
+ typedef int24 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Int32>
+{
+ typedef int32 type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Float32>
+{
+ typedef float type;
+};
+template <>
+struct SampleFormatToType<SampleFormat::Float64>
+{
+ typedef double type;
+};
+
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp
new file mode 100644
index 00000000..e0448b1c
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.cpp
@@ -0,0 +1,175 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/binary/hex.hpp"
+#include "mpt/format/join.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+
+#include <map>
+#include <string>
+#include <vector>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+SoundDevice::Type ParseType(const SoundDevice::Identifier &identifier)
+{
+ std::vector<mpt::ustring> tmp = mpt::split(identifier, MPT_USTRING("_"));
+ if(tmp.size() == 0)
+ {
+ return SoundDevice::Type();
+ }
+ return tmp[0];
+}
+
+
+mpt::ustring Info::GetDisplayName() const
+{
+ mpt::ustring result = apiName + MPT_USTRING(" - ") + mpt::trim(name);
+ switch(flags.usability)
+ {
+ case SoundDevice::Info::Usability::Experimental:
+ result += MPT_USTRING(" [experimental]");
+ break;
+ case SoundDevice::Info::Usability::Deprecated:
+ result += MPT_USTRING(" [deprecated]");
+ break;
+ case SoundDevice::Info::Usability::Broken:
+ result += MPT_USTRING(" [broken]");
+ break;
+ case SoundDevice::Info::Usability::NotAvailable:
+ result += MPT_USTRING(" [alien]");
+ break;
+ default:
+ // nothing
+ break;
+ }
+ if(default_ == SoundDevice::Info::Default::Named)
+ {
+ result += MPT_USTRING(" [default]");
+ }
+ if(apiPath.size() > 0)
+ {
+ result += MPT_USTRING(" (") + mpt::join(apiPath, MPT_USTRING("/")) + MPT_USTRING(")");
+ }
+ return result;
+}
+
+
+SoundDevice::Identifier Info::GetIdentifier() const
+{
+ if(!IsValid())
+ {
+ return mpt::ustring();
+ }
+ mpt::ustring result = mpt::ustring();
+ result += type;
+ result += MPT_USTRING("_");
+ if(useNameAsIdentifier)
+ {
+ // UTF8-encode the name and convert the utf8 to hex.
+ // This ensures that no special characters are contained in the configuration key.
+ std::string utf8String = mpt::transcode<std::string>(mpt::common_encoding::utf8, name);
+ mpt::ustring hexString = mpt::encode_hex(mpt::as_span(utf8String));
+ result += hexString;
+ } else
+ {
+ result += internalID; // safe to not contain special characters
+ }
+ return result;
+}
+
+
+ChannelMapping::ChannelMapping(uint32 numHostChannels)
+{
+ ChannelToDeviceChannel.resize(numHostChannels);
+ for(uint32 channel = 0; channel < numHostChannels; ++channel)
+ {
+ ChannelToDeviceChannel[channel] = channel;
+ }
+}
+
+
+ChannelMapping::ChannelMapping(const std::vector<int32> &mapping)
+{
+ if(IsValid(mapping))
+ {
+ ChannelToDeviceChannel = mapping;
+ }
+}
+
+
+ChannelMapping ChannelMapping::BaseChannel(uint32 channels, int32 baseChannel)
+{
+ SoundDevice::ChannelMapping result;
+ result.ChannelToDeviceChannel.resize(channels);
+ for(uint32 channel = 0; channel < channels; ++channel)
+ {
+ result.ChannelToDeviceChannel[channel] = channel + baseChannel;
+ }
+ return result;
+}
+
+
+bool ChannelMapping::IsValid(const std::vector<int32> &mapping)
+{
+ if(mapping.empty())
+ {
+ return true;
+ }
+ std::map<int32, uint32> inverseMapping;
+ for(uint32 hostChannel = 0; hostChannel < mapping.size(); ++hostChannel)
+ {
+ int32 deviceChannel = mapping[hostChannel];
+ if(deviceChannel < 0)
+ {
+ return false;
+ }
+ if(deviceChannel > MaxDeviceChannel)
+ {
+ return false;
+ }
+ inverseMapping[deviceChannel] = hostChannel;
+ }
+ if(inverseMapping.size() != mapping.size())
+ {
+ return false;
+ }
+ return true;
+}
+
+
+mpt::ustring ChannelMapping::ToUString() const
+{
+ return mpt::join_format<mpt::ustring, int32>(ChannelToDeviceChannel, MPT_USTRING(","));
+}
+
+
+ChannelMapping ChannelMapping::FromString(const mpt::ustring &str)
+{
+ return SoundDevice::ChannelMapping(mpt::split_parse<int32>(str, MPT_USTRING(",")));
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp
new file mode 100644
index 00000000..0f08534f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevice.hpp
@@ -0,0 +1,640 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/osinfo/class.hpp"
+#include "mpt/osinfo/windows_version.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/FlagSet.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if defined(MODPLUG_TRACKER)
+#include "Logging.h"
+#endif // MODPLUG_TRACKER
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifndef MPT_SOUNDDEV_TRACE
+#if defined(MODPLUG_TRACKER)
+#define MPT_SOUNDDEV_TRACE() MPT_TRACE()
+#else // !MODPLUG_TRACKER
+#define MPT_SOUNDDEV_TRACE() \
+ do \
+ { \
+ } while(0)
+#endif // MODPLUG_TRACKER
+#endif
+#ifndef MPT_SOUNDDEV_TRACE_SCOPE
+#if defined(MODPLUG_TRACKER)
+#define MPT_SOUNDDEV_TRACE_SCOPE() MPT_TRACE_SCOPE()
+#else // !MODPLUG_TRACKER
+#define MPT_SOUNDDEV_TRACE_SCOPE() \
+ do \
+ { \
+ } while(0)
+#endif // MODPLUG_TRACKER
+#endif
+
+
+class IMessageReceiver
+{
+public:
+ virtual void SoundDeviceMessage(LogLevel level, const mpt::ustring &str) = 0;
+};
+
+
+inline constexpr mpt::uchar TypeWAVEOUT[] = MPT_ULITERAL("WaveOut");
+inline constexpr mpt::uchar TypeDSOUND[] = MPT_ULITERAL("DirectSound");
+inline constexpr mpt::uchar TypeASIO[] = MPT_ULITERAL("ASIO");
+inline constexpr mpt::uchar TypePORTAUDIO_WASAPI[] = MPT_ULITERAL("WASAPI");
+inline constexpr mpt::uchar TypePORTAUDIO_WDMKS[] = MPT_ULITERAL("WDM-KS");
+inline constexpr mpt::uchar TypePORTAUDIO_WMME[] = MPT_ULITERAL("MME");
+inline constexpr mpt::uchar TypePORTAUDIO_DS[] = MPT_ULITERAL("DS");
+
+typedef mpt::ustring Type;
+
+
+typedef mpt::ustring Identifier;
+
+SoundDevice::Type ParseType(const SoundDevice::Identifier &identifier);
+
+
+struct Info
+{
+ SoundDevice::Type type;
+ mpt::ustring internalID;
+ mpt::ustring name; // user visible (and configuration key if useNameAsIdentifier)
+ mpt::ustring apiName; // user visible
+ std::vector<mpt::ustring> apiPath; // i.e. Wine-support, PortAudio
+ enum class Default
+ {
+ None = 0,
+ Named = 1,
+ Managed = 2,
+ };
+ Default default_;
+ bool useNameAsIdentifier;
+
+ enum class DefaultFor : int8
+ {
+ System = 3,
+ ProAudio = 2,
+ LowLevel = 1,
+ None = 0,
+ };
+ struct ManagerFlags
+ {
+ DefaultFor defaultFor = DefaultFor::None;
+ };
+ ManagerFlags managerFlags;
+
+ enum class Usability : int8
+ {
+ Usable = 3,
+ Experimental = 2,
+ Legacy = 1,
+ Unknown = 0,
+ Deprecated = -4,
+ Broken = -5,
+ NotAvailable = -6,
+ };
+ enum class Level : int8
+ {
+ Primary = 1,
+ Unknown = 0,
+ Secondary = -1,
+ };
+ enum class Compatible : int8
+ {
+ Yes = 1,
+ Unknown = 0,
+ No = -1,
+ };
+ enum class Api : int8
+ {
+ Native = 1,
+ Unknown = 0,
+ Emulated = -1,
+ };
+ enum class Io : int8
+ {
+ FullDuplex = 1,
+ Unknown = 0,
+ OutputOnly = -1,
+ };
+ enum class Mixing : int8
+ {
+ Server = 2,
+ Software = 1,
+ Unknown = 0,
+ Hardware = -1,
+ };
+ enum class Implementor : int8
+ {
+ OpenMPT = 1,
+ Unknown = 0,
+ External = -1,
+ };
+ struct Flags
+ {
+ Usability usability = Usability::Unknown;
+ Level level = Level::Unknown;
+ Compatible compatible = Compatible::Unknown;
+ Api api = Api::Unknown;
+ Io io = Io::Unknown;
+ Mixing mixing = Mixing::Unknown;
+ Implementor implementor = Implementor::Unknown;
+ };
+ Flags flags;
+
+ std::map<mpt::ustring, mpt::ustring> extraData; // user visible (hidden by default)
+
+ Info()
+ : default_(Default::None)
+ , useNameAsIdentifier(false)
+ {
+ }
+
+ bool IsValid() const
+ {
+ return !type.empty() && !internalID.empty();
+ }
+
+ bool IsDeprecated() const
+ {
+ return (static_cast<int8>(flags.usability) <= 0) || (static_cast<int8>(flags.level) <= 0);
+ }
+
+ SoundDevice::Identifier GetIdentifier() const;
+
+ mpt::ustring GetDisplayName() const;
+};
+
+
+struct ChannelMapping
+{
+
+private:
+ std::vector<int32> ChannelToDeviceChannel;
+
+public:
+ static constexpr int32 MaxDeviceChannel = 32000;
+
+public:
+ // Construct default identity mapping
+ ChannelMapping(uint32 numHostChannels = 2);
+
+ // Construct mapping from given vector.
+ // Silently fall back to identity mapping if mapping is invalid.
+ ChannelMapping(const std::vector<int32> &mapping);
+
+ // Construct mapping for #channels with a baseChannel offset.
+ static ChannelMapping BaseChannel(uint32 channels, int32 baseChannel);
+
+private:
+ // check that the channel mapping is actually a 1:1 mapping
+ static bool IsValid(const std::vector<int32> &mapping);
+
+public:
+ operator int() const
+ {
+ return GetNumHostChannels();
+ }
+
+ ChannelMapping &operator=(int channels)
+ {
+ return (*this = ChannelMapping(channels));
+ }
+
+ friend bool operator==(const SoundDevice::ChannelMapping &a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a.ChannelToDeviceChannel == b.ChannelToDeviceChannel);
+ }
+
+ friend bool operator!=(const SoundDevice::ChannelMapping &a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a.ChannelToDeviceChannel != b.ChannelToDeviceChannel);
+ }
+
+ friend bool operator==(int a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a == static_cast<int>(b));
+ }
+
+ friend bool operator==(const SoundDevice::ChannelMapping &a, int b)
+ {
+ return (static_cast<int>(a) == b);
+ }
+
+ friend bool operator!=(int a, const SoundDevice::ChannelMapping &b)
+ {
+ return (a != static_cast<int>(b));
+ }
+
+ friend bool operator!=(const SoundDevice::ChannelMapping &a, int b)
+ {
+ return (static_cast<int>(a) != b);
+ }
+
+ uint32 GetNumHostChannels() const
+ {
+ return static_cast<uint32>(ChannelToDeviceChannel.size());
+ }
+
+ // Get the number of required device channels for this mapping. Derived from the maximum mapped-to channel number.
+ int32 GetRequiredDeviceChannels() const
+ {
+ if(ChannelToDeviceChannel.empty())
+ {
+ return 0;
+ }
+ int32 maxChannel = 0;
+ for(uint32 channel = 0; channel < ChannelToDeviceChannel.size(); ++channel)
+ {
+ if(ChannelToDeviceChannel[channel] > maxChannel)
+ {
+ maxChannel = ChannelToDeviceChannel[channel];
+ }
+ }
+ return maxChannel + 1;
+ }
+
+ // Convert OpenMPT channel number to the mapped device channel number.
+ int32 ToDevice(uint32 channel) const
+ {
+ if(channel >= ChannelToDeviceChannel.size())
+ {
+ return channel;
+ }
+ return ChannelToDeviceChannel[channel];
+ }
+
+ mpt::ustring ToUString() const;
+
+ static SoundDevice::ChannelMapping FromString(const mpt::ustring &str);
+};
+
+
+struct SysInfo
+{
+public:
+ mpt::osinfo::osclass SystemClass = mpt::osinfo::osclass::Unknown;
+ mpt::osinfo::windows::Version WindowsVersion = mpt::osinfo::windows::Version::NoWindows();
+ bool IsWine = false;
+ mpt::osinfo::osclass WineHostClass = mpt::osinfo::osclass::Unknown;
+ mpt::osinfo::windows::wine::version WineVersion;
+
+public:
+ bool IsOriginal() const { return !IsWine; }
+ bool IsWindowsOriginal() const { return !IsWine; }
+ bool IsWindowsWine() const { return IsWine; }
+
+public:
+ SysInfo() = delete;
+ SysInfo(mpt::osinfo::osclass systemClass)
+ : SystemClass(systemClass)
+ {
+ assert(SystemClass != mpt::osinfo::osclass::Windows);
+ return;
+ }
+ SysInfo(mpt::osinfo::osclass systemClass, mpt::osinfo::windows::Version windowsVersion)
+ : SystemClass(systemClass)
+ , WindowsVersion(windowsVersion)
+ {
+ return;
+ }
+ SysInfo(mpt::osinfo::osclass systemClass, mpt::osinfo::windows::Version windowsVersion, bool isWine, mpt::osinfo::osclass wineHostClass, mpt::osinfo::windows::wine::version wineVersion)
+ : SystemClass(systemClass)
+ , WindowsVersion(windowsVersion)
+ , IsWine(isWine)
+ , WineHostClass(wineHostClass)
+ , WineVersion(wineVersion)
+ {
+ return;
+ }
+};
+
+
+struct AppInfo
+{
+ mpt::ustring Name;
+ std::uintptr_t UIHandle; // HWND on Windows
+ int BoostedThreadPriorityXP;
+ mpt::ustring BoostedThreadMMCSSClassVista;
+ bool BoostedThreadRealtimePosix;
+ int BoostedThreadNicenessPosix;
+ int BoostedThreadRtprioPosix;
+#if defined(MODPLUG_TRACKER)
+ bool MaskDriverCrashes;
+#endif // MODPLUG_TRACKER
+ bool AllowDeferredProcessing;
+ AppInfo()
+ : UIHandle(0)
+ , BoostedThreadPriorityXP(2) // THREAD_PRIORITY_HIGHEST
+ , BoostedThreadMMCSSClassVista(MPT_USTRING("Pro Audio"))
+ , BoostedThreadRealtimePosix(false)
+ , BoostedThreadNicenessPosix(-5)
+ , BoostedThreadRtprioPosix(10)
+#if defined(MODPLUG_TRACKER)
+ , MaskDriverCrashes(false)
+#endif // MODPLUG_TRACKER
+ , AllowDeferredProcessing(true)
+ {
+ return;
+ }
+ AppInfo &SetName(const mpt::ustring &name)
+ {
+ Name = name;
+ return *this;
+ }
+ mpt::ustring GetName() const { return Name; }
+#if MPT_OS_WINDOWS
+ AppInfo &SetHWND(HWND hwnd)
+ {
+ UIHandle = reinterpret_cast<uintptr_t>(hwnd);
+ return *this;
+ }
+ HWND GetHWND() const { return reinterpret_cast<HWND>(UIHandle); }
+#endif // MPT_OS_WINDOWS
+};
+
+
+struct Settings
+{
+ double Latency; // seconds
+ double UpdateInterval; // seconds
+ uint32 Samplerate;
+ SoundDevice::ChannelMapping Channels;
+ uint8 InputChannels;
+ SampleFormat sampleFormat;
+ bool ExclusiveMode; // Use hardware buffers directly
+ bool BoostThreadPriority; // Boost thread priority for glitch-free audio rendering
+ bool KeepDeviceRunning;
+ bool UseHardwareTiming;
+ int32 DitherType;
+ uint32 InputSourceID;
+ Settings()
+ : Latency(0.1)
+ , UpdateInterval(0.005)
+ , Samplerate(48000)
+ , Channels(2)
+ , InputChannels(0)
+ , sampleFormat(SampleFormat::Float32)
+ , ExclusiveMode(false)
+ , BoostThreadPriority(true)
+ , KeepDeviceRunning(true)
+ , UseHardwareTiming(false)
+ , DitherType(1)
+ , InputSourceID(0)
+ {
+ return;
+ }
+ bool operator==(const SoundDevice::Settings &cmp) const
+ {
+ return true
+ && mpt::saturate_round<int64>(Latency * 1000000000.0) == mpt::saturate_round<int64>(cmp.Latency * 1000000000.0) // compare in nanoseconds
+ && mpt::saturate_round<int64>(UpdateInterval * 1000000000.0) == mpt::saturate_round<int64>(cmp.UpdateInterval * 1000000000.0) // compare in nanoseconds
+ && Samplerate == cmp.Samplerate
+ && Channels == cmp.Channels
+ && InputChannels == cmp.InputChannels
+ && sampleFormat == cmp.sampleFormat
+ && ExclusiveMode == cmp.ExclusiveMode
+ && BoostThreadPriority == cmp.BoostThreadPriority
+ && KeepDeviceRunning == cmp.KeepDeviceRunning
+ && UseHardwareTiming == cmp.UseHardwareTiming
+ && DitherType == cmp.DitherType
+ && InputSourceID == cmp.InputSourceID;
+ }
+ bool operator!=(const SoundDevice::Settings &cmp) const
+ {
+ return !(*this == cmp);
+ }
+ std::size_t GetBytesPerFrame() const
+ {
+ return sampleFormat.GetSampleSize() * Channels;
+ }
+ std::size_t GetBytesPerSecond() const
+ {
+ return Samplerate * GetBytesPerFrame();
+ }
+ uint32 GetTotalChannels() const
+ {
+ return InputChannels + Channels;
+ }
+};
+
+
+struct Flags
+{
+ // Windows since Vista has a limiter/compressor in the audio path that kicks
+ // in as soon as there are samples > 0dBFs (i.e. the absolute float value >
+ // 1.0). This happens for all APIs that get processed through the system-
+ // wide audio engine. It does not happen for exclusive mode WASAPI streams
+ // or direct WaveRT (labeled WDM-KS in PortAudio) streams. As there is no
+ // known way to disable this annoying behavior, avoid unclipped samples on
+ // affected windows versions and clip them ourselves before handing them to
+ // the APIs.
+ bool WantsClippedOutput;
+ Flags()
+ : WantsClippedOutput(false)
+ {
+ return;
+ }
+};
+
+
+struct Caps
+{
+ bool Available;
+ bool CanUpdateInterval;
+ bool CanSampleFormat;
+ bool CanExclusiveMode;
+ bool CanBoostThreadPriority;
+ bool CanKeepDeviceRunning;
+ bool CanUseHardwareTiming;
+ bool CanChannelMapping;
+ bool CanInput;
+ bool HasNamedInputSources;
+ bool CanDriverPanel;
+ bool HasInternalDither;
+ mpt::ustring ExclusiveModeDescription;
+ double LatencyMin;
+ double LatencyMax;
+ double UpdateIntervalMin;
+ double UpdateIntervalMax;
+ SoundDevice::Settings DefaultSettings;
+ Caps()
+ : Available(false)
+ , CanUpdateInterval(true)
+ , CanSampleFormat(true)
+ , CanExclusiveMode(false)
+ , CanBoostThreadPriority(true)
+ , CanKeepDeviceRunning(false)
+ , CanUseHardwareTiming(false)
+ , CanChannelMapping(false)
+ , CanInput(false)
+ , HasNamedInputSources(false)
+ , CanDriverPanel(false)
+ , HasInternalDither(false)
+ , ExclusiveModeDescription(MPT_USTRING("Use device exclusively"))
+ , LatencyMin(0.002) // 2ms
+ , LatencyMax(0.5) // 500ms
+ , UpdateIntervalMin(0.001) // 1ms
+ , UpdateIntervalMax(0.2) // 200ms
+ {
+ return;
+ }
+};
+
+
+struct DynamicCaps
+{
+ uint32 currentSampleRate = 0;
+ std::vector<uint32> supportedSampleRates;
+ std::vector<uint32> supportedExclusiveSampleRates;
+ std::vector<SampleFormat> supportedSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ std::vector<SampleFormat> supportedExclusiveModeSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ std::vector<mpt::ustring> channelNames;
+ std::vector<std::pair<uint32, mpt::ustring>> inputSourceNames;
+};
+
+
+struct BufferAttributes
+{
+ double Latency; // seconds
+ double UpdateInterval; // seconds
+ int NumBuffers;
+ BufferAttributes()
+ : Latency(0.0)
+ , UpdateInterval(0.0)
+ , NumBuffers(0)
+ {
+ return;
+ }
+};
+
+
+enum RequestFlags : uint32
+{
+ RequestFlagClose = 1 << 0,
+ RequestFlagReset = 1 << 1,
+ RequestFlagRestart = 1 << 2,
+};
+MPT_DECLARE_ENUM(RequestFlags)
+
+
+struct Statistics
+{
+ double InstantaneousLatency;
+ double LastUpdateInterval;
+ mpt::ustring text;
+ Statistics()
+ : InstantaneousLatency(0.0)
+ , LastUpdateInterval(0.0)
+ {
+ return;
+ }
+};
+
+
+class BackendInitializer
+{
+public:
+ BackendInitializer() = default;
+ virtual void Reload()
+ {
+ return;
+ }
+ virtual ~BackendInitializer() = default;
+};
+
+
+class IBase
+{
+
+protected:
+ IBase() = default;
+
+public:
+ virtual ~IBase() = default;
+
+public:
+ virtual void SetMessageReceiver(SoundDevice::IMessageReceiver *receiver) = 0;
+ virtual void SetCallback(SoundDevice::ICallback *callback) = 0;
+
+ virtual SoundDevice::Info GetDeviceInfo() const = 0;
+
+ virtual SoundDevice::Caps GetDeviceCaps() const = 0;
+ virtual SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates) = 0;
+
+ virtual bool Init(const SoundDevice::AppInfo &appInfo) = 0;
+ virtual bool Open(const SoundDevice::Settings &settings) = 0;
+ virtual bool Close() = 0;
+ virtual bool Start() = 0;
+ virtual void Stop() = 0;
+
+ virtual FlagSet<RequestFlags> GetRequestFlags() const = 0;
+
+ virtual bool IsInited() const = 0;
+ virtual bool IsOpen() const = 0;
+ virtual bool IsAvailable() const = 0;
+ virtual bool IsPlaying() const = 0;
+
+ virtual bool IsPlayingSilence() const = 0;
+ virtual void StopAndAvoidPlayingSilence() = 0;
+ virtual void EndPlayingSilence() = 0;
+
+ virtual bool OnIdle() = 0; // return true if any work has been done
+
+ virtual SoundDevice::Settings GetSettings() const = 0;
+ virtual SampleFormat GetActualSampleFormat() const = 0;
+ virtual SoundDevice::BufferAttributes GetEffectiveBufferAttributes() const = 0;
+
+ virtual SoundDevice::TimeInfo GetTimeInfo() const = 0;
+ virtual SoundDevice::StreamPosition GetStreamPosition() const = 0;
+
+ // Debugging aids in case of a crash
+ virtual bool DebugIsFragileDevice() const = 0;
+ virtual bool DebugInRealtimeCallback() const = 0;
+
+ // Informational only, do not use for timing.
+ // Use GetStreamPositionFrames() for timing
+ virtual SoundDevice::Statistics GetStatistics() const = 0;
+
+ virtual bool OpenDriverSettings() = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp
new file mode 100644
index 00000000..14b3bd67
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.cpp
@@ -0,0 +1,1405 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifdef MPT_WITH_ASIO
+
+#include "SoundDeviceASIO.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/bit.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/base/utility.hpp"
+#include "mpt/exception_text/exception_text.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#if defined(MODPLUG_TRACKER)
+#include "mptAssert.h"
+#if !defined(MPT_BUILD_WINESUPPORT)
+#include "../mptrack/ExceptionHandler.h"
+#endif // !MPT_BUILD_WINESUPPORT
+#endif // MODPLUG_TRACKER
+
+#include <algorithm>
+#include <atomic>
+#include <chrono>
+#include <exception>
+#include <memory>
+#include <new>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+
+#include <ASIOModern/ASIO.hpp>
+#include <ASIOModern/ASIOSystemWindows.hpp>
+#if defined(MODPLUG_TRACKER)
+#include <ASIOModern/ASIOSystemWindowsSEH.hpp>
+#endif // MODPLUG_TRACKER
+#include <ASIOModern/ASIOSampleConvert.hpp>
+//#include <ASIOModern/ASIOVerifyABI.hpp>
+
+#endif // MPT_WITH_ASIO
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_ASIO
+
+
+static constexpr uint64 AppID1 = 0x4f70656e4d50542dull; // "OpenMPT-"
+static constexpr uint64 AppID2 = 0x4153494f00000000ull; // "ASIO"
+
+static constexpr double AsioSampleRateTolerance = 0.05;
+
+
+static constexpr inline auto value_cast(ASIO::Bool b) noexcept -> bool
+{
+ return static_cast<bool>(b);
+}
+
+
+// Helper class to temporarily open a driver for a query.
+class TemporaryASIODriverOpener
+{
+protected:
+ CASIODevice &device;
+ const bool wasOpen;
+
+public:
+ TemporaryASIODriverOpener(CASIODevice &d)
+ : device(d)
+ , wasOpen(d.IsDriverOpen())
+ {
+ if(!wasOpen)
+ {
+ device.OpenDriver();
+ }
+ }
+
+ ~TemporaryASIODriverOpener()
+ {
+ if(!wasOpen)
+ {
+ device.CloseDriver();
+ }
+ }
+};
+
+
+static mpt::winstring AsWinstring(const std::basic_string<TCHAR> &str)
+{
+ return mpt::winstring(str.data(), str.length());
+}
+
+
+std::vector<SoundDevice::Info> CASIODevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ std::vector<SoundDevice::Info> devices;
+ std::vector<ASIO::Windows::DriverInfo> drivers = ASIO::Windows::EnumerateDrivers();
+ for(const auto &driver : drivers)
+ {
+ SoundDevice::Info info;
+ info.type = TypeASIO;
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::CLSIDToString(driver.Clsid));
+ info.apiName = MPT_USTRING("ASIO");
+ info.name = mpt::transcode<mpt::ustring>(AsWinstring(driver.DisplayName()));
+ info.useNameAsIdentifier = false;
+ info.default_ = Info::Default::None;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ info.extraData[MPT_USTRING("Key")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Key));
+ ;
+ info.extraData[MPT_USTRING("Id")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Id));
+ info.extraData[MPT_USTRING("CLSID")] = mpt::transcode<mpt::ustring>(mpt::CLSIDToString(driver.Clsid));
+ info.extraData[MPT_USTRING("Name")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Name));
+ ;
+ info.extraData[MPT_USTRING("Description")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.Description));
+ ;
+ info.extraData[MPT_USTRING("DisplayName")] = mpt::transcode<mpt::ustring>(AsWinstring(driver.DisplayName()));
+ ;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Found driver:")());
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Key = '{}'")(info.extraData[MPT_USTRING("Key")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Id = '{}'")(info.extraData[MPT_USTRING("Id")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: CLSID = '{}'")(info.extraData[MPT_USTRING("CLSID")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Name = '{}'")(info.extraData[MPT_USTRING("Name")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Description = '{}'")(info.extraData[MPT_USTRING("Description")]));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: DisplayName = '{}'")(info.extraData[MPT_USTRING("DisplayName")]));
+ devices.push_back(info);
+ }
+ return devices;
+}
+
+
+CASIODevice::CASIODevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_RenderSilence(false)
+ , m_RenderingSilence(false)
+ , m_AsioRequest(0)
+ , m_UsedFeatures(0)
+ , m_DebugRealtimeThreadID(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_Ectx.SetDescription(MPT_UFORMAT_MESSAGE("ASIO Driver: {}")(GetDeviceInternalID()));
+ InitMembers();
+}
+
+
+void CASIODevice::InitMembers()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_DeferredBufferSwitchDispatcher = nullptr;
+ m_Driver = nullptr;
+
+ m_BufferLatency = 0.0;
+ m_nAsioBufferLen = 0;
+ m_BufferInfo.clear();
+ m_BuffersCreated = false;
+ m_ChannelInfo.clear();
+ m_SampleBufferDouble.clear();
+ m_SampleBufferFloat.clear();
+ m_SampleBufferInt16.clear();
+ m_SampleBufferInt24.clear();
+ m_SampleBufferInt32.clear();
+ m_SampleInputBufferDouble.clear();
+ m_SampleInputBufferFloat.clear();
+ m_SampleInputBufferInt16.clear();
+ m_SampleInputBufferInt24.clear();
+ m_SampleInputBufferInt32.clear();
+ m_CanOutputReady = false;
+
+ m_DeviceRunning = false;
+ m_TotalFramesWritten = 0;
+ m_DeferredProcessing = false;
+ m_BufferIndex = 0;
+ m_RenderSilence = false;
+ m_RenderingSilence = false;
+
+ m_AsioRequest.store(0);
+
+ m_DebugRealtimeThreadID.store(0);
+}
+
+
+bool CASIODevice::HandleRequests()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ uint32 flags = m_AsioRequest.exchange(0);
+ if(flags & AsioRequest::LatenciesChanged)
+ {
+ UpdateLatency();
+ result = true;
+ }
+ return result;
+}
+
+
+CASIODevice::~CASIODevice()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ Close();
+}
+
+
+bool CASIODevice::InternalOpen()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ assert(!IsDriverOpen());
+
+ InitMembers();
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Open('{}'): {}-bit, ({},{}) channels, {}Hz, hw-timing={}")(GetDeviceInternalID(), m_Settings.sampleFormat.GetBitsPerSample(), m_Settings.InputChannels, static_cast<int>(m_Settings.Channels), m_Settings.Samplerate, m_Settings.UseHardwareTiming));
+
+ SoundDevice::ChannelMapping inputChannelMapping = SoundDevice::ChannelMapping::BaseChannel(m_Settings.InputChannels, m_Settings.InputSourceID);
+
+ try
+ {
+
+ OpenDriver();
+
+ if(!IsDriverOpen())
+ {
+ throw ASIOException("Initializing driver failed.");
+ }
+
+ ASIO::Channels channels = AsioDriver()->getChannels();
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getChannels() => inputChannels={} outputChannel={}")(channels.Input, channels.Output));
+ if(channels.Input <= 0 && channels.Output <= 0)
+ {
+ m_DeviceUnavailableOnOpen = true;
+ throw ASIOException("Device unavailble.");
+ }
+ if(m_Settings.Channels > channels.Output)
+ {
+ throw ASIOException("Not enough output channels.");
+ }
+ if(m_Settings.Channels.GetRequiredDeviceChannels() > channels.Output)
+ {
+ throw ASIOException("Channel mapping requires more channels than available.");
+ }
+ if(m_Settings.InputChannels > channels.Input)
+ {
+ throw ASIOException("Not enough input channels.");
+ }
+ if(inputChannelMapping.GetRequiredDeviceChannels() > channels.Input)
+ {
+ throw ASIOException("Channel mapping requires more channels than available.");
+ }
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: setSampleRate(sampleRate={})")(m_Settings.Samplerate));
+ AsioDriver()->setSampleRate(m_Settings.Samplerate);
+
+ ASIO::BufferSizes bufferSizes = AsioDriver()->getBufferSizes();
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getBufferSize() => minSize={} maxSize={} preferredSize={} granularity={}")(bufferSizes.Min, bufferSizes.Max, bufferSizes.Preferred, bufferSizes.Granularity));
+ m_nAsioBufferLen = mpt::saturate_round<int32>(m_Settings.Latency * m_Settings.Samplerate / 2.0);
+ if(bufferSizes.Min <= 0 || bufferSizes.Max <= 0 || bufferSizes.Min > bufferSizes.Max)
+ { // limits make no sense
+ if(bufferSizes.Preferred > 0)
+ {
+ m_nAsioBufferLen = bufferSizes.Preferred;
+ } else
+ {
+ // just leave the user value, perhaps it works
+ }
+ } else if(bufferSizes.Granularity < -1)
+ { // bufferSizes.Granularity value not allowed, just clamp value
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ } else if(bufferSizes.Granularity == -1 && (mpt::popcount(static_cast<ASIO::ULong>(bufferSizes.Min)) != 1 || mpt::popcount(static_cast<ASIO::ULong>(bufferSizes.Max)) != 1))
+ { // bufferSizes.Granularity tells us we need power-of-2 sizes, but min or max sizes are no power-of-2
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // just start at 1 and find a matching power-of-2 in range
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = 1; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize *= 2)
+ {
+ if(bufSize >= bufferSizes.Min)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ }
+ // if no power-of-2 in range is found, just leave the clamped value alone, perhaps it works
+ } else if(bufferSizes.Granularity == -1)
+ { // sane values, power-of-2 size required between min and max
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // get the largest allowed buffer size that is smaller or equal to the target size
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = bufferSizes.Min; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize *= 2)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ } else if(bufferSizes.Granularity > 0)
+ { // buffer size in bufferSizes.Granularity steps from min to max allowed
+ m_nAsioBufferLen = std::clamp(m_nAsioBufferLen, bufferSizes.Min, bufferSizes.Max);
+ // get the largest allowed buffer size that is smaller or equal to the target size
+ const ASIO::Long bufTarget = m_nAsioBufferLen;
+ for(ASIO::Long bufSize = bufferSizes.Min; bufSize <= bufferSizes.Max && bufSize <= bufTarget; bufSize += bufferSizes.Granularity)
+ {
+ m_nAsioBufferLen = bufSize;
+ }
+ } else if(bufferSizes.Granularity == 0)
+ { // no bufferSizes.Granularity given, we should use bufferSizes.Preferred if possible
+ if(bufferSizes.Preferred > 0)
+ {
+ m_nAsioBufferLen = bufferSizes.Preferred;
+ } else if(m_nAsioBufferLen >= bufferSizes.Max)
+ { // a large latency was requested, use bufferSizes.Max
+ m_nAsioBufferLen = bufferSizes.Max;
+ } else
+ { // use bufferSizes.Min otherwise
+ m_nAsioBufferLen = bufferSizes.Min;
+ }
+ } else
+ { // should not happen
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+
+ m_BufferInfo.resize(m_Settings.GetTotalChannels());
+ for(uint32 channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ m_BufferInfo[channel] = ASIO::BufferInfo();
+ if(channel < m_Settings.InputChannels)
+ {
+ m_BufferInfo[channel].isInput = true;
+ m_BufferInfo[channel].channelNum = inputChannelMapping.ToDevice(channel);
+ } else
+ {
+ m_BufferInfo[channel].isInput = false;
+ m_BufferInfo[channel].channelNum = m_Settings.Channels.ToDevice(channel - m_Settings.InputChannels);
+ }
+ }
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: createBuffers(numChannels={}, bufferSize={})")(m_Settings.Channels.GetNumHostChannels(), m_nAsioBufferLen));
+ AsioDriver()->template createBuffers<AppID1, AppID2>(m_BufferInfo, m_nAsioBufferLen, *this);
+ m_BuffersCreated = true;
+ for(std::size_t i = 0; i < m_BufferInfo.size(); ++i)
+ {
+ if(!m_BufferInfo[i].buffers[0] || !m_BufferInfo[i].buffers[1])
+ {
+ throw ASIOException("createBuffes returned nullptr.");
+ }
+ }
+
+ m_ChannelInfo.resize(m_Settings.GetTotalChannels());
+ for(uint32 channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ if(channel < m_Settings.InputChannels)
+ {
+ m_ChannelInfo[channel] = AsioDriver()->getChannelInfo(inputChannelMapping.ToDevice(channel), true);
+ } else
+ {
+ m_ChannelInfo[channel] = AsioDriver()->getChannelInfo(m_Settings.Channels.ToDevice(channel - m_Settings.InputChannels), false);
+ }
+ assert(m_ChannelInfo[channel].isActive);
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: getChannelInfo(isInput={} channel={}) => isActive={} channelGroup={} type={} name='{}'")((channel < m_Settings.InputChannels), m_Settings.Channels.ToDevice(channel), value_cast(m_ChannelInfo[channel].isActive), m_ChannelInfo[channel].channelGroup, mpt::to_underlying(m_ChannelInfo[channel].type), mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(m_ChannelInfo[channel].name))));
+ }
+
+ bool allChannelsAreInt = true;
+ bool allChannelsAreInt16ValidBits = true;
+ bool allChannelsAreNativeInt24 = true;
+ bool allChannelsAreFloat32 = true;
+ for(std::size_t channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ ASIO::Sample::Traits sampleTraits = ASIO::Sample::Traits(m_ChannelInfo[channel].type);
+ bool isFloat = sampleTraits.is_float;
+ bool isFloat32 = sampleTraits.is_float && sampleTraits.valid_bits == 32;
+ bool isInt16ValidBits = !sampleTraits.is_float && sampleTraits.valid_bits == 16;
+ bool isInt24 = !sampleTraits.is_float && sampleTraits.size_bytes == 3 && sampleTraits.valid_bits == 24;
+ bool isNative = (mpt::endian_is_little() && !sampleTraits.is_be) || (mpt::endian_is_big() && sampleTraits.is_be);
+ if(isFloat)
+ {
+ allChannelsAreInt = false;
+ }
+ if(!isInt16ValidBits)
+ {
+ allChannelsAreInt16ValidBits = false;
+ }
+ if(!(isInt24 && isNative))
+ {
+ allChannelsAreNativeInt24 = false;
+ }
+ if(!isFloat32)
+ {
+ allChannelsAreFloat32 = false;
+ }
+ }
+ if(allChannelsAreInt16ValidBits)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int16;
+ m_SampleBufferInt16.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt16.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreNativeInt24)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int24;
+ m_SampleBufferInt24.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt24.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreInt)
+ {
+ m_Settings.sampleFormat = SampleFormat::Int32;
+ m_SampleBufferInt32.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferInt32.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else if(allChannelsAreFloat32)
+ {
+ m_Settings.sampleFormat = SampleFormat::Float32;
+ m_SampleBufferFloat.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferFloat.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ } else
+ {
+ m_Settings.sampleFormat = SampleFormat::Float64;
+ m_SampleBufferDouble.resize(m_nAsioBufferLen * m_Settings.Channels);
+ m_SampleInputBufferDouble.resize(m_nAsioBufferLen * m_Settings.InputChannels);
+ }
+
+ for(std::size_t channel = 0; channel < m_Settings.GetTotalChannels(); ++channel)
+ {
+ ASIO::Sample::ClearBufferASIO(m_BufferInfo[channel].buffers[0], m_ChannelInfo[channel].type, m_nAsioBufferLen);
+ ASIO::Sample::ClearBufferASIO(m_BufferInfo[channel].buffers[1], m_ChannelInfo[channel].type, m_nAsioBufferLen);
+ }
+
+ m_CanOutputReady = AsioDriver()->canOutputReady();
+ ;
+
+ m_StreamPositionOffset = m_nAsioBufferLen;
+
+ UpdateLatency();
+
+ return true;
+
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ InternalClose();
+ return false;
+}
+
+
+void CASIODevice::UpdateLatency()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Latencies latencies;
+ try
+ {
+ latencies = AsioDriver()->getLatencies();
+ } catch(const ASIO::Error &)
+ {
+ // continue, failure is not fatal here
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ if(latencies.Output >= m_nAsioBufferLen)
+ {
+ m_BufferLatency = static_cast<double>(latencies.Output + m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate); // ASIO and OpenMPT semantics of 'latency' differ by one chunk/buffer
+ } else
+ {
+ // pointless value returned from asio driver, use a sane estimate
+ m_BufferLatency = 2.0 * static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ }
+}
+
+
+void CASIODevice::SetRenderSilence(bool silence, bool wait)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_RenderSilence = silence;
+ if(!wait)
+ {
+ return;
+ }
+ std::chrono::steady_clock::time_point pollingstart = std::chrono::steady_clock::now();
+ while(m_RenderingSilence != silence)
+ {
+ if((std::chrono::steady_clock::now() - pollingstart) > std::chrono::microseconds(250))
+ {
+#if defined(MODPLUG_TRACKER)
+ if(silence)
+ {
+ if(CallbackIsLockedByCurrentThread())
+ {
+ MPT_ASSERT_MSG(false, "AudioCriticalSection locked while stopping ASIO");
+ } else
+ {
+ MPT_ASSERT_MSG(false, "waiting for asio failed in Stop()");
+ }
+ } else
+ {
+ if(CallbackIsLockedByCurrentThread())
+ {
+ MPT_ASSERT_MSG(false, "AudioCriticalSection locked while starting ASIO");
+ } else
+ {
+ MPT_ASSERT_MSG(false, "waiting for asio failed in Start()");
+ }
+ }
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ break;
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ }
+}
+
+
+bool CASIODevice::InternalStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_ALWAYS_MSG(!CallbackIsLockedByCurrentThread(), "AudioCriticalSection locked while starting ASIO");
+#else // !MODPLUG_TRACKER
+ assert(!CallbackIsLockedByCurrentThread());
+#endif // MODPLUG_TRACKER
+
+ if(m_Settings.KeepDeviceRunning)
+ {
+ if(m_DeviceRunning)
+ {
+ SetRenderSilence(false, true);
+ return true;
+ }
+ }
+
+ SetRenderSilence(false);
+ try
+ {
+ m_TotalFramesWritten = 0;
+ AsioDriver()->start();
+ m_DeviceRunning = true;
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return false;
+ }
+
+ return true;
+}
+
+
+bool CASIODevice::InternalIsPlayingSilence() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return m_Settings.KeepDeviceRunning && m_DeviceRunning && m_RenderSilence.load();
+}
+
+
+void CASIODevice::InternalEndPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InternalIsPlayingSilence())
+ {
+ return;
+ }
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ SetRenderSilence(false);
+}
+
+
+void CASIODevice::InternalStopAndAvoidPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ InternalStopImpl(true);
+}
+
+void CASIODevice::InternalStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ InternalStopImpl(false);
+}
+
+void CASIODevice::InternalStopImpl(bool force)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_ALWAYS_MSG(!CallbackIsLockedByCurrentThread(), "AudioCriticalSection locked while stopping ASIO");
+#else // !MODPLUG_TRACKER
+ assert(!CallbackIsLockedByCurrentThread());
+#endif // MODPLUG_TRACKER
+
+ if(m_Settings.KeepDeviceRunning && !force)
+ {
+ SetRenderSilence(true, true);
+ return;
+ }
+
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ SetRenderSilence(false);
+}
+
+
+bool CASIODevice::InternalClose()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeviceRunning)
+ {
+ m_DeviceRunning = false;
+ try
+ {
+ AsioDriver()->stop();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_TotalFramesWritten = 0;
+ }
+ SetRenderSilence(false);
+
+ m_CanOutputReady = false;
+ m_SampleBufferFloat.clear();
+ m_SampleBufferInt16.clear();
+ m_SampleBufferInt24.clear();
+ m_SampleBufferInt32.clear();
+ m_SampleInputBufferFloat.clear();
+ m_SampleInputBufferInt16.clear();
+ m_SampleInputBufferInt24.clear();
+ m_SampleInputBufferInt32.clear();
+ m_ChannelInfo.clear();
+ if(m_BuffersCreated)
+ {
+ try
+ {
+ AsioDriver()->disposeBuffers();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ m_BuffersCreated = false;
+ }
+ m_BufferInfo.clear();
+ m_nAsioBufferLen = 0;
+ m_BufferLatency = 0.0;
+
+ CloseDriver();
+
+ return true;
+}
+
+
+void CASIODevice::OpenDriver()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsDriverOpen())
+ {
+ return;
+ }
+ CLSID clsid = mpt::StringToCLSID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ try
+ {
+ if(GetAppInfo().AllowDeferredProcessing)
+ {
+ m_DeferredBufferSwitchDispatcher = ASIO::Windows::CreateBufferSwitchDispatcher([=](ASIO::BufferIndex bufferIndex)
+ { this->RealtimeBufferSwitchImpl(bufferIndex); });
+ }
+ {
+ CrashContextGuard guard{&m_Ectx};
+#if defined(MODPLUG_TRACKER)
+ if(GetAppInfo().MaskDriverCrashes)
+ {
+ m_Driver = std::make_unique<ASIO::Driver>(std::make_unique<ASIO::Windows::SEH::Driver>(clsid, GetAppInfo().GetHWND()));
+ } else
+#endif // MODPLUG_TRACKER
+ {
+ m_Driver = std::make_unique<ASIO::Driver>(std::make_unique<ASIO::Windows::Driver>(clsid, GetAppInfo().GetHWND()));
+ }
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return;
+ }
+ std::string driverName;
+ ASIO::Long driverVersion = 0;
+ std::string driverErrorMessage;
+ try
+ {
+ driverName = AsioDriver()->getDriverName();
+ driverVersion = AsioDriver()->getDriverVersion();
+ driverErrorMessage = AsioDriver()->getErrorMessage();
+ } catch(...)
+ {
+ CloseDriver();
+ ExceptionHandler(__func__);
+ return;
+ }
+ MPT_LOG(GetLogger(), LogInformation, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: Opened driver {} Version 0x{}: {}")(mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, driverName), mpt::format<mpt::ustring>::HEX0<8>(driverVersion), mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, driverErrorMessage)));
+}
+
+
+void CASIODevice::CloseDriver()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsDriverOpen())
+ {
+ return;
+ }
+ try
+ {
+ {
+ CrashContextGuard guard{&m_Ectx};
+ m_Driver = nullptr;
+ }
+ m_DeferredBufferSwitchDispatcher = nullptr;
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+}
+
+
+void CASIODevice::InternalFillAudioBuffer()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ FillAsioBuffer();
+}
+
+
+void CASIODevice::FillAsioBuffer(bool useSource)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ const bool rendersilence = !useSource;
+ const std::size_t countChunk = m_nAsioBufferLen;
+ const std::size_t inputChannels = m_Settings.InputChannels;
+ const std::size_t outputChannels = m_Settings.Channels;
+ for(std::size_t inputChannel = 0; inputChannel < inputChannels; ++inputChannel)
+ {
+ std::size_t channel = inputChannel;
+ const void *src = m_BufferInfo[channel].buffers[m_BufferIndex];
+ ASIO::SampleType sampleType = m_ChannelInfo[channel].type;
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ double *const dstDouble = m_SampleInputBufferDouble.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float64LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float64MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstDouble + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstDouble + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ float *const dstFloat = m_SampleInputBufferFloat.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float32MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstFloat + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstFloat + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ int16 *const dstInt16 = m_SampleInputBufferInt16.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int16LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int16MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstInt16 + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstInt16 + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ int24 *const dstInt24 = m_SampleInputBufferInt24.data();
+ assert((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int24LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int24MSB));
+ ASIO::Sample::CopyRawFromASIO(dstInt24 + inputChannel, inputChannels, src, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ int32 *const dstInt32 = m_SampleInputBufferInt32.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int32MSB))
+ {
+ ASIO::Sample::CopyRawFromASIO(dstInt32 + inputChannel, inputChannels, src, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertFromASIO(dstInt32 + inputChannel, inputChannels, sampleType, src, countChunk);
+ }
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ if(rendersilence)
+ {
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ std::fill(m_SampleBufferDouble.data(), m_SampleBufferDouble.data() + countChunk * outputChannels, double(0.0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ std::fill(m_SampleBufferFloat.data(), m_SampleBufferFloat.data() + countChunk * outputChannels, float(0.0f));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ std::fill(m_SampleBufferInt16.data(), m_SampleBufferInt16.data() + countChunk * outputChannels, int16(0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ std::fill(m_SampleBufferInt24.data(), m_SampleBufferInt24.data() + countChunk * outputChannels, int24(0));
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ std::fill(m_SampleBufferInt32.data(), m_SampleBufferInt32.data() + countChunk * outputChannels, int32(0));
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ } else
+ {
+ CallbackLockedAudioReadPrepare(countChunk, m_nAsioBufferLen * 2);
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferDouble.data(), (m_SampleInputBufferDouble.size() > 0) ? m_SampleInputBufferDouble.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferFloat.data(), (m_SampleInputBufferFloat.size() > 0) ? m_SampleInputBufferFloat.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt16.data(), (m_SampleInputBufferInt16.size() > 0) ? m_SampleInputBufferInt16.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt24.data(), (m_SampleInputBufferInt24.size() > 0) ? m_SampleInputBufferInt24.data() : nullptr, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ CallbackLockedAudioProcess(m_SampleBufferInt32.data(), (m_SampleInputBufferInt32.size() > 0) ? m_SampleInputBufferInt32.data() : nullptr, countChunk);
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ for(std::size_t outputChannel = 0; outputChannel < outputChannels; ++outputChannel)
+ {
+ std::size_t channel = outputChannel + m_Settings.InputChannels;
+ void *dst = m_BufferInfo[channel].buffers[m_BufferIndex];
+ ASIO::SampleType sampleType = m_ChannelInfo[channel].type;
+ if(m_Settings.sampleFormat == SampleFormat::Float64)
+ {
+ const double *const srcDouble = m_SampleBufferDouble.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float64LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float64MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcDouble + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcDouble + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Float32)
+ {
+ const float *const srcFloat = m_SampleBufferFloat.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Float32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Float32MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcFloat + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcFloat + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int16)
+ {
+ const int16 *const srcInt16 = m_SampleBufferInt16.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int16LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int16MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcInt16 + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcInt16 + outputChannel, outputChannels, countChunk);
+ }
+ } else if(m_Settings.sampleFormat == SampleFormat::Int24)
+ {
+ const int24 *const srcInt24 = m_SampleBufferInt24.data();
+ assert((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int24LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int24MSB));
+ ASIO::Sample::CopyRawToASIO(dst, srcInt24 + outputChannel, outputChannels, countChunk);
+ } else if(m_Settings.sampleFormat == SampleFormat::Int32)
+ {
+ const int32 *const srcInt32 = m_SampleBufferInt32.data();
+ if((mpt::endian_is_little() && sampleType == ASIO::SampleType::Int32LSB) || (mpt::endian_is_big() && sampleType == ASIO::SampleType::Int32MSB))
+ {
+ ASIO::Sample::CopyRawToASIO(dst, srcInt32 + outputChannel, outputChannels, countChunk);
+ } else
+ {
+ ASIO::Sample::ConvertToASIO(dst, sampleType, srcInt32 + outputChannel, outputChannels, countChunk);
+ }
+ } else
+ {
+#if defined(MODPLUG_TRACKER)
+ MPT_ASSERT_NOTREACHED();
+#else // !MODPLUG_TRACKER
+ assert(false);
+#endif // MODPLUG_TRACKER
+ }
+ }
+ if(m_CanOutputReady)
+ {
+ try
+ {
+ AsioDriver()->outputReady(); // do not handle errors, there is nothing we could do about them
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ }
+ }
+ if(!rendersilence)
+ {
+ CallbackLockedAudioProcessDone();
+ }
+}
+
+
+bool CASIODevice::InternalHasTimeInfo() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return m_Settings.UseHardwareTiming;
+}
+
+
+SoundDevice::BufferAttributes CASIODevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_BufferLatency;
+ bufferAttributes.UpdateInterval = static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.NumBuffers = 2;
+ return bufferAttributes;
+}
+
+
+namespace
+{
+struct DebugRealtimeThreadIdGuard
+{
+ std::atomic<uint32> &ThreadID;
+ DebugRealtimeThreadIdGuard(std::atomic<uint32> &ThreadID)
+ : ThreadID(ThreadID)
+ {
+ ThreadID.store(GetCurrentThreadId());
+ }
+ ~DebugRealtimeThreadIdGuard()
+ {
+ ThreadID.store(0);
+ }
+};
+} // namespace
+
+
+void CASIODevice::RealtimeSampleRateDidChange(ASIO::SampleRate sRate) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(mpt::saturate_round<uint32>(sRate) == m_Settings.Samplerate)
+ {
+ // not different, ignore it
+ return;
+ }
+ m_UsedFeatures.fetch_or(AsioFeature::SampleRateChange);
+ if(static_cast<double>(m_Settings.Samplerate) * (1.0 - AsioSampleRateTolerance) <= sRate && sRate <= static_cast<double>(m_Settings.Samplerate) * (1.0 + AsioSampleRateTolerance))
+ {
+ // ignore slight differences which might be due to a unstable external ASIO clock source
+ return;
+ }
+ // play safe and close the device
+ RequestClose();
+}
+
+
+void CASIODevice::RealtimeRequestDeferredProcessing(bool deferred) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ if(deferred)
+ {
+ m_UsedFeatures.fetch_or(AsioFeature::DeferredProcess);
+ }
+ m_DeferredProcessing = deferred;
+}
+
+
+void CASIODevice::RealtimeTimeInfo(ASIO::Time asioTime) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ if(m_Settings.UseHardwareTiming)
+ {
+ SoundDevice::TimeInfo timeInfo;
+ if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSamplePositionValid) && (asioTime.timeInfo.flags & ASIO::TimeInfoFlagSystemTimeValid))
+ {
+ double speed = 1.0;
+ if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSpeedValid) && (asioTime.timeInfo.speed > 0.0))
+ {
+ speed = asioTime.timeInfo.speed;
+ } else if((asioTime.timeInfo.flags & ASIO::TimeInfoFlagSampleRateValid) && (asioTime.timeInfo.sampleRate > 0.0))
+ {
+ speed *= asioTime.timeInfo.sampleRate / m_Settings.Samplerate;
+ }
+ timeInfo.SyncPointStreamFrames = asioTime.timeInfo.samplePosition - m_StreamPositionOffset;
+ timeInfo.SyncPointSystemTimestamp = asioTime.timeInfo.systemTime;
+ timeInfo.Speed = speed;
+ } else
+ { // spec violation or nothing provided at all, better to estimate this stuff ourselves
+ const uint64 asioNow = CallbackLockedGetReferenceClockNowNanoseconds();
+ timeInfo.SyncPointStreamFrames = m_TotalFramesWritten + m_nAsioBufferLen - m_StreamPositionOffset;
+ timeInfo.SyncPointSystemTimestamp = asioNow + mpt::saturate_round<int64>(m_BufferLatency * 1000.0 * 1000.0 * 1000.0);
+ timeInfo.Speed = 1.0;
+ }
+ timeInfo.RenderStreamPositionBefore = StreamPositionFromFrames(m_TotalFramesWritten - m_StreamPositionOffset);
+ timeInfo.RenderStreamPositionAfter = StreamPositionFromFrames(m_TotalFramesWritten - m_StreamPositionOffset + m_nAsioBufferLen);
+ timeInfo.Latency = GetEffectiveBufferAttributes().Latency;
+ SetTimeInfo(timeInfo);
+ }
+}
+
+
+void CASIODevice::RealtimeBufferSwitch(ASIO::BufferIndex bufferIndex) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeferredBufferSwitchDispatcher && m_DeferredProcessing)
+ {
+ m_DeferredBufferSwitchDispatcher->Dispatch(bufferIndex);
+ } else
+ {
+ RealtimeBufferSwitchImpl(bufferIndex);
+ }
+}
+
+
+void CASIODevice::RealtimeBufferSwitchImpl(ASIO::BufferIndex bufferIndex) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ DebugRealtimeThreadIdGuard debugThreadIdGuard(m_DebugRealtimeThreadID);
+ m_BufferIndex = bufferIndex;
+ bool rendersilence = m_RenderSilence;
+ m_RenderingSilence = rendersilence;
+ if(rendersilence)
+ {
+ m_StreamPositionOffset += m_nAsioBufferLen;
+ FillAsioBuffer(false);
+ } else
+ {
+ CallbackFillAudioBufferLocked();
+ }
+ m_TotalFramesWritten += m_nAsioBufferLen;
+}
+
+
+mpt::ustring CASIODevice::AsioFeaturesToString(AsioFeatures features)
+{
+ std::vector<mpt::ustring> results;
+ if(features & AsioFeature::ResetRequest)
+ {
+ results.push_back(MPT_USTRING("reset"));
+ }
+ if(features & AsioFeature::ResyncRequest)
+ {
+ results.push_back(MPT_USTRING("resync"));
+ }
+ if(features & AsioFeature::BufferSizeChange)
+ {
+ results.push_back(MPT_USTRING("buffer"));
+ }
+ if(features & AsioFeature::Overload)
+ {
+ results.push_back(MPT_USTRING("load"));
+ }
+ if(features & AsioFeature::SampleRateChange)
+ {
+ results.push_back(MPT_USTRING("srate"));
+ }
+ if(features & AsioFeature::DeferredProcess)
+ {
+ results.push_back(MPT_USTRING("deferred"));
+ }
+ return mpt::join(results, MPT_USTRING(","));
+}
+
+
+bool CASIODevice::DebugIsFragileDevice() const
+{
+ return true;
+}
+
+
+bool CASIODevice::DebugInRealtimeCallback() const
+{
+ return GetCurrentThreadId() == m_DebugRealtimeThreadID.load();
+}
+
+
+SoundDevice::Statistics CASIODevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_BufferLatency;
+ result.LastUpdateInterval = static_cast<double>(m_nAsioBufferLen) / static_cast<double>(m_Settings.Samplerate);
+ result.text = mpt::ustring();
+ const AsioFeatures unsupported = AsioFeature::Overload | AsioFeature::BufferSizeChange | AsioFeature::SampleRateChange;
+ AsioFeatures usedFeatures = m_UsedFeatures.fetch_or(0);
+ AsioFeatures unsupportedFeatues = usedFeatures & unsupported;
+ if(unsupportedFeatues)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("WARNING: unsupported features: {}")(AsioFeaturesToString(unsupportedFeatues));
+ } else if(usedFeatures)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("OK, features used: {}")(AsioFeaturesToString(m_UsedFeatures));
+ } else
+ {
+ result.text = MPT_USTRING("OK.");
+ }
+ return result;
+}
+
+
+void CASIODevice::MessageResetRequest() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::ResetRequest);
+ RequestReset();
+}
+
+bool CASIODevice::MessageBufferSizeChange(ASIO::Long newSize) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ MPT_UNUSED(newSize);
+ m_UsedFeatures.fetch_or(AsioFeature::BufferSizeChange);
+ // We do not support ASIO::MessageSelector::BufferSizeChange.
+ // This should cause a driver to send a ASIO::MessageSelector::ResetRequest.
+ return false;
+}
+
+bool CASIODevice::MessageResyncRequest() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::ResyncRequest);
+ RequestRestart();
+ return true;
+}
+
+void CASIODevice::MessageLatenciesChanged() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AsioRequest.fetch_or(AsioRequest::LatenciesChanged);
+}
+
+ASIO::Long CASIODevice::MessageMMCCommand(ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Long result = 0;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: MMCCommand(value={}, message={}, opt={}) => result={}")(value, reinterpret_cast<std::uintptr_t>(message), opt ? mpt::format<mpt::ustring>::val(*opt) : MPT_USTRING("NULL"), result));
+ return result;
+}
+
+void CASIODevice::MessageOverload() noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_UsedFeatures.fetch_or(AsioFeature::Overload);
+}
+
+ASIO::Long CASIODevice::MessageUnknown(ASIO::MessageSelector selector, ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ ASIO::Long result = 0;
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: AsioMessage(selector={}, value={}, message={}, opt={}) => result={}")(mpt::to_underlying(selector), value, reinterpret_cast<std::uintptr_t>(message), opt ? mpt::format<mpt::ustring>::val(*opt) : MPT_USTRING("NULL"), result));
+ return result;
+}
+
+
+void CASIODevice::ExceptionHandler(const char *func)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ try
+ {
+ throw; // rethrow
+#if defined(MODPLUG_TRACKER)
+ } catch(const ASIO::Windows::SEH::DriverCrash &e)
+ {
+#if !defined(MPT_BUILD_WINESUPPORT)
+ ExceptionHandler::TaintProcess(ExceptionHandler::TaintReason::Driver);
+#endif // !MPT_BUILD_WINESUPPORT
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Crash: {}!")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::transcode<mpt::ustring>(mpt::source_encoding, std::string(e.func()))));
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("ASIO Driver Crash: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, std::string(e.func()))));
+#endif // MODPLUG_TRACKER
+ } catch(const std::bad_alloc &)
+ {
+ mpt::throw_out_of_memory();
+ } catch(const ASIO::Windows::DriverLoadFailed &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Load: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const ASIO::Windows::DriverInitFailed &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Driver Init: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const ASIO::Error &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Error: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(const std::exception &e)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Exception: {}")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func), mpt::get_exception_text<mpt::ustring>(e)));
+ } catch(...)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("ASIO: {}: Unknown Exception")(mpt::transcode<mpt::ustring>(mpt::source_encoding, func)));
+ }
+}
+
+
+SoundDevice::Caps CASIODevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+
+ caps.Available = true;
+ caps.CanUpdateInterval = false;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = false;
+ caps.CanKeepDeviceRunning = true;
+ caps.CanUseHardwareTiming = true;
+ caps.CanChannelMapping = true;
+ caps.CanInput = true;
+ caps.HasNamedInputSources = true;
+ caps.CanDriverPanel = true;
+
+ caps.LatencyMin = 0.000001; // 1 us
+ caps.LatencyMax = 0.5; // 500 ms
+ caps.UpdateIntervalMin = 0.0; // disabled
+ caps.UpdateIntervalMax = 0.0; // disabled
+
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CASIODevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ SoundDevice::DynamicCaps caps;
+
+ TemporaryASIODriverOpener opener(*this);
+ if(!IsDriverOpen())
+ {
+ m_DeviceUnavailableOnOpen = true;
+ return caps;
+ }
+
+ try
+ {
+ ASIO::SampleRate samplerate = AsioDriver()->getSampleRate();
+ if(samplerate > 0.0)
+ {
+ caps.currentSampleRate = mpt::saturate_round<uint32>(samplerate);
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+
+ for(size_t i = 0; i < baseSampleRates.size(); i++)
+ {
+ try
+ {
+ if(AsioDriver()->canSampleRate(static_cast<ASIO::SampleRate>(baseSampleRates[i])))
+ {
+ caps.supportedSampleRates.push_back(baseSampleRates[i]);
+ caps.supportedExclusiveSampleRates.push_back(baseSampleRates[i]);
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ }
+
+ try
+ {
+ ASIO::Channels channels = AsioDriver()->getChannels();
+ if(!((channels.Input > 0) || (channels.Output > 0)))
+ {
+ m_DeviceUnavailableOnOpen = true;
+ }
+ for(ASIO::Long i = 0; i < channels.Output; ++i)
+ {
+ mpt::ustring name = mpt::format<mpt::ustring>::dec(i);
+ try
+ {
+ ASIO::ChannelInfo channelInfo = AsioDriver()->getChannelInfo(i, false);
+ name = mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(channelInfo.name));
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ caps.channelNames.push_back(name);
+ }
+ for(ASIO::Long i = 0; i < channels.Input; ++i)
+ {
+ mpt::ustring name = mpt::format<mpt::ustring>::dec(i);
+ try
+ {
+ ASIO::ChannelInfo channelInfo = AsioDriver()->getChannelInfo(i, true);
+ name = mpt::transcode<mpt::ustring>(mpt::logical_encoding::locale, static_cast<std::string>(channelInfo.name));
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(i), name));
+ }
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ // continue
+ }
+ return caps;
+}
+
+
+bool CASIODevice::OpenDriverSettings()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ TemporaryASIODriverOpener opener(*this);
+ if(!IsDriverOpen())
+ {
+ return false;
+ }
+ try
+ {
+ result = AsioDriver()->controlPanel();
+ } catch(...)
+ {
+ ExceptionHandler(__func__);
+ return false;
+ }
+ return result;
+}
+
+
+#endif // MPT_WITH_ASIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp
new file mode 100644
index 00000000..64485328
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceASIO.hpp
@@ -0,0 +1,251 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#ifdef MPT_WITH_ASIO
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <cassert>
+
+#include <ASIOModern/ASIO.hpp>
+#include <ASIOModern/ASIOSystemWindows.hpp>
+
+#if defined(MODPLUG_TRACKER)
+#if !defined(MPT_BUILD_WINESUPPORT)
+#include "../mptrack/ExceptionHandler.h"
+#endif // !MPT_BUILD_WINESUPPORT
+#endif // MODPLUG_TRACKER
+
+#endif // MPT_WITH_ASIO
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#ifdef MPT_WITH_ASIO
+
+
+class ASIOException
+ : public std::runtime_error
+{
+public:
+ ASIOException(const std::string &msg)
+ : std::runtime_error(msg)
+ {
+ return;
+ }
+};
+
+class CASIODevice
+ : public SoundDevice::Base
+ , private ASIO::Driver::CallbackHandler
+{
+
+ friend class TemporaryASIODriverOpener;
+
+protected:
+ std::unique_ptr<ASIO::Windows::IBufferSwitchDispatcher> m_DeferredBufferSwitchDispatcher;
+ std::unique_ptr<ASIO::Driver> m_Driver;
+
+#if defined(MODPLUG_TRACKER) && !defined(MPT_BUILD_WINESUPPORT)
+ using CrashContext = ExceptionHandler::Context;
+ using CrashContextGuard = ExceptionHandler::ContextSetter;
+#else // !(MODPLUG_TRACKER && !MPT_BUILD_WINESUPPORT)
+ struct CrashContext
+ {
+ void SetDescription(mpt::ustring)
+ {
+ return;
+ }
+ };
+ struct CrashContextGuard
+ {
+ CrashContextGuard(CrashContext *)
+ {
+ return;
+ }
+ };
+#endif // MODPLUG_TRACKER && !MPT_BUILD_WINESUPPORT
+ CrashContext m_Ectx;
+
+ class ASIODriverWithContext
+ {
+ private:
+ ASIO::Driver *m_Driver;
+ CrashContextGuard m_Guard;
+
+ public:
+ ASIODriverWithContext(ASIO::Driver *driver, CrashContext *ectx)
+ : m_Driver(driver)
+ , m_Guard(ectx)
+ {
+ assert(driver);
+ assert(ectx);
+ }
+ ASIODriverWithContext(const ASIODriverWithContext &) = delete;
+ ASIODriverWithContext &operator=(const ASIODriverWithContext &) = delete;
+ ASIO::Driver *operator->()
+ {
+ return m_Driver;
+ }
+ };
+
+ ASIODriverWithContext AsioDriver()
+ {
+ assert(m_Driver);
+ return ASIODriverWithContext{m_Driver.get(), &m_Ectx};
+ }
+
+ double m_BufferLatency;
+ ASIO::Long m_nAsioBufferLen;
+ std::vector<ASIO::BufferInfo> m_BufferInfo;
+ bool m_BuffersCreated;
+ std::vector<ASIO::ChannelInfo> m_ChannelInfo;
+ std::vector<double> m_SampleBufferDouble;
+ std::vector<float> m_SampleBufferFloat;
+ std::vector<int16> m_SampleBufferInt16;
+ std::vector<int24> m_SampleBufferInt24;
+ std::vector<int32> m_SampleBufferInt32;
+ std::vector<double> m_SampleInputBufferDouble;
+ std::vector<float> m_SampleInputBufferFloat;
+ std::vector<int16> m_SampleInputBufferInt16;
+ std::vector<int24> m_SampleInputBufferInt24;
+ std::vector<int32> m_SampleInputBufferInt32;
+ bool m_CanOutputReady;
+
+ bool m_DeviceRunning;
+ uint64 m_TotalFramesWritten;
+ bool m_DeferredProcessing;
+ ASIO::BufferIndex m_BufferIndex;
+ std::atomic<bool> m_RenderSilence;
+ std::atomic<bool> m_RenderingSilence;
+
+ int64 m_StreamPositionOffset;
+
+ using AsioRequests = uint8;
+ struct AsioRequest
+ {
+ enum AsioRequestEnum : AsioRequests
+ {
+ LatenciesChanged = 1 << 0,
+ };
+ };
+ std::atomic<AsioRequests> m_AsioRequest;
+
+ using AsioFeatures = uint16;
+ struct AsioFeature
+ {
+ enum AsioFeatureEnum : AsioFeatures
+ {
+ ResetRequest = 1 << 0,
+ ResyncRequest = 1 << 1,
+ BufferSizeChange = 1 << 2,
+ Overload = 1 << 3,
+ SampleRateChange = 1 << 4,
+ DeferredProcess = 1 << 5,
+ };
+ };
+ mutable std::atomic<AsioFeatures> m_UsedFeatures;
+ static mpt::ustring AsioFeaturesToString(AsioFeatures features);
+
+ mutable std::atomic<uint32> m_DebugRealtimeThreadID;
+
+ void SetRenderSilence(bool silence, bool wait = false);
+
+public:
+ CASIODevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CASIODevice();
+
+private:
+ void InitMembers();
+ bool HandleRequests(); // return true if any work has been done
+ void UpdateLatency();
+
+ void InternalStopImpl(bool force);
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_BuffersCreated; }
+
+ bool InternalIsPlayingSilence() const;
+ void InternalStopAndAvoidPlayingSilence();
+ void InternalEndPlayingSilence();
+
+ bool OnIdle() { return HandleRequests(); }
+
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+ bool OpenDriverSettings();
+
+ bool DebugIsFragileDevice() const;
+ bool DebugInRealtimeCallback() const;
+
+ SoundDevice::Statistics GetStatistics() const;
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+protected:
+ void OpenDriver();
+ void CloseDriver();
+ bool IsDriverOpen() const { return (m_Driver != nullptr); }
+
+ bool InternalHasTimeInfo() const;
+
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+
+protected:
+ void FillAsioBuffer(bool useSource = true);
+
+private:
+ // CallbackHandler
+
+ void MessageResetRequest() noexcept override;
+ bool MessageBufferSizeChange(ASIO::Long newSize) noexcept override;
+ bool MessageResyncRequest() noexcept override;
+ void MessageLatenciesChanged() noexcept override;
+ ASIO::Long MessageMMCCommand(ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept override;
+ void MessageOverload() noexcept override;
+
+ ASIO::Long MessageUnknown(ASIO::MessageSelector selector, ASIO::Long value, const void *message, const ASIO::Double *opt) noexcept override;
+
+ void RealtimeSampleRateDidChange(ASIO::SampleRate sRate) noexcept override;
+ void RealtimeRequestDeferredProcessing(bool value) noexcept override;
+ void RealtimeTimeInfo(ASIO::Time time) noexcept override;
+ void RealtimeBufferSwitch(ASIO::BufferIndex bufferIndex) noexcept override;
+
+ void RealtimeBufferSwitchImpl(ASIO::BufferIndex bufferIndex) noexcept;
+
+private:
+ void ExceptionHandler(const char *func);
+};
+
+#endif // MPT_WITH_ASIO
+
+} // namespace SoundDevice
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp
new file mode 100644
index 00000000..e72b2b69
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.cpp
@@ -0,0 +1,457 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceBase.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+Base::Base(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : m_Logger(logger)
+ , m_Callback(nullptr)
+ , m_MessageReceiver(nullptr)
+ , m_Info(info)
+ , m_SysInfo(sysInfo)
+ , m_StreamPositionOutputFrames(0)
+ , m_RequestFlags(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ m_DeviceUnavailableOnOpen = false;
+
+ m_IsPlaying = false;
+ m_StreamPositionRenderFrames = 0;
+ m_StreamPositionOutputFrames = 0;
+
+ m_RequestFlags.store(0);
+}
+
+
+Base::~Base()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ return;
+}
+
+
+SoundDevice::DynamicCaps Base::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps result;
+ result.supportedSampleRates = baseSampleRates;
+ return result;
+}
+
+
+bool Base::Init(const SoundDevice::AppInfo &appInfo)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsInited())
+ {
+ return true;
+ }
+ m_AppInfo = appInfo;
+ m_Caps = InternalGetDeviceCaps();
+ return m_Caps.Available;
+}
+
+
+bool Base::Open(const SoundDevice::Settings &settings)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(IsOpen())
+ {
+ Close();
+ }
+ m_Settings = settings;
+ if(m_Settings.Latency == 0.0) m_Settings.Latency = m_Caps.DefaultSettings.Latency;
+ if(m_Settings.UpdateInterval == 0.0) m_Settings.UpdateInterval = m_Caps.DefaultSettings.UpdateInterval;
+ m_Settings.Latency = std::clamp(m_Settings.Latency, m_Caps.LatencyMin, m_Caps.LatencyMax);
+ m_Settings.UpdateInterval = std::clamp(m_Settings.UpdateInterval, m_Caps.UpdateIntervalMin, m_Caps.UpdateIntervalMax);
+ m_Flags = SoundDevice::Flags();
+ m_DeviceUnavailableOnOpen = false;
+ m_RequestFlags.store(0);
+ return InternalOpen();
+}
+
+
+bool Base::Close()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return true;
+ }
+ Stop();
+ bool result = InternalClose();
+ m_RequestFlags.store(0);
+ return result;
+}
+
+
+uint64 Base::CallbackGetReferenceClockNowNanoseconds() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return 0;
+ }
+ uint64 result = m_Callback->SoundCallbackGetReferenceClockNowNanoseconds();
+ //MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("clock: {}")(result));
+ return result;
+}
+
+
+uint64 Base::CallbackLockedGetReferenceClockNowNanoseconds() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return 0;
+ }
+ uint64 result = m_Callback->SoundCallbackLockedGetReferenceClockNowNanoseconds();
+ //MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("clock-rt: {}")(result));
+ return result;
+}
+
+
+void Base::CallbackNotifyPreStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackPreStart();
+ }
+}
+
+
+void Base::CallbackNotifyPostStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackPostStop();
+ }
+}
+
+
+bool Base::CallbackIsLockedByCurrentThread() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_Callback)
+ {
+ return false;
+ }
+ return m_Callback->SoundCallbackIsLockedByCurrentThread();
+}
+
+
+void Base::CallbackFillAudioBufferLocked()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ CallbackLockedGuard lock(*m_Callback);
+ InternalFillAudioBuffer();
+ }
+}
+
+
+void Base::CallbackLockedAudioReadPrepare(std::size_t numFrames, std::size_t framesLatency)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InternalHasTimeInfo())
+ {
+ SoundDevice::TimeInfo timeInfo;
+ if(InternalHasGetStreamPosition())
+ {
+ timeInfo.SyncPointStreamFrames = InternalHasGetStreamPosition();
+ timeInfo.SyncPointSystemTimestamp = CallbackLockedGetReferenceClockNowNanoseconds();
+ timeInfo.Speed = 1.0;
+ } else
+ {
+ timeInfo.SyncPointStreamFrames = m_StreamPositionRenderFrames + numFrames;
+ timeInfo.SyncPointSystemTimestamp = CallbackLockedGetReferenceClockNowNanoseconds() + mpt::saturate_round<int64>(GetEffectiveBufferAttributes().Latency * 1000000000.0);
+ timeInfo.Speed = 1.0;
+ }
+ timeInfo.RenderStreamPositionBefore = StreamPositionFromFrames(m_StreamPositionRenderFrames);
+ timeInfo.RenderStreamPositionAfter = StreamPositionFromFrames(m_StreamPositionRenderFrames + numFrames);
+ timeInfo.Latency = GetEffectiveBufferAttributes().Latency;
+ SetTimeInfo(timeInfo);
+ }
+ m_StreamPositionRenderFrames += numFrames;
+ if(!InternalHasGetStreamPosition() && !InternalHasTimeInfo())
+ {
+ m_StreamPositionOutputFrames = m_StreamPositionRenderFrames - framesLatency;
+ } else
+ {
+ // unused, no locking
+ m_StreamPositionOutputFrames = 0;
+ }
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcessPrepare(m_TimeInfo);
+ }
+}
+
+
+template <typename Tsample>
+void Base::CallbackLockedAudioProcessImpl(Tsample *buffer, const Tsample *inputBuffer, std::size_t numFrames)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(numFrames <= 0)
+ {
+ return;
+ }
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcess(GetBufferFormat(), numFrames, buffer, inputBuffer);
+ }
+}
+
+void Base::CallbackLockedAudioProcess(uint8 *buffer, const uint8 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Unsigned8);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int8 *buffer, const int8 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int8);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int16 *buffer, const int16 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int16);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int24 *buffer, const int24 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int24);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(int32 *buffer, const int32 *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Int32);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(float *buffer, const float *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Float32);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcess(double *buffer, const double *inputBuffer, std::size_t numFrames)
+{
+ // cppcheck-suppress assertWithSideEffect
+ assert(GetBufferFormat().sampleFormat == SampleFormat::Float64);
+ CallbackLockedAudioProcessImpl(buffer, inputBuffer, numFrames);
+}
+
+void Base::CallbackLockedAudioProcessVoid(void *buffer, const void *inputBuffer, std::size_t numFrames)
+{
+ switch(GetBufferFormat().sampleFormat)
+ {
+ case SampleFormat::Unsigned8:
+ CallbackLockedAudioProcess(static_cast<uint8 *>(buffer), static_cast<const uint8 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int8:
+ CallbackLockedAudioProcess(static_cast<int8 *>(buffer), static_cast<const int8 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int16:
+ CallbackLockedAudioProcess(static_cast<int16 *>(buffer), static_cast<const int16 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int24:
+ CallbackLockedAudioProcess(static_cast<int24 *>(buffer), static_cast<const int24 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Int32:
+ CallbackLockedAudioProcess(static_cast<int32 *>(buffer), static_cast<const int32 *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Float32:
+ CallbackLockedAudioProcess(static_cast<float *>(buffer), static_cast<const float *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Float64:
+ CallbackLockedAudioProcess(static_cast<double *>(buffer), static_cast<const double *>(inputBuffer), numFrames);
+ break;
+ case SampleFormat::Invalid:
+ // nothing
+ break;
+ }
+}
+
+
+void Base::CallbackLockedAudioProcessDone()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Callback)
+ {
+ m_Callback->SoundCallbackLockedProcessDone(m_TimeInfo);
+ }
+}
+
+
+void Base::SendDeviceMessage(LogLevel level, const mpt::ustring &str)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ MPT_LOG(GetLogger(), level, "sounddev", str);
+ if(m_MessageReceiver)
+ {
+ m_MessageReceiver->SoundDeviceMessage(level, str);
+ }
+}
+
+
+bool Base::Start()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return false;
+ }
+ if(!IsPlaying())
+ {
+ m_StreamPositionRenderFrames = 0;
+ {
+ m_StreamPositionOutputFrames = 0;
+ }
+ CallbackNotifyPreStart();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ if(!InternalStart())
+ {
+ CallbackNotifyPostStop();
+ return false;
+ }
+ m_IsPlaying = true;
+ }
+ return true;
+}
+
+
+void Base::Stop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(IsPlaying())
+ {
+ InternalStop();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ CallbackNotifyPostStop();
+ m_IsPlaying = false;
+ m_StreamPositionOutputFrames = 0;
+ m_StreamPositionRenderFrames = 0;
+ }
+}
+
+
+void Base::StopAndAvoidPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(!IsPlaying())
+ {
+ return;
+ }
+ InternalStopAndAvoidPlayingSilence();
+ m_RequestFlags.fetch_and((~RequestFlagRestart).as_bits());
+ CallbackNotifyPostStop();
+ m_IsPlaying = false;
+ m_StreamPositionOutputFrames = 0;
+ m_StreamPositionRenderFrames = 0;
+}
+
+
+void Base::EndPlayingSilence()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return;
+ }
+ if(IsPlaying())
+ {
+ return;
+ }
+ InternalEndPlayingSilence();
+}
+
+
+SoundDevice::StreamPosition Base::GetStreamPosition() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!IsOpen())
+ {
+ return StreamPosition();
+ }
+ int64 frames = 0;
+ if(InternalHasGetStreamPosition())
+ {
+ frames = InternalGetStreamPositionFrames();
+ } else if(InternalHasTimeInfo())
+ {
+ const uint64 now = CallbackGetReferenceClockNowNanoseconds();
+ const SoundDevice::TimeInfo timeInfo = GetTimeInfo();
+ frames = mpt::saturate_round<int64>(
+ timeInfo.SyncPointStreamFrames + (static_cast<double>(static_cast<int64>(now - timeInfo.SyncPointSystemTimestamp)) * timeInfo.Speed * m_Settings.Samplerate * (1.0 / (1000.0 * 1000.0))));
+ } else
+ {
+ frames = m_StreamPositionOutputFrames;
+ }
+ return StreamPositionFromFrames(frames);
+}
+
+
+SoundDevice::Statistics Base::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_Settings.Latency;
+ result.LastUpdateInterval = m_Settings.UpdateInterval;
+ result.text = mpt::ustring();
+ return result;
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp
new file mode 100644
index 00000000..72eee1df
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBase.hpp
@@ -0,0 +1,209 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/mutex/mutex.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <atomic>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+class Base
+ : public IBase
+{
+
+private:
+ class CallbackLockedGuard
+ {
+ private:
+ ICallback &m_Callback;
+
+ public:
+ CallbackLockedGuard(ICallback &callback)
+ : m_Callback(callback)
+ {
+ m_Callback.SoundCallbackLock();
+ }
+ ~CallbackLockedGuard()
+ {
+ m_Callback.SoundCallbackUnlock();
+ }
+ };
+
+protected:
+ ILogger &m_Logger;
+
+private:
+ SoundDevice::ICallback *m_Callback;
+ SoundDevice::IMessageReceiver *m_MessageReceiver;
+
+ const SoundDevice::Info m_Info;
+
+private:
+ SoundDevice::Caps m_Caps;
+
+protected:
+ SoundDevice::SysInfo m_SysInfo;
+ SoundDevice::AppInfo m_AppInfo;
+ SoundDevice::Settings m_Settings;
+ SoundDevice::Flags m_Flags;
+ bool m_DeviceUnavailableOnOpen;
+
+private:
+ bool m_IsPlaying;
+
+ SoundDevice::TimeInfo m_TimeInfo;
+
+ int64 m_StreamPositionRenderFrames; // only updated or read in audio CALLBACK or when device is stopped. requires no further locking
+
+ std::atomic<int64> m_StreamPositionOutputFrames;
+
+ std::atomic<uint32> m_RequestFlags;
+
+public:
+ ILogger &GetLogger() const { return m_Logger; }
+ SoundDevice::SysInfo GetSysInfo() const { return m_SysInfo; }
+ SoundDevice::AppInfo GetAppInfo() const { return m_AppInfo; }
+
+protected:
+ SoundDevice::Type GetDeviceType() const { return m_Info.type; }
+ mpt::ustring GetDeviceInternalID() const { return m_Info.internalID; }
+ SoundDevice::Identifier GetDeviceIdentifier() const { return m_Info.GetIdentifier(); }
+
+ virtual void InternalFillAudioBuffer() = 0;
+
+ uint64 CallbackGetReferenceClockNowNanoseconds() const;
+ void CallbackNotifyPreStart();
+ void CallbackNotifyPostStop();
+ bool CallbackIsLockedByCurrentThread() const;
+ void CallbackFillAudioBufferLocked();
+ uint64 CallbackLockedGetReferenceClockNowNanoseconds() const;
+ void CallbackLockedAudioReadPrepare(std::size_t numFrames, std::size_t framesLatency);
+ template <typename Tsample>
+ void CallbackLockedAudioProcessImpl(Tsample *buffer, const Tsample *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(uint8 *buffer, const uint8 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int8 *buffer, const int8 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int16 *buffer, const int16 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int24 *buffer, const int24 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(int32 *buffer, const int32 *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(float *buffer, const float *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcess(double *buffer, const double *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcessVoid(void *buffer, const void *inputBuffer, std::size_t numFrames);
+ void CallbackLockedAudioProcessDone();
+
+ void RequestClose() { m_RequestFlags.fetch_or(RequestFlagClose); }
+ void RequestReset() { m_RequestFlags.fetch_or(RequestFlagReset); }
+ void RequestRestart() { m_RequestFlags.fetch_or(RequestFlagRestart); }
+
+ void SendDeviceMessage(LogLevel level, const mpt::ustring &str);
+
+protected:
+ void SetTimeInfo(SoundDevice::TimeInfo timeInfo) { m_TimeInfo = timeInfo; }
+
+ SoundDevice::StreamPosition StreamPositionFromFrames(int64 frames) const { return SoundDevice::StreamPosition{frames, static_cast<double>(frames) / static_cast<double>(m_Settings.Samplerate)}; }
+
+ virtual bool InternalHasTimeInfo() const { return false; }
+
+ virtual bool InternalHasGetStreamPosition() const { return false; }
+ virtual int64 InternalGetStreamPositionFrames() const { return 0; }
+
+ virtual bool InternalIsOpen() const = 0;
+
+ virtual bool InternalOpen() = 0;
+ virtual bool InternalStart() = 0;
+ virtual void InternalStop() = 0;
+ virtual bool InternalClose() = 0;
+
+ virtual bool InternalIsPlayingSilence() const { return false; }
+ virtual void InternalStopAndAvoidPlayingSilence() { InternalStop(); }
+ virtual void InternalEndPlayingSilence() { return; }
+
+ virtual SoundDevice::Caps InternalGetDeviceCaps() = 0;
+
+ virtual SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const = 0;
+
+protected:
+ Base(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+
+public:
+ virtual ~Base();
+
+ void SetCallback(SoundDevice::ICallback *callback) { m_Callback = callback; }
+ void SetMessageReceiver(SoundDevice::IMessageReceiver *receiver) { m_MessageReceiver = receiver; }
+
+ SoundDevice::Info GetDeviceInfo() const { return m_Info; }
+
+ SoundDevice::Caps GetDeviceCaps() const { return m_Caps; }
+ virtual SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+ bool Init(const SoundDevice::AppInfo &appInfo);
+ bool Open(const SoundDevice::Settings &settings);
+ bool Close();
+ bool Start();
+ void Stop();
+
+ FlagSet<RequestFlags> GetRequestFlags() const { return FlagSet<RequestFlags>(m_RequestFlags.load()); }
+
+ bool IsInited() const { return m_Caps.Available; }
+ bool IsOpen() const { return IsInited() && InternalIsOpen(); }
+ bool IsAvailable() const { return m_Caps.Available && !m_DeviceUnavailableOnOpen; }
+ bool IsPlaying() const { return m_IsPlaying; }
+
+ virtual bool IsPlayingSilence() const { return IsOpen() && !IsPlaying() && InternalIsPlayingSilence(); }
+ virtual void StopAndAvoidPlayingSilence();
+ virtual void EndPlayingSilence();
+
+ virtual bool OnIdle() { return false; }
+
+ SoundDevice::Settings GetSettings() const { return m_Settings; }
+ SampleFormat GetActualSampleFormat() const { return IsOpen() ? m_Settings.sampleFormat : SampleFormat(SampleFormat::Invalid); }
+ SoundDevice::BufferFormat GetBufferFormat() const
+ {
+ BufferFormat bufferFormat;
+ bufferFormat.Samplerate = m_Settings.Samplerate;
+ bufferFormat.Channels = m_Settings.Channels;
+ bufferFormat.InputChannels = m_Settings.InputChannels;
+ bufferFormat.sampleFormat = m_Settings.sampleFormat;
+ bufferFormat.WantsClippedOutput = m_Flags.WantsClippedOutput;
+ bufferFormat.DitherType = m_Settings.DitherType;
+ return bufferFormat;
+ }
+ SoundDevice::BufferAttributes GetEffectiveBufferAttributes() const { return (IsOpen() && IsPlaying()) ? InternalGetEffectiveBufferAttributes() : SoundDevice::BufferAttributes(); }
+
+ SoundDevice::TimeInfo GetTimeInfo() const { return m_TimeInfo; }
+ SoundDevice::StreamPosition GetStreamPosition() const;
+
+ virtual bool DebugIsFragileDevice() const { return false; }
+ virtual bool DebugInRealtimeCallback() const { return false; }
+
+ virtual SoundDevice::Statistics GetStatistics() const;
+
+ virtual bool OpenDriverSettings() { return false; };
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp
new file mode 100644
index 00000000..81ce7258
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceBuffer.hpp
@@ -0,0 +1,262 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceCallback.hpp"
+
+#include "mpt/audio/span.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/Dither.hpp"
+#include "openmpt/soundbase/CopyMix.hpp"
+
+#include <variant>
+
+#include <cassert>
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+template <typename Tsample>
+class BufferIO
+{
+
+private:
+ mpt::audio_span_interleaved<const Tsample> const m_src;
+ mpt::audio_span_interleaved<Tsample> const m_dst;
+ std::size_t m_countFramesReadProcessed;
+ std::size_t m_countFramesWriteProcessed;
+ const BufferFormat m_bufferFormat;
+
+public:
+ inline BufferIO(Tsample *dst, const Tsample *src, std::size_t numFrames, BufferFormat bufferFormat)
+ : m_src(src, bufferFormat.InputChannels, numFrames)
+ , m_dst(dst, bufferFormat.Channels, numFrames)
+ , m_countFramesReadProcessed(0)
+ , m_countFramesWriteProcessed(0)
+ , m_bufferFormat(bufferFormat)
+ {
+ return;
+ }
+
+ template <typename audio_span_dst>
+ inline void Read(audio_span_dst dst)
+ {
+ assert(m_countFramesReadProcessed + dst.size_frames() <= m_src.size_frames());
+ ConvertBufferToBufferMixInternal(dst, mpt::make_audio_span_with_offset(m_src, m_countFramesReadProcessed), m_bufferFormat.InputChannels, dst.size_frames());
+ m_countFramesReadProcessed += dst.size_frames();
+ }
+
+ template <int fractionalBits, typename audio_span_dst>
+ inline void ReadFixedPoint(audio_span_dst dst)
+ {
+ assert(m_countFramesReadProcessed + dst.size_frames() <= m_src.size_frames());
+ ConvertBufferToBufferMixInternalFixed<fractionalBits>(dst, mpt::make_audio_span_with_offset(m_src, m_countFramesReadProcessed), m_bufferFormat.InputChannels, dst.size_frames());
+ m_countFramesReadProcessed += dst.size_frames();
+ }
+
+ template <typename audio_span_src, typename TDither>
+ inline void Write(audio_span_src src, TDither &dither)
+ {
+ assert(m_countFramesWriteProcessed + src.size_frames() <= m_dst.size_frames());
+ if(m_bufferFormat.WantsClippedOutput)
+ {
+ ConvertBufferMixInternalToBuffer<true>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ } else
+ {
+ ConvertBufferMixInternalToBuffer<false>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ }
+ m_countFramesWriteProcessed += src.size_frames();
+ }
+
+ template <int fractionalBits, typename audio_span_src, typename TDither>
+ inline void WriteFixedPoint(audio_span_src src, TDither &dither)
+ {
+ assert(m_countFramesWriteProcessed + src.size_frames() <= m_dst.size_frames());
+ if(m_bufferFormat.WantsClippedOutput)
+ {
+ ConvertBufferMixInternalFixedToBuffer<fractionalBits, true>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ } else
+ {
+ ConvertBufferMixInternalFixedToBuffer<fractionalBits, false>(mpt::make_audio_span_with_offset(m_dst, m_countFramesWriteProcessed), src, dither, m_bufferFormat.Channels, src.size_frames());
+ }
+ m_countFramesWriteProcessed += src.size_frames();
+ }
+
+ inline ~BufferIO()
+ {
+ // fill remaining buffer with silence
+ while(m_countFramesWriteProcessed < m_dst.size_frames())
+ {
+ for(std::size_t channel = 0; channel < m_dst.size_channels(); ++channel)
+ {
+ m_dst(channel, m_countFramesWriteProcessed) = SC::sample_cast<Tsample>(static_cast<int16>(0));
+ }
+ m_countFramesWriteProcessed += 1;
+ }
+ }
+};
+
+
+template <typename TDithers>
+class CallbackBuffer
+{
+private:
+ std::variant<
+ BufferIO<uint8>,
+ BufferIO<int8>,
+ BufferIO<int16>,
+ BufferIO<int24>,
+ BufferIO<int32>,
+ BufferIO<float>,
+ BufferIO<double>>
+ m_BufferIO;
+ TDithers &m_Dithers;
+ std::size_t m_NumFrames;
+
+public:
+ template <typename Tsample>
+ explicit inline CallbackBuffer(Tsample *dst, const Tsample *src, std::size_t numFrames, TDithers &dithers, BufferFormat bufferFormat)
+ : m_BufferIO(BufferIO<Tsample>{dst, src, numFrames, bufferFormat})
+ , m_Dithers(dithers)
+ , m_NumFrames(numFrames)
+ {
+ return;
+ }
+
+ inline std::size_t GetNumFrames() const
+ {
+ return m_NumFrames;
+ }
+
+ template <typename audio_span_dst>
+ inline void Read(audio_span_dst dst)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ bufferIO.Read(dst);
+ },
+ m_BufferIO);
+ }
+
+ template <int fractionalBits, typename audio_span_dst>
+ inline void ReadFixedPoint(audio_span_dst dst)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ bufferIO.template ReadFixedPoint<fractionalBits>(dst);
+ },
+ m_BufferIO);
+ }
+
+ template <typename audio_span_src>
+ inline void Write(audio_span_src src)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ std::visit(
+ [&](auto &ditherInstance)
+ {
+ bufferIO.Write(src, ditherInstance);
+ },
+ m_Dithers.Variant());
+ },
+ m_BufferIO);
+ }
+
+ template <int fractionalBits, typename audio_span_src>
+ inline void WriteFixedPoint(audio_span_src src)
+ {
+ std::visit(
+ [&](auto &bufferIO)
+ {
+ std::visit(
+ [&](auto &ditherInstance)
+ {
+ bufferIO.template WriteFixedPoint<fractionalBits>(src, ditherInstance);
+ },
+ m_Dithers.Variant());
+ },
+ m_BufferIO);
+ }
+};
+
+
+template <typename TDithers>
+class CallbackBufferHandler
+ : public ICallback
+{
+private:
+ TDithers m_Dithers;
+
+protected:
+ template <typename Trd>
+ explicit CallbackBufferHandler(Trd &rd)
+ : m_Dithers(rd)
+ {
+ return;
+ }
+
+protected:
+ inline TDithers &Dithers()
+ {
+ return m_Dithers;
+ }
+
+private:
+ template <typename Tsample>
+ inline void SoundCallbackLockedProcessImpl(BufferFormat bufferFormat, std::size_t numFrames, Tsample *buffer, const Tsample *inputBuffer)
+ {
+ CallbackBuffer<TDithers> callbackBuffer{buffer, inputBuffer, numFrames, m_Dithers, bufferFormat};
+ SoundCallbackLockedCallback(callbackBuffer);
+ }
+
+public:
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, uint8 *buffer, const uint8 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int8 *buffer, const int8 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int16 *buffer, const int16 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int24 *buffer, const int24 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, int32 *buffer, const int32 *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, float *buffer, const float *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ inline void SoundCallbackLockedProcess(BufferFormat bufferFormat, std::size_t numFrames, double *buffer, const double *inputBuffer) final
+ {
+ SoundCallbackLockedProcessImpl(bufferFormat, numFrames, buffer, inputBuffer);
+ }
+ virtual void SoundCallbackLockedCallback(CallbackBuffer<TDithers> &buffer) = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp
new file mode 100644
index 00000000..edefe115
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceCallback.hpp
@@ -0,0 +1,83 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+struct StreamPosition
+{
+ int64 Frames = 0; // relative to Start()
+ double Seconds = 0.0; // relative to Start()
+};
+
+
+struct TimeInfo
+{
+
+ int64 SyncPointStreamFrames = 0;
+ uint64 SyncPointSystemTimestamp = 0;
+ double Speed = 1.0;
+
+ SoundDevice::StreamPosition RenderStreamPositionBefore;
+ SoundDevice::StreamPosition RenderStreamPositionAfter;
+ // int64 chunkSize = After - Before
+
+ double Latency = 0.0; // seconds
+};
+
+
+struct BufferFormat
+{
+ uint32 Samplerate;
+ uint32 Channels;
+ uint8 InputChannels;
+ SampleFormat sampleFormat;
+ bool WantsClippedOutput;
+ int32 DitherType;
+};
+
+
+class ICallback
+{
+public:
+ // main thread
+ virtual uint64 SoundCallbackGetReferenceClockNowNanoseconds() const = 0; // timeGetTime()*1000000 on Windows
+ virtual void SoundCallbackPreStart() = 0;
+ virtual void SoundCallbackPostStop() = 0;
+ virtual bool SoundCallbackIsLockedByCurrentThread() const = 0;
+ // audio thread
+ virtual void SoundCallbackLock() = 0;
+ virtual uint64 SoundCallbackLockedGetReferenceClockNowNanoseconds() const = 0; // timeGetTime()*1000000 on Windows
+ virtual void SoundCallbackLockedProcessPrepare(SoundDevice::TimeInfo timeInfo) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, uint8 *buffer, const uint8 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int8 *buffer, const int8 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int16 *buffer, const int16 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int24 *buffer, const int24 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, int32 *buffer, const int32 *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, float *buffer, const float *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcess(SoundDevice::BufferFormat bufferFormat, std::size_t numFrames, double *buffer, const double *inputBuffer) = 0;
+ virtual void SoundCallbackLockedProcessDone(SoundDevice::TimeInfo timeInfo) = 0;
+ virtual void SoundCallbackUnlock() = 0;
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp
new file mode 100644
index 00000000..5785d4bb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.cpp
@@ -0,0 +1,572 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceDirectSound.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "mpt/uuid/guid.hpp"
+#include "mpt/uuid/uuid.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <vector>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_DIRECTSOUND)
+
+
+
+namespace
+{
+struct DevicesAndLoggerAndSysInfo
+{
+ std::vector<SoundDevice::Info> devices;
+ ILogger *logger;
+ SoundDevice::SysInfo sysInfo;
+};
+} // namespace
+
+
+static BOOL WINAPI DSEnumCallback(GUID *lpGuid, LPCTSTR lpstrDescription, LPCTSTR lpstrDriver, LPVOID lpContext)
+{
+ DevicesAndLoggerAndSysInfo &devicesAndLoggerAndSysInfo = *(DevicesAndLoggerAndSysInfo *)lpContext;
+ std::vector<SoundDevice::Info> &devices = devicesAndLoggerAndSysInfo.devices;
+ ILogger &logger = *devicesAndLoggerAndSysInfo.logger;
+ SoundDevice::SysInfo &sysInfo = devicesAndLoggerAndSysInfo.sysInfo;
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ if(!lpstrDescription)
+ {
+ return TRUE;
+ }
+ GUID guid = (lpGuid ? *lpGuid : GUID());
+ SoundDevice::Info info;
+ info.type = TypeDSOUND;
+ info.default_ = (!lpGuid ? Info::Default::Managed : Info::Default::None);
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::GUIDToString(guid));
+ info.name = mpt::transcode<mpt::ustring>(mpt::winstring(lpstrDescription));
+ if(lpstrDriver)
+ {
+ info.extraData[MPT_USTRING("DriverName")] = mpt::transcode<mpt::ustring>(mpt::winstring(lpstrDriver));
+ }
+ if(lpGuid)
+ {
+ info.extraData[MPT_USTRING("UUID")] = mpt::format<mpt::ustring>::val(mpt::UUID(guid));
+ }
+ info.apiName = MPT_USTRING("DirectSound");
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsWine() ? Info::Compatible::Yes : Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::OutputOnly,
+ Info::Mixing::Software,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ devices.push_back(info);
+ return TRUE;
+}
+
+
+std::vector<SoundDevice::Info> CDSoundDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ DevicesAndLoggerAndSysInfo devicesAndLoggerAndSysInfo = {std::vector<SoundDevice::Info>(), &logger, sysInfo};
+ DirectSoundEnumerate(DSEnumCallback, &devicesAndLoggerAndSysInfo);
+ return devicesAndLoggerAndSysInfo.devices;
+}
+
+
+CDSoundDevice::CDSoundDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : CSoundDeviceWithThread(logger, info, sysInfo)
+ , m_piDS(NULL)
+ , m_pPrimary(NULL)
+ , m_pMixBuffer(NULL)
+ , m_nDSoundBufferSize(0)
+ , m_bMixRunning(FALSE)
+ , m_dwWritePos(0)
+ , m_StatisticLatencyFrames(0)
+ , m_StatisticPeriodFrames(0)
+{
+ return;
+}
+
+
+CDSoundDevice::~CDSoundDevice()
+{
+ Close();
+}
+
+
+SoundDevice::Caps CDSoundDevice::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = true;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use primary buffer");
+ caps.DefaultSettings.sampleFormat = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista)) ? SampleFormat::Float32 : SampleFormat::Int16;
+ IDirectSound *dummy = nullptr;
+ IDirectSound *ds = nullptr;
+ if(m_piDS)
+ {
+ ds = m_piDS;
+ } else
+ {
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &dummy, NULL) != DS_OK)
+ {
+ return caps;
+ }
+ if(!dummy)
+ {
+ return caps;
+ }
+ ds = dummy;
+ }
+ DSCAPS dscaps = {};
+ dscaps.dwSize = sizeof(dscaps);
+ if(DS_OK == ds->GetCaps(&dscaps))
+ {
+ if(!(dscaps.dwFlags & DSCAPS_EMULDRIVER))
+ {
+ caps.CanExclusiveMode = true;
+ }
+ }
+ if(dummy)
+ {
+ dummy->Release();
+ dummy = nullptr;
+ }
+ ds = nullptr;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CDSoundDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ IDirectSound *dummy = nullptr;
+ IDirectSound *ds = nullptr;
+ if(m_piDS)
+ {
+ ds = m_piDS;
+ } else
+ {
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &dummy, NULL) != DS_OK)
+ {
+ return caps;
+ }
+ if(!dummy)
+ {
+ return caps;
+ }
+ ds = dummy;
+ }
+ DSCAPS dscaps = {};
+ dscaps.dwSize = sizeof(dscaps);
+ if(DS_OK == ds->GetCaps(&dscaps))
+ {
+ if(dscaps.dwMaxSecondarySampleRate == 0)
+ {
+ // nothing known about supported sample rates
+ } else
+ {
+ for(const auto &rate : baseSampleRates)
+ {
+ if(dscaps.dwMinSecondarySampleRate <= rate && rate <= dscaps.dwMaxSecondarySampleRate)
+ {
+ caps.supportedSampleRates.push_back(rate);
+ caps.supportedExclusiveSampleRates.push_back(rate);
+ }
+ }
+ }
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ // Vista
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ } else if(!(dscaps.dwFlags & DSCAPS_EMULDRIVER))
+ {
+ // XP wdm
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(dscaps.dwFlags & DSCAPS_PRIMARY8BIT)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Unsigned8);
+ }
+ if(dscaps.dwFlags & DSCAPS_PRIMARY16BIT)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ if(caps.supportedExclusiveModeSampleFormats.empty())
+ {
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ } else
+ {
+ // XP vdx
+ // nothing, announce all, fail later
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ }
+ if(dummy)
+ {
+ dummy->Release();
+ dummy = nullptr;
+ }
+ ds = nullptr;
+ return caps;
+}
+
+
+bool CDSoundDevice::InternalOpen()
+{
+ if(m_Settings.InputChannels > 0) return false;
+
+ WAVEFORMATEXTENSIBLE wfext;
+ if(!FillWaveFormatExtensible(wfext, m_Settings)) return false;
+ WAVEFORMATEX *pwfx = &wfext.Format;
+
+ const uint32 bytesPerFrame = static_cast<uint32>(m_Settings.GetBytesPerFrame());
+
+ DSBUFFERDESC dsbd;
+ DSBCAPS dsc;
+
+ if(m_piDS) return true;
+ GUID guid = mpt::StringToGUID(mpt::transcode<mpt::winstring>(GetDeviceInternalID()));
+ if(DirectSoundCreate(mpt::IsValid(guid) ? &guid : NULL, &m_piDS, NULL) != DS_OK) return false;
+ if(!m_piDS) return false;
+ if(m_piDS->SetCooperativeLevel(m_AppInfo.GetHWND(), m_Settings.ExclusiveMode ? DSSCL_WRITEPRIMARY : DSSCL_PRIORITY) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ m_bMixRunning = FALSE;
+ m_nDSoundBufferSize = mpt::saturate_round<int32>(m_Settings.Latency * pwfx->nAvgBytesPerSec);
+ m_nDSoundBufferSize = mpt::align_up<uint32>(m_nDSoundBufferSize, bytesPerFrame);
+ m_nDSoundBufferSize = std::clamp(m_nDSoundBufferSize, mpt::align_up<uint32>(DSBSIZE_MIN, bytesPerFrame), mpt::align_down<uint32>(DSBSIZE_MAX, bytesPerFrame));
+ if(!m_Settings.ExclusiveMode)
+ {
+ // Set the format of the primary buffer
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
+ dsbd.dwBufferBytes = 0;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = NULL;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pPrimary, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ if(m_pPrimary->SetFormat(pwfx) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ // Create the secondary buffer
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ dsbd.dwBufferBytes = m_nDSoundBufferSize;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = pwfx;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pMixBuffer, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ } else
+ {
+ dsbd.dwSize = sizeof(dsbd);
+ dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER | DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2;
+ dsbd.dwBufferBytes = 0;
+ dsbd.dwReserved = 0;
+ dsbd.lpwfxFormat = NULL;
+ if(m_piDS->CreateSoundBuffer(&dsbd, &m_pPrimary, NULL) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ if(m_pPrimary->SetFormat(pwfx) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ dsc.dwSize = sizeof(dsc);
+ if(m_pPrimary->GetCaps(&dsc) != DS_OK)
+ {
+ Close();
+ return false;
+ }
+ m_nDSoundBufferSize = dsc.dwBufferBytes;
+ m_pMixBuffer = m_pPrimary;
+ m_pMixBuffer->AddRef();
+ }
+ if(m_Settings.sampleFormat == SampleFormat::Int8)
+ {
+ m_Settings.sampleFormat = SampleFormat::Unsigned8;
+ }
+ LPVOID lpBuf1, lpBuf2;
+ DWORD dwSize1, dwSize2;
+ if(m_pMixBuffer->Lock(0, m_nDSoundBufferSize, &lpBuf1, &dwSize1, &lpBuf2, &dwSize2, 0) == DS_OK)
+ {
+ UINT zero = (pwfx->wBitsPerSample == 8) ? 0x80 : 0x00;
+ if((lpBuf1) && (dwSize1)) memset(lpBuf1, zero, dwSize1);
+ if((lpBuf2) && (dwSize2)) memset(lpBuf2, zero, dwSize2);
+ m_pMixBuffer->Unlock(lpBuf1, dwSize1, lpBuf2, dwSize2);
+ } else
+ {
+ DWORD dwStat = 0;
+ m_pMixBuffer->GetStatus(&dwStat);
+ if(dwStat & DSBSTATUS_BUFFERLOST) m_pMixBuffer->Restore();
+ }
+ m_dwWritePos = 0xFFFFFFFF;
+ SetWakeupInterval(std::min(m_Settings.UpdateInterval, m_nDSoundBufferSize / (2.0 * m_Settings.GetBytesPerSecond())));
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ return true;
+}
+
+
+bool CDSoundDevice::InternalClose()
+{
+ if(m_pMixBuffer)
+ {
+ m_pMixBuffer->Release();
+ m_pMixBuffer = NULL;
+ }
+ if(m_pPrimary)
+ {
+ m_pPrimary->Release();
+ m_pPrimary = NULL;
+ }
+ if(m_piDS)
+ {
+ m_piDS->Release();
+ m_piDS = NULL;
+ }
+ m_bMixRunning = FALSE;
+ return true;
+}
+
+
+void CDSoundDevice::StartFromSoundThread()
+{
+ // done in InternalFillAudioBuffer
+}
+
+
+void CDSoundDevice::StopFromSoundThread()
+{
+ if(m_pMixBuffer)
+ {
+ m_pMixBuffer->Stop();
+ }
+ m_bMixRunning = FALSE;
+}
+
+
+void CDSoundDevice::InternalFillAudioBuffer()
+{
+ if(!m_pMixBuffer)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_nDSoundBufferSize == 0)
+ {
+ RequestClose();
+ return;
+ }
+
+ DWORD dwLatency = 0;
+
+ for(int refillCount = 0; refillCount < 2; ++refillCount)
+ {
+ // Refill the buffer at most twice so we actually sleep some time when CPU is overloaded.
+
+ const uint32 bytesPerFrame = static_cast<uint32>(m_Settings.GetBytesPerFrame());
+
+ DWORD dwPlay = 0;
+ DWORD dwWrite = 0;
+ if(m_pMixBuffer->GetCurrentPosition(&dwPlay, &dwWrite) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+
+ uint32 dwBytes = m_nDSoundBufferSize / 2;
+ if(!m_bMixRunning)
+ {
+ // startup
+ m_dwWritePos = dwWrite;
+ dwLatency = 0;
+ } else
+ {
+ // running
+ dwLatency = (m_dwWritePos - dwPlay + m_nDSoundBufferSize) % m_nDSoundBufferSize;
+ dwLatency = (dwLatency + m_nDSoundBufferSize - 1) % m_nDSoundBufferSize + 1;
+ dwBytes = (dwPlay - m_dwWritePos + m_nDSoundBufferSize) % m_nDSoundBufferSize;
+ dwBytes = std::clamp(dwBytes, uint32(0), m_nDSoundBufferSize / 2); // limit refill amount to half the buffer size
+ }
+ dwBytes = dwBytes / bytesPerFrame * bytesPerFrame; // truncate to full frame
+ if(dwBytes < bytesPerFrame)
+ {
+ // ok, nothing to do
+ return;
+ }
+
+ void *buf1 = nullptr;
+ void *buf2 = nullptr;
+ DWORD dwSize1 = 0;
+ DWORD dwSize2 = 0;
+ HRESULT hr = m_pMixBuffer->Lock(m_dwWritePos, dwBytes, &buf1, &dwSize1, &buf2, &dwSize2, 0);
+ if(hr == DSERR_BUFFERLOST)
+ {
+ // buffer lost, restore buffer and try again, fail if it fails again
+ if(m_pMixBuffer->Restore() != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_pMixBuffer->Lock(m_dwWritePos, dwBytes, &buf1, &dwSize1, &buf2, &dwSize2, 0) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ } else if(hr != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+
+ CallbackLockedAudioReadPrepare(dwSize1 / bytesPerFrame + dwSize2 / bytesPerFrame, dwLatency / bytesPerFrame);
+
+ CallbackLockedAudioProcessVoid(buf1, nullptr, dwSize1 / bytesPerFrame);
+ CallbackLockedAudioProcessVoid(buf2, nullptr, dwSize2 / bytesPerFrame);
+
+ m_StatisticLatencyFrames.store(dwLatency / bytesPerFrame);
+ m_StatisticPeriodFrames.store(dwSize1 / bytesPerFrame + dwSize2 / bytesPerFrame);
+
+ if(m_pMixBuffer->Unlock(buf1, dwSize1, buf2, dwSize2) != DS_OK)
+ {
+ CallbackLockedAudioProcessDone();
+ RequestClose();
+ return;
+ }
+ m_dwWritePos += dwSize1 + dwSize2;
+ m_dwWritePos %= m_nDSoundBufferSize;
+
+ CallbackLockedAudioProcessDone();
+
+ DWORD dwStatus = 0;
+ m_pMixBuffer->GetStatus(&dwStatus);
+ if(!m_bMixRunning || !(dwStatus & DSBSTATUS_PLAYING))
+ {
+ if(!(dwStatus & DSBSTATUS_BUFFERLOST))
+ {
+ // start playing
+ hr = m_pMixBuffer->Play(0, 0, DSBPLAY_LOOPING);
+ } else
+ {
+ // buffer lost flag is set, do not try start playing, we know it will fail with DSERR_BUFFERLOST.
+ hr = DSERR_BUFFERLOST;
+ }
+ if(hr == DSERR_BUFFERLOST)
+ {
+ // buffer lost, restore buffer and try again, fail if it fails again
+ if(m_pMixBuffer->Restore() != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ if(m_pMixBuffer->Play(0, 0, DSBPLAY_LOOPING) != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ } else if(hr != DS_OK)
+ {
+ RequestClose();
+ return;
+ }
+ m_bMixRunning = TRUE;
+ }
+
+ if(dwBytes < m_nDSoundBufferSize / 2)
+ {
+ // Sleep again if we did fill less than half the buffer size.
+ // Otherwise it's a better idea to refill again right away.
+ break;
+ }
+ }
+}
+
+
+SoundDevice::BufferAttributes CDSoundDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_nDSoundBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.UpdateInterval = std::min(m_Settings.UpdateInterval, m_nDSoundBufferSize / (2.0 * m_Settings.GetBytesPerSecond()));
+ bufferAttributes.NumBuffers = 1;
+ return bufferAttributes;
+}
+
+
+SoundDevice::Statistics CDSoundDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = 1.0 * m_StatisticLatencyFrames.load() / m_Settings.Samplerate;
+ result.LastUpdateInterval = 1.0 * m_StatisticPeriodFrames.load() / m_Settings.Samplerate;
+ result.text = mpt::ustring();
+ return result;
+}
+
+
+#endif // MPT_WITH_DIRECTSOUND
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp
new file mode 100644
index 00000000..9c6ef67d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceDirectSound.hpp
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#if defined(MPT_WITH_DIRECTSOUND)
+#include <dsound.h>
+#endif // MPT_WITH_DIRECTSOUND
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#if defined(MPT_WITH_DIRECTSOUND)
+
+
+class CDSoundDevice : public CSoundDeviceWithThread
+{
+protected:
+ IDirectSound *m_piDS;
+ IDirectSoundBuffer *m_pPrimary;
+ IDirectSoundBuffer *m_pMixBuffer;
+ uint32 m_nDSoundBufferSize;
+ BOOL m_bMixRunning;
+ DWORD m_dwWritePos;
+
+ std::atomic<uint32> m_StatisticLatencyFrames;
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CDSoundDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CDSoundDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ void StartFromSoundThread();
+ void StopFromSoundThread();
+ bool InternalIsOpen() const { return (m_pMixBuffer != NULL); }
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+#endif // MPT_WITH_DIRECTSOUND
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp
new file mode 100644
index 00000000..2c165273
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.cpp
@@ -0,0 +1,504 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceManager.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceASIO.hpp"
+#include "SoundDeviceDirectSound.hpp"
+#include "SoundDevicePortAudio.hpp"
+#include "SoundDeviceRtAudio.hpp"
+#include "SoundDeviceWaveout.hpp"
+#include "SoundDevicePulseaudio.hpp"
+#include "SoundDevicePulseSimple.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/detect.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+
+struct CompareInfo
+{
+ static int64 score(const SoundDevice::Info &x)
+ {
+ int64 score = 0;
+ score *= 256;
+ score += static_cast<int8>(x.managerFlags.defaultFor);
+ score *= 256;
+ score += static_cast<int8>(x.flags.usability);
+ score *= 256;
+ score += static_cast<int8>(x.flags.level);
+ score *= 256;
+ score += static_cast<int8>(x.flags.compatible);
+ score *= 256;
+ score += static_cast<int8>(x.flags.api);
+ score *= 256;
+ score += static_cast<int8>(x.flags.io);
+ score *= 256;
+ score += static_cast<int8>(x.flags.mixing);
+ score *= 256;
+ score += static_cast<int8>(x.flags.implementor);
+ return score;
+ }
+ bool operator()(const SoundDevice::Info &x, const SoundDevice::Info &y)
+ {
+ const auto scorex = score(x);
+ const auto scorey = score(y);
+ return (scorex > scorey)
+ || ((scorex == scorey) && (x.type > y.type))
+ || ((scorex == scorey) && (x.type == y.type) && (x.default_ > y.default_))
+ || ((scorex == scorey) && (x.type == y.type) && (x.default_ == y.default_) && (x.name < y.name));
+ }
+};
+
+
+std::vector<std::shared_ptr<IDevicesEnumerator>> Manager::GetDefaultEnumerators()
+{
+ return GetEnabledEnumerators(EnabledBackends());
+}
+
+
+std::vector<std::shared_ptr<IDevicesEnumerator>> Manager::GetEnabledEnumerators(EnabledBackends enabledBackends)
+{
+ std::vector<std::shared_ptr<IDevicesEnumerator>> result;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+#if defined(MPT_WITH_PULSEAUDIO)
+ if(enabledBackends.Pulseaudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<Pulseaudio>>());
+ }
+#endif // MPT_WITH_PULSEAUDIO
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ if(enabledBackends.PulseaudioSimple)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<PulseaudioSimple>>());
+ }
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+#if MPT_OS_WINDOWS
+ if(enabledBackends.WaveOut)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CWaveDevice>>());
+ }
+#endif // MPT_OS_WINDOWS
+
+#if defined(MPT_WITH_DIRECTSOUND)
+ // kind of deprecated by now
+ if(enabledBackends.DirectSound)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CDSoundDevice>>());
+ }
+#endif // MPT_WITH_DIRECTSOUND
+
+#ifdef MPT_WITH_ASIO
+ if(enabledBackends.ASIO)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CASIODevice>>());
+ }
+#endif // MPT_WITH_ASIO
+
+#ifdef MPT_WITH_PORTAUDIO
+ if(enabledBackends.PortAudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CPortaudioDevice>>());
+ }
+#endif // MPT_WITH_PORTAUDIO
+
+#ifdef MPT_WITH_RTAUDIO
+ if(enabledBackends.RtAudio)
+ {
+ result.push_back(std::make_shared<DevicesEnumerator<CRtAudioDevice>>());
+ }
+#endif // MPT_WITH_RTAUDIO
+ return result;
+}
+
+
+void Manager::ReEnumerate(bool firstRun)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_SoundDevices.clear();
+ m_DeviceUnavailable.clear();
+ m_DeviceFactoryMethods.clear();
+ m_DeviceCaps.clear();
+ m_DeviceDynamicCaps.clear();
+
+ if(firstRun)
+ {
+ for(auto &deviceEnumerator : m_DeviceEnumerators)
+ {
+ if(!deviceEnumerator)
+ {
+ continue;
+ }
+ m_BackendInitializers.push_back(deviceEnumerator->BackendInitializer());
+ }
+ } else
+ {
+ for(auto &initializer : m_BackendInitializers)
+ {
+ initializer->Reload();
+ }
+ }
+
+ for(auto &enumerator : m_DeviceEnumerators)
+ {
+ if(!enumerator)
+ {
+ continue;
+ }
+ const auto infos = enumerator->EnumerateDevices(GetLogger(), GetSysInfo());
+ mpt::append(m_SoundDevices, infos);
+ for(const auto &info : infos)
+ {
+ SoundDevice::Identifier identifier = info.GetIdentifier();
+ if(!identifier.empty())
+ {
+ m_DeviceFactoryMethods[identifier] = enumerator->GetCreateFunc();
+ }
+ }
+ }
+
+ struct Default
+ {
+ SoundDevice::Info::DefaultFor value = SoundDevice::Info::DefaultFor::None;
+ };
+
+ std::map<SoundDevice::Type, Default> typeDefault;
+ if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Linux)
+ {
+#if defined(MPT_WITH_PULSEAUDIO)
+ typeDefault[MPT_USTRING("PulseAudio")].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ typeDefault[MPT_USTRING("PulseAudio-Simple")].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("pulse"))].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("alsa"))].value = Info::DefaultFor::LowLevel;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("jack"))].value = Info::DefaultFor::ProAudio;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paALSA)].value = Info::DefaultFor::LowLevel;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paJACK)].value = Info::DefaultFor::ProAudio;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Darwin)
+ {
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("core"))].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paCoreAudio)].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("jack"))].value = Info::DefaultFor::ProAudio;
+#endif
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paJACK)].value = Info::DefaultFor::ProAudio;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::BSD)
+ {
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paOSS)].value = Info::DefaultFor::System;
+#endif
+#if defined(MPT_WITH_RTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("RtAudio-{}")(MPT_USTRING("oss"))].value = Info::DefaultFor::System;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Haiku)
+ {
+#if defined(MPT_WITH_PORTAUDIO)
+ typeDefault[MPT_UFORMAT_MESSAGE("PortAudio-{}")(paBeOS)].value = Info::DefaultFor::System;
+#endif
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine() && GetSysInfo().WineHostClass == mpt::osinfo::osclass::Linux)
+ { // Wine on Linux
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine() && GetSysInfo().WineHostClass == mpt::osinfo::osclass::Darwin)
+ { // Wine on macOS
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().IsWindowsWine())
+ { // Wine
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeDSOUND].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista))
+ { // WinXP
+ typeDefault[SoundDevice::TypeWAVEOUT].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows && GetSysInfo().WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7))
+ { // Vista
+ typeDefault[SoundDevice::TypeWAVEOUT].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else if(GetSysInfo().SystemClass == mpt::osinfo::osclass::Windows)
+ { // >=Win7
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ typeDefault[SoundDevice::TypeASIO].value = Info::DefaultFor::ProAudio;
+ typeDefault[SoundDevice::TypePORTAUDIO_WDMKS].value = Info::DefaultFor::LowLevel;
+ } else
+ { // unknown
+ typeDefault[SoundDevice::TypePORTAUDIO_WASAPI].value = Info::DefaultFor::System;
+ }
+ for(auto &deviceInfo : m_SoundDevices)
+ {
+ if(typeDefault[deviceInfo.type].value != Info::DefaultFor::None)
+ {
+ deviceInfo.managerFlags.defaultFor = typeDefault[deviceInfo.type].value;
+ }
+ }
+ std::stable_sort(m_SoundDevices.begin(), m_SoundDevices.end(), CompareInfo());
+
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE("Sound Devices enumerated:")());
+ for(const auto &device : m_SoundDevices)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Identifier : {}")(device.GetIdentifier()));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Type : {}")(device.type));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" InternalID: {}")(device.internalID));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" API Name : {}")(device.apiName));
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Name : {}")(device.name));
+ for(const auto &extra : device.extraData)
+ {
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_UFORMAT_MESSAGE(" Extra Data: {} = {}")(extra.first, extra.second));
+ }
+ }
+}
+
+
+SoundDevice::Manager::GlobalID Manager::GetGlobalID(SoundDevice::Identifier identifier) const
+{
+ for(std::size_t i = 0; i < m_SoundDevices.size(); ++i)
+ {
+ if(m_SoundDevices[i].GetIdentifier() == identifier)
+ {
+ return i;
+ }
+ }
+ return ~SoundDevice::Manager::GlobalID();
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfo(SoundDevice::Manager::GlobalID id) const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(id > m_SoundDevices.size())
+ {
+ return SoundDevice::Info();
+ }
+ return m_SoundDevices[id];
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfo(SoundDevice::Identifier identifier) const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_SoundDevices.empty())
+ {
+ return SoundDevice::Info();
+ }
+ if(identifier.empty())
+ {
+ return SoundDevice::Info();
+ }
+ for(const auto &info : *this)
+ {
+ if(info.GetIdentifier() == identifier)
+ {
+ return info;
+ }
+ }
+ return SoundDevice::Info();
+}
+
+
+SoundDevice::Info Manager::FindDeviceInfoBestMatch(SoundDevice::Identifier identifier)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_SoundDevices.empty())
+ {
+ return SoundDevice::Info();
+ }
+ if(!identifier.empty())
+ { // valid identifier
+ for(const auto &info : *this)
+ {
+ if((info.GetIdentifier() == identifier) && !IsDeviceUnavailable(info.GetIdentifier()))
+ { // exact match
+ return info;
+ }
+ }
+ }
+ for(const auto &info : *this)
+ { // find first available device
+ if(!IsDeviceUnavailable(info.GetIdentifier()))
+ {
+ return info;
+ }
+ }
+ // default to first device
+ return *begin();
+}
+
+
+bool Manager::OpenDriverSettings(SoundDevice::Identifier identifier, SoundDevice::IMessageReceiver *messageReceiver, SoundDevice::IBase *currentSoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ bool result = false;
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ result = currentSoundDevice->OpenDriverSettings();
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ dummy->SetMessageReceiver(messageReceiver);
+ result = dummy->OpenDriverSettings();
+ }
+ delete dummy;
+ }
+ return result;
+}
+
+
+SoundDevice::Caps Manager::GetDeviceCaps(SoundDevice::Identifier identifier, SoundDevice::IBase *currentSoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_DeviceCaps.find(identifier) == m_DeviceCaps.end())
+ {
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ m_DeviceCaps[identifier] = currentSoundDevice->GetDeviceCaps();
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ m_DeviceCaps[identifier] = dummy->GetDeviceCaps();
+ } else
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ delete dummy;
+ }
+ }
+ return m_DeviceCaps[identifier];
+}
+
+
+SoundDevice::DynamicCaps Manager::GetDeviceDynamicCaps(SoundDevice::Identifier identifier, const std::vector<uint32> &baseSampleRates, SoundDevice::IMessageReceiver *messageReceiver, SoundDevice::IBase *currentSoundDevice, bool update)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if((m_DeviceDynamicCaps.find(identifier) == m_DeviceDynamicCaps.end()) || update)
+ {
+ if(currentSoundDevice && FindDeviceInfo(identifier).IsValid() && (currentSoundDevice->GetDeviceInfo().GetIdentifier() == identifier))
+ {
+ m_DeviceDynamicCaps[identifier] = currentSoundDevice->GetDeviceDynamicCaps(baseSampleRates);
+ if(!currentSoundDevice->IsAvailable())
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ } else
+ {
+ SoundDevice::IBase *dummy = CreateSoundDevice(identifier);
+ if(dummy)
+ {
+ dummy->SetMessageReceiver(messageReceiver);
+ m_DeviceDynamicCaps[identifier] = dummy->GetDeviceDynamicCaps(baseSampleRates);
+ if(!dummy->IsAvailable())
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ } else
+ {
+ SetDeviceUnavailable(identifier);
+ }
+ delete dummy;
+ }
+ }
+ return m_DeviceDynamicCaps[identifier];
+}
+
+
+SoundDevice::IBase *Manager::CreateSoundDevice(SoundDevice::Identifier identifier)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ const SoundDevice::Info info = FindDeviceInfo(identifier);
+ if(!info.IsValid())
+ {
+ return nullptr;
+ }
+ if(m_DeviceFactoryMethods.find(identifier) == m_DeviceFactoryMethods.end())
+ {
+ return nullptr;
+ }
+ if(!m_DeviceFactoryMethods[identifier])
+ {
+ return nullptr;
+ }
+ SoundDevice::IBase *result = m_DeviceFactoryMethods[identifier](GetLogger(), info, GetSysInfo());
+ if(!result)
+ {
+ return nullptr;
+ }
+ if(!result->Init(m_AppInfo))
+ {
+ delete result;
+ result = nullptr;
+ return nullptr;
+ }
+ m_DeviceCaps[identifier] = result->GetDeviceCaps(); // update cached caps
+ return result;
+}
+
+
+
+Manager::Manager(ILogger &logger, SoundDevice::SysInfo sysInfo, SoundDevice::AppInfo appInfo, std::vector<std::shared_ptr<IDevicesEnumerator>> deviceEnumerators)
+ : m_Logger(logger)
+ , m_SysInfo(sysInfo)
+ , m_AppInfo(appInfo)
+ , m_DeviceEnumerators(std::move(deviceEnumerators))
+{
+ ReEnumerate(true);
+}
+
+
+Manager::~Manager()
+{
+ return;
+}
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp
new file mode 100644
index 00000000..3807978b
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceManager.hpp
@@ -0,0 +1,167 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+struct EnabledBackends
+{
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ bool Pulseaudio = true;
+#endif // MPT_WITH_PULSEAUDIO && MPT_ENABLE_PULSEAUDIO_FULL
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+ bool PulseaudioSimple = true;
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+#if MPT_OS_WINDOWS
+ bool WaveOut = true;
+#endif // MPT_OS_WINDOWS
+#if defined(MPT_WITH_DIRECTSOUND)
+ bool DirectSound = true;
+#endif // MPT_WITH_DIRECTSOUND
+#ifdef MPT_WITH_ASIO
+ bool ASIO = true;
+#endif // MPT_WITH_ASIO
+#ifdef MPT_WITH_PORTAUDIO
+ bool PortAudio = true;
+#endif // MPT_WITH_PORTAUDIO
+#ifdef MPT_WITH_RTAUDIO
+ bool RtAudio = true;
+#endif // MPT_WITH_RTAUDIO
+};
+
+
+class IDevicesEnumerator
+{
+protected:
+ typedef SoundDevice::IBase *(*CreateSoundDeviceFunc)(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo);
+
+protected:
+ IDevicesEnumerator() = default;
+
+public:
+ virtual ~IDevicesEnumerator() = default;
+
+public:
+ virtual std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() = 0;
+ virtual std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo) = 0;
+ virtual CreateSoundDeviceFunc GetCreateFunc() = 0;
+};
+
+
+template <typename TSoundDevice>
+class DevicesEnumerator
+ : public IDevicesEnumerator
+{
+public:
+ DevicesEnumerator() = default;
+ ~DevicesEnumerator() override = default;
+
+public:
+ std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() override
+ {
+ return TSoundDevice::BackendInitializer();
+ }
+ std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo) override
+ {
+ return TSoundDevice::EnumerateDevices(logger, sysInfo);
+ }
+ virtual CreateSoundDeviceFunc GetCreateFunc() override
+ {
+ return &ConstructSoundDevice;
+ }
+
+public:
+ static SoundDevice::IBase *ConstructSoundDevice(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo)
+ {
+ return new TSoundDevice(logger, info, sysInfo);
+ }
+};
+
+
+class Manager
+{
+
+public:
+ typedef std::size_t GlobalID;
+
+protected:
+ typedef SoundDevice::IBase *(*CreateSoundDeviceFunc)(ILogger &logger, const SoundDevice::Info &info, SoundDevice::SysInfo sysInfo);
+
+protected:
+ ILogger &m_Logger;
+ const SoundDevice::SysInfo m_SysInfo;
+ const SoundDevice::AppInfo m_AppInfo;
+
+ std::vector<std::shared_ptr<IDevicesEnumerator>> m_DeviceEnumerators;
+
+ std::vector<std::unique_ptr<BackendInitializer>> m_BackendInitializers;
+
+ std::vector<SoundDevice::Info> m_SoundDevices;
+ std::map<SoundDevice::Identifier, bool> m_DeviceUnavailable;
+ std::map<SoundDevice::Identifier, CreateSoundDeviceFunc> m_DeviceFactoryMethods;
+ std::map<SoundDevice::Identifier, SoundDevice::Caps> m_DeviceCaps;
+ std::map<SoundDevice::Identifier, SoundDevice::DynamicCaps> m_DeviceDynamicCaps;
+
+public:
+ Manager(ILogger &logger, SoundDevice::SysInfo sysInfo, SoundDevice::AppInfo appInfo, std::vector<std::shared_ptr<IDevicesEnumerator>> deviceEnumerators = GetDefaultEnumerators());
+ ~Manager();
+
+public:
+ ILogger &GetLogger() const { return m_Logger; }
+ SoundDevice::SysInfo GetSysInfo() const { return m_SysInfo; }
+ SoundDevice::AppInfo GetAppInfo() const { return m_AppInfo; }
+
+ static std::vector<std::shared_ptr<IDevicesEnumerator>> GetDefaultEnumerators();
+ static std::vector<std::shared_ptr<IDevicesEnumerator>> GetEnabledEnumerators(EnabledBackends enabledBackends);
+
+ void ReEnumerate(bool firstRun = false);
+
+ std::vector<SoundDevice::Info>::const_iterator begin() const { return m_SoundDevices.begin(); }
+ std::vector<SoundDevice::Info>::const_iterator end() const { return m_SoundDevices.end(); }
+ const std::vector<SoundDevice::Info> &GetDeviceInfos() const { return m_SoundDevices; }
+
+ SoundDevice::Manager::GlobalID GetGlobalID(SoundDevice::Identifier identifier) const;
+
+ SoundDevice::Info FindDeviceInfo(SoundDevice::Manager::GlobalID id) const;
+ SoundDevice::Info FindDeviceInfo(SoundDevice::Identifier identifier) const;
+ SoundDevice::Info FindDeviceInfoBestMatch(SoundDevice::Identifier identifier);
+
+ bool OpenDriverSettings(SoundDevice::Identifier identifier, SoundDevice::IMessageReceiver *messageReceiver = nullptr, SoundDevice::IBase *currentSoundDevice = nullptr);
+
+ void SetDeviceUnavailable(SoundDevice::Identifier identifier) { m_DeviceUnavailable[identifier] = true; }
+ bool IsDeviceUnavailable(SoundDevice::Identifier identifier) { return m_DeviceUnavailable[identifier]; }
+
+ SoundDevice::Caps GetDeviceCaps(SoundDevice::Identifier identifier, SoundDevice::IBase *currentSoundDevice = nullptr);
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(SoundDevice::Identifier identifier, const std::vector<uint32> &baseSampleRates, SoundDevice::IMessageReceiver *messageReceiver = nullptr, SoundDevice::IBase *currentSoundDevice = nullptr, bool update = false);
+
+ SoundDevice::IBase *CreateSoundDevice(SoundDevice::Identifier identifier);
+};
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp
new file mode 100644
index 00000000..e8824552
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.cpp
@@ -0,0 +1,1081 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePortAudio.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/buffer.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <array>
+#include <vector>
+
+#include <cstddef>
+
+#ifdef MPT_WITH_PORTAUDIO
+#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
+#include "../include/portaudio/src/common/pa_debugprint.h"
+#endif
+#if MPT_OS_WINDOWS
+#include <shellapi.h>
+#include <windows.h>
+#endif
+#endif
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_PORTAUDIO
+
+#ifdef MPT_ALL_LOGGING
+#define PALOG(x) MPT_LOG(GetLogger(), LogDebug, "PortAudio", x)
+#define PA_LOG_ENABLED 1
+#else
+#define PALOG(x) \
+ do \
+ { \
+ } while(0)
+#define PA_LOG_ENABLED 0
+#endif
+
+
+
+CPortaudioDevice::CPortaudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_StatisticPeriodFrames(0)
+{
+ mpt::ustring internalID = GetDeviceInternalID();
+ if(internalID == MPT_USTRING("WASAPI-Default"))
+ {
+ m_DeviceIsDefault = true;
+ m_DeviceIndex = Pa_GetHostApiInfo(Pa_HostApiTypeIdToHostApiIndex(paWASAPI))->defaultOutputDevice;
+ } else
+ {
+ m_DeviceIsDefault = false;
+ m_DeviceIndex = mpt::ConvertStringTo<PaDeviceIndex>(internalID);
+ }
+ m_HostApiType = Pa_GetHostApiInfo(Pa_GetDeviceInfo(m_DeviceIndex)->hostApi)->type;
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo = {};
+#endif // MPT_OS_WINDOWS
+ m_Stream = 0;
+ m_StreamInfo = 0;
+ m_CurrentFrameBuffer = nullptr;
+ m_CurrentFrameBufferInput = nullptr;
+ m_CurrentFrameCount = 0;
+ m_CurrentRealLatency = 0.0;
+}
+
+
+CPortaudioDevice::~CPortaudioDevice()
+{
+ Close();
+}
+
+
+bool CPortaudioDevice::InternalOpen()
+{
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+ m_Stream = 0;
+ m_StreamInfo = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ m_CurrentFrameCount = 0;
+ m_StreamParameters.device = m_DeviceIndex;
+ if(m_StreamParameters.device == -1)
+ {
+ return false;
+ }
+ m_StreamParameters.channelCount = m_Settings.Channels;
+ if(m_Settings.sampleFormat.IsFloat())
+ {
+ if(m_Settings.sampleFormat.GetBitsPerSample() != 32)
+ {
+ return false;
+ }
+ m_StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(m_Settings.sampleFormat.GetBitsPerSample())
+ {
+ case 8: m_StreamParameters.sampleFormat = paInt8; break;
+ case 16: m_StreamParameters.sampleFormat = paInt16; break;
+ case 24: m_StreamParameters.sampleFormat = paInt24; break;
+ case 32: m_StreamParameters.sampleFormat = paInt32; break;
+ default: return false; break;
+ }
+ }
+ m_StreamParameters.suggestedLatency = m_Settings.Latency;
+ m_StreamParameters.hostApiSpecificStreamInfo = NULL;
+ unsigned long framesPerBuffer = static_cast<long>(m_Settings.UpdateInterval * m_Settings.Samplerate);
+ if(m_HostApiType == paWASAPI)
+ {
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo = {};
+ m_WasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
+ m_WasapiStreamInfo.hostApiType = paWASAPI;
+ m_WasapiStreamInfo.version = 1;
+ if(m_Settings.BoostThreadPriority)
+ {
+ m_WasapiStreamInfo.flags |= paWinWasapiThreadPriority;
+ if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING(""))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Audio"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityAudio;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Capture"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityCapture;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Distribution"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityDistribution;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Games"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityGames;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Playback"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityPlayback;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Pro Audio"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityProAudio;
+ } else if(GetAppInfo().BoostedThreadMMCSSClassVista == MPT_USTRING("Window Manager"))
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityWindowManager;
+ } else
+ {
+ m_WasapiStreamInfo.threadPriority = eThreadPriorityNone;
+ }
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+ }
+#endif // MPT_OS_WINDOWS
+ if(m_Settings.ExclusiveMode)
+ {
+ m_Flags.WantsClippedOutput = false;
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags |= paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ } else
+ {
+ m_Flags.WantsClippedOutput = GetSysInfo().IsOriginal();
+ }
+ } else if(m_HostApiType == paWDMKS)
+ {
+ m_Flags.WantsClippedOutput = false;
+ framesPerBuffer = paFramesPerBufferUnspecified; // let portaudio choose
+ } else if(m_HostApiType == paMME)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ } else if(m_HostApiType == paDirectSound)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ } else
+ {
+ m_Flags.WantsClippedOutput = false;
+ }
+ m_InputStreamParameters = m_StreamParameters;
+ if(!HasInputChannelsOnSameDevice())
+ {
+ m_InputStreamParameters.device = static_cast<PaDeviceIndex>(m_Settings.InputSourceID);
+ }
+ m_InputStreamParameters.channelCount = m_Settings.InputChannels;
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ if(m_HostApiType == paWASAPI)
+ {
+ if(m_Settings.ExclusiveMode)
+ {
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags &= ~paWinWasapiExplicitSampleFormat;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ return false;
+ }
+ } else
+ {
+ if(!GetSysInfo().IsWine && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
+ { // retry with automatic stream format conversion (i.e. resampling)
+#if MPT_OS_WINDOWS
+ m_WasapiStreamInfo.flags |= paWinWasapiAutoConvert;
+ m_StreamParameters.hostApiSpecificStreamInfo = &m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ if(Pa_IsFormatSupported((m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate) != paFormatIsSupported)
+ {
+ return false;
+ }
+ } else
+ {
+ return false;
+ }
+ }
+ } else
+ {
+ return false;
+ }
+ }
+ PaStreamFlags flags = paNoFlag;
+ if(m_Settings.DitherType == 0)
+ {
+ flags |= paDitherOff;
+ }
+ if(Pa_OpenStream(&m_Stream, (m_Settings.InputChannels > 0) ? &m_InputStreamParameters : NULL, &m_StreamParameters, m_Settings.Samplerate, framesPerBuffer, flags, StreamCallbackWrapper, reinterpret_cast<void *>(this)) != paNoError)
+ {
+ return false;
+ }
+ m_StreamInfo = Pa_GetStreamInfo(m_Stream);
+ if(!m_StreamInfo)
+ {
+ Pa_CloseStream(m_Stream);
+ m_Stream = 0;
+ return false;
+ }
+ return true;
+}
+
+
+bool CPortaudioDevice::InternalClose()
+{
+ if(m_Stream)
+ {
+ const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
+ Pa_AbortStream(m_Stream);
+ Pa_CloseStream(m_Stream);
+ if(Pa_GetDeviceInfo(m_StreamParameters.device)->hostApi == Pa_HostApiTypeIdToHostApiIndex(paWDMKS))
+ {
+ Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5)); // wait for broken wdm drivers not closing the stream immediatly
+ }
+ m_StreamParameters = {};
+ m_InputStreamParameters = {};
+ m_StreamInfo = 0;
+ m_Stream = 0;
+ m_CurrentFrameCount = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ }
+ return true;
+}
+
+
+bool CPortaudioDevice::InternalStart()
+{
+ return Pa_StartStream(m_Stream) == paNoError;
+}
+
+
+void CPortaudioDevice::InternalStop()
+{
+ Pa_StopStream(m_Stream);
+}
+
+
+void CPortaudioDevice::InternalFillAudioBuffer()
+{
+ if(m_CurrentFrameCount == 0)
+ {
+ return;
+ }
+ CallbackLockedAudioReadPrepare(m_CurrentFrameCount, mpt::saturate_cast<std::size_t>(mpt::saturate_round<int64>(m_CurrentRealLatency * m_StreamInfo->sampleRate)));
+ CallbackLockedAudioProcessVoid(m_CurrentFrameBuffer, m_CurrentFrameBufferInput, m_CurrentFrameCount);
+ m_StatisticPeriodFrames.store(m_CurrentFrameCount);
+ CallbackLockedAudioProcessDone();
+}
+
+
+int64 CPortaudioDevice::InternalGetStreamPositionFrames() const
+{
+ if(Pa_IsStreamActive(m_Stream) != 1)
+ {
+ return 0;
+ }
+ return static_cast<int64>(Pa_GetStreamTime(m_Stream) * m_StreamInfo->sampleRate);
+}
+
+
+SoundDevice::BufferAttributes CPortaudioDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_StreamInfo->outputLatency;
+ bufferAttributes.UpdateInterval = m_Settings.UpdateInterval;
+ bufferAttributes.NumBuffers = 1;
+ if(m_HostApiType == paWASAPI && m_Settings.ExclusiveMode)
+ {
+ // WASAPI exclusive mode streams only account for a single period of latency in PortAudio
+ // (i.e. the same way as Steinerg ASIO defines latency).
+ // That does not match our definition of latency, repair it.
+ bufferAttributes.Latency *= 2.0;
+ }
+ return bufferAttributes;
+}
+
+
+bool CPortaudioDevice::OnIdle()
+{
+ if(!IsPlaying())
+ {
+ return false;
+ }
+ if(m_Stream)
+ {
+ if(m_HostApiType == paWDMKS)
+ {
+ // Catch timeouts in PortAudio threading code that cause the thread to exit.
+ // Restore our desired playback state by resetting the whole sound device.
+ if(Pa_IsStreamActive(m_Stream) <= 0)
+ {
+ // Hung state tends to be caused by an overloaded system.
+ // Sleeping too long would freeze the UI,
+ // but at least sleep a tiny bit of time to let things settle down.
+ const SoundDevice::BufferAttributes bufferAttributes = GetEffectiveBufferAttributes();
+ Pa_Sleep(mpt::saturate_round<long>(bufferAttributes.Latency * 2.0 * 1000.0 + 0.5));
+ RequestReset();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+
+SoundDevice::Statistics CPortaudioDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = m_CurrentRealLatency;
+ result.LastUpdateInterval = 1.0 * m_StatisticPeriodFrames / m_Settings.Samplerate;
+ result.text = mpt::ustring();
+#if MPT_OS_WINDOWS
+ if(m_HostApiType == paWASAPI)
+ {
+ if(m_Settings.ExclusiveMode)
+ {
+ if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiExplicitSampleFormat))
+ {
+ result.text += MPT_USTRING("Exclusive stream.");
+ } else
+ {
+ result.text += MPT_USTRING("Exclusive stream with sample format conversion.");
+ }
+ } else
+ {
+ if(m_StreamParameters.hostApiSpecificStreamInfo && (m_WasapiStreamInfo.flags & paWinWasapiAutoConvert))
+ {
+ result.text += MPT_USTRING("WASAPI stream resampling.");
+ } else
+ {
+ result.text += MPT_USTRING("No resampling.");
+ }
+ }
+ }
+#endif // MPT_OS_WINDOWS
+ return result;
+}
+
+
+SoundDevice::Caps CPortaudioDevice::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = false;
+ caps.CanBoostThreadPriority = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = true;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(m_DeviceIndex);
+ if(deviceInfo)
+ {
+ caps.DefaultSettings.Latency = deviceInfo->defaultLowOutputLatency;
+ }
+ if(HasInputChannelsOnSameDevice())
+ {
+ caps.CanInput = true;
+ caps.HasNamedInputSources = false;
+ } else
+ {
+ caps.CanInput = (EnumerateInputOnlyDevices(m_HostApiType).size() > 0);
+ caps.HasNamedInputSources = caps.CanInput;
+ }
+ if(m_HostApiType == paWASAPI)
+ {
+ caps.CanBoostThreadPriority = true;
+ caps.CanDriverPanel = true;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ if(m_DeviceIsDefault)
+ {
+ caps.CanExclusiveMode = false;
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.010;
+ } else
+ {
+ caps.CanExclusiveMode = true;
+ if(deviceInfo)
+ {
+ // PortAudio WASAPI returns the device period as latency
+ caps.DefaultSettings.Latency = deviceInfo->defaultHighOutputLatency * 2.0;
+ caps.DefaultSettings.UpdateInterval = deviceInfo->defaultHighOutputLatency;
+ }
+ }
+ } else if(m_HostApiType == paWDMKS)
+ {
+ caps.CanUpdateInterval = false;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
+ } else if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsWine)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ } else if(GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ } else if(m_HostApiType == paASIO)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int32;
+ }
+ if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.HasInternalDither = false;
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.HasInternalDither = false;
+ }
+ } else if(m_HostApiType == paJACK)
+ {
+ caps.HasInternalDither = false;
+ } else if(m_HostApiType == paWASAPI)
+ {
+ caps.HasInternalDither = false;
+ }
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CPortaudioDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ PaDeviceIndex device = m_DeviceIndex;
+ if(device == paNoDevice)
+ {
+ return caps;
+ }
+ for(std::size_t n = 0; n < baseSampleRates.size(); n++)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ StreamParameters.sampleFormat = paInt16;
+ if(m_HostApiType == paWASAPI)
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, baseSampleRates[n]) == paFormatIsSupported)
+ {
+ caps.supportedSampleRates.push_back(baseSampleRates[n]);
+ if(!((m_HostApiType == paWASAPI) && m_DeviceIsDefault))
+ {
+ caps.supportedExclusiveSampleRates.push_back(baseSampleRates[n]);
+ }
+ }
+ }
+ if(m_HostApiType == paDirectSound)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+ } else if(m_HostApiType == paMME)
+ {
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+ } else if(m_HostApiType == paJACK)
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ } else if(m_HostApiType == paWASAPI)
+ {
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ }
+#if MPT_OS_WINDOWS
+ if(m_HostApiType == paWASAPI && !m_DeviceIsDefault)
+ {
+ caps.supportedExclusiveModeSampleFormats.clear();
+ const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
+ for(const SampleFormat sampleFormat : sampleFormats)
+ {
+ for(const auto sampleRate : caps.supportedExclusiveSampleRates)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ if(sampleFormat.IsFloat())
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: StreamParameters.sampleFormat = paInt8; break;
+ case 16: StreamParameters.sampleFormat = paInt16; break;
+ case 24: StreamParameters.sampleFormat = paInt24; break;
+ case 32: StreamParameters.sampleFormat = paInt32; break;
+ }
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ PaWasapiStreamInfo wasapiStreamInfo = {};
+ wasapiStreamInfo.size = sizeof(PaWasapiStreamInfo);
+ wasapiStreamInfo.hostApiType = paWASAPI;
+ wasapiStreamInfo.version = 1;
+ wasapiStreamInfo.flags = paWinWasapiExclusive | paWinWasapiExplicitSampleFormat;
+ StreamParameters.hostApiSpecificStreamInfo = &wasapiStreamInfo;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ break;
+ }
+ }
+ }
+ }
+ if(m_HostApiType == paWDMKS)
+ {
+ caps.supportedSampleFormats.clear();
+ caps.supportedExclusiveModeSampleFormats.clear();
+ const std::array<SampleFormat, 5> sampleFormats{SampleFormat::Int8, SampleFormat::Int16, SampleFormat::Int24, SampleFormat::Int32, SampleFormat::Float32};
+ for(const SampleFormat sampleFormat : sampleFormats)
+ {
+ for(const auto sampleRate : caps.supportedSampleRates)
+ {
+ PaStreamParameters StreamParameters = {};
+ StreamParameters.device = device;
+ StreamParameters.channelCount = 2;
+ if(sampleFormat.IsFloat())
+ {
+ StreamParameters.sampleFormat = paFloat32;
+ } else
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: StreamParameters.sampleFormat = paInt8; break;
+ case 16: StreamParameters.sampleFormat = paInt16; break;
+ case 24: StreamParameters.sampleFormat = paInt24; break;
+ case 32: StreamParameters.sampleFormat = paInt32; break;
+ }
+ }
+ StreamParameters.suggestedLatency = 0.0;
+ StreamParameters.hostApiSpecificStreamInfo = NULL;
+ if(Pa_IsFormatSupported(NULL, &StreamParameters, sampleRate) == paFormatIsSupported)
+ {
+ caps.supportedSampleFormats.push_back(sampleFormat);
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ break;
+ }
+ }
+ }
+ if(caps.supportedSampleFormats.empty())
+ {
+ caps.supportedSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ }
+ if(caps.supportedExclusiveModeSampleFormats.empty())
+ {
+ caps.supportedExclusiveModeSampleFormats = DefaultSampleFormats<std::vector<SampleFormat>>();
+ }
+ }
+#endif // MPT_OS_WINDOWS
+#if MPT_OS_WINDOWS
+ if((m_HostApiType == paWASAPI) && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7))
+ {
+ caps.supportedSampleRates = baseSampleRates;
+ }
+#endif // MPT_OS_WINDOWS
+
+ if(!HasInputChannelsOnSameDevice())
+ {
+ caps.inputSourceNames.clear();
+ auto inputDevices = EnumerateInputOnlyDevices(m_HostApiType);
+ for(const auto &dev : inputDevices)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(dev.first), dev.second));
+ }
+ }
+
+ return caps;
+}
+
+
+bool CPortaudioDevice::OpenDriverSettings()
+{
+#if MPT_OS_WINDOWS
+ if(m_HostApiType != paWASAPI)
+ {
+ return false;
+ }
+ bool hasVista = GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista);
+ mpt::winstring controlEXE;
+ TCHAR systemDir[MAX_PATH] = {};
+ if(GetSystemDirectory(systemDir, mpt::saturate_cast<UINT>(std::size(systemDir))) > 0)
+ {
+ controlEXE += mpt::ReadWinBuf(systemDir);
+ controlEXE += TEXT("\\");
+ }
+ controlEXE += TEXT("control.exe");
+ return (reinterpret_cast<INT_PTR>(ShellExecute(NULL, TEXT("open"), controlEXE.c_str(), (hasVista ? TEXT("/name Microsoft.Sound") : TEXT("mmsys.cpl")), NULL, SW_SHOW)) >= 32);
+#else // !MPT_OS_WINDOWS
+ return false;
+#endif // MPT_OS_WINDOWS
+}
+
+
+int CPortaudioDevice::StreamCallback(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
+{
+ if(!input && !output)
+ {
+ return paAbort;
+ }
+ if(m_HostApiType == paWDMKS)
+ {
+ // For WDM-KS, timeInfo->outputBufferDacTime seems to contain bogus values.
+ // Work-around it by using the slightly less accurate per-stream latency estimation.
+ m_CurrentRealLatency = m_StreamInfo->outputLatency;
+ } else if(m_HostApiType == paWASAPI)
+ {
+ // PortAudio latency calculation appears to miss the current period or chunk for WASAPI. Compensate it.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else if(m_HostApiType == paDirectSound)
+ {
+ // PortAudio latency calculation appears to miss the buffering latency.
+ // The current chunk, however, appears to be compensated for.
+ // Repair the confusion.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else if(m_HostApiType == paALSA)
+ {
+ // PortAudio latency calculation appears to miss the buffering latency.
+ // The current chunk, however, appears to be compensated for.
+ // Repair the confusion.
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime + m_StreamInfo->outputLatency - (static_cast<double>(frameCount) / static_cast<double>(m_Settings.Samplerate));
+ } else
+ {
+ m_CurrentRealLatency = timeInfo->outputBufferDacTime - timeInfo->currentTime;
+ }
+ m_CurrentFrameBuffer = output;
+ m_CurrentFrameBufferInput = input;
+ m_CurrentFrameCount = frameCount;
+ CallbackFillAudioBufferLocked();
+ m_CurrentFrameCount = 0;
+ m_CurrentFrameBuffer = 0;
+ m_CurrentFrameBufferInput = 0;
+ if((m_HostApiType == paALSA) && (statusFlags & paOutputUnderflow))
+ {
+ // PortAudio ALSA does not recover well from buffer underruns
+ RequestRestart();
+ }
+ return paContinue;
+}
+
+
+int CPortaudioDevice::StreamCallbackWrapper(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
+{
+ return reinterpret_cast<CPortaudioDevice *>(userData)->StreamCallback(input, output, frameCount, timeInfo, statusFlags);
+}
+
+
+std::vector<SoundDevice::Info> CPortaudioDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if PA_LOG_ENABLED
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
+ {
+ if(!Pa_GetDeviceInfo(dev))
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->hostApi < 0)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
+ {
+ continue;
+ }
+ if(!Pa_GetDeviceInfo(dev)->name)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxOutputChannels <= 0)
+ {
+ continue;
+ }
+ SoundDevice::Info result = SoundDevice::Info();
+ switch((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type))
+ {
+ case paWASAPI:
+ result.type = TypePORTAUDIO_WASAPI;
+ break;
+ case paWDMKS:
+ result.type = TypePORTAUDIO_WDMKS;
+ break;
+ case paMME:
+ result.type = TypePORTAUDIO_WMME;
+ break;
+ case paDirectSound:
+ result.type = TypePORTAUDIO_DS;
+ break;
+ default:
+ result.type = MPT_USTRING("PortAudio") + MPT_USTRING("-") + mpt::format<mpt::ustring>::dec(static_cast<int>(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type));
+ break;
+ }
+ result.internalID = mpt::format<mpt::ustring>::dec(dev);
+ result.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name);
+ result.apiName = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
+ result.extraData[MPT_USTRING("PortAudio-HostAPI-name")] = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name);
+ result.apiPath.push_back(MPT_USTRING("PortAudio"));
+ result.useNameAsIdentifier = true;
+ result.flags = {
+ Info::Usability::Unknown,
+ Info::Level::Unknown,
+ Info::Compatible::Unknown,
+ Info::Api::Unknown,
+ Info::Io::Unknown,
+ Info::Mixing::Unknown,
+ Info::Implementor::External};
+ // clang-format off
+ switch(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type)
+ {
+ case paDirectSound:
+ result.apiName = MPT_USTRING("DirectSound");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paMME:
+ result.apiName = MPT_USTRING("MME");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Legacy : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paASIO:
+ result.apiName = MPT_USTRING("ASIO");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case paCoreAudio:
+ result.apiName = MPT_USTRING("CoreAudio");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case paOSS:
+ result.apiName = MPT_USTRING("OSS");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Api::Native : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Mixing::Hardware : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Software : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paALSA:
+ result.apiName = MPT_USTRING("ALSA");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paAL:
+ result.apiName = MPT_USTRING("OpenAL");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ Info::Usability::Usable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case paWDMKS:
+ result.apiName = sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? MPT_USTRING("WaveRT") : MPT_USTRING("WDM-KS");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Usability::Usable : Info::Usability::Usable : Info::Usability::Broken : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Native : Info::Api::Native : Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case paJACK:
+ result.apiName = MPT_USTRING("JACK");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Managed : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case paWASAPI:
+ result.apiName = MPT_USTRING("WASAPI");
+ result.default_ = ((Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->defaultOutputDevice == static_cast<PaDeviceIndex>(dev)) ? Info::Default::Named : Info::Default::None);
+ result.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ?
+ sysInfo.IsWindowsOriginal() ?
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7) ?
+ Info::Usability::Usable
+ :
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ?
+ Info::Usability::Experimental
+ :
+ Info::Usability::NotAvailable
+ :
+ Info::Usability::Usable
+ :
+ Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ default:
+ // nothing
+ break;
+ }
+ // clang-format on
+ PALOG(MPT_UFORMAT_MESSAGE("PortAudio: {}, {}, {}, {}")(result.internalID, result.name, result.apiName, static_cast<int>(result.default_)));
+ PALOG(MPT_UFORMAT_MESSAGE(" low : {}")(Pa_GetDeviceInfo(dev)->defaultLowOutputLatency));
+ PALOG(MPT_UFORMAT_MESSAGE(" high : {}")(Pa_GetDeviceInfo(dev)->defaultHighOutputLatency));
+ if((result.default_ != Info::Default::None) && (Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type == paWASAPI))
+ {
+ auto defaultResult = result;
+ defaultResult.default_ = Info::Default::Managed;
+ defaultResult.name = MPT_USTRING("Default Device");
+ defaultResult.internalID = MPT_USTRING("WASAPI-Default");
+ defaultResult.useNameAsIdentifier = false;
+ devices.push_back(defaultResult);
+ result.default_ = Info::Default::Named;
+ }
+ devices.push_back(result);
+ }
+ return devices;
+}
+
+
+std::vector<std::pair<PaDeviceIndex, mpt::ustring>> CPortaudioDevice::EnumerateInputOnlyDevices(PaHostApiTypeId hostApiType)
+{
+ std::vector<std::pair<PaDeviceIndex, mpt::ustring>> result;
+ for(PaDeviceIndex dev = 0; dev < Pa_GetDeviceCount(); ++dev)
+ {
+ if(!Pa_GetDeviceInfo(dev))
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->hostApi < 0)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi))
+ {
+ continue;
+ }
+ if(!Pa_GetDeviceInfo(dev)->name)
+ {
+ continue;
+ }
+ if(!Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->name)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxInputChannels <= 0)
+ {
+ continue;
+ }
+ if(Pa_GetDeviceInfo(dev)->maxOutputChannels > 0)
+ { // only find devices with only input channels
+ continue;
+ }
+ if(Pa_GetHostApiInfo(Pa_GetDeviceInfo(dev)->hostApi)->type != hostApiType)
+ {
+ continue;
+ }
+ result.push_back(std::make_pair(dev, mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, Pa_GetDeviceInfo(dev)->name)));
+ }
+ return result;
+}
+
+
+bool CPortaudioDevice::HasInputChannelsOnSameDevice() const
+{
+ if(m_DeviceIndex == paNoDevice)
+ {
+ return false;
+ }
+ const PaDeviceInfo *deviceinfo = Pa_GetDeviceInfo(m_DeviceIndex);
+ if(!deviceinfo)
+ {
+ return false;
+ }
+ return (deviceinfo->maxInputChannels > 0);
+}
+
+
+#if MPT_COMPILER_MSVC
+static void PortaudioLog(const char *text)
+{
+ if(!text)
+ {
+ return;
+ }
+#if PA_LOG_ENABLED
+ MPT_LOG(mpt::log::GlobalLogger(), LogDebug, "PortAudio", MPT_UFORMAT_MESSAGE("PortAudio: {}")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, text)));
+#endif
+}
+#endif // MPT_COMPILER_MSVC
+
+
+PortAudioInitializer::PortAudioInitializer()
+{
+#if defined(MODPLUG_TRACKER) && MPT_COMPILER_MSVC
+ PaUtil_SetDebugPrintFunction(PortaudioLog);
+#endif
+ m_initialized = (Pa_Initialize() == paNoError);
+}
+
+
+void PortAudioInitializer::Reload()
+{
+ if(m_initialized)
+ {
+ Pa_Terminate();
+ m_initialized = false;
+ }
+ m_initialized = (Pa_Initialize() == paNoError);
+}
+
+
+PortAudioInitializer::~PortAudioInitializer()
+{
+ if(!m_initialized)
+ {
+ return;
+ }
+ Pa_Terminate();
+}
+
+
+#endif // MPT_WITH_PORTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp
new file mode 100644
index 00000000..892f819d
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePortAudio.hpp
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#ifdef MPT_WITH_PORTAUDIO
+#include <portaudio.h>
+#if MPT_OS_WINDOWS
+#include <pa_win_wasapi.h>
+#endif // MPT_OS_WINDOWS
+#endif
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+#ifdef MPT_WITH_PORTAUDIO
+
+class PortAudioInitializer
+ : public BackendInitializer
+{
+private:
+ bool m_initialized = false;
+
+public:
+ PortAudioInitializer();
+ PortAudioInitializer(const PortAudioInitializer &) = delete;
+ PortAudioInitializer &operator=(const PortAudioInitializer &) = delete;
+ void Reload();
+ ~PortAudioInitializer() override;
+};
+
+class CPortaudioDevice : public SoundDevice::Base
+{
+
+private:
+ PortAudioInitializer m_PortAudio;
+
+protected:
+ PaDeviceIndex m_DeviceIsDefault;
+ PaDeviceIndex m_DeviceIndex;
+ PaHostApiTypeId m_HostApiType;
+ PaStreamParameters m_StreamParameters;
+ PaStreamParameters m_InputStreamParameters;
+#if MPT_OS_WINDOWS
+ PaWasapiStreamInfo m_WasapiStreamInfo;
+#endif // MPT_OS_WINDOWS
+ PaStream *m_Stream;
+ const PaStreamInfo *m_StreamInfo;
+ void *m_CurrentFrameBuffer;
+ const void *m_CurrentFrameBufferInput;
+ unsigned long m_CurrentFrameCount;
+
+ double m_CurrentRealLatency; // seconds
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CPortaudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CPortaudioDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_Stream ? true : false; }
+ bool InternalHasGetStreamPosition() const { return false; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool OpenDriverSettings();
+ bool OnIdle();
+
+ int StreamCallback(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags);
+
+public:
+ static int StreamCallbackWrapper(
+ const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData);
+
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer()
+ {
+ return std::make_unique<PortAudioInitializer>();
+ }
+
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+private:
+ bool HasInputChannelsOnSameDevice() const;
+
+ static std::vector<std::pair<PaDeviceIndex, mpt::ustring>> EnumerateInputOnlyDevices(PaHostApiTypeId hostApiType);
+};
+
+
+#endif // MPT_WITH_PORTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp
new file mode 100644
index 00000000..5a134921
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.cpp
@@ -0,0 +1,503 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePulseSimple.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/macros.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <vector>
+
+#include <cstddef>
+#include <cstring>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+//#define MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+
+
+mpt::ustring PulseaudioSimple::PulseErrorString(int error)
+{
+ if(error == 0)
+ {
+ return mpt::ustring();
+ }
+ const char *str = pa_strerror(error);
+ if(!str)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ if(std::strlen(str) == 0)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ return MPT_UFORMAT_MESSAGE("{} (error={})")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, str), error);
+}
+
+
+#ifdef MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+static void PulseAudioSinkInfoListCallback(pa_context * /* c */, const pa_sink_info *i, int /* eol */, void *userdata)
+{
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_USTRING("PulseAudioSinkInfoListCallback"));
+ std::vector<SoundDevice::Info> *devices_ = reinterpret_cast<std::vector<SoundDevice::Info> *>(userdata);
+ if(!devices_)
+ {
+ return;
+ }
+ std::vector<SoundDevice::Info> &devices = *devices_;
+ if(!i)
+ {
+ return;
+ }
+ if(!i->name)
+ {
+ return;
+ }
+ if(!i->description)
+ {
+ return;
+ }
+ if(i->n_ports <= 0)
+ {
+ return;
+ }
+ for(uint32 port = 0; port < i->n_ports; ++port)
+ {
+ // we skip all sinks without ports or with all ports known to be currently unavailable
+ if(!i->ports)
+ {
+ break;
+ }
+ if(!i->ports[port])
+ {
+ continue;
+ }
+ if(i->ports[port]->available == PA_PORT_AVAILABLE_NO)
+ {
+ continue;
+ }
+ SoundDevice::Info info;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.type = MPT_USTRING("PulseAudio-Simple");
+#else // !MPT_ENABLE_PULSEAUDIO_FULL
+ info.type = MPT_USTRING("PulseAudio");
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->name);
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->description);
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.apiName = MPT_USTRING("PulseAudio Simple API");
+#else
+ info.apiName = MPT_USTRING("PulseAudio");
+#endif
+ info.default_ = Info::Default::None;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+ break;
+ }
+}
+
+#endif // MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+
+std::vector<SoundDevice::Info> PulseaudioSimple::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if 0
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ SoundDevice::Info info;
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.type = MPT_USTRING("PulseAudio-Simple");
+#else // !MPT_ENABLE_PULSEAUDIO_FULL
+ info.type = MPT_USTRING("PulseAudio");
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+ info.internalID = MPT_USTRING("0");
+ info.name = MPT_USTRING("Default Device");
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+ info.apiName = MPT_USTRING("PulseAudio Simple API");
+#else
+ info.apiName = MPT_USTRING("PulseAudio");
+#endif
+ info.default_ = Info::Default::Managed;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+
+#ifdef MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+ int result = 0;
+ pa_mainloop *m = nullptr;
+ pa_context *c = nullptr;
+ bool doneConnect = false;
+ pa_context_state_t cs = PA_CONTEXT_UNCONNECTED;
+ pa_operation *o = nullptr;
+ pa_operation_state_t s = PA_OPERATION_RUNNING;
+
+ m = pa_mainloop_new();
+ if(!m)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_new"));
+ goto cleanup;
+ }
+ c = pa_context_new(pa_mainloop_get_api(m), mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::ustring()).c_str()); // TODO: get AppInfo
+ if(!c)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_new"));
+ goto cleanup;
+ }
+ if(pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ doneConnect = false;
+ while(!doneConnect)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ cs = pa_context_get_state(c);
+ switch(cs)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ doneConnect = true;
+ break;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ default:
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ break;
+ }
+ }
+ o = pa_context_get_sink_info_list(c, &PulseAudioSinkInfoListCallback, &devices);
+ if(!o)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_get_sink_info_list: ") + PulseErrorString(pa_context_errno(c)));
+ goto cleanup;
+ }
+ s = PA_OPERATION_RUNNING;
+ while((s = pa_operation_get_state(o)) == PA_OPERATION_RUNNING)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ }
+ if(s == PA_OPERATION_CANCELLED)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_operation_get_state"));
+ goto cleanup;
+ }
+ goto cleanup;
+
+cleanup:
+
+ if(o)
+ {
+ pa_operation_unref(o);
+ o = nullptr;
+ }
+ if(c)
+ {
+ pa_context_disconnect(c);
+ pa_context_unref(c);
+ c = nullptr;
+ }
+ if(m)
+ {
+ pa_mainloop_quit(m, 0);
+ pa_mainloop_run(m, &result);
+ pa_mainloop_free(m);
+ m = nullptr;
+ }
+
+#endif // MPT_PULSEAUDIO_SIMPLE_ENUMERATE_DEVICES
+
+ return devices;
+}
+
+
+PulseaudioSimple::PulseaudioSimple(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : ThreadBase(logger, info, sysInfo)
+ , m_PA_SimpleOutput(nullptr)
+ , m_StatisticLastLatencyFrames(0)
+{
+ return;
+}
+
+
+SoundDevice::Caps PulseaudioSimple::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true; // TODO: poll PulseAudio
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Adjust latency");
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.005;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ caps.DefaultSettings.ExclusiveMode = true;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps PulseaudioSimple::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ caps.supportedSampleRates = baseSampleRates;
+ caps.supportedExclusiveSampleRates = baseSampleRates;
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ return caps;
+}
+
+
+bool PulseaudioSimple::InternalIsOpen() const
+{
+ return m_PA_SimpleOutput;
+}
+
+
+bool PulseaudioSimple::InternalOpen()
+{
+ if(m_Settings.sampleFormat != SampleFormat::Float32)
+ {
+ InternalClose();
+ return false;
+ }
+ int error = 0;
+ pa_sample_spec ss = {};
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = m_Settings.Samplerate;
+ ss.channels = m_Settings.Channels;
+ pa_buffer_attr ba = {};
+ ba.minreq = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.UpdateInterval), m_Settings.GetBytesPerFrame());
+ ba.maxlength = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.Latency), m_Settings.GetBytesPerFrame());
+ ba.tlength = ba.maxlength - ba.minreq;
+ ba.prebuf = ba.tlength;
+ ba.fragsize = 0;
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ m_EffectiveBufferAttributes.Latency = static_cast<double>(ba.maxlength) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.UpdateInterval = static_cast<double>(ba.minreq) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.NumBuffers = 1;
+ m_OutputBuffer.resize(ba.minreq / m_Settings.sampleFormat.GetSampleSize());
+ m_PA_SimpleOutput = pa_simple_new(
+ NULL,
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ PA_STREAM_PLAYBACK,
+ ((GetDeviceInternalID() == MPT_USTRING("0")) ? NULL : mpt::transcode<std::string>(mpt::common_encoding::utf8, GetDeviceInternalID()).c_str()),
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ &ss,
+ NULL,
+ (m_Settings.ExclusiveMode ? &ba : NULL),
+ &error);
+ if(!m_PA_SimpleOutput)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_new failed: {}")(PulseErrorString(error)));
+ InternalClose();
+ return false;
+ }
+ return true;
+}
+
+
+void PulseaudioSimple::InternalStartFromSoundThread()
+{
+ return;
+}
+
+
+void PulseaudioSimple::InternalFillAudioBuffer()
+{
+ bool needsClose = false;
+ int error = 0;
+ error = 0;
+ pa_usec_t latency_usec = pa_simple_get_latency(m_PA_SimpleOutput, &error);
+ if(error != 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_get_latency failed: {}")(PulseErrorString(error)));
+ RequestClose();
+ return;
+ }
+ error = 0;
+ // We add the update period to the latency because:
+ // 1. PulseAudio latency calculation is done before we are actually
+ // refilling.
+ // 2. We have 1 additional period latency becasue the writing is blocking and
+ // audio has will be calculated almost one period in advance in the worst
+ // case.
+ // I think, in total we only need to add the period once.
+ std::size_t latencyFrames = 0;
+ latencyFrames += (latency_usec * m_Settings.Samplerate) / 1000000;
+ latencyFrames += 1 * (m_OutputBuffer.size() / m_Settings.Channels);
+ CallbackLockedAudioReadPrepare(m_OutputBuffer.size() / m_Settings.Channels, latencyFrames);
+ CallbackLockedAudioProcess(m_OutputBuffer.data(), nullptr, m_OutputBuffer.size() / m_Settings.Channels);
+ error = 0;
+ if(pa_simple_write(m_PA_SimpleOutput, &(m_OutputBuffer[0]), m_OutputBuffer.size() * sizeof(float32), &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_write failed: {}")(PulseErrorString(error)));
+ needsClose = true;
+ }
+ m_StatisticLastLatencyFrames.store(latencyFrames);
+ CallbackLockedAudioProcessDone();
+ if(needsClose)
+ {
+ RequestClose();
+ return;
+ }
+}
+
+
+void PulseaudioSimple::InternalWaitFromSoundThread()
+{
+ // We block in InternalFillAudioBuffer and thus have no need to wait further
+ return;
+}
+
+
+SoundDevice::BufferAttributes PulseaudioSimple::InternalGetEffectiveBufferAttributes() const
+{
+ return m_EffectiveBufferAttributes;
+}
+
+
+SoundDevice::Statistics PulseaudioSimple::GetStatistics() const
+{
+ SoundDevice::Statistics stats;
+ stats.InstantaneousLatency = static_cast<double>(m_StatisticLastLatencyFrames.load()) / static_cast<double>(m_Settings.Samplerate);
+ stats.LastUpdateInterval = m_EffectiveBufferAttributes.UpdateInterval;
+ stats.text = mpt::ustring();
+ return stats;
+}
+
+
+void PulseaudioSimple::InternalStopFromSoundThread()
+{
+ int error = 0;
+ bool oldVersion = false;
+ std::vector<uint64> version = mpt::split_parse<uint64>(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, pa_get_library_version() ? pa_get_library_version() : ""));
+ if(!version.empty())
+ {
+ if(version[0] < 4)
+ {
+ oldVersion = true;
+ }
+ }
+ if(oldVersion)
+ {
+ // draining is awfully slow with pulseaudio version < 4.0.0,
+ // just flush there
+ error = 0;
+ if(pa_simple_flush(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_flush failed: {}")(PulseErrorString(error)));
+ }
+ } else
+ {
+ error = 0;
+ if(pa_simple_drain(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_drain failed: {}")(PulseErrorString(error)));
+ }
+ }
+ return;
+}
+
+
+bool PulseaudioSimple::InternalClose()
+{
+ if(m_PA_SimpleOutput)
+ {
+ pa_simple_free(m_PA_SimpleOutput);
+ m_PA_SimpleOutput = nullptr;
+ }
+ m_OutputBuffer.resize(0);
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ return true;
+}
+
+
+PulseaudioSimple::~PulseaudioSimple()
+{
+ return;
+}
+
+
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp
new file mode 100644
index 00000000..0fb790bf
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseSimple.hpp
@@ -0,0 +1,74 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <memory>
+#include <vector>
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_WITH_PULSEAUDIO) && defined(MPT_WITH_PULSEAUDIOSIMPLE)
+
+
+class PulseaudioSimple
+ : public ThreadBase
+{
+private:
+ static mpt::ustring PulseErrorString(int error);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+public:
+ PulseaudioSimple(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool InternalIsOpen() const;
+ bool InternalOpen();
+ void InternalStartFromSoundThread();
+ void InternalFillAudioBuffer();
+ void InternalWaitFromSoundThread();
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ void InternalStopFromSoundThread();
+ bool InternalClose();
+ ~PulseaudioSimple();
+
+private:
+ pa_simple *m_PA_SimpleOutput;
+ SoundDevice::BufferAttributes m_EffectiveBufferAttributes;
+ std::vector<float32> m_OutputBuffer;
+ std::atomic<uint32> m_StatisticLastLatencyFrames;
+};
+
+
+#endif // MPT_WITH_PULSEAUDIO && MPT_WITH_PULSEAUDIOSIMPLE
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp
new file mode 100644
index 00000000..237e2228
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.cpp
@@ -0,0 +1,475 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevicePulseaudio.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/split.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <vector>
+
+#include <cstddef>
+#include <cstring>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+
+#if defined(MPT_WITH_PULSEAUDIO)
+
+
+mpt::ustring Pulseaudio::PulseErrorString(int error)
+{
+ if(error == 0)
+ {
+ return mpt::ustring();
+ }
+ const char *str = pa_strerror(error);
+ if(!str)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ if(std::strlen(str) == 0)
+ {
+ return MPT_UFORMAT_MESSAGE("error={}")(error);
+ }
+ return MPT_UFORMAT_MESSAGE("{} (error={})")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, str), error);
+}
+
+
+static void PulseAudioSinkInfoListCallback(pa_context * /* c */, const pa_sink_info *i, int /* eol */, void *userdata)
+{
+ MPT_LOG(GetLogger(), LogDebug, "sounddev", MPT_USTRING("PulseAudioSinkInfoListCallback"));
+ std::vector<SoundDevice::Info> *devices_ = reinterpret_cast<std::vector<SoundDevice::Info> *>(userdata);
+ if(!devices_)
+ {
+ return;
+ }
+ std::vector<SoundDevice::Info> &devices = *devices_;
+ if(!i)
+ {
+ return;
+ }
+ if(!i->name)
+ {
+ return;
+ }
+ if(!i->description)
+ {
+ return;
+ }
+ if(i->n_ports <= 0)
+ {
+ return;
+ }
+ for(uint32 port = 0; port < i->n_ports; ++port)
+ {
+ // we skip all sinks without ports or with all ports known to be currently unavailable
+ if(!i->ports)
+ {
+ break;
+ }
+ if(!i->ports[port])
+ {
+ continue;
+ }
+ if(i->ports[port]->available == PA_PORT_AVAILABLE_NO)
+ {
+ continue;
+ }
+ SoundDevice::Info info;
+ info.type = MPT_USTRING("PulseAudio");
+ info.internalID = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->name);
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, i->description);
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = Info::Default::None;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+ break;
+ }
+}
+
+
+std::vector<SoundDevice::Info> Pulseaudio::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ std::vector<SoundDevice::Info> devices;
+ SoundDevice::Info info;
+ info.type = MPT_USTRING("PulseAudio");
+ info.internalID = MPT_USTRING("0");
+ info.name = MPT_USTRING("Default Device");
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = Info::Default::Managed;
+ info.useNameAsIdentifier = false;
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ // clang-format on
+ devices.push_back(info);
+
+ int result = 0;
+ pa_mainloop *m = nullptr;
+ pa_context *c = nullptr;
+ bool doneConnect = false;
+ pa_context_state_t cs = PA_CONTEXT_UNCONNECTED;
+ pa_operation *o = nullptr;
+ pa_operation_state_t s = PA_OPERATION_RUNNING;
+
+ m = pa_mainloop_new();
+ if(!m)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_new"));
+ goto cleanup;
+ }
+ c = pa_context_new(pa_mainloop_get_api(m), mpt::transcode<std::string>(mpt::common_encoding::utf8, mpt::ustring()).c_str()); // TODO: get AppInfo
+ if(!c)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_new"));
+ goto cleanup;
+ }
+ if(pa_context_connect(c, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ doneConnect = false;
+ while(!doneConnect)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ cs = pa_context_get_state(c);
+ switch(cs)
+ {
+ case PA_CONTEXT_UNCONNECTED:
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ case PA_CONTEXT_READY:
+ doneConnect = true;
+ break;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ default:
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_connect"));
+ goto cleanup;
+ }
+ break;
+ }
+ }
+ o = pa_context_get_sink_info_list(c, &PulseAudioSinkInfoListCallback, &devices);
+ if(!o)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_context_get_sink_info_list: ") + PulseErrorString(pa_context_errno(c)));
+ goto cleanup;
+ }
+ s = PA_OPERATION_RUNNING;
+ while((s = pa_operation_get_state(o)) == PA_OPERATION_RUNNING)
+ {
+ if(pa_mainloop_iterate(m, 1, &result) < 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_mainloop_iterate"));
+ goto cleanup;
+ }
+ }
+ if(s == PA_OPERATION_CANCELLED)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_USTRING("pa_operation_get_state"));
+ goto cleanup;
+ }
+ goto cleanup;
+
+cleanup:
+
+ if(o)
+ {
+ pa_operation_unref(o);
+ o = nullptr;
+ }
+ if(c)
+ {
+ pa_context_disconnect(c);
+ pa_context_unref(c);
+ c = nullptr;
+ }
+ if(m)
+ {
+ pa_mainloop_quit(m, 0);
+ pa_mainloop_run(m, &result);
+ pa_mainloop_free(m);
+ m = nullptr;
+ }
+
+ return devices;
+}
+
+
+Pulseaudio::Pulseaudio(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : ThreadBase(logger, info, sysInfo)
+ , m_PA_SimpleOutput(nullptr)
+ , m_StatisticLastLatencyFrames(0)
+{
+ return;
+}
+
+
+SoundDevice::Caps Pulseaudio::InternalGetDeviceCaps()
+{
+ SoundDevice::Caps caps;
+ caps.Available = true; // TODO: poll PulseAudio
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = false;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = true;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use early requests");
+ caps.DefaultSettings.Latency = 0.030;
+ caps.DefaultSettings.UpdateInterval = 0.005;
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ caps.DefaultSettings.ExclusiveMode = true;
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps Pulseaudio::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ SoundDevice::DynamicCaps caps;
+ caps.supportedSampleRates = baseSampleRates;
+ caps.supportedExclusiveSampleRates = baseSampleRates;
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ return caps;
+}
+
+
+bool Pulseaudio::InternalIsOpen() const
+{
+ return m_PA_SimpleOutput;
+}
+
+
+bool Pulseaudio::InternalOpen()
+{
+ if(m_Settings.sampleFormat != SampleFormat::Float32)
+ {
+ InternalClose();
+ return false;
+ }
+ int error = 0;
+ pa_sample_spec ss = {};
+ ss.format = PA_SAMPLE_FLOAT32;
+ ss.rate = m_Settings.Samplerate;
+ ss.channels = m_Settings.Channels;
+ pa_buffer_attr ba = {};
+ ba.minreq = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.UpdateInterval), m_Settings.GetBytesPerFrame());
+ ba.maxlength = mpt::align_up<uint32>(mpt::saturate_round<uint32>(m_Settings.GetBytesPerSecond() * m_Settings.Latency), m_Settings.GetBytesPerFrame());
+ ba.tlength = ba.maxlength - ba.minreq;
+ ba.prebuf = ba.tlength;
+ ba.fragsize = 0;
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ m_EffectiveBufferAttributes.Latency = static_cast<double>(ba.maxlength) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.UpdateInterval = static_cast<double>(ba.minreq) / static_cast<double>(m_Settings.GetBytesPerSecond());
+ m_EffectiveBufferAttributes.NumBuffers = 1;
+ m_OutputBuffer.resize(ba.minreq / m_Settings.sampleFormat.GetSampleSize());
+ m_PA_SimpleOutput = pa_simple_new(
+ NULL,
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ PA_STREAM_PLAYBACK,
+ ((GetDeviceInternalID() == MPT_USTRING("0")) ? NULL : mpt::transcode<std::string>(mpt::common_encoding::utf8, GetDeviceInternalID()).c_str()),
+ mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName()).c_str(),
+ &ss,
+ NULL,
+ (m_Settings.ExclusiveMode ? &ba : NULL),
+ &error);
+ if(!m_PA_SimpleOutput)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_new failed: {}")(PulseErrorString(error)));
+ InternalClose();
+ return false;
+ }
+ return true;
+}
+
+
+void Pulseaudio::InternalStartFromSoundThread()
+{
+ return;
+}
+
+
+void Pulseaudio::InternalFillAudioBuffer()
+{
+ bool needsClose = false;
+ int error = 0;
+ error = 0;
+ pa_usec_t latency_usec = pa_simple_get_latency(m_PA_SimpleOutput, &error);
+ if(error != 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_get_latency failed: {}")(PulseErrorString(error)));
+ RequestClose();
+ return;
+ }
+ error = 0;
+ // We add the update period to the latency because:
+ // 1. PulseAudio latency calculation is done before we are actually
+ // refilling.
+ // 2. We have 1 additional period latency becasue the writing is blocking and
+ // audio has will be calculated almost one period in advance in the worst
+ // case.
+ // I think, in total we only need to add the period once.
+ std::size_t latencyFrames = 0;
+ latencyFrames += (latency_usec * m_Settings.Samplerate) / 1000000;
+ latencyFrames += 1 * (m_OutputBuffer.size() / m_Settings.Channels);
+ CallbackLockedAudioReadPrepare(m_OutputBuffer.size() / m_Settings.Channels, latencyFrames);
+ CallbackLockedAudioProcess(m_OutputBuffer.data(), nullptr, m_OutputBuffer.size() / m_Settings.Channels);
+ error = 0;
+ if(pa_simple_write(m_PA_SimpleOutput, &(m_OutputBuffer[0]), m_OutputBuffer.size() * sizeof(float32), &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_write failed: {}")(PulseErrorString(error)));
+ needsClose = true;
+ }
+ m_StatisticLastLatencyFrames.store(latencyFrames);
+ CallbackLockedAudioProcessDone();
+ if(needsClose)
+ {
+ RequestClose();
+ return;
+ }
+}
+
+
+void Pulseaudio::InternalWaitFromSoundThread()
+{
+ // We block in InternalFillAudioBuffer and thus have no need to wait further
+ return;
+}
+
+
+SoundDevice::BufferAttributes Pulseaudio::InternalGetEffectiveBufferAttributes() const
+{
+ return m_EffectiveBufferAttributes;
+}
+
+
+SoundDevice::Statistics Pulseaudio::GetStatistics() const
+{
+ SoundDevice::Statistics stats;
+ stats.InstantaneousLatency = static_cast<double>(m_StatisticLastLatencyFrames.load()) / static_cast<double>(m_Settings.Samplerate);
+ stats.LastUpdateInterval = m_EffectiveBufferAttributes.UpdateInterval;
+ stats.text = mpt::ustring();
+ return stats;
+}
+
+
+void Pulseaudio::InternalStopFromSoundThread()
+{
+ int error = 0;
+ bool oldVersion = false;
+ std::vector<uint64> version = mpt::split_parse<uint64>(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, pa_get_library_version() ? pa_get_library_version() : ""));
+ if(!version.empty())
+ {
+ if(version[0] < 4)
+ {
+ oldVersion = true;
+ }
+ }
+ if(oldVersion)
+ {
+ // draining is awfully slow with pulseaudio version < 4.0.0,
+ // just flush there
+ error = 0;
+ if(pa_simple_flush(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_flush failed: {}")(PulseErrorString(error)));
+ }
+ } else
+ {
+ error = 0;
+ if(pa_simple_drain(m_PA_SimpleOutput, &error) < 0)
+ {
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("pa_simple_drain failed: {}")(PulseErrorString(error)));
+ }
+ }
+ return;
+}
+
+
+bool Pulseaudio::InternalClose()
+{
+ if(m_PA_SimpleOutput)
+ {
+ pa_simple_free(m_PA_SimpleOutput);
+ m_PA_SimpleOutput = nullptr;
+ }
+ m_OutputBuffer.resize(0);
+ m_EffectiveBufferAttributes = SoundDevice::BufferAttributes();
+ return true;
+}
+
+
+Pulseaudio::~Pulseaudio()
+{
+ return;
+}
+
+
+#endif // MPT_WITH_PULSEAUDIO
+
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp
new file mode 100644
index 00000000..a7168c9e
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDevicePulseaudio.hpp
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/string/types.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <memory>
+#include <vector>
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+#if defined(MPT_WITH_PULSEAUDIO)
+#include <pulse/pulseaudio.h>
+#include <pulse/simple.h>
+#endif // MPT_WITH_PULSEAUDIO
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if defined(MPT_ENABLE_PULSEAUDIO_FULL)
+
+#if defined(MPT_WITH_PULSEAUDIO)
+
+
+class Pulseaudio
+ : public ThreadBase
+{
+private:
+ static mpt::ustring PulseErrorString(int error);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+
+public:
+ Pulseaudio(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+ bool InternalIsOpen() const;
+ bool InternalOpen();
+ void InternalStartFromSoundThread();
+ void InternalFillAudioBuffer();
+ void InternalWaitFromSoundThread();
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ void InternalStopFromSoundThread();
+ bool InternalClose();
+ ~Pulseaudio();
+
+private:
+ pa_simple *m_PA_SimpleOutput;
+ SoundDevice::BufferAttributes m_EffectiveBufferAttributes;
+ std::vector<float32> m_OutputBuffer;
+ std::atomic<uint32> m_StatisticLastLatencyFrames;
+};
+
+
+#endif // MPT_WITH_PULSEAUDIO
+
+#endif // MPT_ENABLE_PULSEAUDIO_FULL
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp
new file mode 100644
index 00000000..6f1e1d6f
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.cpp
@@ -0,0 +1,779 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceRtAudio.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/alloc.hpp"
+#include "mpt/base/saturate_cast.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string/utility.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <array>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_RTAUDIO
+
+
+
+static constexpr uint8 ParseDigit(char c)
+{
+ return c - '0';
+}
+
+using RtAudioVersion = std::array<unsigned int, 3>;
+
+static constexpr RtAudioVersion ParseVersion(const char *str)
+{
+ RtAudioVersion version = {0, 0, 0};
+ std::size_t version_pos = 0;
+ while(*str)
+ {
+ const char c = *str;
+ if(c == '.')
+ {
+ version_pos += 1;
+ } else
+ {
+ version[version_pos] = (version[version_pos] * 10) + ParseDigit(c);
+ }
+ str++;
+ }
+ return version;
+}
+
+static constexpr bool RtAudioCheckVersion(const char *wanted_)
+{
+ RtAudioVersion actual = ParseVersion(RTAUDIO_VERSION);
+ RtAudioVersion wanted = ParseVersion(wanted_);
+ if(actual[0] > wanted[0])
+ {
+ return true;
+ } else if(actual[0] == wanted[0])
+ {
+ if(actual[1] > wanted[1])
+ {
+ return true;
+ } else if(actual[1] == wanted[1])
+ {
+ return (actual[2] >= wanted[2]);
+ } else
+ {
+ return false;
+ }
+ } else
+ {
+ return false;
+ }
+}
+
+template <typename RtAudio = ::RtAudio, bool is_v5_1_0 = RtAudioCheckVersion("5.1.0")>
+struct RtAudio_v5_1_0_Shim
+{
+};
+
+template <typename RtAudio>
+struct RtAudio_v5_1_0_Shim<RtAudio, true>
+{
+
+ static inline std::string getApiName(typename RtAudio::Api api)
+ {
+ return RtAudio::getApiName(api);
+ }
+
+ static inline std::string getApiDisplayName(typename RtAudio::Api api)
+ {
+ return RtAudio::getApiDisplayName(api);
+ }
+
+ static inline typename RtAudio::Api getCompiledApiByName(const std::string &name)
+ {
+ return RtAudio::getCompiledApiByName(name);
+ }
+};
+
+template <typename RtAudio>
+struct RtAudio_v5_1_0_Shim<RtAudio, false>
+{
+
+ static constexpr const char *rtaudio_api_names[][2] = {
+ {"unspecified", "Unknown" },
+ {"alsa", "ALSA" },
+ {"pulse", "Pulse" },
+ {"oss", "OpenSoundSystem"},
+ {"jack", "Jack" },
+ {"core", "CoreAudio" },
+ {"wasapi", "WASAPI" },
+ {"asio", "ASIO" },
+ {"ds", "DirectSound" },
+ {"dummy", "Dummy" },
+ };
+
+ static constexpr typename RtAudio::Api rtaudio_all_apis[] = {
+ RtAudio::UNIX_JACK,
+ RtAudio::LINUX_PULSE,
+ RtAudio::LINUX_ALSA,
+ RtAudio::LINUX_OSS,
+ RtAudio::WINDOWS_ASIO,
+ RtAudio::WINDOWS_WASAPI,
+ RtAudio::WINDOWS_DS,
+ RtAudio::MACOSX_CORE,
+ RtAudio::RTAUDIO_DUMMY,
+ RtAudio::UNSPECIFIED,
+ };
+
+ static inline std::string getApiName(typename RtAudio::Api api)
+ {
+ if(api < 0)
+ {
+ return std::string();
+ }
+ if(api >= mpt::saturate_cast<int>(std::size(rtaudio_api_names)))
+ {
+ return std::string();
+ }
+ return rtaudio_api_names[api][0];
+ }
+
+ static inline std::string getApiDisplayName(typename RtAudio::Api api)
+ {
+ if(api < 0)
+ {
+ return std::string();
+ }
+ if(api >= mpt::saturate_cast<int>(std::size(rtaudio_api_names)))
+ {
+ return std::string();
+ }
+ return rtaudio_api_names[api][1];
+ }
+
+ static inline typename RtAudio::Api getCompiledApiByName(const std::string &name)
+ {
+ for(std::size_t i = 0; i < std::size(rtaudio_api_names); ++i)
+ {
+ if(name == rtaudio_api_names[rtaudio_all_apis[i]][0])
+ {
+ return rtaudio_all_apis[i];
+ }
+ }
+ return RtAudio::UNSPECIFIED;
+ }
+};
+
+struct RtAudioShim
+ : RtAudio_v5_1_0_Shim<>
+{
+};
+
+
+static RtAudioFormat SampleFormatToRtAudioFormat(SampleFormat sampleFormat)
+{
+ RtAudioFormat result = RtAudioFormat();
+ if(sampleFormat.IsFloat())
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 32: result = RTAUDIO_FLOAT32; break;
+ case 64: result = RTAUDIO_FLOAT64; break;
+ }
+ } else if(sampleFormat.IsInt())
+ {
+ switch(sampleFormat.GetBitsPerSample())
+ {
+ case 8: result = RTAUDIO_SINT8; break;
+ case 16: result = RTAUDIO_SINT16; break;
+ case 24: result = RTAUDIO_SINT24; break;
+ case 32: result = RTAUDIO_SINT32; break;
+ }
+ }
+ return result;
+}
+
+
+CRtAudioDevice::CRtAudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo)
+ , m_RtAudio(std::unique_ptr<RtAudio>())
+ , m_FramesPerChunk(0)
+{
+ m_CurrentFrameBufferOutput = nullptr;
+ m_CurrentFrameBufferInput = nullptr;
+ m_CurrentFrameBufferCount = 0;
+ m_CurrentStreamTime = 0.0;
+ m_StatisticLatencyFrames.store(0);
+ m_StatisticPeriodFrames.store(0);
+ try
+ {
+ m_RtAudio = std::make_unique<RtAudio>(GetApi(info));
+ } catch(const RtAudioError &)
+ {
+ // nothing
+ }
+}
+
+
+CRtAudioDevice::~CRtAudioDevice()
+{
+ Close();
+}
+
+
+bool CRtAudioDevice::InternalOpen()
+{
+ try
+ {
+ if(SampleFormatToRtAudioFormat(m_Settings.sampleFormat) == RtAudioFormat())
+ {
+ return false;
+ }
+ if(ChannelMapping::BaseChannel(m_Settings.Channels, m_Settings.Channels.ToDevice(0)) != m_Settings.Channels)
+ { // only simple base channel mappings are supported
+ return false;
+ }
+ m_OutputStreamParameters.deviceId = GetDevice(GetDeviceInfo());
+ m_OutputStreamParameters.nChannels = m_Settings.Channels;
+ m_OutputStreamParameters.firstChannel = m_Settings.Channels.ToDevice(0);
+ m_InputStreamParameters.deviceId = GetDevice(GetDeviceInfo());
+ m_InputStreamParameters.nChannels = m_Settings.InputChannels;
+ m_InputStreamParameters.firstChannel = m_Settings.InputSourceID;
+ m_FramesPerChunk = mpt::saturate_round<int>(m_Settings.UpdateInterval * m_Settings.Samplerate);
+ m_StreamOptions.flags = RtAudioStreamFlags();
+ m_StreamOptions.numberOfBuffers = mpt::saturate_round<int>(m_Settings.Latency * m_Settings.Samplerate / m_FramesPerChunk);
+ m_StreamOptions.priority = 0;
+ m_StreamOptions.streamName = mpt::transcode<std::string>(mpt::common_encoding::utf8, m_AppInfo.GetName());
+ if(m_Settings.BoostThreadPriority)
+ {
+ m_StreamOptions.flags |= RTAUDIO_SCHEDULE_REALTIME;
+ m_StreamOptions.priority = m_AppInfo.BoostedThreadPriorityXP;
+ }
+ if(m_Settings.ExclusiveMode)
+ {
+ //m_FramesPerChunk = 0; // auto
+ m_StreamOptions.flags |= RTAUDIO_MINIMIZE_LATENCY | RTAUDIO_HOG_DEVICE;
+ m_StreamOptions.numberOfBuffers = 2;
+ }
+ if(m_RtAudio->getCurrentApi() == RtAudio::Api::WINDOWS_WASAPI)
+ {
+ m_Flags.WantsClippedOutput = true;
+ } else if(m_RtAudio->getCurrentApi() == RtAudio::Api::WINDOWS_DS)
+ {
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ }
+ m_RtAudio->openStream((m_OutputStreamParameters.nChannels > 0) ? &m_OutputStreamParameters : nullptr, (m_InputStreamParameters.nChannels > 0) ? &m_InputStreamParameters : nullptr, SampleFormatToRtAudioFormat(m_Settings.sampleFormat), m_Settings.Samplerate, &m_FramesPerChunk, &RtAudioCallback, this, &m_StreamOptions, nullptr);
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+bool CRtAudioDevice::InternalClose()
+{
+ try
+ {
+ m_RtAudio->closeStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+bool CRtAudioDevice::InternalStart()
+{
+ try
+ {
+ m_RtAudio->startStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return false;
+ }
+ return true;
+}
+
+
+void CRtAudioDevice::InternalStop()
+{
+ try
+ {
+ m_RtAudio->stopStream();
+ } catch(const RtAudioError &e)
+ {
+ SendError(e);
+ return;
+ }
+ return;
+}
+
+
+void CRtAudioDevice::InternalFillAudioBuffer()
+{
+ if(m_CurrentFrameBufferCount == 0)
+ {
+ return;
+ }
+ CallbackLockedAudioReadPrepare(m_CurrentFrameBufferCount, m_FramesPerChunk * m_StreamOptions.numberOfBuffers);
+ CallbackLockedAudioProcessVoid(m_CurrentFrameBufferOutput, m_CurrentFrameBufferInput, m_CurrentFrameBufferCount);
+ m_StatisticLatencyFrames.store(m_CurrentFrameBufferCount * m_StreamOptions.numberOfBuffers);
+ m_StatisticPeriodFrames.store(m_CurrentFrameBufferCount);
+ CallbackLockedAudioProcessDone();
+}
+
+
+int64 CRtAudioDevice::InternalGetStreamPositionFrames() const
+{
+ return mpt::saturate_round<int64>(m_RtAudio->getStreamTime() * m_RtAudio->getStreamSampleRate());
+}
+
+
+SoundDevice::BufferAttributes CRtAudioDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_FramesPerChunk * m_StreamOptions.numberOfBuffers / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.UpdateInterval = m_FramesPerChunk / static_cast<double>(m_Settings.Samplerate);
+ bufferAttributes.NumBuffers = m_StreamOptions.numberOfBuffers;
+ return bufferAttributes;
+}
+
+
+int CRtAudioDevice::RtAudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData)
+{
+ reinterpret_cast<CRtAudioDevice *>(userData)->AudioCallback(outputBuffer, inputBuffer, nFrames, streamTime, status);
+ return 0; // continue
+}
+
+
+void CRtAudioDevice::AudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
+{
+ m_CurrentFrameBufferOutput = outputBuffer;
+ m_CurrentFrameBufferInput = inputBuffer;
+ m_CurrentFrameBufferCount = nFrames;
+ m_CurrentStreamTime = streamTime;
+ CallbackFillAudioBufferLocked();
+ m_CurrentFrameBufferCount = 0;
+ m_CurrentFrameBufferOutput = 0;
+ m_CurrentFrameBufferInput = 0;
+ if(status != RtAudioStreamStatus())
+ {
+ // maybe
+ // RequestRestart();
+ }
+}
+
+
+SoundDevice::Statistics CRtAudioDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ long latency = 0;
+ try
+ {
+ if(m_RtAudio->isStreamOpen())
+ {
+ latency = m_RtAudio->getStreamLatency();
+ if(m_Settings.InputChannels > 0 && m_Settings.Channels > 0)
+ {
+ latency /= 2;
+ }
+ }
+ } catch(const RtAudioError &)
+ {
+ latency = 0;
+ }
+ if(latency > 0)
+ {
+ result.InstantaneousLatency = latency / static_cast<double>(m_Settings.Samplerate);
+ result.LastUpdateInterval = m_StatisticPeriodFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ } else
+ {
+ result.InstantaneousLatency = m_StatisticLatencyFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ result.LastUpdateInterval = m_StatisticPeriodFrames.load() / static_cast<double>(m_Settings.Samplerate);
+ }
+ return result;
+}
+
+
+SoundDevice::Caps CRtAudioDevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+ if(!m_RtAudio)
+ {
+ return caps;
+ }
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = m_RtAudio->getDeviceInfo(GetDevice(GetDeviceInfo()));
+ } catch(const RtAudioError &)
+ {
+ return caps;
+ }
+ caps.Available = rtinfo.probed;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = true;
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false; // only base channel is supported, and that does not make too much sense for non-ASIO backends
+ caps.CanInput = (rtinfo.inputChannels > 0);
+ caps.HasNamedInputSources = true;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Exclusive Mode");
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CRtAudioDevice::GetDeviceDynamicCaps(const std::vector<uint32> & /* baseSampleRates */)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps caps;
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = m_RtAudio->getDeviceInfo(GetDevice(GetDeviceInfo()));
+ } catch(const RtAudioError &)
+ {
+ return caps;
+ }
+ if(!rtinfo.probed)
+ {
+ return caps;
+ }
+ caps.inputSourceNames.clear();
+ for(unsigned int channel = 0; channel < rtinfo.inputChannels; ++channel)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(channel, MPT_USTRING("Channel ") + mpt::format<mpt::ustring>::dec(channel + 1)));
+ }
+ mpt::append(caps.supportedSampleRates, rtinfo.sampleRates);
+ std::reverse(caps.supportedSampleRates.begin(), caps.supportedSampleRates.end());
+ mpt::append(caps.supportedExclusiveSampleRates, rtinfo.sampleRates);
+ std::reverse(caps.supportedExclusiveSampleRates.begin(), caps.supportedExclusiveSampleRates.end());
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(rtinfo.nativeFormats & RTAUDIO_SINT8)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int8);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT16)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT24)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int24);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_SINT32)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int32);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_FLOAT32)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Float32);
+ }
+ if(rtinfo.nativeFormats & RTAUDIO_FLOAT64)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Float64);
+ }
+ for(unsigned int channel = 0; channel < rtinfo.outputChannels; ++channel)
+ {
+ caps.channelNames.push_back(MPT_UFORMAT_MESSAGE("Output Channel {}")(channel));
+ }
+ for(unsigned int channel = 0; channel < rtinfo.inputChannels; ++channel)
+ {
+ caps.inputSourceNames.push_back(std::make_pair(static_cast<uint32>(channel), MPT_UFORMAT_MESSAGE("Input Channel {}")(channel)));
+ }
+ return caps;
+}
+
+
+void CRtAudioDevice::SendError(const RtAudioError &e)
+{
+ LogLevel level = LogError;
+ switch(e.getType())
+ {
+ case RtAudioError::WARNING:
+ level = LogWarning;
+ break;
+ case RtAudioError::DEBUG_WARNING:
+ level = LogDebug;
+ break;
+ case RtAudioError::UNSPECIFIED:
+ level = LogError;
+ break;
+ case RtAudioError::NO_DEVICES_FOUND:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_DEVICE:
+ level = LogError;
+ break;
+ case RtAudioError::MEMORY_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_PARAMETER:
+ level = LogError;
+ break;
+ case RtAudioError::INVALID_USE:
+ level = LogError;
+ break;
+ case RtAudioError::DRIVER_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::SYSTEM_ERROR:
+ level = LogError;
+ break;
+ case RtAudioError::THREAD_ERROR:
+ level = LogError;
+ break;
+ default:
+ level = LogError;
+ break;
+ }
+ SendDeviceMessage(level, mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, e.getMessage()));
+}
+
+
+RtAudio::Api CRtAudioDevice::GetApi(SoundDevice::Info info)
+{
+ std::vector<mpt::ustring> apidev = mpt::split(info.internalID, MPT_USTRING(","));
+ if(apidev.size() != 2)
+ {
+ return RtAudio::UNSPECIFIED;
+ }
+ return RtAudioShim::getCompiledApiByName(mpt::transcode<std::string>(mpt::common_encoding::utf8, apidev[0]));
+}
+
+
+unsigned int CRtAudioDevice::GetDevice(SoundDevice::Info info)
+{
+ std::vector<mpt::ustring> apidev = mpt::split(info.internalID, MPT_USTRING(","));
+ if(apidev.size() != 2)
+ {
+ return 0;
+ }
+ return mpt::ConvertStringTo<unsigned int>(apidev[1]);
+}
+
+
+std::vector<SoundDevice::Info> CRtAudioDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+#if 0
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+#else
+ MPT_UNUSED(logger);
+#endif
+ std::vector<SoundDevice::Info> devices;
+ std::vector<RtAudio::Api> apis;
+ RtAudio::getCompiledApi(apis);
+ for(const auto &api : apis)
+ {
+ if(api == RtAudio::RTAUDIO_DUMMY)
+ {
+ continue;
+ }
+ try
+ {
+ RtAudio rtaudio(api);
+ for(unsigned int device = 0; device < rtaudio.getDeviceCount(); ++device)
+ {
+ RtAudio::DeviceInfo rtinfo;
+ try
+ {
+ rtinfo = rtaudio.getDeviceInfo(device);
+ } catch(const RtAudioError &)
+ {
+ continue;
+ }
+ if(!rtinfo.probed)
+ {
+ continue;
+ }
+ SoundDevice::Info info = SoundDevice::Info();
+ info.type = MPT_USTRING("RtAudio") + MPT_USTRING("-") + mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiName(rtaudio.getCurrentApi()));
+ std::vector<mpt::ustring> apidev;
+ apidev.push_back(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiName(rtaudio.getCurrentApi())));
+ apidev.push_back(mpt::format<mpt::ustring>::val(device));
+ info.internalID = mpt::join(apidev, MPT_USTRING(","));
+ info.name = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, rtinfo.name);
+ info.apiName = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiDisplayName(rtaudio.getCurrentApi()));
+ info.extraData[MPT_USTRING("RtAudio-ApiDisplayName")] = mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, RtAudioShim::getApiDisplayName(rtaudio.getCurrentApi()));
+ info.apiPath.push_back(MPT_USTRING("RtAudio"));
+ info.useNameAsIdentifier = true;
+ // clang-format off
+ switch(rtaudio.getCurrentApi())
+ {
+ case RtAudio::LINUX_ALSA:
+ info.apiName = MPT_USTRING("ALSA");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Hardware : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::LINUX_PULSE:
+ info.apiName = MPT_USTRING("PulseAudio");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::LINUX_OSS:
+ info.apiName = MPT_USTRING("OSS");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Api::Native : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Emulated : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ sysInfo.SystemClass == mpt::osinfo::osclass::BSD ? Info::Mixing::Hardware : sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Mixing::Software : Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::UNIX_JACK:
+ info.apiName = MPT_USTRING("JACK");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Usability::Usable : sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::Experimental,
+ Info::Level::Primary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Linux ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::MACOSX_CORE:
+ info.apiName = MPT_USTRING("CoreAudio");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Usability::Usable : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ Info::Compatible::Yes,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Darwin ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_WASAPI:
+ info.apiName = MPT_USTRING("WASAPI");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ?
+ sysInfo.IsWindowsOriginal() ?
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::Win7) ?
+ Info::Usability::Usable
+ :
+ sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ?
+ Info::Usability::Experimental
+ :
+ Info::Usability::NotAvailable
+ :
+ Info::Usability::Usable
+ :
+ Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Server,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_ASIO:
+ info.apiName = MPT_USTRING("ASIO");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Named : Info::Default::None);
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() ? Info::Usability::Usable : Info::Usability::Experimental : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Hardware,
+ Info::Implementor::External
+ };
+ break;
+ case RtAudio::WINDOWS_DS:
+ info.apiName = MPT_USTRING("DirectSound");
+ info.default_ = (rtinfo.isDefaultOutput ? Info::Default::Managed : Info::Default::None);
+ info.flags = {
+ Info::Usability::Broken, // sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::Windows::Version::Win7) ? Info::Usability:Usable : Info::Usability::Deprecated : Info::Usability::NotAvailable,
+ Info::Level::Secondary,
+ Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::FullDuplex,
+ Info::Mixing::Software,
+ Info::Implementor::External
+ };
+ break;
+ default:
+ // nothing
+ break;
+ }
+ // clang-format on
+
+ devices.push_back(info);
+ }
+ } catch(const RtAudioError &)
+ {
+ // nothing
+ }
+ }
+ return devices;
+}
+
+
+#endif // MPT_WITH_RTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp
new file mode 100644
index 00000000..0a414bee
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceRtAudio.hpp
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceBase.hpp"
+
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#ifdef MPT_WITH_RTAUDIO
+#if MPT_COMPILER_MSVC
+#pragma warning(push)
+#pragma warning(disable : 4244) // conversion from 'int' to 'unsigned char', possible loss of data
+#endif
+#if MPT_COMPILER_GCC
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-copy"
+#endif
+#include <RtAudio.h>
+#if MPT_COMPILER_GCC
+#pragma GCC diagnostic pop
+#endif
+#if MPT_COMPILER_MSVC
+#pragma warning(pop)
+#endif
+#endif // MPT_WITH_RTAUDIO
+
+OPENMPT_NAMESPACE_BEGIN
+
+namespace SoundDevice
+{
+
+
+#ifdef MPT_WITH_RTAUDIO
+
+
+class CRtAudioDevice : public SoundDevice::Base
+{
+
+protected:
+ std::unique_ptr<RtAudio> m_RtAudio;
+
+ RtAudio::StreamParameters m_InputStreamParameters;
+ RtAudio::StreamParameters m_OutputStreamParameters;
+ unsigned int m_FramesPerChunk;
+ RtAudio::StreamOptions m_StreamOptions;
+
+ void *m_CurrentFrameBufferOutput;
+ void *m_CurrentFrameBufferInput;
+ unsigned int m_CurrentFrameBufferCount;
+ double m_CurrentStreamTime;
+
+ std::atomic<uint32> m_StatisticLatencyFrames;
+ std::atomic<uint32> m_StatisticPeriodFrames;
+
+public:
+ CRtAudioDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CRtAudioDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ bool InternalStart();
+ void InternalStop();
+ bool InternalIsOpen() const { return m_RtAudio && m_RtAudio->isStreamOpen(); }
+ bool InternalHasGetStreamPosition() const { return true; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+ SoundDevice::Statistics GetStatistics() const;
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+private:
+ void SendError(const RtAudioError &e);
+
+ void AudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
+
+ static int RtAudioCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData);
+
+ static RtAudio::Api GetApi(SoundDevice::Info info);
+ static unsigned int GetDevice(SoundDevice::Info info);
+
+public:
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+
+#endif // MPT_WITH_RTAUDIO
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp
new file mode 100644
index 00000000..67efc310
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.cpp
@@ -0,0 +1,664 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceUtilities.hpp"
+
+#include "SoundDevice.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/macros.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/out_of_memory/out_of_memory.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <thread>
+#include <utility>
+
+#include <cassert>
+
+#if MPT_OS_WINDOWS
+#if(_WIN32_WINNT >= 0x600)
+#include <avrt.h>
+#endif
+#include <mmsystem.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+#if !MPT_OS_WINDOWS
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#ifdef _POSIX_PRIORITY_SCHEDULING // from unistd.h
+#include <sched.h>
+#endif
+#endif
+
+#if defined(MPT_WITH_DBUS)
+#include <dbus/dbus.h>
+#endif
+#if defined(MPT_WITH_RTKIT)
+#include "rtkit/rtkit.h"
+#endif
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+bool FillWaveFormatExtensible(WAVEFORMATEXTENSIBLE &WaveFormat, const SoundDevice::Settings &m_Settings)
+{
+ WaveFormat = {};
+ if(!m_Settings.sampleFormat.IsValid())
+ {
+ return false;
+ }
+ WaveFormat.Format.wFormatTag = m_Settings.sampleFormat.IsFloat() ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
+ WaveFormat.Format.nChannels = (WORD)m_Settings.Channels;
+ WaveFormat.Format.nSamplesPerSec = m_Settings.Samplerate;
+ WaveFormat.Format.nAvgBytesPerSec = (DWORD)m_Settings.GetBytesPerSecond();
+ WaveFormat.Format.nBlockAlign = (WORD)m_Settings.GetBytesPerFrame();
+ WaveFormat.Format.wBitsPerSample = (WORD)m_Settings.sampleFormat.GetBitsPerSample();
+ WaveFormat.Format.cbSize = 0;
+ if((WaveFormat.Format.wBitsPerSample > 16 && m_Settings.sampleFormat.IsInt()) || (WaveFormat.Format.nChannels > 2))
+ {
+ WaveFormat.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ WaveFormat.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ WaveFormat.Samples.wValidBitsPerSample = WaveFormat.Format.wBitsPerSample;
+ switch(WaveFormat.Format.nChannels)
+ {
+ case 1: WaveFormat.dwChannelMask = SPEAKER_FRONT_CENTER; break;
+ case 2: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; break;
+ case 3: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_CENTER; break;
+ case 4: WaveFormat.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT; break;
+ default:
+ WaveFormat.dwChannelMask = 0;
+ return false;
+ break;
+ }
+ const GUID guid_MEDIASUBTYPE_PCM = {
+ 0x00000001, 0x0000, 0x0010, {0x80, 0x00, 0x0, 0xAA, 0x0, 0x38, 0x9B, 0x71}
+ };
+ const GUID guid_MEDIASUBTYPE_IEEE_FLOAT = {
+ 0x00000003, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}
+ };
+ WaveFormat.SubFormat = m_Settings.sampleFormat.IsFloat() ? guid_MEDIASUBTYPE_IEEE_FLOAT : guid_MEDIASUBTYPE_PCM;
+ }
+ return true;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_WINDOWS
+
+CAudioThread::CAudioThread(CSoundDeviceWithThread &SoundDevice)
+ : m_SoundDevice(SoundDevice)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_MMCSSClass = mpt::transcode<mpt::winstring>(m_SoundDevice.m_AppInfo.BoostedThreadMMCSSClassVista);
+ m_WakeupInterval = 0.0;
+ m_hPlayThread = NULL;
+ m_dwPlayThreadId = 0;
+ m_hAudioWakeUp = NULL;
+ m_hAudioThreadTerminateRequest = NULL;
+ m_hAudioThreadGoneIdle = NULL;
+ m_hHardwareWakeupEvent = INVALID_HANDLE_VALUE;
+ m_AudioThreadActive = 0;
+ m_hAudioWakeUp = CreateEvent(NULL, FALSE, FALSE, NULL);
+ m_hAudioThreadTerminateRequest = CreateEvent(NULL, FALSE, FALSE, NULL);
+ m_hAudioThreadGoneIdle = CreateEvent(NULL, TRUE, FALSE, NULL);
+ m_hPlayThread = CreateThread(NULL, 0, AudioThreadWrapper, (LPVOID)this, 0, &m_dwPlayThreadId);
+}
+
+
+CAudioThread::~CAudioThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hPlayThread != NULL)
+ {
+ SetEvent(m_hAudioThreadTerminateRequest);
+ WaitForSingleObject(m_hPlayThread, INFINITE);
+ m_dwPlayThreadId = 0;
+ m_hPlayThread = NULL;
+ }
+ if(m_hAudioThreadTerminateRequest)
+ {
+ CloseHandle(m_hAudioThreadTerminateRequest);
+ m_hAudioThreadTerminateRequest = 0;
+ }
+ if(m_hAudioThreadGoneIdle != NULL)
+ {
+ CloseHandle(m_hAudioThreadGoneIdle);
+ m_hAudioThreadGoneIdle = 0;
+ }
+ if(m_hAudioWakeUp != NULL)
+ {
+ CloseHandle(m_hAudioWakeUp);
+ m_hAudioWakeUp = NULL;
+ }
+}
+
+
+CPriorityBooster::CPriorityBooster(SoundDevice::SysInfo sysInfo, bool boostPriority, const mpt::winstring &priorityClass, int priority)
+ : m_SysInfo(sysInfo)
+ , m_BoostPriority(boostPriority)
+ , m_Priority(priority)
+ , task_idx(0)
+ , hTask(NULL)
+ , oldPriority(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#ifdef MPT_BUILD_DEBUG
+ m_BoostPriority = false;
+#endif
+ if(m_BoostPriority)
+ {
+#if(_WIN32_WINNT >= 0x600)
+ if(!priorityClass.empty())
+ {
+ hTask = AvSetMmThreadCharacteristics(priorityClass.c_str(), &task_idx);
+ }
+ MPT_UNUSED(priority);
+#else // < Vista
+ oldPriority = GetThreadPriority(GetCurrentThread());
+ SetThreadPriority(GetCurrentThread(), m_Priority);
+ MPT_UNUSED(priorityClass);
+#endif
+ }
+}
+
+
+CPriorityBooster::~CPriorityBooster()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_BoostPriority)
+ {
+#if(_WIN32_WINNT >= 0x600)
+ if(hTask)
+ {
+ AvRevertMmThreadCharacteristics(hTask);
+ }
+ hTask = NULL;
+ task_idx = 0;
+#else // < Vista
+ SetThreadPriority(GetCurrentThread(), oldPriority);
+#endif
+ }
+}
+
+
+class CPeriodicWaker
+{
+private:
+ double sleepSeconds;
+ long sleepMilliseconds;
+ int64 sleep100Nanoseconds;
+
+ bool periodic_nt_timer;
+
+ HANDLE sleepEvent;
+
+public:
+ explicit CPeriodicWaker(double sleepSeconds_)
+ : sleepSeconds(sleepSeconds_)
+ {
+
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ sleepMilliseconds = static_cast<long>(sleepSeconds * 1000.0);
+ sleep100Nanoseconds = static_cast<int64>(sleepSeconds * 10000000.0);
+ if(sleepMilliseconds < 1) sleepMilliseconds = 1;
+ if(sleep100Nanoseconds < 1) sleep100Nanoseconds = 1;
+
+ periodic_nt_timer = (sleep100Nanoseconds >= 10000); // can be represented as a millisecond period, otherwise use non-periodic timers which allow higher precision but might me slower because we have to set them again in each period
+
+ sleepEvent = NULL;
+
+ if(periodic_nt_timer)
+ {
+ sleepEvent = CreateWaitableTimer(NULL, FALSE, NULL);
+ if(!sleepEvent)
+ {
+ mpt::throw_out_of_memory();
+ }
+ LARGE_INTEGER dueTime;
+ dueTime.QuadPart = 0 - sleep100Nanoseconds; // negative time means relative
+ SetWaitableTimer(sleepEvent, &dueTime, sleepMilliseconds, NULL, NULL, FALSE);
+ } else
+ {
+ sleepEvent = CreateWaitableTimer(NULL, TRUE, NULL);
+ if(!sleepEvent)
+ {
+ mpt::throw_out_of_memory();
+ }
+ }
+ }
+
+ CPeriodicWaker(const CPeriodicWaker &) = delete;
+ CPeriodicWaker &operator=(const CPeriodicWaker &) = delete;
+
+ long GetSleepMilliseconds() const
+ {
+ return sleepMilliseconds;
+ }
+
+ HANDLE GetWakeupEvent() const
+ {
+ return sleepEvent;
+ }
+
+ void Retrigger()
+ {
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!periodic_nt_timer)
+ {
+ LARGE_INTEGER dueTime;
+ dueTime.QuadPart = 0 - sleep100Nanoseconds; // negative time means relative
+ SetWaitableTimer(sleepEvent, &dueTime, 0, NULL, NULL, FALSE);
+ }
+ }
+
+ ~CPeriodicWaker()
+ {
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(periodic_nt_timer)
+ {
+ CancelWaitableTimer(sleepEvent);
+ }
+ CloseHandle(sleepEvent);
+ sleepEvent = NULL;
+ }
+};
+
+
+DWORD WINAPI CAudioThread::AudioThreadWrapper(LPVOID user)
+{
+ return ((CAudioThread *)user)->AudioThread();
+}
+DWORD CAudioThread::AudioThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+
+ bool terminate = false;
+ while(!terminate)
+ {
+
+ bool idle = true;
+ while(!terminate && idle)
+ {
+ HANDLE waithandles[2] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp};
+ SetEvent(m_hAudioThreadGoneIdle);
+ switch(WaitForMultipleObjects(2, waithandles, FALSE, INFINITE))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ case WAIT_OBJECT_0 + 1:
+ idle = false;
+ break;
+ }
+ }
+
+ if(!terminate)
+ {
+
+ CPriorityBooster priorityBooster(m_SoundDevice.GetSysInfo(), m_SoundDevice.m_Settings.BoostThreadPriority, m_MMCSSClass, m_SoundDevice.m_AppInfo.BoostedThreadPriorityXP);
+ CPeriodicWaker periodicWaker(m_WakeupInterval);
+
+ m_SoundDevice.StartFromSoundThread();
+
+ while(!terminate && IsActive())
+ {
+
+ m_SoundDevice.FillAudioBufferLocked();
+
+ periodicWaker.Retrigger();
+
+ if(m_hHardwareWakeupEvent != INVALID_HANDLE_VALUE)
+ {
+ HANDLE waithandles[4] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp, m_hHardwareWakeupEvent, periodicWaker.GetWakeupEvent()};
+ switch(WaitForMultipleObjects(4, waithandles, FALSE, periodicWaker.GetSleepMilliseconds()))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ }
+ } else
+ {
+ HANDLE waithandles[3] = {m_hAudioThreadTerminateRequest, m_hAudioWakeUp, periodicWaker.GetWakeupEvent()};
+ switch(WaitForMultipleObjects(3, waithandles, FALSE, periodicWaker.GetSleepMilliseconds()))
+ {
+ case WAIT_OBJECT_0:
+ terminate = true;
+ break;
+ }
+ }
+ }
+
+ m_SoundDevice.StopFromSoundThread();
+ }
+ }
+
+ SetEvent(m_hAudioThreadGoneIdle);
+
+ return 0;
+}
+
+
+void CAudioThread::SetWakeupEvent(HANDLE ev)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_hHardwareWakeupEvent = ev;
+}
+
+
+void CAudioThread::SetWakeupInterval(double seconds)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_WakeupInterval = seconds;
+}
+
+
+bool CAudioThread::IsActive()
+{
+ return InterlockedExchangeAdd(&m_AudioThreadActive, 0) ? true : false;
+}
+
+
+void CAudioThread::Activate()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(InterlockedExchangeAdd(&m_AudioThreadActive, 0))
+ {
+ assert(false);
+ return;
+ }
+ ResetEvent(m_hAudioThreadGoneIdle);
+ InterlockedExchange(&m_AudioThreadActive, 1);
+ SetEvent(m_hAudioWakeUp);
+}
+
+
+void CAudioThread::Deactivate()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!InterlockedExchangeAdd(&m_AudioThreadActive, 0))
+ {
+ assert(false);
+ return;
+ }
+ InterlockedExchange(&m_AudioThreadActive, 0);
+ WaitForSingleObject(m_hAudioThreadGoneIdle, INFINITE);
+}
+
+
+CSoundDeviceWithThread::CSoundDeviceWithThread(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : SoundDevice::Base(logger, info, sysInfo), m_AudioThread(*this)
+{
+ return;
+}
+
+
+CSoundDeviceWithThread::~CSoundDeviceWithThread()
+{
+ return;
+}
+
+
+void CSoundDeviceWithThread::FillAudioBufferLocked()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ CallbackFillAudioBufferLocked();
+}
+
+
+void CSoundDeviceWithThread::SetWakeupEvent(HANDLE ev)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.SetWakeupEvent(ev);
+}
+
+
+void CSoundDeviceWithThread::SetWakeupInterval(double seconds)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.SetWakeupInterval(seconds);
+}
+
+
+bool CSoundDeviceWithThread::InternalStart()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.Activate();
+ return true;
+}
+
+
+void CSoundDeviceWithThread::InternalStop()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_AudioThread.Deactivate();
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+class ThreadPriorityGuardImpl
+{
+
+private:
+ ILogger &m_Logger;
+ bool active;
+ bool successfull;
+ bool realtime;
+ int niceness;
+ int rt_priority;
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ DBusConnection *bus;
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+
+private:
+ ILogger &GetLogger() const
+ {
+ return m_Logger;
+ }
+
+public:
+ ThreadPriorityGuardImpl(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority)
+ : m_Logger(logger)
+ , active(active)
+ , successfull(false)
+ , realtime(realtime)
+ , niceness(niceness)
+ , rt_priority(rt_priority)
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ , bus(NULL)
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ {
+ if(active)
+ {
+ if(realtime)
+ {
+#ifdef _POSIX_PRIORITY_SCHEDULING
+ sched_param p = sched_param{};
+ p.sched_priority = rt_priority;
+#if MPT_OS_LINUX
+ if(sched_setscheduler(0, SCHED_RR | SCHED_RESET_ON_FORK, &p) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#endif
+ }
+#else
+ if(sched_setscheduler(0, SCHED_RR, &p) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("sched_setscheduler: {}")(errno));
+#endif
+ }
+#endif
+#endif
+ } else
+ {
+ if(setpriority(PRIO_PROCESS, 0, niceness) == 0)
+ {
+ successfull = true;
+ } else
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ MPT_LOG(GetLogger(), LogNotification, "sounddev", MPT_UFORMAT_MESSAGE("setpriority: {}")(errno));
+#else
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("setpriority: {}")(errno));
+#endif
+ }
+ }
+ if(!successfull)
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ DBusError error;
+ dbus_error_init(&error);
+ bus = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
+ if(!bus)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("DBus: dbus_bus_get: {}")(mpt::transcode<mpt::ustring>(mpt::common_encoding::utf8, error.message)));
+ }
+ dbus_error_free(&error);
+ if(bus)
+ {
+ if(realtime)
+ {
+ int e = rtkit_make_realtime(bus, 0, rt_priority);
+ if(e != 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("RtKit: rtkit_make_realtime: {}")(e));
+ } else
+ {
+ successfull = true;
+ }
+ } else
+ {
+ int e = rtkit_make_high_priority(bus, 0, niceness);
+ if(e != 0)
+ {
+ MPT_LOG(GetLogger(), LogError, "sounddev", MPT_UFORMAT_MESSAGE("RtKit: rtkit_make_high_priority: {}")(e));
+ } else
+ {
+ successfull = true;
+ }
+ }
+ }
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ }
+ }
+ }
+
+ ~ThreadPriorityGuardImpl()
+ {
+ if(active)
+ {
+#if defined(MPT_WITH_DBUS) && defined(MPT_WITH_RTKIT)
+ if(bus)
+ {
+ // TODO: Do we want to reset priorities here?
+ dbus_connection_unref(bus);
+ bus = NULL;
+ }
+#endif // MPT_WITH_DBUS && MPT_WITH_RTKIT
+ }
+ }
+};
+
+
+ThreadPriorityGuard::ThreadPriorityGuard(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority)
+ : impl(std::make_unique<ThreadPriorityGuardImpl>(logger, active, realtime, niceness, rt_priority))
+{
+ return;
+}
+
+
+ThreadPriorityGuard::~ThreadPriorityGuard()
+{
+ return;
+}
+
+
+ThreadBase::ThreadBase(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : Base(logger, info, sysInfo)
+ , m_ThreadStopRequest(false)
+{
+ return;
+}
+
+bool ThreadBase::InternalStart()
+{
+ m_ThreadStopRequest.store(false);
+ m_Thread = std::move(std::thread(&ThreadProcStatic, this));
+ m_ThreadStarted.wait();
+ m_ThreadStarted.post();
+ return true;
+}
+
+void ThreadBase::ThreadProcStatic(ThreadBase *this_)
+{
+ this_->ThreadProc();
+}
+
+void ThreadBase::ThreadProc()
+{
+ ThreadPriorityGuard priorityGuard(GetLogger(), m_Settings.BoostThreadPriority, m_AppInfo.BoostedThreadRealtimePosix, m_AppInfo.BoostedThreadNicenessPosix, m_AppInfo.BoostedThreadRealtimePosix);
+ m_ThreadStarted.post();
+ InternalStartFromSoundThread();
+ while(!m_ThreadStopRequest.load())
+ {
+ CallbackFillAudioBufferLocked();
+ InternalWaitFromSoundThread();
+ }
+ InternalStopFromSoundThread();
+}
+
+void ThreadBase::InternalStop()
+{
+ m_ThreadStopRequest.store(true);
+ m_Thread.join();
+ m_Thread = std::move(std::thread());
+ m_ThreadStopRequest.store(false);
+}
+
+ThreadBase::~ThreadBase()
+{
+ return;
+}
+
+
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp
new file mode 100644
index 00000000..68234874
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceUtilities.hpp
@@ -0,0 +1,219 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceBase.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+// we use c++11 in native support library
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <thread>
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+#if MPT_OS_WINDOWS
+#include <mmreg.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+bool FillWaveFormatExtensible(WAVEFORMATEXTENSIBLE &WaveFormat, const SoundDevice::Settings &m_Settings);
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_WINDOWS
+
+
+class CSoundDeviceWithThread;
+
+
+class CPriorityBooster
+{
+private:
+ SoundDevice::SysInfo m_SysInfo;
+ bool m_BoostPriority;
+ int m_Priority;
+ DWORD task_idx;
+ HANDLE hTask;
+ int oldPriority;
+
+public:
+ CPriorityBooster(SoundDevice::SysInfo sysInfo, bool boostPriority, const mpt::winstring &priorityClass, int priority);
+ ~CPriorityBooster();
+};
+
+
+class CAudioThread
+{
+ friend class CPeriodicWaker;
+
+private:
+ CSoundDeviceWithThread &m_SoundDevice;
+ mpt::winstring m_MMCSSClass;
+ double m_WakeupInterval;
+ HANDLE m_hAudioWakeUp;
+ HANDLE m_hPlayThread;
+ HANDLE m_hAudioThreadTerminateRequest;
+ HANDLE m_hAudioThreadGoneIdle;
+ HANDLE m_hHardwareWakeupEvent;
+ DWORD m_dwPlayThreadId;
+ LONG m_AudioThreadActive;
+ static DWORD WINAPI AudioThreadWrapper(LPVOID user);
+ DWORD AudioThread();
+ bool IsActive();
+
+public:
+ CAudioThread(CSoundDeviceWithThread &SoundDevice);
+ CAudioThread(const CAudioThread &) = delete;
+ CAudioThread &operator=(const CAudioThread &) = delete;
+ ~CAudioThread();
+ void Activate();
+ void Deactivate();
+ void SetWakeupEvent(HANDLE ev);
+ void SetWakeupInterval(double seconds);
+};
+
+
+class CSoundDeviceWithThread
+ : public SoundDevice::Base
+{
+ friend class CAudioThread;
+
+protected:
+ CAudioThread m_AudioThread;
+
+private:
+ void FillAudioBufferLocked();
+
+protected:
+ void SetWakeupEvent(HANDLE ev);
+ void SetWakeupInterval(double seconds);
+
+public:
+ CSoundDeviceWithThread(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ virtual ~CSoundDeviceWithThread();
+ bool InternalStart();
+ void InternalStop();
+ virtual void StartFromSoundThread() = 0;
+ virtual void StopFromSoundThread() = 0;
+};
+
+
+#endif // MPT_OS_WINDOWS
+
+
+#if MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+class semaphore
+{
+private:
+ unsigned int count;
+ unsigned int waiters_count;
+ std::mutex mutex;
+ std::condition_variable count_nonzero;
+
+public:
+ semaphore(unsigned int initial_count = 0)
+ : count(initial_count)
+ , waiters_count(0)
+ {
+ return;
+ }
+ ~semaphore()
+ {
+ return;
+ }
+ void wait()
+ {
+ std::unique_lock<std::mutex> l(mutex);
+ waiters_count++;
+ while(count == 0)
+ {
+ count_nonzero.wait(l);
+ }
+ waiters_count--;
+ count--;
+ }
+ void post()
+ {
+ std::unique_lock<std::mutex> l(mutex);
+ if(waiters_count > 0)
+ {
+ count_nonzero.notify_one();
+ }
+ count++;
+ }
+ void lock()
+ {
+ wait();
+ }
+ void unlock()
+ {
+ post();
+ }
+};
+
+
+class ThreadPriorityGuardImpl;
+
+class ThreadPriorityGuard
+{
+private:
+ std::unique_ptr<ThreadPriorityGuardImpl> impl;
+
+public:
+ ThreadPriorityGuard(ILogger &logger, bool active, bool realtime, int niceness, int rt_priority);
+ ~ThreadPriorityGuard();
+};
+
+
+class ThreadBase
+ : public SoundDevice::Base
+{
+private:
+ semaphore m_ThreadStarted;
+ std::atomic<bool> m_ThreadStopRequest;
+ std::thread m_Thread;
+
+private:
+ static void ThreadProcStatic(ThreadBase *this_);
+ void ThreadProc();
+
+public:
+ ThreadBase(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ virtual ~ThreadBase();
+ bool InternalStart();
+ void InternalStop();
+ virtual void InternalStartFromSoundThread() = 0;
+ virtual void InternalWaitFromSoundThread() = 0;
+ virtual void InternalStopFromSoundThread() = 0;
+};
+
+
+#endif // MPT_OS_LINUX || MPT_OS_MACOSX_OR_IOS || MPT_OS_FREEBSD
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp
new file mode 100644
index 00000000..6665a4bb
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.cpp
@@ -0,0 +1,707 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDeviceWaveout.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "mpt/base/numeric.hpp"
+#include "mpt/base/saturate_round.hpp"
+#include "mpt/format/message_macros.hpp"
+#include "mpt/format/simple.hpp"
+#include "mpt/parse/parse.hpp"
+#include "mpt/string/buffer.hpp"
+#include "mpt/string/types.hpp"
+#include "mpt/string_transcode/transcode.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+#include "openmpt/soundbase/SampleFormat.hpp"
+
+#include <algorithm>
+#include <array>
+#include <set>
+#include <vector>
+
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+
+
+static constexpr std::size_t WAVEOUT_MINBUFFERS = 3;
+static constexpr std::size_t WAVEOUT_MAXBUFFERS = 4096;
+static constexpr std::size_t WAVEOUT_MINBUFFERFRAMECOUNT = 8;
+static constexpr std::size_t WAVEOUT_MAXBUFFERSIZE = 16384; // fits in int16
+
+
+static inline LONG *interlocked_access(DWORD *p)
+{
+ static_assert(sizeof(LONG) == sizeof(DWORD));
+ return reinterpret_cast<LONG *>(p);
+}
+
+
+CWaveDevice::CWaveDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo)
+ : CSoundDeviceWithThread(logger, info, sysInfo)
+ , m_DriverBugs(0)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ m_ThreadWakeupEvent = NULL;
+ m_Failed = false;
+ m_hWaveOut = NULL;
+ m_nWaveBufferSize = 0;
+ m_JustStarted = false;
+ m_nPreparedHeaders = 0;
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ m_nBuffersPending = 0;
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+}
+
+
+CWaveDevice::~CWaveDevice()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ Close();
+}
+
+
+int CWaveDevice::GetDeviceIndex() const
+{
+ return mpt::ConvertStringTo<int>(GetDeviceInternalID());
+}
+
+
+SoundDevice::Caps CWaveDevice::InternalGetDeviceCaps()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Caps caps;
+ caps.Available = true;
+ caps.CanUpdateInterval = true;
+ caps.CanSampleFormat = true;
+ caps.CanExclusiveMode = (GetDeviceIndex() > 0); // no direct mode for WAVE_MAPPER, makes no sense there
+ caps.CanBoostThreadPriority = true;
+ caps.CanKeepDeviceRunning = false;
+ caps.CanUseHardwareTiming = false;
+ caps.CanChannelMapping = false;
+ caps.CanInput = false;
+ caps.HasNamedInputSources = false;
+ caps.CanDriverPanel = false;
+ caps.HasInternalDither = false;
+ caps.ExclusiveModeDescription = MPT_USTRING("Use direct mode");
+ if(GetSysInfo().IsWine)
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ } else if(GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Float32;
+ } else
+ {
+ caps.DefaultSettings.sampleFormat = SampleFormat::Int16;
+ }
+ return caps;
+}
+
+
+SoundDevice::DynamicCaps CWaveDevice::GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::DynamicCaps caps;
+ if(GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista))
+ { // emulated on WASAPI
+ caps.supportedSampleFormats = {SampleFormat::Float32};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32};
+ } else
+ { // native WDM/VDX, or Wine
+ caps.supportedSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ caps.supportedExclusiveModeSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ }
+ if(GetDeviceIndex() > 0)
+ { // direct mode
+ if((GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista)) || !GetSysInfo().IsOriginal())
+ { // emulated on WASAPI, or Wine
+ WAVEOUTCAPS woc = {};
+ caps.supportedExclusiveModeSampleFormats.clear();
+ if(waveOutGetDevCaps(GetDeviceIndex() - 1, &woc, sizeof(woc)) == MMSYSERR_NOERROR)
+ {
+ if(woc.dwFormats & (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(96000);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(48000);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(44100);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(22050);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16))
+ {
+ caps.supportedExclusiveSampleRates.push_back(11025);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_48M08 | WAVE_FORMAT_96M08 | WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_48S08 | WAVE_FORMAT_96S08))
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Unsigned8);
+ }
+ if(woc.dwFormats & (WAVE_FORMAT_1M16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_4M16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96S16))
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(SampleFormat::Int16);
+ }
+ }
+ } else
+ { // native WDM/VDX
+ caps.supportedExclusiveSampleRates.clear();
+ caps.supportedExclusiveModeSampleFormats.clear();
+ std::set<uint32> supportedSampleRates;
+ std::set<SampleFormat> supportedSampleFormats;
+ std::array<SampleFormat, 5> baseSampleFormats = {SampleFormat::Float32, SampleFormat::Int32, SampleFormat::Int24, SampleFormat::Int16, SampleFormat::Unsigned8};
+ for(const uint32 sampleRate : baseSampleRates)
+ {
+ for(const SampleFormat sampleFormat : baseSampleFormats)
+ {
+ WAVEFORMATEXTENSIBLE wfex = {};
+ Settings settings;
+ settings.Samplerate = sampleRate;
+ settings.Channels = 2;
+ settings.sampleFormat = sampleFormat;
+ if(FillWaveFormatExtensible(wfex, settings))
+ {
+ if(waveOutOpen(NULL, GetDeviceIndex() - 1, &wfex.Format, NULL, NULL, CALLBACK_NULL | WAVE_FORMAT_DIRECT | WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR)
+ {
+ supportedSampleRates.insert(sampleRate);
+ supportedSampleFormats.insert(sampleFormat);
+ }
+ }
+ }
+ }
+ for(const uint32 sampleRate : baseSampleRates)
+ {
+ if(supportedSampleRates.count(sampleRate) > 0)
+ {
+ caps.supportedExclusiveSampleRates.push_back(sampleRate);
+ }
+ }
+ for(const SampleFormat sampleFormat : baseSampleFormats)
+ {
+ if(supportedSampleFormats.count(sampleFormat) > 0)
+ {
+ caps.supportedExclusiveModeSampleFormats.push_back(sampleFormat);
+ }
+ }
+ }
+ }
+ return caps;
+}
+
+
+bool CWaveDevice::InternalOpen()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_Settings.InputChannels > 0)
+ {
+ return false;
+ }
+ WAVEFORMATEXTENSIBLE wfext;
+ if(!FillWaveFormatExtensible(wfext, m_Settings))
+ {
+ return false;
+ }
+ WAVEFORMATEX *pwfx = &wfext.Format;
+ UINT nWaveDev = GetDeviceIndex();
+ nWaveDev = (nWaveDev > 0) ? nWaveDev - 1 : WAVE_MAPPER;
+ m_ThreadWakeupEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if(m_ThreadWakeupEvent == INVALID_HANDLE_VALUE)
+ {
+ InternalClose();
+ return false;
+ }
+ m_Failed = false;
+ m_DriverBugs = 0;
+ m_hWaveOut = NULL;
+ if(waveOutOpen(&m_hWaveOut, nWaveDev, pwfx, (DWORD_PTR)WaveOutCallBack, (DWORD_PTR)this, CALLBACK_FUNCTION | (m_Settings.ExclusiveMode ? WAVE_FORMAT_DIRECT : 0)) != MMSYSERR_NOERROR)
+ {
+ InternalClose();
+ return false;
+ }
+ if(waveOutPause(m_hWaveOut) != MMSYSERR_NOERROR)
+ {
+ InternalClose();
+ return false;
+ }
+ m_nWaveBufferSize = mpt::saturate_round<int32>(m_Settings.UpdateInterval * pwfx->nAvgBytesPerSec);
+ m_nWaveBufferSize = mpt::align_up<uint32>(m_nWaveBufferSize, pwfx->nBlockAlign);
+ m_nWaveBufferSize = std::clamp(m_nWaveBufferSize, static_cast<uint32>(WAVEOUT_MINBUFFERFRAMECOUNT * pwfx->nBlockAlign), static_cast<uint32>(mpt::align_down<uint32>(WAVEOUT_MAXBUFFERSIZE, pwfx->nBlockAlign)));
+ std::size_t numBuffers = mpt::saturate_round<int32>(m_Settings.Latency * pwfx->nAvgBytesPerSec / m_nWaveBufferSize);
+ numBuffers = std::clamp(numBuffers, WAVEOUT_MINBUFFERS, WAVEOUT_MAXBUFFERS);
+ m_nPreparedHeaders = 0;
+ m_WaveBuffers.resize(numBuffers);
+ m_WaveBuffersData.resize(numBuffers);
+ for(std::size_t buf = 0; buf < numBuffers; ++buf)
+ {
+ m_WaveBuffers[buf] = {};
+ m_WaveBuffersData[buf].resize(m_nWaveBufferSize);
+ m_WaveBuffers[buf].dwFlags = 0;
+ m_WaveBuffers[buf].lpData = &m_WaveBuffersData[buf][0];
+ m_WaveBuffers[buf].dwBufferLength = m_nWaveBufferSize;
+ if(waveOutPrepareHeader(m_hWaveOut, &m_WaveBuffers[buf], sizeof(WAVEHDR)) != MMSYSERR_NOERROR)
+ {
+ break;
+ }
+ m_WaveBuffers[buf].dwFlags |= WHDR_DONE;
+ m_nPreparedHeaders++;
+ }
+ if(!m_nPreparedHeaders)
+ {
+ InternalClose();
+ return false;
+ }
+ if(m_Settings.sampleFormat == SampleFormat::Int8)
+ {
+ m_Settings.sampleFormat = SampleFormat::Unsigned8;
+ }
+ m_nBuffersPending = 0;
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ SetWakeupEvent(m_ThreadWakeupEvent);
+ SetWakeupInterval(m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond());
+ m_Flags.WantsClippedOutput = (GetSysInfo().IsOriginal() && GetSysInfo().WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista));
+ return true;
+}
+
+
+bool CWaveDevice::InternalClose()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ waveOutReset(m_hWaveOut);
+ m_JustStarted = false;
+ InterlockedExchange(&m_nBuffersPending, 0);
+ m_nWriteBuffer = 0;
+ m_nDoneBuffer = 0;
+ while(m_nPreparedHeaders > 0)
+ {
+ m_nPreparedHeaders--;
+ waveOutUnprepareHeader(m_hWaveOut, &m_WaveBuffers[m_nPreparedHeaders], sizeof(WAVEHDR));
+ }
+ waveOutClose(m_hWaveOut);
+ m_hWaveOut = NULL;
+ }
+#ifdef MPT_BUILD_DEBUG
+ if(m_DriverBugs.load())
+ {
+ SendDeviceMessage(LogError, MPT_USTRING("Errors were detected while playing sound:\n") + GetStatistics().text);
+ }
+#endif
+ m_DriverBugs = 0;
+ m_Failed = false;
+ if(m_ThreadWakeupEvent)
+ {
+ CloseHandle(m_ThreadWakeupEvent);
+ m_ThreadWakeupEvent = NULL;
+ }
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ return true;
+}
+
+
+void CWaveDevice::StartFromSoundThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ m_JustStarted = true;
+ // Actual starting is done in InternalFillAudioBuffer to avoid crackling with tiny buffers.
+ }
+}
+
+
+void CWaveDevice::StopFromSoundThread()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(m_hWaveOut)
+ {
+ CheckResult(waveOutPause(m_hWaveOut));
+ m_JustStarted = false;
+ {
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ m_PositionLast = {};
+ m_PositionWrappedCount = 0;
+ }
+ }
+}
+
+
+bool CWaveDevice::CheckResult(MMRESULT result)
+{
+ if(result == MMSYSERR_NOERROR)
+ {
+ return true;
+ }
+ if(!m_Failed)
+ { // only show the first error
+ m_Failed = true;
+ TCHAR errortext[MAXERRORLENGTH + 1] = {};
+ waveOutGetErrorText(result, errortext, MAXERRORLENGTH);
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("WaveOut error: 0x{}: {}")(mpt::format<mpt::ustring>::hex0<8>(result), mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(errortext)))));
+ }
+ RequestClose();
+ return false;
+}
+
+
+bool CWaveDevice::CheckResult(MMRESULT result, DWORD param)
+{
+ if(result == MMSYSERR_NOERROR)
+ {
+ return true;
+ }
+ if(!m_Failed)
+ { // only show the first error
+ m_Failed = true;
+ TCHAR errortext[MAXERRORLENGTH + 1] = {};
+ waveOutGetErrorText(result, errortext, MAXERRORLENGTH);
+ SendDeviceMessage(LogError, MPT_UFORMAT_MESSAGE("WaveOut error: 0x{} (param 0x{}): {}")(mpt::format<mpt::ustring>::hex0<8>(result), mpt::format<mpt::ustring>::hex0<8>(param), mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(errortext)))));
+ }
+ RequestClose();
+ return false;
+}
+
+
+void CWaveDevice::InternalFillAudioBuffer()
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if(!m_hWaveOut)
+ {
+ return;
+ }
+
+ const std::size_t bytesPerFrame = m_Settings.GetBytesPerFrame();
+
+ ULONG oldBuffersPending = InterlockedExchangeAdd(&m_nBuffersPending, 0); // read
+ ULONG nLatency = oldBuffersPending * m_nWaveBufferSize;
+
+ ULONG nBytesWritten = 0;
+ while((oldBuffersPending < m_nPreparedHeaders) && !m_Failed)
+ {
+#if(_WIN32_WINNT >= 0x0600)
+ DWORD oldFlags = InterlockedOr(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), 0);
+#else
+ DWORD oldFlags = _InterlockedOr(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), 0);
+#endif
+ uint32 driverBugs = 0;
+ if(oldFlags & WHDR_INQUEUE)
+ {
+ driverBugs |= DriverBugBufferFillAndHeaderInQueue;
+ }
+ if(!(oldFlags & WHDR_DONE))
+ {
+ driverBugs |= DriverBugBufferFillAndHeaderNotDone;
+ }
+ driverBugs |= m_DriverBugs.fetch_or(driverBugs);
+ if(oldFlags & WHDR_INQUEUE)
+ {
+ if(driverBugs & DriverBugDoneNotificationOutOfOrder)
+ {
+ // Some drivers/setups can return WaveHeader notifications out of
+ // order. WaveHeaders which have not yet been notified to be ready stay
+ // in the INQUEUE and !DONE state internally and cannot be reused yet
+ // even though they causally should be able to. waveOutWrite fails for
+ // them.
+ // In this case we skip filling the buffers until we actually see the
+ // next expected buffer to be ready for refilling.
+ // This problem has been spotted on Wine 1.7.46 (non-official packages)
+ // running on Debian 8 Jessie 32bit. It may also be related to WaveOut
+ // playback being too fast and crackling which had benn reported on
+ // Wine 1.6 + WinePulse on UbuntuStudio 12.04 32bit (this has not been
+ // verified yet because the problem is not always reproducable on the
+ // system in question).
+ return;
+ }
+ }
+ nLatency += m_nWaveBufferSize;
+ CallbackLockedAudioReadPrepare(m_nWaveBufferSize / bytesPerFrame, nLatency / bytesPerFrame);
+ CallbackLockedAudioProcessVoid(m_WaveBuffers[m_nWriteBuffer].lpData, nullptr, m_nWaveBufferSize / bytesPerFrame);
+ nBytesWritten += m_nWaveBufferSize;
+#if(_WIN32_WINNT >= 0x0600)
+ InterlockedAnd(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), ~static_cast<DWORD>(WHDR_INQUEUE | WHDR_DONE));
+#else
+ _InterlockedAnd(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwFlags), ~static_cast<DWORD>(WHDR_INQUEUE | WHDR_DONE));
+#endif
+ InterlockedExchange(interlocked_access(&m_WaveBuffers[m_nWriteBuffer].dwBufferLength), m_nWaveBufferSize);
+ InterlockedIncrement(&m_nBuffersPending);
+ oldBuffersPending++; // increment separately to avoid looping without leaving at all when rendering takes more than 100% CPU
+ CheckResult(waveOutWrite(m_hWaveOut, &m_WaveBuffers[m_nWriteBuffer], sizeof(WAVEHDR)), oldFlags);
+ m_nWriteBuffer++;
+ m_nWriteBuffer %= m_nPreparedHeaders;
+ CallbackLockedAudioProcessDone();
+ }
+
+ if(m_JustStarted && !m_Failed)
+ {
+ // Fill the buffers completely before starting the stream.
+ // This avoids buffer underruns which result in audible crackling with small buffers.
+ m_JustStarted = false;
+ CheckResult(waveOutRestart(m_hWaveOut));
+ }
+}
+
+
+int64 CWaveDevice::InternalGetStreamPositionFrames() const
+{
+ // Apparently, at least with Windows XP, TIME_SAMPLES wraps aroud at 0x7FFFFFF (see
+ // http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.mmedia/2005-02/0070.html
+ // ).
+ // We may also, additionally, default to TIME_BYTES which would wraparound the earliest.
+ // We could thereby try to avoid any potential wraparound inside the driver on older
+ // Windows versions, which would be, once converted into other units, really
+ // difficult to detect or handle.
+ static constexpr UINT timeType = TIME_SAMPLES; // should work for sane systems
+ //static constexpr std::size_t valid_bits = 32; // should work for sane systems
+ //static constexpr UINT timeType = TIME_BYTES; // safest
+ static constexpr std::size_t valid_bits = 27; // safe for WinXP TIME_SAMPLES
+ static constexpr uint32 valid_mask = static_cast<uint32>((uint64(1) << valid_bits) - 1u);
+ static constexpr uint32 valid_watermark = static_cast<uint32>(uint64(1) << (valid_bits - 1u)); // half the valid range in order to be able to catch backwards fluctuations
+
+ MMTIME mmtime = {};
+ mmtime.wType = timeType;
+ if(waveOutGetPosition(m_hWaveOut, &mmtime, sizeof(mmtime)) != MMSYSERR_NOERROR)
+ {
+ return 0;
+ }
+ if(mmtime.wType != TIME_MS && mmtime.wType != TIME_BYTES && mmtime.wType != TIME_SAMPLES)
+ { // unsupported time format
+ return 0;
+ }
+ int64 offset = 0;
+ {
+ // handle wraparound
+ mpt::lock_guard<mpt::mutex> guard(m_PositionWraparoundMutex);
+ if(!m_PositionLast.wType)
+ {
+ // first call
+ m_PositionWrappedCount = 0;
+ } else if(mmtime.wType != m_PositionLast.wType)
+ {
+ // what? value type changed, do not try handling that for now.
+ m_PositionWrappedCount = 0;
+ } else
+ {
+ DWORD oldval = 0;
+ DWORD curval = 0;
+ switch(mmtime.wType)
+ {
+ case TIME_MS:
+ oldval = m_PositionLast.u.ms;
+ curval = mmtime.u.ms;
+ break;
+ case TIME_BYTES:
+ oldval = m_PositionLast.u.cb;
+ curval = mmtime.u.cb;
+ break;
+ case TIME_SAMPLES:
+ oldval = m_PositionLast.u.sample;
+ curval = mmtime.u.sample;
+ break;
+ }
+ oldval &= valid_mask;
+ curval &= valid_mask;
+ if(((curval - oldval) & valid_mask) >= valid_watermark) // guard against driver problems resulting in time jumping backwards for short periods of time. BEWARE of integer wraparound when refactoring
+ {
+ curval = oldval;
+ }
+ switch(mmtime.wType)
+ {
+ case TIME_MS: mmtime.u.ms = curval; break;
+ case TIME_BYTES: mmtime.u.cb = curval; break;
+ case TIME_SAMPLES: mmtime.u.sample = curval; break;
+ }
+ if((curval ^ oldval) & valid_watermark) // MSB flipped
+ {
+ if(!(curval & valid_watermark)) // actually wrapped
+ {
+ m_PositionWrappedCount += 1;
+ }
+ }
+ }
+ m_PositionLast = mmtime;
+ offset = (static_cast<uint64>(m_PositionWrappedCount) << valid_bits);
+ }
+ int64 result = 0;
+ switch(mmtime.wType)
+ {
+ case TIME_MS: result += (static_cast<int64>(mmtime.u.ms & valid_mask) + offset) * m_Settings.GetBytesPerSecond() / (1000 * m_Settings.GetBytesPerFrame()); break;
+ case TIME_BYTES: result += (static_cast<int64>(mmtime.u.cb & valid_mask) + offset) / m_Settings.GetBytesPerFrame(); break;
+ case TIME_SAMPLES: result += (static_cast<int64>(mmtime.u.sample & valid_mask) + offset); break;
+ }
+ return result;
+}
+
+
+void CWaveDevice::HandleWaveoutDone(WAVEHDR *hdr)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+#if(_WIN32_WINNT >= 0x0600)
+ DWORD flags = InterlockedOr(interlocked_access(&hdr->dwFlags), 0);
+#else
+ DWORD flags = _InterlockedOr(interlocked_access(&hdr->dwFlags), 0);
+#endif
+ std::size_t hdrIndex = hdr - &(m_WaveBuffers[0]);
+ uint32 driverBugs = 0;
+ if(hdrIndex != m_nDoneBuffer)
+ {
+ driverBugs |= DriverBugDoneNotificationOutOfOrder;
+ }
+ if(!(flags & WHDR_DONE))
+ {
+ driverBugs |= DriverBugDoneNotificationAndHeaderNotDone;
+ }
+ if(flags & WHDR_INQUEUE)
+ {
+ driverBugs |= DriverBugDoneNotificationAndHeaderInQueue;
+ }
+ if(driverBugs)
+ {
+ m_DriverBugs.fetch_or(driverBugs);
+ }
+ m_nDoneBuffer += 1;
+ m_nDoneBuffer %= m_nPreparedHeaders;
+ InterlockedDecrement(&m_nBuffersPending);
+ SetEvent(m_ThreadWakeupEvent);
+}
+
+
+void CWaveDevice::WaveOutCallBack(HWAVEOUT, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR param1, DWORD_PTR /* param2 */)
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ if((uMsg == WOM_DONE) && (dwUser))
+ {
+ CWaveDevice *that = (CWaveDevice *)dwUser;
+ that->HandleWaveoutDone((WAVEHDR *)param1);
+ }
+}
+
+
+SoundDevice::BufferAttributes CWaveDevice::InternalGetEffectiveBufferAttributes() const
+{
+ SoundDevice::BufferAttributes bufferAttributes;
+ bufferAttributes.Latency = m_nWaveBufferSize * m_nPreparedHeaders * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.UpdateInterval = m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ bufferAttributes.NumBuffers = m_nPreparedHeaders;
+ return bufferAttributes;
+}
+
+
+SoundDevice::Statistics CWaveDevice::GetStatistics() const
+{
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ SoundDevice::Statistics result;
+ result.InstantaneousLatency = InterlockedExchangeAdd(&m_nBuffersPending, 0) * m_nWaveBufferSize * 1.0 / m_Settings.GetBytesPerSecond();
+ result.LastUpdateInterval = 1.0 * m_nWaveBufferSize / m_Settings.GetBytesPerSecond();
+ uint32 bugs = m_DriverBugs.load();
+ if(bugs != 0)
+ {
+ result.text = MPT_UFORMAT_MESSAGE("Problematic driver detected! Error flags: {}")(mpt::format<mpt::ustring>::hex0<8>(bugs));
+ } else
+ {
+ result.text = MPT_UFORMAT_MESSAGE("Driver working as expected.")();
+ }
+ return result;
+}
+
+
+std::vector<SoundDevice::Info> CWaveDevice::EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo)
+{
+ auto GetLogger = [&]() -> ILogger &
+ {
+ return logger;
+ };
+ MPT_SOUNDDEV_TRACE_SCOPE();
+ std::vector<SoundDevice::Info> devices;
+ UINT numDevs = waveOutGetNumDevs();
+ for(UINT index = 0; index <= numDevs; ++index)
+ {
+ SoundDevice::Info info;
+ info.type = TypeWAVEOUT;
+ info.internalID = mpt::format<mpt::ustring>::dec(index);
+ info.apiName = MPT_USTRING("MME");
+ info.useNameAsIdentifier = true;
+ WAVEOUTCAPS woc = {};
+ if(waveOutGetDevCaps((index == 0) ? WAVE_MAPPER : (index - 1), &woc, sizeof(woc)) == MMSYSERR_NOERROR)
+ {
+ info.name = mpt::transcode<mpt::ustring>(static_cast<mpt::winstring>(mpt::ReadWinBuf(woc.szPname)));
+ info.extraData[MPT_USTRING("DriverID")] = MPT_UFORMAT_MESSAGE("{}:{}")(mpt::format<mpt::ustring>::hex0<4>(woc.wMid), mpt::format<mpt::ustring>::hex0<4>(woc.wPid));
+ info.extraData[MPT_USTRING("DriverVersion")] = MPT_UFORMAT_MESSAGE("{}.{}")(mpt::format<mpt::ustring>::dec((static_cast<uint32>(woc.vDriverVersion) >> 24) & 0xff), mpt::format<mpt::ustring>::dec((static_cast<uint32>(woc.vDriverVersion) >> 0) & 0xff));
+ }
+ if(info.name.empty())
+ {
+ if(index == 0)
+ {
+ info.name = MPT_UFORMAT_MESSAGE("Auto (Wave Mapper)")();
+ } else
+ {
+ info.name = MPT_UFORMAT_MESSAGE("Device {}")(index - 1);
+ }
+ }
+ info.default_ = ((index == 0) ? Info::Default::Managed : Info::Default::None);
+ // clang-format off
+ info.flags = {
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsOriginal() && sysInfo.WindowsVersion.IsBefore(mpt::osinfo::windows::Version::Win7) ? Info::Usability::Usable : Info::Usability::Legacy : Info::Usability::NotAvailable,
+ Info::Level::Primary,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows && sysInfo.IsWindowsOriginal() ? Info::Compatible::Yes : Info::Compatible::No,
+ sysInfo.SystemClass == mpt::osinfo::osclass::Windows ? sysInfo.IsWindowsWine() ? Info::Api::Emulated : sysInfo.WindowsVersion.IsAtLeast(mpt::osinfo::windows::Version::WinVista) ? Info::Api::Emulated : Info::Api::Native : Info::Api::Emulated,
+ Info::Io::OutputOnly,
+ Info::Mixing::Software,
+ Info::Implementor::OpenMPT
+ };
+ // clang-format on
+ devices.push_back(info);
+ }
+ return devices;
+}
+
+#endif // MPT_OS_WINDOWS
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END
diff --git a/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp
new file mode 100644
index 00000000..bf65dfde
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/src/openmpt/sounddevice/SoundDeviceWaveout.hpp
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/* SPDX-FileCopyrightText: Olivier Lapicque */
+/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */
+
+
+#pragma once
+
+#include "openmpt/all/BuildSettings.hpp"
+
+#include "SoundDevice.hpp"
+#include "SoundDeviceUtilities.hpp"
+
+#include "mpt/base/detect.hpp"
+#include "openmpt/base/Types.hpp"
+#include "openmpt/logging/Logger.hpp"
+
+#include <atomic>
+#include <memory>
+#include <vector>
+
+#include <cstddef>
+
+#if MPT_OS_WINDOWS
+#include <MMSystem.h>
+#include <windows.h>
+#endif // MPT_OS_WINDOWS
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+namespace SoundDevice
+{
+
+
+#if MPT_OS_WINDOWS
+
+
+class CWaveDevice : public CSoundDeviceWithThread
+{
+protected:
+ HANDLE m_ThreadWakeupEvent;
+ bool m_Failed;
+ HWAVEOUT m_hWaveOut;
+ uint32 m_nWaveBufferSize;
+ bool m_JustStarted;
+ ULONG m_nPreparedHeaders;
+ ULONG m_nWriteBuffer;
+ ULONG m_nDoneBuffer;
+ mutable LONG m_nBuffersPending;
+ std::vector<WAVEHDR> m_WaveBuffers;
+ std::vector<std::vector<char>> m_WaveBuffersData;
+
+ mutable mpt::mutex m_PositionWraparoundMutex;
+ mutable MMTIME m_PositionLast;
+ mutable std::size_t m_PositionWrappedCount;
+
+ static constexpr uint32 DriverBugDoneNotificationAndHeaderInQueue = (1u << 0u); // 1
+ static constexpr uint32 DriverBugDoneNotificationAndHeaderNotDone = (1u << 1u); // 2
+ static constexpr uint32 DriverBugBufferFillAndHeaderInQueue = (1u << 2u); // 4
+ static constexpr uint32 DriverBugBufferFillAndHeaderNotDone = (1u << 3u); // 8
+ static constexpr uint32 DriverBugDoneNotificationOutOfOrder = (1u << 4u); // 10
+ std::atomic<uint32> m_DriverBugs;
+
+public:
+ CWaveDevice(ILogger &logger, SoundDevice::Info info, SoundDevice::SysInfo sysInfo);
+ ~CWaveDevice();
+
+public:
+ bool InternalOpen();
+ bool InternalClose();
+ void InternalFillAudioBuffer();
+ void StartFromSoundThread();
+ void StopFromSoundThread();
+ bool InternalIsOpen() const { return (m_hWaveOut != NULL); }
+ bool InternalHasGetStreamPosition() const { return true; }
+ int64 InternalGetStreamPositionFrames() const;
+ SoundDevice::BufferAttributes InternalGetEffectiveBufferAttributes() const;
+
+ SoundDevice::Statistics GetStatistics() const;
+
+ SoundDevice::Caps InternalGetDeviceCaps();
+ SoundDevice::DynamicCaps GetDeviceDynamicCaps(const std::vector<uint32> &baseSampleRates);
+
+private:
+ bool CheckResult(MMRESULT result);
+ bool CheckResult(MMRESULT result, DWORD param);
+
+ void HandleWaveoutDone(WAVEHDR *hdr);
+
+ int GetDeviceIndex() const;
+
+public:
+ static void CALLBACK WaveOutCallBack(HWAVEOUT, UINT uMsg, DWORD_PTR, DWORD_PTR dw1, DWORD_PTR dw2);
+ static std::unique_ptr<SoundDevice::BackendInitializer> BackendInitializer() { return std::make_unique<SoundDevice::BackendInitializer>(); }
+ static std::vector<SoundDevice::Info> EnumerateDevices(ILogger &logger, SoundDevice::SysInfo sysInfo);
+};
+
+#endif // MPT_OS_WINDOWS
+
+
+} // namespace SoundDevice
+
+
+OPENMPT_NAMESPACE_END