Initial community commit

This commit is contained in:
Jef
2024-09-24 14:54:57 +02:00
parent 537bcbc862
commit 20d28e80a5
16810 changed files with 4640254 additions and 2 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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