Updated libOpenMPT to version 0.7.12

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
This commit is contained in:
Christopher Snowhill 2024-12-04 23:04:11 -08:00
parent afe65df6c6
commit 7e3112efc0
34 changed files with 278 additions and 139 deletions

View file

@ -1,4 +1,4 @@
MPT_SVNVERSION=21953 MPT_SVNVERSION=22406
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.11 MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.12
MPT_SVNDATE=2024-10-26T13:09:27.804941Z MPT_SVNDATE=2024-12-01T13:10:15.135688Z

View file

@ -53,7 +53,8 @@ ifneq ($(SSE),0)
FPU_SSSE3 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -mfpmath=sse FPU_SSSE3 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -mfpmath=sse
FPU_SSE4_1 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4.1 -mfpmath=sse FPU_SSE4_1 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4.1 -mfpmath=sse
FPU_SSE4_2 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4.1 -msse4.2 -mfpmath=sse FPU_SSE4_2 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4.1 -msse4.2 -mfpmath=sse
FPU_SSE4A := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4a -mfpmath=sse FPU_SSE4A := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -msse4a -mfpmath=sse
FPU_SSSE4A := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mssse3 -msse4a -mfpmath=sse
else else
FPU_NONE := -mno-80387 FPU_NONE := -mno-80387
FPU_287 := -m80387 -mfpmath=387 -mno-fancy-math-387 FPU_287 := -m80387 -mfpmath=387 -mno-fancy-math-387
@ -68,7 +69,8 @@ else
FPU_SSSE3 := -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSSE3 := -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387
FPU_SSE4_1 := -mno-sse4.1 -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSE4_1 := -mno-sse4.1 -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387
FPU_SSE4_2 := -mno-sse4.2 -mno-sse4.1 -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSE4_2 := -mno-sse4.2 -mno-sse4.1 -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387
FPU_SSE4A := -mno-sse4a -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSE4A := -mno-sse4a -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387
FPU_SSSE4A := -mno-sse4a -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387
endif endif
OPT_DEF := -Os OPT_DEF := -Os
@ -202,11 +204,11 @@ amd/sempron64 := $(___) -march=k8 $(FPU_SSE2) -mtune=k8
amd/geode-gx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=0 amd/geode-gx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=0
amd/geode-lx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=128 amd/geode-lx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=128
amd/geode-nx := $(___) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=256 amd/geode-nx := $(___) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=256
amd/bobcat := $(___) -march=btver1 $(FPU_SSE4A) -mtune=btver1 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=512 amd/bobcat := $(___) -march=btver1 $(FPU_SSSE4A) -mtune=btver1 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=512
amd/jaguar := $(___) -march=btver2 $(FPU_SSE4A) -mtune=btver2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=1024 amd/jaguar := $(___) -march=btver2 $(FPU_SSSE4A) -mtune=btver2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=1024
amd/late-3dnow := $(XX_) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=512 amd/late-3dnow := $(XX_) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=512
amd/late := $(XX_) -march=i686 $(FPU_SSE4A) -mtune=generic $(OPT_SIMD) amd/late := $(XX_) -march=i686 $(FPU_SSSE4A) -mtune=generic $(OPT_SIMD)

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#define OPENMPT_VERSION_SVNVERSION "21953" #define OPENMPT_VERSION_SVNVERSION "22406"
#define OPENMPT_VERSION_REVISION 21953 #define OPENMPT_VERSION_REVISION 22406
#define OPENMPT_VERSION_DIRTY 0 #define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0 #define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.11" #define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.12"
#define OPENMPT_VERSION_DATE "2024-10-26T13:09:27.804941Z" #define OPENMPT_VERSION_DATE "2024-12-01T13:10:15.135688Z"
#define OPENMPT_VERSION_IS_PACKAGE 1 #define OPENMPT_VERSION_IS_PACKAGE 1

View file

@ -68,7 +68,7 @@ TempFileGuard::~TempFileGuard()
{ {
if(!filename.empty()) if(!filename.empty())
{ {
DeleteFile(filename.AsNative().c_str()); DeleteFile(mpt::support_long_path(filename.AsNative()).c_str());
} }
} }
@ -80,7 +80,7 @@ TempDirGuard::TempDirGuard(const mpt::TemporaryPathname &pathname)
{ {
return; return;
} }
if(::CreateDirectory(dirname.AsNative().c_str(), NULL) == 0) if(::CreateDirectory(mpt::support_long_path(dirname.AsNative()).c_str(), NULL) == 0)
{ // fail { // fail
dirname = mpt::PathString(); dirname = mpt::PathString();
} }

View file

@ -55,7 +55,7 @@ mpt::PathString AbsolutePathToRelative(const mpt::PathString &path, const mpt::P
using namespace path_literals; using namespace path_literals;
using char_type = RawPathString::value_type; using char_type = RawPathString::value_type;
mpt::PathString result = path; mpt::PathString result = path;
if(path.empty()) if(path.empty() || relativeTo.empty())
{ {
return result; return result;
} }
@ -79,7 +79,7 @@ mpt::PathString RelativePathToAbsolute(const mpt::PathString &path, const mpt::P
using namespace path_literals; using namespace path_literals;
using char_type = RawPathString::value_type; using char_type = RawPathString::value_type;
mpt::PathString result = path; mpt::PathString result = path;
if(path.empty()) if(path.empty() || relativeTo.empty())
{ {
return result; return result;
} }

View file

@ -621,6 +621,7 @@ mpt::ustring GetFullCreditsString()
"Revenant (https://revenant1.net/)\n" "Revenant (https://revenant1.net/)\n"
"SYRiNX\n" "SYRiNX\n"
"xaimus (http://xaimus.com/)\n" "xaimus (http://xaimus.com/)\n"
"zersal\n"
"\n" "\n"
"Thanks to:\n" "Thanks to:\n"
"\n" "\n"
@ -660,7 +661,7 @@ mpt::ustring GetFullCreditsString()
"https://github.com/iamgreaser/it2everything/\n" "https://github.com/iamgreaser/it2everything/\n"
"\n" "\n"
"Antti S. Lankila for Amiga resampler implementation\n" "Antti S. Lankila for Amiga resampler implementation\n"
"https://bel.fi/alankila/modguide/interpolate.txt\n" "https://web.archive.org/web/20221228071135/https://bel.fi/alankila/modguide/\n"
"\n" "\n"
"Shayde / Reality Productions for Opal OPL3 emulator\n" "Shayde / Reality Productions for Opal OPL3 emulator\n"
"https://www.3eality.com/\n" "https://www.3eality.com/\n"
@ -774,7 +775,7 @@ mpt::ustring GetFullCreditsString()
"https://www.behance.net/ulfurkolka\n" "https://www.behance.net/ulfurkolka\n"
"\n" "\n"
"Nobuyuki for file icon\n" "Nobuyuki for file icon\n"
"https://twitter.com/nobuyukinyuu\n" "https://github.com/nobuyukinyuu/\n"
"\n" "\n"
#endif #endif
"Daniel Collin (emoon/TBL) for providing test infrastructure\n" "Daniel Collin (emoon/TBL) for providing test infrastructure\n"
@ -784,8 +785,8 @@ mpt::ustring GetFullCreditsString()
"in the form of ideas, testing and support;\n" "in the form of ideas, testing and support;\n"
"thanks particularly to:\n" "thanks particularly to:\n"
"33, 8bitbubsy, AliceLR, Anboi, BooT-SectoR-ViruZ, Bvanoudtshoorn\n" "33, 8bitbubsy, AliceLR, Anboi, BooT-SectoR-ViruZ, Bvanoudtshoorn\n"
"christofori, cubaxd, Diamond, Ganja, Georg, Goor00,\n" "a11cf0, christofori, cubaxd, Diamond, Ganja, Georg, Goor00,\n"
"Harbinger, jmkz, KrazyKatz, LPChip, Nofold, Rakib, Sam Zen\n" "Harbinger, jmkz, KrazyKatz, LPChip, MiDoRi, Nofold, Rakib, Sam Zen\n"
"Skaven, Skilletaudio, Snu, Squirrel Havoc, Teimoso, Waxhead\n" "Skaven, Skilletaudio, Snu, Squirrel Havoc, Teimoso, Waxhead\n"
"\n" "\n"
#ifdef MPT_WITH_VST #ifdef MPT_WITH_VST

View file

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

View file

@ -169,11 +169,11 @@ namespace openmpt {
if ( volume < 0.0 || volume > 1.0 ) { if ( volume < 0.0 || volume > 1.0 ) {
throw openmpt::exception("invalid global volume"); throw openmpt::exception("invalid global volume");
} }
m_sndFile->m_PlayState.m_nGlobalVolume = mpt::saturate_round<uint32_t>( volume * MAX_GLOBAL_VOLUME ); m_sndFile->m_PlayState.m_nGlobalVolume = mpt::saturate_round<uint32_t>( volume * OpenMPT::MAX_GLOBAL_VOLUME );
} }
double module_ext_impl::get_global_volume( ) const { double module_ext_impl::get_global_volume( ) const {
return m_sndFile->m_PlayState.m_nGlobalVolume / static_cast<double>( MAX_GLOBAL_VOLUME ); return m_sndFile->m_PlayState.m_nGlobalVolume / static_cast<double>( OpenMPT::MAX_GLOBAL_VOLUME );
} }
void module_ext_impl::set_channel_volume( std::int32_t channel, double volume ) { void module_ext_impl::set_channel_volume( std::int32_t channel, double volume ) {

View file

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

View file

@ -1,8 +1,8 @@
LIBOPENMPT_VERSION_MAJOR=0 LIBOPENMPT_VERSION_MAJOR=0
LIBOPENMPT_VERSION_MINOR=7 LIBOPENMPT_VERSION_MINOR=7
LIBOPENMPT_VERSION_PATCH=11 LIBOPENMPT_VERSION_PATCH=12
LIBOPENMPT_VERSION_PREREL= LIBOPENMPT_VERSION_PREREL=
LIBOPENMPT_LTVER_CURRENT=4 LIBOPENMPT_LTVER_CURRENT=4
LIBOPENMPT_LTVER_REVISION=11 LIBOPENMPT_LTVER_REVISION=12
LIBOPENMPT_LTVER_AGE=4 LIBOPENMPT_LTVER_AGE=4

View file

@ -231,6 +231,7 @@ bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, C
#ifdef MMCMP_LOG #ifdef MMCMP_LOG
MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" 16-bit block: pos={} size={} {} {}")(psubblk->position, psubblk->size, (blk.flags & MMCMP_DELTA) ? U_("DELTA ") : U_(""), (blk.flags & MMCMP_ABS16) ? U_("ABS16 ") : U_(""))); MPT_LOG_GLOBAL(LogDebug, "MMCMP", MPT_UFORMAT(" 16-bit block: pos={} size={} {} {}")(psubblk->position, psubblk->size, (blk.flags & MMCMP_DELTA) ? U_("DELTA ") : U_(""), (blk.flags & MMCMP_ABS16) ? U_("ABS16 ") : U_("")));
#endif #endif
if(numbits > 15) return false;
if(!file.Seek(memPos + blk.tt_entries)) return false; if(!file.Seek(memPos + blk.tt_entries)) return false;
if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false; if(!file.CanRead(blk.pk_size - blk.tt_entries)) return false;
BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) }; BitReader bitFile{ file.GetChunk(blk.pk_size - blk.tt_entries) };
@ -316,6 +317,7 @@ bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, C
uint32 numbits = blk.num_bits; uint32 numbits = blk.num_bits;
uint32 oldval = 0; uint32 oldval = 0;
if(blk.tt_entries > sizeof(ptable) if(blk.tt_entries > sizeof(ptable)
|| numbits > 7
|| !file.Seek(memPos) || !file.Seek(memPos)
|| file.ReadRaw(mpt::span(ptable, blk.tt_entries)).size() < blk.tt_entries) || file.ReadRaw(mpt::span(ptable, blk.tt_entries)).size() < blk.tt_entries)
return false; return false;

View file

@ -2127,9 +2127,9 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
} }
} }
pIns->nFadeOut = 1024; pIns->nFadeOut = 1024;
pIns->nMidiProgram = (uint8)(dlsIns.ulInstrument & 0x7F) + 1; pIns->nMidiProgram = static_cast<uint8>(1 + (dlsIns.ulInstrument & 0x7F));
pIns->nMidiChannel = (uint8)(isDrum ? 10 : 0); pIns->nMidiChannel = static_cast<uint8>(isDrum ? 10 : 0);
pIns->wMidiBank = (uint16)(((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F)); pIns->wMidiBank = static_cast<uint16>(1 + (((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F)));
pIns->nNNA = NewNoteAction::NoteOff; pIns->nNNA = NewNoteAction::NoteOff;
pIns->nDCT = DuplicateCheckType::Note; pIns->nDCT = DuplicateCheckType::Note;
pIns->nDNA = DuplicateNoteAction::NoteFade; pIns->nDNA = DuplicateNoteAction::NoteFade;
@ -2146,7 +2146,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
const DLSREGION &rgn = dlsIns.Regions[nRgn]; const DLSREGION &rgn = dlsIns.Regions[nRgn];
if(rgn.IsDummy()) if(rgn.IsDummy())
continue; continue;
// Elimitate Duplicate Regions // Eliminate Duplicate Regions
uint32 dupRegion; uint32 dupRegion;
for(dupRegion = minRegion; dupRegion < nRgn; dupRegion++) for(dupRegion = minRegion; dupRegion < nRgn; dupRegion++)
{ {

View file

@ -530,10 +530,9 @@ void ITSample::ConvertToIT(const ModSample &mptSmp, MODTYPE fromType, bool compr
// Convert an ITSample to OpenMPT's internal sample representation. // Convert an ITSample to OpenMPT's internal sample representation.
uint32 ITSample::ConvertToMPT(ModSample &mptSmp) const uint32 ITSample::ConvertToMPT(ModSample &mptSmp) const
{ {
if(memcmp(id, "IMPS", 4)) // IT does not check for the IMPS magic, and some bad XM->IT converter out there doesn't write the magic bytes for empty sample slots.
{ //if(memcmp(id, "IMPS", 4))
return 0; // return 0;
}
mptSmp.Initialize(MOD_TYPE_IT); mptSmp.Initialize(MOD_TYPE_IT);
mptSmp.SetDefaultCuePoints(); // For old IT/MPTM files mptSmp.SetDefaultCuePoints(); // For old IT/MPTM files

View file

@ -109,7 +109,7 @@ struct DBMInstrument
void ConvertToMPT(ModSample &mptSmp) const void ConvertToMPT(ModSample &mptSmp) const
{ {
mptSmp.Initialize(); mptSmp.Initialize(MOD_TYPE_DBM);
mptSmp.nVolume = std::min(static_cast<uint16>(volume), uint16(64)) * 4u; mptSmp.nVolume = std::min(static_cast<uint16>(volume), uint16(64)) * 4u;
mptSmp.nC5Speed = Util::muldivr(sampleRate, 8303, 8363); mptSmp.nC5Speed = Util::muldivr(sampleRate, 8303, 8363);
@ -583,7 +583,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags)
cmd1 = CMD_NONE; cmd1 = CMD_NONE;
} }
const auto lostCommand = m.FillInTwoCommands(cmd1, param1, cmd2, param2); const auto lostCommand = m.FillInTwoCommands(cmd1, param1, cmd2, param2, true);
if(ModCommand::IsGlobalCommand(lostCommand.first, lostCommand.second)) if(ModCommand::IsGlobalCommand(lostCommand.first, lostCommand.second))
lostGlobalCommands.insert(lostGlobalCommands.begin(), lostCommand); // Insert at front so that the last command of same type "wins" lostGlobalCommands.insert(lostGlobalCommands.begin(), lostCommand); // Insert at front so that the last command of same type "wins"

View file

@ -177,7 +177,7 @@ struct IMFSample
// Convert an IMFSample to OpenMPT's internal sample representation. // Convert an IMFSample to OpenMPT's internal sample representation.
void ConvertToMPT(ModSample &mptSmp) const void ConvertToMPT(ModSample &mptSmp) const
{ {
mptSmp.Initialize(); mptSmp.Initialize(MOD_TYPE_IMF);
mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename); mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
mptSmp.nLength = length; mptSmp.nLength = length;
@ -556,7 +556,7 @@ bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags)
const auto [e1c, e1d, e2c, e2d] = patternChunk.ReadArray<uint8, 4>(); // Command 1, Data 1, Command 2, Data 2 const auto [e1c, e1d, e2c, e2d] = patternChunk.ReadArray<uint8, 4>(); // Command 1, Data 1, Command 2, Data 2
const auto [command1, param1] = TranslateIMFEffect(e1c, e1d); const auto [command1, param1] = TranslateIMFEffect(e1c, e1d);
const auto [command2, param2] = TranslateIMFEffect(e2c, e2d); const auto [command2, param2] = TranslateIMFEffect(e2c, e2d);
m.FillInTwoCommands(command1, param1, command2, param2); m.FillInTwoCommands(command1, param1, command2, param2, true);
} else if(mask & 0xC0) } else if(mask & 0xC0)
{ {
// There's one effect, just stick it in the effect column (unless it's a volume command) // There's one effect, just stick it in the effect column (unless it's a volume command)

View file

@ -798,6 +798,19 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
bool possibleXMconversion = false; bool possibleXMconversion = false;
// There's a bug in IT somewhere that resets the "sample data present" flag in sample headers, but keeps the sample length
// of a previously deleted sample (presumably).
// As old ModPlug versions didn't set this flag under some circumstances (if a sample wasn't referenced by any instruments in instrument mode),
// and because there appear to be some external tools that forget to set this flag at all, we only respect the flag if the file
// vaguely looks like it was saved with IT. Some files that play garbage data if we don't do this:
// astral projection.it by Lord Jon Ray
// classic illusions.it by Blackstar
// deep in dance.it by Simply DJ
// There are many more such files but they don't reference the broken samples in their pattern data, or the sample data pointer
// points right to the end of the file, so in both cases no audible problem can be observed.
const bool muteBuggySamples = !interpretModPlugMade && fileHeader.cwtv >= 0x0100 && fileHeader.cwtv <= 0x0217
&& (fileHeader.cwtv < 0x0207 || fileHeader.reserved != 0);
// Reading Samples // Reading Samples
m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.smpnum), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1)); m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.smpnum), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
bool lastSampleCompressed = false, anyADPCM = false; bool lastSampleCompressed = false, anyADPCM = false;
@ -806,9 +819,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
ITSample sampleHeader; ITSample sampleHeader;
if(smpPos[i] > 0 && file.Seek(smpPos[i]) && file.ReadStruct(sampleHeader)) if(smpPos[i] > 0 && file.Seek(smpPos[i]) && file.ReadStruct(sampleHeader))
{ {
// IT does not check for the IMPS magic, and some bad XM->IT converter out there doesn't write the magic bytes for empty sample slots.
ModSample &sample = Samples[i + 1]; ModSample &sample = Samples[i + 1];
size_t sampleOffset = sampleHeader.ConvertToMPT(sample); size_t sampleOffset = sampleHeader.ConvertToMPT(sample);
if(muteBuggySamples && !(sampleHeader.flags & ITSample::sampleDataPresent))
sample.nLength = 0;
m_szNames[i + 1] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name); m_szNames[i + 1] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name);
@ -1257,6 +1271,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
} else if(fileHeader.cwtv == 0 && madeWithTracker.empty()) } else if(fileHeader.cwtv == 0 && madeWithTracker.empty())
{ {
madeWithTracker = U_("Unknown"); madeWithTracker = U_("Unknown");
} else if(fileHeader.cwtv >= 0x0208 && fileHeader.cwtv <= 0x0214 && !fileHeader.reserved && m_FileHistory.empty() && madeWithTracker.empty())
{
// Any file made with IT starting from v2.07 onwards should have an edit history
madeWithTracker = UL_("Unknown");
} else if(fileHeader.cmwt < 0x0300 && madeWithTracker.empty()) } else if(fileHeader.cmwt < 0x0300 && madeWithTracker.empty())
{ {
madeWithTracker = GetImpulseTrackerVersion(fileHeader.cwtv, fileHeader.cmwt); madeWithTracker = GetImpulseTrackerVersion(fileHeader.cwtv, fileHeader.cmwt);
@ -2650,14 +2668,6 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel
m_nMixLevels = MixLevels::Original; m_nMixLevels = MixLevels::Original;
//m_dwCreatedWithVersion //m_dwCreatedWithVersion
//m_dwLastSavedWithVersion //m_dwLastSavedWithVersion
//m_nSamplePreAmp
//m_nVSTiVolume
//m_nDefaultGlobalVolume
LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME);
//m_nRestartPos
//m_ModFlags
LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT);
LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT);
return true; return true;
} }

View file

@ -368,7 +368,7 @@ struct MMDDump
MPT_BINARY_STRUCT(MMDDump, 10) MPT_BINARY_STRUCT(MMDDump, 10)
static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPerBeat) static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool softwareMixing, bool bpmMode, uint8 rowsPerBeat)
{ {
if(bpmMode && !is8Ch) if(bpmMode && !is8Ch)
{ {
@ -382,10 +382,14 @@ static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPer
// MED Soundstudio uses these tempos when importing old files // MED Soundstudio uses these tempos when importing old files
static constexpr uint8 tempos[10] = {179, 164, 152, 141, 131, 123, 116, 110, 104, 99}; static constexpr uint8 tempos[10] = {179, 164, 152, 141, 131, 123, 116, 110, 104, 99};
return TEMPO(tempos[tempo - 1], 0); return TEMPO(tempos[tempo - 1], 0);
} else if(tempo > 0 && tempo <= 10) } else if(!softwareMixing && tempo > 0 && tempo <= 10)
{ {
// SoundTracker compatible tempo // SoundTracker compatible tempo
return TEMPO((6.0 * 1773447.0 / 14500.0) / tempo); return TEMPO((6.0 * 1773447.0 / 14500.0) / tempo);
} else if(softwareMixing && tempo < 8)
{
// Observed in MED SoundStudio 1.03 with 1-64ch mixing mode and SPD tempo mode (bug?)
return TEMPO(157.86);
} }
return TEMPO(tempo / 0.264); return TEMPO(tempo / 0.264);
@ -398,10 +402,11 @@ struct TranslateMEDPatternContext
const CHANNELINDEX numTracks; const CHANNELINDEX numTracks;
const uint8 version; const uint8 version;
const uint8 rowsPerBeat; const uint8 rowsPerBeat;
const bool hardwareMixSamples : 1;
const bool is8Ch : 1; const bool is8Ch : 1;
const bool softwareMixing : 1;
const bool bpmMode : 1; const bool bpmMode : 1;
const bool volHex : 1; const bool volHex : 1;
const bool vol7bit : 1;
}; };
@ -410,9 +415,29 @@ static std::pair<EffectCommand, ModCommand::PARAM> ConvertMEDEffect(ModCommand &
const uint8 nibbleLo = std::min(param, uint8(0x0F)); const uint8 nibbleLo = std::min(param, uint8(0x0F));
switch(command) switch(command)
{ {
case 0x01: // Portamento Up (avoid effect memory when importing as XM)
if(param)
m.SetEffectCommand(CMD_PORTAMENTOUP, param);
break;
case 0x02: // Portamento Down (avoid effect memory when importing as XM)
if(param)
m.SetEffectCommand(CMD_PORTAMENTODOWN, param);
break;
case 0x04: // Vibrato (twice as deep as in ProTracker) case 0x04: // Vibrato (twice as deep as in ProTracker)
m.SetEffectCommand(CMD_VIBRATO, (param & 0xF0) | std::min<uint8>((param & 0x0F) * 2, 0x0F)); m.SetEffectCommand(CMD_VIBRATO, (param & 0xF0) | std::min<uint8>((param & 0x0F) * 2, 0x0F));
break; break;
case 0x05: // Tone Porta + Volume Slide (avoid effect memory when importing as XM)
if(param)
m.SetEffectCommand(CMD_TONEPORTAVOL, param);
else
m.SetEffectCommand(CMD_TONEPORTAMENTO, 0);
break;
case 0x06: // Vibrato + Volume Slide (avoid effect memory when importing as XM)
if(param)
m.SetEffectCommand(CMD_VIBRATOVOL, param);
else
m.SetEffectCommand(CMD_VIBRATO, 0);
break;
case 0x08: // Hold and decay case 0x08: // Hold and decay
break; break;
case 0x09: // Set secondary speed case 0x09: // Set secondary speed
@ -422,13 +447,14 @@ static std::pair<EffectCommand, ModCommand::PARAM> ConvertMEDEffect(ModCommand &
case 0x0C: // Set Volume (note: parameters >= 0x80 (only in hex mode?) should set the default instrument volume, which we don't support) case 0x0C: // Set Volume (note: parameters >= 0x80 (only in hex mode?) should set the default instrument volume, which we don't support)
if(!ctx.volHex && param < 0x99) if(!ctx.volHex && param < 0x99)
m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>((param >> 4) * 10 + (param & 0x0F))); m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>((param >> 4) * 10 + (param & 0x0F)));
else if(ctx.volHex && ctx.version < 3) else if(ctx.volHex && !ctx.vol7bit)
m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>(std::min(param & 0x7F, 64))); m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>(std::min(param & 0x7F, 64)));
else if(ctx.volHex) else if(ctx.volHex)
m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>(((param & 0x7F) + 1) / 2)); m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>(((param & 0x7F) + 1) / 2));
break; break;
case 0x0D: case 0x0D:
m.SetEffectCommand(CMD_VOLUMESLIDE, param); if(param)
m.SetEffectCommand(CMD_VOLUMESLIDE, param);
break; break;
case 0x0E: // Synth jump case 0x0E: // Synth jump
m.command = CMD_NONE; m.command = CMD_NONE;
@ -446,7 +472,7 @@ static std::pair<EffectCommand, ModCommand::PARAM> ConvertMEDEffect(ModCommand &
m.param = 0x70; m.param = 0x70;
} else } else
{ {
uint16 tempo = mpt::saturate_round<uint16>(MMDTempoToBPM(param, ctx.is8Ch, ctx.bpmMode, ctx.rowsPerBeat).ToDouble()); uint16 tempo = mpt::saturate_round<uint16>(MMDTempoToBPM(param, ctx.is8Ch, ctx.softwareMixing, ctx.bpmMode, ctx.rowsPerBeat).ToDouble());
if(tempo <= Util::MaxValueOfType(m.param)) if(tempo <= Util::MaxValueOfType(m.param))
{ {
m.param = static_cast<ModCommand::PARAM>(tempo); m.param = static_cast<ModCommand::PARAM>(tempo);
@ -602,7 +628,7 @@ static bool TranslateMEDPattern(FileReader &file, FileReader &cmdExt, CPattern &
param1 = param; param1 = param;
} }
// Octave wrapping for 4-channel modules // Octave wrapping for 4-channel modules
if(ctx.hardwareMixSamples && note >= NOTE_MIDDLEC + 2 * 12) if(note >= NOTE_MIDDLEC + 2 * 12)
needInstruments = true; needInstruments = true;
if(note >= NOTE_MIN && note <= NOTE_MAX) if(note >= NOTE_MIN && note <= NOTE_MAX)
@ -615,7 +641,7 @@ static bool TranslateMEDPattern(FileReader &file, FileReader &cmdExt, CPattern &
if(oldCmd.first != CMD_NONE && m->command != oldCmd.first) if(oldCmd.first != CMD_NONE && m->command != oldCmd.first)
{ {
if(!ModCommand::CombineEffects(m->command, m->param, oldCmd.first, oldCmd.second) && m->volcmd == VOLCMD_NONE) if(!ModCommand::CombineEffects(m->command, m->param, oldCmd.first, oldCmd.second) && m->volcmd == VOLCMD_NONE)
m->FillInTwoCommands(m->command, m->param, oldCmd.first, oldCmd.second); m->FillInTwoCommands(m->command, m->param, oldCmd.first, oldCmd.second, true);
// Reset X-Param to 8-bit value if this cell was overwritten with a "useful" effect // Reset X-Param to 8-bit value if this cell was overwritten with a "useful" effect
if(row > 0 && oldCmd.first == CMD_XPARAM && m->command != CMD_XPARAM) if(row > 0 && oldCmd.first == CMD_XPARAM && m->command != CMD_XPARAM)
pattern.GetpModCommand(row - 1, chn)->param = Util::MaxValueOfType(m->param); pattern.GetpModCommand(row - 1, chn)->param = Util::MaxValueOfType(m->param);
@ -895,13 +921,14 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
{ {
needInstruments = true; needInstruments = true;
instr.Transpose(-24); instr.Transpose(-24);
} else if(!isSynth && hardwareMixSamples) } else if(!isSynth && (hardwareMixSamples || sampleHeader.sampleTranspose))
{ {
int offset = NOTE_MIDDLEC + (hardwareMixSamples ? 24 : 36);
for(auto &note : instr.NoteMap) for(auto &note : instr.NoteMap)
{ {
int realNote = note + sampleHeader.sampleTranspose; int realNote = note + sampleHeader.sampleTranspose;
if(realNote >= NOTE_MIDDLEC + 24) if(realNote >= offset)
note -= static_cast<uint8>(mpt::align_down(realNote - NOTE_MIDDLEC - 12, 12)); note -= static_cast<uint8>(mpt::align_down(realNote - offset + 12, 12));
} }
} }
@ -968,6 +995,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
{ {
sampleIO |= SampleIO::stereoSplit; sampleIO |= SampleIO::stereoSplit;
length /= 2; length /= 2;
m_SongFlags.reset(SONG_ISAMIGA); // Amiga resampler does not handle stereo samples
} }
if(instrHeader.type & MMDInstrHeader::DELTA) if(instrHeader.type & MMDInstrHeader::DELTA)
{ {
@ -1167,9 +1195,9 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
if((header.mixEchoType == 1 || header.mixEchoType == 2) && numPlugins < MAX_MIXPLUGINS) if((header.mixEchoType == 1 || header.mixEchoType == 2) && numPlugins < MAX_MIXPLUGINS)
{ {
// Emulating MED echo using the DMO echo requires to compensate for the differences in initial feedback in the latter. // Emulating MED echo using the DMO echo requires to compensate for the differences in initial feedback in the latter.
const float feedback = 1.0f / (1 << std::max(header.mixEchoDepth, uint8(1))); // The feedback we want const float feedback = 1.0f / (1 << std::clamp(header.mixEchoDepth, uint8(1), uint8(9))); // The feedback we want
const float initialFeedback = std::sqrt(1.0f - (feedback * feedback)); // Actual strength of first delay's feedback const float initialFeedback = std::sqrt(1.0f - (feedback * feedback)); // Actual strength of first delay's feedback
const float wetFactor = feedback / initialFeedback; // Factor to compensate for this const float wetFactor = feedback / initialFeedback; // Factor to compensate for this
const float delay = (std::max(header.mixEchoLength.get(), uint16(1)) - 1) / 1999.0f; const float delay = (std::max(header.mixEchoLength.get(), uint16(1)) - 1) / 1999.0f;
SNDMIXPLUGIN &mixPlug = m_MixPlugins[numPlugins]; SNDMIXPLUGIN &mixPlug = m_MixPlugins[numPlugins];
mpt::reconstruct(mixPlug); mpt::reconstruct(mixPlug);
@ -1257,10 +1285,11 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
const bool volHex = (songHeader.flags & MMDSong::FLAG_VOLHEX) != 0; const bool volHex = (songHeader.flags & MMDSong::FLAG_VOLHEX) != 0;
const bool is8Ch = (songHeader.flags & MMDSong::FLAG_8CHANNEL) != 0; const bool is8Ch = (songHeader.flags & MMDSong::FLAG_8CHANNEL) != 0;
const bool bpmMode = (songHeader.flags2 & MMDSong::FLAG2_BPM) != 0; const bool bpmMode = (songHeader.flags2 & MMDSong::FLAG2_BPM) != 0;
const bool softwareMixing = (songHeader.flags2 & MMDSong::FLAG2_MIX) != 0;
const uint8 rowsPerBeat = 1 + (songHeader.flags2 & MMDSong::FLAG2_BMASK); const uint8 rowsPerBeat = 1 + (songHeader.flags2 & MMDSong::FLAG2_BMASK);
if(song == 0) if(song == 0)
{ {
m_nDefaultTempo = MMDTempoToBPM(songHeader.defaultTempo, is8Ch, bpmMode, rowsPerBeat); m_nDefaultTempo = MMDTempoToBPM(songHeader.defaultTempo, is8Ch, softwareMixing, bpmMode, rowsPerBeat);
m_nDefaultSpeed = Clamp<uint8, uint8>(songHeader.tempo2, 1, 32); m_nDefaultSpeed = Clamp<uint8, uint8>(songHeader.tempo2, 1, 32);
if(bpmMode) if(bpmMode)
{ {
@ -1275,6 +1304,9 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
// For MED, this affects both volume and pitch slides // For MED, this affects both volume and pitch slides
m_SongFlags.set(SONG_FASTVOLSLIDES, !(songHeader.flags & MMDSong::FLAG_STSLIDE)); m_SongFlags.set(SONG_FASTVOLSLIDES, !(songHeader.flags & MMDSong::FLAG_STSLIDE));
m_playBehaviour.set(kST3OffsetWithoutInstrument);
m_playBehaviour.set(kST3PortaSampleChange);
m_playBehaviour.set(kFT2PortaNoNote);
if(expData.songNameOffset && file.Seek(expData.songNameOffset)) if(expData.songNameOffset && file.Seek(expData.songNameOffset))
{ {
@ -1382,6 +1414,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
int16 transpose = NOTE_MIN + 47 + songHeader.playTranspose; int16 transpose = NOTE_MIN + 47 + songHeader.playTranspose;
uint16 numPages = 0; uint16 numPages = 0;
FileReader cmdExt, commandPages; FileReader cmdExt, commandPages;
bool vol7bit = false;
if(version < 1) if(version < 1)
{ {
@ -1423,6 +1456,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
&& file.Seek(blockInfo.cmdExtTableOffset) && file.Seek(blockInfo.cmdExtTableOffset)
&& file.Seek(file.ReadUint32BE())) && file.Seek(file.ReadUint32BE()))
{ {
vol7bit = true;
cmdExt = file.ReadChunk(numTracks * numRows * (1 + numPages)); cmdExt = file.ReadChunk(numTracks * numRows * (1 + numPages));
} }
@ -1437,7 +1471,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
pattern.SetName(patName); pattern.SetName(patName);
LimitMax(numTracks, m_nChannels); LimitMax(numTracks, m_nChannels);
TranslateMEDPatternContext context{transpose, numTracks, version, rowsPerBeat, hardwareMixSamples, is8Ch, bpmMode, volHex}; TranslateMEDPatternContext context{transpose, numTracks, version, rowsPerBeat, is8Ch, softwareMixing, bpmMode, volHex, vol7bit};
needInstruments |= TranslateMEDPattern(file, cmdExt, pattern, context, false); needInstruments |= TranslateMEDPattern(file, cmdExt, pattern, context, false);
for(uint16 page = 0; page < numPages; page++) for(uint16 page = 0; page < numPages; page++)

View file

@ -617,8 +617,12 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
const size_t patternStartOffset = file.GetPosition(); const size_t patternStartOffset = file.GetPosition();
const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset; const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset;
const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * numChannels * 256; const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * numChannels * 256;
// There are some WOW files with an extra byte at the end, and also a MOD file (idntmind.mod, MD5 a3af5c3e1af269e32dfb6677c41c8453, SHA1 4884717c298575f9884b2211c762bb1725f73743)
// where only the "official" patterns should be counted but the file also has an extra byte at the end.
// Since MOD files can technically not have an odd file size, we just always round the actual file size down.
const auto fileSize = mpt::align_down(file.GetLength(), FileReader::pos_type{2});
if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == (file.GetLength() & ~1)) if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == fileSize)
{ {
// Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files. // Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files.
// We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end // We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end
@ -627,7 +631,7 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
if(ValidateMODPatternData(file, 16, true)) if(ValidateMODPatternData(file, 16, true))
numChannels = 8; numChannels = 8;
file.Seek(patternStartOffset); file.Seek(patternStartOffset);
} else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == file.GetLength())) } else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == fileSize))
{ {
// 15-sample SoundTracker specifics: // 15-sample SoundTracker specifics:
// Fix SoundTracker modules where "hidden" patterns should be ignored. // Fix SoundTracker modules where "hidden" patterns should be ignored.
@ -657,7 +661,7 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
file.Seek(patternStartOffset); file.Seek(patternStartOffset);
} }
if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == file.GetLength()) if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == fileSize)
{ {
// Even those illegal pattern indexes (> 128) appear to be valid... What a weird file! // Even those illegal pattern indexes (> 128) appear to be valid... What a weird file!
// e.g. NIETNU.MOD, where the end of the order list is filled with FF rather than 00, and the file actually contains 256 patterns. // e.g. NIETNU.MOD, where the end of the order list is filled with FF rather than 00, and the file actually contains 256 patterns.
@ -1072,6 +1076,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
// Reading patterns // Reading patterns
Patterns.ResizeArray(numPatterns); Patterns.ResizeArray(numPatterns);
std::bitset<32> referencedSamples;
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{ {
ModCommand *rowBase = nullptr; ModCommand *rowBase = nullptr;
@ -1179,6 +1184,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
if(m.instr != 0) if(m.instr != 0)
{ {
lastInstrument[chn] = m.instr; lastInstrument[chn] = m.instr;
if(isStartrekker)
referencedSamples.set(m.instr & 0x1F);
} }
} }
if(hasSpeedOnRow && hasTempoOnRow) if(hasSpeedOnRow && hasTempoOnRow)
@ -1215,7 +1222,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
{ {
m_SongFlags.set(SONG_ISAMIGA); m_SongFlags.set(SONG_ISAMIGA);
} }
if(isGenericMultiChannel || isMdKd) if(isGenericMultiChannel || isMdKd || IsMagic(magic, "M!K!"))
{ {
m_playBehaviour.set(kFT2MODTremoloRampWaveform); m_playBehaviour.set(kFT2MODTremoloRampWaveform);
} }
@ -1316,7 +1323,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
m_nInstruments = 31; m_nInstruments = 31;
#endif #endif
for(SAMPLEINDEX smp = 1; smp <= m_nInstruments; smp++) for(SAMPLEINDEX smp = 1; smp <= GetNumInstruments(); smp++)
{ {
// For Startrekker AM synthesis, we need instrument envelopes. // For Startrekker AM synthesis, we need instrument envelopes.
ModInstrument *ins = AllocateInstrument(smp, smp); ModInstrument *ins = AllocateInstrument(smp, smp);
@ -1339,6 +1346,32 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
} }
#endif // MPT_EXTERNAL_SAMPLES || MPT_BUILD_FUZZER #endif // MPT_EXTERNAL_SAMPLES || MPT_BUILD_FUZZER
if((loadFlags & loadSampleData) && isStartrekker && !m_nInstruments)
{
uint8 emptySampleReferences = 0;
for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
{
if(referencedSamples[smp] && !Samples[smp].nLength)
{
if(++emptySampleReferences > 1)
{
#ifdef MPT_EXTERNAL_SAMPLES
mpt::ustring filenameHint;
if(file.GetOptionalFileName())
{
const auto filename = file.GetOptionalFileName()->GetFilename().ToUnicode();
filenameHint = MPT_UFORMAT(" ({}.nt or {}.as)")(filename, filename);
}
AddToLog(LogWarning, MPT_UFORMAT("This Startrekker AM file is most likely missing its companion file{}. Synthesized instruments will not play.")(filenameHint));
#else
AddToLog(LogWarning, U_("This appears to be a Startrekker AM file with external synthesizes instruments. External instruments are currently not supported."));
#endif // MPT_EXTERNAL_SAMPLES
break;
}
}
}
}
// Fix VBlank MODs. Arbitrary threshold: 8 minutes (enough for "frame of mind" by Dascon...). // Fix VBlank MODs. Arbitrary threshold: 8 minutes (enough for "frame of mind" by Dascon...).
// Basically, this just converts all tempo commands into speed commands // Basically, this just converts all tempo commands into speed commands
// for MODs which are supposed to have VBlank timing (instead of CIA timing). // for MODs which are supposed to have VBlank timing (instead of CIA timing).

View file

@ -232,6 +232,15 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
m_nMinPeriod = 64; m_nMinPeriod = 64;
m_nMaxPeriod = 32767; m_nMaxPeriod = 32767;
ReadOrderFromFile<uint8>(Order(), file, fileHeader.ordNum, 0xFF, 0xFE);
// Read sample header offsets
std::vector<uint16le> sampleOffsets;
file.ReadVector(sampleOffsets, fileHeader.smpNum);
// Read pattern offsets
std::vector<uint16le> patternOffsets;
file.ReadVector(patternOffsets, fileHeader.patNum);
// ST3 ignored Zxx commands, so if we find that a file was made with ST3, we should erase all MIDI macros. // ST3 ignored Zxx commands, so if we find that a file was made with ST3, we should erase all MIDI macros.
bool keepMidiMacros = false; bool keepMidiMacros = false;
@ -241,6 +250,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
bool isST3 = false; bool isST3 = false;
bool isSchism = false; bool isSchism = false;
const bool usePanningTable = fileHeader.usePanningTable == S3MFileHeader::idPanning; const bool usePanningTable = fileHeader.usePanningTable == S3MFileHeader::idPanning;
const bool offsetsAreCanonical = !patternOffsets.empty() && !sampleOffsets.empty() && patternOffsets[0] > sampleOffsets[0];
const int32 schismDateVersion = SchismTrackerEpoch + ((fileHeader.cwtv == 0x4FFF) ? fileHeader.reserved2 : (fileHeader.cwtv - 0x4050)); const int32 schismDateVersion = SchismTrackerEpoch + ((fileHeader.cwtv == 0x4FFF) ? fileHeader.reserved2 : (fileHeader.cwtv - 0x4050));
switch(fileHeader.cwtv & S3MFileHeader::trackerMask) switch(fileHeader.cwtv & S3MFileHeader::trackerMask)
{ {
@ -252,18 +262,25 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
if(!memcmp(&fileHeader.reserved2, "SCLUB2.0", 8)) if(!memcmp(&fileHeader.reserved2, "SCLUB2.0", 8))
{ {
madeWithTracker = UL_("Sound Club 2"); madeWithTracker = UL_("Sound Club 2");
} else if(fileHeader.cwtv == S3MFileHeader::trkST3_20 && fileHeader.special == 0 && (fileHeader.ordNum & 0x0F) == 0 && fileHeader.ultraClicks == 0 && (fileHeader.flags & ~0x50) == 0 && usePanningTable) } else if(fileHeader.cwtv == S3MFileHeader::trkST3_20 && fileHeader.special == 0 && (fileHeader.ordNum & 0x01) == 0 && fileHeader.ultraClicks == 0 && (fileHeader.flags & ~0x50) == 0 && usePanningTable && offsetsAreCanonical)
{ {
// MPT and OpenMPT before 1.17.03.02 - Simply keep default (filter) MIDI macros // Canonical offset check avoids mis-detection of an automatic conversion of Vic's "Paper" demo track
if((fileHeader.masterVolume & 0x80) != 0) if((fileHeader.ordNum & 0x0F) == 0)
{ {
m_dwLastSavedWithVersion = MPT_V("1.16"); // MPT and OpenMPT before 1.17.03.02 - Simply keep default (filter) MIDI macros
madeWithTracker = UL_("ModPlug Tracker / OpenMPT 1.17"); if((fileHeader.masterVolume & 0x80) != 0)
} else {
m_dwLastSavedWithVersion = MPT_V("1.16");
madeWithTracker = UL_("ModPlug Tracker / OpenMPT 1.17");
} else
{
// MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 alpha6 does.
m_dwLastSavedWithVersion = MPT_V("1.00.00.A0");
madeWithTracker = UL_("ModPlug Tracker 1.0 alpha");
}
} else if((fileHeader.masterVolume & 0x80) != 0)
{ {
// MPT 1.0 alpha5 doesn't set the stereo flag, but MPT 1.0 alpha6 does. madeWithTracker = UL_("Schism Tracker");
m_dwLastSavedWithVersion = MPT_V("1.00.00.A0");
madeWithTracker = UL_("ModPlug Tracker 1.0 alpha");
} }
keepMidiMacros = true; keepMidiMacros = true;
nonCompatTracker = true; nonCompatTracker = true;
@ -511,15 +528,6 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
m_nChannels = 1; m_nChannels = 1;
} }
ReadOrderFromFile<uint8>(Order(), file, fileHeader.ordNum, 0xFF, 0xFE);
// Read sample header offsets
std::vector<uint16le> sampleOffsets;
file.ReadVector(sampleOffsets, fileHeader.smpNum);
// Read pattern offsets
std::vector<uint16le> patternOffsets;
file.ReadVector(patternOffsets, fileHeader.patNum);
// Read extended channel panning // Read extended channel panning
if(usePanningTable) if(usePanningTable)
{ {
@ -538,6 +546,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
if(m_nChannels < 32 && m_dwLastSavedWithVersion == MPT_V("1.16")) if(m_nChannels < 32 && m_dwLastSavedWithVersion == MPT_V("1.16"))
{ {
// MPT 1.0 alpha 6 up to 1.16.203 set ths panning bit for all channels, regardless of whether they are used or not. // MPT 1.0 alpha 6 up to 1.16.203 set ths panning bit for all channels, regardless of whether they are used or not.
// Note: Schism Tracker fixed the same bug in git commit f21fe8bcae8b6dde2df27ede4ac9fe563f91baff
if(hasChannelsWithoutPanning) if(hasChannelsWithoutPanning)
m_modFormat.madeWithTracker = UL_("ModPlug Tracker 1.16 / OpenMPT 1.17"); m_modFormat.madeWithTracker = UL_("ModPlug Tracker 1.16 / OpenMPT 1.17");
else else

View file

@ -49,7 +49,7 @@ struct STMSampleHeader
&& mptSmp.nLoopEnd != 0xFFFF) && mptSmp.nLoopEnd != 0xFFFF)
{ {
mptSmp.uFlags = CHN_LOOP; mptSmp.uFlags = CHN_LOOP;
mptSmp.nLoopEnd = std::min(mptSmp.nLoopEnd, mptSmp.nLength); mptSmp.nLength = std::max(mptSmp.nLoopEnd, mptSmp.nLength);
} }
} }
}; };

View file

@ -41,6 +41,13 @@ void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
nC5Speed = Util::muldivr_unsigned(nC5Speed, 8363, 8287); nC5Speed = Util::muldivr_unsigned(nC5Speed, 8363, 8287);
FrequencyToTranspose(); FrequencyToTranspose();
} }
if(toType == MOD_TYPE_MOD)
{
if(RelativeTone == -1 && nFineTune == 0)
nFineTune = -128;
RelativeTone = 0;
nFineTune &= ~0x0F;
}
// No ping-pong loop, panning and auto-vibrato for MOD / S3M samples // No ping-pong loop, panning and auto-vibrato for MOD / S3M samples
if(toType & (MOD_TYPE_MOD | MOD_TYPE_S3M)) if(toType & (MOD_TYPE_MOD | MOD_TYPE_S3M))
@ -51,8 +58,6 @@ void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
nVibRate = 0; nVibRate = 0;
nVibSweep = 0; nVibSweep = 0;
nVibType = VIB_SINE; nVibType = VIB_SINE;
RelativeTone = 0;
} }
// No global volume / sustain loops for MOD/S3M/XM // No global volume / sustain loops for MOD/S3M/XM
@ -85,7 +90,6 @@ void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
LimitMax(nVibRate, uint8(63)); LimitMax(nVibRate, uint8(63));
} }
// Autovibrato sweep setting is inverse in XM (0 = "no sweep") and IT (0 = "no vibrato") // Autovibrato sweep setting is inverse in XM (0 = "no sweep") and IT (0 = "no vibrato")
if(((fromType & MOD_TYPE_XM) && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT))) || ((toType & MOD_TYPE_XM) && (fromType & (MOD_TYPE_IT | MOD_TYPE_MPT)))) if(((fromType & MOD_TYPE_XM) && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT))) || ((toType & MOD_TYPE_XM) && (fromType & (MOD_TYPE_IT | MOD_TYPE_MPT))))
{ {
@ -150,7 +154,16 @@ void ModSample::Initialize(MODTYPE type)
rootNote = 0; rootNote = 0;
filename = ""; filename = "";
RemoveAllCuePoints(); if(type & (MOD_TYPE_DBM | MOD_TYPE_IMF | MOD_TYPE_MED))
{
for(SmpLength i = 1; i < 10; i++)
{
cues[i - 1] = Util::muldiv_unsigned(i, 255 * 256, 9);
}
} else
{
RemoveAllCuePoints();
}
} }

View file

@ -558,7 +558,7 @@ bool CSoundFile::SaveFLACSample(SAMPLEINDEX nSample, std::ostream &f) const
{ {
#ifdef MPT_WITH_FLAC #ifdef MPT_WITH_FLAC
const ModSample &sample = Samples[nSample]; const ModSample &sample = Samples[nSample];
if(sample.uFlags[CHN_ADLIB]) if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false; return false;
FLAC__StreamEncoder_RAII encoder(f); FLAC__StreamEncoder_RAII encoder(f);

View file

@ -559,7 +559,7 @@ bool CSoundFile::ReadWAVSample(SAMPLEINDEX nSample, FileReader &file, bool mayNo
bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, std::ostream &f) const bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, std::ostream &f) const
{ {
const ModSample &sample = Samples[nSample]; const ModSample &sample = Samples[nSample];
if(sample.uFlags[CHN_ADLIB]) if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false; return false;
mpt::IO::OFile<std::ostream> ff(f); mpt::IO::OFile<std::ostream> ff(f);
@ -843,6 +843,8 @@ bool CSoundFile::ReadW64Sample(SAMPLEINDEX nSample, FileReader &file, bool mayNo
bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, std::ostream &f) const bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, std::ostream &f) const
{ {
const ModSample &sample = Samples[nSample]; const ModSample &sample = Samples[nSample];
if(!sample.HasSampleData())
return false;
SampleIO( SampleIO(
sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit, sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
sample.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved : SampleIO::mono, sample.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved : SampleIO::mono,
@ -1188,6 +1190,8 @@ bool CSoundFile::ReadS3ISample(SAMPLEINDEX nSample, FileReader &file)
bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, std::ostream &f) const bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, std::ostream &f) const
{ {
const ModSample &sample = Samples[smp]; const ModSample &sample = Samples[smp];
if(!sample.uFlags[CHN_ADLIB] && !sample.HasSampleData())
return false;
S3MSampleHeader sampleHeader{}; S3MSampleHeader sampleHeader{};
SmpLength length = sampleHeader.ConvertToS3M(sample); SmpLength length = sampleHeader.ConvertToS3M(sample);
mpt::String::WriteBuf(mpt::String::nullTerminated, sampleHeader.name) = m_szNames[smp]; mpt::String::WriteBuf(mpt::String::nullTerminated, sampleHeader.name) = m_szNames[smp];
@ -2762,7 +2766,7 @@ static uint32 WriteIFFStringChunk(std::ostream &f, IFFChunk::ChunkIdentifiers id
bool CSoundFile::SaveIFFSample(SAMPLEINDEX smp, std::ostream &f) const bool CSoundFile::SaveIFFSample(SAMPLEINDEX smp, std::ostream &f) const
{ {
const ModSample &sample = Samples[smp]; const ModSample &sample = Samples[smp];
if(sample.uFlags[CHN_ADLIB]) if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false; return false;
mpt::IO::OFile<std::ostream> ff(f); mpt::IO::OFile<std::ostream> ff(f);

View file

@ -299,7 +299,8 @@ DECLARE_FLAGSET(SongFlags)
#define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels #define SNDMIX_MUTECHNMODE 0x100000 // Notes are not played on muted channels
#define MAX_GLOBAL_VOLUME 256u inline constexpr uint32 MAX_GLOBAL_VOLUME = 256;
inline constexpr uint32 MAX_PREAMP = 2000;
// Resampling modes // Resampling modes
enum ResamplingMode : uint8 enum ResamplingMode : uint8

View file

@ -1391,7 +1391,7 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo
if(pIns->NoteMap[note - NOTE_MIN] > NOTE_MAX) return; if(pIns->NoteMap[note - NOTE_MIN] > NOTE_MAX) return;
uint32 n = pIns->Keyboard[note - NOTE_MIN]; uint32 n = pIns->Keyboard[note - NOTE_MIN];
pSmp = ((n) && (n < MAX_SAMPLES)) ? &Samples[n] : nullptr; pSmp = (n <= GetNumSamples()) ? &Samples[n] : &Samples[0];
} else if(GetNumInstruments()) } else if(GetNumInstruments())
{ {
// No valid instrument, or not a valid note. // No valid instrument, or not a valid note.
@ -1751,9 +1751,9 @@ void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetE
if((pIns) && (note - NOTE_MIN < (int)std::size(pIns->Keyboard))) if((pIns) && (note - NOTE_MIN < (int)std::size(pIns->Keyboard)))
{ {
uint32 n = pIns->Keyboard[note - NOTE_MIN]; uint32 n = pIns->Keyboard[note - NOTE_MIN];
if((n) && (n < MAX_SAMPLES)) if(n > 0)
{ {
pSmp = &Samples[n]; pSmp = &Samples[(n <= GetNumSamples()) ? n : 0];
} else if(m_playBehaviour[kITEmptyNoteMapSlot] && !chn.HasMIDIOutput()) } else if(m_playBehaviour[kITEmptyNoteMapSlot] && !chn.HasMIDIOutput())
{ {
// Impulse Tracker ignores empty slots. // Impulse Tracker ignores empty slots.
@ -1958,7 +1958,7 @@ void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetE
chn.nLoopEnd = pSmp->nLength; chn.nLoopEnd = pSmp->nLength;
chn.nLoopStart = 0; chn.nLoopStart = 0;
chn.position.Set(0); chn.position.Set(0);
if((m_SongFlags[SONG_PT_MODE] || m_playBehaviour[kST3OffsetWithoutInstrument]) && !chn.rowCommand.instr) if((m_SongFlags[SONG_PT_MODE] || m_playBehaviour[kST3OffsetWithoutInstrument] || GetType() == MOD_TYPE_MED) && !chn.rowCommand.instr)
{ {
chn.position.SetInt(std::min(chn.prevNoteOffset, chn.nLength - SmpLength(1))); chn.position.SetInt(std::min(chn.prevNoteOffset, chn.nLength - SmpLength(1)));
} else } else
@ -2229,9 +2229,9 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
// Test case: dct_smp_note_test.it // Test case: dct_smp_note_test.it
if(!m_playBehaviour[kITDCTBehaviour] || !m_playBehaviour[kITRealNoteMapping]) if(!m_playBehaviour[kITDCTBehaviour] || !m_playBehaviour[kITRealNoteMapping])
dnaNote = pIns->NoteMap[note - NOTE_MIN]; dnaNote = pIns->NoteMap[note - NOTE_MIN];
if(smp > 0 && smp < MAX_SAMPLES) if(smp > 0)
{ {
pSample = &Samples[smp]; pSample = &Samples[(smp <= GetNumSamples()) ? smp : 0];
} else if(m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel()) } else if(m_playBehaviour[kITEmptyNoteMapSlot] && !pIns->HasValidMIDIChannel())
{ {
// Impulse Tracker ignores empty slots. // Impulse Tracker ignores empty slots.
@ -4993,6 +4993,7 @@ void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool
} else } else
{ {
// SysEx message, find end of message // SysEx message, find end of message
sendLen = outSize - sendPos;
for(uint32 i = sendPos + 1; i < outSize; i++) for(uint32 i = sendPos + 1; i < outSize; i++)
{ {
if(out[i] == 0xF7) if(out[i] == 0xF7)
@ -5002,12 +5003,6 @@ void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool
break; 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)) } else if(!(out[sendPos] & 0x80))
{ {
@ -5100,16 +5095,16 @@ void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool is
// Velocity // Velocity
// This is "almost" how IT does it - apparently, IT seems to lag one row behind on global volume or channel volume changes. // This is "almost" how IT does it - apparently, IT seems to lag one row behind on global volume or channel volume changes.
const int swing = (m_playBehaviour[kITSwingBehaviour] || m_playBehaviour[kMPTOldSwingBehaviour]) ? chn.nVolSwing : 0; const int swing = (m_playBehaviour[kITSwingBehaviour] || m_playBehaviour[kMPTOldSwingBehaviour]) ? chn.nVolSwing : 0;
const int vol = Util::muldiv((chn.nVolume + swing) * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 20); const int vol = Util::muldiv((chn.nVolume + swing) * playState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 20);
data = static_cast<uint8>(Clamp(vol / 2, 1, 127)); data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
//data = (unsigned char)std::min((chn.nVolume * chn.nGlobalVol * m_nGlobalVolume) >> (1 + 6 + 8), 127); //data = (unsigned char)std::min((chn.nVolume * chn.nGlobalVol * playState.m_nGlobalVolume) >> (1 + 6 + 8), 127);
} else if(macro[pos] == 'u') } else if(macro[pos] == 'u')
{ {
// Calculated volume // Calculated volume
// Same note as with velocity applies here, but apparently also for instrument / sample volumes? // Same note as with velocity applies here, but apparently also for instrument / sample volumes?
const int vol = Util::muldiv(chn.nCalcVolume * m_PlayState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 26); const int vol = Util::muldiv(chn.nCalcVolume * playState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 26);
data = static_cast<uint8>(Clamp(vol / 2, 1, 127)); data = static_cast<uint8>(Clamp(vol / 2, 1, 127));
//data = (unsigned char)std::min((chn.nCalcVolume * chn.nGlobalVol * m_nGlobalVolume) >> (7 + 6 + 8), 127); //data = (unsigned char)std::min((chn.nCalcVolume * chn.nGlobalVol * playState.m_nGlobalVolume) >> (7 + 6 + 8), 127);
} else if(macro[pos] == 'x') } else if(macro[pos] == 'x')
{ {
// Pan set // Pan set
@ -5214,14 +5209,32 @@ void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool is
firstNibble = true; firstNibble = true;
} }
} }
// Finish current byte
if(!firstNibble) if(!firstNibble)
{
// Finish current byte
outPos++; outPos++;
}
if(updateZxxParam < 0x80) if(updateZxxParam < 0x80)
chn.lastZxxParam = updateZxxParam; chn.lastZxxParam = updateZxxParam;
// Add end of SysEx byte if necessary
for(size_t i = 0; i < outPos; i++)
{
if(out[i] != 0xF0)
continue;
if(outPos - i >= 4 && (out[i + 1] == 0xF0 || out[i + 1] == 0xF1))
{
// Internal message
i += 3;
} else
{
// Real SysEx
while(i < outPos && out[i] != 0xF7)
i++;
if(i == outPos && outPos < out.size())
out[outPos++] = 0xF7;
}
}
out = out.first(outPos); out = out.first(outPos);
} }
@ -5438,7 +5451,7 @@ void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const
{ {
// ST3 compatibility: Instrument-less note recalls previous note's offset // ST3 compatibility: Instrument-less note recalls previous note's offset
// Test case: OxxMemory.s3m // Test case: OxxMemory.s3m
if(m_playBehaviour[kST3OffsetWithoutInstrument]) if(m_playBehaviour[kST3OffsetWithoutInstrument] || GetType() == MOD_TYPE_MED)
chn.prevNoteOffset = 0; chn.prevNoteOffset = 0;
chn.prevNoteOffset += param; chn.prevNoteOffset += param;
@ -5717,7 +5730,10 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
const bool fading = chn.dwFlags[CHN_NOTEFADE]; const bool fading = chn.dwFlags[CHN_NOTEFADE];
const auto oldPrevNoteOffset = chn.prevNoteOffset; const auto oldPrevNoteOffset = chn.prevNoteOffset;
chn.prevNoteOffset = 0; // Retriggered notes should not use previous offset (test case: OxxMemoryWithRetrig.s3m) // Retriggered notes should not use previous offset in S3M
// Test cases: OxxMemoryWithRetrig.s3m, PTOffsetRetrigger.mod
if(GetType() == MOD_TYPE_S3M)
chn.prevNoteOffset = 0;
// IT compatibility: Really weird combination of envelopes and retrigger (see Storlek's q.it testcase) // IT compatibility: Really weird combination of envelopes and retrigger (see Storlek's q.it testcase)
// Test cases: retrig.it, RetrigSlide.s3m // Test cases: retrig.it, RetrigSlide.s3m
const bool itS3Mstyle = m_playBehaviour[kITRetrigger] || (GetType() == MOD_TYPE_S3M && chn.nLength && !oplRealRetrig); const bool itS3Mstyle = m_playBehaviour[kITRetrigger] || (GetType() == MOD_TYPE_S3M && chn.nLength && !oplRealRetrig);

View file

@ -594,6 +594,8 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags)
for(SAMPLEINDEX nSmp = 1; nSmp <= m_nSamples; nSmp++) for(SAMPLEINDEX nSmp = 1; nSmp <= m_nSamples; nSmp++)
{ {
ModSample &sample = Samples[nSmp]; ModSample &sample = Samples[nSmp];
LimitMax(sample.nLength, MAX_SAMPLE_LENGTH);
sample.SanitizeLoops();
#ifdef MPT_EXTERNAL_SAMPLES #ifdef MPT_EXTERNAL_SAMPLES
if(SampleHasPath(nSmp)) if(SampleHasPath(nSmp))
@ -662,6 +664,8 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags)
LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT); LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT);
LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT); LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT);
LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME); LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME);
LimitMax(m_nSamplePreAmp, MAX_PREAMP);
LimitMax(m_nVSTiVolume, MAX_PREAMP);
if(!m_tempoSwing.empty()) if(!m_tempoSwing.empty())
m_tempoSwing.resize(m_nDefaultRowsPerBeat); m_tempoSwing.resize(m_nDefaultRowsPerBeat);

View file

@ -1840,8 +1840,15 @@ void CSoundFile::ProcessSampleAutoVibrato(ModChannel &chn, int32 &period, Tuning
vdelta += Util::muldiv(period, fineUpTable[l & 0x03], 0x10000) - period; vdelta += Util::muldiv(period, fineUpTable[l & 0x03], 0x10000) - period;
} }
} }
period = (period + vdelta) / 256; if(Util::MaxValueOfType(period) - period >= vdelta)
nPeriodFrac = vdelta & 0xFF; {
period = (period + vdelta) / 256;
nPeriodFrac = vdelta & 0xFF;
} else
{
period = Util::MaxValueOfType(period) / 256;
nPeriodFrac = 0;
}
} else } else
{ {
// MPT's autovibrato code // MPT's autovibrato code

View file

@ -1346,7 +1346,7 @@ bool ModCommand::CombineEffects(EffectCommand &eff1, uint8 &param1, EffectComman
} }
std::pair<EffectCommand, ModCommand::PARAM> ModCommand::FillInTwoCommands(EffectCommand effect1, uint8 param1, EffectCommand effect2, uint8 param2) std::pair<EffectCommand, ModCommand::PARAM> ModCommand::FillInTwoCommands(EffectCommand effect1, uint8 param1, EffectCommand effect2, uint8 param2, bool allowLowResOffset)
{ {
if(effect1 == effect2) if(effect1 == effect2)
{ {
@ -1401,6 +1401,12 @@ std::pair<EffectCommand, ModCommand::PARAM> ModCommand::FillInTwoCommands(Effect
std::swap(effect1, effect2); std::swap(effect1, effect2);
std::swap(param1, param2); std::swap(param1, param2);
} }
if(effect2 == CMD_OFFSET && (allowLowResOffset || param2 == 0))
{
SetVolumeCommand(VOLCMD_OFFSET, static_cast<ModCommand::VOL>(param2 ? std::max(param2 * 9 / 255, 1) : 0));
SetEffectCommand(effect1, param1);
return {CMD_NONE, ModCommand::PARAM(0)};
}
SetVolumeCommand(VOLCMD_NONE, 0); SetVolumeCommand(VOLCMD_NONE, 0);
SetEffectCommand(effect2, param2); SetEffectCommand(effect2, param2);
return {effect1, param1}; return {effect1, param1};

View file

@ -230,7 +230,7 @@ public:
// Try to convert a an effect into a volume column effect. Returns converted effect on success. // Try to convert a an effect into a volume column effect. Returns converted effect on success.
[[nodiscard]] static std::pair<VolumeCommand, VOL> ConvertToVolCommand(const EffectCommand effect, PARAM param, bool force); [[nodiscard]] static std::pair<VolumeCommand, VOL> ConvertToVolCommand(const EffectCommand effect, PARAM param, bool force);
// Takes two "normal" effect commands and converts them to volume column + effect column commands. Returns the dropped command + param (CMD_NONE if nothing had to be dropped). // Takes two "normal" effect commands and converts them to volume column + effect column commands. Returns the dropped command + param (CMD_NONE if nothing had to be dropped).
std::pair<EffectCommand, PARAM> FillInTwoCommands(EffectCommand effect1, uint8 param1, EffectCommand effect2, uint8 param2); std::pair<EffectCommand, PARAM> FillInTwoCommands(EffectCommand effect1, uint8 param1, EffectCommand effect2, uint8 param2, bool allowLowResOffset = false);
// Try to combine two commands into one. Returns true on success and the combined command is placed in eff1 / param1. // Try to combine two commands into one. Returns true on success and the combined command is placed in eff1 / param1.
static bool CombineEffects(EffectCommand &eff1, uint8 &param1, EffectCommand &eff2, uint8 &param2); static bool CombineEffects(EffectCommand &eff1, uint8 &param1, EffectCommand &eff2, uint8 &param2);

View file

@ -505,7 +505,7 @@ void I3DL2Reverb::RecalculateI3DL2ReverbParams()
m_roomFilter = 0.0f; m_roomFilter = 0.0f;
} else } else
{ {
float freq = std::cos(HFReference() * (2.0f * mpt::numbers::pi_v<float>) / m_effectiveSampleRate); float freq = std::min(std::cos(HFReference() * (2.0f * mpt::numbers::pi_v<float>) / m_effectiveSampleRate), 0.9999f);
float roomFilter = (freq * (roomHF + roomHF) - 2.0f + std::sqrt(freq * (roomHF * roomHF * freq * 4.0f) + roomHF * 8.0f - roomHF * roomHF * 4.0f - roomHF * freq * 8.0f)) / (roomHF + roomHF - 2.0f); float roomFilter = (freq * (roomHF + roomHF) - 2.0f + std::sqrt(freq * (roomHF * roomHF * freq * 4.0f) + roomHF * 8.0f - roomHF * roomHF * 4.0f - roomHF * freq * 8.0f)) / (roomHF + roomHF - 2.0f);
m_roomFilter = Clamp(roomFilter, 0.0f, 1.0f); m_roomFilter = Clamp(roomFilter, 0.0f, 1.0f);
} }

View file

@ -224,7 +224,8 @@ private:
static bool IsValidRatio(RATIOTYPE ratio) static bool IsValidRatio(RATIOTYPE ratio)
{ {
return (ratio > static_cast<RATIOTYPE>(0.0)); // Arbitrary epsilon > 0 to avoid NaNs and infinite values in ratio calculation
return (ratio > static_cast<RATIOTYPE>(0.02f));
} }
private: private:

View file

@ -50,7 +50,9 @@
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
#define MPT_COMPILER_MSVC 1 #define MPT_COMPILER_MSVC 1
#if (_MSC_VER >= 1941) #if (_MSC_VER >= 1942)
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 12)
#elif (_MSC_VER >= 1941)
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 11) #define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 11)
#elif (_MSC_VER >= 1940) #elif (_MSC_VER >= 1940)
#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 10) #define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 10)

View file

@ -46,18 +46,17 @@ public:
inline WriteBuffer(Tfile & f_, mpt::byte_span buffer_) inline WriteBuffer(Tfile & f_, mpt::byte_span buffer_)
: buffer(buffer_) : buffer(buffer_)
, f(f_) { , f(f_) {
return;
} }
inline ~WriteBuffer() noexcept(false) { inline ~WriteBuffer() noexcept(false) {
if (!writeError) if (!writeError) {
{
FlushLocal(); FlushLocal();
} }
} }
public: public:
inline Tfile & file() const { inline Tfile & file() const {
if (IsDirty()) if (IsDirty()) {
{
FlushLocal(); FlushLocal();
} }
return f; return f;
@ -84,30 +83,24 @@ public:
} }
inline bool Write(mpt::const_byte_span data) { inline bool Write(mpt::const_byte_span data) {
bool result = true; bool result = true;
for (std::size_t i = 0; i < data.size(); ++i) for (std::size_t i = 0; i < data.size(); ++i) {
{
buffer[size] = data[i]; buffer[size] = data[i];
size++; size++;
if (IsFull()) if (IsFull()) {
{
FlushLocal(); FlushLocal();
} }
} }
return result; return result;
} }
inline void FlushLocal() { inline void FlushLocal() {
if (IsClean()) if (IsClean()) {
{
return; return;
} }
try try {
{ if (!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size))) {
if (!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size)))
{
writeError = true; writeError = true;
} }
} catch (const std::exception &) } catch (const std::exception &) {
{
writeError = true; writeError = true;
throw; throw;
} }

View file

@ -2405,6 +2405,8 @@ static MPT_NOINLINE void TestCharsets()
VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\foo"), exePath), P_("C:\\foo")); VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\foo"), exePath), P_("C:\\foo"));
VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file")); VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file"));
VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file")); VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("\\\\server\\path\\file"), exePath), P_("\\\\server\\path\\file"));
VERIFY_EQUAL(mpt::AbsolutePathToRelative(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
#endif #endif
#ifdef MODPLUG_TRACKER #ifdef MODPLUG_TRACKER