Update libopenmpt to version 0.5.4

CQTexperiment
Christopher Snowhill 2020-12-04 16:22:42 -08:00
parent b375f06faa
commit 24231ecdbb
29 changed files with 464 additions and 330 deletions

View File

@ -1,4 +1,4 @@
MPT_SVNVERSION=13775
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.3
MPT_SVNDATE=2020-10-25T14:02:16.624929Z
MPT_SVNVERSION=13932
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.4
MPT_SVNDATE=2020-11-29T15:01:39.790705Z

View File

@ -1,10 +1,10 @@
#pragma once
#define OPENMPT_VERSION_SVNVERSION "13775"
#define OPENMPT_VERSION_REVISION 13775
#define OPENMPT_VERSION_SVNVERSION "13932"
#define OPENMPT_VERSION_REVISION 13932
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.3"
#define OPENMPT_VERSION_DATE "2020-10-25T14:02:16.624929Z"
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.5.4"
#define OPENMPT_VERSION_DATE "2020-11-29T15:01:39.790705Z"
#define OPENMPT_VERSION_IS_PACKAGE 1

View File

@ -423,7 +423,7 @@ std::shared_ptr<const type> ReloadComponent()
}
static inline mpt::PathString GetComponentPath()
inline mpt::PathString GetComponentPath()
{
return ComponentManager::Instance()->GetComponentPath();
}
@ -454,7 +454,7 @@ std::shared_ptr<const type> GetComponent()
}
static inline mpt::PathString GetComponentPath()
inline mpt::PathString GetComponentPath()
{
return mpt::PathString();
}

View File

@ -44,22 +44,22 @@ using std::endian;
static_assert(mpt::endian::big != mpt::endian::little, "platform with all scalar types having size 1 is not supported");
static constexpr mpt::endian get_endian() noexcept
constexpr mpt::endian get_endian() noexcept
{
return mpt::endian::native;
}
static constexpr bool endian_is_little() noexcept
constexpr bool endian_is_little() noexcept
{
return get_endian() == mpt::endian::little;
}
static constexpr bool endian_is_big() noexcept
constexpr bool endian_is_big() noexcept
{
return get_endian() == mpt::endian::big;
}
static constexpr bool endian_is_weird() noexcept
constexpr bool endian_is_weird() noexcept
{
return !endian_is_little() && !endian_is_big();
}
@ -128,7 +128,7 @@ static_assert(mpt::endian::big != mpt::endian::little, "platform with all scalar
namespace detail {
static MPT_FORCEINLINE mpt::endian endian_probe() noexcept
MPT_FORCEINLINE mpt::endian endian_probe() noexcept
{
using endian_probe_type = uint32;
static_assert(sizeof(endian_probe_type) == 4);
@ -154,7 +154,7 @@ namespace detail {
} // namespace detail
static MPT_FORCEINLINE mpt::endian get_endian() noexcept
MPT_FORCEINLINE mpt::endian get_endian() noexcept
{
#if MPT_COMPILER_MSVC
#pragma warning(push)
@ -172,17 +172,17 @@ static MPT_FORCEINLINE mpt::endian get_endian() noexcept
#endif // MPT_COMPILER_MSVC
}
static MPT_FORCEINLINE bool endian_is_little() noexcept
MPT_FORCEINLINE bool endian_is_little() noexcept
{
return get_endian() == mpt::endian::little;
}
static MPT_FORCEINLINE bool endian_is_big() noexcept
MPT_FORCEINLINE bool endian_is_big() noexcept
{
return get_endian() == mpt::endian::big;
}
static MPT_FORCEINLINE bool endian_is_weird() noexcept
MPT_FORCEINLINE bool endian_is_weird() noexcept
{
return !endian_is_little() && !endian_is_big();
}
@ -265,19 +265,19 @@ namespace mpt { namespace detail {
// catch system macros
#ifndef MPT_bswap16
#ifdef bswap16
static MPT_FORCEINLINE uint16 mpt_bswap16(uint16 x) { return bswap16(x); }
MPT_FORCEINLINE uint16 mpt_bswap16(uint16 x) { return bswap16(x); }
#define MPT_bswap16 mpt::detail::mpt_bswap16
#endif
#endif
#ifndef MPT_bswap32
#ifdef bswap32
static MPT_FORCEINLINE uint32 mpt_bswap32(uint32 x) { return bswap32(x); }
MPT_FORCEINLINE uint32 mpt_bswap32(uint32 x) { return bswap32(x); }
#define MPT_bswap32 mpt::detail::mpt_bswap32
#endif
#endif
#ifndef MPT_bswap64
#ifdef bswap64
static MPT_FORCEINLINE uint64 mpt_bswap64(uint64 x) { return bswap64(x); }
MPT_FORCEINLINE uint64 mpt_bswap64(uint64 x) { return bswap64(x); }
#define MPT_bswap64 mpt::detail::mpt_bswap64
#endif
#endif
@ -297,7 +297,7 @@ static MPT_FORCEINLINE uint64 mpt_bswap64(uint64 x) { return bswap64(x); }
template <typename T, typename Tendian, std::size_t size>
static MPT_CONSTEXPR17_FUN std::array<std::byte, size> EndianEncode(T val) noexcept
MPT_CONSTEXPR17_FUN std::array<std::byte, size> EndianEncode(T val) noexcept
{
static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big);
static_assert(std::numeric_limits<T>::is_integer);
@ -325,7 +325,7 @@ static MPT_CONSTEXPR17_FUN std::array<std::byte, size> EndianEncode(T val) noexc
}
template <typename T, typename Tendian, std::size_t size>
static MPT_CONSTEXPR17_FUN T EndianDecode(std::array<std::byte, size> data) noexcept
MPT_CONSTEXPR17_FUN T EndianDecode(std::array<std::byte, size> data) noexcept
{
static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big);
static_assert(std::numeric_limits<T>::is_integer);
@ -359,20 +359,20 @@ namespace mpt
namespace detail
{
static MPT_CONSTEXPR20_FUN uint64 SwapBytes(uint64 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap64(value); } else { return MPT_bswap64(value); } }
static MPT_CONSTEXPR20_FUN uint32 SwapBytes(uint32 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap32(value); } else { return MPT_bswap32(value); } }
static MPT_CONSTEXPR20_FUN uint16 SwapBytes(uint16 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap16(value); } else { return MPT_bswap16(value); } }
static MPT_CONSTEXPR20_FUN int64 SwapBytes(int64 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap64(value); } else { return MPT_bswap64(value); } }
static MPT_CONSTEXPR20_FUN int32 SwapBytes(int32 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap32(value); } else { return MPT_bswap32(value); } }
static MPT_CONSTEXPR20_FUN int16 SwapBytes(int16 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap16(value); } else { return MPT_bswap16(value); } }
MPT_CONSTEXPR20_FUN uint64 SwapBytes(uint64 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap64(value); } else { return MPT_bswap64(value); } }
MPT_CONSTEXPR20_FUN uint32 SwapBytes(uint32 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap32(value); } else { return MPT_bswap32(value); } }
MPT_CONSTEXPR20_FUN uint16 SwapBytes(uint16 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap16(value); } else { return MPT_bswap16(value); } }
MPT_CONSTEXPR20_FUN int64 SwapBytes(int64 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap64(value); } else { return MPT_bswap64(value); } }
MPT_CONSTEXPR20_FUN int32 SwapBytes(int32 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap32(value); } else { return MPT_bswap32(value); } }
MPT_CONSTEXPR20_FUN int16 SwapBytes(int16 value) noexcept { MPT_MAYBE_CONSTANT_IF(MPT_IS_CONSTANT_EVALUATED20()) { return MPT_constexpr_bswap16(value); } else { return MPT_bswap16(value); } }
// Do NOT remove these overloads, even if they seem useless.
// We do not want risking to extend 8bit integers to int and then
// endian-converting and casting back to int.
// Thus these overloads.
static MPT_CONSTEXPR20_FUN uint8 SwapBytes(uint8 value) noexcept { return value; }
static MPT_CONSTEXPR20_FUN int8 SwapBytes(int8 value) noexcept { return value; }
static MPT_CONSTEXPR20_FUN char SwapBytes(char value) noexcept { return value; }
MPT_CONSTEXPR20_FUN uint8 SwapBytes(uint8 value) noexcept { return value; }
MPT_CONSTEXPR20_FUN int8 SwapBytes(int8 value) noexcept { return value; }
MPT_CONSTEXPR20_FUN char SwapBytes(char value) noexcept { return value; }
} // namespace detail
} // namespace mpt
@ -387,7 +387,7 @@ static MPT_CONSTEXPR20_FUN char SwapBytes(char value) noexcept { return valu
// 1.0f --> 0x3f800000u
static MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f)
MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f)
{
if constexpr(mpt::float_traits<float32>::is_ieee754_binary32ne)
{
@ -419,7 +419,7 @@ static MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f)
}
}
}
static MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f)
MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f)
{
if constexpr(mpt::float_traits<float64>::is_ieee754_binary64ne)
{
@ -453,7 +453,7 @@ static MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f)
}
// 0x3f800000u --> 1.0f
static MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i)
MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i)
{
if constexpr(mpt::float_traits<float32>::is_ieee754_binary32ne)
{
@ -479,7 +479,7 @@ static MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i)
}
}
}
static MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i)
MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i)
{
if constexpr(mpt::float_traits<float64>::is_ieee754_binary64ne)
{

View File

@ -1348,7 +1348,7 @@ using MemoryFileReader = detail::FileReader<FileReaderTraitsMemory>;
// Initialize file reader object with pointer to data and data length.
template <typename Tbyte> static inline FileReader make_FileReader(mpt::span<Tbyte> bytedata, const mpt::PathString *filename = nullptr)
template <typename Tbyte> inline FileReader make_FileReader(mpt::span<Tbyte> bytedata, const mpt::PathString *filename = nullptr)
{
return FileReader(mpt::byte_cast<mpt::const_byte_span>(bytedata), filename);
}
@ -1356,7 +1356,7 @@ template <typename Tbyte> static inline FileReader make_FileReader(mpt::span<Tby
#if defined(MPT_FILEREADER_CALLBACK_STREAM)
// Initialize file reader object with a CallbackStream.
static inline FileReader make_FileReader(CallbackStream s, const mpt::PathString *filename = nullptr)
inline FileReader make_FileReader(CallbackStream s, const mpt::PathString *filename = nullptr)
{
return FileReader(
FileDataContainerCallbackStreamSeekable::IsSeekable(s) ?
@ -1369,7 +1369,7 @@ static inline FileReader make_FileReader(CallbackStream s, const mpt::PathString
#endif // MPT_FILEREADER_CALLBACK_STREAM
// Initialize file reader object with a std::istream.
static inline FileReader make_FileReader(std::istream *s, const mpt::PathString *filename = nullptr)
inline FileReader make_FileReader(std::istream *s, const mpt::PathString *filename = nullptr)
{
return FileReader(
FileDataContainerStdStreamSeekable::IsSeekable(s) ?

View File

@ -126,7 +126,7 @@ extern bool ConsoleEnabled;
void SetFacilities(const std::string &solo, const std::string &blocked);
bool IsFacilityActive(const char *facility);
#else
static MPT_FORCEINLINE bool IsFacilityActive(const char * /*facility*/ ) { return true; }
MPT_FORCEINLINE bool IsFacilityActive(const char * /*facility*/ ) { return true; }
#endif
@ -179,7 +179,7 @@ namespace Trace {
// if there are not multiple thread adding trace points at high frequency (way greater than 1000Hz),
// which, in OpenMPT, is only ever the case for just a single thread (the audio thread), if at all.
extern std::atomic<bool> g_Enabled;
static inline bool IsEnabled() { return g_Enabled; }
inline bool IsEnabled() { return g_Enabled; }
enum class Direction : int8
{

View File

@ -52,13 +52,13 @@ extern uint8 ProcStepping;
void InitProcSupport();
// enabled processor features for inline asm and intrinsics
static inline uint32 GetProcSupport()
inline uint32 GetProcSupport()
{
return ProcSupport;
}
// available processor features
static inline uint32 GetRealProcSupport()
inline uint32 GetRealProcSupport()
{
return RealProcSupport;
}

View File

@ -169,7 +169,7 @@ enum class FlushMode
Full = 2, // explicitly flush *all* layers, up to and including disk write caches
};
static inline FlushMode FlushModeFromBool(bool flush)
inline FlushMode FlushModeFromBool(bool flush)
{
return flush ? FlushMode::Full : FlushMode::None;
}

View File

@ -277,17 +277,17 @@ public:
#if defined(MPT_ENABLE_CHARSET_LOCALE)
#if MPT_OS_WINDOWS
#ifdef UNICODE
[[deprecated]] static inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
[[deprecated]] inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
#else
MPT_DEPRECATED_PATH static inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.AsNative()); }
MPT_DEPRECATED_PATH inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.AsNative()); }
#endif
#else
MPT_DEPRECATED_PATH static inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
MPT_DEPRECATED_PATH inline std::string ToString(const mpt::PathString & x) { return mpt::ToCharset(mpt::Charset::Locale, x.ToUnicode()); }
#endif
#endif
static inline mpt::ustring ToUString(const mpt::PathString & x) { return x.ToUnicode(); }
inline mpt::ustring ToUString(const mpt::PathString & x) { return x.ToUnicode(); }
#if MPT_WSTRING_FORMAT
static inline std::wstring ToWString(const mpt::PathString & x) { return x.ToWide(); }
inline std::wstring ToWString(const mpt::PathString & x) { return x.ToWide(); }
#endif
} // namespace mpt

View File

@ -958,7 +958,7 @@ static widestring FromUTF8(const Tsrcstring &str, widechar replacement = wide_de
if ( charsleft == 0 ) {
if ( ( c & 0x80 ) == 0x00 ) {
out.push_back( (wchar_t)c );
out.push_back( (widechar)c );
} else if ( ( c & 0xE0 ) == 0xC0 ) {
ucs4 = c & 0x1F;
charsleft = 1;
@ -1030,7 +1030,7 @@ static Tdststring ToUTF8(const widestring &str, char replacement = '?')
for ( std::size_t i=0; i<in.length(); i++ ) {
wchar_t wc = in[i];
widechar wc = in[i];
char32_t ucs4 = 0;
if constexpr ( sizeof( widechar ) == 2 ) {

View File

@ -179,7 +179,7 @@ inline Tstring Replace(Tstring str, const Tstring2 &oldStr_, const Tstring3 &new
} // namespace String
static inline std::string truncate(std::string str, std::size_t maxLen)
inline std::string truncate(std::string str, std::size_t maxLen)
{
if(str.length() > maxLen)
{
@ -343,10 +343,10 @@ using u8string = MPT_ENCODED_STRING_TYPE(mpt::Charset::UTF8);
// The wide encoding is UTF-16 or UTF-32, based on sizeof(wchar_t).
// If str does not contain any invalid characters, this conversion is lossless.
// Invalid source bytes will be replaced by some replacement character or string.
static inline std::wstring ToWide(const std::wstring &str) { return str; }
static inline std::wstring ToWide(const wchar_t * str) { return (str ? std::wstring(str) : std::wstring()); }
inline std::wstring ToWide(const std::wstring &str) { return str; }
inline std::wstring ToWide(const wchar_t * str) { return (str ? std::wstring(str) : std::wstring()); }
std::wstring ToWide(Charset from, const std::string &str);
static inline std::wstring ToWide(Charset from, const char * str) { return ToWide(from, str ? std::string(str) : std::string()); }
inline std::wstring ToWide(Charset from, const char * str) { return ToWide(from, str ? std::string(str) : std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
std::wstring ToWide(const mpt::lstring &str);
#endif // MPT_ENABLE_CHARSET_LOCALE
@ -360,10 +360,10 @@ std::wstring ToWide(const mpt::lstring &str);
// destination charset will be replaced by some replacement character or string.
#if MPT_WSTRING_CONVERT
std::string ToCharset(Charset to, const std::wstring &str);
static inline std::string ToCharset(Charset to, const wchar_t * str) { return ToCharset(to, str ? std::wstring(str) : std::wstring()); }
inline std::string ToCharset(Charset to, const wchar_t * str) { return ToCharset(to, str ? std::wstring(str) : std::wstring()); }
#endif
std::string ToCharset(Charset to, Charset from, const std::string &str);
static inline std::string ToCharset(Charset to, Charset from, const char * str) { return ToCharset(to, from, str ? std::string(str) : std::string()); }
inline std::string ToCharset(Charset to, Charset from, const char * str) { return ToCharset(to, from, str ? std::string(str) : std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
std::string ToCharset(Charset to, const mpt::lstring &str);
#endif // MPT_ENABLE_CHARSET_LOCALE
@ -371,20 +371,20 @@ std::string ToCharset(Charset to, const mpt::lstring &str);
#if defined(MPT_ENABLE_CHARSET_LOCALE)
#if MPT_WSTRING_CONVERT
mpt::lstring ToLocale(const std::wstring &str);
static inline mpt::lstring ToLocale(const wchar_t * str) { return ToLocale(str ? std::wstring(str): std::wstring()); }
inline mpt::lstring ToLocale(const wchar_t * str) { return ToLocale(str ? std::wstring(str): std::wstring()); }
#endif
mpt::lstring ToLocale(Charset from, const std::string &str);
static inline mpt::lstring ToLocale(Charset from, const char * str) { return ToLocale(from, str ? std::string(str): std::string()); }
static inline mpt::lstring ToLocale(const mpt::lstring &str) { return str; }
inline mpt::lstring ToLocale(Charset from, const char * str) { return ToLocale(from, str ? std::string(str): std::string()); }
inline mpt::lstring ToLocale(const mpt::lstring &str) { return str; }
#endif // MPT_ENABLE_CHARSET_LOCALE
#if MPT_OS_WINDOWS
#if MPT_WSTRING_CONVERT
mpt::winstring ToWin(const std::wstring &str);
static inline mpt::winstring ToWin(const wchar_t * str) { return ToWin(str ? std::wstring(str): std::wstring()); }
inline mpt::winstring ToWin(const wchar_t * str) { return ToWin(str ? std::wstring(str): std::wstring()); }
#endif
mpt::winstring ToWin(Charset from, const std::string &str);
static inline mpt::winstring ToWin(Charset from, const char * str) { return ToWin(from, str ? std::string(str): std::string()); }
inline mpt::winstring ToWin(Charset from, const char * str) { return ToWin(from, str ? std::string(str): std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
mpt::winstring ToWin(const mpt::lstring &str);
#endif // MPT_ENABLE_CHARSET_LOCALE
@ -399,11 +399,11 @@ mpt::winstring ToWin(const mpt::lstring &str);
// Convert to a MFC CString. The CString encoding depends on UNICODE.
// This should also be used when converting to TCHAR strings.
// If UNICODE is defined, this is a completely lossless operation.
static inline CString ToCString(const CString &str) { return str; }
inline CString ToCString(const CString &str) { return str; }
CString ToCString(const std::wstring &str);
static inline CString ToCString(const wchar_t * str) { return ToCString(str ? std::wstring(str) : std::wstring()); }
inline CString ToCString(const wchar_t * str) { return ToCString(str ? std::wstring(str) : std::wstring()); }
CString ToCString(Charset from, const std::string &str);
static inline CString ToCString(Charset from, const char * str) { return ToCString(from, str ? std::string(str) : std::string()); }
inline CString ToCString(Charset from, const char * str) { return ToCString(from, str ? std::string(str) : std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
CString ToCString(const mpt::lstring &str);
mpt::lstring ToLocale(const CString &str);
@ -476,24 +476,24 @@ using uchar = MPT_U8CHAR_TYPE;
#if !(MPT_WSTRING_CONVERT)
#error "MPT_USTRING_MODE_WIDE depends on MPT_WSTRING_CONVERT)"
#endif
static inline mpt::ustring ToUnicode(const std::wstring &str) { return str; }
static inline mpt::ustring ToUnicode(const wchar_t * str) { return (str ? std::wstring(str) : std::wstring()); }
static inline mpt::ustring ToUnicode(Charset from, const std::string &str) { return ToWide(from, str); }
static inline mpt::ustring ToUnicode(Charset from, const char * str) { return ToUnicode(from, str ? std::string(str) : std::string()); }
inline mpt::ustring ToUnicode(const std::wstring &str) { return str; }
inline mpt::ustring ToUnicode(const wchar_t * str) { return (str ? std::wstring(str) : std::wstring()); }
inline mpt::ustring ToUnicode(Charset from, const std::string &str) { return ToWide(from, str); }
inline mpt::ustring ToUnicode(Charset from, const char * str) { return ToUnicode(from, str ? std::string(str) : std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
static inline mpt::ustring ToUnicode(const mpt::lstring &str) { return ToWide(str); }
inline mpt::ustring ToUnicode(const mpt::lstring &str) { return ToWide(str); }
#endif // MPT_ENABLE_CHARSET_LOCALE
#if defined(MPT_WITH_MFC)
static inline mpt::ustring ToUnicode(const CString &str) { return ToWide(str); }
inline mpt::ustring ToUnicode(const CString &str) { return ToWide(str); }
#endif // MFC
#else // !MPT_USTRING_MODE_WIDE
static inline mpt::ustring ToUnicode(const mpt::ustring &str) { return str; }
inline mpt::ustring ToUnicode(const mpt::ustring &str) { return str; }
#if MPT_WSTRING_CONVERT
mpt::ustring ToUnicode(const std::wstring &str);
static inline mpt::ustring ToUnicode(const wchar_t * str) { return ToUnicode(str ? std::wstring(str) : std::wstring()); }
inline mpt::ustring ToUnicode(const wchar_t * str) { return ToUnicode(str ? std::wstring(str) : std::wstring()); }
#endif
mpt::ustring ToUnicode(Charset from, const std::string &str);
static inline mpt::ustring ToUnicode(Charset from, const char * str) { return ToUnicode(from, str ? std::string(str) : std::string()); }
inline mpt::ustring ToUnicode(Charset from, const char * str) { return ToUnicode(from, str ? std::string(str) : std::string()); }
#if defined(MPT_ENABLE_CHARSET_LOCALE)
mpt::ustring ToUnicode(const mpt::lstring &str);
#endif // MPT_ENABLE_CHARSET_LOCALE

View File

@ -76,8 +76,8 @@ template <typename T> auto ToString(const T & x) -> decltype(mpt::ToCharset(mpt:
template <typename T> auto ToString(const T & x) -> decltype(mpt::ToCharset(mpt::CharsetLocaleOrUTF8, x.ToUString())) { return mpt::ToCharset(mpt::CharsetLocaleOrUTF8, x.ToUString()); }
#endif
static inline std::string ToString(const std::string & x) { return x; }
static inline std::string ToString(const char * const & x) { return x; }
inline std::string ToString(const std::string & x) { return x; }
inline std::string ToString(const char * const & x) { return x; }
std::string ToString(const char &x) = delete; // deprecated to catch potential API mis-use, use std::string(1, x) instead
#if MPT_WSTRING_FORMAT
std::string ToString(const std::wstring & x) = delete; // Unknown encoding.
@ -108,7 +108,7 @@ std::string ToString(const long double & x);
// fallback to member function ToUString()
template <typename T> auto ToUString(const T & x) -> decltype(x.ToUString()) { return x.ToUString(); }
static inline mpt::ustring ToUString(const mpt::ustring & x) { return x; }
inline mpt::ustring ToUString(const mpt::ustring & x) { return x; }
mpt::ustring ToUString(const std::string & x) = delete; // Unknown encoding.
mpt::ustring ToUString(const char * const & x) = delete; // Unknown encoding. Note that this also applies to TCHAR in !UNICODE builds as the type is indistinguishable from char. Wrap with CString or FromTcharStr in this case.
mpt::ustring ToUString(const char & x) = delete; // deprecated to catch potential API mis-use, use std::string(1, x) instead
@ -141,8 +141,8 @@ mpt::ustring ToUString(const long double & x);
std::wstring ToWString(const std::string & x) = delete; // Unknown encoding.
std::wstring ToWString(const char * const & x) = delete; // Unknown encoding. Note that this also applies to TCHAR in !UNICODE builds as the type is indistinguishable from char. Wrap with CString or FromTcharStr in this case.
std::wstring ToWString(const char & x) = delete; // deprecated to catch potential API mis-use, use std::string(1, x) instead
static inline std::wstring ToWString(const std::wstring & x) { return x; }
static inline std::wstring ToWString(const wchar_t * const & x) { return x; }
inline std::wstring ToWString(const std::wstring & x) { return x; }
inline std::wstring ToWString(const wchar_t * const & x) { return x; }
std::wstring ToWString(const wchar_t & x) = delete; // deprecated to catch potential API mis-use, use std::wstring(1, x) instead
#if MPT_USTRING_MODE_UTF8
std::wstring ToWString(const mpt::ustring & x);

View File

@ -17,7 +17,7 @@ OPENMPT_NAMESPACE_BEGIN
// Version definitions. The only thing that needs to be changed when changing version number.
#define VER_MAJORMAJOR 1
#define VER_MAJOR 29
#define VER_MINOR 05
#define VER_MINOR 06
#define VER_MINORMINOR 00
OPENMPT_NAMESPACE_END

View File

@ -5,6 +5,20 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This
is just a high-level summary.
### libopenmpt 0.5.4 (2020-11-29)
* AMS: An upper bound for uncompressed sample size is now established to
avoid memory exhaustion from malformed files.
* DMF: Support early format beta versions (in particular versions 1-4).
* MED: Also use octave wrapping in 8-channel mode for MMD0/MMD1 modules.
* MED: If 8-channel mode is activated, ignore BPM mode.
* MED: Emulate tempo commands F01 and F02 quirk.
* MED: Tempo commands below 32 BPM were interpreted as tempo slides.
* IMF: Instrument sample mapping was off by one octave, notable in the guitar
part of Astaris by Karsten Koch.
* pugixml: Update to v1.11 (2020-11-26).
### libopenmpt 0.5.3 (2020-10-25)
* [**Sec**] Possible hang if a MED file claimed to contain 256 songs. (r13704)

View File

@ -14,8 +14,6 @@
#include "libopenmpt_impl.hpp"
#include "libopenmpt_ext.hpp"
using namespace OpenMPT;
namespace openmpt {
class module_ext_impl

View File

@ -19,7 +19,7 @@
/*! \brief libopenmpt minor version number */
#define OPENMPT_API_VERSION_MINOR 5
/*! \brief libopenmpt patch version number */
#define OPENMPT_API_VERSION_PATCH 3
#define OPENMPT_API_VERSION_PATCH 4
/*! \brief libopenmpt pre-release tag */
#define OPENMPT_API_VERSION_PREREL ""
/*! \brief libopenmpt pre-release flag */

View File

@ -1,8 +1,8 @@
LIBOPENMPT_VERSION_MAJOR=0
LIBOPENMPT_VERSION_MINOR=5
LIBOPENMPT_VERSION_PATCH=3
LIBOPENMPT_VERSION_PATCH=4
LIBOPENMPT_VERSION_PREREL=
LIBOPENMPT_LTVER_CURRENT=2
LIBOPENMPT_LTVER_REVISION=3
LIBOPENMPT_LTVER_REVISION=4
LIBOPENMPT_LTVER_AGE=2

View File

@ -2,12 +2,12 @@
* load_dmf.cpp
* ------------
* Purpose: DMF module loader (X-Tracker by D-LUSiON).
* Notes : If it wasn't already outdated when the tracker was released, this would be a rather interesting
* Notes : If it wasn't already outdated when the tracker left beta state, this would be a rather interesting
* and in some parts even sophisticated format - effect columns are separated by effect type, an easy to
* understand BPM tempo mode, effect durations are always divided into a 256th row, vibrato effects are
* specified by period length and the same 8-Bit granularity is used for both volume and panning.
* Unluckily, this format does not offer any envelopes or multi-sample instruments, and bidi sample loops
* are missing as well, so it was already well behind FT2 and IT back then.
* are missing as well, so it was already well behind FT2 back then.
* Authors: Johannes Schultz (mostly based on DMF.TXT, DMF_EFFC.TXT, trial and error and some invaluable hints by Zatzen)
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
@ -23,9 +23,9 @@ OPENMPT_NAMESPACE_BEGIN
// DMF header
struct DMFFileHeader
{
char signature[4]; // "DDMF"
uint8 version; // 1 - 7 are beta versions, 8 is the official thing, 10 is xtracker32
char tracker[8]; // "XTRACKER"
char signature[4]; // "DDMF"
uint8 version; // 1 - 7 are beta versions, 8 is the official thing, 10 is xtracker32
char tracker[8]; // "XTRACKER"
char songname[30];
char composer[20];
uint8 creationDay;
@ -40,14 +40,14 @@ struct DMFChunk
// 32-Bit chunk identifiers
enum ChunkIdentifiers
{
idCMSG = MagicLE("CMSG"), // Song message
idSEQU = MagicLE("SEQU"), // Order list
idPATT = MagicLE("PATT"), // Patterns
idSMPI = MagicLE("SMPI"), // Sample headers
idSMPD = MagicLE("SMPD"), // Sample data
idSMPJ = MagicLE("SMPJ"), // Sample jump table (XTracker 32 only)
idENDE = MagicLE("ENDE"), // Last four bytes of DMF file
idSETT = MagicLE("SETT"), // Probably contains GUI settings
idCMSG = MagicLE("CMSG"), // Song message
idSEQU = MagicLE("SEQU"), // Order list
idPATT = MagicLE("PATT"), // Patterns
idSMPI = MagicLE("SMPI"), // Sample headers
idSMPD = MagicLE("SMPD"), // Sample data
idSMPJ = MagicLE("SMPJ"), // Sample jump table (XTracker 32 only)
idENDE = MagicLE("ENDE"), // Last four bytes of DMF file
idSETT = MagicLE("SETT"), // Probably contains GUI settings
};
uint32le id;
@ -66,21 +66,11 @@ struct DMFChunk
MPT_BINARY_STRUCT(DMFChunk, 8)
// Order list
struct DMFSequence
{
uint16le loopStart;
uint16le loopEnd;
// order list follows here ...
};
MPT_BINARY_STRUCT(DMFSequence, 4)
// Pattern header (global)
struct DMFPatterns
{
uint16le numPatterns; // 1..1024 patterns
uint8le numTracks; // 1..32 channels
uint16le numPatterns; // 1..1024 patterns
uint8le numTracks; // 1..32 channels
};
MPT_BINARY_STRUCT(DMFPatterns, 3)
@ -88,8 +78,8 @@ MPT_BINARY_STRUCT(DMFPatterns, 3)
// Pattern header (for each pattern)
struct DMFPatternHeader
{
uint8le numTracks; // 1..32 channels
uint8le beat; // [hi|lo] -> hi = rows per beat, lo = reserved
uint8le numTracks; // 1..32 channels
uint8le beat; // [hi|lo] -> hi = rows per beat, lo = reserved
uint16le numRows;
uint32le patternLength;
// patttern data follows here ...
@ -103,20 +93,20 @@ struct DMFSampleHeader
enum SampleFlags
{
// Sample flags
smpLoop = 0x01,
smp16Bit = 0x02,
smpLoop = 0x01,
smp16Bit = 0x02,
smpCompMask = 0x0C,
smpComp1 = 0x04, // Compression type 1
smpComp2 = 0x08, // Compression type 2 (unused)
smpComp3 = 0x0C, // Compression type 3 (ditto)
smpLibrary = 0x80, // Sample is stored in a library
smpComp1 = 0x04, // Compression type 1
smpComp2 = 0x08, // Compression type 2 (unused)
smpComp3 = 0x0C, // Compression type 3 (ditto)
smpLibrary = 0x80, // Sample is stored in a library
};
uint32le length;
uint32le loopStart;
uint32le loopEnd;
uint16le c3freq; // 1000..45000hz
uint8le volume; // 0 = ignore
uint16le c3freq; // 1000..45000hz
uint8le volume; // 0 = ignore
uint8le flags;
// Convert an DMFSampleHeader to OpenMPT's internal sample representation.
@ -151,15 +141,6 @@ struct DMFSampleHeader
MPT_BINARY_STRUCT(DMFSampleHeader, 16)
// Sample header tail (between head and tail, there might be the library name of the sample, depending on the DMF version)
struct DMFSampleHeaderTail
{
uint16le filler;
uint32le crc32;
};
MPT_BINARY_STRUCT(DMFSampleHeaderTail, 6)
// Pattern translation memory
struct DMFPatternSettings
@ -182,7 +163,7 @@ struct DMFPatternSettings
uint8 internalTicks = 6; // Ticks per row in final pattern
DMFPatternSettings(CHANNELINDEX numChannels)
: channels(numChannels)
: channels(numChannels)
{ }
};
@ -250,9 +231,7 @@ static uint8 DMFvibrato2MPT(uint8 val, const uint8 internalTicks)
static void ApplyEffectMemory(const ModCommand *m, ROWINDEX row, CHANNELINDEX numChannels, uint8 effect, uint8 &param)
{
if(effect == CMD_NONE || param == 0)
{
return;
}
const bool isTonePortaEffect = (effect == CMD_PORTAMENTOUP || effect == CMD_PORTAMENTODOWN || effect == CMD_TONEPORTAMENTO);
const bool isVolSlideEffect = (effect == CMD_VOLUMESLIDE || effect == CMD_TONEPORTAVOL || effect == CMD_VIBRATOVOL);
@ -309,28 +288,39 @@ static void ApplyEffectMemory(const ModCommand *m, ROWINDEX row, CHANNELINDEX nu
}
static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &settings, CSoundFile &sndFile)
static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, DMFPatternSettings &settings, CSoundFile &sndFile)
{
// Pattern flags
enum PatternFlags
{
// Global Track
patGlobPack = 0x80, // Pack information for global track follows
patGlobMask = 0x3F, // Mask for global effects
patGlobPack = 0x80, // Pack information for global track follows
patGlobMask = 0x3F, // Mask for global effects
// Note tracks
patCounter = 0x80, // Pack information for current channel follows
patInstr = 0x40, // Instrument number present
patNote = 0x20, // Note present
patVolume = 0x10, // Volume present
patInsEff = 0x08, // Instrument effect present
patNoteEff = 0x04, // Note effect present
patVolEff = 0x02, // Volume effect stored
patCounter = 0x80, // Pack information for current channel follows
patInstr = 0x40, // Instrument number present
patNote = 0x20, // Note present
patVolume = 0x10, // Volume present
patInsEff = 0x08, // Instrument effect present
patNoteEff = 0x04, // Note effect present
patVolEff = 0x02, // Volume effect stored
};
file.Rewind();
DMFPatternHeader patHead;
file.ReadStruct(patHead);
if(fileVersion < 3)
{
patHead.numTracks = file.ReadUint8();
file.Skip(2); // not sure what this is, later X-Tracker versions just skip over it
patHead.numRows = file.ReadUint16LE();
patHead.patternLength = file.ReadUint32LE();
} else
{
file.ReadStruct(patHead);
}
if(fileVersion < 6)
patHead.beat = 0;
const ROWINDEX numRows = Clamp(ROWINDEX(patHead.numRows), ROWINDEX(1), MAX_PATTERN_ROWS);
const PATTERNINDEX pat = sndFile.Patterns.InsertAny(numRows);
@ -379,17 +369,17 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
switch(globalInfo)
{
case 1: // Set Tick Frame Speed
case 1: // Set Tick Frame Speed
settings.realBPMmode = false;
settings.tempoTicks = std::max(uint8(1), globalData); // Tempo in 1/4 rows per second
settings.tempoBPM = 0; // Automatically updated by X-Tracker
settings.tempoTicks = std::max(uint8(1), globalData); // Tempo in 1/4 rows per second
settings.tempoBPM = 0; // Automatically updated by X-Tracker
tempoChange = true;
break;
case 2: // Set BPM Speed (real BPM mode)
if(globalData) // DATA = 0 doesn't do anything
case 2: // Set BPM Speed (real BPM mode)
if(globalData) // DATA = 0 doesn't do anything
{
settings.realBPMmode = true;
settings.tempoBPM = globalData; // Tempo in real BPM (depends on rows per beat)
settings.tempoBPM = globalData; // Tempo in real BPM (depends on rows per beat)
if(settings.beat != 0)
{
settings.tempoTicks = (globalData * settings.beat * 15); // Automatically updated by X-Tracker
@ -397,7 +387,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
tempoChange = true;
}
break;
case 3: // Set Beat
case 3: // Set Beat
settings.beat = (globalData >> 4);
if(settings.beat != 0)
{
@ -409,12 +399,12 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
settings.realBPMmode = false;
}
break;
case 4: // Tick Delay
case 4: // Tick Delay
writeDelay = globalData;
break;
case 5: // Set External Flag
case 5: // Set External Flag
break;
case 6: // Slide Speed Up
case 6: // Slide Speed Up
if(globalData > 0)
{
uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
@ -428,7 +418,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
tempoChange = true;
}
break;
case 7: // Slide Speed Down
case 7: // Slide Speed Down
if(globalData > 0)
{
uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
@ -482,7 +472,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
}
}
m = sndFile.Patterns[pat].GetpModCommand(row, 1); // Reserve first channel for global effects
m = sndFile.Patterns[pat].GetpModCommand(row, 1); // Reserve first channel for global effects
for(CHANNELINDEX chn = 1; chn <= numChannels; chn++, m++)
{
@ -499,7 +489,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
////////////////////////////////////////////////////////////////
// 0x40: Instrument
bool slideNote = true; // If there is no instrument number next to a note, the note is not retriggered!
bool slideNote = true; // If there is no instrument number next to a note, the note is not retriggered!
if((channelInfo & patInstr) != 0)
{
m->instr = file.ReadUint8();
@ -551,7 +541,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
if((channelInfo & patVolume) != 0)
{
m->volcmd = VOLCMD_VOLUME;
m->vol = (file.ReadUint8() + 2) / 4; // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker.
m->vol = (file.ReadUint8() + 2) / 4; // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker.
}
////////////////////////////////////////////////////////////////
@ -563,20 +553,20 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
switch(effect1)
{
case 1: // Stop Sample
case 1: // Stop Sample
m->note = NOTE_NOTECUT;
effect1 = CMD_NONE;
break;
case 2: // Stop Sample Loop
case 2: // Stop Sample Loop
m->note = NOTE_KEYOFF;
effect1 = CMD_NONE;
break;
case 3: // Instrument Volume Override (aka "Restart")
case 3: // Instrument Volume Override (aka "Restart")
m->note = settings.channels[chn].lastNote;
settings.channels[chn].playDir = false;
effect1 = CMD_NONE;
break;
case 4: // Sample Delay
case 4: // Sample Delay
effectParam1 = DMFdelay2MPT(effectParam1, settings.internalTicks);
if(effectParam1)
{
@ -592,15 +582,15 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
settings.channels[chn].playDir = false;
}
break;
case 5: // Tremolo Retrig Sample (who invented those stupid effect names?)
case 5: // Tremolo Retrig Sample (who invented those stupid effect names?)
effectParam1 = std::max(uint8(1), DMFdelay2MPT(effectParam1, settings.internalTicks));
effect1 = CMD_RETRIG;
settings.channels[chn].playDir = false;
break;
case 6: // Offset
case 7: // Offset + 64k
case 8: // Offset + 128k
case 9: // Offset + 192k
case 6: // Offset
case 7: // Offset + 64k
case 8: // Offset + 128k
case 9: // Offset + 192k
// Put high offset on previous row
if(row > 0 && effect1 != settings.channels[chn].highOffset)
{
@ -640,12 +630,12 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
switch(effect2)
{
case 1: // Note Finetune
case 1: // Note Finetune
effect2 = static_cast<ModCommand::COMMAND>(effectParam2 < 128 ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN);
if(effectParam2 > 128) effectParam2 = 255 - effectParam2 + 1;
effectParam2 = 0xF0 | std::min(uint8(0x0F), effectParam2); // Well, this is not too accurate...
effectParam2 = 0xF0 | std::min(uint8(0x0F), effectParam2); // Well, this is not too accurate...
break;
case 2: // Note Delay (wtf is the difference to Sample Delay?)
case 2: // Note Delay (wtf is the difference to Sample Delay?)
effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
if(effectParam2)
{
@ -657,17 +647,17 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
}
useMem2 = true;
break;
case 3: // Arpeggio
case 3: // Arpeggio
effect2 = CMD_ARPEGGIO;
useMem2 = true;
break;
case 4: // Portamento Up
case 5: // Portamento Down
case 4: // Portamento Up
case 5: // Portamento Down
effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, true);
effect2 = static_cast<ModCommand::COMMAND>(effect2 == 4 ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN);
useMem2 = true;
break;
case 6: // Portamento to Note
case 6: // Portamento to Note
if(m->note == NOTE_NONE)
{
m->note = settings.channels[chn].noteBuffer;
@ -676,15 +666,15 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
effect2 = CMD_TONEPORTAMENTO;
useMem2 = true;
break;
case 7: // Scratch to Note (neat! but we don't have such an effect...)
case 7: // Scratch to Note (neat! but we don't have such an effect...)
m->note = static_cast<ModCommand::NOTE>(Clamp(effectParam2 + 25, NOTE_MIN, NOTE_MAX));
effect2 = CMD_TONEPORTAMENTO;
effectParam2 = 0xFF;
useMem2 = true;
break;
case 8: // Vibrato Sine
case 9: // Vibrato Triangle (ramp down should be close enough)
case 10: // Vibrato Square
case 8: // Vibrato Sine
case 9: // Vibrato Triangle (ramp down should be close enough)
case 10: // Vibrato Square
// Put vibrato type on previous row
if(row > 0 && effect2 != settings.channels[chn].vibratoType)
{
@ -697,12 +687,12 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
effectParam2 = DMFvibrato2MPT(effectParam2, settings.internalTicks);
useMem2 = true;
break;
case 11: // Note Tremolo
case 11: // Note Tremolo
effectParam2 = DMFtremor2MPT(effectParam2, settings.internalTicks);
effect2 = CMD_TREMOR;
useMem2 = true;
break;
case 12: // Note Cut
case 12: // Note Cut
effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
if(effectParam2)
{
@ -730,20 +720,20 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
switch(effect3)
{
case 1: // Volume Slide Up
case 2: // Volume Slide Down
case 1: // Volume Slide Up
case 2: // Volume Slide Down
effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 1));
effect3 = CMD_VOLUMESLIDE;
useMem3 = true;
break;
case 3: // Volume Tremolo (actually this is Tremor)
case 3: // Volume Tremolo (actually this is Tremor)
effectParam3 = DMFtremor2MPT(effectParam3, settings.internalTicks);
effect3 = CMD_TREMOR;
useMem3 = true;
break;
case 4: // Tremolo Sine
case 5: // Tremolo Triangle (ramp down should be close enough)
case 6: // Tremolo Square
case 4: // Tremolo Sine
case 5: // Tremolo Triangle (ramp down should be close enough)
case 6: // Tremolo Square
// Put tremolo type on previous row
if(row > 0 && effect3 != settings.channels[chn].tremoloType)
{
@ -756,16 +746,16 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
useMem3 = true;
break;
case 7: // Set Balance
case 7: // Set Balance
effect3 = CMD_PANNING8;
break;
case 8: // Slide Balance Left
case 9: // Slide Balance Right
case 8: // Slide Balance Left
case 9: // Slide Balance Right
effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 8));
effect3 = CMD_PANNINGSLIDE;
useMem3 = true;
break;
case 10: // Balance Vibrato Left/Right (always sine modulated)
case 10: // Balance Vibrato Left/Right (always sine modulated)
effect3 = CMD_PANBRELLO;
effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
useMem3 = true;
@ -778,13 +768,9 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
// Let's see if we can help the effect swapper by reducing some effect parameters to "continue" parameters.
if(useMem2)
{
ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect2, effectParam2);
}
if(useMem3)
{
ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect3, effectParam3);
}
// I guess this is close enough to "not retriggering the note"
if(slideNote && m->IsNote())
@ -837,7 +823,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
{
channelCounter[chn]--;
}
} // End for all channels
} // End for all channels
// Now we can try to write tempo information.
if(tempoChange)
@ -858,7 +844,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, DMFPatternSettings &sett
sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x60u | Clamp(param, uint8(1), uint8(15))).Row(row).AllowMultiple());
}
writeDelay = 0;
} // End for all rows
} // End for all rows
return pat;
}
@ -925,19 +911,43 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
m_FileHistory.clear();
m_FileHistory.push_back(mptHistory);
// Go through all chunks now
ChunkReader chunkFile(file);
ChunkReader::ChunkList<DMFChunk> chunks = chunkFile.ReadChunks<DMFChunk>(1);
// Go through all chunks now... cannot use our standard IFF chunk reader here because early X-Tracker versions write some malformed chunk headers... fun code ahead!
ChunkReader::ChunkList<DMFChunk> chunks;
while(file.CanRead(sizeof(DMFChunk)))
{
DMFChunk chunkHeader;
file.Read(chunkHeader);
uint32 chunkLength = chunkHeader.length, chunkSkip = 0;
// When loop start was added to version 3, the chunk size was not updated...
if(fileHeader.version == 3 && chunkHeader.GetID() == DMFChunk::idSEQU && chunkLength < uint32_max - 2)
chunkSkip = 2;
// ...and when the loop end was added to version 4, it was also note updated! Luckily they fixed it in version 5.
else if(fileHeader.version == 4 && chunkHeader.GetID() == DMFChunk::idSEQU && chunkLength < uint32_max - 4)
chunkSkip = 4;
// Earlier X-Tracker versions also write a garbage length for the SMPD chunk if samples are compressed.
// I don't know when exactly this stopped, but I have no version 5-7 files to check (and no X-Tracker version that writes those versions).
// Since this is practically always the last chunk in the file, the following code is safe for those versions, though.
else if(fileHeader.version < 8 && chunkHeader.GetID() == DMFChunk::idSMPD)
chunkLength = uint32_max;
chunks.emplace_back(chunkHeader, file.ReadChunk(chunkLength));
file.Skip(chunkSkip);
}
FileReader chunk;
// Read order list
DMFSequence seqHeader;
chunk = chunks.GetChunk(DMFChunk::idSEQU);
if(!chunk.ReadStruct(seqHeader))
{
return false;
}
ReadOrderFromFile<uint16le>(Order(), chunk, (chunk.GetLength() - sizeof(DMFSequence)) / 2);
ORDERINDEX seqLoopStart = 0, seqLoopEnd = ORDERINDEX_MAX;
if(fileHeader.version >= 3)
seqLoopStart = chunk.ReadUint16LE();
if(fileHeader.version >= 4)
seqLoopEnd = chunk.ReadUint16LE();
// HIPOMATK.DMF has a loop end of 0, other v4 files have proper loop ends. Later X-Tracker versions import it as-is but it cannot be intentional.
// We just assume that this feature might have been buggy in early v4 versions and ignore the loop end in that case.
if(fileHeader.version == 4 && seqLoopEnd == 0)
seqLoopEnd = ORDERINDEX_MAX;
ReadOrderFromFile<uint16le>(Order(), chunk, chunk.BytesLeft() / 2);
LimitMax(seqLoopStart, Order().GetLastIndex());
LimitMax(seqLoopEnd, Order().GetLastIndex());
// Read patterns
chunk = chunks.GetChunk(DMFChunk::idPATT);
@ -951,10 +961,11 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
std::vector<FileReader> patternChunks(patHeader.numPatterns);
for(auto &patternChunk : patternChunks)
{
DMFPatternHeader header;
chunk.ReadStruct(header);
chunk.SkipBack(sizeof(header));
patternChunk = chunk.ReadChunk(sizeof(header) + header.patternLength);
const uint8 headerSize = fileHeader.version < 3 ? 9 : 8;
chunk.Skip(headerSize - sizeof(uint32le));
const uint32 patLength = chunk.ReadUint32LE();
chunk.SkipBack(headerSize);
patternChunk = chunk.ReadChunk(headerSize + patLength);
}
// Now go through the order list and load them.
@ -966,14 +977,14 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
// Create one pattern for each order item, as the same pattern can be played with different settings
if(pat < patternChunks.size())
{
pat = ConvertDMFPattern(patternChunks[pat], settings, *this);
pat = ConvertDMFPattern(patternChunks[pat], fileHeader.version, settings, *this);
}
}
// Write loop end if necessary
if(Order().IsValidPat(seqHeader.loopEnd) && (seqHeader.loopStart > 0 || seqHeader.loopEnd < Order().GetLastIndex()))
if(Order().IsValidPat(seqLoopEnd) && (seqLoopStart > 0 || seqLoopEnd < Order().GetLastIndex()))
{
PATTERNINDEX pat = Order()[seqHeader.loopEnd];
Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(seqHeader.loopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
PATTERNINDEX pat = Order()[seqLoopEnd];
Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(seqLoopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
}
}
@ -995,20 +1006,19 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
chunk.ReadSizedString<uint8le, mpt::String::spacePadded>(m_szNames[smp]);
const uint8 nameLength = (fileHeader.version < 2) ? 30 : chunk.ReadUint8();
chunk.ReadString<mpt::String::spacePadded>(m_szNames[smp], nameLength);
DMFSampleHeader sampleHeader;
ModSample &sample = Samples[smp];
chunk.ReadStruct(sampleHeader);
sampleHeader.ConvertToMPT(sample);
// Read library name in version 8 files
if(fileHeader.version >= 8)
{
// Read library name in version 8 files
chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);
}
// We don't care for the checksum of the sample data...
chunk.Skip(sizeof(DMFSampleHeaderTail));
// Filler + CRC
chunk.Skip(fileHeader.version > 1 ? 6 : 2);
// Now read the sample data from the data chunk
FileReader sampleData = sampleDataChunk.ReadChunk(sampleDataChunk.ReadUint32LE());
@ -1024,7 +1034,7 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
}
InitializeChannels();
m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX; // this will be converted to IT format by MPT. SONG_ITOLDEFFECTS is not set because of tremor and vibrato.
m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX; // this will be converted to IT format by MPT. SONG_ITOLDEFFECTS is not set because of tremor and vibrato.
m_nDefaultSpeed = 6;
m_nDefaultTempo.Set(120);
m_nDefaultGlobalVolume = 256;
@ -1050,7 +1060,7 @@ struct DMFHTree
DMFHNode nodes[256];
DMFHTree(FileReader &file)
: file(file)
: file(file)
, lastnode(0)
, nodecount(0)
{

View File

@ -123,10 +123,9 @@ struct IMFInstrument
if(smpNum)
{
static_assert(mpt::array_size<decltype(mptIns.Keyboard)>::size >= mpt::array_size<decltype(map)>::size);
for(size_t note = 0; note < std::size(map); note++)
for(size_t note = 0; note < std::min(std::size(map), std::size(mptIns.Keyboard) - 12u); note++)
{
mptIns.Keyboard[note] = firstSample + map[note];
mptIns.Keyboard[note + 12] = firstSample + map[note];
}
}

View File

@ -266,16 +266,20 @@ struct MMDInstrExt
// Below fields saved by >= V5
uint8be defaultPitch;
uint8be instrFlags;
uint16be longMidiPreset;
uint16be longMidiPreset; // Legacy MIDI program mode that doesn't use banks but a combination of two program change commands
// Below fields saved by >= V5.02
uint8be outputDevice;
uint8be reserved;
// Below fields saved by >= V7
uint32be loopStart;
uint32be loopLength;
// Not sure which version starts saving those but they are saved by MED Soundstudio for Windows
uint8 volume; // 0...127
uint8 outputPort; // Index into user-configurable device list (NOT WinAPI port index)
uint16le midiBank;
};
MPT_BINARY_STRUCT(MMDInstrExt, 18)
MPT_BINARY_STRUCT(MMDInstrExt, 22)
struct MMDInstrInfo
@ -350,9 +354,19 @@ struct MMDTag
MPT_BINARY_STRUCT(MMDTag, 8)
struct MMDDump
{
uint32be length;
uint32be dataPointer;
uint16be extLength; // If >= 20: name follows as char[20]
};
MPT_BINARY_STRUCT(MMDDump, 10)
static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPerBeat)
{
if(bpmMode)
if(bpmMode && !is8Ch)
{
// You would have thought that we could use modern tempo mode here.
// Alas, the number of ticks per row still influences the tempo. :(
@ -412,7 +426,14 @@ static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rows
} else if(m.param <= 0xF0)
{
m.command = CMD_TEMPO;
m.param = mpt::saturate_round<ModCommand::PARAM>(MMDTempoToBPM(m.param, is8ch, bpmMode, rowsPerBeat).ToDouble());
if(m.param < 0x03) // This appears to be a bug in OctaMED which is not emulated in MED Soundstudio on Windows.
m.param = 0x70;
else
m.param = mpt::saturate_round<ModCommand::PARAM>(MMDTempoToBPM(m.param, is8ch, bpmMode, rowsPerBeat).ToDouble());
#ifdef MODPLUG_TRACKER
if(m.param < 0x20)
m.param = 0x20;
#endif // MODPLUG_TRACKER
} else switch(m.command)
{
case 0xF1: // Play note twice
@ -447,6 +468,10 @@ static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rows
break;
}
break;
case 0x10: // MIDI message
m.command = CMD_MIDI;
m.param |= 0x80;
break;
case 0x11: // Slide pitch up
m.command = CMD_MODCMDEX;
m.param = 0x10 | std::min<uint8>(m.param, 0x0F);
@ -482,6 +507,16 @@ static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rows
m.command = CMD_MODCMDEX;
m.param = 0xB0 | std::min<uint8>(m.param, 0x0F);
break;
case 0x1C: // MIDI program
if(m.param > 0 && m.param <= 128)
{
m.command = CMD_MIDI;
m.param--;
} else
{
m.command = CMD_NONE;
}
break;
case 0x1D: // Pattern break (in hex)
m.command = CMD_PATTERNBREAK;
break;
@ -620,7 +655,7 @@ static bool ValidateHeader(const MMD0FileHeader &fileHeader)
|| fileHeader.songOffset < sizeof(MMD0FileHeader)
|| fileHeader.songOffset > uint32_max - 63 * sizeof(MMD0Sample) - sizeof(MMDSong)
|| fileHeader.blockArrOffset < sizeof(MMD0FileHeader)
|| fileHeader.sampleArrOffset < sizeof(MMD0FileHeader)
|| (fileHeader.sampleArrOffset > 0 && fileHeader.sampleArrOffset < sizeof(MMD0FileHeader))
|| fileHeader.expDataOffset > uint32_max - sizeof(MMD0Exp))
{
return false;
@ -633,7 +668,7 @@ static uint64 GetHeaderMinimumAdditionalSize(const MMD0FileHeader &fileHeader)
{
return std::max<uint64>({ fileHeader.songOffset + 63 * sizeof(MMD0Sample) + sizeof(MMDSong),
fileHeader.blockArrOffset,
fileHeader.sampleArrOffset,
fileHeader.sampleArrOffset ? fileHeader.sampleArrOffset : sizeof(MMD0FileHeader),
fileHeader.expDataOffset + sizeof(MMD0Exp) }) - sizeof(MMD0FileHeader);
}
@ -689,15 +724,21 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
// Start with the instruments, as those are shared between songs
std::vector<uint32be> instrOffsets;
file.Seek(fileHeader.sampleArrOffset);
file.ReadVector(instrOffsets, songHeader.numSamples);
if(fileHeader.sampleArrOffset)
{
file.Seek(fileHeader.sampleArrOffset);
file.ReadVector(instrOffsets, songHeader.numSamples);
} else if(songHeader.numSamples > 0)
{
return false;
}
m_nInstruments = m_nSamples = songHeader.numSamples;
// In MMD0 / MMD1, octave wrapping is only done in 4-channel modules (hardware mixing!), and not for synth instruments
// - It's required e.g. for automatic terminated to.mmd0
// - dissociate.mmd0 (8 channels) and starkelsesirap.mmd0 (synth) on the other hand don't need it
// In MMD0 / MMD1, octave wrapping is not done for synth instruments
// - It's required e.g. for automatic terminated to.mmd0 and you got to let the music.mmd1
// - starkelsesirap.mmd0 (synth instruments) on the other hand don't need it
// In MMD2 / MMD3, the mix flag is used instead.
const bool hardwareMixSamples = (version < 2 && m_nChannels == 4) || (version >= 2 && !(songHeader.flags2 & MMDSong::FLAG2_MIX));
const bool hardwareMixSamples = (version < 2) || (version >= 2 && !(songHeader.flags2 & MMDSong::FLAG2_MIX));
bool needInstruments = false;
bool anySynthInstrs = false;
@ -925,10 +966,10 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
ins.VolEnv.dwFlags.set(ENV_ENABLED);
needInstruments = true;
}
if(size > offsetof(MMDInstrExt, longMidiPreset))
{
ins.wMidiBank = instrExt.longMidiPreset;
}
if(size > offsetof(MMDInstrExt, volume))
ins.nGlobalVol = (instrExt.volume + 1u) / 2u;
if(size > offsetof(MMDInstrExt, midiBank))
ins.wMidiBank = instrExt.midiBank;
#ifndef NO_VST
if(ins.nMixPlug > 0)
{
@ -995,6 +1036,10 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
}
}
// Setup a program change macro for command 1C (even if MIDI plugin is disabled, as otherwise these commands may act as filter commands)
m_MidiCfg.ClearZxxMacros();
strcpy(m_MidiCfg.szMidiSFXExt[0], "Cc z");
file.Rewind();
PATTERNINDEX basePattern = 0;
for(SEQUENCEINDEX song = 0; song < numSongs; song++)
@ -1062,56 +1107,57 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
|| !file.CanRead(songHeader.songLength * 2)
|| !file.ReadVector(sections, songHeader.songLength))
continue;
for(uint16 section : sections)
{
if(section > header.numPlaySeqs)
continue;
file.Seek(header.playSeqTableOffset + section * 4);
if(file.Seek(file.ReadUint32BE()) || !file.CanRead(sizeof(MMD2PlaySeq)))
if(!file.Seek(file.ReadUint32BE()) || !file.CanRead(sizeof(MMD2PlaySeq)))
continue;
MMD2PlaySeq playSeq;
file.ReadStruct(playSeq);
if(!order.empty())
order.push_back(order.GetIgnoreIndex());
size_t readOrders = playSeq.length;
if(!file.CanRead(readOrders))
LimitMax(readOrders, file.BytesLeft());
LimitMax(readOrders, ORDERINDEX_MAX);
size_t orderStart = order.size();
order.reserve(orderStart + readOrders);
for(size_t ord = 0; ord < readOrders; ord++)
{
MMD2PlaySeq playSeq;
file.ReadStruct(playSeq);
if(!order.empty())
order.push_back(order.GetIgnoreIndex());
size_t readOrders = playSeq.length;
if(!file.CanRead(readOrders))
LimitMax(readOrders, file.BytesLeft());
LimitMax(readOrders, ORDERINDEX_MAX);
size_t orderStart = order.size();
order.reserve(orderStart + readOrders);
for(size_t ord = 0; ord < readOrders; ord++)
PATTERNINDEX pat = file.ReadUint16BE();
if(pat < 0x8000)
{
PATTERNINDEX pat = file.ReadUint16BE();
if(pat < 0x8000)
{
order.push_back(basePattern + pat);
}
order.push_back(basePattern + pat);
}
if(playSeq.name[0])
order.SetName(mpt::ToUnicode(mpt::Charset::ISO8859_1, playSeq.name));
}
if(playSeq.name[0])
order.SetName(mpt::ToUnicode(mpt::Charset::ISO8859_1, playSeq.name));
// Play commands (jump / stop)
if(playSeq.commandTableOffset > 0 && file.Seek(playSeq.commandTableOffset))
// Play commands (jump / stop)
if(playSeq.commandTableOffset > 0 && file.Seek(playSeq.commandTableOffset))
{
MMDPlaySeqCommand command;
while(file.ReadStruct(command))
{
MMDPlaySeqCommand command;
while(file.ReadStruct(command))
FileReader chunk = file.ReadChunk(command.extraSize);
ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(orderStart + command.offset);
if(command.offset == 0xFFFF || ord >= order.size())
break;
if(command.command == MMDPlaySeqCommand::kStop)
{
FileReader chunk = file.ReadChunk(command.extraSize);
ORDERINDEX ord = mpt::saturate_cast<ORDERINDEX>(orderStart + command.offset);
if(command.offset == 0xFFFF || ord >= order.size())
break;
if(command.command == MMDPlaySeqCommand::kStop)
{
order[ord] = order.GetInvalidPatIndex();
} else if(command.command == MMDPlaySeqCommand::kJump)
{
jumpTargets[ord] = chunk.ReadUint16BE();
order[ord] = order.GetIgnoreIndex();
}
order[ord] = order.GetInvalidPatIndex();
} else if(command.command == MMDPlaySeqCommand::kJump)
{
jumpTargets[ord] = chunk.ReadUint16BE();
order[ord] = order.GetIgnoreIndex();
}
}
}
@ -1148,7 +1194,38 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
m_songMessage.Read(file, expData.annoLength - 1, SongMessage::leAutodetect);
}
if(expData.mmdInfoOffset && file.Seek(expData.mmdInfoOffset))
#ifndef NO_VST
// Read MIDI messages
if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8))
{
uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(std::size(m_MidiCfg.szMidiZXXExt)));
file.Skip(6);
if(file.CanRead(numDumps * 4))
{
std::vector<uint32be> dumpPointers;
file.ReadVector(dumpPointers, numDumps);
for(uint16 dump = 0; dump < numDumps; dump++)
{
if(!file.Seek(dumpPointers[dump]) || !file.CanRead(sizeof(MMDDump)))
continue;
MMDDump dumpHeader;
file.ReadStruct(dumpHeader);
if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length))
continue;
auto &macro = m_MidiCfg.szMidiZXXExt[dump];
auto length = std::min(static_cast<size_t>(dumpHeader.length), std::size(macro) / 2u);
for(size_t i = 0; i < length; i++)
{
const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F;
macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A);
macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A);
}
}
}
}
#endif
if(expData.mmdInfoOffset && file.Seek(expData.mmdInfoOffset) && file.CanRead(12))
{
file.Skip(6); // Next info file (unused) + reserved
if(file.ReadUint16BE() == 1) // ASCII text

View File

@ -430,16 +430,17 @@ struct MidiChannelState
}
}
uint8 GetRPN() const
void SetRPNRelative(int8 value)
{
switch(rpn)
{
case 0: // Pitch Bend Range
return pitchBendRange;
pitchBendRange = static_cast<uint8>(std::clamp(pitchBendRange + value, 1, 0x7F));
break;
case 2: // Coarse Tune
return transpose;
transpose = mpt::saturate_cast<int8>(transpose + value);
break;
}
return 0;
}
};
@ -979,7 +980,7 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags)
case MIDIEvents::MIDICC_DataButtonincrement:
case MIDIEvents::MIDICC_DataButtondecrement:
midiChnStatus[midiCh].SetRPN(midiChnStatus[midiCh].GetRPN() + ((data1 == MIDIEvents::MIDICC_DataButtonincrement) ? 1 : -1));
midiChnStatus[midiCh].SetRPNRelative((data1 == MIDIEvents::MIDICC_DataButtonincrement) ? 1 : -1);
break;
case MIDIEvents::MIDICC_NonRegisteredParameter_Fine:
@ -1350,7 +1351,6 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags)
}
}
#endif // MODPLUG_TRACKER
return true;
}

View File

@ -963,10 +963,11 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
// - Scan patterns to check if file could be a NoiseTracker file in disguise.
// In this case, the parameter of Dxx commands needs to be ignored.
// - Use the same code to find notes that would be out-of-range on Amiga.
// - Detect 7-bit panning.
// - Detect 7-bit panning and whether 8xx / E8x commands should be interpreted as panning at all.
bool onlyAmigaNotes = true;
bool fix7BitPanning = false;
uint8 maxPanning = 0; // For detecting 8xx-as-sync
const uint8 ENABLE_MOD_PANNING_THRESHOLD = 0x30;
if(!isNoiseTracker)
{
bool leftPanning = false, extendedPanning = false; // For detecting 800-880 panning
@ -1003,7 +1004,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
}
}
}
fix7BitPanning = leftPanning && !extendedPanning;
fix7BitPanning = leftPanning && !extendedPanning && maxPanning >= ENABLE_MOD_PANNING_THRESHOLD;
}
file.Seek(1084);
@ -1140,7 +1141,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
m_playBehaviour.set(kMODOutOfRangeNoteDelay);
m_playBehaviour.set(kMODTempoOnSecondTick);
// Arbitrary threshold for deciding that 8xx effects are only used as sync markers
if(maxPanning < 0x30)
if(maxPanning < ENABLE_MOD_PANNING_THRESHOLD)
{
m_playBehaviour.set(kMODIgnorePanning);
if(fileHeader.restartPos != 0x7F)

View File

@ -311,4 +311,21 @@ void ModInstrument::Transpose(int8 amount)
}
uint8 ModInstrument::GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const
{
if(chn >= std::size(sndFile.m_PlayState.Chn))
return 0;
// For mapped channels, return their pattern channel, modulo 16 (because there are only 16 MIDI channels)
const ModChannel &channel = sndFile.m_PlayState.Chn[chn];
if(nMidiChannel == MidiMappedChannel)
return static_cast<uint8>((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u);
else if(HasValidMIDIChannel())
return (nMidiChannel - MidiFirstChannel) % 16u;
else
return 0;
}
OPENMPT_NAMESPACE_END

View File

@ -21,6 +21,8 @@
OPENMPT_NAMESPACE_BEGIN
class CSoundFile;
// Instrument Nodes
struct EnvelopeNode
{
@ -147,6 +149,7 @@ struct ModInstrument
void SetResonance(uint8 resonance, bool enable) { nIFR = std::min(resonance, uint8(0x7F)) | (enable ? 0x80 : 0x00); }
bool HasValidMIDIChannel() const { return (nMidiChannel >= 1 && nMidiChannel <= 17); }
uint8 GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const;
void SetTuning(CTuning *pT)
{

View File

@ -100,6 +100,21 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const
else
maxLength = Util::MaxValueOfType(maxLength);
LimitMax(sample.nLength, mpt::saturate_cast<SmpLength>(maxLength));
} else if(GetEncoding() == AMS)
{
if(fileSize <= 9)
return 0;
file.Skip(4); // Target sample size (we already know this)
SmpLength maxLength = std::min(file.ReadUint32LE(), mpt::saturate_cast<uint32>(fileSize));
file.SkipBack(8);
// In the best case, every byte triplet can decode to 255 bytes, which is a ratio of exactly 1:85
if(Util::MaxValueOfType(maxLength) / 85 >= maxLength)
maxLength *= 85;
else
maxLength = Util::MaxValueOfType(maxLength);
LimitMax(sample.nLength, maxLength / (m_bitdepth / 8u));
}
if(sample.nLength < 1)
@ -153,18 +168,23 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const
} else if(GetEncoding() == AMS && GetChannelFormat() == mono)
{
// AMS compressed samples
if(fileSize > 9)
{
file.Skip(4); // Target sample size (we already know this)
uint32 sourceSize = file.ReadUint32LE();
int8 packCharacter = file.ReadUint8();
bytesRead += 9;
FileReader::PinnedRawDataView packedDataView = file.ReadPinnedRawDataView(sourceSize);
LimitMax(sourceSize, mpt::saturate_cast<uint32>(packedDataView.size()));
bytesRead += sourceSize;
file.Skip(4); // Target sample size (we already know this)
uint32 sourceSize = file.ReadUint32LE();
int8 packCharacter = file.ReadUint8();
bytesRead += 9;
AMSUnpack(reinterpret_cast<const int8 *>(packedDataView.data()), packedDataView.size(), sample.samplev(), sample.GetSampleSizeInBytes(), packCharacter);
FileReader::PinnedRawDataView packedDataView = file.ReadPinnedRawDataView(sourceSize);
LimitMax(sourceSize, mpt::saturate_cast<uint32>(packedDataView.size()));
bytesRead += sourceSize;
AMSUnpack(reinterpret_cast<const int8 *>(packedDataView.data()), packedDataView.size(), sample.samplev(), sample.GetSampleSizeInBytes(), packCharacter);
if(sample.uFlags[CHN_16BIT] && !mpt::endian_is_little())
{
auto p = sample.sample16();
for(SmpLength length = sample.nLength; length != 0; length--, p++)
{
*p = mpt::bit_cast<int16le>(*p);
}
}
} else if(GetEncoding() == PTM8Dto16 && GetChannelFormat() == mono && GetBitDepth() == 16)
{

View File

@ -4983,7 +4983,22 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
{
// MIDI channel
isNibble = true;
data = GetBestMidiChannel(nChn);
data = 0xFF;
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted);
if(plug > 0 && plug <= MAX_MIXPLUGINS)
{
auto midiPlug = dynamic_cast<const IMidiPlugin *>(m_MixPlugins[plug - 1u].pMixPlugin);
if(midiPlug)
data = midiPlug->GetMidiChannel(nChn);
}
if(data == 0xFF)
{
// Fallback if no plugin was found
if(pIns)
data = pIns->GetMIDIChannel(*this, nChn);
else
data = 0;
}
} else if(macro[pos] == 'n')
{
// Last triggered note
@ -5836,7 +5851,7 @@ void CSoundFile::SetTempo(TEMPO param, bool setFromUI)
const CModSpecifications &specs = GetModSpecifications();
// Anything lower than the minimum tempo is considered to be a tempo slide
const TEMPO minTempo = (GetType() == MOD_TYPE_MDL) ? TEMPO(1, 0) : TEMPO(32, 0);
const TEMPO minTempo = (GetType() & (MOD_TYPE_MDL | MOD_TYPE_MED)) ? TEMPO(1, 0) : TEMPO(32, 0);
if(setFromUI)
{
@ -6263,31 +6278,6 @@ IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const
}
// Get the MIDI channel currently associated with a given tracker channel
uint8 CSoundFile::GetBestMidiChannel(CHANNELINDEX trackerChn) const
{
if(trackerChn >= std::size(m_PlayState.Chn))
{
return 0;
}
const ModChannel &chn = m_PlayState.Chn[trackerChn];
const ModInstrument *ins = chn.pModInstrument;
if(ins != nullptr)
{
if(ins->nMidiChannel == MidiMappedChannel)
{
// For mapped channels, return their pattern channel, modulo 16 (because there are only 16 MIDI channels)
return static_cast<uint8>((chn.nMasterChn ? (chn.nMasterChn - 1u) : trackerChn) % 16u);
} else if(ins->HasValidMIDIChannel())
{
return (ins->nMidiChannel - 1u) % 16u;
}
}
return 0;
}
#ifdef MODPLUG_TRACKER
void CSoundFile::HandlePatternTransitionEvents()
{

View File

@ -1170,7 +1170,6 @@ private:
public:
PLUGINDEX GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const;
uint8 GetBestMidiChannel(CHANNELINDEX nChn) const;
};

View File

@ -755,7 +755,13 @@ void IMidiPlugin::ApplyPitchWheelDepth(int32 &value, int8 pwd)
// Get the MIDI channel currently associated with a given tracker channel
uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const
{
return m_SndFile.GetBestMidiChannel(trackChannel);
if(trackChannel >= std::size(m_SndFile.m_PlayState.Chn))
return 0;
if(auto ins = m_SndFile.m_PlayState.Chn[trackChannel].pModInstrument; ins != nullptr)
return ins->GetMIDIChannel(m_SndFile, trackChannel);
else
return 0;
}

View File

@ -266,14 +266,14 @@ public:
void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override;
bool IsNotePlaying(uint32 note, CHANNELINDEX trackerChn) override;
// Get the MIDI channel currently associated with a given tracker channel
virtual uint8 GetMidiChannel(CHANNELINDEX trackChannel) const;
protected:
// Plugin wants to send MIDI to OpenMPT
virtual void ReceiveMidi(uint32 midiCode);
virtual void ReceiveSysex(mpt::const_byte_span sysex);
// Get the MIDI channel currently associated with a given tracker channel
virtual uint8 GetMidiChannel(CHANNELINDEX trackChannel) const;
// Converts a 14-bit MIDI pitch bend position to our internal pitch bend position representation
static constexpr int32 EncodePitchBendParam(int32 position) { return (position << vstPitchBendShift); }
// Converts the internal pitch bend position to a 14-bit MIDI pitch bend position