diff options
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/src/openmpt')
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 |