diff --git a/common/versionNumber.h b/common/versionNumber.h index a367dfc62ac..c8441b918b9 100644 --- a/common/versionNumber.h +++ b/common/versionNumber.h @@ -16,4 +16,4 @@ #define VER_MAJORMAJOR 1 #define VER_MAJOR 32 #define VER_MINOR 00 -#define VER_MINORMINOR 26 +#define VER_MINORMINOR 27 diff --git a/mptrack/dlg_misc.cpp b/mptrack/dlg_misc.cpp index d8b93855bb9..812b134488a 100644 --- a/mptrack/dlg_misc.cpp +++ b/mptrack/dlg_misc.cpp @@ -730,6 +730,7 @@ void CLegacyPlaybackSettingsDlg::OnFilterStringChanged() case kITOffsetWithInstrNumber: desc = _T("Offset command with instrument number recalls offset with last note"); break; case kContinueSampleWithoutInstr: desc = _T("New note without instrument number does not play looped samples from the start"); break; case kMIDINotesFromChannelPlugin: desc = _T("MIDI notes can be sent to channel plugins"); break; + case kITDoublePortamentoSlides: desc = _T("Parameters of conflicting volume and effect column portamento commands may overwrite each other"); break; default: MPT_ASSERT_NOTREACHED(); } diff --git a/soundlib/Snd_defs.h b/soundlib/Snd_defs.h index 8aabe365d42..f0407375114 100644 --- a/soundlib/Snd_defs.h +++ b/soundlib/Snd_defs.h @@ -598,6 +598,7 @@ enum PlayBehaviour kITOffsetWithInstrNumber, // IT applies offset commands even if just an instrument number without note is present kContinueSampleWithoutInstr, // FTM: A note without instrument number continues looped samples with the new pitch instead of retriggering them kMIDINotesFromChannelPlugin, // Behaviour before OpenMPT 1.26: Channel plugin can be used to send MIDI notes + kITDoublePortamentoSlides, // IT only reads parameters once per row, so if two commands sharing effect parameters are found in the two effect columns, they influence each other // Add new play behaviours here. diff --git a/soundlib/Snd_fx.cpp b/soundlib/Snd_fx.cpp index e541bd11111..38c6c2c4eb6 100644 --- a/soundlib/Snd_fx.cpp +++ b/soundlib/Snd_fx.cpp @@ -138,7 +138,7 @@ class GetLengthMemory case VOLCMD_TONEPORTAMENTO: { const auto [porta, clearEffectCommand] = sndFile.GetVolCmdTonePorta(m, 0); - sndFile.TonePortamento(*state, channel, porta); + sndFile.TonePortamento(*state, channel, porta, true); if(clearEffectCommand) command = CMD_NONE; } @@ -155,10 +155,10 @@ class GetLengthMemory switch(command) { case CMD_TONEPORTAMENTO: - sndFile.TonePortamento(*state, channel, m.param); + sndFile.TonePortamento(*state, channel, m.param, false); break; case CMD_TONEPORTAVOL: - sndFile.TonePortamento(*state, channel, 0); + sndFile.TonePortamento(*state, channel, 0, false); break; case CMD_PORTAMENTOUP: if(m.param || !(sndFile.GetType() & MOD_TYPE_MOD)) @@ -193,7 +193,7 @@ class GetLengthMemory } if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamento) && !chn.rowCommand.IsTonePortamento()) - sndFile.TonePortamento(*state, channel, chn.portamentoSlide); + sndFile.TonePortamento(*state, channel, chn.portamentoSlide, false); else if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration)) sndFile.TonePortamentoWithDuration(chn, 0); if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) @@ -3145,6 +3145,32 @@ bool CSoundFile::ProcessEffects() { doVolumeColumn = m_PlayState.m_nTickCount != 0 && (m_PlayState.m_nTickCount != nStartTick || (chn.rowCommand.instr == 0 && volcmd != VOLCMD_TONEPORTAMENTO)); } + + // IT compatibility: Various mind-boggling behaviours when combining volume colum and effect column portamentos + // The most crucial thing here is to initialize effect memory in the exact right order, and use different effect memory for tone portamento in the both columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(m_playBehaviour[kITDoublePortamentoSlides] && chn.isFirstTick) + { + const bool effectColumnTonePorta = (cmd == CMD_TONEPORTAMENTO || cmd == CMD_TONEPORTAVOL); + if(effectColumnTonePorta) + InitTonePortamento(chn, static_cast(param)); + if(volcmd == VOLCMD_TONEPORTAMENTO) + InitTonePortamento(chn, GetVolCmdTonePorta(chn.rowCommand, nStartTick).first); + + if(vol && (volcmd == VOLCMD_PORTAUP || volcmd == VOLCMD_PORTADOWN)) + { + chn.nOldPortaUp = chn.nOldPortaDown = vol << 2; + if(!effectColumnTonePorta && TonePortamentoSharesEffectMemory()) + chn.portamentoSlide = vol << 2; + } + if(param && (cmd == CMD_PORTAMENTOUP || cmd == CMD_PORTAMENTODOWN)) + { + chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + if(TonePortamentoSharesEffectMemory()) + chn.portamentoSlide = static_cast(param); + } + } + if(volcmd > VOLCMD_PANNING && doVolumeColumn) { if(volcmd == VOLCMD_TONEPORTAMENTO) @@ -3153,7 +3179,7 @@ bool CSoundFile::ProcessEffects() if(clearEffectCommand) cmd = CMD_NONE; - TonePortamento(nChn, porta); + TonePortamento(nChn, porta, true); } else { // FT2 Compatibility: FT2 ignores some volume commands with parameter = 0. @@ -3333,13 +3359,13 @@ bool CSoundFile::ProcessEffects() // Tone-Portamento case CMD_TONEPORTAMENTO: - TonePortamento(nChn, static_cast(param)); + TonePortamento(nChn, static_cast(param), false); break; // Tone-Portamento + Volume Slide case CMD_TONEPORTAVOL: if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast(param)); - TonePortamento(nChn, 0); + TonePortamento(nChn, 0, false); break; // Vibrato @@ -3873,7 +3899,7 @@ void CSoundFile::ProcessAutoSlides(PlayState &playState, CHANNELINDEX channel) { ModChannel &chn = playState.Chn[channel]; if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamento) && !chn.rowCommand.IsTonePortamento()) - TonePortamento(channel, chn.portamentoSlide); + TonePortamento(channel, chn.portamentoSlide, false); else if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration)) TonePortamentoWithDuration(chn); if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) @@ -4027,7 +4053,9 @@ void CSoundFile::PortamentoUp(PlayState &playState, CHANNELINDEX nChn, ModComman { ModChannel &chn = playState.Chn[nChn]; - if(param) + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(param && !m_playBehaviour[kITDoublePortamentoSlides]) { // FT2 compatibility: Separate effect memory for all portamento commands // Test case: Porta-LinkMem.xm @@ -4098,7 +4126,9 @@ void CSoundFile::PortamentoDown(PlayState &playState, CHANNELINDEX nChn, ModComm { ModChannel &chn = playState.Chn[nChn]; - if(param) + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(param && !m_playBehaviour[kITDoublePortamentoSlides]) { // FT2 compatibility: Separate effect memory for all portamento commands // Test case: Porta-LinkMem.xm @@ -4394,9 +4424,30 @@ std::pair CSoundFile::GetVolCmdTonePorta(const ModCommand &m, uint } -void CSoundFile::TonePortamento(CHANNELINDEX nChn, uint16 param) +bool CSoundFile::TonePortamentoSharesEffectMemory() const +{ + return (!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM; +} + + +void CSoundFile::InitTonePortamento(ModChannel &chn, uint16 param) const +{ + // IT compatibility 03: Share effect memory with portamento up/down + if(TonePortamentoSharesEffectMemory()) + { + if(param == 0) + param = chn.nOldPortaUp; + chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + } + + if(param) + chn.portamentoSlide = param; +} + + +void CSoundFile::TonePortamento(CHANNELINDEX nChn, uint16 param, bool volumeColumn) { - auto delta = TonePortamento(m_PlayState, nChn, param); + auto delta = TonePortamento(m_PlayState, nChn, param, volumeColumn); if(!delta) return; @@ -4415,33 +4466,39 @@ void CSoundFile::TonePortamento(CHANNELINDEX nChn, uint16 param) // Portamento Slide -int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param) const +int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param, bool volumeColumn) const { ModChannel &chn = playState.Chn[nChn]; chn.dwFlags.set(CHN_PORTAMENTO); if(m_SongFlags[SONG_AUTO_TONEPORTA]) chn.autoSlide.SetActive(AutoSlideCommand::TonePortamento, param != 0 || m_SongFlags[SONG_AUTO_TONEPORTA_CONT]); - //IT compatibility 03: Share effect memory with portamento up/down - if((!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM) + int32 delta; + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Also, tone portamento in the effect column stores the slide amount separately from the regular E/F/G memory once the effect is running, + // while volume column tone portamento always accesses the effect memory directly. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(m_playBehaviour[kITDoublePortamentoSlides]) { - if(param == 0) param = chn.nOldPortaUp; - chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + if(volumeColumn && TonePortamentoSharesEffectMemory()) + delta = chn.nOldPortaUp; + else + delta = chn.portamentoSlide; + } else + { + InitTonePortamento(chn, param); + delta = chn.portamentoSlide; } - if(param) - chn.portamentoSlide = param; - if(chn.HasCustomTuning()) { //Behavior: Param tells number of finesteps(or 'fullsteps'(notes) with glissando) //to slide per row(not per tick). - if(chn.portamentoSlide == 0) + if(delta == 0) return 0; const int32 oldPortamentoTickSlide = (playState.m_nTickCount != 0) ? chn.m_PortamentoTickSlide : 0; - int32 delta = chn.portamentoSlide; if(chn.nPortamentoDest < 0) delta = -delta; @@ -4483,7 +4540,6 @@ int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 || (playState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) || m_SongFlags[SONG_FASTPORTAS]; - int32 delta = chn.portamentoSlide; if(GetType() == MOD_TYPE_PLM && delta >= 0xF0) { delta -= 0xF0; diff --git a/soundlib/Sndfile.cpp b/soundlib/Sndfile.cpp index 94252ff9bb7..d1bcecceaff 100644 --- a/soundlib/Sndfile.cpp +++ b/soundlib/Sndfile.cpp @@ -1198,6 +1198,7 @@ PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) playBehaviour.set(kITNoSustainOnPortamento); playBehaviour.set(kITEmptyNoteMapSlotIgnoreCell); playBehaviour.set(kITOffsetWithInstrNumber); + playBehaviour.set(kITDoublePortamentoSlides); break; case MOD_TYPE_XM: diff --git a/soundlib/Sndfile.h b/soundlib/Sndfile.h index 5b6682a8810..4da300e6a9f 100644 --- a/soundlib/Sndfile.h +++ b/soundlib/Sndfile.h @@ -1086,8 +1086,10 @@ class CSoundFile int16 CalculateFinetuneTarget(PATTERNINDEX pattern, ROWINDEX row, CHANNELINDEX channel) const; void NoteSlide(ModChannel &chn, uint32 param, bool slideUp, bool retrig) const; std::pair GetVolCmdTonePorta(const ModCommand &m, uint32 startTick) const; - void TonePortamento(CHANNELINDEX chn, uint16 param); - int32 TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param) const; + bool TonePortamentoSharesEffectMemory() const; + void InitTonePortamento(ModChannel &chn, uint16 param) const; + void TonePortamento(CHANNELINDEX chn, uint16 param, bool volumeColumn); + int32 TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param, bool volumeColumn) const; void TonePortamentoWithDuration(ModChannel &chn, uint16 param = uint16_max) const; void Vibrato(ModChannel &chn, uint32 param) const; void FineVibrato(ModChannel &chn, uint32 param) const; diff --git a/soundlib/UpgradeModule.cpp b/soundlib/UpgradeModule.cpp index 6bf6c30907b..cf6c39071c9 100644 --- a/soundlib/UpgradeModule.cpp +++ b/soundlib/UpgradeModule.cpp @@ -601,6 +601,7 @@ void CSoundFile::UpgradeModule() { kITNoSustainOnPortamento, MPT_V("1.32.00.13") }, { kITEmptyNoteMapSlotIgnoreCell, MPT_V("1.32.00.13") }, { kITOffsetWithInstrNumber, MPT_V("1.32.00.15") }, + { kITDoublePortamentoSlides, MPT_V("1.32.00.27") }, }; for(const auto &b : behaviours)