Initial community commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user