Update libopenmpt to version 0.5.4
parent
b375f06faa
commit
24231ecdbb
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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) ?
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
#include "libopenmpt_impl.hpp"
|
||||
#include "libopenmpt_ext.hpp"
|
||||
|
||||
using namespace OpenMPT;
|
||||
|
||||
namespace openmpt {
|
||||
|
||||
class module_ext_impl
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<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)
|
||||
{
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ¯o = 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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -1170,7 +1170,6 @@ private:
|
|||
|
||||
public:
|
||||
PLUGINDEX GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const;
|
||||
uint8 GetBestMidiChannel(CHANNELINDEX nChn) const;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue