diff --git a/Frameworks/OpenMPT/OpenMPT/build/dist.mk b/Frameworks/OpenMPT/OpenMPT/build/dist.mk index 2d038bdac..231c97f64 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/dist.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/dist.mk @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h index ffe4e8be4..e000ef227 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h +++ b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h index f8c555fca..1fb0307e7 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h +++ b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h @@ -423,7 +423,7 @@ std::shared_ptr ReloadComponent() } -static inline mpt::PathString GetComponentPath() +inline mpt::PathString GetComponentPath() { return ComponentManager::Instance()->GetComponentPath(); } @@ -454,7 +454,7 @@ std::shared_ptr GetComponent() } -static inline mpt::PathString GetComponentPath() +inline mpt::PathString GetComponentPath() { return mpt::PathString(); } diff --git a/Frameworks/OpenMPT/OpenMPT/common/Endianness.h b/Frameworks/OpenMPT/OpenMPT/common/Endianness.h index b2a80612c..d4c0ccf70 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/Endianness.h +++ b/Frameworks/OpenMPT/OpenMPT/common/Endianness.h @@ -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 -static MPT_CONSTEXPR17_FUN std::array EndianEncode(T val) noexcept +MPT_CONSTEXPR17_FUN std::array EndianEncode(T val) noexcept { static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big); static_assert(std::numeric_limits::is_integer); @@ -325,7 +325,7 @@ static MPT_CONSTEXPR17_FUN std::array EndianEncode(T val) noexc } template -static MPT_CONSTEXPR17_FUN T EndianDecode(std::array data) noexcept +MPT_CONSTEXPR17_FUN T EndianDecode(std::array data) noexcept { static_assert(Tendian::endian == mpt::endian::little || Tendian::endian == mpt::endian::big); static_assert(std::numeric_limits::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::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::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::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::is_ieee754_binary64ne) { diff --git a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h index e6c3118a2..7569ce5ec 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h +++ b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h @@ -1348,7 +1348,7 @@ using MemoryFileReader = detail::FileReader; // Initialize file reader object with pointer to data and data length. -template static inline FileReader make_FileReader(mpt::span bytedata, const mpt::PathString *filename = nullptr) +template inline FileReader make_FileReader(mpt::span bytedata, const mpt::PathString *filename = nullptr) { return FileReader(mpt::byte_cast(bytedata), filename); } @@ -1356,7 +1356,7 @@ template static inline FileReader make_FileReader(mpt::span g_Enabled; -static inline bool IsEnabled() { return g_Enabled; } +inline bool IsEnabled() { return g_Enabled; } enum class Direction : int8 { diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h b/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h index 5b3eb3a24..4ca098427 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h @@ -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; } diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h b/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h index 1c8ad5099..19a2a342a 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h @@ -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; } diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h index 169dc5e0f..f971e61cb 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp index b8a8563f3..1d93485e0 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp @@ -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 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 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.h b/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.h index 2eb6a7c37..d8cf10006 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.h @@ -76,8 +76,8 @@ template auto ToString(const T & x) -> decltype(mpt::ToCharset(mpt: template 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 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); diff --git a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h index 9e0e119c1..3b2a02f09 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h +++ b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md index d8e3b424a..bfcc2a96f 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md @@ -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) diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.hpp index 7cc0a3a22..2f3fd8976 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.hpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.hpp @@ -14,8 +14,6 @@ #include "libopenmpt_impl.hpp" #include "libopenmpt_ext.hpp" -using namespace OpenMPT; - namespace openmpt { class module_ext_impl diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h index 5045bd800..5736522fd 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h @@ -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 */ diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk index 4c3613743..9627719c5 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk @@ -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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp index a09d1bde1..3e6f1e373 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp @@ -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 ¶m) { 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(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(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(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 chunks = chunkFile.ReadChunks(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 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(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(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 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(seqHeader.loopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow()); + PATTERNINDEX pat = Order()[seqLoopEnd]; + Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast(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(m_szNames[smp]); + const uint8 nameLength = (fileHeader.version < 2) ? 30 : chunk.ReadUint8(); + chunk.ReadString(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(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) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp index 23c72a15d..202b0a3aa 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp @@ -123,10 +123,9 @@ struct IMFInstrument if(smpNum) { - static_assert(mpt::array_size::size >= mpt::array_size::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]; } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp index ac1e88940..e7c4ea4f5 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp @@ -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(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(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(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(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({ 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 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(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(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(std::size(m_MidiCfg.szMidiZXXExt))); + file.Skip(6); + if(file.CanRead(numDumps * 4)) + { + std::vector 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 ¯o = m_MidiCfg.szMidiZXXExt[dump]; + auto length = std::min(static_cast(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 diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp index 4c3fe140a..7c7b4ef05 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp @@ -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(std::clamp(pitchBendRange + value, 1, 0x7F)); + break; case 2: // Coarse Tune - return transpose; + transpose = mpt::saturate_cast(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; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp index f909e9b60..79e318c8f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp @@ -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) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp index 6260ea5ed..ba087f258 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp @@ -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((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u); + else if(HasValidMIDIChannel()) + return (nMidiChannel - MidiFirstChannel) % 16u; + else + return 0; + +} + + OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h index b5f5a2a9d..c28f4caf9 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h @@ -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) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp index 671cad111..cf26f1cf9 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp @@ -100,6 +100,21 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const else maxLength = Util::MaxValueOfType(maxLength); LimitMax(sample.nLength, mpt::saturate_cast(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(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(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(packedDataView.data()), packedDataView.size(), sample.samplev(), sample.GetSampleSizeInBytes(), packCharacter); + FileReader::PinnedRawDataView packedDataView = file.ReadPinnedRawDataView(sourceSize); + LimitMax(sourceSize, mpt::saturate_cast(packedDataView.size())); + bytesRead += sourceSize; + + AMSUnpack(reinterpret_cast(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(*p); + } } } else if(GetEncoding() == PTM8Dto16 && GetChannelFormat() == mono && GetBitDepth() == 16) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp index d389e14b3..922f07b21 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp @@ -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(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((chn.nMasterChn ? (chn.nMasterChn - 1u) : trackerChn) % 16u); - } else if(ins->HasValidMIDIChannel()) - { - return (ins->nMidiChannel - 1u) % 16u; - } - } - return 0; -} - - #ifdef MODPLUG_TRACKER void CSoundFile::HandlePatternTransitionEvents() { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h index 739b013cf..6515fed6d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h @@ -1170,7 +1170,6 @@ private: public: PLUGINDEX GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; - uint8 GetBestMidiChannel(CHANNELINDEX nChn) const; }; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp index 981fb213a..43c323e62 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp @@ -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; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h index d8e6ac188..42b4abe06 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h @@ -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