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 18fe6c0563
commit 75a4f68feb
34 changed files with 278 additions and 139 deletions

View file

@ -1,4 +1,4 @@
MPT_SVNVERSION=21953
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.11
MPT_SVNDATE=2024-10-26T13:09:27.804941Z
MPT_SVNVERSION=22406
MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.12
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_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_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
FPU_NONE := -mno-80387
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_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_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
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-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/bobcat := $(___) -march=btver1 $(FPU_SSE4A) -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/bobcat := $(___) -march=btver1 $(FPU_SSSE4A) -mtune=btver1 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=512
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 := $(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
#define OPENMPT_VERSION_SVNVERSION "21953"
#define OPENMPT_VERSION_REVISION 21953
#define OPENMPT_VERSION_SVNVERSION "22406"
#define OPENMPT_VERSION_REVISION 22406
#define OPENMPT_VERSION_DIRTY 0
#define OPENMPT_VERSION_MIXEDREVISIONS 0
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.11"
#define OPENMPT_VERSION_DATE "2024-10-26T13:09:27.804941Z"
#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.12"
#define OPENMPT_VERSION_DATE "2024-12-01T13:10:15.135688Z"
#define OPENMPT_VERSION_IS_PACKAGE 1

View file

@ -68,7 +68,7 @@ TempFileGuard::~TempFileGuard()
{
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;
}
if(::CreateDirectory(dirname.AsNative().c_str(), NULL) == 0)
if(::CreateDirectory(mpt::support_long_path(dirname.AsNative()).c_str(), NULL) == 0)
{ // fail
dirname = mpt::PathString();
}

View file

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

View file

@ -621,6 +621,7 @@ mpt::ustring GetFullCreditsString()
"Revenant (https://revenant1.net/)\n"
"SYRiNX\n"
"xaimus (http://xaimus.com/)\n"
"zersal\n"
"\n"
"Thanks to:\n"
"\n"
@ -660,7 +661,7 @@ mpt::ustring GetFullCreditsString()
"https://github.com/iamgreaser/it2everything/\n"
"\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"
"Shayde / Reality Productions for Opal OPL3 emulator\n"
"https://www.3eality.com/\n"
@ -774,7 +775,7 @@ mpt::ustring GetFullCreditsString()
"https://www.behance.net/ulfurkolka\n"
"\n"
"Nobuyuki for file icon\n"
"https://twitter.com/nobuyukinyuu\n"
"https://github.com/nobuyukinyuu/\n"
"\n"
#endif
"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"
"thanks particularly to:\n"
"33, 8bitbubsy, AliceLR, Anboi, BooT-SectoR-ViruZ, Bvanoudtshoorn\n"
"christofori, cubaxd, Diamond, Ganja, Georg, Goor00,\n"
"Harbinger, jmkz, KrazyKatz, LPChip, Nofold, Rakib, Sam Zen\n"
"a11cf0, christofori, cubaxd, Diamond, Ganja, Georg, Goor00,\n"
"Harbinger, jmkz, KrazyKatz, LPChip, MiDoRi, Nofold, Rakib, Sam Zen\n"
"Skaven, Skilletaudio, Snu, Squirrel Havoc, Teimoso, Waxhead\n"
"\n"
#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.
#define VER_MAJORMAJOR 1
#define VER_MAJOR 31
#define VER_MINOR 11
#define VER_MINOR 13
#define VER_MINORMINOR 00
OPENMPT_NAMESPACE_END

View file

@ -169,11 +169,11 @@ namespace openmpt {
if ( volume < 0.0 || volume > 1.0 ) {
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 {
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 ) {

View file

@ -21,7 +21,7 @@
/*! \brief libopenmpt minor version number */
#define OPENMPT_API_VERSION_MINOR 7
/*! \brief libopenmpt patch version number */
#define OPENMPT_API_VERSION_PATCH 11
#define OPENMPT_API_VERSION_PATCH 12
/*! \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=7
LIBOPENMPT_VERSION_PATCH=11
LIBOPENMPT_VERSION_PATCH=12
LIBOPENMPT_VERSION_PREREL=
LIBOPENMPT_LTVER_CURRENT=4
LIBOPENMPT_LTVER_REVISION=11
LIBOPENMPT_LTVER_REVISION=12
LIBOPENMPT_LTVER_AGE=4

View file

@ -231,6 +231,7 @@ bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, C
#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_("")));
#endif
if(numbits > 15) return false;
if(!file.Seek(memPos + 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) };
@ -316,6 +317,7 @@ bool UnpackMMCMP(std::vector<ContainerItem> &containerItems, FileReader &file, C
uint32 numbits = blk.num_bits;
uint32 oldval = 0;
if(blk.tt_entries > sizeof(ptable)
|| numbits > 7
|| !file.Seek(memPos)
|| file.ReadRaw(mpt::span(ptable, blk.tt_entries)).size() < blk.tt_entries)
return false;

View file

@ -2127,9 +2127,9 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
}
}
pIns->nFadeOut = 1024;
pIns->nMidiProgram = (uint8)(dlsIns.ulInstrument & 0x7F) + 1;
pIns->nMidiChannel = (uint8)(isDrum ? 10 : 0);
pIns->wMidiBank = (uint16)(((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F));
pIns->nMidiProgram = static_cast<uint8>(1 + (dlsIns.ulInstrument & 0x7F));
pIns->nMidiChannel = static_cast<uint8>(isDrum ? 10 : 0);
pIns->wMidiBank = static_cast<uint16>(1 + (((dlsIns.ulBank & 0x7F00) >> 1) | (dlsIns.ulBank & 0x7F)));
pIns->nNNA = NewNoteAction::NoteOff;
pIns->nDCT = DuplicateCheckType::Note;
pIns->nDNA = DuplicateNoteAction::NoteFade;
@ -2146,7 +2146,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui
const DLSREGION &rgn = dlsIns.Regions[nRgn];
if(rgn.IsDummy())
continue;
// Elimitate Duplicate Regions
// Eliminate Duplicate Regions
uint32 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.
uint32 ITSample::ConvertToMPT(ModSample &mptSmp) const
{
if(memcmp(id, "IMPS", 4))
{
return 0;
}
// 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;
mptSmp.Initialize(MOD_TYPE_IT);
mptSmp.SetDefaultCuePoints(); // For old IT/MPTM files

View file

@ -109,7 +109,7 @@ struct DBMInstrument
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.Initialize(MOD_TYPE_DBM);
mptSmp.nVolume = std::min(static_cast<uint16>(volume), uint16(64)) * 4u;
mptSmp.nC5Speed = Util::muldivr(sampleRate, 8303, 8363);
@ -583,7 +583,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags)
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))
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.
void ConvertToMPT(ModSample &mptSmp) const
{
mptSmp.Initialize();
mptSmp.Initialize(MOD_TYPE_IMF);
mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
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 [command1, param1] = TranslateIMFEffect(e1c, e1d);
const auto [command2, param2] = TranslateIMFEffect(e2c, e2d);
m.FillInTwoCommands(command1, param1, command2, param2);
m.FillInTwoCommands(command1, param1, command2, param2, true);
} else if(mask & 0xC0)
{
// 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;
// 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
m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.smpnum), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
bool lastSampleCompressed = false, anyADPCM = false;
@ -806,9 +819,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
ITSample 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];
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);
@ -1257,6 +1271,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags)
} else if(fileHeader.cwtv == 0 && madeWithTracker.empty())
{
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())
{
madeWithTracker = GetImpulseTrackerVersion(fileHeader.cwtv, fileHeader.cmwt);
@ -2650,14 +2668,6 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel
m_nMixLevels = MixLevels::Original;
//m_dwCreatedWithVersion
//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;
}

View file

@ -368,7 +368,7 @@ struct MMDDump
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)
{
@ -382,10 +382,14 @@ static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool bpmMode, uint8 rowsPer
// MED Soundstudio uses these tempos when importing old files
static constexpr uint8 tempos[10] = {179, 164, 152, 141, 131, 123, 116, 110, 104, 99};
return TEMPO(tempos[tempo - 1], 0);
} else if(tempo > 0 && tempo <= 10)
} else if(!softwareMixing && tempo > 0 && tempo <= 10)
{
// SoundTracker compatible 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);
@ -398,10 +402,11 @@ struct TranslateMEDPatternContext
const CHANNELINDEX numTracks;
const uint8 version;
const uint8 rowsPerBeat;
const bool hardwareMixSamples : 1;
const bool is8Ch : 1;
const bool softwareMixing : 1;
const bool bpmMode : 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));
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)
m.SetEffectCommand(CMD_VIBRATO, (param & 0xF0) | std::min<uint8>((param & 0x0F) * 2, 0x0F));
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
break;
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)
if(!ctx.volHex && param < 0x99)
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)));
else if(ctx.volHex)
m.SetEffectCommand(CMD_VOLUME, static_cast<ModCommand::PARAM>(((param & 0x7F) + 1) / 2));
break;
case 0x0D:
m.SetEffectCommand(CMD_VOLUMESLIDE, param);
if(param)
m.SetEffectCommand(CMD_VOLUMESLIDE, param);
break;
case 0x0E: // Synth jump
m.command = CMD_NONE;
@ -446,7 +472,7 @@ static std::pair<EffectCommand, ModCommand::PARAM> ConvertMEDEffect(ModCommand &
m.param = 0x70;
} 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))
{
m.param = static_cast<ModCommand::PARAM>(tempo);
@ -602,7 +628,7 @@ static bool TranslateMEDPattern(FileReader &file, FileReader &cmdExt, CPattern &
param1 = param;
}
// Octave wrapping for 4-channel modules
if(ctx.hardwareMixSamples && note >= NOTE_MIDDLEC + 2 * 12)
if(note >= NOTE_MIDDLEC + 2 * 12)
needInstruments = true;
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(!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
if(row > 0 && oldCmd.first == CMD_XPARAM && m->command != CMD_XPARAM)
pattern.GetpModCommand(row - 1, chn)->param = Util::MaxValueOfType(m->param);
@ -895,13 +921,14 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
{
needInstruments = true;
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)
{
int realNote = note + sampleHeader.sampleTranspose;
if(realNote >= NOTE_MIDDLEC + 24)
note -= static_cast<uint8>(mpt::align_down(realNote - NOTE_MIDDLEC - 12, 12));
if(realNote >= offset)
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;
length /= 2;
m_SongFlags.reset(SONG_ISAMIGA); // Amiga resampler does not handle stereo samples
}
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)
{
// 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 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 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 wetFactor = feedback / initialFeedback; // Factor to compensate for this
const float delay = (std::max(header.mixEchoLength.get(), uint16(1)) - 1) / 1999.0f;
SNDMIXPLUGIN &mixPlug = m_MixPlugins[numPlugins];
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 is8Ch = (songHeader.flags & MMDSong::FLAG_8CHANNEL) != 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);
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);
if(bpmMode)
{
@ -1275,6 +1304,9 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
// For MED, this affects both volume and pitch slides
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))
{
@ -1382,6 +1414,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
int16 transpose = NOTE_MIN + 47 + songHeader.playTranspose;
uint16 numPages = 0;
FileReader cmdExt, commandPages;
bool vol7bit = false;
if(version < 1)
{
@ -1423,6 +1456,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
&& file.Seek(blockInfo.cmdExtTableOffset)
&& file.Seek(file.ReadUint32BE()))
{
vol7bit = true;
cmdExt = file.ReadChunk(numTracks * numRows * (1 + numPages));
}
@ -1437,7 +1471,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags)
pattern.SetName(patName);
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);
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 sizeWithoutPatterns = totalSampleLen + patternStartOffset;
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.
// 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))
numChannels = 8;
file.Seek(patternStartOffset);
} else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == file.GetLength()))
} else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == fileSize))
{
// 15-sample SoundTracker specifics:
// Fix SoundTracker modules where "hidden" patterns should be ignored.
@ -657,7 +661,7 @@ static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERIN
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!
// 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
Patterns.ResizeArray(numPatterns);
std::bitset<32> referencedSamples;
for(PATTERNINDEX pat = 0; pat < numPatterns; pat++)
{
ModCommand *rowBase = nullptr;
@ -1179,6 +1184,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
if(m.instr != 0)
{
lastInstrument[chn] = m.instr;
if(isStartrekker)
referencedSamples.set(m.instr & 0x1F);
}
}
if(hasSpeedOnRow && hasTempoOnRow)
@ -1215,7 +1222,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
{
m_SongFlags.set(SONG_ISAMIGA);
}
if(isGenericMultiChannel || isMdKd)
if(isGenericMultiChannel || isMdKd || IsMagic(magic, "M!K!"))
{
m_playBehaviour.set(kFT2MODTremoloRampWaveform);
}
@ -1316,7 +1323,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
m_nInstruments = 31;
#endif
for(SAMPLEINDEX smp = 1; smp <= m_nInstruments; smp++)
for(SAMPLEINDEX smp = 1; smp <= GetNumInstruments(); smp++)
{
// For Startrekker AM synthesis, we need instrument envelopes.
ModInstrument *ins = AllocateInstrument(smp, smp);
@ -1339,6 +1346,32 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags)
}
#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...).
// Basically, this just converts all tempo commands into speed commands
// 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_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.
bool keepMidiMacros = false;
@ -241,6 +250,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
bool isST3 = false;
bool isSchism = false;
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));
switch(fileHeader.cwtv & S3MFileHeader::trackerMask)
{
@ -252,18 +262,25 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
if(!memcmp(&fileHeader.reserved2, "SCLUB2.0", 8))
{
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
if((fileHeader.masterVolume & 0x80) != 0)
// Canonical offset check avoids mis-detection of an automatic conversion of Vic's "Paper" demo track
if((fileHeader.ordNum & 0x0F) == 0)
{
m_dwLastSavedWithVersion = MPT_V("1.16");
madeWithTracker = UL_("ModPlug Tracker / OpenMPT 1.17");
} else
// MPT and OpenMPT before 1.17.03.02 - Simply keep default (filter) MIDI macros
if((fileHeader.masterVolume & 0x80) != 0)
{
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.
m_dwLastSavedWithVersion = MPT_V("1.00.00.A0");
madeWithTracker = UL_("ModPlug Tracker 1.0 alpha");
madeWithTracker = UL_("Schism Tracker");
}
keepMidiMacros = true;
nonCompatTracker = true;
@ -511,15 +528,6 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
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
if(usePanningTable)
{
@ -538,6 +546,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags)
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.
// Note: Schism Tracker fixed the same bug in git commit f21fe8bcae8b6dde2df27ede4ac9fe563f91baff
if(hasChannelsWithoutPanning)
m_modFormat.madeWithTracker = UL_("ModPlug Tracker 1.16 / OpenMPT 1.17");
else

View file

@ -49,7 +49,7 @@ struct STMSampleHeader
&& mptSmp.nLoopEnd != 0xFFFF)
{
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);
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
if(toType & (MOD_TYPE_MOD | MOD_TYPE_S3M))
@ -51,8 +58,6 @@ void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
nVibRate = 0;
nVibSweep = 0;
nVibType = VIB_SINE;
RelativeTone = 0;
}
// No global volume / sustain loops for MOD/S3M/XM
@ -85,7 +90,6 @@ void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
LimitMax(nVibRate, uint8(63));
}
// 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))))
{
@ -150,7 +154,16 @@ void ModSample::Initialize(MODTYPE type)
rootNote = 0;
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
const ModSample &sample = Samples[nSample];
if(sample.uFlags[CHN_ADLIB])
if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false;
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
{
const ModSample &sample = Samples[nSample];
if(sample.uFlags[CHN_ADLIB])
if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false;
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
{
const ModSample &sample = Samples[nSample];
if(!sample.HasSampleData())
return false;
SampleIO(
sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
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
{
const ModSample &sample = Samples[smp];
if(!sample.uFlags[CHN_ADLIB] && !sample.HasSampleData())
return false;
S3MSampleHeader sampleHeader{};
SmpLength length = sampleHeader.ConvertToS3M(sample);
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
{
const ModSample &sample = Samples[smp];
if(sample.uFlags[CHN_ADLIB])
if(sample.uFlags[CHN_ADLIB] || !sample.HasSampleData())
return false;
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 MAX_GLOBAL_VOLUME 256u
inline constexpr uint32 MAX_GLOBAL_VOLUME = 256;
inline constexpr uint32 MAX_PREAMP = 2000;
// Resampling modes
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;
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())
{
// 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)))
{
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())
{
// 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.nLoopStart = 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)));
} else
@ -2229,9 +2229,9 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo
// Test case: dct_smp_note_test.it
if(!m_playBehaviour[kITDCTBehaviour] || !m_playBehaviour[kITRealNoteMapping])
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())
{
// Impulse Tracker ignores empty slots.
@ -4993,6 +4993,7 @@ void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool
} else
{
// SysEx message, find end of message
sendLen = outSize - sendPos;
for(uint32 i = sendPos + 1; i < outSize; i++)
{
if(out[i] == 0xF7)
@ -5002,12 +5003,6 @@ void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool
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))
{
@ -5100,16 +5095,16 @@ void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool is
// Velocity
// 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 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 = (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')
{
// Calculated volume
// 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 = (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')
{
// Pan set
@ -5214,14 +5209,32 @@ void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool is
firstNibble = true;
}
}
// Finish current byte
if(!firstNibble)
{
// Finish current byte
outPos++;
}
if(updateZxxParam < 0x80)
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);
}
@ -5438,7 +5451,7 @@ void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const
{
// ST3 compatibility: Instrument-less note recalls previous note's offset
// Test case: OxxMemory.s3m
if(m_playBehaviour[kST3OffsetWithoutInstrument])
if(m_playBehaviour[kST3OffsetWithoutInstrument] || GetType() == MOD_TYPE_MED)
chn.prevNoteOffset = 0;
chn.prevNoteOffset += param;
@ -5717,7 +5730,10 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset)
const bool fading = chn.dwFlags[CHN_NOTEFADE];
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)
// Test cases: retrig.it, RetrigSlide.s3m
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++)
{
ModSample &sample = Samples[nSmp];
LimitMax(sample.nLength, MAX_SAMPLE_LENGTH);
sample.SanitizeLoops();
#ifdef MPT_EXTERNAL_SAMPLES
if(SampleHasPath(nSmp))
@ -662,6 +664,8 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags)
LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT);
LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT);
LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME);
LimitMax(m_nSamplePreAmp, MAX_PREAMP);
LimitMax(m_nVSTiVolume, MAX_PREAMP);
if(!m_tempoSwing.empty())
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;
}
}
period = (period + vdelta) / 256;
nPeriodFrac = vdelta & 0xFF;
if(Util::MaxValueOfType(period) - period >= vdelta)
{
period = (period + vdelta) / 256;
nPeriodFrac = vdelta & 0xFF;
} else
{
period = Util::MaxValueOfType(period) / 256;
nPeriodFrac = 0;
}
} else
{
// 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)
{
@ -1401,6 +1401,12 @@ std::pair<EffectCommand, ModCommand::PARAM> ModCommand::FillInTwoCommands(Effect
std::swap(effect1, effect2);
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);
SetEffectCommand(effect2, param2);
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.
[[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).
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.
static bool CombineEffects(EffectCommand &eff1, uint8 &param1, EffectCommand &eff2, uint8 &param2);

View file

@ -505,7 +505,7 @@ void I3DL2Reverb::RecalculateI3DL2ReverbParams()
m_roomFilter = 0.0f;
} 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);
m_roomFilter = Clamp(roomFilter, 0.0f, 1.0f);
}

View file

@ -224,7 +224,8 @@ private:
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:

View file

@ -50,7 +50,9 @@
#elif defined(_MSC_VER)
#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)
#elif (_MSC_VER >= 1940)
#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_)
: buffer(buffer_)
, f(f_) {
return;
}
inline ~WriteBuffer() noexcept(false) {
if (!writeError)
{
if (!writeError) {
FlushLocal();
}
}
public:
inline Tfile & file() const {
if (IsDirty())
{
if (IsDirty()) {
FlushLocal();
}
return f;
@ -84,30 +83,24 @@ public:
}
inline bool Write(mpt::const_byte_span data) {
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];
size++;
if (IsFull())
{
if (IsFull()) {
FlushLocal();
}
}
return result;
}
inline void FlushLocal() {
if (IsClean())
{
if (IsClean()) {
return;
}
try
{
if (!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size)))
{
try {
if (!mpt::IO::WriteRaw(f, mpt::as_span(buffer.data(), size))) {
writeError = true;
}
} catch (const std::exception &)
{
} catch (const std::exception &) {
writeError = true;
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::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::AbsolutePathToRelative(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
VERIFY_EQUAL(mpt::RelativePathToAbsolute(P_("C:\\OpenMPT"), mpt::PathString{}), P_("C:\\OpenMPT"));
#endif
#ifdef MODPLUG_TRACKER