/* * UpdateModule.cpp * ---------------- * Purpose: CSoundFile functions for correcting modules made with previous versions of OpenMPT. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "Sndfile.h" #include "../common/mptStringBuffer.h" #include "../common/version.h" OPENMPT_NAMESPACE_BEGIN struct UpgradePatternData { UpgradePatternData(CSoundFile &sf) : sndFile(sf) , compatPlay(sf.m_playBehaviour[MSF_COMPATIBLE_PLAY]) { } void operator() (ModCommand &m) { const CHANNELINDEX curChn = chn; chn++; if(chn >= sndFile.GetNumChannels()) { chn = 0; } if(m.IsPcNote()) { return; } const auto version = sndFile.m_dwLastSavedWithVersion; const auto modType = sndFile.GetType(); if(modType == MOD_TYPE_S3M) { // Out-of-range global volume commands should be ignored in S3M. Fixed in OpenMPT 1.19 (r831). // So for tracks made with older versions of OpenMPT, we limit invalid global volume commands. if(version < MPT_V("1.19.00.00") && m.command == CMD_GLOBALVOLUME) { LimitMax(m.param, ModCommand::PARAM(64)); } } else if(modType & (MOD_TYPE_IT | MOD_TYPE_MPT)) { if(version < MPT_V("1.17.03.02") || (!compatPlay && version < MPT_V("1.20.00.00"))) { if(m.command == CMD_GLOBALVOLUME) { // Out-of-range global volume commands should be ignored in IT. // OpenMPT 1.17.03.02 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. // So for tracks made with older versions than OpenMPT 1.17.03.02 or tracks made with 1.17.03.02 <= version < 1.20, we limit invalid global volume commands. LimitMax(m.param, ModCommand::PARAM(128)); } // SC0 and SD0 should be interpreted as SC1 and SD1 in IT files. // OpenMPT 1.17.03.02 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. else if(m.command == CMD_S3MCMDEX) { if(m.param == 0xC0) { m.command = CMD_NONE; m.note = NOTE_NOTECUT; } else if(m.param == 0xD0) { m.command = CMD_NONE; } } } // In the IT format, slide commands with both nibbles set should be ignored. // For note volume slides, OpenMPT 1.18 fixes this in compatible mode, OpenMPT 1.20 fixes this in normal mode as well. const bool noteVolSlide = (version < MPT_V("1.18.00.00") || (!compatPlay && version < MPT_V("1.20.00.00"))) && (m.command == CMD_VOLUMESLIDE || m.command == CMD_VIBRATOVOL || m.command == CMD_TONEPORTAVOL || m.command == CMD_PANNINGSLIDE); // OpenMPT 1.20 also fixes this for global volume and channel volume slides. const bool chanVolSlide = (version < MPT_V("1.20.00.00")) && (m.command == CMD_GLOBALVOLSLIDE || m.command == CMD_CHANNELVOLSLIDE); if(noteVolSlide || chanVolSlide) { if((m.param & 0x0F) != 0x00 && (m.param & 0x0F) != 0x0F && (m.param & 0xF0) != 0x00 && (m.param & 0xF0) != 0xF0) { if(m.command == CMD_GLOBALVOLSLIDE) m.param &= 0xF0; else m.param &= 0x0F; } } if(version < MPT_V("1.22.01.04") && version != MPT_V("1.22.00.00")) // Ignore compatibility export { // OpenMPT 1.22.01.04 fixes illegal (out of range) instrument numbers; they should do nothing. In previous versions, they stopped the playing sample. if(sndFile.GetNumInstruments() && m.instr > sndFile.GetNumInstruments() && !compatPlay) { m.volcmd = VOLCMD_VOLUME; m.vol = 0; } } // Command I11 accidentally behaved the same as command I00 with compatible IT tremor and old effects disabled if(m.command == CMD_TREMOR && m.param == 0x11 && version < MPT_V("1.29.12.02") && sndFile.m_playBehaviour[kITTremor] && !sndFile.m_SongFlags[SONG_ITOLDEFFECTS]) { m.param = 0; } } else if(modType == MOD_TYPE_XM) { // Something made be believe that out-of-range global volume commands are ignored in XM // just like they are ignored in IT, but apparently they are not. Aaaaaargh! if(((version >= MPT_V("1.17.03.02") && compatPlay) || (version >= MPT_V("1.20.00.00"))) && version < MPT_V("1.24.02.02") && m.command == CMD_GLOBALVOLUME && m.param > 64) { m.command = CMD_NONE; } if(version < MPT_V("1.19.00.00") || (!compatPlay && version < MPT_V("1.20.00.00"))) { if(m.command == CMD_OFFSET && m.volcmd == VOLCMD_TONEPORTAMENTO) { // If there are both a portamento and an offset effect, the portamento should be preferred in XM files. // OpenMPT 1.19 fixed this in compatible mode, OpenMPT 1.20 fixes it in normal mode as well. m.command = CMD_NONE; } } if(version < MPT_V("1.20.01.10") && m.volcmd == VOLCMD_TONEPORTAMENTO && m.command == CMD_TONEPORTAMENTO && (m.vol != 0 || compatPlay) && m.param != 0) { // Mx and 3xx on the same row does weird things in FT2: 3xx is completely ignored and the Mx parameter is doubled. Fixed in revision 1312 / OpenMPT 1.20.01.10 // Previously the values were just added up, so let's fix this! m.volcmd = VOLCMD_NONE; const uint16 param = static_cast(m.param) + static_cast(m.vol << 4); m.param = mpt::saturate_cast(param); } if(version < MPT_V("1.22.07.09") && m.command == CMD_SPEED && m.param == 0) { // OpenMPT can emulate FT2's F00 behaviour now. m.command = CMD_NONE; } } if(version < MPT_V("1.20.00.00")) { // Pattern Delay fixes const bool fixS6x = (m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0x60); // We also fix X6x commands in hacked XM files, since they are treated identically to the S6x command in IT/S3M files. // We don't treat them in files made with OpenMPT 1.18+ that have compatible play enabled, though, since they are ignored there anyway. const bool fixX6x = (m.command == CMD_XFINEPORTAUPDOWN && (m.param & 0xF0) == 0x60 && (!(compatPlay && modType == MOD_TYPE_XM) || version < MPT_V("1.18.00.00"))); if(fixS6x || fixX6x) { // OpenMPT 1.20 fixes multiple fine pattern delays on the same row. Previously, only the last command was considered, // but all commands should be added up. Since Scream Tracker 3 itself doesn't support S6x, we also use Impulse Tracker's behaviour here, // since we can assume that most S3Ms that make use of S6x were composed with Impulse Tracker. for(ModCommand *fixCmd = (&m) - curChn; fixCmd < &m; fixCmd++) { if((fixCmd->command == CMD_S3MCMDEX || fixCmd->command == CMD_XFINEPORTAUPDOWN) && (fixCmd->param & 0xF0) == 0x60) { fixCmd->command = CMD_NONE; } } } if(m.command == CMD_S3MCMDEX && (m.param & 0xF0) == 0xE0) { // OpenMPT 1.20 fixes multiple pattern delays on the same row. Previously, only the *last* command was considered, // but Scream Tracker 3 and Impulse Tracker only consider the *first* command. for(ModCommand *fixCmd = (&m) - curChn; fixCmd < &m; fixCmd++) { if(fixCmd->command == CMD_S3MCMDEX && (fixCmd->param & 0xF0) == 0xE0) { fixCmd->command = CMD_NONE; } } } } if(m.volcmd == VOLCMD_VIBRATODEPTH && version < MPT_V("1.27.00.37") && version != MPT_V("1.27.00.00")) { // Fix handling of double vibrato commands - previously only one of them was applied at a time if(m.command == CMD_VIBRATOVOL && m.vol > 0) { m.command = CMD_VOLUMESLIDE; } else if((m.command == CMD_VIBRATO || m.command == CMD_FINEVIBRATO) && (m.param & 0x0F) == 0) { m.command = CMD_VIBRATO; m.param |= (m.vol & 0x0F); m.volcmd = VOLCMD_NONE; } else if(m.command == CMD_VIBRATO || m.command == CMD_VIBRATOVOL || m.command == CMD_FINEVIBRATO) { m.volcmd = VOLCMD_NONE; } } // Volume column offset in IT/XM is bad, mkay? if(modType != MOD_TYPE_MPT && m.volcmd == VOLCMD_OFFSET && m.command == CMD_NONE) { m.command = CMD_OFFSET; m.param = m.vol << 3; m.volcmd = VOLCMD_NONE; } } const CSoundFile &sndFile; CHANNELINDEX chn = 0; const bool compatPlay; }; void CSoundFile::UpgradeModule() { if(m_dwLastSavedWithVersion < MPT_V("1.17.02.46") && m_dwLastSavedWithVersion != MPT_V("1.17.00.00")) { // Compatible playback mode didn't exist in earlier versions, so definitely disable it. m_playBehaviour.reset(MSF_COMPATIBLE_PLAY); } const bool compatModeIT = m_playBehaviour[MSF_COMPATIBLE_PLAY] && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)); const bool compatModeXM = m_playBehaviour[MSF_COMPATIBLE_PLAY] && GetType() == MOD_TYPE_XM; if(m_dwLastSavedWithVersion < MPT_V("1.20.00.00")) { for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) if(Instruments[i] != nullptr) { ModInstrument *ins = Instruments[i]; // Previously, volume swing values ranged from 0 to 64. They should reach from 0 to 100 instead. ins->nVolSwing = static_cast(std::min(static_cast(ins->nVolSwing * 100 / 64), uint32(100))); if(!compatModeIT || m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) { // Previously, Pitch/Pan Separation was only half depth (plot twist: it was actually only quarter depth). // This was corrected in compatible mode in OpenMPT 1.18, and in OpenMPT 1.20 it is corrected in normal mode as well. ins->nPPS = (ins->nPPS + (ins->nPPS >= 0 ? 1 : -1)) / 2; } if(!compatModeIT || m_dwLastSavedWithVersion < MPT_V("1.17.03.02")) { // IT compatibility 24. Short envelope loops // Previously, the pitch / filter envelope loop handling was broken, the loop was shortened by a tick (like in XM). // This was corrected in compatible mode in OpenMPT 1.17.03.02, and in OpenMPT 1.20 it is corrected in normal mode as well. ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType()); } if(m_dwLastSavedWithVersion >= MPT_V("1.17.00.00") && m_dwLastSavedWithVersion < MPT_V("1.17.02.50")) { // If there are any plugins that can receive volume commands, enable volume bug emulation. if(ins->nMixPlug && ins->HasValidMIDIChannel()) { m_playBehaviour.set(kMIDICCBugEmulation); } } if(m_dwLastSavedWithVersion < MPT_V("1.17.02.50") && (ins->nVolSwing | ins->nPanSwing | ins->nCutSwing | ins->nResSwing)) { // If there are any instruments with random variation, enable the old random variation behaviour. m_playBehaviour.set(kMPTOldSwingBehaviour); break; } } if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (m_dwLastSavedWithVersion < MPT_V("1.17.03.02") || !compatModeIT)) { // In the IT format, a sweep value of 0 shouldn't apply vibrato at all. Previously, a value of 0 was treated as "no sweep". // In OpenMPT 1.17.03.02, this was corrected in compatible mode, in OpenMPT 1.20 it is corrected in normal mode as well, // so we have to fix the setting while loading. for(SAMPLEINDEX i = 1; i <= GetNumSamples(); i++) { if(Samples[i].nVibSweep == 0 && (Samples[i].nVibDepth | Samples[i].nVibRate)) { Samples[i].nVibSweep = 255; } } } // Fix old nasty broken (non-standard) MIDI configs in files. m_MidiCfg.UpgradeMacros(); } if(m_dwLastSavedWithVersion < MPT_V("1.20.02.10") && m_dwLastSavedWithVersion != MPT_V("1.20.00.00") && (GetType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT))) { bool instrPlugs = false; // Old pitch wheel commands were closest to sample pitch bend commands if the PWD is 13. for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) { if(Instruments[i] != nullptr && Instruments[i]->nMidiChannel != MidiNoChannel) { Instruments[i]->midiPWD = 13; instrPlugs = true; } } if(instrPlugs) { m_playBehaviour.set(kOldMIDIPitchBends); } } if(m_dwLastSavedWithVersion < MPT_V("1.22.03.12") && m_dwLastSavedWithVersion != MPT_V("1.22.00.00") && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (m_playBehaviour[MSF_COMPATIBLE_PLAY] || m_playBehaviour[kMPTOldSwingBehaviour])) { // The "correct" pan swing implementation did nothing if the instrument also had a pan envelope. // If there's a pan envelope, disable pan swing for such modules. for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) { if(Instruments[i] != nullptr && Instruments[i]->nPanSwing != 0 && Instruments[i]->PanEnv.dwFlags[ENV_ENABLED]) { Instruments[i]->nPanSwing = 0; } } } #ifndef NO_PLUGINS if(m_dwLastSavedWithVersion < MPT_V("1.22.07.01")) { // Convert ANSI plugin path names to UTF-8 (irrelevant in probably 99% of all cases anyway, I think I've never seen a VST plugin with a non-ASCII file name) for(auto &plugin : m_MixPlugins) { #if defined(MODPLUG_TRACKER) const std::string name = mpt::ToCharset(mpt::Charset::UTF8, mpt::Charset::Locale, plugin.Info.szLibraryName); #else const std::string name = mpt::ToCharset(mpt::Charset::UTF8, mpt::Charset::Windows1252, plugin.Info.szLibraryName); #endif plugin.Info.szLibraryName = name; } } #endif // NO_PLUGINS // Starting from OpenMPT 1.22.07.19, FT2-style panning was applied in compatible mix mode. // Starting from OpenMPT 1.23.01.04, FT2-style panning has its own mix mode instead. if(GetType() == MOD_TYPE_XM) { if(m_dwLastSavedWithVersion >= MPT_V("1.22.07.19") && m_dwLastSavedWithVersion < MPT_V("1.23.01.04") && GetMixLevels() == mixLevelsCompatible) { SetMixLevels(mixLevelsCompatibleFT2); } } if(m_dwLastSavedWithVersion < MPT_V("1.25.00.07") && m_dwLastSavedWithVersion != MPT_V("1.25.00.00")) { // Instrument plugins can now receive random volume variation. // For old instruments, disable volume swing in case there was no sample associated. for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) { if(Instruments[i] != nullptr && Instruments[i]->nVolSwing != 0 && Instruments[i]->nMidiChannel != MidiNoChannel) { bool hasSample = false; for(auto smp : Instruments[i]->Keyboard) { if(smp != 0) { hasSample = true; break; } } if(!hasSample) { Instruments[i]->nVolSwing = 0; } } } } if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) { for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) if(Instruments[i] != nullptr) { ModInstrument *ins = Instruments[i]; // Even after fixing it in OpenMPT 1.18, instrument PPS was only half the depth. ins->nPPS = (ins->nPPS + (ins->nPPS >= 0 ? 1 : -1)) / 2; // OpenMPT 1.18 fixed the depth of random pan in compatible mode. // OpenMPT 1.26 fixes it in normal mode too. if(!compatModeIT || m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) { ins->nPanSwing = (ins->nPanSwing + 3) / 4u; } } } if(m_dwLastSavedWithVersion < MPT_V("1.28.00.12")) { for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) if(Instruments[i] != nullptr) { if(Instruments[i]->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET) { m_playBehaviour.set(kLegacyReleaseNode); break; } } } if(m_dwLastSavedWithVersion < MPT_V("1.28.03.04")) { for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) if (Instruments[i] != nullptr) { if(Instruments[i]->pluginVolumeHandling == PLUGIN_VOLUMEHANDLING_MIDI || Instruments[i]->pluginVolumeHandling == PLUGIN_VOLUMEHANDLING_DRYWET) { m_playBehaviour.set(kMIDIVolumeOnNoteOffBug); break; } } } Patterns.ForEachModCommand(UpgradePatternData(*this)); // Convert compatibility flags // NOTE: Some of these version numbers are just approximations. // Sometimes a quirk flag is shared by several code locations which might have been fixed at different times. // Sometimes the quirk behaviour has been revised over time, in which case the first version that emulated the quirk enables it. struct PlayBehaviourVersion { PlayBehaviour behaviour; Version version; }; if(compatModeIT && m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) { // Pre-1.26: Detailed compatibility flags did not exist. static constexpr PlayBehaviourVersion behaviours[] = { { kTempoClamp, MPT_V("1.17.03.02") }, { kPerChannelGlobalVolSlide, MPT_V("1.17.03.02") }, { kPanOverride, MPT_V("1.17.03.02") }, { kITInstrWithoutNote, MPT_V("1.17.02.46") }, { kITVolColFinePortamento, MPT_V("1.17.02.49") }, { kITArpeggio, MPT_V("1.17.02.49") }, { kITOutOfRangeDelay, MPT_V("1.17.02.49") }, { kITPortaMemoryShare, MPT_V("1.17.02.49") }, { kITPatternLoopTargetReset, MPT_V("1.17.02.49") }, { kITFT2PatternLoop, MPT_V("1.17.02.49") }, { kITPingPongNoReset, MPT_V("1.17.02.51") }, { kITEnvelopeReset, MPT_V("1.17.02.51") }, { kITClearOldNoteAfterCut, MPT_V("1.17.02.52") }, { kITVibratoTremoloPanbrello, MPT_V("1.17.03.02") }, { kITTremor, MPT_V("1.17.03.02") }, { kITRetrigger, MPT_V("1.17.03.02") }, { kITMultiSampleBehaviour, MPT_V("1.17.03.02") }, { kITPortaTargetReached, MPT_V("1.17.03.02") }, { kITPatternLoopBreak, MPT_V("1.17.03.02") }, { kITOffset, MPT_V("1.17.03.02") }, { kITSwingBehaviour, MPT_V("1.18.00.00") }, { kITNNAReset, MPT_V("1.18.00.00") }, { kITSCxStopsSample, MPT_V("1.18.00.01") }, { kITEnvelopePositionHandling, MPT_V("1.18.01.00") }, { kITPortamentoInstrument, MPT_V("1.19.00.01") }, { kITPingPongMode, MPT_V("1.19.00.21") }, { kITRealNoteMapping, MPT_V("1.19.00.30") }, { kITHighOffsetNoRetrig, MPT_V("1.20.00.14") }, { kITFilterBehaviour, MPT_V("1.20.00.35") }, { kITNoSurroundPan, MPT_V("1.20.00.53") }, { kITShortSampleRetrig, MPT_V("1.20.00.54") }, { kITPortaNoNote, MPT_V("1.20.00.56") }, { kRowDelayWithNoteDelay, MPT_V("1.20.00.76") }, { kITFT2DontResetNoteOffOnPorta, MPT_V("1.20.02.06") }, { kITVolColMemory, MPT_V("1.21.01.16") }, { kITPortamentoSwapResetsPos, MPT_V("1.21.01.25") }, { kITEmptyNoteMapSlot, MPT_V("1.21.01.25") }, { kITFirstTickHandling, MPT_V("1.22.07.09") }, { kITSampleAndHoldPanbrello, MPT_V("1.22.07.19") }, { kITClearPortaTarget, MPT_V("1.23.04.03") }, { kITPanbrelloHold, MPT_V("1.24.01.06") }, { kITPanningReset, MPT_V("1.24.01.06") }, { kITPatternLoopWithJumpsOld, MPT_V("1.25.00.19") }, }; for(const auto &b : behaviours) { m_playBehaviour.set(b.behaviour, (m_dwLastSavedWithVersion >= b.version || m_dwLastSavedWithVersion == b.version.Masked(0xFFFF0000u))); } } else if(compatModeXM && m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) { // Pre-1.26: Detailed compatibility flags did not exist. static constexpr PlayBehaviourVersion behaviours[] = { { kTempoClamp, MPT_V("1.17.03.02") }, { kPerChannelGlobalVolSlide, MPT_V("1.17.03.02") }, { kPanOverride, MPT_V("1.17.03.02") }, { kITFT2PatternLoop, MPT_V("1.17.03.02") }, { kFT2Arpeggio, MPT_V("1.17.03.02") }, { kFT2Retrigger, MPT_V("1.17.03.02") }, { kFT2VolColVibrato, MPT_V("1.17.03.02") }, { kFT2PortaNoNote, MPT_V("1.17.03.02") }, { kFT2KeyOff, MPT_V("1.17.03.02") }, { kFT2PanSlide, MPT_V("1.17.03.02") }, { kFT2ST3OffsetOutOfRange, MPT_V("1.17.03.02") }, { kFT2RestrictXCommand, MPT_V("1.18.00.00") }, { kFT2RetrigWithNoteDelay, MPT_V("1.18.00.00") }, { kFT2SetPanEnvPos, MPT_V("1.18.00.00") }, { kFT2PortaIgnoreInstr, MPT_V("1.18.00.01") }, { kFT2VolColMemory, MPT_V("1.18.01.00") }, { kFT2LoopE60Restart, MPT_V("1.18.02.01") }, { kFT2ProcessSilentChannels, MPT_V("1.18.02.01") }, { kFT2ReloadSampleSettings, MPT_V("1.20.00.36") }, { kFT2PortaDelay, MPT_V("1.20.00.40") }, { kFT2Transpose, MPT_V("1.20.00.62") }, { kFT2PatternLoopWithJumps, MPT_V("1.20.00.69") }, { kFT2PortaTargetNoReset, MPT_V("1.20.00.69") }, { kFT2EnvelopeEscape, MPT_V("1.20.00.77") }, { kFT2Tremor, MPT_V("1.20.01.11") }, { kFT2OutOfRangeDelay, MPT_V("1.20.02.02") }, { kFT2Periods, MPT_V("1.22.03.01") }, { kFT2PanWithDelayedNoteOff, MPT_V("1.22.03.02") }, { kFT2VolColDelay, MPT_V("1.22.07.19") }, { kFT2FinetunePrecision, MPT_V("1.22.07.19") }, }; for(const auto &b : behaviours) { m_playBehaviour.set(b.behaviour, m_dwLastSavedWithVersion >= b.version); } } if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) { // The following behaviours were added in/after OpenMPT 1.26, so are not affected by the upgrade mechanism above. static constexpr PlayBehaviourVersion behaviours[] = { { kITInstrWithNoteOff, MPT_V("1.26.00.01") }, { kITMultiSampleInstrumentNumber, MPT_V("1.27.00.27") }, { kITInstrWithNoteOffOldEffects, MPT_V("1.28.02.06") }, { kITDoNotOverrideChannelPan, MPT_V("1.29.00.22") }, { kITPatternLoopWithJumps, MPT_V("1.29.00.32") }, { kITDCTBehaviour, MPT_V("1.29.00.57") }, }; for(const auto &b : behaviours) { if(m_dwLastSavedWithVersion < b.version.Masked(0xFFFF0000u)) m_playBehaviour.reset(b.behaviour); // Full version information available, i.e. not compatibility-exported. else if(m_dwLastSavedWithVersion > b.version.Masked(0xFFFF0000u) && m_dwLastSavedWithVersion < b.version) m_playBehaviour.reset(b.behaviour); } } else if(GetType() == MOD_TYPE_XM) { // The following behaviours were added after OpenMPT 1.26, so are not affected by the upgrade mechanism above. static constexpr PlayBehaviourVersion behaviours[] = { { kFT2NoteOffFlags, MPT_V("1.27.00.27") }, { kRowDelayWithNoteDelay, MPT_V("1.27.00.37") }, { kFT2MODTremoloRampWaveform, MPT_V("1.27.00.37") }, { kFT2PortaUpDownMemory, MPT_V("1.27.00.37") }, { kFT2PanSustainRelease, MPT_V("1.28.00.09") }, { kFT2NoteDelayWithoutInstr, MPT_V("1.28.00.44") }, { kITFT2DontResetNoteOffOnPorta, MPT_V("1.29.00.34" )}, }; for(const auto &b : behaviours) { if(m_dwLastSavedWithVersion < b.version) m_playBehaviour.reset(b.behaviour); } } else if(GetType() == MOD_TYPE_S3M) { // We do not store any of these flags in S3M files. static constexpr PlayBehaviourVersion behaviours[] = { { kST3NoMutedChannels, MPT_V("1.18.00.00") }, { kST3EffectMemory, MPT_V("1.20.00.00") }, { kRowDelayWithNoteDelay, MPT_V("1.20.00.00") }, { kST3PortaSampleChange, MPT_V("1.22.00.00") }, { kST3VibratoMemory, MPT_V("1.26.00.00") }, { kITPanbrelloHold, MPT_V("1.26.00.00") }, { KST3PortaAfterArpeggio, MPT_V("1.27.00.00") }, { kST3OffsetWithoutInstrument, MPT_V("1.28.00.00") }, { kST3RetrigAfterNoteCut, MPT_V("1.29.00.00") }, { kFT2ST3OffsetOutOfRange, MPT_V("1.29.00.00") }, }; for(const auto &b : behaviours) { if(m_dwLastSavedWithVersion < b.version) m_playBehaviour.reset(b.behaviour); } } if(GetType() == MOD_TYPE_XM && m_dwLastSavedWithVersion < MPT_V("1.19.00.00")) { // This bug was introduced sometime between 1.18.03.00 and 1.19.01.00 m_playBehaviour.set(kFT2NoteDelayWithoutInstr); } if(m_dwLastSavedWithVersion >= MPT_V("1.27.00.27") && m_dwLastSavedWithVersion < MPT_V("1.27.00.49")) { // OpenMPT 1.27 inserted some IT/FT2 flags before the S3M flags that are never saved to files anyway, to keep the flag IDs a bit more compact. // However, it was overlooked that these flags would still be read by OpenMPT 1.26 and thus S3M-specific behaviour would be enabled in IT/XM files. // Hence, in OpenMPT 1.27.00.49 the flag IDs got remapped to no longer conflict with OpenMPT 1.26. // Files made with the affected pre-release versions of OpenMPT 1.27 are upgraded here to use the new IDs. for(int i = 0; i < 5; i++) { m_playBehaviour.set(kFT2NoteOffFlags + i, m_playBehaviour[kST3NoMutedChannels + i]); m_playBehaviour.reset(kST3NoMutedChannels + i); } } if(m_dwLastSavedWithVersion < MPT_V("1.17.00.00")) { // MPT 1.16 has a maximum tempo of 255. m_playBehaviour.set(kTempoClamp); } else if(m_dwLastSavedWithVersion >= MPT_V("1.17.00.00") && m_dwLastSavedWithVersion <= MPT_V("1.20.01.03") && m_dwLastSavedWithVersion != MPT_V("1.20.00.00")) { // OpenMPT introduced some "fixes" that execute regular portamentos also at speed 1. m_playBehaviour.set(kSlidesAtSpeed1); } if(m_dwLastSavedWithVersion < MPT_V("1.24.00.00")) { // No frequency slides in Hz before OpenMPT 1.24 m_playBehaviour.reset(kHertzInLinearMode); } else if(m_dwLastSavedWithVersion >= MPT_V("1.24.00.00") && m_dwLastSavedWithVersion < MPT_V("1.26.00.00") && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) { // Frequency slides were always in Hz rather than periods in this version range. m_playBehaviour.set(kHertzInLinearMode); } if(m_playBehaviour[kITEnvelopePositionHandling] && m_dwLastSavedWithVersion >= MPT_V("1.23.01.02") && m_dwLastSavedWithVersion < MPT_V("1.28.00.43")) { // Bug that effectively clamped the release node to the sustain end for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) if(Instruments[i] != nullptr) { if(Instruments[i]->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET && Instruments[i]->VolEnv.dwFlags[ENV_SUSTAIN] && Instruments[i]->VolEnv.nReleaseNode > Instruments[i]->VolEnv.nSustainEnd) { m_playBehaviour.set(kReleaseNodePastSustainBug); break; } } } if(GetType() & (MOD_TYPE_MPT | MOD_TYPE_S3M)) { for(SAMPLEINDEX i = 1; i <= GetNumSamples(); i++) { if(Samples[i].uFlags[CHN_ADLIB]) { if(GetType() == MOD_TYPE_MPT && GetNumInstruments() && m_dwLastSavedWithVersion >= MPT_V("1.28.00.20") && m_dwLastSavedWithVersion <= MPT_V("1.29.00.55")) m_playBehaviour.set(kOPLNoResetAtEnvelopeEnd); if(GetType() == MOD_TYPE_S3M && m_dwLastSavedWithVersion < MPT_V("1.29")) m_playBehaviour.set(kOPLRealRetrig); else if(GetType() != MOD_TYPE_S3M) m_playBehaviour.reset(kOPLRealRetrig); break; } } } } OPENMPT_NAMESPACE_END