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
All rights reserved.

View File

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

View File

@ -47,6 +47,14 @@ How to compile
OpenMPT for, as Visual Studio only builds one architecture configuration
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,
or Windows 11 (or later) ARM64.

View File

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

View File

@ -18,6 +18,12 @@ include build/make/config-clang.mk
NO_LTDL?=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)
# 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
CXX ?= c++
LD ?= c++

View File

@ -1,2 +1,4 @@
$(warning warning: CONFIG=haiku is deprecated. The OS is auto-detected.)
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
#pragma once
#define OPENMPT_VERSION_SVNVERSION "16293"
#define OPENMPT_VERSION_REVISION 16293
#define OPENMPT_VERSION_SVNVERSION "16764"
#define OPENMPT_VERSION_REVISION 16764
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.0"
#define OPENMPT_VERSION_DATE "2021-12-23T14:50:28.728256Z"
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.6.1"
#define OPENMPT_VERSION_DATE "2022-01-30T16:49:19.812343Z"
#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)
mpt::ustring ToUString(const CString & x) { return mpt::ToUnicode(x); }
#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 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); }
@ -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 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_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 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 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); }
@ -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 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
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"
#endif
"\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"
"\n"
"Developers:\n"
"Johannes Schultz (2008-2021)\n"
"J\xC3\xB6rn Heusipp (2012-2021)\n"
"Johannes Schultz (2008-2022)\n"
"J\xC3\xB6rn Heusipp (2012-2022)\n"
"Ahti Lepp\xC3\xA4nen (2005-2011)\n"
"Robin Fernandes (2004-2007)\n"
"Sergiy Pylypenko (2007)\n"
@ -792,7 +792,7 @@ mpt::ustring GetFullCreditsString()
mpt::ustring GetLicenseString()
{
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"
"All rights reserved." "\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.
#define VER_MAJORMAJOR 1
#define VER_MAJOR 30
#define VER_MINOR 01
#define VER_MINOR 02
#define VER_MINORMINOR 00
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
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
All rights reserved.

View File

@ -5,6 +5,35 @@ Changelog {#changelog}
For fully detailed change log, please see the source repository directly. This
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)
* [**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
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
supported.
* [**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.
* `openmpt::module::set_position_seconds()` accuracy has been improved for
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: Pitch/Pan Separation was affected by note-off commands, and wasn't reset
by panning commands like in Impulse Tracker.

View File

@ -11,6 +11,15 @@ Dependencies
* Supported compilers for building libopenmpt:
* **Microsoft Visual Studio 2017** or higher, running on a amd64 build
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
* **Clang 7** or higher
* **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 ) {
std::ostringstream about;
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 << 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
#define LIBOPENMPT_DEPRECATED_STRING( str ) str
#endif
#else
#define LIBOPENMPT_DEPRECATED_STRING( str ) str
#endif

View File

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

View File

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

View File

@ -192,7 +192,7 @@ BEGIN
VALUE "FileDescription", VER_FILEDESC_STR
VALUE "FileVersion", VER_FILEVERSION_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 "ProductName", "libopenmpt"
VALUE "ProductVersion", VER_FILEVERSION_STR

View File

@ -519,7 +519,7 @@ static void clear_current_timeinfo() {
static void WINAPI openmpt_About( HWND win ) {
std::ostringstream about;
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 << std::endl;
about << openmpt::string::get( "contact" ) << std::endl;

View File

@ -8,7 +8,7 @@
*/
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"
"All rights reserved." "\n"
"" "\n"
@ -464,7 +464,7 @@ static std::string seconds_to_string( double time ) {
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 << "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 ) {
log << std::endl;
return;
@ -541,7 +541,7 @@ static void show_info( std::ostream & log, bool verbose ) {
static void show_man_version( textout & log ) {
log << "openmpt123" << " v" << OPENMPT123_VERSION_STRING << 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 ) {

View File

@ -1047,6 +1047,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
instruments.clear();
DLSENVELOPE dlsEnv;
int32 instrAttenuation = 0;
int16 instrFinetune = 0;
// Default Envelope Values
dlsEnv.wVolAttack = 0;
dlsEnv.wVolDecay = 0;
@ -1069,6 +1070,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
continue;
for(const auto &gen : generators)
{
const int16 value = static_cast<int16>(gen.genAmount);
switch(gen.sfGenOper)
{
case SF2_GEN_ATTACKVOLENV:
@ -1096,7 +1098,13 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
keyRange = gen.genAmount;
break;
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;
#if 0
default:
@ -1128,7 +1136,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
DLSREGION globalZone{};
globalZone.uUnityNote = 0xFF; // 0xFF means undefined -> use sample root note
globalZone.tuning = 100;
globalZone.sFineTune = 0;
globalZone.sFineTune = instrFinetune;
globalZone.nWaveLink = Util::MaxValueOfType(globalZone.nWaveLink);
if(keyRange != 0xFFFF)
{
@ -1187,7 +1195,7 @@ bool CDLSBank::ConvertSF2ToDLS(SF2LoaderInfo &sf2info)
}
break;
case SF2_GEN_UNITYNOTE:
if (value < 128) rgn.uUnityNote = (uint8)value;
if (value < 128) rgn.uUnityNote = static_cast<uint8>(value);
break;
case SF2_GEN_ATTACKVOLENV:
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())
{
#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
return false;
}

View File

@ -111,6 +111,9 @@ protected:
public:
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 uint32 MakeMelodicCode(uint32 bank, uint32 instr) { return ((bank << 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.
#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)
{
@ -417,11 +417,16 @@ void CSoundFile::CreateStereoMix(int count)
else if(m_SamplePlayLengths != nullptr)
{
// Detecting the longest play time for each sample for optimization
SmpLength pos = chn.position.GetUInt();
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);
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

View File

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

View File

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

View File

@ -44,12 +44,11 @@ static void ReadDIGIPatternEntry(FileReader &file, ModCommand &m)
switch(m.param & 0xF0)
{
case 0x30:
// E30 / E31: Play sample backwards (with some weird parameters that we won't support for now)
if(m.param <= 0x31)
{
m.command = CMD_S3MCMDEX;
m.param = 0x9F;
}
// E3x: Play sample backwards (E30 stops sample when it reaches the beginning, any other value plays it from the beginning including regular loop)
// 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_DIGIREVERSESAMPLE;
m.param &= 0x0F;
break;
case 0x40:
// E40: Stop playing sample

View File

@ -414,6 +414,9 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags)
{
m->command = CMD_TEMPO;
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
{
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);
mpt::String::WriteBuf(mpt::String::nullTerminated, itHeader.songname) = m_songName;
itHeader.highlight_minor = (uint8)std::min(m_nDefaultRowsPerBeat, ROWINDEX(uint8_max));
itHeader.highlight_major = (uint8)std::min(m_nDefaultRowsPerMeasure, ROWINDEX(uint8_max));
itHeader.highlight_minor = mpt::saturate_cast<uint8>(m_nDefaultRowsPerBeat);
itHeader.highlight_major = mpt::saturate_cast<uint8>(m_nDefaultRowsPerMeasure);
if(GetType() == MOD_TYPE_MPT)
{
@ -2470,7 +2470,10 @@ void CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel
ModSample &sample = Samples[smp];
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
{
uint8le note;
uint8le instr;
uint8le volcmd;
uint8le command;
uint8le vol;
uint8le param;
uint8 note;
uint8 instr;
uint8 volcmd;
uint8 command;
uint8 vol;
uint8 param;
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;
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.command = (command < MAX_EFFECTS) ? static_cast<EffectCommand>(command.get()) : CMD_NONE;
result.volcmd = (volcmd < MAX_VOLCMDS) ? static_cast<VolumeCommand>(volcmd.get()) : VOLCMD_NONE;
result.volcmd = (volcmd < std::size(ITPVolCmds)) ? ITPVolCmds[volcmd] : VOLCMD_NONE;
result.command = (command < std::size(ITPCommands)) ? ITPCommands[command] : CMD_NONE;
result.vol = vol;
result.param = param;
return result;
@ -281,8 +301,8 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags)
if(pat < numNamedPats)
{
char patName[32];
pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen);
Patterns[pat].SetName(patName);
if(pattNames.ReadString<mpt::String::maybeNullTerminated>(patName, patNameLen))
Patterns[pat].SetName(patName);
}
// 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
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
if(m.param == 0 && m.vol == 0)
{
m.command = CMD_S3MCMDEX;
m.param = 0x9F;
if(m.IsNote())
{
m.command = CMD_S3MCMDEX;
m.param = 0x9F;
}
} else
{
// 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)
m_MidiCfg.ClearZxxMacros();
strcpy(m_MidiCfg.szMidiSFXExt[0], "Cc z");
m_MidiCfg.SFx[0] = "Cc z";
file.Rewind();
PATTERNINDEX basePattern = 0;
@ -1201,7 +1204,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
// Read MIDI messages
if(expData.midiDumpOffset && file.Seek(expData.midiDumpOffset) && file.CanRead(8))
{
uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(std::size(m_MidiCfg.szMidiZXXExt)));
uint16 numDumps = std::min(file.ReadUint16BE(), static_cast<uint16>(m_MidiCfg.Zxx.size()));
file.Skip(6);
if(file.CanRead(numDumps * 4))
{
@ -1215,14 +1218,15 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
file.ReadStruct(dumpHeader);
if(!file.Seek(dumpHeader.dataPointer) || !file.CanRead(dumpHeader.length))
continue;
auto &macro = m_MidiCfg.szMidiZXXExt[dump];
auto length = std::min(static_cast<size_t>(dumpHeader.length), std::size(macro) / 2u);
std::array<char, kMacroLength> macro{};
auto length = std::min(static_cast<size_t>(dumpHeader.length), macro.size() / 2u);
for(size_t i = 0; i < length; i++)
{
const uint8 byte = file.ReadUint8(), high = byte >> 4, low = byte & 0x0F;
macro[i * 2] = high + (high < 0x0A ? '0' : 'A' - 0x0A);
macro[i * 2 + 1] = low + (low < 0x0A ? '0' : 'A' - 0x0A);
}
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
{
mptSmp.Initialize();
mptSmp.SetDefaultCuePoints();
if(type & (MOD_TYPE_IT | MOD_TYPE_S3M))
{
if(frequencyIsHertz)
@ -907,16 +908,16 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags)
for(uint32 i = 0; i < 16; 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
mpt::String::WriteAutoBuf(m_MidiCfg.szMidiSFXExt[i]) = "";
m_MidiCfg.SFx[i] = "";
}
for(uint32 i = 0; i < 128; i++)
{
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
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
{
uint8be numOrders;
uint8be restartPos;
uint8be restartPos; // Tempo (early SoundTracker) or restart position (only PC trackers?)
uint8be orderList[128];
};
@ -585,6 +585,7 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
const size_t patternStartOffset = file.GetPosition();
const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset;
const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * numChannels * 256;
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))
numChannels = 8;
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.
// razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b)
// 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.
// 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.
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))
numPatterns = officialPatterns;
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())
{
// 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)
{
if(!file.CanRead(1080 + 4))
if(!file.LengthIsAtLeast(1080 + 4))
{
return ProbeWantMoreData;
}
@ -1290,7 +1285,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
}
#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
// 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,
@ -1303,12 +1298,12 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
if(isMdKd && hasTempoCommands && !definitelyCIA)
{
const double songTime = GetLength(eNoAdjust).front().duration;
if(songTime >= 540.0)
if(songTime >= 480.0)
{
m_playBehaviour.set(kMODVBlankTiming);
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...
m_playBehaviour.reset(kMODVBlankTiming);
} else

View File

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

View File

@ -534,6 +534,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
const bool useGUS = gusAddresses > 1;
m_playBehaviour.set(kST3PortaSampleChange, 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)");
// 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)

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
mptSmp.RemoveAllCuePoints();
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.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));
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
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
mpt::String::WriteAutoBuf(macro) = "F0F0007F F0F00100";
macro = "F0F0007F F0F00100";
return true;
} else if(event.command == SymEvent::DSPEcho)
{
const uint8 type = (event.note < 5) ? event.note : 0;
const uint8 length = (event.param < 128) ? event.param : 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;
} else if(event.command == SymEvent::DSPDelay)
{
@ -1218,7 +1220,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
static_assert(MAX_SAMPLES >= MAX_INSTRUMENTS);
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)
{
#ifdef MPT_EXTERNAL_SAMPLES
@ -1452,7 +1454,8 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
break;
case SymEvent::KeyOff:
// TODO needs note
if(m.note == NOTE_NONE)
m.note = chnState.lastNote;
m.volcmd = VOLCMD_OFFSET;
m.vol = 1;
break;
@ -1642,10 +1645,10 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
{
m.command = CMD_MIDI;
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());
if(ConvertDSP(event, m_MidiCfg.szMidiZXXExt[param], *this))
if(ConvertDSP(event, m_MidiCfg.Zxx[param], *this))
{
m.command = CMD_MIDI;
m.param = macroMap[event] = 0x80 | param;
@ -1925,8 +1928,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags)
{
InitChannel(chn);
ChnSettings[chn].nPan = (chn & 1) ? 256 : 0;
if(useDSP)
ChnSettings[chn].nMixPlugin = 1; // For MIDI macros controlling the echo DSP
ChnSettings[chn].nMixPlugin = useDSP ? 1 : 0; // For MIDI macros controlling the echo DSP
}
m_modFormat.formatName = U_("Symphonie");

View File

@ -9,10 +9,8 @@
#include "stdafx.h"
#include "../soundlib/MIDIEvents.h"
#include "MIDIMacros.h"
#include "../common/mptStringBuffer.h"
#include "../common/misc_util.h"
#include "../soundlib/MIDIEvents.h"
#ifdef MODPLUG_TRACKER
#include "Sndfile.h"
@ -23,24 +21,25 @@ OPENMPT_NAMESPACE_BEGIN
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++)
{
ParameteredMacro sfx = static_cast<ParameteredMacro>(i);
if(sfx != kSFxCustom)
{
if(macro.compare(CreateParameteredMacro(sfx)) == 0) return sfx;
if(macro == CreateParameteredMacro(sfx))
return sfx;
}
}
// 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;
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 kSFxCustom; // custom / unknown
return kSFxCustom; // custom / unknown
}
@ -54,19 +53,10 @@ FixedMacro MIDIMacroConfig::GetFixedMacroType() const
if(zxx != kZxxCustom)
{
// Prepare macro pattern to compare
Macro macros[128];
CreateFixedMacro(macros, zxx);
bool found = true;
for(uint32 j = 0; j < 128; j++)
{
if(strncmp(macros[j], szMidiZXXExt[j], MACRO_LENGTH))
{
found = false;
break;
}
}
if(found) return zxx;
decltype(Zxx) fixedMacros{};
CreateFixedMacro(fixedMacros, zxx);
if(fixedMacros == Zxx)
return zxx;
}
}
return kZxxCustom; // Custom setup
@ -77,17 +67,17 @@ void MIDIMacroConfig::CreateParameteredMacro(Macro &parameteredMacro, Parametere
{
switch(macroType)
{
case kSFxUnused: mpt::String::WriteAutoBuf(parameteredMacro) = ""; break;
case kSFxCutoff: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F000z"; break;
case kSFxReso: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F001z"; break;
case kSFxFltMode: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F002z"; break;
case kSFxDryWet: mpt::String::WriteAutoBuf(parameteredMacro) = "F0F003z"; break;
case kSFxCC: mpt::String::WriteAutoBuf(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 kSFxChannelAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Dcz"; break;
case kSFxPolyAT: mpt::String::WriteAutoBuf(parameteredMacro) = "Acnz"; break;
case kSFxPitch: mpt::String::WriteAutoBuf(parameteredMacro) = "Ec00z"; break;
case kSFxProgChange: mpt::String::WriteAutoBuf(parameteredMacro) = "Ccz"; break;
case kSFxUnused: parameteredMacro = ""; break;
case kSFxCutoff: parameteredMacro = "F0F000z"; break;
case kSFxReso: parameteredMacro = "F0F001z"; break;
case kSFxFltMode: parameteredMacro = "F0F002z"; break;
case kSFxDryWet: parameteredMacro = "F0F003z"; break;
case kSFxCC: parameteredMacro = MPT_AFORMAT("Bc{}z")(mpt::afmt::HEX0<2>(subType & 0x7F)); break;
case kSFxPlugParam: parameteredMacro = MPT_AFORMAT("F0F{}z")(mpt::afmt::HEX0<3>(std::min(subType, 0x17F) + 0x80)); break;
case kSFxChannelAT: parameteredMacro = "Dcz"; break;
case kSFxPolyAT: parameteredMacro = "Acnz"; break;
case kSFxPitch: parameteredMacro = "Ec00z"; break;
case kSFxProgChange: parameteredMacro = "Ccz"; break;
case kSFxCustom:
default:
MPT_ASSERT_NOTREACHED();
@ -98,59 +88,59 @@ void MIDIMacroConfig::CreateParameteredMacro(Macro &parameteredMacro, Parametere
std::string MIDIMacroConfig::CreateParameteredMacro(ParameteredMacro macroType, int subType) const
{
Macro parameteredMacro;
Macro parameteredMacro{};
CreateParameteredMacro(parameteredMacro, macroType, subType);
return mpt::String::ReadAutoBuf(parameteredMacro);
return parameteredMacro;
}
// 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;
switch(macroType)
{
case kZxxUnused:
mpt::String::WriteAutoBuf(fixedMacros[i]) = "";
fixedMacros[i] = "";
break;
case kZxxReso4Bit:
param = i * 8;
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
mpt::String::WriteAutoBuf(fixedMacros[i]) = "";
fixedMacros[i] = "";
break;
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;
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;
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;
case kZxxResoFltMode:
param = (i & 0x0F) * 8;
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)
mpt::String::WriteAutoBuf(fixedMacros[i]) = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param));
fixedMacros[i] = MPT_AFORMAT("F0F002{}")(mpt::afmt::HEX0<2>(param));
else
mpt::String::WriteAutoBuf(fixedMacros[i]) = "";
fixedMacros[i] = "";
break;
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;
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;
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;
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;
case kZxxCustom:
default:
@ -161,19 +151,14 @@ void MIDIMacroConfig::CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro mac
}
#ifdef MODPLUG_TRACKER
bool MIDIMacroConfig::operator== (const MIDIMacroConfig &other) const
{
for(auto left = begin(), right = other.begin(); left != end(); left++, right++)
{
if(strncmp(*left, *right, MACRO_LENGTH))
return false;
}
return true;
return std::equal(begin(), end(), other.begin());
}
#ifdef MODPLUG_TRACKER
// Returns macro description including plugin parameter / MIDI CC information
CString MIDIMacroConfig::GetParameteredMacroName(uint32 macroIndex, IMixPlugin *plugin) const
{
@ -262,7 +247,7 @@ CString MIDIMacroConfig::GetFixedMacroName(FixedMacro macroType) const
int MIDIMacroConfig::MacroToPlugParam(uint32 macroIndex) const
{
const std::string macro = GetSafeMacro(szMidiSFXExt[macroIndex]);
const std::string macro = SFx[macroIndex].NormalizedString();
int code = 0;
const char *param = macro.c_str();
@ -281,7 +266,7 @@ int MIDIMacroConfig::MacroToPlugParam(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;
const char *param = macro.c_str();
@ -297,7 +282,7 @@ int MIDIMacroConfig::MacroToMidiCC(uint32 macroIndex) 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)
{
@ -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.
bool MIDIMacroConfig::IsMacroDefaultSetupUsed() const
{
const MIDIMacroConfig defaultConfig;
// 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;
return *this == MIDIMacroConfig{};
}
// Reset MIDI macro config to default values.
void MIDIMacroConfig::Reset()
{
MemsetZero(szMidiGlb);
MemsetZero(szMidiSFXExt);
MemsetZero(szMidiZXXExt);
std::fill(begin(), end(), Macro{});
strcpy(szMidiGlb[MIDIOUT_START], "FF");
strcpy(szMidiGlb[MIDIOUT_STOP], "FC");
strcpy(szMidiGlb[MIDIOUT_NOTEON], "9c n v");
strcpy(szMidiGlb[MIDIOUT_NOTEOFF], "9c n 0");
strcpy(szMidiGlb[MIDIOUT_PROGRAM], "Cc p");
Global[MIDIOUT_START] = "FF";
Global[MIDIOUT_STOP] = "FC";
Global[MIDIOUT_NOTEON] = "9c n v";
Global[MIDIOUT_NOTEOFF] = "9c n 0";
Global[MIDIOUT_PROGRAM] = "Cc p";
// SF0: Z00-Z7F controls cutoff
CreateParameteredMacro(0, kSFxCutoff);
// Z80-Z8F controls resonance
@ -359,8 +323,8 @@ void MIDIMacroConfig::Reset()
// Clear all Zxx macros so that they do nothing.
void MIDIMacroConfig::ClearZxxMacros()
{
MemsetZero(szMidiSFXExt);
MemsetZero(szMidiZXXExt);
std::fill(SFx.begin(), SFx.end(), Macro{});
std::fill(Zxx.begin(), Zxx.end(), Macro{});
}
@ -369,27 +333,7 @@ void MIDIMacroConfig::Sanitize()
{
for(auto &macro : *this)
{
macro[MACRO_LENGTH - 1] = '\0';
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';
}
macro.Sanitize();
}
}
@ -399,15 +343,15 @@ void MIDIMacroConfig::UpgradeMacros()
{
for(auto &macro : *this)
{
UpgradeMacroString(macro);
macro.UpgradeLegacyMacro();
}
}
// 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;
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

View File

@ -18,8 +18,10 @@ OPENMPT_NAMESPACE_BEGIN
enum
{
NUM_MACROS = 16, // number of parametered macros
MACRO_LENGTH = 32, // max number of chars per macro
kGlobalMacros = 9, // Number of global macros
kSFxMacros = 16, // Number of parametered macros
kZxxMacros = 128, // Number of fixed macros
kMacroLength = 32, // Max number of chars per macro
};
OPENMPT_NAMESPACE_END
@ -70,7 +72,7 @@ enum FixedMacro
// Global macro types
enum
enum GlobalMacro
{
MIDIOUT_START = 0,
MIDIOUT_STOP,
@ -86,19 +88,74 @@ enum
struct MIDIMacroConfigData
{
typedef char Macro[MACRO_LENGTH];
// encoding is ASCII
Macro szMidiGlb[9]; // Global MIDI macros
Macro szMidiSFXExt[16]; // Parametric MIDI macros
Macro szMidiZXXExt[128]; // Fixed MIDI macros
struct Macro
{
public:
Macro &operator=(const Macro &other) = default;
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); }
const Macro *begin() const { return std::begin(szMidiGlb); }
Macro *end() { return std::end(szMidiZXXExt); }
const Macro *end() const { return std::end(szMidiZXXExt); }
bool operator==(const Macro &other) const noexcept
{
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.
}
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
{
@ -117,22 +174,23 @@ protected:
public:
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;
protected:
void CreateFixedMacro(Macro (&fixedMacros)[128], FixedMacro macroType) const;
void CreateFixedMacro(std::array<Macro, kZxxMacros> &fixedMacros, FixedMacro macroType) const;
public:
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;
bool operator!= (const MIDIMacroConfig &other) const { return !(*this == other); }
#ifdef MODPLUG_TRACKER
// Translate macro type or macro string to macro name
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.
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!

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)
const ModChannel &channel = sndFile.m_PlayState.Chn[chn];
if(nMidiChannel == MidiMappedChannel)
return static_cast<uint8>((channel.nMasterChn ? (channel.nMasterChn - 1u) : chn) % 16u);
else if(HasValidMIDIChannel())

View File

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

View File

@ -148,7 +148,7 @@ void ModSample::Initialize(MODTYPE type)
rootNote = 0;
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.
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;
}
@ -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)
{
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)
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.
bool HasCustomCuePoints() const;
void SetDefaultCuePoints();
// Set cue points so that they are suitable for regular offset command extension
void Set16BitCuePoints();
void RemoveAllCuePoints();
void SetAdlib(bool enable, OPLPatch patch = OPLPatch{{}});
};

View File

@ -35,6 +35,20 @@
#include <stdlib.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>
#endif

View File

@ -64,10 +64,6 @@ public:
uint8 vol = 0xFF;
};
#ifndef NO_PLUGINS
typedef std::map<std::pair<ModCommand::INSTR, uint16>, uint16> PlugParamMap;
PlugParamMap plugParams;
#endif
std::vector<ChnSettings> chnSettings;
double elapsedTime;
static constexpr uint32 IGNORE_CHANNEL = uint32_max;
@ -81,9 +77,8 @@ public:
void Reset()
{
#ifndef NO_PLUGINS
plugParams.clear();
#endif
if(state->m_midiMacroEvaluationResults)
state->m_midiMacroEvaluationResults.emplace();
elapsedTime = 0.0;
state->m_lTotalSampleCount = 0;
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
uint32 oldTickDuration = 0;
bool breakToRow = false;
@ -469,9 +467,9 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
if(p->IsPcNote())
{
#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
chn.rowCommand.Clear();
@ -820,6 +818,17 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
case CMD_PANBRELLO:
Panbrello(chn, param);
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)
@ -925,6 +934,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
chn.nNewNote = chn.nLastNote;
if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta);
NoteChange(chn, m.note, porta);
HandleDigiSamplePlayDirection(playState, nChn);
memory.chnSettings[nChn].incChanged = true;
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;
case CMD_DIGIREVERSESAMPLE:
DigiBoosterSampleReverse(chn, m.param);
break;
case CMD_FINETUNE:
case CMD_FINETUNE_SMOOTH:
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)
{
const auto midiMacroEvaluationResults = std::move(playState.m_midiMacroEvaluationResults);
playState.m_midiMacroEvaluationResults.reset();
// Target found, or there is no target (i.e. play whole song)...
m_PlayState = std::move(playState);
m_PlayState.ResetGlobalVolumeRamping();
@ -1190,11 +1206,11 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
}
#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;
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;
if(plugin != nullptr)
{
@ -1204,7 +1220,7 @@ std::vector<GetLengthType> CSoundFile::GetLength(enmGetLengthResetMode adjustMod
plugSetProgram.set(plug);
plugin->BeginSetProgram();
}
plugin->SetParameter(param.first.second, param.second / PlugParamValue(ModCommand::maxColumnValue));
plugin->SetParameter(plugParam.second, value);
}
}
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
} else if(adjustMode != eAdjustOnSuccess)
{
@ -2243,7 +2264,7 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
IMixPlugin *pPlugin = nullptr;
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)
{
@ -2860,6 +2881,7 @@ bool CSoundFile::ProcessEffects()
}
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))
{
chn.dwFlags.set(CHN_FASTVOLRAMP);
@ -3342,7 +3364,7 @@ bool CSoundFile::ProcessEffects()
{
SetFinetune(nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH);
#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);
#endif // NO_PLUGINS
}
@ -3443,6 +3465,11 @@ bool CSoundFile::ProcessEffects()
}
break;
#endif // NO_PLUGINS
// Digi Booster sample reverse
case CMD_DIGIREVERSESAMPLE:
DigiBoosterSampleReverse(chn, static_cast<ModCommand::PARAM>(param));
break;
}
if(m_playBehaviour[kST3EffectMemory] && param != 0)
@ -3823,7 +3850,7 @@ void CSoundFile::MidiPortamento(CHANNELINDEX nChn, int param, bool doFineSlides)
if(pitchBend)
{
#ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn);
IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr)
{
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.
// Parameters:
// playState: The playback state to operate on.
// nChn: Mod channel to apply macro on
// isSmooth: If true, internal macros are interpolated between two rows
// macro: Actual MIDI Macro string
// param: Parameter for parametric macros (Z00 - Z7F)
// macro: MIDI Macro string to process
// param: Parameter for parametric macros (Zxx / \xx parameter)
// 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];
const ModInstrument *pIns = GetNumInstruments() ? chn.pModInstrument : nullptr;
playState.m_midiMacroScratchSpace.resize(macro.Length() + 1);
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
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?
uint8 data = 0; // data that has just been parsed
@ -4800,8 +4900,7 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
{
isNibble = true;
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;
data = static_cast<uint8>(macro[pos] - 'A' + 0x0A);
@ -4811,19 +4910,19 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
isNibble = true;
data = 0xFF;
#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)
{
auto midiPlug = dynamic_cast<const IMidiPlugin *>(m_MixPlugins[plug - 1u].pMixPlugin);
if(midiPlug)
data = midiPlug->GetMidiChannel(nChn);
data = midiPlug->GetMidiChannel(playState.Chn[nChn], nChn);
}
#endif // NO_PLUGINS
if(data == 0xFF)
{
// Fallback if no plugin was found
if(pIns)
data = pIns->GetMIDIChannel(*this, nChn);
data = pIns->GetMIDIChannel(playState.Chn[nChn], nChn);
else
data = 0;
}
@ -4893,13 +4992,13 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
} else if(macro[pos] == 'z')
{
// Zxx parameter
data = param & 0x7F;
data = param;
if(isSmooth && chn.lastZxxParam < 0x80
&& (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0))
{
// Interpolation for external MIDI messages - interpolation for internal messages
// 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;
updateZxxParam = 0x80;
} else if(updateZxxParam == 0xFF)
@ -4909,13 +5008,13 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
} else if(macro[pos] == 's')
{
// 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);
if(outPos - startPos < 5 || out[startPos] != 0xF0)
{
continue;
}
for(uint32 p = startPos + 5; p != outPos; p++)
for(auto p = startPos + 5u; p != outPos; p++)
{
data += out[p];
}
@ -4940,7 +5039,7 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
firstNibble = !firstNibble;
} 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++;
}
@ -4956,83 +5055,19 @@ void CSoundFile::ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *
if(updateZxxParam < 0x80)
chn.lastZxxParam = updateZxxParam;
// Macro string has been parsed and translated, now send the message(s)...
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;
}
out = out.first(outPos);
}
// 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);
const uint32 ticksLeft = m_PlayState.TicksOnRow() - m_PlayState.m_nTickCount;
MPT_ASSERT(playState.TicksOnRow() > playState.m_nTickCount);
const uint32 ticksLeft = playState.TicksOnRow() - playState.m_nTickCount;
if(ticksLeft > 1)
{
// Slide param
const float step = (param - currentValue) / (float)ticksLeft;
const float step = (param - currentValue) / static_cast<float>(ticksLeft);
return (currentValue + step);
} 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.
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)
{
return 0;
}
if(macro.size() < 1)
return;
// 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)
{
// Start Song, Stop Song, MIDI Reset - both interpreted internally and sent to plugins
for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++)
{
m_PlayState.Chn[chn].nCutOff = 0x7F;
m_PlayState.Chn[chn].nResonance = 0x00;
playState.Chn[chn].nCutOff = 0x7F;
playState.Chn[chn].nResonance = 0x00;
}
}
ModChannel &chn = m_PlayState.Chn[nChn];
if(macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1))
ModChannel &chn = playState.Chn[nChn];
if(macro.size() == 4 && macro[0] == 0xF0 && (macro[1] == 0xF0 || macro[1] == 0xF1))
{
// Internal device.
if(macroLen < 4)
{
return 0;
}
const bool isExtended = (macro[1] == 0xF1);
const uint8 macroCode = macro[2];
const uint8 param = macro[3];
@ -5076,36 +5108,26 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
{
// F0.F0.00.xx: Set CutOff
if(!isSmooth)
{
chn.nCutOff = param;
} else
{
chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(chn.nCutOff, param));
}
else
chn.nCutOff = mpt::saturate_round<uint8>(CalculateSmoothParamChange(playState, chn.nCutOff, param));
chn.nRestoreCutoffOnNewNote = 0;
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
m_opl->Volume(nChn, static_cast<uint8>(cutoff / 4), true);
}
return 4;
} else if(macroCode == 0x01 && !isExtended && param < 0x80)
{
// F0.F0.01.xx: Set Resonance
if(!isSmooth)
{
chn.nResonance = param;
} else
{
chn.nResonance = (uint8)CalculateSmoothParamChange((float)chn.nResonance, (float)param);
}
else
chn.nResonance = mpt::saturate_round<uint8>(CalculateSmoothParamChange(playState, chn.nResonance, param));
chn.nRestoreResonanceOnNewNote = 0;
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
return 4;
} else if(macroCode == 0x02 && !isExtended)
{
// 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);
SetupChannelFilter(chn, !chn.dwFlags[CHN_FILTER]);
}
return 4;
#ifndef NO_PLUGINS
} else if(macroCode == 0x03 && !isExtended)
{
// 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)
{
const float newRatio = (0x7F - (param & 0x7F)) / 127.0f;
if(!isSmooth)
{
m_MixPlugins[plug - 1].fDryRatio = newRatio;
} else
{
m_MixPlugins[plug - 1].fDryRatio = CalculateSmoothParamChange(m_MixPlugins[plug - 1].fDryRatio, newRatio);
}
plug--;
const float newRatio = (127 - param) / 127.0f;
if(localOnly)
playState.m_midiMacroEvaluationResults->pluginDryWetRatio[plug] = newRatio;
else if(!isSmooth)
m_MixPlugins[plug].fDryRatio = newRatio;
else
m_MixPlugins[plug].fDryRatio = CalculateSmoothParamChange(playState, m_MixPlugins[plug].fDryRatio, newRatio);
}
return 4;
} else if((macroCode & 0x80) || isExtended)
{
// 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);
const uint32 plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
if(plug > 0 && plug <= MAX_MIXPLUGINS)
PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted);
if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80)
{
IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin;
if(pPlugin && param < 0x80)
plug--;
IMixPlugin *pPlugin = m_MixPlugins[plug].pMixPlugin;
if(pPlugin)
{
const float fParam = param / 127.0f;
if(!isSmooth)
{
pPlugin->SetParameter(plugParam, fParam);
} else
{
pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(pPlugin->GetParameter(plugParam), fParam));
}
const PlugParamIndex plugParam = isExtended ? (0x80 + macroCode) : (macroCode & 0x7F);
const PlugParamValue value = param / 127.0f;
if(localOnly)
playState.m_midiMacroEvaluationResults->pluginParameter[{plug, plugParam}] = value;
else if(!isSmooth)
pPlugin->SetParameter(plugParam, value);
else
pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), value));
}
}
return 4;
#endif // NO_PLUGINS
}
// If we reach this point, the internal macro was invalid.
} else
} else if(!localOnly)
{
#ifndef NO_PLUGINS
// 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;
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)
@ -5181,12 +5194,12 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
{
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
{
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;
memcpy(&curData, macro, len);
memcpy(&curData, macro.data(), len);
pPlugin->MidiSend(curData);
}
}
@ -5195,11 +5208,7 @@ uint32 CSoundFile::SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned
#else
MPT_UNREFERENCED_PARAMETER(plugin);
#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.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);
}
}
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)
{
// 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;
int32 oldPeriod = chn.nPeriod;
// 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);
bool resetEnv = false;
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;
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)
// Test case: retrig.it
NoteChange(chn, note, m_playBehaviour[kITRetrigger], resetEnv, false, nChn);
// Test cases: retrig.it, RetrigSlide.s3m
const bool itS3Mstyle = m_playBehaviour[kITRetrigger] || (GetType() == MOD_TYPE_S3M && chn.nLength && !oplRealRetrig);
NoteChange(chn, note, itS3Mstyle, resetEnv, false, nChn);
if(!chn.rowCommand.instr)
chn.prevNoteOffset = oldPrevNoteOffset;
// 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)))
retrigCount = 0;
// IT compatibility: see previous IT compatibility comment =)
if(m_playBehaviour[kITRetrigger]) chn.position.Set(0);
if(itS3Mstyle)
chn.position.Set(0);
offset--;
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();
// 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)
{
@ -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
{
@ -6105,23 +6149,23 @@ PLUGINDEX CSoundFile::GetBestPlugin(CHANNELINDEX nChn, PluginPriority priority,
switch (priority)
{
case ChannelOnly:
plugin = GetChannelPlugin(nChn, respectMutes);
plugin = GetChannelPlugin(playState, nChn, respectMutes);
break;
case InstrumentOnly:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
break;
case PrioritiseInstrument:
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS)
{
plugin = GetChannelPlugin(nChn, respectMutes);
plugin = GetChannelPlugin(playState, nChn, respectMutes);
}
break;
case PrioritiseChannel:
plugin = GetChannelPlugin(nChn, respectMutes);
plugin = GetChannelPlugin(playState, nChn, respectMutes);
if(!plugin || plugin > MAX_MIXPLUGINS)
{
plugin = GetActiveInstrumentPlugin(nChn, respectMutes);
plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes);
}
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;
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.
// 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 (nChn >= m_nChannels && channel.nMasterChn > 0)
if(channel.nMasterChn > 0)
{
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,
// so we don't need to worry about finding the master chan.
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;
} else
{
plug = m_PlayState.Chn[nChn].pModInstrument->nMixPlug;
plug = chn.pModInstrument->nMixPlug;
}
}
return plug;
@ -6183,10 +6227,10 @@ PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(CHANNELINDEX nChn, PluginMutePri
// 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,
// 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
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
// 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;
}
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
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
@ -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)
{
m_nMixChannels = 0;
#ifdef MODPLUG_TRACKER
m_pModDoc = pModDoc;
#else
bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags)
{
#endif // MODPLUG_TRACKER
m_nMixChannels = 0;
#ifndef MODPLUG_TRACKER
MPT_UNUSED(pModDoc);
m_nFreqFactor = m_nTempoFactor = 65536;
#endif
#endif // MODPLUG_TRACKER
MemsetZero(Instruments);
Clear(m_szNames);
#ifndef NO_PLUGINS
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())
{
std::vector<ContainerItem> containerItems;
MODCONTAINERTYPE packedContainerType = MOD_CONTAINERTYPE_NONE;
if(!(loadFlags & skipContainer))
{
#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;
ContainerLoadingFlags containerLoadFlags = (loadFlags == onlyVerifyHeader) ? ContainerOnlyVerifyHeader : ContainerUnwrapData;
#if !defined(MPT_WITH_ANCIENT)
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 && UnpackMMCMP(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_MMCMP;
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 && UnpackMMCMP(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_MMCMP;
#endif // !MPT_WITH_ANCIENT
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackUMX(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_UMX;
if(packedContainerType != MOD_CONTAINERTYPE_NONE)
if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackUMX(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_UMX;
if(packedContainerType != MOD_CONTAINERTYPE_NONE)
{
if(loadFlags == onlyVerifyHeader)
{
if(loadFlags == onlyVerifyHeader)
{
return true;
}
if(!containerItems.empty())
{
// cppcheck false-positive
// cppcheck-suppress containerOutOfBounds
file = containerItems[0].file;
}
return true;
}
if(!containerItems.empty())
{
// cppcheck false-positive
// 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
{
// New song

View File

@ -232,9 +232,7 @@ class CTuningCollection;
using CTuningCollection = Tuning::CTuningCollection;
struct CModSpecifications;
class OPL;
#ifdef MODPLUG_TRACKER
class CModDoc;
#endif // MODPLUG_TRACKER
/////////////////////////////////////////////////////////////////////////
@ -585,11 +583,17 @@ public:
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)!
public:
PlayState()
struct MIDIMacroEvaluationResults
{
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()
{
@ -720,12 +724,13 @@ public:
#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.
CModDoc *GetpModDoc() const noexcept { return m_pModDoc; }
#endif // MODPLUG_TRACKER
bool Create(FileReader file, ModLoadingFlags loadFlags = loadCompleteModule, CModDoc *pModDoc = nullptr);
#else
bool Create(FileReader file, ModLoadingFlags loadFlags);
#endif // MODPLUG_TRACKER
private:
bool CreateInternal(FileReader file, ModLoadingFlags loadFlags);
public:
bool Destroy();
Enum<MODTYPE> GetType() const noexcept { return m_nType; }
@ -1096,6 +1101,8 @@ protected:
void ProcessSampleOffset(ModChannel &chn, CHANNELINDEX nChn, const PlayState &playState) const;
void SampleOffset(ModChannel &chn, SmpLength 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 PatternLoop(PlayState &state, ModChannel &chn, ModCommand::PARAM param) const;
bool HandleNextRow(PlayState &state, const ModSequence &order, bool honorPatternLoop) const;
@ -1108,9 +1115,10 @@ protected:
void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide);
void ProcessMacroOnChannel(CHANNELINDEX nChn);
void ProcessMIDIMacro(CHANNELINDEX nChn, bool isSmooth, const char *macro, uint8 param = 0, PLUGINDEX plugin = 0);
float CalculateSmoothParamChange(float currentValue, float param) const;
uint32 SendMIDIData(CHANNELINDEX nChn, bool isSmooth, const unsigned char *macro, uint32 macroLen, PLUGINDEX plugin);
void ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro &macro, uint8 param = 0, PLUGINDEX plugin = 0);
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;
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);
int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const;
@ -1236,12 +1244,12 @@ public:
void ProcessStereoSeparation(long countChunk);
private:
PLUGINDEX GetChannelPlugin(CHANNELINDEX nChn, PluginMutePriority respectMutes) const;
PLUGINDEX GetActiveInstrumentPlugin(CHANNELINDEX, PluginMutePriority respectMutes) const;
IMixPlugin *GetChannelInstrumentPlugin(CHANNELINDEX chn) const;
PLUGINDEX GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const;
static PLUGINDEX GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes);
IMixPlugin *GetChannelInstrumentPlugin(const ModChannel &chn) const;
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:
#ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn);
IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr)
{
// 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:
#ifndef NO_PLUGINS
IMixPlugin *plugin = GetChannelInstrumentPlugin(nChn);
IMixPlugin *plugin = GetChannelInstrumentPlugin(m_PlayState.Chn[nChn]);
if(plugin != nullptr)
{
plugin->MidiVibrato(0, 0, nChn);
@ -2527,15 +2527,15 @@ void CSoundFile::ProcessMacroOnChannel(CHANNELINDEX nChn)
if(nChn < GetNumChannels())
{
// TODO evaluate per-plugin macros here
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_PAN]);
//ProcessMIDIMacro(nChn, false, m_MidiCfg.szMidiGlb[MIDIOUT_VOLUME]);
//ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[MIDIOUT_PAN]);
//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.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
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
const PLUGINDEX nPlugin = GetBestPlugin(nChn, PrioritiseInstrument, RespectMutes);
const PLUGINDEX nPlugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes);
IMixPlugin *pPlugin = nullptr;
if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS)
{
@ -2622,7 +2622,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn)
if(ModCommand::IsNote(note))
realNote = pIns->NoteMap[note - NOTE_MIN];
// 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));
}

View File

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

View File

@ -30,7 +30,7 @@ const EffectType effectTypes[] =
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_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
EFFECT_TYPE_NORMAL,
EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
};
static_assert(std::size(effectTypes) == MAX_EFFECTS);
@ -84,7 +84,7 @@ void ModCommand::ExtendedMODtoS3MEffect()
case 0x70: param = (param & 0x03) | 0x40; 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 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 0xD0: if(param == 0xD0) { command = CMD_NONE; } break; // ditto
// 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.
fromType = MOD_TYPE_MOD;
}
if(command == CMD_DIGIREVERSESAMPLE && toType != MOD_TYPE_DIGI)
{
command = CMD_S3MCMDEX;
param = 0x9F;
}
// helper variables
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)
{
@ -1036,6 +1041,7 @@ size_t ModCommand::GetEffectWeight(COMMAND cmd)
CMD_VOLUMESLIDE,
CMD_VIBRATOVOL,
CMD_VOLUME,
CMD_DIGIREVERSESAMPLE,
CMD_REVERSEOFFSET,
CMD_OFFSETPERCENTAGE,
CMD_OFFSET,

View File

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

View File

@ -28,10 +28,7 @@ void CPatternContainer::ClearPatterns()
void CPatternContainer::DestroyPatterns()
{
for(PATTERNINDEX i = 0; i < m_Patterns.size(); i++)
{
Remove(i);
}
m_Patterns.clear();
}
@ -67,7 +64,7 @@ PATTERNINDEX CPatternContainer::InsertAny(const ROWINDEX rows, bool respectQtyLi
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;
if(IsValidPat(index))
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
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
{
if(trackChannel >= std::size(m_SndFile.m_PlayState.Chn))
return 0;
if(auto ins = m_SndFile.m_PlayState.Chn[trackChannel].pModInstrument; ins != nullptr)
return ins->GetMIDIChannel(m_SndFile, trackChannel);
if(trackChannel < std::size(m_SndFile.m_PlayState.Chn))
return GetMidiChannel(m_SndFile.m_PlayState.Chn[trackChannel], trackChannel);
else
return 0;
}
@ -884,7 +890,7 @@ void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vo
uint8 high = static_cast<uint8>(midiBank >> 7);
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_Fine, midiCh, low));
@ -897,7 +903,7 @@ void IMidiPlugin::MidiCommand(const ModInstrument &instr, uint16 note, uint16 vo
if(progChanged || (midiProg < 0x80 && bankChanged))
{
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));
}

View File

@ -25,6 +25,7 @@ OPENMPT_NAMESPACE_BEGIN
struct VSTPluginLib;
struct SNDMIXPLUGIN;
struct ModInstrument;
struct ModChannel;
class CSoundFile;
class CModDoc;
class CAbstractVstEditor;
@ -275,9 +276,11 @@ public:
bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override;
// 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:
uint8 GetMidiChannel(CHANNELINDEX trackChannel) const;
// Plugin wants to send MIDI to OpenMPT
virtual void ReceiveMidi(uint32 midiCode);
virtual void ReceiveSysex(mpt::const_byte_span sysex);

View File

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

View File

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

View File

@ -24,6 +24,10 @@ OPENMPT_NAMESPACE_BEGIN
namespace Tuning {
static RATIOTYPE SanitizeGroupRatio(RATIOTYPE ratio)
{
return std::clamp(std::abs(ratio), 1e-15f, 1e+07f);
}
namespace CTuningS11n
{
@ -257,7 +261,12 @@ RATIOTYPE CTuning::GetRatio(const NOTEINDEXTYPE note) const
{
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;
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(!((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;
}
}
for(auto ratio : m_RatioTable)
{
if(!std::isfinite(ratio))
return SerializationResult::Failure;
}
//Fineratios
if(version <= 2)
@ -698,6 +723,11 @@ SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Chars
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_NoteMin
@ -721,8 +751,8 @@ SerializationResult CTuning::InitDeserializeOLD(std::istream &inStrm, mpt::Chars
//m_GroupRatio
IEEE754binary32LE groupratio = IEEE754binary32LE(0.0f);
mpt::IO::Read(inStrm, groupratio);
m_GroupRatio = groupratio;
if(m_GroupRatio < 0)
m_GroupRatio = SanitizeGroupRatio(groupratio);
if(!std::isfinite(m_GroupRatio))
{
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
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;
#else
#else // !C++20
// C++2a compatible bit_cast.
// Not implementing constexpr because this is not easily possible pre C++20.
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));
return dst;
}
#endif
#endif // C++20
@ -79,11 +79,18 @@ constexpr bool endian_is_weird() noexcept {
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define MPT_PLATFORM_LITTLE_ENDIAN
#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
// fallback:
#if !defined(MPT_PLATFORM_BIG_ENDIAN) && !defined(MPT_PLATFORM_LITTLE_ENDIAN)
// taken from boost/detail/endian.hpp
#if (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) \
|| (defined(__BIG_ENDIAN__) && !defined(__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(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN))
#define MPT_PLATFORM_LITTLE_ENDIAN
#elif defined(__sparc) || defined(__sparc__) \
|| defined(_POWER) || defined(__powerpc__) \
|| defined(__ppc__) || defined(__hpux) || defined(__hppa) \
|| defined(_MIPSEB) || defined(_POWER) \
#elif defined(__hpux) || defined(__hppa) \
|| defined(_MIPSEB) \
|| defined(__s390__)
#define MPT_PLATFORM_BIG_ENDIAN
#elif defined(__i386__) || defined(__alpha__) \
|| defined(__ia64) || defined(__ia64__) \
|| defined(_M_IX86) || defined(_M_IA64) \
|| defined(_M_ALPHA) || defined(__amd64) \
|| defined(__amd64__) || defined(_M_AMD64) \
|| defined(__x86_64) || defined(__x86_64__) \
|| defined(_M_X64) || defined(__bfin__)
#elif defined(__i386__) || defined(_M_IX86) \
|| defined(__amd64) || defined(__amd64__) || defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) \
|| defined(__bfin__)
#define MPT_PLATFORM_LITTLE_ENDIAN
#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
// <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_libc.hpp"
#include "mpt/base/detect_libcxx.hpp"
#include "mpt/base/detect_os.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 (__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)
#define MPT_CXX 17
#else

View File

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

View File

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

View File

@ -9,7 +9,7 @@
#include "mpt/base/integer.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>
#endif // C++20
@ -19,7 +19,7 @@ namespace mpt {
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;

View File

@ -23,7 +23,7 @@ namespace mpt {
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;
@ -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_greater;

View File

@ -30,7 +30,7 @@ MPT_WARNING("C stdlib does not provide math constants. Please #define _USE_MATH_
#endif
#endif
#if !MPT_LIBC_MS
#if MPT_LIBC_GLIBC
#if !defined(_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.")

View File

@ -8,22 +8,22 @@
#include "mpt/base/detect.hpp"
#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
#define MPT_FORMAT_CXX17_FLOAT 0
#define MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17 0
#endif
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include "mpt/base/algorithm.hpp"
#endif
#include "mpt/base/namespace.hpp"
#include "mpt/format/helpers.hpp"
#include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <charconv>
#endif
#if !MPT_FORMAT_CXX17_FLOAT
#if !MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <iomanip>
#include <ios>
#include <limits>
@ -31,7 +31,7 @@
#include <sstream>
#endif
#include <string>
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_DEFAULT_FLOAT_CXX17
#include <system_error>
#endif
#include <type_traits>
@ -42,7 +42,7 @@ namespace mpt {
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>
inline Tstring to_chars_string(const T & x) {
std::string str(1, '\0');
@ -66,7 +66,7 @@ inline Tstring format_value_default(const T & x) {
#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>
inline Tstring to_stream_string(const T & x) {
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"
#if 1
#define MPT_FORMAT_CXX17_INT 1
#define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 1
#else
#define MPT_FORMAT_CXX17_INT 0
#define MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17 0
#endif
#if MPT_FORMAT_CXX17_INT
#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#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/utility.hpp"
#include "mpt/format/helpers.hpp"
#include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_INT
#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <charconv>
#endif // MPT_FORMAT_CXX17_INT
#if !MPT_FORMAT_CXX17_INT
#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#if !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <ios>
#include <locale>
#include <sstream>
#endif // !MPT_FORMAT_CXX17_INT
#endif // !MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <string>
#if MPT_FORMAT_CXX17_INT
#if MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <system_error>
#endif // MPT_FORMAT_CXX17_INT
#endif // MPT_FORMAT_FORMAT_DEFAULT_INT_CXX17
#include <type_traits>
@ -40,7 +40,7 @@ namespace mpt {
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>
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));
}
#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>
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));
}
#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>

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"
#endif
#include "mpt/base/namespace.hpp"
#include "mpt/format/default_floatingpoint.hpp"
#include "mpt/format/helpers.hpp"
#include "mpt/format/simple_spec.hpp"
#include "mpt/string/types.hpp"
#include "mpt/string_transcode/transcode.hpp"
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <charconv>
#endif
#if !MPT_FORMAT_CXX17_FLOAT
#if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <iomanip>
#include <ios>
#endif
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <iterator>
#endif
#if !MPT_FORMAT_CXX17_FLOAT
#if !MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <limits>
#include <locale>
#include <sstream>
#endif
#include <string>
#if MPT_FORMAT_CXX17_FLOAT
#if MPT_FORMAT_FORMAT_SIMPLE_FLOAT_CXX17
#include <system_error>
#endif
#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>
@ -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>
@ -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/format/helpers.hpp"
#include "mpt/format/simple_spec.hpp"
#include "mpt/string/types.hpp"
#include <charconv>
#include <string>

View File

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

View File

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

View File

@ -38,6 +38,7 @@
#include "openmpt/soundbase/SampleConvert.hpp"
#include "openmpt/soundbase/SampleDecode.hpp"
#include "openmpt/soundbase/SampleEncode.hpp"
#include "openmpt/soundbase/SampleFormat.hpp"
#include "../soundlib/SampleCopy.h"
#include "../soundlib/SampleNormalize.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(""), 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);
}