libOpenMPT: Updated to version 0.6.1

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
CQTexperiment
Christopher Snowhill 2022-01-30 15:19:56 -08:00
parent ebb9c5b4f1
commit f3ddd69f63
79 changed files with 1204 additions and 716 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors
Copyright (c) 1997-2003, Olivier Lapicque Copyright (c) 1997-2003, Olivier Lapicque
All rights reserved. All rights reserved.

View File

@ -264,6 +264,9 @@ endif
ifeq ($(UNAME_S),FreeBSD) ifeq ($(UNAME_S),FreeBSD)
HOST_FLAVOUR=FREEBSD HOST_FLAVOUR=FREEBSD
endif endif
ifeq ($(UNAME_S),OpenBSD)
HOST_FLAVOUR=OPENBSD
endif
ifeq ($(UNAME_S),Haiku) ifeq ($(UNAME_S),Haiku)
HOST_FLAVOUR=HAIKU HOST_FLAVOUR=HAIKU
endif endif

View File

@ -47,6 +47,14 @@ How to compile
OpenMPT for, as Visual Studio only builds one architecture configuration OpenMPT for, as Visual Studio only builds one architecture configuration
at a time. at a time.
Please note that we do not support building with a later Visual Studio
installation with an earlier compiler version. This is because, while
later Visual Studio versions allow installing earlier compilers to be
available via the later version's environment, in this configuration,
the earlier compiler will still use the later C and C++ runtime's
headers and implementation, which significantly increases the matrix of
possible configurations to test.
- OpenMPT requires the compile host system to be Windows 8.1 (or later) amd64, - OpenMPT requires the compile host system to be Windows 8.1 (or later) amd64,
or Windows 11 (or later) ARM64. or Windows 11 (or later) ARM64.

View File

@ -1,4 +1,4 @@
MPT_SVNVERSION=16293 MPT_SVNVERSION=16764
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.0 MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.1
MPT_SVNDATE=2021-12-23T14:50:28.728256Z MPT_SVNDATE=2022-01-30T16:49:19.812343Z

View File

@ -18,6 +18,12 @@ include build/make/config-clang.mk
NO_LTDL?=1 NO_LTDL?=1
NO_PORTAUDIOCPP?=1 NO_PORTAUDIOCPP?=1
else ifeq ($(HOST_FLAVOUR),OPENBSD)
NO_PORTAUDIOCPP?=1
NO_PULSEAUDIO?=1
include build/make/config-clang.mk
else ifeq ($(HOST_FLAVOUR),HAIKU) else ifeq ($(HOST_FLAVOUR),HAIKU)
# In Haiku x86 32bit (but not 64bit), # In Haiku x86 32bit (but not 64bit),

View File

@ -1,4 +1,6 @@
$(warning warning: CONFIG=generic is deprecated. Use CONFIG=standard instead.)
CC ?= cc CC ?= cc
CXX ?= c++ CXX ?= c++
LD ?= c++ LD ?= c++

View File

@ -1,2 +1,4 @@
$(warning warning: CONFIG=haiku is deprecated. The OS is auto-detected.)
include config-defaults.mk include config-defaults.mk

View File

@ -1,2 +1,4 @@
$(warning warning: CONFIG=macosx is deprecated. The OS is auto-detected.)
include config-defaults.mk include config-defaults.mk

View File

@ -10,8 +10,8 @@ CXXFLAGS += $(CXXFLAGS_STDCXX)
CFLAGS += $(CFLAGS_STDC) CFLAGS += $(CFLAGS_STDC)
CPPFLAGS += -DWIN32 -D_WIN32 -DWINVER=0x0410 -D_WIN32_WINDOWS=0x0410 -DMPT_BUILD_RETRO CPPFLAGS += -DWIN32 -D_WIN32 -DWINVER=0x0410 -D_WIN32_WINDOWS=0x0410 -DMPT_BUILD_RETRO
CXXFLAGS += -mconsole CXXFLAGS += -mconsole -mthreads
CFLAGS += -mconsole CFLAGS += -mconsole -mthreads
LDFLAGS += LDFLAGS +=
LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm
ARFLAGS := rcs ARFLAGS := rcs

View File

@ -10,8 +10,8 @@ CXXFLAGS += $(CXXFLAGS_STDCXX)
CFLAGS += $(CFLAGS_STDC) CFLAGS += $(CFLAGS_STDC)
CPPFLAGS += -DWIN32 -D_WIN32 CPPFLAGS += -DWIN32 -D_WIN32
CXXFLAGS += -municode -mconsole CXXFLAGS += -municode -mconsole -mthreads
CFLAGS += -municode -mconsole CFLAGS += -municode -mconsole -mthreads
LDFLAGS += LDFLAGS +=
LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm
ARFLAGS := rcs ARFLAGS := rcs

View File

@ -10,8 +10,8 @@ CXXFLAGS += $(CXXFLAGS_STDCXX)
CFLAGS += $(CFLAGS_STDC) CFLAGS += $(CFLAGS_STDC)
CPPFLAGS += -DWIN32 -D_WIN32 -DWIN64 -D_WIN64 CPPFLAGS += -DWIN32 -D_WIN32 -DWIN64 -D_WIN64
CXXFLAGS += -municode -mconsole CXXFLAGS += -municode -mconsole -mthreads
CFLAGS += -municode -mconsole CFLAGS += -municode -mconsole -mthreads
LDFLAGS += LDFLAGS +=
LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm
ARFLAGS := rcs ARFLAGS := rcs

View File

@ -10,8 +10,8 @@ CXXFLAGS += $(CXXFLAGS_STDCXX)
CFLAGS += $(CFLAGS_STDC) CFLAGS += $(CFLAGS_STDC)
CPPFLAGS += -DWIN32 -D_WIN32 -DWIN64 -D_WIN64 -DWINAPI_FAMILY=0x2 -D_WIN32_WINNT=0x0602 CPPFLAGS += -DWIN32 -D_WIN32 -DWIN64 -D_WIN64 -DWINAPI_FAMILY=0x2 -D_WIN32_WINNT=0x0602
CXXFLAGS += -municode -mconsole CXXFLAGS += -municode -mconsole -mthreads
CFLAGS += -municode -mconsole CFLAGS += -municode -mconsole -mthreads
LDFLAGS += LDFLAGS +=
LDLIBS += -lm -lole32 -lwinmm LDLIBS += -lm -lole32 -lwinmm
ARFLAGS := rcs ARFLAGS := rcs

View File

@ -10,8 +10,8 @@ CXXFLAGS += $(CXXFLAGS_STDCXX)
CFLAGS += $(CFLAGS_STDC) CFLAGS += $(CFLAGS_STDC)
CPPFLAGS += -DWIN32 -D_WIN32 -DWINAPI_FAMILY=0x2 -D_WIN32_WINNT=0x0602 CPPFLAGS += -DWIN32 -D_WIN32 -DWINAPI_FAMILY=0x2 -D_WIN32_WINNT=0x0602
CXXFLAGS += -municode -mconsole CXXFLAGS += -municode -mconsole -mthreads
CFLAGS += -municode -mconsole CFLAGS += -municode -mconsole -mthreads
LDFLAGS += LDFLAGS +=
LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm LDLIBS += -lm -lole32 -lrpcrt4 -lwinmm
ARFLAGS := rcs ARFLAGS := rcs

View File

@ -1,10 +1,10 @@
#pragma once #pragma once
#define OPENMPT_VERSION_SVNVERSION "16293" #define OPENMPT_VERSION_SVNVERSION "16764"
#define OPENMPT_VERSION_REVISION 16293 #define OPENMPT_VERSION_REVISION 16764
#define OPENMPT_VERSION_DIRTY 0 #define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0 #define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.0" #define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.1"
#define OPENMPT_VERSION_DATE "2021-12-23T14:50:28.728256Z" #define OPENMPT_VERSION_DATE "2022-01-30T16:49:19.812343Z"
#define OPENMPT_VERSION_IS_PACKAGE 1 #define OPENMPT_VERSION_IS_PACKAGE 1

View File

@ -49,23 +49,6 @@ mpt::ustring ToUString(const wchar_t * const & x) { return mpt::ToUnicode(x); }
#if defined(MPT_WITH_MFC) #if defined(MPT_WITH_MFC)
mpt::ustring ToUString(const CString & x) { return mpt::ToUnicode(x); } mpt::ustring ToUString(const CString & x) { return mpt::ToUnicode(x); }
#endif // MPT_WITH_MFC #endif // MPT_WITH_MFC
#if MPT_USTRING_MODE_WIDE
mpt::ustring ToUString(const bool & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const signed char & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const unsigned char & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const signed short & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const unsigned short & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const signed int & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const unsigned int & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const signed long & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const unsigned long & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const signed long long & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const unsigned long long & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const float & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const double & x) { return mpt::format_value_default<std::wstring>(x); }
mpt::ustring ToUString(const long double & x) { return mpt::format_value_default<std::wstring>(x); }
#endif
#if MPT_USTRING_MODE_UTF8
mpt::ustring ToUString(const bool & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const bool & x) { return mpt::format_value_default<mpt::ustring>(x); }
mpt::ustring ToUString(const signed char & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const signed char & x) { return mpt::format_value_default<mpt::ustring>(x); }
mpt::ustring ToUString(const unsigned char & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const unsigned char & x) { return mpt::format_value_default<mpt::ustring>(x); }
@ -80,7 +63,6 @@ mpt::ustring ToUString(const unsigned long long & x) { return mpt::format_value_
mpt::ustring ToUString(const float & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const float & x) { return mpt::format_value_default<mpt::ustring>(x); }
mpt::ustring ToUString(const double & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const double & x) { return mpt::format_value_default<mpt::ustring>(x); }
mpt::ustring ToUString(const long double & x) { return mpt::format_value_default<mpt::ustring>(x); } mpt::ustring ToUString(const long double & x) { return mpt::format_value_default<mpt::ustring>(x); }
#endif
#if MPT_WSTRING_FORMAT #if MPT_WSTRING_FORMAT
#if MPT_USTRING_MODE_UTF8 #if MPT_USTRING_MODE_UTF8
@ -122,23 +104,6 @@ std::string FormatValA(const float & x, const FormatSpec & f) { return mpt::form
std::string FormatValA(const double & x, const FormatSpec & f) { return mpt::format_simple<std::string>(x, f); } std::string FormatValA(const double & x, const FormatSpec & f) { return mpt::format_simple<std::string>(x, f); }
std::string FormatValA(const long double & x, const FormatSpec & f) { return mpt::format_simple<std::string>(x, f); } std::string FormatValA(const long double & x, const FormatSpec & f) { return mpt::format_simple<std::string>(x, f); }
#if MPT_USTRING_MODE_WIDE
mpt::ustring FormatValU(const bool & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const signed char & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const unsigned char & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const signed short & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const unsigned short & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const signed int & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const unsigned int & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const signed long & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const unsigned long & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const signed long long & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const unsigned long long & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const float & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const double & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
mpt::ustring FormatValU(const long double & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }
#endif
#if MPT_USTRING_MODE_UTF8
mpt::ustring FormatValU(const bool & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const bool & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
mpt::ustring FormatValU(const signed char & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const signed char & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
mpt::ustring FormatValU(const unsigned char & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const unsigned char & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
@ -153,7 +118,6 @@ mpt::ustring FormatValU(const unsigned long long & x, const FormatSpec & f) { re
mpt::ustring FormatValU(const float & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const float & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
mpt::ustring FormatValU(const double & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const double & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
mpt::ustring FormatValU(const long double & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); } mpt::ustring FormatValU(const long double & x, const FormatSpec & f) { return mpt::format_simple<mpt::ustring>(x, f); }
#endif
#if MPT_WSTRING_FORMAT #if MPT_WSTRING_FORMAT
std::wstring FormatValW(const bool & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); } std::wstring FormatValW(const bool & x, const FormatSpec & f) { return mpt::format_simple<std::wstring>(x, f); }

View File

@ -592,12 +592,12 @@ mpt::ustring GetFullCreditsString()
"libopenmpt (based on OpenMPT / Open ModPlug Tracker)\n" "libopenmpt (based on OpenMPT / Open ModPlug Tracker)\n"
#endif #endif
"\n" "\n"
"Copyright \xC2\xA9 2004-2021 OpenMPT Project Developers and Contributors\n" "Copyright \xC2\xA9 2004-2022 OpenMPT Project Developers and Contributors\n"
"Copyright \xC2\xA9 1997-2003 Olivier Lapicque\n" "Copyright \xC2\xA9 1997-2003 Olivier Lapicque\n"
"\n" "\n"
"Developers:\n" "Developers:\n"
"Johannes Schultz (2008-2021)\n" "Johannes Schultz (2008-2022)\n"
"J\xC3\xB6rn Heusipp (2012-2021)\n" "J\xC3\xB6rn Heusipp (2012-2022)\n"
"Ahti Lepp\xC3\xA4nen (2005-2011)\n" "Ahti Lepp\xC3\xA4nen (2005-2011)\n"
"Robin Fernandes (2004-2007)\n" "Robin Fernandes (2004-2007)\n"
"Sergiy Pylypenko (2007)\n" "Sergiy Pylypenko (2007)\n"
@ -792,7 +792,7 @@ mpt::ustring GetFullCreditsString()
mpt::ustring GetLicenseString() mpt::ustring GetLicenseString()
{ {
return MPT_UTF8( return MPT_UTF8(
"Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors" "\n" "Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors" "\n"
"Copyright (c) 1997-2003, Olivier Lapicque" "\n" "Copyright (c) 1997-2003, Olivier Lapicque" "\n"
"All rights reserved." "\n" "All rights reserved." "\n"
"" "\n" "" "\n"

View File

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

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors
Copyright (c) 1997-2003, Olivier Lapicque Copyright (c) 1997-2003, Olivier Lapicque
All rights reserved. All rights reserved.

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors
Copyright (c) 1997-2003, Olivier Lapicque Copyright (c) 1997-2003, Olivier Lapicque
All rights reserved. All rights reserved.

View File

@ -5,6 +5,35 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This For fully detailed change log, please see the source repository directly. This
is just a high-level summary. is just a high-level summary.
### libopenmpt 0.6.1 (2022-01-30)
* [**Bug**] Linking libmpg123 no longer fails on OpenBSD.
* [**Bug**] Possible hang with malformed DMF, DSM, MED, MUS, OKT and SymMOD
files containing 65536 or more patterns when destroying the module.
* [**Bug**] Avoid NaNs and infinite values with custom tunings and in the
I3DL2Reverb plugin.
* The letter "z" is now evaluated in fixed MIDI macros (Z80...ZFF) the same
way as in Impulse Tracker.
* MOD: Loosened VBlank timing heuristics so that "frame of mind" by Dascon
plays correctly.
* MOD: Validate the contents of "hidden" patterns beyond the end of the order
list when the file size matches the expected size when only taken "official"
patterns into account. This fixes Shofixti Ditty.mod from Star Control 2
while keeping other (partly broken) modules working.
* MED: Command 20 (reverse sample) is now only applied when it's next to a
note.
* S3M: Introducing the "Send OPL key-off when triggering notes" compatibility
setting broke retrigger for OPL notes again (they retriggered rather than
not retriggering).
* S3M: Retriggering a note no longer resets its pitch after a portamento.
* S3M: Partially implement retrigger behaviour for stopped notes in
SoundBlaster mode: Like in IT, it is not possible to retrigger a sample that
has already stopped playing.
* DIGI: Improve compatibility with E3x reverse sample command.
* DSym: Tempos < 32 were treated as tempo slides.
* SymMOD: Key-off command was not implemented properly.
### libopenmpt 0.6.0 (2021-12-23) ### libopenmpt 0.6.0 (2021-12-23)
* [**New**] `MUS` files from Psycho Pinball and Micro Machines 2 are now * [**New**] `MUS` files from Psycho Pinball and Micro Machines 2 are now
@ -14,6 +43,8 @@ is just a high-level summary.
* [**New**] `FMT` files created with Davey W Taylor's FM Tracker are now * [**New**] `FMT` files created with Davey W Taylor's FM Tracker are now
supported. supported.
* [**New**] `DSYM` files created with Digital Symphony are now supported. * [**New**] `DSYM` files created with Digital Symphony are now supported.
* [**New**] `STX` files (transitional format between Scream Tracker 2 and 3)
are now supported.
* [**New**] TakeTracker MODs with `TDZ1` to `TDZ3` magic bytes are now * [**New**] TakeTracker MODs with `TDZ1` to `TDZ3` magic bytes are now
supported. supported.
* [**New**] openmpt123: openmpt123 will now expand file wildcards passed on * [**New**] openmpt123: openmpt123 will now expand file wildcards passed on
@ -68,6 +99,7 @@ is just a high-level summary.
song does not restart from the beginning even if the repeat count is not 0. song does not restart from the beginning even if the repeat count is not 0.
* `openmpt::module::set_position_seconds()` accuracy has been improved for * `openmpt::module::set_position_seconds()` accuracy has been improved for
modules with pattern loops. modules with pattern loops.
* Samples played at the wrong volume when rendering modules in mono.
* IT: Portamentos in files with Linear Slides disabled are now more accurate. * IT: Portamentos in files with Linear Slides disabled are now more accurate.
* IT: Pitch/Pan Separation was affected by note-off commands, and wasn't reset * IT: Pitch/Pan Separation was affected by note-off commands, and wasn't reset
by panning commands like in Impulse Tracker. by panning commands like in Impulse Tracker.

View File

@ -11,6 +11,15 @@ Dependencies
* Supported compilers for building libopenmpt: * Supported compilers for building libopenmpt:
* **Microsoft Visual Studio 2017** or higher, running on a amd64 build * **Microsoft Visual Studio 2017** or higher, running on a amd64 build
system (other target systems are supported) system (other target systems are supported)
Please note that we do not support building with a later Visual Studio
installation with an earlier compiler version. This is because, while
later Visual Studio versions allow installing earlier compilers to be
available via the later version's environment, in this configuration,
the earlier compiler will still use the later C and C++ runtime's
headers and implementation, which significantly increases the matrix of
possible configurations to test.
* **GCC 8.1** or higher * **GCC 8.1** or higher
* **Clang 7** or higher * **Clang 7** or higher
* **MinGW-W64 8.1** or higher (it is recommended to preferably use * **MinGW-W64 8.1** or higher (it is recommended to preferably use

View File

@ -260,7 +260,7 @@ static void config( HWND hwndParent ) {
static void about( HWND hwndParent ) { static void about( HWND hwndParent ) {
std::ostringstream about; std::ostringstream about;
about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl; about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl;
about << " Copyright (c) 2013-2021 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl; about << " Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl;
about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl; about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl;
about << std::endl; about << std::endl;
about << openmpt::string::get( "contact" ) << std::endl; about << openmpt::string::get( "contact" ) << std::endl;

View File

@ -161,6 +161,8 @@ LIBOPENMPT_DEPRECATED static const int LIBOPENMPT_DEPRECATED_STRING_CONSTANT = 0
#else #else
#define LIBOPENMPT_DEPRECATED_STRING( str ) str #define LIBOPENMPT_DEPRECATED_STRING( str ) str
#endif #endif
#else
#define LIBOPENMPT_DEPRECATED_STRING( str ) str
#endif #endif

View File

@ -21,7 +21,7 @@
/*! \brief libopenmpt minor version number */ /*! \brief libopenmpt minor version number */
#define OPENMPT_API_VERSION_MINOR 6 #define OPENMPT_API_VERSION_MINOR 6
/*! \brief libopenmpt patch version number */ /*! \brief libopenmpt patch version number */
#define OPENMPT_API_VERSION_PATCH 0 #define OPENMPT_API_VERSION_PATCH 1
/*! \brief libopenmpt pre-release tag */ /*! \brief libopenmpt pre-release tag */
#define OPENMPT_API_VERSION_PREREL "" #define OPENMPT_API_VERSION_PREREL ""
/*! \brief libopenmpt pre-release flag */ /*! \brief libopenmpt pre-release flag */

View File

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

View File

@ -192,7 +192,7 @@ BEGIN
VALUE "FileDescription", VER_FILEDESC_STR VALUE "FileDescription", VER_FILEDESC_STR
VALUE "FileVersion", VER_FILEVERSION_STR VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", VER_FILENAME_STR VALUE "InternalName", VER_FILENAME_STR
VALUE "LegalCopyright", "Copyright © 2004-2020 OpenMPT Project Developers and Contributors, Copyright © 1997-2003 Olivier Lapicque" VALUE "LegalCopyright", "Copyright © 2004-2022 OpenMPT Project Developers and Contributors, Copyright © 1997-2003 Olivier Lapicque"
VALUE "OriginalFilename", VER_FILENAME_STR VALUE "OriginalFilename", VER_FILENAME_STR
VALUE "ProductName", "libopenmpt" VALUE "ProductName", "libopenmpt"
VALUE "ProductVersion", VER_FILEVERSION_STR VALUE "ProductVersion", VER_FILEVERSION_STR

View File

@ -519,7 +519,7 @@ static void clear_current_timeinfo() {
static void WINAPI openmpt_About( HWND win ) { static void WINAPI openmpt_About( HWND win ) {
std::ostringstream about; std::ostringstream about;
about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl; about << SHORT_TITLE << " version " << openmpt::string::get( "library_version" ) << " " << "(built " << openmpt::string::get( "build" ) << ")" << std::endl;
about << " Copyright (c) 2013-2021 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl; about << " Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors (https://lib.openmpt.org/)" << std::endl;
about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl; about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl;
about << std::endl; about << std::endl;
about << openmpt::string::get( "contact" ) << std::endl; about << openmpt::string::get( "contact" ) << std::endl;

View File

@ -8,7 +8,7 @@
*/ */
static const char * const license = static const char * const license =
"Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors" "\n" "Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors" "\n"
"Copyright (c) 1997-2003, Olivier Lapicque" "\n" "Copyright (c) 1997-2003, Olivier Lapicque" "\n"
"All rights reserved." "\n" "All rights reserved." "\n"
"" "\n" "" "\n"
@ -464,7 +464,7 @@ static std::string seconds_to_string( double time ) {
static void show_info( std::ostream & log, bool verbose ) { static void show_info( std::ostream & log, bool verbose ) {
log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << ", libopenmpt " << openmpt::string::get( "library_version" ) << " (" << "OpenMPT " << openmpt::string::get( "core_version" ) << ")" << std::endl; log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << ", libopenmpt " << openmpt::string::get( "library_version" ) << " (" << "OpenMPT " << openmpt::string::get( "core_version" ) << ")" << std::endl;
log << "Copyright (c) 2013-2021 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl; log << "Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl;
if ( !verbose ) { if ( !verbose ) {
log << std::endl; log << std::endl;
return; return;
@ -541,7 +541,7 @@ static void show_info( std::ostream & log, bool verbose ) {
static void show_man_version( textout & log ) { static void show_man_version( textout & log ) {
log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << std::endl; log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << std::endl;
log << std::endl; log << std::endl;
log << "Copyright (c) 2013-2021 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl; log << "Copyright (c) 2013-2022 OpenMPT Project Developers and Contributors <https://lib.openmpt.org/>" << std::endl;
} }
static void show_short_version( textout & log ) { static void show_short_version( textout & log ) {

View File

@ -1047,6 +1047,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
instruments.clear(); instruments.clear();
DLSENVELOPE dlsEnv; DLSENVELOPE dlsEnv;
int32 instrAttenuation = 0; int32 instrAttenuation = 0;
int16 instrFinetune = 0;
// Default Envelope Values // Default Envelope Values
dlsEnv.wVolAttack = 0; dlsEnv.wVolAttack = 0;
dlsEnv.wVolDecay = 0; dlsEnv.wVolDecay = 0;
@ -1069,6 +1070,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
continue; continue;
for(const auto &gen : generators) for(const auto &gen : generators)
{ {
const int16 value = static_cast<int16>(gen.genAmount);
switch(gen.sfGenOper) switch(gen.sfGenOper)
{ {
case SF2_GEN_ATTACKVOLENV: case SF2_GEN_ATTACKVOLENV:
@ -1096,7 +1098,13 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
keyRange = gen.genAmount; keyRange = gen.genAmount;
break; break;
case SF2_GEN_ATTENUATION: case SF2_GEN_ATTENUATION:
instrAttenuation = -static_cast<int16>(gen.genAmount); instrAttenuation = -value;
break;
case SF2_GEN_COARSETUNE:
instrFinetune += value * 128;
break;
case SF2_GEN_FINETUNE:
instrFinetune += static_cast<int16>(Util::muldiv(static_cast<int8>(value), 128, 100));
break; break;
#if 0 #if 0
default: default:
@ -1128,7 +1136,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
DLSREGION globalZone{}; DLSREGION globalZone{};
globalZone.uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note globalZone.uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note
globalZone.tuning = 100; globalZone.tuning = 100;
globalZone.sFineTune = 0; globalZone.sFineTune = instrFinetune;
globalZone.nWaveLink = Util::MaxValueOfType(globalZone.nWaveLink); globalZone.nWaveLink = Util::MaxValueOfType(globalZone.nWaveLink);
if(keyRange != 0xFFFF) if(keyRange != 0xFFFF)
{ {
@ -1187,7 +1195,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
} }
break; break;
case SF2_GEN_UNITYNOTE: case SF2_GEN_UNITYNOTE:
if (value < 128) rgn.uUnityNote = (uint8)value; if (value < 128) rgn.uUnityNote = static_cast<uint8>(value);
break; break;
case SF2_GEN_ATTACKVOLENV: case SF2_GEN_ATTACKVOLENV:
pDlsEnv->wVolAttack = SF2TimeToDLS(gen.genAmount); pDlsEnv->wVolAttack = SF2TimeToDLS(gen.genAmount);
@ -1583,7 +1591,7 @@ bool CDLSBank::ExtractWaveForm(uint32 nIns, uint32 nRgn, std::vector<uint8> &wav
if(nRgn >= dlsIns.Regions.size()) if(nRgn >= dlsIns.Regions.size())
{ {
#ifdef DLSBANK_LOG #ifdef DLSBANK_LOG
MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("invalid waveform region: nIns={} nRgn={} pSmp->nRegions={}")(nIns, nRgn, dlsIns.nRegions)); MPT_LOG_GLOBAL(LogDebug, "DLSBANK", MPT_UFORMAT("invalid waveform region: nIns={} nRgn={} pSmp->nRegions={}")(nIns, nRgn, dlsIns.Regions.size()));
#endif #endif
return false; return false;
} }

View File

@ -111,6 +111,9 @@ protected:
public: public:
CDLSBank(); CDLSBank();
bool operator==(const CDLSBank &other) const noexcept { return !mpt::PathString::CompareNoCase(m_szFileName, other.m_szFileName); }
static bool IsDLSBank(const mpt::PathString &filename); static bool IsDLSBank(const mpt::PathString &filename);
static uint32 MakeMelodicCode(uint32 bank, uint32 instr) { return ((bank << 16) | (instr));} static uint32 MakeMelodicCode(uint32 bank, uint32 instr) { return ((bank << 16) | (instr));}
static uint32 MakeDrumCode(uint32 rgn, uint32 instr) { return (0x80000000 | (rgn << 16) | (instr));} static uint32 MakeDrumCode(uint32 rgn, uint32 instr) { return (0x80000000 | (rgn << 16) | (instr));}

View File

@ -345,7 +345,7 @@ void CSoundFile::CreateStereoMix(int count)
//Look for plugins associated with this implicit tracker channel. //Look for plugins associated with this implicit tracker channel.
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes); PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState, m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes);
if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr) if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr)
{ {
@ -417,11 +417,16 @@ void CSoundFile::CreateStereoMix(int count)
else if(m_SamplePlayLengths != nullptr) else if(m_SamplePlayLengths != nullptr)
{ {
// Detecting the longest play time for each sample for optimization // Detecting the longest play time for each sample for optimization
SmpLength pos = chn.position.GetUInt();
chn.position += chn.increment * nSmpCount; chn.position += chn.increment * nSmpCount;
if(!chn.increment.IsNegative())
{
pos = chn.position.GetUInt();
}
size_t smp = std::distance(static_cast<const ModSample*>(static_cast<std::decay<decltype(Samples)>::type>(Samples)), chn.pModSample); size_t smp = std::distance(static_cast<const ModSample*>(static_cast<std::decay<decltype(Samples)>::type>(Samples)), chn.pModSample);
if(smp < m_SamplePlayLengths->size()) if(smp < m_SamplePlayLengths->size())
{ {
(*m_SamplePlayLengths)[smp] = std::max((*m_SamplePlayLengths)[smp], chn.position.GetUInt()); (*m_SamplePlayLengths)[smp] = std::max((*m_SamplePlayLengths)[smp], pos);
} }
} }
#endif #endif

View File

@ -535,6 +535,7 @@ uint32 ITSample::ConvertToMPT(ModSample &mptSmp) const
} }
mptSmp.Initialize(MOD_TYPE_IT); mptSmp.Initialize(MOD_TYPE_IT);
mptSmp.SetDefaultCuePoints(); // For old IT/MPTM files
mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename); mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
// Volume / Panning // Volume / Panning

View File

@ -624,10 +624,10 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags)
for(uint32 i = 0; i < 32; i++) for(uint32 i = 0; i < 32; i++)
{ {
uint32 param = (i * 127u) / 32u; uint32 param = (i * 127u) / 32u;
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i ]) = MPT_AFORMAT("F0F080{}")(mpt::afmt::HEX0<2>(param)); m_MidiCfg.Zxx[i ] = MPT_AFORMAT("F0F080{}")(mpt::afmt::HEX0<2>(param));
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 32]) = MPT_AFORMAT("F0F081{}")(mpt::afmt::HEX0<2>(param)); m_MidiCfg.Zxx[i + 32] = MPT_AFORMAT("F0F081{}")(mpt::afmt::HEX0<2>(param));
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 64]) = MPT_AFORMAT("F0F082{}")(mpt::afmt::HEX0<2>(param)); m_MidiCfg.Zxx[i + 64] = MPT_AFORMAT("F0F082{}")(mpt::afmt::HEX0<2>(param));
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i + 96]) = MPT_AFORMAT("F0F083{}")(mpt::afmt::HEX0<2>(param)); m_MidiCfg.Zxx[i + 96] = MPT_AFORMAT("F0F083{}")(mpt::afmt::HEX0<2>(param));
} }
} }
#endif // NO_PLUGINS #endif // NO_PLUGINS

View File

@ -44,12 +44,11 @@ static void ReadDIGIPatternEntry(FileReader &file, ModCommand &m)
switch(m.param & 0xF0) switch(m.param & 0xF0)
{ {
case 0x30: case 0x30:
// E30 / E31: Play sample backwards (with some weird parameters that we won't support for now) // E3x: Play sample backwards (E30 stops sample when it reaches the beginning, any other value plays it from the beginning including regular loop)
if(m.param <= 0x31) // The play direction is also reset if a new note is played on the other channel linked to this channel.
{ // The behaviour is rather broken when there is no note next to the ommand.
m.command = CMD_S3MCMDEX; m.command = CMD_DIGIREVERSESAMPLE;
m.param = 0x9F; m.param &= 0x0F;
}
break; break;
case 0x40: case 0x40:
// E40: Stop playing sample // E40: Stop playing sample

View File

@ -414,6 +414,9 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags)
{ {
m->command = CMD_TEMPO; m->command = CMD_TEMPO;
m->param = mpt::saturate_cast<ModCommand::PARAM>(std::max(8, param + 4) / 8); m->param = mpt::saturate_cast<ModCommand::PARAM>(std::max(8, param + 4) / 8);
#ifdef MODPLUG_TRACKER
m->param = std::max(m->param, ModCommand::PARAM(0x20));
#endif
} else } else
{ {
m->command = CMD_NONE; m->command = CMD_NONE;

View File

@ -1393,8 +1393,8 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c
memcpy(itHeader.id, "IMPM", 4); memcpy(itHeader.id, "IMPM", 4);
mpt::String::WriteBuf(mpt::String::nullTerminated, itHeader.songname) = m_songName; mpt::String::WriteBuf(mpt::String::nullTerminated, itHeader.songname) = m_songName;
itHeader.highlight_minor = (uint8)std::min(m_nDefaultRowsPerBeat, ROWINDEX(uint8_max)); itHeader.highlight_minor = mpt::saturate_cast<uint8>(m_nDefaultRowsPerBeat);
itHeader.highlight_major = (uint8)std::min(m_nDefaultRowsPerMeasure, ROWINDEX(uint8_max)); itHeader.highlight_major = mpt::saturate_cast<uint8>(m_nDefaultRowsPerMeasure);
if(GetType() == MOD_TYPE_MPT) if(GetType() == MOD_TYPE_MPT)
{ {
@ -2470,7 +2470,10 @@ void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel
ModSample &sample = Samples[smp]; ModSample &sample = Samples[smp];
for(auto &cue : sample.cues) for(auto &cue : sample.cues)
{ {
cue = chunk.ReadUint32LE(); if(chunk.CanRead(4))
cue = chunk.ReadUint32LE();
else
cue = MAX_SAMPLE_LENGTH;
} }
} }
} }

View File

@ -38,19 +38,39 @@ OPENMPT_NAMESPACE_BEGIN
struct ITPModCommand struct ITPModCommand
{ {
uint8le note; uint8 note;
uint8le instr; uint8 instr;
uint8le volcmd; uint8 volcmd;
uint8le command; uint8 command;
uint8le vol; uint8 vol;
uint8le param; uint8 param;
operator ModCommand() const operator ModCommand() const
{ {
static constexpr VolumeCommand ITPVolCmds[] =
{
VOLCMD_NONE, VOLCMD_VOLUME, VOLCMD_PANNING, VOLCMD_VOLSLIDEUP,
VOLCMD_VOLSLIDEDOWN, VOLCMD_FINEVOLUP, VOLCMD_FINEVOLDOWN, VOLCMD_VIBRATOSPEED,
VOLCMD_VIBRATODEPTH, VOLCMD_PANSLIDELEFT, VOLCMD_PANSLIDERIGHT, VOLCMD_TONEPORTAMENTO,
VOLCMD_PORTAUP, VOLCMD_PORTADOWN, VOLCMD_PLAYCONTROL, VOLCMD_OFFSET,
};
static constexpr EffectCommand ITPCommands[] =
{
CMD_NONE, CMD_ARPEGGIO, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN,
CMD_TONEPORTAMENTO, CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL,
CMD_TREMOLO, CMD_PANNING8, CMD_OFFSET, CMD_VOLUMESLIDE,
CMD_POSITIONJUMP, CMD_VOLUME, CMD_PATTERNBREAK, CMD_RETRIG,
CMD_SPEED, CMD_TEMPO, CMD_TREMOR, CMD_MODCMDEX,
CMD_S3MCMDEX, CMD_CHANNELVOLUME, CMD_CHANNELVOLSLIDE, CMD_GLOBALVOLUME,
CMD_GLOBALVOLSLIDE, CMD_KEYOFF, CMD_FINEVIBRATO, CMD_PANBRELLO,
CMD_XFINEPORTAUPDOWN, CMD_PANNINGSLIDE, CMD_SETENVPOSITION, CMD_MIDI,
CMD_SMOOTHMIDI, CMD_DELAYCUT, CMD_XPARAM,
};
ModCommand result; ModCommand result;
result.note = (ModCommand::IsNote(note) || ModCommand::IsSpecialNote(note)) ? static_cast<ModCommand::NOTE>(note.get()) : static_cast<ModCommand::NOTE>(NOTE_NONE); result.note = (ModCommand::IsNote(note) || ModCommand::IsSpecialNote(note)) ? static_cast<ModCommand::NOTE>(note) : static_cast<ModCommand::NOTE>(NOTE_NONE);
result.instr = instr; result.instr = instr;
result.command = (command < MAX_EFFECTS) ? static_cast<EffectCommand>(command.get()) : CMD_NONE; result.volcmd = (volcmd < std::size(ITPVolCmds)) ? ITPVolCmds[volcmd] : VOLCMD_NONE;
result.volcmd = (volcmd < MAX_VOLCMDS) ? static_cast<VolumeCommand>(volcmd.get()) : VOLCMD_NONE; result.command = (command < std::size(ITPCommands)) ? ITPCommands[command] : CMD_NONE;
result.vol = vol; result.vol = vol;
result.param = param; result.param = param;
return result; return result;
@ -281,8 +301,8 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags)
if(pat < numNamedPats) if(pat < numNamedPats)
{ {
char patName[32]; char patName[32];
pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen); if(pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen))
Patterns[pat].SetName(patName); Patterns[pat].SetName(patName);
} }
// Pattern data // Pattern data
@ -379,6 +399,11 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags)
} }
} }
for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
{
Samples[smp].SetDefaultCuePoints();
}
// Song extensions // Song extensions
if(code == MagicBE("MPTS")) if(code == MagicBE("MPTS"))
{ {

View File

@ -548,8 +548,11 @@ static void ConvertMEDEffect(ModCommand &m, bool is8ch, bool bpmMode, uint8 rows
case 0x20: // Reverse sample + skip samples case 0x20: // Reverse sample + skip samples
if(m.param == 0 && m.vol == 0) if(m.param == 0 && m.vol == 0)
{ {
m.command = CMD_S3MCMDEX; if(m.IsNote())
m.param = 0x9F; {
m.command = CMD_S3MCMDEX;
m.param = 0x9F;
}
} else } else
{ {
// Skip given number of samples // Skip given number of samples
@ -1041,7 +1044,7 @@ 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) // 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(); m_MidiCfg.ClearZxxMacros();
strcpy(m_MidiCfg.szMidiSFXExt[0], "Cc z"); m_MidiCfg.SFx[0] = "Cc z";
file.Rewind(); file.Rewind();
PATTERNINDEX basePattern = 0; PATTERNINDEX basePattern = 0;
@ -1201,7 +1204,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
// Read MIDI messages // Read MIDI messages
if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8)) if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8))
{ {
uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(std::size(m_MidiCfg.szMidiZXXExt))); uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(m_MidiCfg.Zxx.size()));
file.Skip(6); file.Skip(6);
if(file.CanRead(numDumps * 4)) if(file.CanRead(numDumps * 4))
{ {
@ -1215,14 +1218,15 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(dumpHeader); file.ReadStruct(dumpHeader);
if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length)) if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length))
continue; continue;
auto &macro = m_MidiCfg.szMidiZXXExt[dump]; std::array<char, kMacroLength> macro{};
auto length = std::min(static_cast<size_t>(dumpHeader.length), std::size(macro) / 2u); auto length = std::min(static_cast<size_t>(dumpHeader.length), macro.size() / 2u);
for(size_t i = 0; i < length; i++) for(size_t i = 0; i < length; i++)
{ {
const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F; const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F;
macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A); macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A);
macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A); macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A);
} }
m_MidiCfg.Zxx[dump] = std::string_view{macro.data(), length * 2};
} }
} }
} }

View File

@ -303,6 +303,7 @@ struct MO3Sample
void ConvertToMPT(ModSample &mptSmp, MODTYPE type, bool frequencyIsHertz) const void ConvertToMPT(ModSample &mptSmp, MODTYPE type, bool frequencyIsHertz) const
{ {
mptSmp.Initialize(); mptSmp.Initialize();
mptSmp.SetDefaultCuePoints();
if(type & (MOD_TYPE_IT | MOD_TYPE_S3M)) if(type & (MOD_TYPE_IT | MOD_TYPE_S3M))
{ {
if(frequencyIsHertz) if(frequencyIsHertz)
@ -907,16 +908,16 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
for(uint32 i = 0; i < 16; i++) for(uint32 i = 0; i < 16; i++)
{ {
if(fileHeader.sfxMacros[i]) if(fileHeader.sfxMacros[i])
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1)); m_MidiCfg.SFx[i] = MPT_AFORMAT("F0F0{}z")(mpt::afmt::HEX0<2>(fileHeader.sfxMacros[i] - 1));
else else
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = ""; m_MidiCfg.SFx[i] = "";
} }
for(uint32 i = 0; i < 128; i++) for(uint32 i = 0; i < 128; i++)
{ {
if(fileHeader.fixedMacros[i][1]) if(fileHeader.fixedMacros[i][1])
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i]) = MPT_AFORMAT("F0F0{}{}")(mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][1] - 1), mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][0].get())); m_MidiCfg.Zxx[i] = MPT_AFORMAT("F0F0{}{}")(mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][1] - 1), mpt::afmt::HEX0<2>(fileHeader.fixedMacros[i][0].get()));
else else
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiZXXExt[i]) = ""; m_MidiCfg.Zxx[i] = "";
} }
} }

View File

@ -209,7 +209,7 @@ void CSoundFile::ModSaveCommand(uint8 &command, uint8 &param, bool toXM, bool co
struct MODFileHeader struct MODFileHeader
{ {
uint8be numOrders; uint8be numOrders;
uint8be restartPos; uint8be restartPos; // Tempo (early SoundTracker) or restart position (only PC trackers?)
uint8be orderList[128]; uint8be orderList[128];
}; };
@ -585,6 +585,7 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
const size_t patternStartOffset = file.GetPosition(); const size_t patternStartOffset = file.GetPosition();
const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset; const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset;
const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * numChannels * 256;
if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == (file.GetLength() & ~1)) if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == (file.GetLength() & ~1))
{ {
@ -595,8 +596,9 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
if(ValidateMODPatternData(file, 16, true)) if(ValidateMODPatternData(file, 16, true))
numChannels = 8; numChannels = 8;
file.Seek(patternStartOffset); file.Seek(patternStartOffset);
} else if(numPatterns != officialPatterns && validateHiddenPatterns) } else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == file.GetLength()))
{ {
// 15-sample SoundTracker specifics:
// Fix SoundTracker modules where "hidden" patterns should be ignored. // Fix SoundTracker modules where "hidden" patterns should be ignored.
// razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b) // razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b)
// and captain_fizz.mod (MD5 55bd89fe5a8e345df65438dbfc2df94e, SHA1 9e0e8b7dc67939885435ea8d3ff4be7704207a43) // and captain_fizz.mod (MD5 55bd89fe5a8e345df65438dbfc2df94e, SHA1 9e0e8b7dc67939885435ea8d3ff4be7704207a43)
@ -609,28 +611,21 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
// only play correctly if we ignore the hidden patterns. // only play correctly if we ignore the hidden patterns.
// Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data. // Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data.
// If that is the case, we assume it's part of the sample data and only consider the "official" patterns. // If that is the case, we assume it's part of the sample data and only consider the "official" patterns.
file.Seek(patternStartOffset + officialPatterns * 1024);
// 31-sample NoiseTracker / ProTracker specifics:
// Interestingly, (broken) variants of the ProTracker modules
// "killing butterfly" (MD5 bd676358b1dbb40d40f25435e845cf6b, SHA1 9df4ae21214ff753802756b616a0cafaeced8021),
// "quartex" by Reflex (MD5 35526bef0fb21cb96394838d94c14bab, SHA1 116756c68c7b6598dcfbad75a043477fcc54c96c),
// seem to have the "correct" file size when only taking the "official" patterns into account, but they only play
// correctly when also loading the inofficial patterns.
// On the other hand, "Shofixti Ditty.mod" from Star Control 2 (MD5 62b7b0819123400e4d5a7813eef7fc7d, SHA1 8330cd595c61f51c37a3b6f2a8559cf3fcaaa6e8)
// doesn't sound correct when taking the second "inofficial" pattern into account.
file.Seek(patternStartOffset + officialPatterns * numChannels * 256);
if(!ValidateMODPatternData(file, 64, true)) if(!ValidateMODPatternData(file, 64, true))
numPatterns = officialPatterns; numPatterns = officialPatterns;
file.Seek(patternStartOffset); file.Seek(patternStartOffset);
} }
#ifdef MPT_BUILD_DEBUG
// Check if the "hidden" patterns in the order list are actually real, i.e. if they are saved in the file.
// OpenMPT did this check in the past, but no other tracker appears to do this.
// Interestingly, (broken) variants of the ProTracker modules
// "killing butterfly" (MD5 bd676358b1dbb40d40f25435e845cf6b, SHA1 9df4ae21214ff753802756b616a0cafaeced8021),
// "quartex" by Reflex (MD5 35526bef0fb21cb96394838d94c14bab, SHA1 116756c68c7b6598dcfbad75a043477fcc54c96c),
// seem to have the "correct" file size when only taking the "official" patterns into account, but they only play
// correctly when also loading the inofficial patterns.
// See also the above check for ambiguities with SoundTracker modules.
// Keep this assertion in the code to find potential other broken MODs.
if(numPatterns != officialPatterns && sizeWithoutPatterns + officialPatterns * numChannels * 256 == file.GetLength())
{
MPT_ASSERT(false);
//numPatterns = officialPatterns;
} else
#endif
if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == file.GetLength()) if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == file.GetLength())
{ {
// Even those illegal pattern indexes (> 128) appear to be valid... What a weird file! // Even those illegal pattern indexes (> 128) appear to be valid... What a weird file!
@ -779,7 +774,7 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result)
CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMOD(MemoryFileReader file, const uint64 *pfilesize) CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMOD(MemoryFileReader file, const uint64 *pfilesize)
{ {
if(!file.CanRead(1080 + 4)) if(!file.LengthIsAtLeast(1080 + 4))
{ {
return ProbeWantMoreData; return ProbeWantMoreData;
} }
@ -1290,7 +1285,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
} }
#endif // MPT_EXTERNAL_SAMPLES || MPT_BUILD_FUZZER #endif // MPT_EXTERNAL_SAMPLES || MPT_BUILD_FUZZER
// Fix VBlank MODs. Arbitrary threshold: 9 minutes (enough for Guitar Slinger...). // Fix VBlank MODs. Arbitrary threshold: 8 minutes (enough for "frame of mind" by Dascon...).
// Basically, this just converts all tempo commands into speed commands // Basically, this just converts all tempo commands into speed commands
// for MODs which are supposed to have VBlank timing (instead of CIA timing). // for MODs which are supposed to have VBlank timing (instead of CIA timing).
// There is no perfect way to do this, since both MOD types look the same, // There is no perfect way to do this, since both MOD types look the same,
@ -1303,12 +1298,12 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
if(isMdKd && hasTempoCommands && !definitelyCIA) if(isMdKd && hasTempoCommands && !definitelyCIA)
{ {
const double songTime = GetLength(eNoAdjust).front().duration; const double songTime = GetLength(eNoAdjust).front().duration;
if(songTime >= 540.0) if(songTime >= 480.0)
{ {
m_playBehaviour.set(kMODVBlankTiming); m_playBehaviour.set(kMODVBlankTiming);
if(GetLength(eNoAdjust, GetLengthTarget(songTime)).front().targetReached) if(GetLength(eNoAdjust, GetLengthTarget(songTime)).front().targetReached)
{ {
// This just makes things worse, song is at least as long as in CIA mode (e.g. in "Stary Hallway" by Neurodancer) // This just makes things worse, song is at least as long as in CIA mode
// Obviously we should keep using CIA timing then... // Obviously we should keep using CIA timing then...
m_playBehaviour.reset(kMODVBlankTiming); m_playBehaviour.reset(kMODVBlankTiming);
} else } else

View File

@ -1027,6 +1027,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags)
{ {
ModSample &mptSmp = Samples[i + 1]; ModSample &mptSmp = Samples[i + 1];
mptSmp.Initialize(MOD_TYPE_IT); mptSmp.Initialize(MOD_TYPE_IT);
mptSmp.SetDefaultCuePoints();
MT2Sample sampleHeader; MT2Sample sampleHeader;
sampleChunk.ReadStruct(sampleHeader); sampleChunk.ReadStruct(sampleHeader);

View File

@ -534,6 +534,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
const bool useGUS = gusAddresses > 1; const bool useGUS = gusAddresses > 1;
m_playBehaviour.set(kST3PortaSampleChange, useGUS); m_playBehaviour.set(kST3PortaSampleChange, useGUS);
m_playBehaviour.set(kST3SampleSwap, !useGUS); m_playBehaviour.set(kST3SampleSwap, !useGUS);
m_playBehaviour.set(kITShortSampleRetrig, !useGUS); // Only half the truth but close enough for now
m_modFormat.madeWithTracker += useGUS ? UL_(" (GUS)") : UL_(" (SB)"); m_modFormat.madeWithTracker += useGUS ? UL_(" (GUS)") : UL_(" (SB)");
// ST3's GUS driver doesn't use this value. Ignoring it fixes the balance between FM and PCM samples (e.g. in Rotagilla by Manwe) // ST3's GUS driver doesn't use this value. Ignoring it fixes the balance between FM and PCM samples (e.g. in Rotagilla by Manwe)
if(useGUS) if(useGUS)

View File

@ -621,8 +621,10 @@ struct SymInstrument
} }
// This must be applied last because some sample processors are time-dependent and Symphonie would be doing this during playback instead // This must be applied last because some sample processors are time-dependent and Symphonie would be doing this during playback instead
mptSmp.RemoveAllCuePoints();
if(type == Sustain && numRepetitions > 0 && loopLen > 0) if(type == Sustain && numRepetitions > 0 && loopLen > 0)
{ {
mptSmp.cues[0] = loopStart + loopLen * (numRepetitions + 1u);
mptSmp.nSustainStart = loopStart; // This is of purely informative value and not used for playback mptSmp.nSustainStart = loopStart; // This is of purely informative value and not used for playback
mptSmp.nSustainEnd = loopStart + loopLen; mptSmp.nSustainEnd = loopStart + loopLen;
@ -955,18 +957,18 @@ static bool ConvertDSP(const SymEvent event, MIDIMacroConfigData::Macro &macro,
const uint8 reso = static_cast<uint8>(std::min(127, event.inst * 127 / 185)); const uint8 reso = static_cast<uint8>(std::min(127, event.inst * 127 / 185));
if(type == 1) // lowpass filter if(type == 1) // lowpass filter
mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F000{} F0F001{} F0F00200")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); macro = MPT_AFORMAT("F0F000{} F0F001{} F0F00200")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso));
else if(type == 2) // highpass filter else if(type == 2) // highpass filter
mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F000{} F0F001{} F0F00210")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso)); macro = MPT_AFORMAT("F0F000{} F0F001{} F0F00210")(mpt::afmt::HEX0<2>(cutoff), mpt::afmt::HEX0<2>(reso));
else // no filter or unsupported filter type else // no filter or unsupported filter type
mpt::String::WriteAutoBuf(macro) = "F0F0007F F0F00100"; macro = "F0F0007F F0F00100";
return true; return true;
} else if(event.command == SymEvent::DSPEcho) } else if(event.command == SymEvent::DSPEcho)
{ {
const uint8 type = (event.note < 5) ? event.note : 0; const uint8 type = (event.note < 5) ? event.note : 0;
const uint8 length = (event.param < 128) ? event.param : 127; const uint8 length = (event.param < 128) ? event.param : 127;
const uint8 feedback = (event.inst < 128) ? event.inst : 127; const uint8 feedback = (event.inst < 128) ? event.inst : 127;
mpt::String::WriteAutoBuf(macro) = MPT_AFORMAT("F0F080{} F0F081{} F0F082{}")(mpt::afmt::HEX0<2>(type), mpt::afmt::HEX0<2>(length), mpt::afmt::HEX0<2>(feedback)); macro = MPT_AFORMAT("F0F080{} F0F081{} F0F082{}")(mpt::afmt::HEX0<2>(type), mpt::afmt::HEX0<2>(length), mpt::afmt::HEX0<2>(feedback));
return true; return true;
} else if(event.command == SymEvent::DSPDelay) } else if(event.command == SymEvent::DSPDelay)
{ {
@ -1218,7 +1220,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
static_assert(MAX_SAMPLES >= MAX_INSTRUMENTS); static_assert(MAX_SAMPLES >= MAX_INSTRUMENTS);
m_nSamples = std::max(m_nSamples, m_nInstruments); m_nSamples = std::max(m_nSamples, m_nInstruments);
// Supporting this is probably rather useless, as the paths will always be Amiga paths. We just take the filename without path for now. // Supporting this is probably rather useless, as the paths will always be full Amiga paths. We just take the filename without path for now.
if(externalSamples) if(externalSamples)
{ {
#ifdef MPT_EXTERNAL_SAMPLES #ifdef MPT_EXTERNAL_SAMPLES
@ -1452,7 +1454,8 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
break; break;
case SymEvent::KeyOff: case SymEvent::KeyOff:
// TODO needs note if(m.note == NOTE_NONE)
m.note = chnState.lastNote;
m.volcmd = VOLCMD_OFFSET; m.volcmd = VOLCMD_OFFSET;
m.vol = 1; m.vol = 1;
break; break;
@ -1642,10 +1645,10 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
{ {
m.command = CMD_MIDI; m.command = CMD_MIDI;
m.param = macroMap[event]; m.param = macroMap[event];
} else if(macroMap.size() < std::size(m_MidiCfg.szMidiZXXExt)) } else if(macroMap.size() < m_MidiCfg.Zxx.size())
{ {
uint8 param = static_cast<uint8>(macroMap.size()); uint8 param = static_cast<uint8>(macroMap.size());
if(ConvertDSP(event, m_MidiCfg.szMidiZXXExt[param], *this)) if(ConvertDSP(event, m_MidiCfg.Zxx[param], *this))
{ {
m.command = CMD_MIDI; m.command = CMD_MIDI;
m.param = macroMap[event] = 0x80 | param; m.param = macroMap[event] = 0x80 | param;
@ -1925,8 +1928,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
{ {
InitChannel(chn); InitChannel(chn);
ChnSettings[chn].nPan = (chn & 1) ? 256 : 0; ChnSettings[chn].nPan = (chn & 1) ? 256 : 0;
if(useDSP) ChnSettings[chn].nMixPlugin = useDSP ? 1 : 0; // For MIDI macros controlling the echo DSP
ChnSettings[chn].nMixPlugin = 1; // For MIDI macros controlling the echo DSP
} }
m_modFormat.formatName = U_("Symphonie"); m_modFormat.formatName = U_("Symphonie");

View File

@ -9,10 +9,8 @@
#include "stdafx.h" #include "stdafx.h"
#include "../soundlib/MIDIEvents.h"
#include "MIDIMacros.h" #include "MIDIMacros.h"
#include "../common/mptStringBuffer.h" #include "../soundlib/MIDIEvents.h"
#include "../common/misc_util.h"
#ifdef MODPLUG_TRACKER #ifdef MODPLUG_TRACKER
#include "Sndfile.h" #include "Sndfile.h"
@ -23,24 +21,25 @@ OPENMPT_NAMESPACE_BEGIN
ParameteredMacro MIDIMacroConfig::GetParameteredMacroType(uint32 macroIndex) const ParameteredMacro MIDIMacroConfig::GetParameteredMacroType(uint32 macroIndex) const
{ {
const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); const std::string macro = SFx[macroIndex].NormalizedString();
for(uint32 i = 0; i < kSFxMax; i++) for(uint32 i = 0; i < kSFxMax; i++)
{ {
ParameteredMacro sfx = static_cast<ParameteredMacro>(i); ParameteredMacro sfx = static_cast<ParameteredMacro>(i);
if(sfx != kSFxCustom) if(sfx != kSFxCustom)
{ {
if(macro.compare(CreateParameteredMacro(sfx)) == 0) return sfx; if(macro == CreateParameteredMacro(sfx))
return sfx;
} }
} }
// Special macros with additional "parameter": // Special macros with additional "parameter":
if (macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_start)) >= 0 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_end)) <= 0 && macro.size() == 5) if(macro.size() == 5 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_start)) >= 0 && macro.compare(CreateParameteredMacro(kSFxCC, MIDIEvents::MIDICC_end)) <= 0)
return kSFxCC; return kSFxCC;
if (macro.compare(CreateParameteredMacro(kSFxPlugParam, 0)) >= 0 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0x17F)) <= 0 && macro.size() == 7) if(macro.size() == 7 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0)) >= 0 && macro.compare(CreateParameteredMacro(kSFxPlugParam, 0x17F)) <= 0)
return kSFxPlugParam; return kSFxPlugParam;
return kSFxCustom; // custom / unknown return kSFxCustom; // custom / unknown
} }
@ -54,19 +53,10 @@ FixedMacro MIDIMacroConfig::GetFixedMacroType() const
if(zxx != kZxxCustom) if(zxx != kZxxCustom)
{ {
// Prepare macro pattern to compare // Prepare macro pattern to compare
Macro macros[128]; decltype(Zxx) fixedMacros{};
CreateFixedMacro(macros, zxx); CreateFixedMacro(fixedMacros, zxx);
if(fixedMacros == Zxx)
bool found = true; return zxx;
for(uint32 j = 0; j < 128; j++)
{
if(strncmp(macros[j], szMidiZXXExt[j], MACRO_LENGTH))
{
found = false;
break;
}
}
if(found) return zxx;
} }
} }
return kZxxCustom; // Custom setup return kZxxCustom; // Custom setup
@ -77,17 +67,17 @@ void MIDIMacroConfig::CreateParameteredMacro(Macro &parameteredMacro, Parametere
{ {
switch(macroType) switch(macroType)
{ {
case kSFxUnused: mpt::String::WriteAutoBuf(parameteredMacro) = ""; break; case kSFxUnused: parameteredMacro = ""; break;
case kSFxCutoff: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F000z"; break; case kSFxCutoff: parameteredMacro = "F0F000z"; break;
case kSFxReso: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F001z"; break; case kSFxReso: parameteredMacro = "F0F001z"; break;
case kSFxFltMode: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F002z"; break; case kSFxFltMode: parameteredMacro = "F0F002z"; break;
case kSFxDryWet: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F003z"; break; case kSFxDryWet: parameteredMacro = "F0F003z"; break;
case kSFxCC: mpt::String::WriteAutoBuf(parameteredMacro) = MPT_AFORMAT("Bc{}z")(mpt::afmt::HEX0<2>(subType & 0x7F)); break; case kSFxCC: parameteredMacro = MPT_AFORMAT("Bc{}z")(mpt::afmt::HEX0<2>(subType & 0x7F)); break;
case kSFxPlugParam: mpt::String::WriteAutoBuf(parameteredMacro) = MPT_AFORMAT("F0F{}z")(mpt::afmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break; case kSFxPlugParam: parameteredMacro = MPT_AFORMAT("F0F{}z")(mpt::afmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break;
case kSFxChannelAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Dcz"; break; case kSFxChannelAT: parameteredMacro = "Dcz"; break;
case kSFxPolyAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Acnz"; break; case kSFxPolyAT: parameteredMacro = "Acnz"; break;
case kSFxPitch: mpt::String::WriteAutoBuf(parameteredMacro) = "Ec00z"; break; case kSFxPitch: parameteredMacro = "Ec00z"; break;
case kSFxProgChange: mpt::String::WriteAutoBuf(parameteredMacro) = "Ccz"; break; case kSFxProgChange: parameteredMacro = "Ccz"; break;
case kSFxCustom: case kSFxCustom:
default: default:
MPT_ASSERT_NOTREACHED(); MPT_ASSERT_NOTREACHED();
@ -98,59 +88,59 @@ void MIDIMacroConfig::CreateParameteredMacro(Macro &parameteredMacro, Parametere
std::string MIDIMacroConfig::CreateParameteredMacro(ParameteredMacro macroType, int subType) const std::string MIDIMacroConfig::CreateParameteredMacro(ParameteredMacro macroType, int subType) const
{ {
Macro parameteredMacro; Macro parameteredMacro{};
CreateParameteredMacro(parameteredMacro, macroType, subType); CreateParameteredMacro(parameteredMacro, macroType, subType);
return mpt::String::ReadAutoBuf(parameteredMacro); return parameteredMacro;
} }
// Create Zxx (Z80 - ZFF) from preset // Create Zxx (Z80 - ZFF) from preset
void MIDIMacroConfig::CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const void MIDIMacroConfig::CreateFixedMacro(std::array<Macro, kZxxMacros> &fixedMacros, FixedMacro macroType) const
{ {
for(uint32 i = 0; i < 128; i++) for(uint32 i = 0; i < kZxxMacros; i++)
{ {
uint32 param = i; uint32 param = i;
switch(macroType) switch(macroType)
{ {
case kZxxUnused: case kZxxUnused:
mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; fixedMacros[i] = "";
break; break;
case kZxxReso4Bit: case kZxxReso4Bit:
param = i * 8; param = i * 8;
if(i < 16) if(i < 16)
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param));
else else
mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; fixedMacros[i] = "";
break; break;
case kZxxReso7Bit: case kZxxReso7Bit:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxCutoff: case kZxxCutoff:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F000{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F000{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxFltMode: case kZxxFltMode:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxResoFltMode: case kZxxResoFltMode:
param = (i & 0x0F) * 8; param = (i & 0x0F) * 8;
if(i < 16) if(i < 16)
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F001{}")(mpt::afmt::HEX0<2>(param));
else if(i < 32) else if(i < 32)
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param));
else else
mpt::String::WriteAutoBuf(fixedMacros[i]) = ""; fixedMacros[i] = "";
break; break;
case kZxxChannelAT: case kZxxChannelAT:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Dc{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("Dc{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxPolyAT: case kZxxPolyAT:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Acn{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("Acn{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxPitch: case kZxxPitch:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Ec00{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("Ec00{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxProgChange: case kZxxProgChange:
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("Cc{}")(mpt::afmt::HEX0<2>(param)); fixedMacros[i] = MPT_AFORMAT("Cc{}")(mpt::afmt::HEX0<2>(param));
break; break;
case kZxxCustom: case kZxxCustom:
default: default:
@ -161,19 +151,14 @@ void MIDIMacroConfig::CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro mac
} }
#ifdef MODPLUG_TRACKER
bool MIDIMacroConfig::operator== (const MIDIMacroConfig &other) const bool MIDIMacroConfig::operator== (const MIDIMacroConfig &other) const
{ {
for(auto left = begin(), right = other.begin(); left != end(); left++, right++) return std::equal(begin(), end(), other.begin());
{
if(strncmp(*left, *right, MACRO_LENGTH))
return false;
}
return true;
} }
#ifdef MODPLUG_TRACKER
// Returns macro description including plugin parameter / MIDI CC information // Returns macro description including plugin parameter / MIDI CC information
CString MIDIMacroConfig::GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin) const CString MIDIMacroConfig::GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin) const
{ {
@ -262,7 +247,7 @@ CString MIDIMacroConfig::GetFixedMacroName(FixedMacro macroType) const
int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const
{ {
const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); const std::string macro = SFx[macroIndex].NormalizedString();
int code = 0; int code = 0;
const char *param = macro.c_str(); const char *param = macro.c_str();
@ -281,7 +266,7 @@ int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const
int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) const int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) const
{ {
const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]); const std::string macro = SFx[macroIndex].NormalizedString();
int code = 0; int code = 0;
const char *param = macro.c_str(); const char *param = macro.c_str();
@ -297,7 +282,7 @@ int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) const
int MIDIMacroConfig::FindMacroForParam(PlugParamIndex param) const int MIDIMacroConfig::FindMacroForParam(PlugParamIndex param) const
{ {
for(int macroIndex = 0; macroIndex < NUM_MACROS; macroIndex++) for(int macroIndex = 0; macroIndex < kSFxMacros; macroIndex++)
{ {
if(GetParameteredMacroType(macroIndex) == kSFxPlugParam && MacroToPlugParam(macroIndex) == param) if(GetParameteredMacroType(macroIndex) == kSFxPlugParam && MacroToPlugParam(macroIndex) == param)
{ {
@ -314,41 +299,20 @@ int MIDIMacroConfig::FindMacroForParam(PlugParamIndex param) const
// i.e. the configuration that is assumed when loading a file that has no macros embedded. // i.e. the configuration that is assumed when loading a file that has no macros embedded.
bool MIDIMacroConfig::IsMacroDefaultSetupUsed() const bool MIDIMacroConfig::IsMacroDefaultSetupUsed() const
{ {
const MIDIMacroConfig defaultConfig; return *this == MIDIMacroConfig{};
// TODO - Global macros (currently not checked because they are not editable)
// SF0: Z00-Z7F controls cutoff, all other parametered macros are unused
for(uint32 i = 0; i < NUM_MACROS; i++)
{
if(GetParameteredMacroType(i) != defaultConfig.GetParameteredMacroType(i))
{
return false;
}
}
// Z80-Z8F controls resonance
if(GetFixedMacroType() != defaultConfig.GetFixedMacroType())
{
return false;
}
return true;
} }
// Reset MIDI macro config to default values. // Reset MIDI macro config to default values.
void MIDIMacroConfig::Reset() void MIDIMacroConfig::Reset()
{ {
MemsetZero(szMidiGlb); std::fill(begin(), end(), Macro{});
MemsetZero(szMidiSFXExt);
MemsetZero(szMidiZXXExt);
strcpy(szMidiGlb[MIDIOUT_START], "FF"); Global[MIDIOUT_START] = "FF";
strcpy(szMidiGlb[MIDIOUT_STOP], "FC"); Global[MIDIOUT_STOP] = "FC";
strcpy(szMidiGlb[MIDIOUT_NOTEON], "9c n v"); Global[MIDIOUT_NOTEON] = "9c n v";
strcpy(szMidiGlb[MIDIOUT_NOTEOFF], "9c n 0"); Global[MIDIOUT_NOTEOFF] = "9c n 0";
strcpy(szMidiGlb[MIDIOUT_PROGRAM], "Cc p"); Global[MIDIOUT_PROGRAM] = "Cc p";
// SF0: Z00-Z7F controls cutoff // SF0: Z00-Z7F controls cutoff
CreateParameteredMacro(0, kSFxCutoff); CreateParameteredMacro(0, kSFxCutoff);
// Z80-Z8F controls resonance // Z80-Z8F controls resonance
@ -359,8 +323,8 @@ void MIDIMacroConfig::Reset()
// Clear all Zxx macros so that they do nothing. // Clear all Zxx macros so that they do nothing.
void MIDIMacroConfig::ClearZxxMacros() void MIDIMacroConfig::ClearZxxMacros()
{ {
MemsetZero(szMidiSFXExt); std::fill(SFx.begin(), SFx.end(), Macro{});
MemsetZero(szMidiZXXExt); std::fill(Zxx.begin(), Zxx.end(), Macro{});
} }
@ -369,27 +333,7 @@ void MIDIMacroConfig::Sanitize()
{ {
for(auto &macro : *this) for(auto &macro : *this)
{ {
macro[MACRO_LENGTH - 1] = '\0'; macro.Sanitize();
std::fill(std::find(std::begin(macro), std::end(macro), '\0'), std::end(macro), '\0');
}
}
// Helper function for UpgradeMacros()
void MIDIMacroConfig::UpgradeMacroString(Macro &macro) const
{
for(auto &c : macro)
{
if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants
{
c = c - 'a' + 'A';
} else if(c == 'K' || c == 'k') // Channel was K or k
{
c = 'c';
} else if(c == 'X' || c == 'x' || c == 'Y' || c == 'y') // Those were pointless
{
c = 'z';
}
} }
} }
@ -399,15 +343,15 @@ void MIDIMacroConfig::UpgradeMacros()
{ {
for(auto &macro : *this) for(auto &macro : *this)
{ {
UpgradeMacroString(macro); macro.UpgradeLegacyMacro();
} }
} }
// Normalize by removing blanks and other unwanted characters from macro strings for internal usage. // Normalize by removing blanks and other unwanted characters from macro strings for internal usage.
std::string MIDIMacroConfig::GetSafeMacro(const Macro &macro) const std::string MIDIMacroConfig::Macro::NormalizedString() const
{ {
std::string sanitizedMacro = macro; std::string sanitizedMacro = *this;
std::string::size_type pos; std::string::size_type pos;
while((pos = sanitizedMacro.find_first_not_of("0123456789ABCDEFabchmnopsuvxyz")) != std::string::npos) while((pos = sanitizedMacro.find_first_not_of("0123456789ABCDEFabchmnopsuvxyz")) != std::string::npos)
@ -419,4 +363,35 @@ std::string MIDIMacroConfig::GetSafeMacro(const Macro &macro) const
} }
void MIDIMacroConfig::Macro::Sanitize() noexcept
{
m_data.back() = '\0';
const auto length = Length();
std::fill(m_data.begin() + length, m_data.end(), '\0');
for(size_t i = 0; i < length; i++)
{
if(m_data[i] < 32 || m_data[i] >= 127)
m_data[i] = ' ';
}
}
void MIDIMacroConfig::Macro::UpgradeLegacyMacro() noexcept
{
for(auto &c : m_data)
{
if(c >= 'a' && c <= 'f') // Both A-F and a-f were treated as hex constants
{
c = c - 'a' + 'A';
} else if(c == 'K' || c == 'k') // Channel was K or k
{
c = 'c';
} else if(c == 'X' || c == 'x' || c == 'Y' || c == 'y') // Those were pointless
{
c = 'z';
}
}
}
OPENMPT_NAMESPACE_END OPENMPT_NAMESPACE_END

View File

@ -18,8 +18,10 @@ OPENMPT_NAMESPACE_BEGIN
enum enum
{ {
NUM_MACROS = 16, // number of parametered macros kGlobalMacros = 9, // Number of global macros
MACRO_LENGTH = 32, // max number of chars per macro kSFxMacros = 16, // Number of parametered macros
kZxxMacros = 128, // Number of fixed macros
kMacroLength = 32, // Max number of chars per macro
}; };
OPENMPT_NAMESPACE_END OPENMPT_NAMESPACE_END
@ -70,7 +72,7 @@ enum FixedMacro
// Global macro types // Global macro types
enum enum GlobalMacro
{ {
MIDIOUT_START = 0, MIDIOUT_START = 0,
MIDIOUT_STOP, MIDIOUT_STOP,
@ -86,19 +88,74 @@ enum
struct MIDIMacroConfigData struct MIDIMacroConfigData
{ {
typedef char Macro[MACRO_LENGTH]; struct Macro
// encoding is ASCII {
Macro szMidiGlb[9]; // Global MIDI macros public:
Macro szMidiSFXExt[16]; // Parametric MIDI macros Macro &operator=(const Macro &other) = default;
Macro szMidiZXXExt[128]; // Fixed MIDI macros Macro &operator=(const std::string_view other) noexcept
{
const size_t copyLength = std::min({m_data.size() - 1u, other.size(), other.find('\0')});
std::copy(other.begin(), other.begin() + copyLength, m_data.begin());
m_data[copyLength] = '\0';
Sanitize();
return *this;
}
Macro *begin() { return std::begin(szMidiGlb); } bool operator==(const Macro &other) const noexcept
const Macro *begin() const { return std::begin(szMidiGlb); } {
Macro *end() { return std::end(szMidiZXXExt); } return m_data == other.m_data; // Don't care about data past null-terminator as operator= and Sanitize() ensure there is no data behind it.
const Macro *end() const { return std::end(szMidiZXXExt); } }
bool operator!=(const Macro &other) const noexcept
{
return !(*this == other);
}
operator mpt::span<const char>() const noexcept
{
return {m_data.data(), Length()};
}
operator std::string_view() const noexcept
{
return {m_data.data(), Length()};
}
operator std::string() const
{
return {m_data.data(), Length()};
}
MPT_CONSTEXPR20_FUN size_t Length() const noexcept
{
return static_cast<size_t>(std::distance(m_data.begin(), std::find(m_data.begin(), m_data.end(), '\0')));
}
MPT_CONSTEXPR20_FUN void Clear() noexcept
{
m_data.fill('\0');
}
// Remove blanks and other unwanted characters from macro strings for internal usage.
std::string NormalizedString() const;
void Sanitize() noexcept;
void UpgradeLegacyMacro() noexcept;
private:
std::array<char, kMacroLength> m_data;
};
std::array<Macro, kGlobalMacros> Global;
std::array<Macro, kSFxMacros> SFx; // Parametered macros for Z00...Z7F
std::array<Macro, kZxxMacros> Zxx; // Fixed macros Z80...ZFF
constexpr Macro *begin() noexcept {return Global.data(); }
constexpr const Macro *begin() const noexcept { return Global.data(); }
constexpr Macro *end() noexcept { return Zxx.data() + Zxx.size(); }
constexpr const Macro *end() const noexcept { return Zxx.data() + Zxx.size(); }
}; };
MPT_BINARY_STRUCT(MIDIMacroConfigData, 4896) // this is directly written to files, so the size must be correct! // This is directly written to files, so the size must be correct!
MPT_BINARY_STRUCT(MIDIMacroConfigData::Macro, 32)
MPT_BINARY_STRUCT(MIDIMacroConfigData, 4896)
class MIDIMacroConfig : public MIDIMacroConfigData class MIDIMacroConfig : public MIDIMacroConfigData
{ {
@ -117,22 +174,23 @@ protected:
public: public:
void CreateParameteredMacro(uint32 macroIndex, ParameteredMacro macroType, int subType = 0) void CreateParameteredMacro(uint32 macroIndex, ParameteredMacro macroType, int subType = 0)
{ {
CreateParameteredMacro(szMidiSFXExt[macroIndex], macroType, subType); if(macroIndex < std::size(SFx))
CreateParameteredMacro(SFx[macroIndex], macroType, subType);
} }
std::string CreateParameteredMacro(ParameteredMacro macroType, int subType = 0) const; std::string CreateParameteredMacro(ParameteredMacro macroType, int subType = 0) const;
protected: protected:
void CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const; void CreateFixedMacro(std::array<Macro, kZxxMacros> &fixedMacros, FixedMacro macroType) const;
public: public:
void CreateFixedMacro(FixedMacro macroType) void CreateFixedMacro(FixedMacro macroType)
{ {
CreateFixedMacro(szMidiZXXExt, macroType); CreateFixedMacro(Zxx, macroType);
} }
#ifdef MODPLUG_TRACKER bool operator==(const MIDIMacroConfig &other) const;
bool operator!=(const MIDIMacroConfig &other) const { return !(*this == other); }
bool operator== (const MIDIMacroConfig &other) const; #ifdef MODPLUG_TRACKER
bool operator!= (const MIDIMacroConfig &other) const { return !(*this == other); }
// Translate macro type or macro string to macro name // Translate macro type or macro string to macro name
CString GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin = nullptr) const; CString GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin = nullptr) const;
@ -162,15 +220,6 @@ public:
// Fix old-format (not conforming to IT's MIDI macro definitions) MIDI config strings. // Fix old-format (not conforming to IT's MIDI macro definitions) MIDI config strings.
void UpgradeMacros(); void UpgradeMacros();
protected:
// Helper function for FixMacroFormat()
void UpgradeMacroString(Macro &macro) const;
// Remove blanks and other unwanted characters from macro strings for internal usage.
std::string GetSafeMacro(const Macro &macro) const;
}; };
static_assert(sizeof(MIDIMacroConfig) == sizeof(MIDIMacroConfigData)); // this is directly written to files, so the size must be correct! static_assert(sizeof(MIDIMacroConfig) == sizeof(MIDIMacroConfigData)); // this is directly written to files, so the size must be correct!

View File

@ -314,13 +314,9 @@ void ModInstrument::Transpose(int8 amount)
} }
uint8 ModInstrument::GetMIDIChannel(const CSoundFile &sndFile, CHANNELINDEX chn) const uint8 ModInstrument::GetMIDIChannel(const ModChannel &channel, 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) // 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) if(nMidiChannel == MidiMappedChannel)
return static_cast<uint8>((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u); return static_cast<uint8>((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u);
else if(HasValidMIDIChannel()) else if(HasValidMIDIChannel())

View File

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

View File

@ -148,7 +148,7 @@ void ModSample::Initialize(MODTYPE type)
rootNote = 0; rootNote = 0;
filename = ""; filename = "";
SetDefaultCuePoints(); RemoveAllCuePoints();
} }
@ -504,16 +504,29 @@ void ModSample::Transpose(double amount)
} }
// Check if the sample has any valid cue points
bool ModSample::HasAnyCuePoints() const
{
if(uFlags[CHN_ADLIB])
return false;
for(auto pt : cues)
{
if(pt < nLength)
return true;
}
return false;
}
// Check if the sample's cue points are the default cue point set. // Check if the sample's cue points are the default cue point set.
bool ModSample::HasCustomCuePoints() const bool ModSample::HasCustomCuePoints() const
{ {
if(!uFlags[CHN_ADLIB]) if(uFlags[CHN_ADLIB])
return false;
for(SmpLength i = 0; i < std::size(cues); i++)
{ {
for(SmpLength i = 0; i < std::size(cues); i++) if(cues[i] != (i + 1) << 11)
{ return true;
if(cues[i] != (i + 1) << 11)
return true;
}
} }
return false; return false;
} }
@ -539,6 +552,13 @@ void ModSample::Set16BitCuePoints()
} }
void ModSample::RemoveAllCuePoints()
{
if(!uFlags[CHN_ADLIB])
cues.fill(MAX_SAMPLE_LENGTH);
}
void ModSample::SetAdlib(bool enable, OPLPatch patch) void ModSample::SetAdlib(bool enable, OPLPatch patch)
{ {
if(!enable && uFlags[CHN_ADLIB]) if(!enable && uFlags[CHN_ADLIB])

View File

@ -160,11 +160,14 @@ struct ModSample
// Transpose the sample by amount specified in octaves (i.e. amount=1 transposes one octave up) // Transpose the sample by amount specified in octaves (i.e. amount=1 transposes one octave up)
void Transpose(double amount); void Transpose(double amount);
// Check if the sample has any valid cue points
bool HasAnyCuePoints() const;
// Check if the sample's cue points are the default cue point set. // Check if the sample's cue points are the default cue point set.
bool HasCustomCuePoints() const; bool HasCustomCuePoints() const;
void SetDefaultCuePoints(); void SetDefaultCuePoints();
// Set cue points so that they are suitable for regular offset command extension // Set cue points so that they are suitable for regular offset command extension
void Set16BitCuePoints(); void Set16BitCuePoints();
void RemoveAllCuePoints();
void SetAdlib(bool enable, OPLPatch patch = OPLPatch{{}}); void SetAdlib(bool enable, OPLPatch patch = OPLPatch{{}});
}; };

View File

@ -35,6 +35,20 @@
#include <stdlib.h> #include <stdlib.h>
#include <sys/types.h> #include <sys/types.h>
#if MPT_OS_OPENBSD
// This is kind-of a hack.
// See <https://sourceforge.net/p/mpg123/bugs/330/>.
#if MPT_COMPILER_CLANG
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-id-macro"
#endif
#ifdef _FILE_OFFSET_BITS
#undef _FILE_OFFSET_BITS
#endif
#if MPT_COMPILER_CLANG
#pragma clang diagnostic pop
#endif
#endif
#include <mpg123.h> #include <mpg123.h>
#endif #endif

View File

@ -64,10 +64,6 @@ public:
uint8 vol = 0xFF; uint8 vol = 0xFF;
}; };
#ifndef NO_PLUGINS
typedef std::map<std::pair<ModCommand::INSTR, uint16>, uint16> PlugParamMap;
PlugParamMap plugParams;
#endif
std::vector<ChnSettings> chnSettings; std::vector<ChnSettings> chnSettings;
double elapsedTime; double elapsedTime;
static constexpr uint32 IGNORE_CHANNEL = uint32_max; static constexpr uint32 IGNORE_CHANNEL = uint32_max;
@ -81,9 +77,8 @@ public:
void Reset() void Reset()
{ {
#ifndef NO_PLUGINS if(state->m_midiMacroEvaluationResults)
plugParams.clear(); state->m_midiMacroEvaluationResults.emplace();
#endif
elapsedTime = 0.0; elapsedTime = 0.0;
state->m_lTotalSampleCount = 0; state->m_lTotalSampleCount = 0;
state->m_nMusicSpeed = sndFile.m_nDefaultSpeed; state->m_nMusicSpeed = sndFile.m_nDefaultSpeed;
@ -295,6 +290,9 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
} }
} }
if(adjustMode & eAdjust)
playState.m_midiMacroEvaluationResults.emplace();
// If samples are being synced, force them to resync if tick duration changes // If samples are being synced, force them to resync if tick duration changes
uint32 oldTickDuration = 0; uint32 oldTickDuration = 0;
bool breakToRow = false; bool breakToRow = false;
@ -469,9 +467,9 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
if(p->IsPcNote()) if(p->IsPcNote())
{ {
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
if((adjustMode & eAdjust) && p->instr > 0 && p->instr <= MAX_MIXPLUGINS) if(playState.m_midiMacroEvaluationResults && p->instr > 0 && p->instr <= MAX_MIXPLUGINS)
{ {
memory.plugParams[std::make_pair(p->instr, p->GetValueVolCol())] = p->GetValueEffectCol(); playState.m_midiMacroEvaluationResults->pluginParameter[{static_cast<PLUGINDEX>(p->instr - 1), p->GetValueVolCol()}] = p->GetValueEffectCol() / PlugParamValue(ModCommand::maxColumnValue);
} }
#endif // NO_PLUGINS #endif // NO_PLUGINS
chn.rowCommand.Clear(); chn.rowCommand.Clear();
@ -820,6 +818,17 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
case CMD_PANBRELLO: case CMD_PANBRELLO:
Panbrello(chn, param); Panbrello(chn, param);
break; break;
case CMD_MIDI:
case CMD_SMOOTHMIDI:
if(param < 0x80)
ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.SFx[chn.nActiveMacro], chn.rowCommand.param, 0);
else
ProcessMIDIMacro(playState, nChn, false, m_MidiCfg.Zxx[param & 0x7F], chn.rowCommand.param, 0);
break;
default:
break;
} }
switch(chn.rowCommand.volcmd) switch(chn.rowCommand.volcmd)
@ -925,6 +934,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
chn.nNewNote = chn.nLastNote; chn.nNewNote = chn.nLastNote;
if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta); if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta);
NoteChange(chn, m.note, porta); NoteChange(chn, m.note, porta);
HandleDigiSamplePlayDirection(playState, nChn);
memory.chnSettings[nChn].incChanged = true; memory.chnSettings[nChn].incChanged = true;
if((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xD0 && paramLo < numTicks) if((m.command == CMD_MODCMDEX || m.command == CMD_S3MCMDEX) && (m.param & 0xF0) == 0xD0 && paramLo < numTicks)
@ -1078,6 +1088,10 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
} }
break; break;
case CMD_DIGIREVERSESAMPLE:
DigiBoosterSampleReverse(chn, m.param);
break;
case CMD_FINETUNE: case CMD_FINETUNE:
case CMD_FINETUNE_SMOOTH: case CMD_FINETUNE_SMOOTH:
memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far
@ -1161,6 +1175,8 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
{ {
if(retval.targetReached || target.mode == GetLengthTarget::NoTarget) if(retval.targetReached || target.mode == GetLengthTarget::NoTarget)
{ {
const auto midiMacroEvaluationResults = std::move(playState.m_midiMacroEvaluationResults);
playState.m_midiMacroEvaluationResults.reset();
// Target found, or there is no target (i.e. play whole song)... // Target found, or there is no target (i.e. play whole song)...
m_PlayState = std::move(playState); m_PlayState = std::move(playState);
m_PlayState.ResetGlobalVolumeRamping(); m_PlayState.ResetGlobalVolumeRamping();
@ -1190,11 +1206,11 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
} }
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
// If there were any PC events, update plugin parameters to their latest value. // If there were any PC events or MIDI macros updating plugin parameters, update plugin parameters to their latest value.
std::bitset<MAX_MIXPLUGINS> plugSetProgram; std::bitset<MAX_MIXPLUGINS> plugSetProgram;
for(const auto &param : memory.plugParams) for(const auto &[plugParam, value] : midiMacroEvaluationResults->pluginParameter)
{ {
PLUGINDEX plug = param.first.first - 1; PLUGINDEX plug = plugParam.first;
IMixPlugin *plugin = m_MixPlugins[plug].pMixPlugin; IMixPlugin *plugin = m_MixPlugins[plug].pMixPlugin;
if(plugin != nullptr) if(plugin != nullptr)
{ {
@ -1204,7 +1220,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
plugSetProgram.set(plug); plugSetProgram.set(plug);
plugin->BeginSetProgram(); plugin->BeginSetProgram();
} }
plugin->SetParameter(param.first.second, param.second / PlugParamValue(ModCommand::maxColumnValue)); plugin->SetParameter(plugParam.second, value);
} }
} }
if(plugSetProgram.any()) if(plugSetProgram.any())
@ -1217,6 +1233,11 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
} }
} }
} }
// Do the same for dry/wet ratios
for(const auto &[plug, dryWetRatio] : midiMacroEvaluationResults->pluginDryWetRatio)
{
m_MixPlugins[plug].fDryRatio = dryWetRatio;
}
#endif // NO_PLUGINS #endif // NO_PLUGINS
} else if(adjustMode != eAdjustOnSuccess) } else if(adjustMode != eAdjustOnSuccess)
{ {
@ -2243,7 +2264,7 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
IMixPlugin *pPlugin = nullptr; IMixPlugin *pPlugin = nullptr;
if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan
{ {
PLUGINDEX plugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes); PLUGINDEX plugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes);
if(plugin > 0 && plugin <= MAX_MIXPLUGINS) if(plugin > 0 && plugin <= MAX_MIXPLUGINS)
{ {
@ -2860,6 +2881,7 @@ bool CSoundFile::ProcessEffects()
} }
NoteChange(chn, note, bPorta, !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)), false, nChn); NoteChange(chn, note, bPorta, !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)), false, nChn);
HandleDigiSamplePlayDirection(m_PlayState, nChn);
if ((bPorta) && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr)) if ((bPorta) && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr))
{ {
chn.dwFlags.set(CHN_FASTVOLRAMP); chn.dwFlags.set(CHN_FASTVOLRAMP);
@ -3342,7 +3364,7 @@ bool CSoundFile::ProcessEffects()
{ {
SetFinetune(nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH); SetFinetune(nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH);
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
if(IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); plugin != nullptr) if(IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]); plugin != nullptr)
plugin->MidiPitchBendRaw(chn.GetMIDIPitchBend(), nChn); plugin->MidiPitchBendRaw(chn.GetMIDIPitchBend(), nChn);
#endif // NO_PLUGINS #endif // NO_PLUGINS
} }
@ -3443,6 +3465,11 @@ bool CSoundFile::ProcessEffects()
} }
break; break;
#endif // NO_PLUGINS #endif // NO_PLUGINS
// Digi Booster sample reverse
case CMD_DIGIREVERSESAMPLE:
DigiBoosterSampleReverse(chn, static_cast<ModCommand::PARAM>(param));
break;
} }
if(m_playBehaviour[kST3EffectMemory] && param != 0) if(m_playBehaviour[kST3EffectMemory] && param != 0)
@ -3823,7 +3850,7 @@ void CSoundFile::MidiPortamento(CHANNELINDEX nChn, int param, bool doFineSlides)
if(pitchBend) if(pitchBend)
{ {
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr) if(plugin != nullptr)
{ {
int8 pwd = 13; // Early OpenMPT legacy... Actually it's not *exactly* 13, but close enough... int8 pwd = 13; // Early OpenMPT legacy... Actually it's not *exactly* 13, but close enough...
@ -4774,23 +4801,96 @@ void CSoundFile::InvertLoop(ModChannel &chn)
// Process a MIDI Macro. // Process a MIDI Macro.
// Parameters: // Parameters:
// playState: The playback state to operate on.
// nChn: Mod channel to apply macro on // nChn: Mod channel to apply macro on
// isSmooth: If true, internal macros are interpolated between two rows // isSmooth: If true, internal macros are interpolated between two rows
// macro: Actual MIDI Macro string // macro: MIDI Macro string to process
// param: Parameter for parametric macros (Z00 - Z7F) // param: Parameter for parametric macros (Zxx / \xx parameter)
// plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected) // plugin: Plugin to send MIDI message to (if not specified but needed, it is autodetected)
void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param, PLUGINDEX plugin) void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro &macro, uint8 param, PLUGINDEX plugin)
{ {
ModChannel &chn = m_PlayState.Chn[nChn]; playState.m_midiMacroScratchSpace.resize(macro.Length() + 1);
const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr; auto out = mpt::as_span(playState.m_midiMacroScratchSpace);
ParseMIDIMacro(playState, nChn, isSmooth, macro, out, param, plugin);
// Macro string has been parsed and translated, now send the message(s)...
uint32 outSize = static_cast<uint32>(out.size());
uint32 sendPos = 0;
uint8 runningStatus = 0;
while(sendPos < out.size())
{
uint32 sendLen = 0;
if(out[sendPos] == 0xF0)
{
// SysEx start
if((outSize - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1))
{
// Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long
sendLen = 4;
} else
{
// SysEx message, find end of message
for(uint32 i = sendPos + 1; i < outSize; i++)
{
if(out[i] == 0xF7)
{
// Found end of SysEx message
sendLen = i - sendPos + 1;
break;
}
}
if(sendLen == 0)
{
// Didn't find end, so "invent" end of SysEx message
out[outSize++] = 0xF7;
sendLen = outSize - sendPos;
}
}
} else if(!(out[sendPos] & 0x80))
{
// Missing status byte? Try inserting running status
if(runningStatus != 0)
{
sendPos--;
out[sendPos] = runningStatus;
} else
{
// No running status to re-use; skip this byte
sendPos++;
}
continue;
} else
{
// Other MIDI messages
sendLen = std::min(static_cast<uint32>(MIDIEvents::GetEventLength(out[sendPos])), outSize - sendPos);
}
if(sendLen == 0)
break;
if(out[sendPos] < 0xF0)
{
runningStatus = out[sendPos];
}
const auto midiMsg = out.subspan(sendPos, sendLen);
SendMIDIData(playState, nChn, isSmooth, midiMsg, plugin);
sendPos += sendLen;
}
}
void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span<const char> macro, mpt::span<uint8> &out, uint8 param, PLUGINDEX plugin) const
{
ModChannel &chn = playState.Chn[nChn];
const ModInstrument *pIns = chn.pModInstrument;
uint8 out[MACRO_LENGTH];
uint32 outPos = 0; // output buffer position, which also equals the number of complete bytes
const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string
uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message
bool firstNibble = true;
for(uint32 pos = 0; pos < (MACRO_LENGTH - 1) && macro[pos]; pos++) bool firstNibble = true;
size_t outPos = 0; // output buffer position, which also equals the number of complete bytes
for(size_t pos = 0; pos < macro.size() && outPos < out.size(); pos++)
{ {
bool isNibble = false; // did we parse a nibble or a byte value? bool isNibble = false; // did we parse a nibble or a byte value?
uint8 data = 0; // data that has just been parsed uint8 data = 0; // data that has just been parsed
@ -4800,8 +4900,7 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
{ {
isNibble = true; isNibble = true;
data = static_cast<uint8>(macro[pos] - '0'); data = static_cast<uint8>(macro[pos] - '0');
} } else if(macro[pos] >= 'A' && macro[pos] <= 'F')
else if(macro[pos] >= 'A' && macro[pos] <= 'F')
{ {
isNibble = true; isNibble = true;
data = static_cast<uint8>(macro[pos] - 'A' + 0x0A); data = static_cast<uint8>(macro[pos] - 'A' + 0x0A);
@ -4811,19 +4910,19 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
isNibble = true; isNibble = true;
data = 0xFF; data = 0xFF;
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted);
if(plug > 0 && plug <= MAX_MIXPLUGINS) if(plug > 0 && plug <= MAX_MIXPLUGINS)
{ {
auto midiPlug = dynamic_cast<const IMidiPlugin *>(m_MixPlugins[plug - 1u].pMixPlugin); auto midiPlug = dynamic_cast<const IMidiPlugin *>(m_MixPlugins[plug - 1u].pMixPlugin);
if(midiPlug) if(midiPlug)
data = midiPlug->GetMidiChannel(nChn); data = midiPlug->GetMidiChannel(playState.Chn[nChn], nChn);
} }
#endif // NO_PLUGINS #endif // NO_PLUGINS
if(data == 0xFF) if(data == 0xFF)
{ {
// Fallback if no plugin was found // Fallback if no plugin was found
if(pIns) if(pIns)
data = pIns->GetMIDIChannel(*this, nChn); data = pIns->GetMIDIChannel(playState.Chn[nChn], nChn);
else else
data = 0; data = 0;
} }
@ -4893,13 +4992,13 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
} else if(macro[pos] == 'z') } else if(macro[pos] == 'z')
{ {
// Zxx parameter // Zxx parameter
data = param & 0x7F; data = param;
if(isSmooth && chn.lastZxxParam < 0x80 if(isSmooth && chn.lastZxxParam < 0x80
&& (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0)) && (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0))
{ {
// Interpolation for external MIDI messages - interpolation for internal messages // Interpolation for external MIDI messages - interpolation for internal messages
// is handled separately to allow for more than 7-bit granularity where it's possible // is handled separately to allow for more than 7-bit granularity where it's possible
data = static_cast<uint8>(CalculateSmoothParamChange(lastZxxParam, data)); data = static_cast<uint8>(CalculateSmoothParamChange(playState, lastZxxParam, data));
chn.lastZxxParam = data; chn.lastZxxParam = data;
updateZxxParam = 0x80; updateZxxParam = 0x80;
} else if(updateZxxParam == 0xFF) } else if(updateZxxParam == 0xFF)
@ -4909,13 +5008,13 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
} else if(macro[pos] == 's') } else if(macro[pos] == 's')
{ {
// SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience) // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience)
uint32 startPos = outPos; auto startPos = outPos;
while(startPos > 0 && out[--startPos] != 0xF0); while(startPos > 0 && out[--startPos] != 0xF0);
if(outPos - startPos < 5 || out[startPos] != 0xF0) if(outPos - startPos < 5 || out[startPos] != 0xF0)
{ {
continue; continue;
} }
for(uint32 p = startPos + 5; p != outPos; p++) for(auto p = startPos + 5u; p != outPos; p++)
{ {
data += out[p]; data += out[p];
} }
@ -4940,7 +5039,7 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
firstNibble = !firstNibble; firstNibble = !firstNibble;
} else // parsed a byte (variable) } else // parsed a byte (variable)
{ {
if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first
{ {
outPos++; outPos++;
} }
@ -4956,83 +5055,19 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
if(updateZxxParam < 0x80) if(updateZxxParam < 0x80)
chn.lastZxxParam = updateZxxParam; chn.lastZxxParam = updateZxxParam;
// Macro string has been parsed and translated, now send the message(s)... out = out.first(outPos);
uint32 sendPos = 0;
uint8 runningStatus = 0;
while(sendPos < outPos)
{
uint32 sendLen = 0;
if(out[sendPos] == 0xF0)
{
// SysEx start
if((outPos - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1))
{
// Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long
sendLen = 4;
} else
{
// SysEx message, find end of message
for(uint32 i = sendPos + 1; i < outPos; i++)
{
if(out[i] == 0xF7)
{
// Found end of SysEx message
sendLen = i - sendPos + 1;
break;
}
}
if(sendLen == 0)
{
// Didn't find end, so "invent" end of SysEx message
out[outPos++] = 0xF7;
sendLen = outPos - sendPos;
}
}
} else if(!(out[sendPos] & 0x80))
{
// Missing status byte? Try inserting running status
if(runningStatus != 0)
{
sendPos--;
out[sendPos] = runningStatus;
} else
{
// No running status to re-use; skip this byte
sendPos++;
}
continue;
} else
{
// Other MIDI messages
sendLen = std::min(static_cast<uint32>(MIDIEvents::GetEventLength(out[sendPos])), outPos - sendPos);
}
if(sendLen == 0)
break;
if(out[sendPos] < 0xF0)
{
runningStatus = out[sendPos];
}
uint32 bytesSent = SendMIDIData(nChn, isSmooth, out + sendPos, sendLen, plugin);
// If there's no error in the macro data (e.g. unrecognized internal MIDI macro), we have sendLen == bytesSent.
if(bytesSent > 0)
sendPos += bytesSent;
else
sendPos += sendLen;
}
} }
// Calculate smooth MIDI macro slide parameter for current tick. // Calculate smooth MIDI macro slide parameter for current tick.
float CSoundFile::CalculateSmoothParamChange(float currentValue, float param) const float CSoundFile::CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param)
{ {
MPT_ASSERT(m_PlayState.TicksOnRow() > m_PlayState.m_nTickCount); MPT_ASSERT(playState.TicksOnRow() > playState.m_nTickCount);
const uint32 ticksLeft = m_PlayState.TicksOnRow() - m_PlayState.m_nTickCount; const uint32 ticksLeft = playState.TicksOnRow() - playState.m_nTickCount;
if(ticksLeft > 1) if(ticksLeft > 1)
{ {
// Slide param // Slide param
const float step = (param - currentValue) / (float)ticksLeft; const float step = (param - currentValue) / static_cast<float>(ticksLeft);
return (currentValue + step); return (currentValue + step);
} else } else
{ {
@ -5043,31 +5078,28 @@ float CSoundFile::CalculateSmoothParamChange(float currentValue, float param) co
// Process exactly one MIDI message parsed by ProcessMIDIMacro. Returns bytes sent on success, 0 on (parse) failure. // Process exactly one MIDI message parsed by ProcessMIDIMacro. Returns bytes sent on success, 0 on (parse) failure.
uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin) void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span<const uint8> macro, PLUGINDEX plugin)
{ {
if(macroLen < 1) if(macro.size() < 1)
{ return;
return 0;
} // Don't do anything that modifies state outside of the playState itself.
const bool localOnly = playState.m_midiMacroEvaluationResults.has_value();
if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF) if(macro[0] == 0xFA || macro[0] == 0xFC || macro[0] == 0xFF)
{ {
// Start Song, Stop Song, MIDI Reset - both interpreted internally and sent to plugins // Start Song, Stop Song, MIDI Reset - both interpreted internally and sent to plugins
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{ {
m_PlayState.Chn[chn].nCutOff = 0x7F; playState.Chn[chn].nCutOff = 0x7F;
m_PlayState.Chn[chn].nResonance = 0x00; playState.Chn[chn].nResonance = 0x00;
} }
} }
ModChannel &chn = m_PlayState.Chn[nChn]; ModChannel &chn = playState.Chn[nChn];
if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1)) if(macro.size() == 4 && macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1))
{ {
// Internal device. // Internal device.
if(macroLen < 4)
{
return 0;
}
const bool isExtended = (macro[1] == 0xF1); const bool isExtended = (macro[1] == 0xF1);
const uint8 macroCode = macro[2]; const uint8 macroCode = macro[2];
const uint8 param = macro[3]; const uint8 param = macro[3];
@ -5076,36 +5108,26 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
{ {
// F0.F0.00.xx: Set CutOff // F0.F0.00.xx: Set CutOff
if(!isSmooth) if(!isSmooth)
{
chn.nCutOff = param; chn.nCutOff = param;
} else else
{ chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(playState, chn.nCutOff, param));
chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(chn.nCutOff, param));
}
chn.nRestoreCutoffOnNewNote = 0; chn.nRestoreCutoffOnNewNote = 0;
int cutoff = SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); int cutoff = SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl) if(cutoff >= 0 && chn.dwFlags[CHN_ADLIB] && m_opl && !localOnly)
{ {
// Cutoff doubles as modulator intensity for FM instruments // Cutoff doubles as modulator intensity for FM instruments
m_opl->Volume(nChn, static_cast<uint8>(cutoff / 4), true); m_opl->Volume(nChn, static_cast<uint8>(cutoff / 4), true);
} }
return 4;
} else if(macroCode == 0x01 && !isExtended && param < 0x80) } else if(macroCode == 0x01 && !isExtended && param < 0x80)
{ {
// F0.F0.01.xx: Set Resonance // F0.F0.01.xx: Set Resonance
if(!isSmooth) if(!isSmooth)
{
chn.nResonance = param; chn.nResonance = param;
} else else
{ chn.nResonance = mpt::saturate_round<uint8>(CalculateSmoothParamChange(playState, chn.nResonance, param));
chn.nResonance = (uint8)CalculateSmoothParamChange((float)chn.nResonance, (float)param);
}
chn.nRestoreResonanceOnNewNote = 0; chn.nRestoreResonanceOnNewNote = 0;
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
return 4;
} else if(macroCode == 0x02 && !isExtended) } else if(macroCode == 0x02 && !isExtended)
{ {
// F0.F0.02.xx: Set filter mode (high nibble determines filter mode) // F0.F0.02.xx: Set filter mode (high nibble determines filter mode)
@ -5114,54 +5136,45 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
chn.nFilterMode = static_cast<FilterMode>(param >> 4); chn.nFilterMode = static_cast<FilterMode>(param >> 4);
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]); SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
} }
return 4;
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
} else if(macroCode == 0x03 && !isExtended) } else if(macroCode == 0x03 && !isExtended)
{ {
// F0.F0.03.xx: Set plug dry/wet // F0.F0.03.xx: Set plug dry/wet
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted);
if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80) if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
{ {
const float newRatio = (0x7F - (param & 0x7F)) / 127.0f; plug--;
if(!isSmooth) const float newRatio = (127 - param) / 127.0f;
{ if(localOnly)
m_MixPlugins[plug - 1].fDryRatio = newRatio; playState.m_midiMacroEvaluationResults->pluginDryWetRatio[plug] = newRatio;
} else else if(!isSmooth)
{ m_MixPlugins[plug].fDryRatio = newRatio;
m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio); else
} m_MixPlugins[plug].fDryRatio = CalculateSmoothParamChange(playState, m_MixPlugins[plug].fDryRatio, newRatio);
} }
return 4;
} else if((macroCode & 0x80) || isExtended) } else if((macroCode & 0x80) || isExtended)
{ {
// F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx // F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx
const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted);
const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F); if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
if(plug > 0 && plug <= MAX_MIXPLUGINS)
{ {
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin; plug--;
if(pPlugin && param < 0x80) IMixPlugin *pPlugin = m_MixPlugins[plug].pMixPlugin;
if(pPlugin)
{ {
const float fParam = param / 127.0f; const PlugParamIndex plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
if(!isSmooth) const PlugParamValue value = param / 127.0f;
{ if(localOnly)
pPlugin->SetParameter(plugParam, fParam); playState.m_midiMacroEvaluationResults->pluginParameter[{plug, plugParam}] = value;
} else else if(!isSmooth)
{ pPlugin->SetParameter(plugParam, value);
pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam)); else
} pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), value));
} }
} }
return 4;
#endif // NO_PLUGINS #endif // NO_PLUGINS
} }
} else if(!localOnly)
// If we reach this point, the internal macro was invalid.
} else
{ {
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
// Not an internal device. Pass on to appropriate plugin. // Not an internal device. Pass on to appropriate plugin.
@ -5171,7 +5184,7 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
PLUGINDEX plug = 0; PLUGINDEX plug = 0;
if(!chn.dwFlags[CHN_NOFX]) if(!chn.dwFlags[CHN_NOFX])
{ {
plug = (plugin != 0) ? plugin : GetBestPlugin(nChn, PrioritiseChannel, EvenIfMuted); plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted);
} }
if(plug > 0 && plug <= MAX_MIXPLUGINS) if(plug > 0 && plug <= MAX_MIXPLUGINS)
@ -5181,12 +5194,12 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
{ {
if(macro[0] == 0xF0) if(macro[0] == 0xF0)
{ {
pPlugin->MidiSysexSend(mpt::as_span(mpt::byte_cast<const std::byte*>(macro), macroLen)); pPlugin->MidiSysexSend(mpt::byte_cast<mpt::const_byte_span>(macro));
} else } else
{ {
uint32 len = std::min(static_cast<uint32>(MIDIEvents::GetEventLength(macro[0])), macroLen); size_t len = std::min(static_cast<size_t>(MIDIEvents::GetEventLength(macro[0])), macro.size());
uint32 curData = 0; uint32 curData = 0;
memcpy(&curData, macro, len); memcpy(&curData, macro.data(), len);
pPlugin->MidiSend(curData); pPlugin->MidiSend(curData);
} }
} }
@ -5195,11 +5208,7 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
#else #else
MPT_UNREFERENCED_PARAMETER(plugin); MPT_UNREFERENCED_PARAMETER(plugin);
#endif // NO_PLUGINS #endif // NO_PLUGINS
return macroLen;
} }
return 0;
} }
@ -5348,12 +5357,43 @@ void CSoundFile::ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) c
{ {
chn.dwFlags.set(CHN_PINGPONGFLAG); chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.dwFlags.reset(CHN_LOOP); chn.dwFlags.reset(CHN_LOOP);
chn.nLength = chn.pModSample->nLength; // If there was a loop, extend sample to whole length. chn.nLength = chn.pModSample->nLength; // If there was a loop, extend sample to whole length.
chn.position.Set((chn.nLength - 1) - std::min(SmpLength(param) << 8, chn.nLength - SmpLength(1)), 0); chn.position.Set((chn.nLength - 1) - std::min(SmpLength(param) << 8, chn.nLength - SmpLength(1)), 0);
} }
} }
void CSoundFile::DigiBoosterSampleReverse(ModChannel &chn, ModCommand::PARAM param) const
{
if(chn.isFirstTick && chn.pModSample != nullptr && chn.pModSample->nLength > 0)
{
chn.dwFlags.set(CHN_PINGPONGFLAG);
chn.nLength = chn.pModSample->nLength; // If there was a loop, extend sample to whole length.
chn.position.Set(chn.nLength - 1, 0);
chn.dwFlags.set(CHN_LOOP | CHN_PINGPONGLOOP, param > 0);
if(param > 0)
{
chn.nLoopStart = 0;
chn.nLoopEnd = chn.nLength;
// TODO: When the sample starts playing in forward direction again, the loop should be updated to the normal sample loop.
}
}
}
void CSoundFile::HandleDigiSamplePlayDirection(PlayState &state, CHANNELINDEX chn) const
{
// Digi Booster mixes two channels into one Paula channel, and when a note is triggered on one of them it resets the reverse play flag on the other.
if(GetType() == MOD_TYPE_DIGI)
{
state.Chn[chn].dwFlags.reset(CHN_PINGPONGFLAG);
const CHANNELINDEX otherChn = chn ^ 1;
if(otherChn < GetNumChannels())
state.Chn[otherChn].dwFlags.reset(CHN_PINGPONGFLAG);
}
}
void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset) void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
{ {
// Retrig: bit 8 is set if it's the new XM retrig // Retrig: bit 8 is set if it's the new XM retrig
@ -5480,7 +5520,9 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
uint32 note = chn.nNewNote; uint32 note = chn.nNewNote;
int32 oldPeriod = chn.nPeriod; int32 oldPeriod = chn.nPeriod;
// ST3 doesn't retrigger OPL notes // ST3 doesn't retrigger OPL notes
if(note >= NOTE_MIN && note <= NOTE_MAX && chn.nLength && (!chn.dwFlags[CHN_ADLIB] || GetType() != MOD_TYPE_S3M || m_playBehaviour[kOPLRealRetrig])) // Test case: RetrigSlide.s3m
const bool oplRealRetrig = chn.dwFlags[CHN_ADLIB] && m_playBehaviour[kOPLRealRetrig];
if(note >= NOTE_MIN && note <= NOTE_MAX && chn.nLength && (GetType() != MOD_TYPE_S3M || oplRealRetrig))
CheckNNA(nChn, 0, note, true); CheckNNA(nChn, 0, note, true);
bool resetEnv = false; bool resetEnv = false;
if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)) if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))
@ -5498,8 +5540,9 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
const auto oldPrevNoteOffset = chn.prevNoteOffset; const auto oldPrevNoteOffset = chn.prevNoteOffset;
chn.prevNoteOffset = 0; // Retriggered notes should not use previous offset (test case: OxxMemoryWithRetrig.s3m) chn.prevNoteOffset = 0; // Retriggered notes should not use previous offset (test case: OxxMemoryWithRetrig.s3m)
// IT compatibility: Really weird combination of envelopes and retrigger (see Storlek's q.it testcase) // IT compatibility: Really weird combination of envelopes and retrigger (see Storlek's q.it testcase)
// Test case: retrig.it // Test cases: retrig.it, RetrigSlide.s3m
NoteChange(chn, note, m_playBehaviour[kITRetrigger], resetEnv, false, nChn); const bool itS3Mstyle = m_playBehaviour[kITRetrigger] || (GetType() == MOD_TYPE_S3M && chn.nLength && !oplRealRetrig);
NoteChange(chn, note, itS3Mstyle, resetEnv, false, nChn);
if(!chn.rowCommand.instr) if(!chn.rowCommand.instr)
chn.prevNoteOffset = oldPrevNoteOffset; chn.prevNoteOffset = oldPrevNoteOffset;
// XM compatibility: Prevent NoteChange from resetting the fade flag in case an instrument number + note-off is present. // XM compatibility: Prevent NoteChange from resetting the fade flag in case an instrument number + note-off is present.
@ -5519,7 +5562,8 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT))) if(!(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)))
retrigCount = 0; retrigCount = 0;
// IT compatibility: see previous IT compatibility comment =) // IT compatibility: see previous IT compatibility comment =)
if(m_playBehaviour[kITRetrigger]) chn.position.Set(0); if(itS3Mstyle)
chn.position.Set(0);
offset--; offset--;
if(chn.pModSample != nullptr && offset >= 0 && offset <= static_cast<int>(std::size(chn.pModSample->cues))) if(chn.pModSample != nullptr && offset >= 0 && offset <= static_cast<int>(std::size(chn.pModSample->cues)))
@ -5757,7 +5801,7 @@ void CSoundFile::SetTempo(TEMPO param, bool setFromUI)
const CModSpecifications &specs = GetModSpecifications(); const CModSpecifications &specs = GetModSpecifications();
// Anything lower than the minimum tempo is considered to be a tempo slide // Anything lower than the minimum tempo is considered to be a tempo slide
const TEMPO minTempo = (GetType() & (MOD_TYPE_MDL | MOD_TYPE_MED)) ? TEMPO(1, 0) : TEMPO(32, 0); const TEMPO minTempo = (GetType() & (MOD_TYPE_MDL | MOD_TYPE_MED | MOD_TYPE_MOD)) ? TEMPO(1, 0) : TEMPO(32, 0);
if(setFromUI) if(setFromUI)
{ {
@ -6093,7 +6137,7 @@ uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPerio
} }
PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const PLUGINDEX CSoundFile::GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const
{ {
if (nChn >= MAX_CHANNELS) //Check valid channel number if (nChn >= MAX_CHANNELS) //Check valid channel number
{ {
@ -6105,23 +6149,23 @@ PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority,
switch (priority) switch (priority)
{ {
case ChannelOnly: case ChannelOnly:
plugin = GetChannelPlugin(nChn, respectMutes); plugin = GetChannelPlugin(playState, nChn, respectMutes);
break; break;
case InstrumentOnly: case InstrumentOnly:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes); plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
break; break;
case PrioritiseInstrument: case PrioritiseInstrument:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes); plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS) if(!plugin || plugin > MAX_MIXPLUGINS)
{ {
plugin = GetChannelPlugin(nChn, respectMutes); plugin = GetChannelPlugin(playState, nChn, respectMutes);
} }
break; break;
case PrioritiseChannel: case PrioritiseChannel:
plugin = GetChannelPlugin(nChn, respectMutes); plugin = GetChannelPlugin(playState, nChn, respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS) if(!plugin || plugin > MAX_MIXPLUGINS)
{ {
plugin = GetActiveInstrumentPlugin(nChn, respectMutes); plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
} }
break; break;
} }
@ -6130,9 +6174,9 @@ PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority,
} }
PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const PLUGINDEX CSoundFile::GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const
{ {
const ModChannel &channel = m_PlayState.Chn[nChn]; const ModChannel &channel = playState.Chn[nChn];
PLUGINDEX plugin; PLUGINDEX plugin;
if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX]) if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX])
@ -6142,8 +6186,7 @@ PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority res
{ {
// If it looks like this is an NNA channel, we need to find the master channel. // If it looks like this is an NNA channel, we need to find the master channel.
// This ensures we pick up the right ChnSettings. // This ensures we pick up the right ChnSettings.
// NB: nMasterChn == 0 means no master channel, so we need to -1 to get correct index. if(channel.nMasterChn > 0)
if (nChn >= m_nChannels && channel.nMasterChn > 0)
{ {
nChn = channel.nMasterChn - 1; nChn = channel.nMasterChn - 1;
} }
@ -6160,20 +6203,21 @@ PLUGINDEX CSoundFile::GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority res
} }
PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes)
{ {
// Unlike channel settings, pModInstrument is copied from the original chan to the NNA chan, // Unlike channel settings, pModInstrument is copied from the original chan to the NNA chan,
// so we don't need to worry about finding the master chan. // so we don't need to worry about finding the master chan.
PLUGINDEX plug = 0; PLUGINDEX plug = 0;
if(m_PlayState.Chn[nChn].pModInstrument != nullptr) if(chn.pModInstrument != nullptr)
{ {
if(respectMutes == RespectMutes && m_PlayState.Chn[nChn].pModSample && m_PlayState.Chn[nChn].pModSample->uFlags[CHN_MUTE]) // TODO this looks fishy. Shouldn't it check the mute status of the instrument itself?!
if(respectMutes == RespectMutes && chn.pModSample && chn.pModSample->uFlags[CHN_MUTE])
{ {
plug = 0; plug = 0;
} else } else
{ {
plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug; plug = chn.pModInstrument->nMixPlug;
} }
} }
return plug; return plug;
@ -6183,10 +6227,10 @@ PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePri
// Retrieve the plugin that is associated with the channel's current instrument. // Retrieve the plugin that is associated with the channel's current instrument.
// No plugin is returned if the channel is muted or if the instrument doesn't have a MIDI channel set up, // No plugin is returned if the channel is muted or if the instrument doesn't have a MIDI channel set up,
// As this is meant to be used with instrument plugins. // As this is meant to be used with instrument plugins.
IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(const ModChannel &chn) const
{ {
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
if(m_PlayState.Chn[chn].dwFlags[CHN_MUTE | CHN_SYNCMUTE]) if(chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE])
{ {
// Don't process portamento on muted channels. Note that this might have a side-effect // Don't process portamento on muted channels. Note that this might have a side-effect
// on other channels which trigger notes on the same MIDI channel of the same plugin, // on other channels which trigger notes on the same MIDI channel of the same plugin,
@ -6194,9 +6238,9 @@ IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(CHANNELINDEX chn) const
return nullptr; return nullptr;
} }
if(m_PlayState.Chn[chn].HasMIDIOutput()) if(chn.HasMIDIOutput())
{ {
const ModInstrument *pIns = m_PlayState.Chn[chn].pModInstrument; const ModInstrument *pIns = chn.pModInstrument;
// Instrument sends to a MIDI channel // Instrument sends to a MIDI channel
if(pIns->nMixPlug != 0 && pIns->nMixPlug <= MAX_MIXPLUGINS) if(pIns->nMixPlug != 0 && pIns->nMixPlug <= MAX_MIXPLUGINS)
{ {

View File

@ -70,6 +70,13 @@ mpt::ustring FileHistory::AsISO8601() const
} }
CSoundFile::PlayState::PlayState()
{
std::fill(std::begin(Chn), std::end(Chn), ModChannel{});
m_midiMacroScratchSpace.reserve(kMacroLength); // Note: If macros ever become variable-length, the scratch space needs to be at least one byte longer than the longest macro in the file for end-of-SysEx insertion to stay allocation-free in the mixer!
}
////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////
// CSoundFile // CSoundFile
@ -367,108 +374,107 @@ CSoundFile::ProbeResult CSoundFile::Probe(ProbeFlags flags, mpt::span<const std:
} }
#ifdef MODPLUG_TRACKER
bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags, CModDoc *pModDoc) bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags, CModDoc *pModDoc)
{ {
m_nMixChannels = 0;
#ifdef MODPLUG_TRACKER
m_pModDoc = pModDoc; m_pModDoc = pModDoc;
#else #else
bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags) MPT_UNUSED(pModDoc);
{
#endif // MODPLUG_TRACKER
m_nMixChannels = 0;
#ifndef MODPLUG_TRACKER
m_nFreqFactor = m_nTempoFactor = 65536; m_nFreqFactor = m_nTempoFactor = 65536;
#endif #endif // MODPLUG_TRACKER
MemsetZero(Instruments);
Clear(m_szNames); Clear(m_szNames);
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
std::fill(std::begin(m_MixPlugins), std::end(m_MixPlugins), SNDMIXPLUGIN()); std::fill(std::begin(m_MixPlugins), std::end(m_MixPlugins), SNDMIXPLUGIN());
#endif // NO_PLUGINS #endif // NO_PLUGINS
if(CreateInternal(file, loadFlags))
return true;
#ifndef NO_ARCHIVE_SUPPORT
if(!(loadFlags & skipContainer) && file.IsValid())
{
CUnarchiver unarchiver(file);
if(unarchiver.ExtractBestFile(GetSupportedExtensions(true)))
{
if(CreateInternal(unarchiver.GetOutputFile(), loadFlags))
{
// Read archive comment if there is no song comment
if(m_songMessage.empty())
{
m_songMessage.assign(mpt::ToCharset(mpt::Charset::Locale, unarchiver.GetComment()));
}
return true;
}
}
}
#endif
return false;
}
bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags)
{
if(file.IsValid()) if(file.IsValid())
{ {
std::vector<ContainerItem> containerItems;
MODCONTAINERTYPE packedContainerType = MOD_CONTAINERTYPE_NONE;
if(!(loadFlags & skipContainer))
{ {
ContainerLoadingFlags containerLoadFlags = (loadFlags == onlyVerifyHeader) ? ContainerOnlyVerifyHeader : ContainerUnwrapData;
#ifndef NO_ARCHIVE_SUPPORT
CUnarchiver unarchiver(file);
if(!(loadFlags & skipContainer))
{
if (unarchiver.ExtractBestFile(GetSupportedExtensions(true)))
{
file = unarchiver.GetOutputFile();
}
}
#endif
std::vector<ContainerItem> containerItems;
MODCONTAINERTYPE packedContainerType = MOD_CONTAINERTYPE_NONE;
if(!(loadFlags & skipContainer))
{
ContainerLoadingFlags containerLoadFlags = (loadFlags == onlyVerifyHeader) ? ContainerOnlyVerifyHeader : ContainerUnwrapData;
#if !defined(MPT_WITH_ANCIENT) #if !defined(MPT_WITH_ANCIENT)
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackXPK(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_XPK; if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackXPK(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_XPK;
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackPP20(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_PP20; if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackPP20(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_PP20;
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackMMCMP(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_MMCMP; if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackMMCMP(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_MMCMP;
#endif // !MPT_WITH_ANCIENT #endif // !MPT_WITH_ANCIENT
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackUMX(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_UMX; if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackUMX(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_UMX;
if(packedContainerType != MOD_CONTAINERTYPE_NONE) if(packedContainerType != MOD_CONTAINERTYPE_NONE)
{
if(loadFlags == onlyVerifyHeader)
{ {
if(loadFlags == onlyVerifyHeader) return true;
{ }
return true; if(!containerItems.empty())
} {
if(!containerItems.empty()) // cppcheck false-positive
{ // cppcheck-suppress containerOutOfBounds
// cppcheck false-positive file = containerItems[0].file;
// cppcheck-suppress containerOutOfBounds
file = containerItems[0].file;
}
} }
} }
if(loadFlags & skipModules)
{
return false;
}
// Try all module format loaders
bool loaderSuccess = false;
for(const auto &format : ModuleFormatLoaders)
{
loaderSuccess = (this->*(format.loader))(file, loadFlags);
if(loaderSuccess)
break;
}
if(!loaderSuccess)
{
m_nType = MOD_TYPE_NONE;
m_ContainerType = MOD_CONTAINERTYPE_NONE;
}
if(loadFlags == onlyVerifyHeader)
{
return loaderSuccess;
}
if(packedContainerType != MOD_CONTAINERTYPE_NONE && m_ContainerType == MOD_CONTAINERTYPE_NONE)
{
m_ContainerType = packedContainerType;
}
#ifndef NO_ARCHIVE_SUPPORT
// Read archive comment if there is no song comment
if(m_songMessage.empty())
{
m_songMessage.assign(mpt::ToCharset(mpt::Charset::Locale, unarchiver.GetComment()));
}
#endif
m_visitedRows.Initialize(true);
} }
if(loadFlags & skipModules)
{
return false;
}
// Try all module format loaders
bool loaderSuccess = false;
for(const auto &format : ModuleFormatLoaders)
{
loaderSuccess = (this->*(format.loader))(file, loadFlags);
if(loaderSuccess)
break;
}
if(!loaderSuccess)
{
m_nType = MOD_TYPE_NONE;
m_ContainerType = MOD_CONTAINERTYPE_NONE;
}
if(loadFlags == onlyVerifyHeader)
{
return loaderSuccess;
}
if(packedContainerType != MOD_CONTAINERTYPE_NONE && m_ContainerType == MOD_CONTAINERTYPE_NONE)
{
m_ContainerType = packedContainerType;
}
m_visitedRows.Initialize(true);
} else } else
{ {
// New song // New song

View File

@ -232,9 +232,7 @@ class CTuningCollection;
using CTuningCollection = Tuning::CTuningCollection; using CTuningCollection = Tuning::CTuningCollection;
struct CModSpecifications; struct CModSpecifications;
class OPL; class OPL;
#ifdef MODPLUG_TRACKER
class CModDoc; class CModDoc;
#endif // MODPLUG_TRACKER
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
@ -585,11 +583,17 @@ public:
CHANNELINDEX ChnMix[MAX_CHANNELS]; // Index of channels in Chn to be actually mixed CHANNELINDEX ChnMix[MAX_CHANNELS]; // Index of channels in Chn to be actually mixed
ModChannel Chn[MAX_CHANNELS]; // Mixing channels... First m_nChannels channels are master channels (i.e. they are never NNA channels)! ModChannel Chn[MAX_CHANNELS]; // Mixing channels... First m_nChannels channels are master channels (i.e. they are never NNA channels)!
public: struct MIDIMacroEvaluationResults
PlayState()
{ {
std::fill(std::begin(Chn), std::end(Chn), ModChannel()); std::map<PLUGINDEX, float> pluginDryWetRatio;
} std::map<std::pair<PLUGINDEX, PlugParamIndex>, PlugParamValue> pluginParameter;
};
std::vector<uint8> m_midiMacroScratchSpace;
std::optional<MIDIMacroEvaluationResults> m_midiMacroEvaluationResults;
public:
PlayState();
void ResetGlobalVolumeRamping() void ResetGlobalVolumeRamping()
{ {
@ -720,12 +724,13 @@ public:
#ifdef MODPLUG_TRACKER #ifdef MODPLUG_TRACKER
// Get parent CModDoc. Can be nullptr if previewing from tree view, and is always nullptr if we're not actually compiling OpenMPT. // Get parent CModDoc. Can be nullptr if previewing from tree view, and is always nullptr if we're not actually compiling OpenMPT.
CModDoc *GetpModDoc() const noexcept { return m_pModDoc; } CModDoc *GetpModDoc() const noexcept { return m_pModDoc; }
#endif // MODPLUG_TRACKER
bool Create(FileReader file, ModLoadingFlags loadFlags = loadCompleteModule, CModDoc *pModDoc = nullptr); bool Create(FileReader file, ModLoadingFlags loadFlags = loadCompleteModule, CModDoc *pModDoc = nullptr);
#else private:
bool Create(FileReader file, ModLoadingFlags loadFlags); bool CreateInternal(FileReader file, ModLoadingFlags loadFlags);
#endif // MODPLUG_TRACKER
public:
bool Destroy(); bool Destroy();
Enum<MODTYPE> GetType() const noexcept { return m_nType; } Enum<MODTYPE> GetType() const noexcept { return m_nType; }
@ -1096,6 +1101,8 @@ protected:
void ProcessSampleOffset(ModChannel &chn, CHANNELINDEX nChn, const PlayState &playState) const; void ProcessSampleOffset(ModChannel &chn, CHANNELINDEX nChn, const PlayState &playState) const;
void SampleOffset(ModChannel &chn, SmpLength param) const; void SampleOffset(ModChannel &chn, SmpLength param) const;
void ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const; void ReverseSampleOffset(ModChannel &chn, ModCommand::PARAM param) const;
void DigiBoosterSampleReverse(ModChannel &chn, ModCommand::PARAM param) const;
void HandleDigiSamplePlayDirection(PlayState &state, CHANNELINDEX chn) const;
void NoteCut(CHANNELINDEX nChn, uint32 nTick, bool cutSample); void NoteCut(CHANNELINDEX nChn, uint32 nTick, bool cutSample);
void PatternLoop(PlayState &state, ModChannel &chn, ModCommand::PARAM param) const; void PatternLoop(PlayState &state, ModChannel &chn, ModCommand::PARAM param) const;
bool HandleNextRow(PlayState &state, const ModSequence &order, bool honorPatternLoop) const; bool HandleNextRow(PlayState &state, const ModSequence &order, bool honorPatternLoop) const;
@ -1108,9 +1115,10 @@ protected:
void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide); void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide);
void ProcessMacroOnChannel(CHANNELINDEX nChn); void ProcessMacroOnChannel(CHANNELINDEX nChn);
void ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param = 0, PLUGINDEX plugin = 0); void ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro &macro, uint8 param = 0, PLUGINDEX plugin = 0);
float CalculateSmoothParamChange(float currentValue, float param) const; void ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span<const char> macro, mpt::span<uint8> &out, uint8 param = 0, PLUGINDEX plugin = 0) const;
uint32 SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin); static float CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param);
void SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span<const uint8> macro, PLUGINDEX plugin);
void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume); void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume);
int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const; int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const;
@ -1236,12 +1244,12 @@ public:
void ProcessStereoSeparation(long countChunk); void ProcessStereoSeparation(long countChunk);
private: private:
PLUGINDEX GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const; PLUGINDEX GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const;
PLUGINDEX GetActiveInstrumentPlugin(CHANNELINDEX, PluginMutePriority respectMutes) const; static PLUGINDEX GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes);
IMixPlugin *GetChannelInstrumentPlugin(CHANNELINDEX chn) const; IMixPlugin *GetChannelInstrumentPlugin(const ModChannel &chn) const;
public: public:
PLUGINDEX GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; PLUGINDEX GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const;
}; };

View File

@ -1704,7 +1704,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT
// Process MIDI vibrato for plugins: // Process MIDI vibrato for plugins:
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr) if(plugin != nullptr)
{ {
// If the Pitch Wheel Depth is configured correctly (so it's the same as the plugin's PWD), // If the Pitch Wheel Depth is configured correctly (so it's the same as the plugin's PWD),
@ -1727,7 +1727,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT
{ {
// Stop MIDI vibrato for plugins: // Stop MIDI vibrato for plugins:
#ifndef NO_PLUGINS #ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn); IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr) if(plugin != nullptr)
{ {
plugin->MidiVibrato(0, 0, nChn); plugin->MidiVibrato(0, 0, nChn);
@ -2527,15 +2527,15 @@ void CSoundFile::ProcessMacroOnChannel(CHANNELINDEX nChn)
if(nChn < GetNumChannels()) if(nChn < GetNumChannels())
{ {
// TODO evaluate per-plugin macros here // TODO evaluate per-plugin macros here
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN]); //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[MIDIOUT_PAN]);
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME]); //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[MIDIOUT_VOLUME]);
if((chn.rowCommand.command == CMD_MIDI && m_SongFlags[SONG_FIRSTTICK]) || chn.rowCommand.command == CMD_SMOOTHMIDI) if((chn.rowCommand.command == CMD_MIDI && m_SongFlags[SONG_FIRSTTICK]) || chn.rowCommand.command == CMD_SMOOTHMIDI)
{ {
if(chn.rowCommand.param < 0x80) if(chn.rowCommand.param < 0x80)
ProcessMIDIMacro(nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiSFXExt[chn.nActiveMacro], chn.rowCommand.param); ProcessMIDIMacro(m_PlayState, nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.SFx[chn.nActiveMacro], chn.rowCommand.param);
else else
ProcessMIDIMacro(nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.szMidiZXXExt[(chn.rowCommand.param & 0x7F)], 0); ProcessMIDIMacro(m_PlayState, nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.Zxx[chn.rowCommand.param & 0x7F], chn.rowCommand.param);
} }
} }
} }
@ -2561,7 +2561,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn)
} }
// Check instrument plugins // Check instrument plugins
const PLUGINDEX nPlugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes); const PLUGINDEX nPlugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes);
IMixPlugin *pPlugin = nullptr; IMixPlugin *pPlugin = nullptr;
if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS) if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS)
{ {
@ -2622,7 +2622,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn)
if(ModCommand::IsNote(note)) if(ModCommand::IsNote(note))
realNote = pIns->NoteMap[note - NOTE_MIN]; realNote = pIns->NoteMap[note - NOTE_MIN];
// Experimental VST panning // Experimental VST panning
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN], 0, nPlugin); //ProcessMIDIMacro(nChn, false, m_MidiCfg.Global[MIDIOUT_PAN], 0, nPlugin);
SendMIDINote(nChn, realNote, static_cast<uint16>(velocity)); SendMIDINote(nChn, realNote, static_cast<uint16>(velocity));
} }

View File

@ -67,7 +67,7 @@ constexpr CModSpecifications mptm_ =
true, // Has artist name true, // Has artist name
true, // Has default resampling true, // Has default resampling
true, // Fixed point tempo true, // Fixed point tempo
" JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*????????", // Supported Effects " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*?????????", // Supported Effects
" vpcdabuh??gfe?o", // Supported Volume Column commands " vpcdabuh??gfe?o", // Supported Volume Column commands
}; };
@ -117,7 +117,7 @@ constexpr CModSpecifications mod_ =
false, // Doesn't have artist name false, // Doesn't have artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" 0123456789ABCD?FF?E?????????????????????????", // Supported Effects " 0123456789ABCD?FF?E??????????????????????????", // Supported Effects
" ???????????????", // Supported Volume Column commands " ???????????????", // Supported Volume Column commands
}; };
@ -165,7 +165,7 @@ constexpr CModSpecifications xm_ =
false, // Doesn't have artist name false, // Doesn't have artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" 0123456789ABCDRFFTE???GHK??XPL??????W???????", // Supported Effects " 0123456789ABCDRFFTE???GHK??XPL??????W????????", // Supported Effects
" vpcdabuhlrg????", // Supported Volume Column commands " vpcdabuhlrg????", // Supported Volume Column commands
}; };
@ -213,7 +213,7 @@ constexpr CModSpecifications xmEx_ =
true, // Has artist name true, // Has artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" 0123456789ABCDRFFTE???GHK?YXPLZ\\?#??W???????", // Supported Effects " 0123456789ABCDRFFTE???GHK?YXPLZ\\?#??W????????", // Supported Effects
" vpcdabuhlrg????", // Supported Volume Column commands " vpcdabuhlrg????", // Supported Volume Column commands
}; };
@ -260,7 +260,7 @@ constexpr CModSpecifications s3m_ =
false, // Doesn't have artist name false, // Doesn't have artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" JFEGHLKRXODB?CQATI?SMNVW?U?????????? ???????", // Supported Effects " JFEGHLKRXODB?CQATI?SMNVW?U?????????? ????????", // Supported Effects
" vp?????????????", // Supported Volume Column commands " vp?????????????", // Supported Volume Column commands
}; };
@ -308,7 +308,7 @@ constexpr CModSpecifications s3mEx_ =
false, // Doesn't have artist name false, // Doesn't have artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ???????", // Supported Effects " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ????????", // Supported Effects
" vp?????????????", // Supported Volume Column commands " vp?????????????", // Supported Volume Column commands
}; };
@ -355,7 +355,7 @@ constexpr CModSpecifications it_ =
false, // Doesn't have artist name false, // Doesn't have artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ???????", // Supported Effects " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ????????", // Supported Effects
" vpcdab?h??gfe??", // Supported Volume Column commands " vpcdab?h??gfe??", // Supported Volume Column commands
}; };
@ -402,7 +402,7 @@ constexpr CModSpecifications itEx_ =
true, // Has artist name true, // Has artist name
false, // Doesn't have default resampling false, // Doesn't have default resampling
false, // Integer tempo false, // Integer tempo
" JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\?#?? ???????", // Supported Effects " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\?#?? ????????", // Supported Effects
" vpcdab?h??gfe??", // Supported Volume Column commands " vpcdab?h??gfe??", // Supported Volume Column commands
}; };

View File

@ -30,7 +30,7 @@ const EffectType effectTypes[] =
EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH,
EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH,
EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
}; };
static_assert(std::size(effectTypes) == MAX_EFFECTS); static_assert(std::size(effectTypes) == MAX_EFFECTS);
@ -84,7 +84,7 @@ void ModCommand::ExtendedMODtoS3MEffect()
case 0x70: param = (param & 0x03) | 0x40; break; case 0x70: param = (param & 0x03) | 0x40; break;
case 0x90: command = CMD_RETRIG; param = (param & 0x0F); break; case 0x90: command = CMD_RETRIG; param = (param & 0x0F); break;
case 0xA0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param = (param << 4) | 0x0F; } else command = CMD_NONE; break; case 0xA0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param = (param << 4) | 0x0F; } else command = CMD_NONE; break;
case 0xB0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param |= 0xF0; } else command = CMD_NONE; break; case 0xB0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param = 0xF0 | std::min(param, PARAM(0x0E)); } else command = CMD_NONE; break;
case 0xC0: if(param == 0xC0) { command = CMD_NONE; note = NOTE_NOTECUT; } break; // this does different things in IT and ST3 case 0xC0: if(param == 0xC0) { command = CMD_NONE; note = NOTE_NOTECUT; } break; // this does different things in IT and ST3
case 0xD0: if(param == 0xD0) { command = CMD_NONE; } break; // ditto case 0xD0: if(param == 0xD0) { command = CMD_NONE; } break; // ditto
// rest are the same or handled elsewhere // rest are the same or handled elsewhere
@ -142,6 +142,11 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd
// Apart from these special fixups, do a regular conversion from MOD. // Apart from these special fixups, do a regular conversion from MOD.
fromType = MOD_TYPE_MOD; fromType = MOD_TYPE_MOD;
} }
if(command == CMD_DIGIREVERSESAMPLE && toType != MOD_TYPE_DIGI)
{
command = CMD_S3MCMDEX;
param = 0x9F;
}
// helper variables // helper variables
const bool oldTypeIsMOD = (fromType == MOD_TYPE_MOD), oldTypeIsXM = (fromType == MOD_TYPE_XM), const bool oldTypeIsMOD = (fromType == MOD_TYPE_MOD), oldTypeIsXM = (fromType == MOD_TYPE_XM),
@ -870,7 +875,7 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd
} }
bool ModCommand::IsContinousCommand(const CSoundFile& sndFile) const bool ModCommand::IsContinousCommand(const CSoundFile &sndFile) const
{ {
switch(command) switch(command)
{ {
@ -1036,6 +1041,7 @@ size_t ModCommand::GetEffectWeight(COMMAND cmd)
CMD_VOLUMESLIDE, CMD_VOLUMESLIDE,
CMD_VIBRATOVOL, CMD_VIBRATOVOL,
CMD_VOLUME, CMD_VOLUME,
CMD_DIGIREVERSESAMPLE,
CMD_REVERSEOFFSET, CMD_REVERSEOFFSET,
CMD_OFFSETPERCENTAGE, CMD_OFFSETPERCENTAGE,
CMD_OFFSET, CMD_OFFSET,

View File

@ -107,6 +107,7 @@ enum EffectCommand : uint8
CMD_REVERSEOFFSET = 42, // PTM Nxx Revert sample + offset CMD_REVERSEOFFSET = 42, // PTM Nxx Revert sample + offset
CMD_DBMECHO = 43, // DBM enable/disable echo CMD_DBMECHO = 43, // DBM enable/disable echo
CMD_OFFSETPERCENTAGE = 44, // PLM Percentage Offset CMD_OFFSETPERCENTAGE = 44, // PLM Percentage Offset
CMD_DIGIREVERSESAMPLE = 45, // DIGI reverse sample
MAX_EFFECTS MAX_EFFECTS
}; };

View File

@ -28,10 +28,7 @@ void CPatternContainer::ClearPatterns()
void CPatternContainer::DestroyPatterns() void CPatternContainer::DestroyPatterns()
{ {
for(PATTERNINDEX i = 0; i < m_Patterns.size(); i++) m_Patterns.clear();
{
Remove(i);
}
} }
@ -67,7 +64,7 @@ PATTERNINDEX CPatternContainer::InsertAny(const ROWINDEX rows, bool respectQtyLi
bool CPatternContainer::Insert(const PATTERNINDEX index, const ROWINDEX rows) bool CPatternContainer::Insert(const PATTERNINDEX index, const ROWINDEX rows)
{ {
if(rows > MAX_PATTERN_ROWS || rows == 0) if(rows > MAX_PATTERN_ROWS || rows == 0 || index >= PATTERNINDEX_INVALID)
return false; return false;
if(IsValidPat(index)) if(IsValidPat(index))
return false; return false;

View File

@ -775,13 +775,19 @@ void IMidiPlugin::ApplyPitchWheelDepth(int32 &value, int8 pwd)
// Get the MIDI channel currently associated with a given tracker channel // Get the MIDI channel currently associated with a given tracker channel
uint8 IMidiPlugin::GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const
{
if(auto ins = chn.pModInstrument; ins != nullptr)
return ins->GetMIDIChannel(chn, trackChannel);
else
return 0;
}
uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const uint8 IMidiPlugin::GetMidiChannel(CHANNELINDEX trackChannel) const
{ {
if(trackChannel >= std::size(m_SndFile.m_PlayState.Chn)) if(trackChannel < std::size(m_SndFile.m_PlayState.Chn))
return 0; return GetMidiChannel(m_SndFile.m_PlayState.Chn[trackChannel], trackChannel);
if(auto ins = m_SndFile.m_PlayState.Chn[trackChannel].pModInstrument; ins != nullptr)
return ins->GetMIDIChannel(m_SndFile, trackChannel);
else else
return 0; return 0;
} }
@ -884,7 +890,7 @@ void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vo
uint8 high = static_cast<uint8>(midiBank >> 7); uint8 high = static_cast<uint8>(midiBank >> 7);
uint8 low = static_cast<uint8>(midiBank & 0x7F); uint8 low = static_cast<uint8>(midiBank & 0x7F);
//m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_BANKSEL], 0, m_nSlot + 1); //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_BANKSEL], 0, m_nSlot + 1);
MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Coarse, midiCh, high)); MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Coarse, midiCh, high));
MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Fine, midiCh, low)); MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_BankSelect_Fine, midiCh, low));
@ -897,7 +903,7 @@ void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vo
if(progChanged || (midiProg < 0x80 && bankChanged)) if(progChanged || (midiProg < 0x80 && bankChanged))
{ {
channel.currentProgram = midiProg; channel.currentProgram = midiProg;
//m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.szMidiGlb[MIDIOUT_PROGRAM], 0, m_nSlot + 1); //m_SndFile.ProcessMIDIMacro(trackChannel, false, m_SndFile.m_MidiCfg.Global[MIDIOUT_PROGRAM], 0, m_nSlot + 1);
MidiSend(MIDIEvents::ProgramChange(midiCh, midiProg)); MidiSend(MIDIEvents::ProgramChange(midiCh, midiProg));
} }

View File

@ -25,6 +25,7 @@ OPENMPT_NAMESPACE_BEGIN
struct VSTPluginLib; struct VSTPluginLib;
struct SNDMIXPLUGIN; struct SNDMIXPLUGIN;
struct ModInstrument; struct ModInstrument;
struct ModChannel;
class CSoundFile; class CSoundFile;
class CModDoc; class CModDoc;
class CAbstractVstEditor; class CAbstractVstEditor;
@ -275,9 +276,11 @@ public:
bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override; bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override;
// Get the MIDI channel currently associated with a given tracker channel // Get the MIDI channel currently associated with a given tracker channel
virtual uint8 GetMidiChannel(CHANNELINDEX trackChannel) const; virtual uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const;
protected: protected:
uint8 GetMidiChannel(CHANNELINDEX trackChannel) const;
// Plugin wants to send MIDI to OpenMPT // Plugin wants to send MIDI to OpenMPT
virtual void ReceiveMidi(uint32 midiCode); virtual void ReceiveMidi(uint32 midiCode);
virtual void ReceiveSysex(mpt::const_byte_span sysex); virtual void ReceiveSysex(mpt::const_byte_span sysex);

View File

@ -22,8 +22,8 @@ OPENMPT_NAMESPACE_BEGIN
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Mix Plugins // Mix Plugins
typedef int32 PlugParamIndex; using PlugParamIndex = int32;
typedef float PlugParamValue; using PlugParamValue = float;
struct SNDMIXPLUGINSTATE; struct SNDMIXPLUGINSTATE;
struct SNDMIXPLUGIN; struct SNDMIXPLUGIN;

View File

@ -625,6 +625,7 @@ float I3DL2Reverb::CalcDecayCoeffs(int32 index)
c2 = (c23 - c22) / (c21 + c21); c2 = (c23 - c22) / (c21 + c21);
if(std::abs(c2) > 1.0f) if(std::abs(c2) > 1.0f)
c2 = (-c22 - c23) / (c21 + c21); c2 = (-c22 - c23) / (c21 + c21);
c2 = mpt::sanitize_nan(c2);
} }
m_delayCoeffs[index][0] = c1; m_delayCoeffs[index][0] = c1;
m_delayCoeffs[index][1] = c2; m_delayCoeffs[index][1] = c2;

View File

@ -24,6 +24,10 @@ OPENMPT_NAMESPACE_BEGIN
namespace Tuning { namespace Tuning {
static RATIOTYPE SanitizeGroupRatio(RATIOTYPE ratio)
{
return std::clamp(std::abs(ratio), 1e-15f, 1e+07f);
}
namespace CTuningS11n namespace CTuningS11n
{ {
@ -257,7 +261,12 @@ RATIOTYPE CTuning::GetRatio(const NOTEINDEXTYPE note) const
{ {
return s_DefaultFallbackRatio; return s_DefaultFallbackRatio;
} }
return m_RatioTable[note - m_NoteMin]; const auto ratio = m_RatioTable[note - m_NoteMin];
if(ratio <= 1e-15f)
{
return s_DefaultFallbackRatio;
}
return ratio;
} }
@ -480,6 +489,17 @@ SerializationResult CTuning::InitDeserialize(std::istream &iStrm, mpt::Charset d
UNOTEINDEXTYPE ratiotableSize = 0; UNOTEINDEXTYPE ratiotableSize = 0;
ssb.ReadItem(ratiotableSize, "RTI4"); ssb.ReadItem(ratiotableSize, "RTI4");
m_GroupRatio = SanitizeGroupRatio(m_GroupRatio);
if(!std::isfinite(m_GroupRatio))
{
return SerializationResult::Failure;
}
for(auto ratio : m_RatioTable)
{
if(!std::isfinite(ratio))
return SerializationResult::Failure;
}
// If reader status is ok and m_NoteMin is somewhat reasonable, process data. // If reader status is ok and m_NoteMin is somewhat reasonable, process data.
if(!((ssb.GetStatus() & srlztn::SNT_FAILURE) == 0 && m_NoteMin >= -300 && m_NoteMin <= 300)) if(!((ssb.GetStatus() & srlztn::SNT_FAILURE) == 0 && m_NoteMin >= -300 && m_NoteMin <= 300))
{ {
@ -683,6 +703,11 @@ SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Chars
return SerializationResult::Failure; return SerializationResult::Failure;
} }
} }
for(auto ratio : m_RatioTable)
{
if(!std::isfinite(ratio))
return SerializationResult::Failure;
}
//Fineratios //Fineratios
if(version <= 2) if(version <= 2)
@ -698,6 +723,11 @@ SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Chars
return SerializationResult::Failure; return SerializationResult::Failure;
} }
} }
for(auto ratio : m_RatioTableFine)
{
if(!std::isfinite(ratio))
return SerializationResult::Failure;
}
m_FineStepCount = mpt::saturate_cast<USTEPINDEXTYPE>(m_RatioTableFine.size()); m_FineStepCount = mpt::saturate_cast<USTEPINDEXTYPE>(m_RatioTableFine.size());
// m_NoteMin // m_NoteMin
@ -721,8 +751,8 @@ SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Chars
//m_GroupRatio //m_GroupRatio
IEEE754binary32LE groupratio = IEEE754binary32LE(0.0f); IEEE754binary32LE groupratio = IEEE754binary32LE(0.0f);
mpt::IO::Read(inStrm, groupratio); mpt::IO::Read(inStrm, groupratio);
m_GroupRatio = groupratio; m_GroupRatio = SanitizeGroupRatio(groupratio);
if(m_GroupRatio < 0) if(!std::isfinite(m_GroupRatio))
{ {
return SerializationResult::Failure; return SerializationResult::Failure;
} }

View File

@ -1,4 +1,4 @@
Copyright (c) 2004-2021, OpenMPT Project Developers and Contributors Copyright (c) 2004-2022, OpenMPT Project Developers and Contributors
Copyright (c) 1997-2003, Olivier Lapicque Copyright (c) 1997-2003, Olivier Lapicque
All rights reserved. All rights reserved.

View File

@ -30,9 +30,9 @@ inline namespace MPT_INLINE_NS {
#if MPT_CXX_AT_LEAST(20) #if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(14, 0, 0)
using std::bit_cast; using std::bit_cast;
#else #else // !C++20
// C++2a compatible bit_cast. // C++2a compatible bit_cast.
// Not implementing constexpr because this is not easily possible pre C++20. // Not implementing constexpr because this is not easily possible pre C++20.
template <typename Tdst, typename Tsrc> template <typename Tdst, typename Tsrc>
@ -41,7 +41,7 @@ MPT_FORCEINLINE typename std::enable_if<(sizeof(Tdst) == sizeof(Tsrc)) && std::i
std::memcpy(&dst, &src, sizeof(Tdst)); std::memcpy(&dst, &src, sizeof(Tdst));
return dst; return dst;
} }
#endif #endif // C++20
@ -79,11 +79,18 @@ constexpr bool endian_is_weird() noexcept {
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ #elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define MPT_PLATFORM_LITTLE_ENDIAN #define MPT_PLATFORM_LITTLE_ENDIAN
#endif #endif
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && defined(__ORDER_LITTLE_ENDIAN__)
#if __ORDER_BIG_ENDIAN__ != __ORDER_LITTLE_ENDIAN__
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define MPT_PLATFORM_BIG_ENDIAN
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define MPT_PLATFORM_LITTLE_ENDIAN
#endif
#endif
#endif #endif
// fallback: // fallback:
#if !defined(MPT_PLATFORM_BIG_ENDIAN) && !defined(MPT_PLATFORM_LITTLE_ENDIAN) #if !defined(MPT_PLATFORM_BIG_ENDIAN) && !defined(MPT_PLATFORM_LITTLE_ENDIAN)
// taken from boost/detail/endian.hpp
#if (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) \ #if (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) \
|| (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) \ || (defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)) \
|| (defined(_STLP_BIG_ENDIAN) && !defined(_STLP_LITTLE_ENDIAN)) || (defined(_STLP_BIG_ENDIAN) && !defined(_STLP_LITTLE_ENDIAN))
@ -92,19 +99,13 @@ constexpr bool endian_is_weird() noexcept {
|| (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) \ || (defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)) \
|| (defined(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN)) || (defined(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN))
#define MPT_PLATFORM_LITTLE_ENDIAN #define MPT_PLATFORM_LITTLE_ENDIAN
#elif defined(__sparc) || defined(__sparc__) \ #elif defined(__hpux) || defined(__hppa) \
|| defined(_POWER) || defined(__powerpc__) \ || defined(_MIPSEB) \
|| defined(__ppc__) || defined(__hpux) || defined(__hppa) \
|| defined(_MIPSEB) || defined(_POWER) \
|| defined(__s390__) || defined(__s390__)
#define MPT_PLATFORM_BIG_ENDIAN #define MPT_PLATFORM_BIG_ENDIAN
#elif defined(__i386__) || defined(__alpha__) \ #elif defined(__i386__) || defined(_M_IX86) \
|| defined(__ia64) || defined(__ia64__) \ || defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \
|| defined(_M_IX86) || defined(_M_IA64) \ || defined(__bfin__)
|| defined(_M_ALPHA) || defined(__amd64) \
|| defined(__amd64__) || defined(_M_AMD64) \
|| defined(__x86_64) || defined(__x86_64__) \
|| defined(_M_X64) || defined(__bfin__)
#define MPT_PLATFORM_LITTLE_ENDIAN #define MPT_PLATFORM_LITTLE_ENDIAN
#endif #endif
#endif #endif
@ -182,7 +183,7 @@ MPT_FORCEINLINE bool endian_is_weird() noexcept {
#if MPT_CXX_AT_LEAST(20) && !MPT_COMPILER_MSVC #if MPT_CXX_AT_LEAST(20) && !MPT_COMPILER_MSVC && !MPT_CLANG_BEFORE(12, 0, 0)
// Disabled for VS2022 for now because of // Disabled for VS2022 for now because of
// <https://developercommunity.visualstudio.com/t/vs2022-cl-193030705-generates-non-universally-avai/1578571> // <https://developercommunity.visualstudio.com/t/vs2022-cl-193030705-generates-non-universally-avai/1578571>

View File

@ -6,10 +6,10 @@
#include "mpt/base/detect_compiler.hpp" #include "mpt/base/detect_compiler.hpp"
#include "mpt/base/detect_libc.hpp"
#include "mpt/base/detect_libcxx.hpp"
#include "mpt/base/detect_os.hpp" #include "mpt/base/detect_os.hpp"
#include "mpt/base/detect_quirks.hpp" #include "mpt/base/detect_quirks.hpp"
#include "mpt/base/detect_libcxx.hpp"
#include "mpt/base/detect_libc.hpp"

View File

@ -146,7 +146,9 @@
#if MPT_COMPILER_GENERIC || MPT_COMPILER_GCC || MPT_COMPILER_CLANG #if MPT_COMPILER_GENERIC || MPT_COMPILER_GCC || MPT_COMPILER_CLANG
#if (__cplusplus >= 202002) #if (__cplusplus >= 202002)
#define MPT_CXX 20 // Support for C++20 is lacking across all compilers.
// Only assume C++17 for non-MSVC, even when in C++20 mode.
#define MPT_CXX 17
#elif (__cplusplus >= 201703) #elif (__cplusplus >= 201703)
#define MPT_CXX 17 #define MPT_CXX 17
#else #else

View File

@ -7,7 +7,6 @@
#include "mpt/base/detect_compiler.hpp" #include "mpt/base/detect_compiler.hpp"
#include "mpt/base/detect_os.hpp" #include "mpt/base/detect_os.hpp"
#include "mpt/base/detect_quirks.hpp"
#include <cstddef> #include <cstddef>
@ -18,7 +17,7 @@
#define MPT_LIBC_GENERIC 1 #define MPT_LIBC_GENERIC 1
#elif MPT_COMPILER_GCC && (defined(__MINGW32__) || defined(__MINGW64__)) #elif MPT_COMPILER_GCC && (defined(__MINGW32__) || defined(__MINGW64__))
#define MPT_LIBC_MS 1 #define MPT_LIBC_MS 1
#elif defined(__GNU_LIBRARY__) #elif defined(__GLIBC__)
#define MPT_LIBC_GLIBC 1 #define MPT_LIBC_GLIBC 1
#elif MPT_COMPILER_MSVC #elif MPT_COMPILER_MSVC
#define MPT_LIBC_MS 1 #define MPT_LIBC_MS 1

View File

@ -7,7 +7,6 @@
#include "mpt/base/detect_compiler.hpp" #include "mpt/base/detect_compiler.hpp"
#include "mpt/base/detect_os.hpp" #include "mpt/base/detect_os.hpp"
#include "mpt/base/detect_quirks.hpp"
#if MPT_CXX_AT_LEAST(20) #if MPT_CXX_AT_LEAST(20)
#include <version> #include <version>

View File

@ -9,7 +9,7 @@
#include "mpt/base/integer.hpp" #include "mpt/base/integer.hpp"
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#if MPT_CXX_AT_LEAST(20) #if MPT_CXX_AT_LEAST(20) && !MPT_MSVC_BEFORE(2022, 0) && !MPT_COMPILER_CLANG
#include <source_location> #include <source_location>
#endif // C++20 #endif // C++20
@ -19,7 +19,7 @@ namespace mpt {
inline namespace MPT_INLINE_NS { inline namespace MPT_INLINE_NS {
#if MPT_CXX_AT_LEAST(20) && !MPT_MSVC_BEFORE(2022, 0) #if MPT_CXX_AT_LEAST(20) && !MPT_MSVC_BEFORE(2022, 0) && !MPT_COMPILER_CLANG
using std::source_location; using std::source_location;

View File

@ -23,7 +23,7 @@ namespace mpt {
inline namespace MPT_INLINE_NS { inline namespace MPT_INLINE_NS {
#if MPT_CXX_AT_LEAST(20) #if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(13, 0, 0)
using std::in_range; using std::in_range;
@ -78,7 +78,7 @@ inline void reset(T & x) {
#if MPT_CXX_AT_LEAST(20) #if MPT_CXX_AT_LEAST(20) && !MPT_CLANG_BEFORE(13, 0, 0)
using std::cmp_equal; using std::cmp_equal;
using std::cmp_greater; using std::cmp_greater;

View File

@ -30,7 +30,7 @@ MPT_WARNING("C stdlib does not provide math constants. Please #define _USE_MATH_
#endif #endif
#endif #endif
#if !MPT_LIBC_MS #if MPT_LIBC_GLIBC
#if !defined(_FILE_OFFSET_BITS) #if !defined(_FILE_OFFSET_BITS)
#ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_FILE_OFFSET_BITS #ifndef MPT_CHECK_LIBC_IGNORE_WARNING_NO_FILE_OFFSET_BITS
MPT_WARNING("C stdlib may not provide 64bit std::FILE access. Please #define _FILE_OFFSET_BITS=64.") MPT_WARNING("C stdlib may not provide 64bit std::FILE access. Please #define _FILE_OFFSET_BITS=64.")

View File

@ -8,22 +8,22 @@
#include "mpt/base/detect.hpp" #include "mpt/base/detect.hpp"
#if MPT_MSVC_AT_LEAST(2019, 4) || MPT_GCC_AT_LEAST(11, 1, 0) #if MPT_MSVC_AT_LEAST(2019, 4) || MPT_GCC_AT_LEAST(11, 1, 0)
#define MPT_FORMAT_CXX17_FLOAT 1 #define MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17 1
#else #else
#define MPT_FORMAT_CXX17_FLOAT 0 #define MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17 0
#endif #endif
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include "mpt/base/algorithm.hpp" #include "mpt/base/algorithm.hpp"
#endif #endif
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#include "mpt/format/helpers.hpp" #include "mpt/format/helpers.hpp"
#include "mpt/string_transcode/transcode.hpp" #include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <charconv> #include <charconv>
#endif #endif
#if !MPT_FORMAT_CXX17_FLOAT #if !MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <iomanip> #include <iomanip>
#include <ios> #include <ios>
#include <limits> #include <limits>
@ -31,7 +31,7 @@
#include <sstream> #include <sstream>
#endif #endif
#include <string> #include <string>
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <system_error> #include <system_error>
#endif #endif
#include <type_traits> #include <type_traits>
@ -42,7 +42,7 @@ namespace mpt {
inline namespace MPT_INLINE_NS { inline namespace MPT_INLINE_NS {
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
inline Tstring to_chars_string(const T & x) { inline Tstring to_chars_string(const T & x) {
std::string str(1, '\0'); std::string str(1, '\0');
@ -66,7 +66,7 @@ inline Tstring format_value_default(const T & x) {
#endif #endif
#if !MPT_FORMAT_CXX17_FLOAT #if !MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true> template <typename Tstring, typename T, std::enable_if_t<std::is_floating_point<T>::value, bool> = true>
inline Tstring to_stream_string(const T & x) { inline Tstring to_stream_string(const T & x) {
using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type; using stream_char_type = typename mpt::select_format_char_type<typename Tstring::value_type>::type;

View File

@ -7,31 +7,31 @@
#include "mpt/base/detect.hpp" #include "mpt/base/detect.hpp"
#if 1 #if 1
#define MPT_FORMAT_CXX17_INT 1 #define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 1
#else #else
#define MPT_FORMAT_CXX17_INT 0 #define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 0
#endif #endif
#if MPT_FORMAT_CXX17_INT #if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include "mpt/base/algorithm.hpp" #include "mpt/base/algorithm.hpp"
#endif // MPT_FORMAT_CXX17_INT #endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#include "mpt/base/utility.hpp" #include "mpt/base/utility.hpp"
#include "mpt/format/helpers.hpp" #include "mpt/format/helpers.hpp"
#include "mpt/string_transcode/transcode.hpp" #include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_INT #if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <charconv> #include <charconv>
#endif // MPT_FORMAT_CXX17_INT #endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#if !MPT_FORMAT_CXX17_INT #if !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <ios> #include <ios>
#include <locale> #include <locale>
#include <sstream> #include <sstream>
#endif // !MPT_FORMAT_CXX17_INT #endif // !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <string> #include <string>
#if MPT_FORMAT_CXX17_INT #if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <system_error> #include <system_error>
#endif // MPT_FORMAT_CXX17_INT #endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <type_traits> #include <type_traits>
@ -40,7 +40,7 @@ namespace mpt {
inline namespace MPT_INLINE_NS { inline namespace MPT_INLINE_NS {
#if MPT_FORMAT_CXX17_INT #if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true> template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
inline Tstring to_chars_string(const T & x) { inline Tstring to_chars_string(const T & x) {
@ -73,10 +73,10 @@ inline Tstring format_value_default(const T & x) {
return mpt::transcode<Tstring>(mpt::to_chars_string<typename mpt::select_format_string_type<Tstring>::type>(x)); return mpt::transcode<Tstring>(mpt::to_chars_string<typename mpt::select_format_string_type<Tstring>::type>(x));
} }
#endif // MPT_FORMAT_CXX17_INT #endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#if !MPT_FORMAT_CXX17_INT #if !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true> template <typename Tstring, typename T, std::enable_if_t<std::is_integral<T>::value, bool> = true>
inline Tstring to_stream_string(const T & x) { inline Tstring to_stream_string(const T & x) {
@ -98,7 +98,7 @@ inline Tstring format_value_default(const T & x) {
return mpt::transcode<Tstring>(mpt::to_stream_string<typename mpt::select_format_string_type<Tstring>::type>(x)); return mpt::transcode<Tstring>(mpt::to_stream_string<typename mpt::select_format_string_type<Tstring>::type>(x));
} }
#endif // !MPT_FORMAT_CXX17_INT #endif // !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
template <typename Tstring, typename T, std::enable_if_t<std::is_enum<T>::value, bool> = true> template <typename Tstring, typename T, std::enable_if_t<std::is_enum<T>::value, bool> = true>

View File

@ -5,32 +5,41 @@
#if MPT_FORMAT_CXX17_FLOAT #include "mpt/base/detect.hpp"
#if MPT_MSVC_AT_LEAST(2019, 4) || MPT_GCC_AT_LEAST(11, 1, 0)
#define MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17 1
#else
#define MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17 0
#endif
#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include "mpt/base/algorithm.hpp" #include "mpt/base/algorithm.hpp"
#endif #endif
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#include "mpt/format/default_floatingpoint.hpp" #include "mpt/format/default_floatingpoint.hpp"
#include "mpt/format/helpers.hpp" #include "mpt/format/helpers.hpp"
#include "mpt/format/simple_spec.hpp" #include "mpt/format/simple_spec.hpp"
#include "mpt/string/types.hpp"
#include "mpt/string_transcode/transcode.hpp" #include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <charconv> #include <charconv>
#endif #endif
#if !MPT_FORMAT_CXX17_FLOAT #if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <iomanip> #include <iomanip>
#include <ios> #include <ios>
#endif #endif
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <iterator> #include <iterator>
#endif #endif
#if !MPT_FORMAT_CXX17_FLOAT #if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <limits> #include <limits>
#include <locale> #include <locale>
#include <sstream> #include <sstream>
#endif #endif
#include <string> #include <string>
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <system_error> #include <system_error>
#endif #endif
#include <type_traits> #include <type_traits>
@ -42,7 +51,7 @@ inline namespace MPT_INLINE_NS {
#if MPT_FORMAT_CXX17_FLOAT #if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
template <typename Tstring, typename T> template <typename Tstring, typename T>
@ -128,7 +137,7 @@ inline Tstring format_simple(const T & x, const format_simple_spec & f) {
} }
#else // !MPT_FORMAT_CXX17_FLOAT #else // !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
template <typename Tchar> template <typename Tchar>
@ -229,7 +238,7 @@ inline Tstring format_simple(const T & x, const format_simple_spec & format) {
#endif // MPT_FORMAT_CXX17_FLOAT #endif // MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17

View File

@ -9,6 +9,7 @@
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#include "mpt/format/helpers.hpp" #include "mpt/format/helpers.hpp"
#include "mpt/format/simple_spec.hpp" #include "mpt/format/simple_spec.hpp"
#include "mpt/string/types.hpp"
#include <charconv> #include <charconv>
#include <string> #include <string>

View File

@ -5,6 +5,7 @@
#include "mpt/base/detect.hpp"
#include "mpt/base/namespace.hpp" #include "mpt/base/namespace.hpp"
#include "mpt/detect/mfc.hpp" #include "mpt/detect/mfc.hpp"

View File

@ -47,14 +47,137 @@ private:
static MPT_CONSTEXPRINLINE SampleFormat::Enum Sanitize(T x) noexcept static MPT_CONSTEXPRINLINE SampleFormat::Enum Sanitize(T x) noexcept
{ {
using uT = typename std::make_unsigned<T>::type; using uT = typename std::make_unsigned<T>::type;
uT ux = static_cast<uT>(x); uT val = static_cast<uT>(x);
if(ux == static_cast<uT>(-8)) if(!val)
{ {
ux = 8 + 1; return SampleFormat::Enum::Invalid;
}
if(val == static_cast<uT>(-8))
{
val = 8 + 1;
} }
// float|64|32|16|8|?|?|unsigned // float|64|32|16|8|?|?|unsigned
ux &= 0b11111001; val &= 0b1'1111'00'1;
return static_cast<SampleFormat::Enum>(ux); auto is_float = [](uT val) -> bool
{
return (val & 0b1'0000'00'0) ? true : false;
};
auto is_unsigned = [](uT val) -> bool
{
return (val & 0b0'0000'00'1) ? true : false;
};
auto has_size = [](uT val) -> bool
{
return (val & 0b0'1111'00'0) ? true : false;
};
auto unique_size = [](uT val) -> bool
{
val &= 0b0'1111'00'0;
if(val == 0b0'0001'00'0)
{
return true;
} else if(val == 0b0'0010'00'0)
{
return true;
} else if(val == 0b0'0011'00'0)
{
return true;
} else if(val == 0b0'0100'00'0)
{
return true;
} else if(val == 0b0'1000'00'0)
{
return true;
} else
{
return false;
}
};
auto get_size = [](uT val) -> std::size_t
{
val &= 0b0'1111'00'0;
if(val == 0b0'0001'00'0)
{
return 1;
} else if(val == 0b0'0010'00'0)
{
return 2;
} else if(val == 0b0'0011'00'0)
{
return 3;
} else if(val == 0b0'0100'00'0)
{
return 4;
} else if(val == 0b0'1000'00'0)
{
return 8;
} else
{
return 2; // default to 16bit
}
};
if(!has_size(val))
{
if(is_unsigned(val) && is_float(val))
{
val = mpt::to_underlying(Enum::Invalid);
} else if(is_unsigned(val))
{
val = mpt::to_underlying(Enum::Unsigned8);
} else if(is_float(val))
{
val = mpt::to_underlying(Enum::Float32);
} else
{
val = mpt::to_underlying(Enum::Invalid);
}
} else if(!unique_size(val))
{
// order of size check matters
if((val & 0b0'0011'00'0) == 0b0'0011'00'0)
{
val = mpt::to_underlying(Enum::Int24);
} else if(val & 0b0'0010'00'0)
{
val = mpt::to_underlying(Enum::Int16);
} else if(val & 0b0'0100'00'0)
{
if(is_float(val))
{
val = mpt::to_underlying(Enum::Float32);
} else
{
val = mpt::to_underlying(Enum::Int32);
}
} else if(val & 0b0'1000'00'0)
{
val = mpt::to_underlying(Enum::Float64);
} else if(val & 0b0'0001'00'0)
{
if(is_unsigned(val))
{
val = mpt::to_underlying(Enum::Unsigned8);
} else
{
val = mpt::to_underlying(Enum::Int8);
}
}
} else
{
if(is_unsigned(val) && (get_size(val) > 1))
{
val &= ~0b0'0000'00'1; // remove unsigned
}
if(is_float(val) && (get_size(val) < 4))
{
val &= ~0b1'0000'00'0; // remove float
}
if(!is_float(val) && (get_size(val) == 8))
{
val |= 0b1'0000'00'0; // add float
}
}
return static_cast<SampleFormat::Enum>(val);
} }
public: public:
@ -95,42 +218,59 @@ public:
MPT_CONSTEXPRINLINE bool IsValid() const noexcept MPT_CONSTEXPRINLINE bool IsValid() const noexcept
{ {
return value != SampleFormat::Invalid; return false
|| (value == SampleFormat::Unsigned8)
|| (value == SampleFormat::Int8)
|| (value == SampleFormat::Int16)
|| (value == SampleFormat::Int24)
|| (value == SampleFormat::Int32)
|| (value == SampleFormat::Float32)
|| (value == SampleFormat::Float64);
} }
MPT_CONSTEXPRINLINE bool IsUnsigned() const noexcept MPT_CONSTEXPRINLINE bool IsUnsigned() const noexcept
{ {
return IsValid() && (value == SampleFormat::Unsigned8); return false
|| (value == SampleFormat::Unsigned8);
} }
MPT_CONSTEXPRINLINE bool IsFloat() const noexcept MPT_CONSTEXPRINLINE bool IsFloat() const noexcept
{ {
return IsValid() && ((value == SampleFormat::Float32) || (value == SampleFormat::Float64)); return false
|| (value == SampleFormat::Float32)
|| (value == SampleFormat::Float64);
} }
MPT_CONSTEXPRINLINE bool IsInt() const noexcept MPT_CONSTEXPRINLINE bool IsInt() const noexcept
{ {
return IsValid() && ((value != SampleFormat::Float32) && (value != SampleFormat::Float64)); return false
|| (value == SampleFormat::Unsigned8)
|| (value == SampleFormat::Int8)
|| (value == SampleFormat::Int16)
|| (value == SampleFormat::Int24)
|| (value == SampleFormat::Int32);
} }
MPT_CONSTEXPRINLINE uint8 GetSampleSize() const noexcept MPT_CONSTEXPRINLINE uint8 GetSampleSize() const noexcept
{ {
return !IsValid() ? 0 : (value == SampleFormat::Unsigned8) ? 1 return !IsValid() ? 0
: (value == SampleFormat::Int8) ? 1 : (value == SampleFormat::Unsigned8) ? 1
: (value == SampleFormat::Int16) ? 2 : (value == SampleFormat::Int8) ? 1
: (value == SampleFormat::Int24) ? 3 : (value == SampleFormat::Int16) ? 2
: (value == SampleFormat::Int32) ? 4 : (value == SampleFormat::Int24) ? 3
: (value == SampleFormat::Float32) ? 4 : (value == SampleFormat::Int32) ? 4
: (value == SampleFormat::Float64) ? 8 : (value == SampleFormat::Float32) ? 4
: 0; : (value == SampleFormat::Float64) ? 8
: 0;
} }
MPT_CONSTEXPRINLINE uint8 GetBitsPerSample() const noexcept MPT_CONSTEXPRINLINE uint8 GetBitsPerSample() const noexcept
{ {
return !IsValid() ? 0 : (value == SampleFormat::Unsigned8) ? 8 return !IsValid() ? 0
: (value == SampleFormat::Int8) ? 8 : (value == SampleFormat::Unsigned8) ? 8
: (value == SampleFormat::Int16) ? 16 : (value == SampleFormat::Int8) ? 8
: (value == SampleFormat::Int24) ? 24 : (value == SampleFormat::Int16) ? 16
: (value == SampleFormat::Int32) ? 32 : (value == SampleFormat::Int24) ? 24
: (value == SampleFormat::Float32) ? 32 : (value == SampleFormat::Int32) ? 32
: (value == SampleFormat::Float64) ? 64 : (value == SampleFormat::Float32) ? 32
: 0; : (value == SampleFormat::Float64) ? 64
: 0;
} }
MPT_CONSTEXPRINLINE operator SampleFormat::Enum() const noexcept MPT_CONSTEXPRINLINE operator SampleFormat::Enum() const noexcept

View File

@ -38,6 +38,7 @@
#include "openmpt/soundbase/SampleConvert.hpp" #include "openmpt/soundbase/SampleConvert.hpp"
#include "openmpt/soundbase/SampleDecode.hpp" #include "openmpt/soundbase/SampleDecode.hpp"
#include "openmpt/soundbase/SampleEncode.hpp" #include "openmpt/soundbase/SampleEncode.hpp"
#include "openmpt/soundbase/SampleFormat.hpp"
#include "../soundlib/SampleCopy.h" #include "../soundlib/SampleCopy.h"
#include "../soundlib/SampleNormalize.h" #include "../soundlib/SampleNormalize.h"
#include "../soundlib/ModSampleCopy.h" #include "../soundlib/ModSampleCopy.h"
@ -841,6 +842,101 @@ static MPT_NOINLINE void TestMisc1()
VERIFY_EQUAL(CModSpecifications::ExtensionToType("s2m"), MOD_TYPE_NONE); VERIFY_EQUAL(CModSpecifications::ExtensionToType("s2m"), MOD_TYPE_NONE);
VERIFY_EQUAL(CModSpecifications::ExtensionToType(""), MOD_TYPE_NONE); VERIFY_EQUAL(CModSpecifications::ExtensionToType(""), MOD_TYPE_NONE);
// invalid
VERIFY_EQUAL(SampleFormat::FromInt(0), SampleFormat::Invalid);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'11'0), SampleFormat::Invalid);
// correct
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'1), SampleFormat::Unsigned8);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'0), SampleFormat::Int8);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'0), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'0), SampleFormat::Float64);
// no size
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'00'0), SampleFormat::Invalid);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0000'00'1), SampleFormat::Unsigned8);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0000'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0000'00'1), SampleFormat::Invalid);
// invalid unsigned
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'1), SampleFormat::Int32);
// invalid float
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'0), SampleFormat::Int8);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'0), SampleFormat::Int24);
// bogus size
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'0), SampleFormat::Int8);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'0), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0101'00'0), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0110'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0111'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1000'00'0), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1001'00'0), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1011'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1100'00'0), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1101'00'0), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1110'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1111'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0001'00'1), SampleFormat::Unsigned8);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0010'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0011'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0100'00'1), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0101'00'1), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0110'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'0111'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1000'00'1), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1001'00'1), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1010'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1011'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1100'00'1), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1101'00'1), SampleFormat::Int32);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1110'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b0'1111'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'0), SampleFormat::Int8);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0101'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0110'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0111'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'0), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1001'00'0), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1010'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1011'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1100'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1101'00'0), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1110'00'0), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1111'00'0), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0001'00'1), SampleFormat::Unsigned8);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0010'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0011'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0100'00'1), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0101'00'1), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0110'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'0111'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1000'00'1), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1001'00'1), SampleFormat::Float64);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1010'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1011'00'1), SampleFormat::Int24);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1100'00'1), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1101'00'1), SampleFormat::Float32);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1110'00'1), SampleFormat::Int16);
VERIFY_EQUAL(SampleFormat::FromInt(0b1'1111'00'1), SampleFormat::Int24);
} }