From: <sv...@op...> - 2024-05-28 17:05:19
|
Author: sagamusix Date: Sun May 26 00:27:30 2024 New Revision: 20850 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=20850 Log: [New] Better support for automatic slide commands in various formats (https://bugs.openmpt.org/view.php?id=1773). [Mod] OpenMPT: Version is now 1.32.00.14 Modified: trunk/OpenMPT/common/versionNumber.h trunk/OpenMPT/mptrack/EffectInfo.cpp trunk/OpenMPT/soundlib/Load_669.cpp trunk/OpenMPT/soundlib/Load_far.cpp trunk/OpenMPT/soundlib/Load_gmc.cpp trunk/OpenMPT/soundlib/Load_stk.cpp trunk/OpenMPT/soundlib/Load_stp.cpp trunk/OpenMPT/soundlib/Load_ult.cpp trunk/OpenMPT/soundlib/Load_xmf.cpp trunk/OpenMPT/soundlib/ModChannel.cpp trunk/OpenMPT/soundlib/ModChannel.h trunk/OpenMPT/soundlib/Snd_defs.h trunk/OpenMPT/soundlib/Snd_fx.cpp trunk/OpenMPT/soundlib/Sndfile.h trunk/OpenMPT/soundlib/Sndmix.cpp trunk/OpenMPT/soundlib/mod_specifications.cpp trunk/OpenMPT/soundlib/modcommand.cpp trunk/OpenMPT/soundlib/modcommand.h Modified: trunk/OpenMPT/common/versionNumber.h ============================================================================== --- trunk/OpenMPT/common/versionNumber.h Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/common/versionNumber.h Sun May 26 00:27:30 2024 (r20850) @@ -18,6 +18,6 @@ #define VER_MAJORMAJOR 1 #define VER_MAJOR 32 #define VER_MINOR 00 -#define VER_MINORMINOR 13 +#define VER_MINORMINOR 14 OPENMPT_NAMESPACE_END Modified: trunk/OpenMPT/mptrack/EffectInfo.cpp ============================================================================== --- trunk/OpenMPT/mptrack/EffectInfo.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/mptrack/EffectInfo.cpp Sun May 26 00:27:30 2024 (r20850) @@ -128,6 +128,11 @@ {CMD_VOLUME8, 0, 0, 0, MOD_TYPE_NONE, _T("Set 8-bit Volume")}, {CMD_HMN_MEGA_ARP, 0, 0, 0, MOD_TYPE_NONE, _T("His Master's Noise Mega-Arpeggio")}, {CMD_MED_SYNTH_JUMP, 0, 0, 0, MOD_TYPE_NONE, _T("MED Synth Jump / MIDI Panning")}, + {CMD_AUTO_VOLUMESLIDE, 0, 0, 0, MOD_TYPE_NONE, _T("Automatic Volume Slide")}, + {CMD_AUTO_PORTAUP, 0, 0, 0, MOD_TYPE_NONE, _T("Automatic Portamento Up")}, + {CMD_AUTO_PORTADOWN, 0, 0, 0, MOD_TYPE_NONE, _T("Automatic Portamento Down")}, + {CMD_AUTO_PORTAUP_FINE, 0, 0, 0, MOD_TYPE_NONE, _T("Automatic Fine Portamento Up")}, + {CMD_AUTO_PORTADOWN_FINE, 0, 0, 0, MOD_TYPE_NONE, _T("Automatic Fine Portamento Down")}, }; @@ -944,21 +949,21 @@ static constexpr MPTVolCmdInfo gVolCmdInfo[] = { - {VOLCMD_VOLUME, MOD_TYPE_NOMOD, _T("Set Volume")}, - {VOLCMD_PANNING, MOD_TYPE_NOMOD, _T("Set Panning")}, - {VOLCMD_VOLSLIDEUP, MOD_TYPE_XMITMPT, _T("Volume slide up")}, - {VOLCMD_VOLSLIDEDOWN, MOD_TYPE_XMITMPT, _T("Volume slide down")}, - {VOLCMD_FINEVOLUP, MOD_TYPE_XMITMPT, _T("Fine volume up")}, - {VOLCMD_FINEVOLDOWN, MOD_TYPE_XMITMPT, _T("Fine volume down")}, - {VOLCMD_VIBRATOSPEED, MOD_TYPE_XM, _T("Vibrato speed")}, - {VOLCMD_VIBRATODEPTH, MOD_TYPE_XMITMPT, _T("Vibrato depth")}, - {VOLCMD_PANSLIDELEFT, MOD_TYPE_XM, _T("Pan slide left")}, - {VOLCMD_PANSLIDERIGHT, MOD_TYPE_XM, _T("Pan slide right")}, - {VOLCMD_TONEPORTAMENTO, MOD_TYPE_XMITMPT, _T("Tone portamento")}, - {VOLCMD_PORTAUP, MOD_TYPE_ITMPT, _T("Portamento up")}, - {VOLCMD_PORTADOWN, MOD_TYPE_ITMPT, _T("Portamento down")}, - {VOLCMD_PLAYCONTROL, MOD_TYPE_NONE, _T("Play Control")}, - {VOLCMD_OFFSET, MOD_TYPE_MPT, _T("Sample Cue")}, + {VOLCMD_VOLUME, MOD_TYPE_NOMOD, _T("Set Volume")}, + {VOLCMD_PANNING, MOD_TYPE_NOMOD, _T("Set Panning")}, + {VOLCMD_VOLSLIDEUP, MOD_TYPE_XMITMPT, _T("Volume slide up")}, + {VOLCMD_VOLSLIDEDOWN, MOD_TYPE_XMITMPT, _T("Volume slide down")}, + {VOLCMD_FINEVOLUP, MOD_TYPE_XMITMPT, _T("Fine volume up")}, + {VOLCMD_FINEVOLDOWN, MOD_TYPE_XMITMPT, _T("Fine volume down")}, + {VOLCMD_VIBRATOSPEED, MOD_TYPE_XM, _T("Vibrato speed")}, + {VOLCMD_VIBRATODEPTH, MOD_TYPE_XMITMPT, _T("Vibrato depth")}, + {VOLCMD_PANSLIDELEFT, MOD_TYPE_XM, _T("Pan slide left")}, + {VOLCMD_PANSLIDERIGHT, MOD_TYPE_XM, _T("Pan slide right")}, + {VOLCMD_TONEPORTAMENTO, MOD_TYPE_XMITMPT, _T("Tone portamento")}, + {VOLCMD_PORTAUP, MOD_TYPE_ITMPT, _T("Portamento up")}, + {VOLCMD_PORTADOWN, MOD_TYPE_ITMPT, _T("Portamento down")}, + {VOLCMD_PLAYCONTROL, MOD_TYPE_NONE, _T("Play Control")}, + {VOLCMD_OFFSET, MOD_TYPE_MPT, _T("Sample Cue")}, }; static_assert(mpt::array_size<decltype(gVolCmdInfo)>::size == (MAX_VOLCMDS - 1)); Modified: trunk/OpenMPT/soundlib/Load_669.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_669.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_669.cpp Sun May 26 00:27:30 2024 (r20850) @@ -142,7 +142,7 @@ Order().SetDefaultSpeed(4); m_nChannels = 8; m_playBehaviour.set(kPeriodsAreHertz); - m_SongFlags.set(SONG_FASTPORTAS); + m_SongFlags.set(SONG_FASTPORTAS | SONG_AUTO_TONEPORTA); #ifdef MODPLUG_TRACKER // 669 uses frequencies rather than periods, so linear slides mode will sound better in the higher octaves. //m_SongFlags.set(SONG_LINEARSLIDES); @@ -195,8 +195,8 @@ static constexpr ModCommand::COMMAND effTrans[] = { - CMD_PORTAMENTOUP, // Slide up (param * 80) Hz on every tick - CMD_PORTAMENTODOWN, // Slide down (param * 80) Hz on every tick + CMD_AUTO_PORTAUP, // Slide up (param * 80) Hz on every tick + CMD_AUTO_PORTADOWN, // Slide down (param * 80) Hz on every tick CMD_TONEPORTAMENTO, // Slide to note by (param * 40) Hz on every tick CMD_S3MCMDEX, // Add (param * 80) Hz to sample frequency CMD_VIBRATO, // Add (param * 669) Hz on every other tick Modified: trunk/OpenMPT/soundlib/Load_far.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_far.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_far.cpp Sun May 26 00:27:30 2024 (r20850) @@ -168,7 +168,7 @@ Order().SetDefaultSpeed(fileHeader.defaultSpeed); Order().SetDefaultTempoInt(80); m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; - m_SongFlags = SONG_LINEARSLIDES; + m_SongFlags = SONG_LINEARSLIDES | SONG_AUTO_TONEPORTA; m_playBehaviour.set(kPeriodsAreHertz); m_modFormat.formatName = U_("Farandole Composer"); @@ -244,12 +244,9 @@ ROWINDEX breakRow = patternChunk.ReadUint8(); patternChunk.Skip(1); if(breakRow > 0 && breakRow < numRows - 2) - { breakRow++; - } else - { + else breakRow = ROWINDEX_INVALID; - } // Read pattern data for(ROWINDEX row = 0; row < numRows; row++) @@ -277,24 +274,25 @@ case 0x02: m.param |= 0xF0; break; - case 0x03: // Porta to note (TODO: Parameter is number of rows the portamento should take) - m.param <<= 2; + case 0x03: // Porta to note (TODO: Parameter is number of rows the portamento should take) + if(m.param != 0) + m.param = 60 / m.param; break; - case 0x04: // Retrig - m.param = static_cast<ModCommand::PARAM>(6 / (1 + (m.param & 0xf)) + 1); // ugh? + case 0x04: // Retrig + m.param = static_cast<ModCommand::PARAM>(6 / (1 + (m.param & 0xf)) + 1); break; - case 0x06: // Vibrato speed - case 0x07: // Volume slide up + case 0x06: // Vibrato speed + case 0x07: // Volume slide up m.param *= 8; break; - case 0x0A: // Volume-portamento (what!) + case 0x0A: // Volume-portamento (what!) m.volcmd = VOLCMD_VOLUME; m.vol = static_cast<ModCommand::VOL>((m.param << 2) + 4); break; - case 0x0B: // Panning + case 0x0B: // Panning m.param |= 0x80; break; - case 0x0C: // Note offset + case 0x0C: // Note offset m.param = static_cast<ModCommand::PARAM>(6 / (1 + m.param) + 1); m.param |= 0x0D; } Modified: trunk/OpenMPT/soundlib/Load_gmc.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_gmc.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_gmc.cpp Sun May 26 00:27:30 2024 (r20850) @@ -152,7 +152,6 @@ continue; } - std::array<uint8, 4> portaOn = {0, 0, 0, 0}; for(ROWINDEX row = 0; row < 64; row++) { auto rowBase = Patterns[pat].GetRow(row); @@ -175,7 +174,6 @@ break; case 0x01: // Portamento up case 0x02: // Portamento down - portaOn[chn] = (param != 0) ? command : 0; break; case 0x03: // Volume command = 0x0C; @@ -207,16 +205,11 @@ ConvertModCommand(m, command, param); if(noteCut) m.note = NOTE_NOTECUT; - if(command != 1 && command != 2) - { - if(m.note != NOTE_NONE) - portaOn[chn] = 0; - else if(portaOn[chn] == 1) - m.volcmd = VOLCMD_PORTAUP; - else if(portaOn[chn] == 2) - m.volcmd = VOLCMD_PORTADOWN; - } - if(m.command == CMD_TEMPO) + if(m.command == CMD_PORTAMENTOUP) + m.command = CMD_AUTO_PORTAUP; + else if(m.command == CMD_PORTAMENTODOWN) + m.command = CMD_AUTO_PORTADOWN; + else if(m.command == CMD_TEMPO) m.command = CMD_SPEED; } } Modified: trunk/OpenMPT/soundlib/Load_stk.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_stk.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_stk.cpp Sun May 26 00:27:30 2024 (r20850) @@ -228,7 +228,7 @@ m_nMinPeriod = 113 * 4; m_nMaxPeriod = 856 * 4; m_nSamplePreAmp = 64; - m_SongFlags.set(SONG_PT_MODE); + m_SongFlags.set(SONG_PT_MODE | SONG_AUTO_VOLSLIDE_STK); m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeaders.songname); // Setup channel pan positions and volume @@ -359,7 +359,6 @@ continue; } - uint8 autoSlide[4] = {0, 0, 0, 0}; for(ROWINDEX row = 0; row < 64; row++) { auto rowBase = Patterns[pat].GetRow(row); @@ -368,24 +367,8 @@ ModCommand &m = rowBase[chn]; auto [command, param] = ReadMODPatternEntry(patternData[row][chn], m); - if(!param || command == 0x0E) - { - autoSlide[chn] = 0; - } if(command || param) { - if(autoSlide[chn] != 0) - { - if(autoSlide[chn] & 0xF0) - { - m.volcmd = VOLCMD_VOLSLIDEUP; - m.vol = autoSlide[chn] >> 4; - } else - { - m.volcmd = VOLCMD_VOLSLIDEDOWN; - m.vol = autoSlide[chn] & 0x0F; - } - } if(command == 0x0D) { if(minVersion != ST2_00) @@ -402,9 +385,9 @@ param &= 0x7F; } else if(command == 0x0E && (param > 0x01 || minVersion < ST_IX) && useAutoSlides) { - // Import auto-slides as normal slides and fake them using volume column slides. - command = 0x0A; - autoSlide[chn] = param; + m.command = CMD_AUTO_VOLUMESLIDE; + m.param = param; + continue; } else if(command == 0x0F) { // Only the low nibble is evaluated in Soundtracker. @@ -449,9 +432,6 @@ { ConvertModCommand(m, command, param); } - } else - { - autoSlide[chn] = 0; } } } Modified: trunk/OpenMPT/soundlib/Load_stp.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_stp.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_stp.cpp Sun May 26 00:27:30 2024 (r20850) @@ -262,6 +262,7 @@ m_nChannels = 4; m_nSamples = 0; + m_SongFlags.set(SONG_AUTO_TONEPORTA | SONG_AUTO_GLOBALVOL | SONG_AUTO_VIBRATO | SONG_AUTO_TREMOLO); Order().SetDefaultSpeed(fileHeader.speed); Order().SetDefaultTempo(ConvertTempo(fileHeader.timerCount)); @@ -383,15 +384,7 @@ file.Seek(patOffset); } - struct ChannelMemory - { - uint8 autoFinePorta, autoPortaUp, autoPortaDown, autoVolSlide, autoVibrato; - uint8 vibratoMem, autoTremolo, autoTonePorta, tonePortaMem; - }; - std::vector<ChannelMemory> channelMemory(m_nChannels); - uint8 globalVolSlide = 0; uint8 speedFrac = static_cast<uint8>(fileHeader.speedFrac); - for(uint16 pat = 0; pat < numPatterns; pat++) { PATTERNINDEX actualPat = pat; @@ -419,8 +412,6 @@ { auto rowBase = Patterns[actualPat].GetRow(row); - bool didGlobalVolSlide = false; - // if a fractional speed value is in use then determine if we should stick a fine pattern delay somewhere bool shouldDelay; switch(speedFrac & 3) @@ -436,17 +427,13 @@ for(CHANNELINDEX chn = 0; chn < channels; chn++) { - ChannelMemory &chnMem = channelMemory[chn]; ModCommand &m = rowBase[chn]; const auto [instr, note, command, param] = file.ReadArray<uint8, 4>(); m.instr = instr; m.param = param; if(note) - { m.note = NOTE_MIDDLEC - 36 + note; - chnMem = ChannelMemory(); - } // Volume slides not only have their nibbles swapped, but the up and down parameters also add up const int totalSlide = -static_cast<int>(m.param >> 4) + (m.param & 0x0F); @@ -458,8 +445,7 @@ uint16 ciaTempo = (static_cast<uint16>(command & 0x0F) << 8) | m.param; if(ciaTempo) { - m.param = mpt::saturate_round<ModCommand::PARAM>(ConvertTempo(ciaTempo).ToDouble()); - m.command = CMD_TEMPO; + m.SetEffectCommand(CMD_TEMPO, mpt::saturate_round<ModCommand::PARAM>(ConvertTempo(ciaTempo).ToDouble())); } } else switch(command) { @@ -468,64 +454,51 @@ m.command = CMD_ARPEGGIO; break; case 0x01: // portamento up - m.command = CMD_PORTAMENTOUP; + if(m.param) + m.command = CMD_PORTAMENTOUP; break; case 0x02: // portamento down - m.command = CMD_PORTAMENTODOWN; + if(m.param) + m.command = CMD_PORTAMENTODOWN; break; case 0x03: // auto fine portamento up - chnMem.autoFinePorta = 0x10 | std::min(m.param, ModCommand::PARAM(15)); - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTAUP_FINE; break; case 0x04: // auto fine portamento down - chnMem.autoFinePorta = 0x20 | std::min(m.param, ModCommand::PARAM(15)); - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTADOWN_FINE; break; case 0x05: // auto portamento up - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = m.param; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTAUP; break; case 0x06: // auto portamento down - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = m.param; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTADOWN; break; case 0x07: // set global volume m.command = CMD_GLOBALVOLUME; - globalVolSlide = 0; break; case 0x08: // auto global fine volume slide - globalVolSlide = slideParam; + if(totalSlide < 0) + m.SetEffectCommand(CMD_GLOBALVOLSLIDE, 0xF0 | slideParam); + else if(totalSlide > 0) + m.SetEffectCommand(CMD_GLOBALVOLSLIDE, slideParam | 0x0F); break; case 0x09: // fine portamento up - m.command = CMD_MODCMDEX; - m.param = 0x10 | std::min(m.param, ModCommand::PARAM(15)); + m.SetEffectCommand(CMD_MODCMDEX, 0x10 | std::min(m.param, ModCommand::PARAM(15))); break; case 0x0A: // fine portamento down - m.command = CMD_MODCMDEX; - m.param = 0x20 | std::min(m.param, ModCommand::PARAM(15)); + m.SetEffectCommand(CMD_MODCMDEX, 0x20 | std::min(m.param, ModCommand::PARAM(15))); break; case 0x0B: // auto fine volume slide - chnMem.autoVolSlide = slideParam; + m.SetEffectCommand(CMD_AUTO_VOLUMESLIDE, slideParam); break; case 0x0C: // set volume - m.volcmd = VOLCMD_VOLUME; - m.vol = m.param; - chnMem.autoVolSlide = 0; + m.SetVolumeCommand(VOLCMD_VOLUME, std::min(m.param, ModCommand::PARAM(64))); break; case 0x0D: // volume slide (param is swapped compared to .mod) if(totalSlide < 0) m.SetVolumeCommand(VOLCMD_VOLSLIDEDOWN, slideParam & 0x0F); else if(totalSlide > 0) m.SetVolumeCommand(VOLCMD_VOLSLIDEUP, slideParam >> 4); - chnMem.autoVolSlide = 0; break; case 0x0E: // set filter (also uses opposite value compared to .mod) m.SetEffectCommand(CMD_MODCMDEX, 1 ^ (m.param ? 1 : 0)); @@ -535,25 +508,16 @@ m.SetEffectCommand(CMD_SPEED, m.param >> 4); break; case 0x10: // auto vibrato - chnMem.autoVibrato = m.param; - chnMem.vibratoMem = 0; + m.command = CMD_VIBRATO; break; case 0x11: // auto tremolo - if(m.param & 0xF) - chnMem.autoTremolo = m.param; - else - chnMem.autoTremolo = 0; + m.command = CMD_TREMOLO; break; case 0x12: // pattern break m.command = CMD_PATTERNBREAK; break; case 0x13: // auto tone portamento - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = m.param; - - chnMem.tonePortaMem = 0; + m.command = CMD_TONEPORTAMENTO; break; case 0x14: // position jump m.command = CMD_POSITIONJUMP; @@ -561,13 +525,11 @@ case 0x16: // start loop sequence if(m.instr && m.instr <= loopInfo.size()) { - STPLoopList &loopList = loopInfo[m.instr - 1]; - + const STPLoopList &loopList = loopInfo[m.instr - 1]; m.param--; if(m.param < std::min(std::size(ModSample().cues), loopList.size())) { - m.volcmd = VOLCMD_OFFSET; - m.vol = m.param; + m.SetVolumeCommand(VOLCMD_OFFSET, m.param); } } break; @@ -575,7 +537,6 @@ if(m.instr && m.instr <= loopInfo.size()) { STPLoopList &loopList = loopInfo[m.instr - 1]; - m.param--; if(m.param < loopList.size()) { @@ -588,13 +549,11 @@ case 0x18: // play sequence without loop if(m.instr && m.instr <= loopInfo.size()) { - STPLoopList &loopList = loopInfo[m.instr - 1]; - + const STPLoopList &loopList = loopInfo[m.instr - 1]; m.param--; if(m.param < std::min(std::size(ModSample().cues), loopList.size())) { - m.volcmd = VOLCMD_OFFSET; - m.vol = m.param; + m.SetVolumeCommand(VOLCMD_OFFSET, m.param); } // switch to non-looped version of sample and create it if needed if(!nonLooped[m.instr - 1] && CanAddMoreSamples()) @@ -606,7 +565,6 @@ if(m.instr && m.instr <= loopInfo.size()) { STPLoopList &loopList = loopInfo[m.instr - 1]; - m.param--; if(m.param < loopList.size()) { @@ -617,7 +575,7 @@ } break; case 0x1D: // fine volume slide (nibble order also swapped) - if(totalSlide < 0) // slide down + if(totalSlide < 0) // slide down m.SetEffectCommand(CMD_MODCMDEX, 0xB0 | (slideParam & 0x0F)); else if(totalSlide > 0) m.SetEffectCommand(CMD_MODCMDEX, 0xA0 | (slideParam >> 4)); @@ -626,12 +584,9 @@ // just behave like either a normal fade or a notecut // depending on the speed if(m.param & 0xF0) - { - chnMem.autoVolSlide = m.param >> 4; - } else - { - m.SetEffectCommand(CMD_MODCMDEX, 0xC0 | (m.param & 0xF)); - } + m.SetEffectCommand(CMD_AUTO_VOLUMESLIDE, m.param >> 4); + else + m.SetEffectCommand(CMD_MODCMDEX, 0xC0 | (m.param & 0x0F)); break; case 0x21: // note delay m.SetEffectCommand(CMD_MODCMDEX, 0xD0 | std::min(m.param, ModCommand::PARAM(15))); @@ -660,90 +615,16 @@ break; } - bool didVolSlide = false; - - // try to put volume slide in volume command - if(chnMem.autoVolSlide && m.volcmd == VOLCMD_NONE) - { - if(chnMem.autoVolSlide & 0xF0) - { - m.volcmd = VOLCMD_FINEVOLUP; - m.vol = chnMem.autoVolSlide >> 4; - } else - { - m.volcmd = VOLCMD_FINEVOLDOWN; - m.vol = chnMem.autoVolSlide & 0xF; - } - didVolSlide = true; - } - // try to place/combine all remaining running effects. - if(m.command == CMD_NONE) + if(shouldDelay && m.command == CMD_NONE) { - if(chnMem.autoPortaUp) - { - m.command = CMD_PORTAMENTOUP; - m.param = chnMem.autoPortaUp; - - } else if(chnMem.autoPortaDown) - { - m.command = CMD_PORTAMENTODOWN; - m.param = chnMem.autoPortaDown; - } else if(chnMem.autoFinePorta) - { - m.command = CMD_MODCMDEX; - m.param = chnMem.autoFinePorta; - - } else if(chnMem.autoTonePorta) - { - m.command = CMD_TONEPORTAMENTO; - m.param = chnMem.tonePortaMem = chnMem.autoTonePorta; - - } else if(chnMem.autoVibrato) - { - m.command = CMD_VIBRATO; - m.param = chnMem.vibratoMem = chnMem.autoVibrato; - - } else if(!didVolSlide && chnMem.autoVolSlide) - { - m.command = CMD_VOLUMESLIDE; - m.param = chnMem.autoVolSlide; - // convert to a "fine" value by setting the other nibble to 0xF - if(m.param & 0x0F) - m.param |= 0xF0; - else if(m.param & 0xF0) - m.param |= 0x0F; - didVolSlide = true; - MPT_UNUSED(didVolSlide); - - } else if(chnMem.autoTremolo) - { - m.command = CMD_TREMOLO; - m.param = chnMem.autoTremolo; - - } else if(shouldDelay) - { - // insert a fine pattern delay here - m.command = CMD_S3MCMDEX; - m.param = 0x61; - shouldDelay = false; - - } else if(!didGlobalVolSlide && globalVolSlide) - { - m.command = CMD_GLOBALVOLSLIDE; - m.param = globalVolSlide; - // convert to a "fine" value by setting the other nibble to 0xF - if(m.param & 0x0F) - m.param |= 0xF0; - else if(m.param & 0xF0) - m.param |= 0x0F; - - didGlobalVolSlide = true; - } + // insert a fine pattern delay here + m.SetEffectCommand(CMD_S3MCMDEX, 0x61); + shouldDelay = false; } } - // TODO: create/use extra channels for global volslide/delay if needed + // TODO: create/use extra channels for delay if needed? } } @@ -797,13 +678,9 @@ { // make duplicate samples for this individual section if needed if(info.looped) - { ConvertLoopSlice(Samples[smp], Samples[info.looped], info.loopStart, info.loopLength, true); - } if(info.nonLooped) - { ConvertLoopSlice(Samples[smp], Samples[info.nonLooped], info.loopStart, info.loopLength, false); - } } } } Modified: trunk/OpenMPT/soundlib/Load_ult.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_ult.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_ult.cpp Sun May 26 00:27:30 2024 (r20850) @@ -197,7 +197,7 @@ } -static int ReadULTEvent(ModCommand &m, FileReader &file, uint8 version) +static uint8 ReadULTEvent(ModCommand &m, FileReader &file, uint8 version) { uint8 repeat = 1; uint8 b = file.ReadUint8(); @@ -263,75 +263,6 @@ } -// Functor for postfixing ULT patterns (this is easier than just remembering everything WHILE we're reading the pattern events) -struct PostFixUltCommands -{ - PostFixUltCommands(CHANNELINDEX channels) : numChannels{channels} - { - isPortaActive.resize(channels, false); - } - - void operator()(ModCommand &m) - { - // Attempt to fix portamentos. - // UltraTracker will slide until the destination note is reached or 300 is encountered. - - // Stop porta? - if(m.command == CMD_TONEPORTAMENTO && m.param == 0) - { - isPortaActive[curChannel] = false; - m.command = CMD_NONE; - } - if(m.volcmd == VOLCMD_TONEPORTAMENTO && m.vol == 0) - { - isPortaActive[curChannel] = false; - m.volcmd = VOLCMD_NONE; - } - - // Apply porta? - if(m.note == NOTE_NONE && isPortaActive[curChannel]) - { - if(m.command == CMD_NONE && m.volcmd != VOLCMD_TONEPORTAMENTO) - { - m.command = CMD_TONEPORTAMENTO; - m.param = 0; - } else if(m.volcmd == VOLCMD_NONE && m.command != CMD_TONEPORTAMENTO) - { - m.volcmd = VOLCMD_TONEPORTAMENTO; - m.vol = 0; - } - } else // new note -> stop porta (or initialize again) - { - isPortaActive[curChannel] = (m.command == CMD_TONEPORTAMENTO || m.volcmd == VOLCMD_TONEPORTAMENTO); - } - - // attempt to fix F00 (reset to tempo 125, speed 6) - if(writeT125 && m.command == CMD_NONE) - { - m.command = CMD_TEMPO; - m.param = 125; - } - if(m.command == CMD_SPEED && m.param == 0) - { - m.param = 6; - writeT125 = true; - } - if(m.command == CMD_TEMPO) // don't try to fix this anymore if the tempo has already changed. - { - writeT125 = false; - } - curChannel++; - if(curChannel >= numChannels) - curChannel = 0; - } - - std::vector<bool> isPortaActive; - const CHANNELINDEX numChannels; - CHANNELINDEX curChannel = 0; - bool writeT125 = false; -}; - - static bool ValidateHeader(const UltFileHeader &fileHeader) { if(fileHeader.version < '1' || fileHeader.version > '4' @@ -351,13 +282,9 @@ { UltFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) - { return ProbeWantMoreData; - } if(!ValidateHeader(fileHeader)) - { return ProbeFailure; - } return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader)); } @@ -368,21 +295,13 @@ UltFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) - { return false; - } if(!ValidateHeader(fileHeader)) - { return false; - } if(loadFlags == onlyVerifyHeader) - { return true; - } if(!file.CanRead(mpt::saturate_cast<FileReader::pos_type>(GetHeaderMinimumAdditionalSize(fileHeader)))) - { return false; - } InitializeGlobals(MOD_TYPE_ULT); m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); @@ -393,7 +312,7 @@ m_modFormat.madeWithTracker = U_("UltraTracker ") + versions[fileHeader.version - '1']; m_modFormat.charset = mpt::Charset::CP437; - m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT. + m_SongFlags = SONG_AUTO_TONEPORTA | SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT. // Read "messageLength" lines, each containing 32 characters. m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0); @@ -447,6 +366,7 @@ return false; } + bool postFixSpeedCommands = false; for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) { ModCommand evnote; @@ -461,6 +381,8 @@ repeat = 64 - row; if(repeat == 0) break; + if(evnote.command == CMD_SPEED && evnote.param == 0) + postFixSpeedCommands = true; while(repeat--) { *note = evnote; @@ -470,10 +392,24 @@ } } } - - // Post-fix some effects. - Patterns.ForEachModCommand(PostFixUltCommands(GetNumChannels())); - + if(postFixSpeedCommands) + { + for(CPattern &pat : Patterns) + { + for(ROWINDEX row = 0; row < pat.GetNumRows(); row++) + { + for(auto &m : pat.GetRow(row)) + { + if(m.command == CMD_SPEED && m.param == 0) + { + m.param = 6; + pat.WriteEffect(EffectWriter(CMD_TEMPO, 125).Row(row).RetryNextRow()); + } + } + } + } + } + if(loadFlags & loadSampleData) { for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++) Modified: trunk/OpenMPT/soundlib/Load_xmf.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Load_xmf.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Load_xmf.cpp Sun May 26 00:27:30 2024 (r20850) @@ -166,6 +166,7 @@ InitializeGlobals(MOD_TYPE_MOD); m_SongFlags.set(SONG_IMPORTED); m_SongFlags.reset(SONG_ISAMIGA); + m_SongFlags.set(SONG_AUTO_TONEPORTA, type < 4); m_nSamples = numSamples; m_nSamplePreAmp = (type == 3) ? 192 : 48; // Imperium Galactica files are really quiet, no other XMFs appear to use type 3 Modified: trunk/OpenMPT/soundlib/ModChannel.cpp ============================================================================== --- trunk/OpenMPT/soundlib/ModChannel.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/ModChannel.cpp Sun May 26 00:27:30 2024 (r20850) @@ -34,6 +34,7 @@ nFadeOutVol = 0; dwFlags.set(CHN_KEYOFF | CHN_NOTEFADE); dwOldFlags.reset(); + autoSlide.Reset(); nnaGeneration = 0; //IT compatibility 15. Retrigger if(sndFile.m_playBehaviour[kITRetrigger]) Modified: trunk/OpenMPT/soundlib/ModChannel.h ============================================================================== --- trunk/OpenMPT/soundlib/ModChannel.h Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/ModChannel.h Sun May 26 00:27:30 2024 (r20850) @@ -41,6 +41,23 @@ } }; + struct AutoSlideStatus + { + bool AnyActive() const noexcept { return m_set.any(); } + bool IsActive(AutoSlideCommand cmd) const noexcept { return m_set[static_cast<size_t>(cmd)]; } + void SetActive(AutoSlideCommand cmd, bool active = true) noexcept { m_set[static_cast<size_t>(cmd)] = active; } + void Reset() noexcept { m_set.reset(); } + + bool AnyPitchSlideActive() const noexcept + { + return IsActive(AutoSlideCommand::TonePortamento) + || IsActive(AutoSlideCommand::PortamentoUp) || IsActive(AutoSlideCommand::PortamentoDown) + || IsActive(AutoSlideCommand::FinePortamentoUp) || IsActive(AutoSlideCommand::FinePortamentoDown); + } + private: + std::bitset<static_cast<size_t>(AutoSlideCommand::NumCommands)> m_set; + }; + // Information used in the mixer (should be kept tight for better caching) SamplePosition position; // Current play position (fixed point) SamplePosition increment; // Sample speed relative to mixing frequency (fixed point) @@ -84,6 +101,7 @@ int32 nAutoVibDepth; uint32 nEFxOffset; // Offset memory for Invert Loop (EFx, .MOD only) ROWINDEX nPatternLoop; + AutoSlideStatus autoSlide; uint16 portamentoSlide; int16 nTranspose; int16 nFineTune; @@ -146,8 +164,6 @@ uint16 m_RowPlugParam; PLUGINDEX m_RowPlug; - void ClearRowCmd() { rowCommand = ModCommand(); } - // Get a reference to a specific envelope of this channel const EnvInfo &GetEnvelope(EnvelopeType envType) const { Modified: trunk/OpenMPT/soundlib/Snd_defs.h ============================================================================== --- trunk/OpenMPT/soundlib/Snd_defs.h Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Snd_defs.h Sun May 26 00:27:30 2024 (r20850) @@ -118,6 +118,23 @@ }; +enum class AutoSlideCommand +{ + TonePortamento, + PortamentoUp, + PortamentoDown, + FinePortamentoUp, + FinePortamentoDown, + FineVolumeSlideUp, + FineVolumeSlideDown, + VolumeSlideSTK, + GlobalVolumeSlide, + Vibrato, + Tremolo, + NumCommands +}; + + // Module channel / sample flags enum ChannelFlags : uint32 { @@ -262,18 +279,23 @@ // Module flags - contains both song configuration and playback state... Use SONG_FILE_FLAGS and SONG_PLAY_FLAGS distinguish between the two. enum SongFlags { - SONG_FASTPORTAS = 0x01, // Portamentos are executed on every tick - SONG_FASTVOLSLIDES = 0x02, // Old Scream Tracker 3.0 volume slides (executed on every tick) - SONG_ITOLDEFFECTS = 0x04, // Old Impulse Tracker effect implementations - SONG_ITCOMPATGXX = 0x08, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) - SONG_LINEARSLIDES = 0x10, // Linear slides vs. Amiga slides - SONG_EXFILTERRANGE = 0x20, // Cutoff Filter has double frequency range (up to ~10Khz) - SONG_AMIGALIMITS = 0x40, // Enforce amiga frequency limits - SONG_S3MOLDVIBRATO = 0x80, // ScreamTracker 2 vibrato in S3M files - SONG_PT_MODE = 0x100, // ProTracker 1/2 playback mode - SONG_ISAMIGA = 0x200, // Is an Amiga module and thus qualifies to be played using the Paula BLEP resampler - SONG_IMPORTED = 0x400, // Song type does not represent actual module format / was imported from a different format (OpenMPT) - SONG_PLAYALLSONGS = 0x800, // Play all subsongs consecutively (libopenmpt) + SONG_FASTPORTAS = 0x01, // Portamentos are executed on every tick + SONG_FASTVOLSLIDES = 0x02, // Old Scream Tracker 3.0 volume slides (executed on every tick) + SONG_ITOLDEFFECTS = 0x04, // Old Impulse Tracker effect implementations + SONG_ITCOMPATGXX = 0x08, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) + SONG_LINEARSLIDES = 0x10, // Linear slides vs. Amiga slides + SONG_EXFILTERRANGE = 0x20, // Cutoff Filter has double frequency range (up to ~10Khz) + SONG_AMIGALIMITS = 0x40, // Enforce amiga frequency limits + SONG_S3MOLDVIBRATO = 0x80, // ScreamTracker 2 vibrato in S3M files + SONG_PT_MODE = 0x100, // ProTracker 1/2 playback mode + SONG_ISAMIGA = 0x200, // Is an Amiga module and thus qualifies to be played using the Paula BLEP resampler + SONG_IMPORTED = 0x400, // Song type does not represent actual module format / was imported from a different format (OpenMPT) + SONG_PLAYALLSONGS = 0x800, // Play all subsongs consecutively (libopenmpt) + SONG_AUTO_TONEPORTA = 0x1000, // Tone portamento command is continued automatically + SONG_AUTO_GLOBALVOL = 0x2000, // Global volume slide command is continued automatically + SONG_AUTO_VIBRATO = 0x4000, // Vibrato command is continued automatically + SONG_AUTO_TREMOLO = 0x8000, // Tremolo command is continued automatically + SONG_AUTO_VOLSLIDE_STK = 0x1'0000, // Automatic volume slide command is interpreted like in STK files (rather than like in STP files) }; DECLARE_FLAGSET(SongFlags) Modified: trunk/OpenMPT/soundlib/Snd_fx.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Snd_fx.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Snd_fx.cpp Sun May 26 00:27:30 2024 (r20850) @@ -183,6 +183,17 @@ break; } + if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamento) && !chn.rowCommand.IsTonePortamento()) + sndFile.TonePortamento(*state, channel, chn.portamentoSlide); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + sndFile.PortamentoUp(*state, channel, chn.nOldPortaUp, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown)) + sndFile.PortamentoDown(*state, channel, chn.nOldPortaDown, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp)) + sndFile.FinePortamentoUp(chn, chn.nOldFinePortaUpDown); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown)) + sndFile.FinePortamentoDown(chn, chn.nOldFinePortaUpDown); + updateInc = true; } @@ -274,6 +285,43 @@ } chnSettings[channel].ticksToRender = 0; } + + void GlobalVolSlide(ModChannel &chn, ModCommand::PARAM param, uint32 nonRowTicks) + { + if(sndFile.m_SongFlags[SONG_AUTO_GLOBALVOL]) + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, param != 0); + if(param) + chn.nOldGlobalVolSlide = param; + else + param = chn.nOldGlobalVolSlide; + + if((param & 0x0F) == 0x0F && (param & 0xF0)) + { + param >>= 4; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume += param << 1; + } else if((param & 0xF0) == 0xF0 && (param & 0x0F)) + { + param = (param & 0x0F) << 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume -= param; + } else if(param & 0xF0) + { + param >>= 4; + param <<= 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume += param * nonRowTicks; + } else + { + param = (param & 0x0F) << 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; + state->m_nGlobalVolume -= param * nonRowTicks; + } + Limit(state->m_nGlobalVolume, 0, 256); + } }; @@ -326,7 +374,7 @@ for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, m++) { if(m->note == NOTE_NOTECUT || m->note == NOTE_KEYOFF || (m->note == NOTE_FADE && GetNumInstruments()) - || (m->IsNote() && m->instr && !m->IsPortamento())) + || (m->IsNote() && m->instr && !m->IsTonePortamento())) { memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL; } @@ -597,13 +645,13 @@ for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++) { ModChannel &chn = playState.Chn[nChn]; - if(chn.rowCommand.IsEmpty()) + if(chn.rowCommand.IsEmpty() && !chn.autoSlide.AnyActive()) continue; ModCommand::COMMAND command = chn.rowCommand.command; ModCommand::PARAM param = chn.rowCommand.param; ModCommand::NOTE note = chn.rowCommand.note; - if(adjustMode & eAdjust) + if((adjustMode & eAdjust) && !chn.rowCommand.IsEmpty()) { if(chn.rowCommand.instr) { @@ -627,6 +675,9 @@ if(instr->IsResonanceEnabled()) chn.nResonance = instr->GetResonance(); } + const bool wasGlobalSlideRunning = chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide); + chn.autoSlide.Reset(); + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, wasGlobalSlideRunning); } } @@ -754,6 +805,9 @@ // The following calculations are not interesting if we just want to get the song length. if(!(adjustMode & eAdjust)) continue; + + ResetAutoSlides(chn); + switch(command) { // Portamento Up/Down @@ -791,6 +845,9 @@ case CMD_TONEPORTAVOL: if (param) chn.nOldVolumeSlide = param; break; + case CMD_AUTO_VOLUMESLIDE: + AutoVolumeSlide(chn, param); + break; // Set Volume case CMD_VOLUME: memory.chnSettings[nChn].vol = param; @@ -809,40 +866,11 @@ { playState.m_nGlobalVolume = 256; } + playState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, false); break; // Global Volume Slide case CMD_GLOBALVOLSLIDE: - if(m_playBehaviour[kPerChannelGlobalVolSlide]) - { - // IT compatibility 16. Global volume slide params are stored per channel (FT2/IT) - if (param) chn.nOldGlobalVolSlide = param; else param = chn.nOldGlobalVolSlide; - } else - { - if (param) playState.Chn[0].nOldGlobalVolSlide = param; else param = playState.Chn[0].nOldGlobalVolSlide; - } - if (((param & 0x0F) == 0x0F) && (param & 0xF0)) - { - param >>= 4; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume += param << 1; - } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) - { - param = (param & 0x0F) << 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume -= param; - } else if (param & 0xF0) - { - param >>= 4; - param <<= 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume += param * nonRowTicks; - } else - { - param = (param & 0x0F) << 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume -= param * nonRowTicks; - } - Limit(playState.m_nGlobalVolume, 0, 256); + memory.GlobalVolSlide(playState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0], param, nonRowTicks); break; case CMD_CHANNELVOLUME: if (param <= 64) chn.nGlobalVol = param; @@ -930,6 +958,9 @@ break; case CMD_PANBRELLO: Panbrello(chn, param); + // Panbrello effect is permanent in compatible mode, so actually apply panbrello for the last tick of this row + chn.nPanbrelloPos += static_cast<uint8>(chn.nPanbrelloSpeed * nonRowTicks); + ProcessPanbrello(chn); break; case CMD_MIDI: @@ -965,39 +996,36 @@ break; } - // Process vibrato / tremolo / panbrello - switch(chn.rowCommand.command) + chn.isFirstTick = true; + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) && command != CMD_AUTO_VOLUMESLIDE) + FineVolumeUp(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown) && command != CMD_AUTO_VOLUMESLIDE) + FineVolumeDown(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) { - case CMD_VIBRATO: - case CMD_FINEVIBRATO: - case CMD_VIBRATOVOL: + for(uint32 i = 0; i < numTicks; i++) { - uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; - uint32 inc = chn.nVibratoSpeed * vibTicks; - if(m_playBehaviour[kITVibratoTremoloPanbrello]) - inc *= 4; - chn.nVibratoPos += static_cast<uint8>(inc); - } - break; - - case CMD_TREMOLO: - { - uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; - uint32 inc = chn.nTremoloSpeed * tremTicks; - if(m_playBehaviour[kITVibratoTremoloPanbrello]) - inc *= 4; - chn.nTremoloPos += static_cast<uint8>(inc); + chn.isFirstTick = (i == 0); + VolumeSlide(chn, 0); } - break; - - case CMD_PANBRELLO: - // Panbrello effect is permanent in compatible mode, so actually apply panbrello for the last tick of this row - chn.nPanbrelloPos += static_cast<uint8>(chn.nPanbrelloSpeed * nonRowTicks); - ProcessPanbrello(chn); - break; - - default: - break; + } + if(chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide) && command != CMD_GLOBALVOLSLIDE) + memory.GlobalVolSlide(chn, chn.nOldGlobalVolSlide, nonRowTicks); + if(command == CMD_VIBRATO || command == CMD_FINEVIBRATO || command == CMD_VIBRATOVOL || chn.autoSlide.IsActive(AutoSlideCommand::Vibrato)) + { + uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; + uint32 inc = chn.nVibratoSpeed * vibTicks; + if(m_playBehaviour[kITVibratoTremoloPanbrello]) + inc *= 4; + chn.nVibratoPos += static_cast<uint8>(inc); + } + if(command == CMD_TREMOLO || chn.autoSlide.IsActive(AutoSlideCommand::Tremolo)) + { + uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; + uint32 inc = chn.nTremoloSpeed * tremTicks; + if(m_playBehaviour[kITVibratoTremoloPanbrello]) + inc *= 4; + chn.nTremoloPos += static_cast<uint8>(inc); } if(m_playBehaviour[kST3EffectMemory] && command != CMD_NONE && param != 0) @@ -1040,7 +1068,7 @@ uint32 paramHi = m.param >> 4, paramLo = m.param & 0x0F; uint32 startTick = 0; - const bool porta = m.IsPortamento(); + const bool porta = m.IsTonePortamento(); bool stopNote = false; if(m.instr) chn.prevNoteOffset = 0; @@ -1206,6 +1234,25 @@ memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far chn.microTuning = CalculateFinetuneTarget(playState.m_nPattern, playState.m_nRow, nChn); // TODO should render each tick individually for CMD_FINETUNE_SMOOTH for higher sync accuracy break; + + // Auto portamentos + case CMD_AUTO_PORTAUP: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, m.param != 0); + chn.nOldPortaUp = m.param; + break; + case CMD_AUTO_PORTADOWN: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, m.param != 0); + chn.nOldPortaDown = m.param; + break; + case CMD_AUTO_PORTAUP_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, m.param != 0); + chn.nOldFinePortaUpDown = m.param; + break; + case CMD_AUTO_PORTADOWN_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, m.param != 0); + chn.nOldFinePortaUpDown = m.param; + break; + default: break; } @@ -1245,7 +1292,8 @@ if(chn.isPaused) continue; - if(m.IsAnyPitchSlide()) + + if(m.IsAnyPitchSlide() || chn.autoSlide.AnyPitchSlideActive()) { // Portamento needs immediate syncing, as the pitch changes on each tick uint32 portaTick = memory.chnSettings[nChn].ticksToRender + startTick; @@ -2107,6 +2155,9 @@ { chn.paulaState.Reset(); } + const bool wasGlobalSlideRunning = chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide); + chn.autoSlide.Reset(); + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, wasGlobalSlideRunning); } @@ -2482,7 +2533,7 @@ uint32 vol = chn.rowCommand.vol; ModCommand::COMMAND cmd = chn.rowCommand.command; uint32 param = chn.rowCommand.param; - bool bPorta = chn.rowCommand.IsPortamento(); + bool bPorta = chn.rowCommand.IsTonePortamento(); uint32 nStartTick = 0; chn.isFirstTick = m_PlayState.m_flags[SONG_FIRSTTICK]; @@ -3038,6 +3089,8 @@ if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) // not even effects are processed on muted S3M channels continue; + ResetAutoSlides(chn); + // Volume Column Effect (except volume & panning) /* A few notes, paraphrased from ITTECH.TXT by Storlek (creator of schismtracker): Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value @@ -3221,6 +3274,24 @@ PortamentoDown(nChn, static_cast<ModCommand::PARAM>(param), false); break; + // Auto portamentos + case CMD_AUTO_PORTAUP: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, param != 0); + chn.nOldPortaUp = static_cast<uint8>(param); + break; + case CMD_AUTO_PORTADOWN: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, param != 0); + chn.nOldPortaDown = static_cast<uint8>(param); + break; + case CMD_AUTO_PORTAUP_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, param != 0); + chn.nOldFinePortaUpDown = static_cast<uint8>(param); + break; + case CMD_AUTO_PORTADOWN_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, param != 0); + chn.nOldFinePortaUpDown = static_cast<uint8>(param); + break; + // Volume Slide case CMD_VOLUMESLIDE: if (param || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast<ModCommand::PARAM>(param)); @@ -3393,15 +3464,13 @@ { m_PlayState.m_nGlobalVolume = 256; } + m_PlayState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, false); break; // Global Volume Slide case CMD_GLOBALVOLSLIDE: //IT compatibility 16. Saving last global volume slide param per channel (FT2/IT) - if(m_playBehaviour[kPerChannelGlobalVolSlide]) - GlobalVolSlide(static_cast<ModCommand::PARAM>(param), chn.nOldGlobalVolSlide); - else - GlobalVolSlide(static_cast<ModCommand::PARAM>(param), m_PlayState.Chn[0].nOldGlobalVolSlide); + GlobalVolSlide(m_PlayState, static_cast<ModCommand::PARAM>(param), m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0); break; // Set 8-bit Panning @@ -3610,6 +3679,10 @@ DigiBoosterSampleReverse(chn, static_cast<ModCommand::PARAM>(param)); break; + case CMD_AUTO_VOLUMESLIDE: + AutoVolumeSlide(chn, static_cast<ModCommand::PARAM>(param)); + break; + default: break; } @@ -3625,6 +3698,7 @@ chn.nOldIns = chn.rowCommand.instr; } + ProcessAutoSlides(m_PlayState, nChn); } // for(...) end // Navigation Effects @@ -3710,6 +3784,70 @@ // Channels effects +void CSoundFile::ResetAutoSlides(ModChannel &chn) const +{ + const auto cmd = chn.rowCommand.command; + const auto volcmd = chn.rowCommand.volcmd; + if(cmd != CMD_NONE && GetType() == MOD_TYPE_669) + { + chn.autoSlide.Reset(); + return; + } + + if(cmd == CMD_NONE && chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) + chn.autoSlide.SetActive(AutoSlideCommand::VolumeSlideSTK, false); + + if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown) || chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown) + || chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp) || chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + { + if(!chn.rowCommand.IsTonePortamento() && chn.rowCommand.IsAnyPitchSlide()) + { + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, false); + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, false); + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, false); + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, false); + } + } + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) || chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown)) + { + if(cmd == CMD_VOLUME || cmd == CMD_AUTO_VOLUMESLIDE || chn.rowCommand.IsNormalVolumeSlide() + || volcmd == VOLCMD_VOLUME || volcmd == VOLCMD_VOLSLIDEUP || volcmd == VOLCMD_VOLSLIDEDOWN || volcmd == VOLCMD_FINEVOLUP || volcmd == VOLCMD_FINEVOLDOWN) + { + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideUp, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideDown, false); + } + } +} + + +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); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + PortamentoUp(channel, chn.nOldPortaUp, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown)) + PortamentoDown(channel, chn.nOldPortaDown, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp)) + FinePortamentoUp(chn, chn.nOldFinePortaUpDown); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown)) + FinePortamentoDown(chn, chn.nOldFinePortaUpDown); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) && chn.rowCommand.command != CMD_AUTO_VOLUMESLIDE) + FineVolumeUp(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown) && chn.rowCommand.command != CMD_AUTO_VOLUMESLIDE) + FineVolumeDown(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) + VolumeSlide(chn, 0); + if(chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide) && chn.rowCommand.command != CMD_GLOBALVOLSLIDE) + GlobalVolSlide(playState, chn.nOldGlobalVolSlide, channel); + if(chn.autoSlide.IsActive(AutoSlideCommand::Vibrato)) + chn.dwFlags.set(CHN_VIBRATO); + if(chn.autoSlide.IsActive(AutoSlideCommand::Tremolo)) + chn.dwFlags.set(CHN_TREMOLO); +} + + // Update the effect memory of all S3M effects that use the last non-zero effect parameter as memory (Dxy, Exx, Fxx, Ixy, Jxy, Kxy, Lxy, Qxy, Rxy, Sxy) // Test case: ParamMemory.s3m void CSoundFile::UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const @@ -4227,6 +4365,8 @@ { ModChannel &chn = playState.Chn[nChn]; chn.dwFlags.set(CHN_PORTAMENTO); + if(m_SongFlags[SONG_AUTO_TONEPORTA]) + chn.autoSlide.SetActive(AutoSlideCommand::TonePortamento, param != 0); //IT compatibility 03: Share effect memory with portamento up/down if((!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM) @@ -4331,7 +4471,10 @@ { if (param & 0x0F) chn.nVibratoDepth = (param & 0x0F) * 4; if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_VIBRATO); + if(m_SongFlags[SONG_AUTO_VIBRATO]) + chn.autoSlide.SetActive(AutoSlideCommand::Vibrato, param != 0); + else + chn.dwFlags.set(CHN_VIBRATO); } @@ -4339,7 +4482,10 @@ { if (param & 0x0F) chn.nVibratoDepth = param & 0x0F; if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_VIBRATO); + if(m_SongFlags[SONG_AUTO_VIBRATO]) + chn.autoSlide.SetActive(AutoSlideCommand::Vibrato, param != 0); + else + chn.dwFlags.set(CHN_VIBRATO); // ST3 compatibility: Do not distinguish between vibrato types in effect memory // Test case: VibratoTypeChange.s3m if(m_playBehaviour[kST3VibratoMemory] && (param & 0x0F)) @@ -4408,6 +4554,27 @@ } +void CSoundFile::AutoVolumeSlide(ModChannel &chn, ModCommand::PARAM param) const +{ + if(m_SongFlags[SONG_AUTO_VOLSLIDE_STK]) + { + chn.nOldVolumeSlide = param; + chn.autoSlide.SetActive(AutoSlideCommand::VolumeSlideSTK); + } else + { + if(param & 0x0F) + { + FineVolumeDown(chn, param, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideDown); + } else + { + FineVolumeUp(chn, param, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideUp); + } + } +} + + void CSoundFile::VolumeSlide(ModChannel &chn, ModCommand::PARAM param) const { if (param) @@ -4610,7 +4777,10 @@ { if (param & 0x0F) chn.nTremoloDepth = (param & 0x0F) << 2; if (param & 0xF0) chn.nTremoloSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_TREMOLO); + if(m_SongFlags[SONG_AUTO_TREMOLO]) + chn.autoSlide.SetActive(AutoSlideCommand::Tremolo, (param & 0x0F) != 0); + else + chn.dwFlags.set(CHN_TREMOLO); } @@ -6129,10 +6299,15 @@ } -void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide) +void CSoundFile::GlobalVolSlide(PlayState &playState, ModCommand::PARAM param, CHANNELINDEX chn) const { - int32 nGlbSlide = 0; - if (param) nOldGlobalVolSlide = param; else param = nOldGlobalVolSlide; + if(m_SongFlags[SONG_AUTO_GLOBALVOL]) + playState.Chn[chn].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, param != 0); + + if(param) + playState.Chn[chn].nOldGlobalVolSlide = param; + else + param = playState.Chn[chn].nOldGlobalVolSlide; if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))) { @@ -6146,16 +6321,17 @@ } } + int32 nGlbSlide = 0; if (((param & 0x0F) == 0x0F) && (param & 0xF0)) { - if(m_PlayState.m_flags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2; + if(playState.m_flags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2; } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) { - if(m_PlayState.m_flags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2); + if(playState.m_flags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2); } else { - if(!m_PlayState.m_flags[SONG_FIRSTTICK]) + if(!playState.m_flags[SONG_FIRSTTICK]) { if (param & 0xF0) { @@ -6171,9 +6347,9 @@ if (nGlbSlide) { if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM))) nGlbSlide *= 2; - nGlbSlide += m_PlayState.m_nGlobalVolume; + nGlbSlide += playState.m_nGlobalVolume; Limit(nGlbSlide, 0, 256); - m_PlayState.m_nGlobalVolume = nGlbSlide; + playState.m_nGlobalVolume = nGlbSlide; } } Modified: trunk/OpenMPT/soundlib/Sndfile.h ============================================================================== --- trunk/OpenMPT/soundlib/Sndfile.h Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Sndfile.h Sun May 26 00:27:30 2024 (r20850) @@ -1118,6 +1118,8 @@ Pan8bit = 8, }; // Channel Effects + void ResetAutoSlides(ModChannel &chn) const; + void ProcessAutoSlides(PlayState &playState, CHANNELINDEX channel); void UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const; void PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular); void PortamentoUp(PlayState &playState, CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular) const; @@ -1139,6 +1141,7 @@ int32 TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param) const; void Vibrato(ModChannel &chn, uint32 param) const; void FineVibrato(ModChannel &chn, uint32 param) const; + void AutoVolumeSlide(ModChannel& chn, ModCommand::PARAM param) const; void VolumeSlide(ModChannel &chn, ModCommand::PARAM param) const; void PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool memory = true) const; void ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const; @@ -1162,7 +1165,7 @@ void InvertLoop(ModChannel &chn); void PositionJump(PlayState &state, CHANNELINDEX chn) const; ROWINDEX PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 param) const; - void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide); + void GlobalVolSlide(PlayState &playState, ModCommand::PARAM param, CHANNELINDEX chn) const; void ProcessMacroOnChannel(CHANNELINDEX nChn); void ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param = 0, PLUGINDEX plugin = 0); Modified: trunk/OpenMPT/soundlib/Sndmix.cpp ============================================================================== --- trunk/OpenMPT/soundlib/Sndmix.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/Sndmix.cpp Sun May 26 00:27:30 2024 (r20850) @@ -695,7 +695,7 @@ && pChn->dwFlags[CHN_ADLIB] && pChn->nPortamentoDest && pChn->rowCommand.IsNote() - && pChn->rowCommand.IsPortamento()) + && pChn->rowCommand.IsTonePortamento()) { // ST3: Adlib Note + Tone Portamento does not execute the slide, but changes to the target note instantly on the next row (unless there is another note with tone portamento) // Test case: TonePortamentoWithAdlibNote.s3m @@ -1473,8 +1473,8 @@ // - If there's no arpeggio // - but an arpeggio note is still active and // - there's no note stop or new note that would stop it anyway - if((arpOnRow && chn.nArpeggioLastNote != arpNote && (!chn.isFirstTick || !chn.rowCommand.IsNote() || chn.rowCommand.IsPortamento())) - || (!arpOnRow && (chn.rowCommand.note == NOTE_NONE || chn.rowCommand.IsPortamento()) && chn.nArpeggioLastNote != NOTE_NONE)) + if((arpOnRow && chn.nArpeggioLastNote != arpNote && (!chn.isFirstTick || !chn.rowCommand.IsNote() || chn.rowCommand.IsTonePortamento())) + || (!arpOnRow && (chn.rowCommand.note == NOTE_NONE || chn.rowCommand.IsTonePortamento()) && chn.nArpeggioLastNote != NOTE_NONE)) SendMIDINote(nChn, arpNote | IMixPlugin::MIDI_NOTE_ARPEGGIO, static_cast<uint16>(chn.nVolume)); // Stop note: // - If some arpeggio note is still registered or @@ -2265,7 +2265,7 @@ // When glissando mode is set to semitones, clamp to the next halftone. if((chn.dwFlags & (CHN_GLISSANDO | CHN_PORTAMENTO)) == (CHN_GLISSANDO | CHN_PORTAMENTO) - && (!m_SongFlags[SONG_PT_MODE] || (chn.rowCommand.IsPortamento() && !m_PlayState.m_flags[SONG_FIRSTTICK]))) + && (!m_SongFlags[SONG_PT_MODE] || (chn.rowCommand.IsTonePortamento() && !m_PlayState.m_flags[SONG_FIRSTTICK]))) { if(period != chn.cachedPeriod) { @@ -2689,7 +2689,7 @@ realNote = pIns->NoteMap[note - NOTE_MIN]; // Experimental VST panning //ProcessMIDIMacro(nChn, false, m_MidiCfg.Global[MIDIOUT_PAN], 0, nPlugin); - if(m_playBehaviour[kPluginIgnoreTonePortamento] || !chn.rowCommand.IsPortamento()) + if(m_playBehaviour[kPluginIgnoreTonePortamento] || !chn.rowCommand.IsTonePortamento()) SendMIDINote(nChn, realNote, static_cast<uint16>(velocity)); } Modified: trunk/OpenMPT/soundlib/mod_specifications.cpp ============================================================================== --- trunk/OpenMPT/soundlib/mod_specifications.cpp Sat May 25 23:29:11 2024 (r20849) +++ trunk/OpenMPT/soundlib/mod_specifications.cpp Sun May 26 00:27:30 2024 (r20850) @@ -67,13 +67,10 @@ true, // Has artist name true, // Has default resampling true, // Fixed point tempo - " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*????????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*?????????????????", // Supported Effects " vpcdabuh??gfe?o", // Supported Volume Column commands }; - - - constexpr CModSpecifications mod_ = { MOD_TYPE_MOD, // Internal MODTYPE value @@ -117,11 +114,10 @@ false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCD?FF?E?????????????????????????????", // Supported Effects + " 0123456789ABCD?FF?E??????????????????????????????????", // Supported Effects " ???????????????", // Supported Volume Column commands }; - constexpr CModSpecifications xm_ = { MOD_TYPE_XM, // Internal MODTYPE value @@ -165,7 +161,7 @@ false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCDRFFTE???GHK??XPL??????W???????????", // Supported Effects + " 0123456789ABCDRFFTE???GHK??XPL??????W????????????????", // Supported Effects " vpcdabuhlrg????", // Supported Volume Column commands }; @@ -213,7 +209,7 @@ true, // Has artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCDRFFTE???GHK?YXPLZ\\?#??W???????????", // Supported Effects + " 0123... [truncated message content] |