/* * EffectInfo.cpp * -------------- * Purpose: Provide information about effect names, parameter interpretation to the tracker interface. * 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 "EffectInfo.h" #include "Mptrack.h" // for szHexChar #include "../soundlib/Sndfile.h" #include "../soundlib/mod_specifications.h" #include "../soundlib/Tables.h" OPENMPT_NAMESPACE_BEGIN /////////////////////////////////////////////////////////////////////////// // Effects description struct MPTEffectInfo { EffectCommand effect; // CMD_XXXX ModCommand::PARAM paramMask; // 0 = default ModCommand::PARAM paramValue; // 0 = default ModCommand::PARAM paramLimit; // Parameter Editor limit FlagSet supportedFormats; // MOD_TYPE_XXX combo const TCHAR *name; // e.g. "Tone Portamento" }; static constexpr FlagSet MOD_TYPE_MODXM = MOD_TYPE_MOD | MOD_TYPE_XM; static constexpr FlagSet MOD_TYPE_S3MIT = MOD_TYPE_S3M | MOD_TYPE_IT; static constexpr FlagSet MOD_TYPE_S3MITMPT = MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT; static constexpr FlagSet MOD_TYPE_NOMOD = MOD_TYPE_S3M | MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT; static constexpr FlagSet MOD_TYPE_XMIT = MOD_TYPE_XM | MOD_TYPE_IT; static constexpr FlagSet MOD_TYPE_XMITMPT = MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT; static constexpr FlagSet MOD_TYPE_ITMPT = MOD_TYPE_IT | MOD_TYPE_MPT; static constexpr FlagSet MOD_TYPE_ALL = MODTYPE(~0); static constexpr MPTEffectInfo gFXInfo[] = { {CMD_ARPEGGIO, 0,0, 0, MOD_TYPE_ALL, _T("Arpeggio")}, {CMD_PORTAMENTOUP, 0,0, 0, MOD_TYPE_ALL, _T("Portamento Up")}, {CMD_PORTAMENTODOWN,0,0, 0, MOD_TYPE_ALL, _T("Portamento Down")}, {CMD_TONEPORTAMENTO,0,0, 0, MOD_TYPE_ALL, _T("Tone Portamento")}, {CMD_VIBRATO, 0,0, 0, MOD_TYPE_ALL, _T("Vibrato")}, {CMD_TONEPORTAVOL, 0,0, 0, MOD_TYPE_ALL, _T("Volslide+Toneporta")}, {CMD_VIBRATOVOL, 0,0, 0, MOD_TYPE_ALL, _T("VolSlide+Vibrato")}, {CMD_TREMOLO, 0,0, 0, MOD_TYPE_ALL, _T("Tremolo")}, {CMD_PANNING8, 0,0, 0, MOD_TYPE_ALL, _T("Set Panning")}, {CMD_OFFSET, 0,0, 0, MOD_TYPE_ALL, _T("Set Offset")}, {CMD_VOLUMESLIDE, 0,0, 0, MOD_TYPE_ALL, _T("Volume Slide")}, {CMD_POSITIONJUMP, 0,0, 0, MOD_TYPE_ALL, _T("Position Jump")}, {CMD_VOLUME, 0,0, 0, MOD_TYPE_MODXM, _T("Set Volume")}, {CMD_PATTERNBREAK, 0,0, 0, MOD_TYPE_ALL, _T("Pattern Break")}, {CMD_RETRIG, 0,0, 0, MOD_TYPE_NOMOD, _T("Retrigger Note")}, {CMD_SPEED, 0,0, 0, MOD_TYPE_ALL, _T("Set Speed")}, {CMD_TEMPO, 0,0, 0, MOD_TYPE_ALL, _T("Set Tempo")}, {CMD_TREMOR, 0,0, 0, MOD_TYPE_NOMOD, _T("Tremor")}, {CMD_CHANNELVOLUME, 0,0, 0, MOD_TYPE_S3MITMPT, _T("Set Channel Volume")}, {CMD_CHANNELVOLSLIDE,0,0, 0, MOD_TYPE_S3MITMPT, _T("Channel Volume Slide")}, {CMD_GLOBALVOLUME, 0,0, 0, MOD_TYPE_NOMOD, _T("Set Global Volume")}, {CMD_GLOBALVOLSLIDE,0,0, 0, MOD_TYPE_NOMOD, _T("Global Volume Slide")}, {CMD_KEYOFF, 0,0, 0, MOD_TYPE_XM, _T("Key Off")}, {CMD_FINEVIBRATO, 0,0, 0, MOD_TYPE_S3MITMPT, _T("Fine Vibrato")}, {CMD_PANBRELLO, 0,0, 0, MOD_TYPE_NOMOD, _T("Panbrello")}, {CMD_PANNINGSLIDE, 0,0, 0, MOD_TYPE_NOMOD, _T("Panning Slide")}, {CMD_SETENVPOSITION,0,0, 0, MOD_TYPE_XM, _T("Envelope position")}, {CMD_MIDI, 0,0, 0x7F, MOD_TYPE_NOMOD, _T("MIDI Macro")}, {CMD_SMOOTHMIDI, 0,0, 0x7F, MOD_TYPE_XMITMPT, _T("Smooth MIDI Macro")}, // Extended MOD/XM effects {CMD_MODCMDEX, 0xF0,0x00, 0, MOD_TYPE_MOD, _T("Set Filter")}, {CMD_MODCMDEX, 0xF0,0x10, 0, MOD_TYPE_MODXM, _T("Fine Porta Up")}, {CMD_MODCMDEX, 0xF0,0x20, 0, MOD_TYPE_MODXM, _T("Fine Porta Down")}, {CMD_MODCMDEX, 0xF0,0x30, 0, MOD_TYPE_MODXM, _T("Glissando Control")}, {CMD_MODCMDEX, 0xF0,0x40, 0, MOD_TYPE_MODXM, _T("Vibrato Waveform")}, {CMD_MODCMDEX, 0xF0,0x50, 0, MOD_TYPE_MODXM, _T("Set Finetune")}, {CMD_MODCMDEX, 0xF0,0x60, 0, MOD_TYPE_MODXM, _T("Pattern Loop")}, {CMD_MODCMDEX, 0xF0,0x70, 0, MOD_TYPE_MODXM, _T("Tremolo Waveform")}, {CMD_MODCMDEX, 0xF0,0x80, 0, MOD_TYPE_MODXM, _T("Set Panning")}, {CMD_MODCMDEX, 0xF0,0x90, 0, MOD_TYPE_MODXM, _T("Retrigger Note")}, {CMD_MODCMDEX, 0xF0,0xA0, 0, MOD_TYPE_MODXM, _T("Fine Volslide Up")}, {CMD_MODCMDEX, 0xF0,0xB0, 0, MOD_TYPE_MODXM, _T("Fine Volslide Down")}, {CMD_MODCMDEX, 0xF0,0xC0, 0, MOD_TYPE_MODXM, _T("Note Cut")}, {CMD_MODCMDEX, 0xF0,0xD0, 0, MOD_TYPE_MODXM, _T("Note Delay")}, {CMD_MODCMDEX, 0xF0,0xE0, 0, MOD_TYPE_MODXM, _T("Pattern Delay")}, {CMD_MODCMDEX, 0xF0,0xF0, 0, MOD_TYPE_XM, _T("Set Active Macro")}, {CMD_MODCMDEX, 0xF0,0xF0, 0, MOD_TYPE_MOD, _T("Invert Loop")}, // Extended S3M/IT effects {CMD_S3MCMDEX, 0xF0,0x10, 0, MOD_TYPE_S3MITMPT, _T("Glissando Control")}, {CMD_S3MCMDEX, 0xF0,0x20, 0, MOD_TYPE_S3M, _T("Set Finetune")}, {CMD_S3MCMDEX, 0xF0,0x30, 0, MOD_TYPE_S3MITMPT, _T("Vibrato Waveform")}, {CMD_S3MCMDEX, 0xF0,0x40, 0, MOD_TYPE_S3MITMPT, _T("Tremolo Waveform")}, {CMD_S3MCMDEX, 0xF0,0x50, 0, MOD_TYPE_S3MITMPT, _T("Panbrello Waveform")}, {CMD_S3MCMDEX, 0xF0,0x60, 0, MOD_TYPE_S3MITMPT, _T("Fine Pattern Delay")}, {CMD_S3MCMDEX, 0xF0,0x80, 0, MOD_TYPE_S3MITMPT, _T("Set Panning")}, {CMD_S3MCMDEX, 0xF0,0xA0, 0, MOD_TYPE_ITMPT, _T("Set High Offset")}, {CMD_S3MCMDEX, 0xF0,0xB0, 0, MOD_TYPE_S3MITMPT, _T("Pattern Loop")}, {CMD_S3MCMDEX, 0xF0,0xC0, 0, MOD_TYPE_S3MITMPT, _T("Note Cut")}, {CMD_S3MCMDEX, 0xF0,0xD0, 0, MOD_TYPE_S3MITMPT, _T("Note Delay")}, {CMD_S3MCMDEX, 0xF0,0xE0, 0, MOD_TYPE_S3MITMPT, _T("Pattern Delay")}, {CMD_S3MCMDEX, 0xF0,0xF0, 0, MOD_TYPE_ITMPT, _T("Set Active Macro")}, // MPT XM extensions and special effects {CMD_XFINEPORTAUPDOWN,0xF0,0x10,0, MOD_TYPE_XM, _T("Extra Fine Porta Up")}, {CMD_XFINEPORTAUPDOWN,0xF0,0x20,0, MOD_TYPE_XM, _T("Extra Fine Porta Down")}, {CMD_XFINEPORTAUPDOWN,0xF0,0x50,0, MOD_TYPE_XM, _T("Panbrello Waveform")}, {CMD_XFINEPORTAUPDOWN,0xF0,0x60,0, MOD_TYPE_XM, _T("Fine Pattern Delay")}, {CMD_XFINEPORTAUPDOWN,0xF0,0x90,0, MOD_TYPE_XM, _T("Sound Control")}, {CMD_XFINEPORTAUPDOWN,0xF0,0xA0,0, MOD_TYPE_XM, _T("Set High Offset")}, // MPT IT extensions and special effects {CMD_S3MCMDEX, 0xF0,0x90, 0, MOD_TYPE_S3MITMPT, _T("Sound Control")}, {CMD_S3MCMDEX, 0xF0,0x70, 0, MOD_TYPE_ITMPT, _T("Instr. Control")}, {CMD_DELAYCUT, 0x00,0x00, 0, MOD_TYPE_MPT, _T("Note Delay and Cut")}, {CMD_XPARAM, 0,0, 0, MOD_TYPE_XMITMPT, _T("Parameter Extension")}, {CMD_NOTESLIDEUP, 0,0, 0, MOD_TYPE_IMF | MOD_TYPE_PTM, _T("Note Slide Up")}, // IMF / PTM effect {CMD_NOTESLIDEDOWN, 0,0, 0, MOD_TYPE_IMF | MOD_TYPE_PTM, _T("Note Slide Down")}, // IMF / PTM effect {CMD_NOTESLIDEUPRETRIG, 0,0, 0, MOD_TYPE_PTM, _T("Note Slide Up + Retrigger Note")}, // PTM effect {CMD_NOTESLIDEDOWNRETRIG,0,0, 0, MOD_TYPE_PTM, _T("Note Slide Down + Retrigger Note")}, // PTM effect {CMD_REVERSEOFFSET, 0,0, 0, MOD_TYPE_PTM, _T("Revert Sample + Offset")}, // PTM effect {CMD_DBMECHO, 0,0, 0, MOD_TYPE_DBM, _T("Echo Enable")}, // DBM effect {CMD_OFFSETPERCENTAGE, 0,0, 0, MOD_TYPE_PLM, _T("Offset (Percentage)")}, // PLM effect {CMD_FINETUNE, 0,0, 0, MOD_TYPE_MPT, _T("Finetune")}, {CMD_FINETUNE_SMOOTH, 0,0, 0, MOD_TYPE_MPT, _T("Finetune (Smooth)")}, {CMD_DUMMY, 0,0, 0, MOD_TYPE_NONE, _T("Empty") }, {CMD_DIGIREVERSESAMPLE, 0, 0, 0, MOD_TYPE_NONE, _T("Reverse Sample")}, // DIGI effect }; UINT EffectInfo::GetNumEffects() const { return static_cast(std::size(gFXInfo)); } bool EffectInfo::IsExtendedEffect(UINT ndx) const { return ((ndx < std::size(gFXInfo)) && (gFXInfo[ndx].paramMask)); } bool EffectInfo::GetEffectName(CString &pszDescription, ModCommand::COMMAND command, UINT param, bool bXX) const { bool bSupported; UINT fxndx = static_cast(std::size(gFXInfo)); pszDescription.Empty(); for (UINT i = 0; i < std::size(gFXInfo); i++) { if ((command == gFXInfo[i].effect) // Effect && ((param & gFXInfo[i].paramMask) == gFXInfo[i].paramValue)) // Value { fxndx = i; // if format is compatible, everything is fine. if not, let's still search // for another command. this fixes searching for the EFx command, which // does different things in MOD format. if((sndFile.GetType() & gFXInfo[i].supportedFormats)) break; } } if (fxndx == std::size(gFXInfo)) return false; bSupported = ((sndFile.GetType() & gFXInfo[fxndx].supportedFormats)); if (gFXInfo[fxndx].name) { if ((bXX) && (bSupported)) { pszDescription.Format(_T("%c%c%c: ") , sndFile.GetModSpecifications().GetEffectLetter(command) , ((gFXInfo[fxndx].paramMask & 0xF0) == 0xF0) ? szHexChar[gFXInfo[fxndx].paramValue >> 4] : 'x' , ((gFXInfo[fxndx].paramMask & 0x0F) == 0x0F) ? szHexChar[gFXInfo[fxndx].paramValue & 0x0F] : 'x' ); } pszDescription += gFXInfo[fxndx].name; } return bSupported; } LONG EffectInfo::GetIndexFromEffect(ModCommand::COMMAND command, ModCommand::PARAM param) const { UINT ndx = static_cast(std::size(gFXInfo)); for (UINT i = 0; i < std::size(gFXInfo); i++) { if ((command == gFXInfo[i].effect) // Effect && ((param & gFXInfo[i].paramMask) == gFXInfo[i].paramValue)) // Value { ndx = i; if((sndFile.GetType() & gFXInfo[i].supportedFormats)) break; // found fitting format; this is correct for sure } } return ndx; } //Returns command and corrects parameter refParam if necessary EffectCommand EffectInfo::GetEffectFromIndex(UINT ndx, ModCommand::PARAM &refParam) const { if (ndx >= std::size(gFXInfo)) { refParam = 0; return CMD_NONE; } // Cap parameter to match FX if necessary. if (gFXInfo[ndx].paramMask) { if (refParam < gFXInfo[ndx].paramValue) { refParam = gFXInfo[ndx].paramValue; // for example: delay with param < D0 becomes SD0 } else if (refParam > gFXInfo[ndx].paramValue + 15) { refParam = gFXInfo[ndx].paramValue + 15; // for example: delay with param > DF becomes SDF } } if (gFXInfo[ndx].paramLimit) { // used for Zxx macro control in parameter editor: limit to 7F max. LimitMax(refParam, gFXInfo[ndx].paramLimit); } return gFXInfo[ndx].effect; } EffectCommand EffectInfo::GetEffectFromIndex(UINT ndx) const { if (ndx >= std::size(gFXInfo)) { return CMD_NONE; } return gFXInfo[ndx].effect; } UINT EffectInfo::GetEffectMaskFromIndex(UINT ndx) const { if (ndx >= std::size(gFXInfo)) { return 0; } return gFXInfo[ndx].paramValue; } bool EffectInfo::GetEffectInfo(UINT ndx, CString *s, bool bXX, ModCommand::PARAM *prangeMin, ModCommand::PARAM *prangeMax) const { if (s) s->Empty(); if (prangeMin) *prangeMin = 0; if (prangeMax) *prangeMax = 0; if ((ndx >= std::size(gFXInfo)) || (!(sndFile.GetType() & gFXInfo[ndx].supportedFormats))) return FALSE; if (s) GetEffectName(*s, gFXInfo[ndx].effect, gFXInfo[ndx].paramValue, bXX); if ((prangeMin) && (prangeMax)) { ModCommand::PARAM nmin = 0, nmax = 0xFF; if (gFXInfo[ndx].paramMask == 0xF0) { nmin = gFXInfo[ndx].paramValue; nmax = nmin | 0x0F; } switch(gFXInfo[ndx].effect) { case CMD_ARPEGGIO: if (sndFile.GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM)) nmin = 1; break; case CMD_VOLUME: case CMD_CHANNELVOLUME: nmax = 0x40; break; case CMD_SPEED: nmin = 1; if (sndFile.GetType() & (MOD_TYPE_XM | MOD_TYPE_MOD)) nmax = 0x1F; else nmax = 0xFF; break; case CMD_TEMPO: if (sndFile.GetType() & (MOD_TYPE_XM | MOD_TYPE_MOD)) nmin = 0x20; else nmin = 0; break; case CMD_VOLUMESLIDE: case CMD_TONEPORTAVOL: case CMD_VIBRATOVOL: case CMD_GLOBALVOLSLIDE: case CMD_CHANNELVOLSLIDE: case CMD_PANNINGSLIDE: nmax = (sndFile.GetType() & MOD_TYPE_S3MITMPT) ? 59 : 30; break; case CMD_PANNING8: if (sndFile.GetType() & (MOD_TYPE_S3M)) nmax = 0x81; else nmax = 0xFF; break; case CMD_GLOBALVOLUME: nmax = (sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) ? 128 : 64; break; case CMD_MODCMDEX: // adjust waveform types for XM/MOD if(gFXInfo[ndx].paramValue == 0x40 || gFXInfo[ndx].paramValue == 0x70) nmax = gFXInfo[ndx].paramValue | 0x07; if(gFXInfo[ndx].paramValue == 0x00) nmax = 1; break; case CMD_S3MCMDEX: // adjust waveform types for IT/S3M if(gFXInfo[ndx].paramValue >= 0x30 && gFXInfo[ndx].paramValue <= 0x50) nmax = gFXInfo[ndx].paramValue | ((sndFile.m_playBehaviour[kITVibratoTremoloPanbrello] || sndFile.GetType() == MOD_TYPE_S3M) ? 0x03 : 0x07); break; case CMD_PATTERNBREAK: // no big patterns in MOD/S3M files, and FT2 disallows breaking to rows > 63 if(sndFile.GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M | MOD_TYPE_XM)) nmax = 63; break; } *prangeMin = nmin; *prangeMax = nmax; } return TRUE; } UINT EffectInfo::MapValueToPos(UINT ndx, UINT param) const { UINT pos; if (ndx >= std::size(gFXInfo)) return 0; pos = param; if (gFXInfo[ndx].paramMask == 0xF0) { pos &= 0x0F; pos |= gFXInfo[ndx].paramValue; } switch(gFXInfo[ndx].effect) { case CMD_VOLUMESLIDE: case CMD_TONEPORTAVOL: case CMD_VIBRATOVOL: case CMD_GLOBALVOLSLIDE: case CMD_CHANNELVOLSLIDE: case CMD_PANNINGSLIDE: if (sndFile.GetType() & MOD_TYPE_S3MITMPT) { if (!param) pos = 29; else if (((param & 0x0F) == 0x0F) && (param & 0xF0)) pos = 29 + (param >> 4); // Fine Up else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) pos = 29 - (param & 0x0F); // Fine Down else if (param & 0x0F) pos = 15 - (param & 0x0F); // Down else pos = (param >> 4) + 44; // Up } else { if (param & 0x0F) pos = 15 - (param & 0x0F); else pos = (param >> 4) + 15; } break; case CMD_PANNING8: if(sndFile.GetType() == MOD_TYPE_S3M) { pos = Clamp(param, 0u, 0x80u); if(param == 0xA4) pos = 0x81; } break; } return pos; } UINT EffectInfo::MapPosToValue(UINT ndx, UINT pos) const { UINT param; if (ndx >= std::size(gFXInfo)) return 0; param = pos; if (gFXInfo[ndx].paramMask == 0xF0) param |= gFXInfo[ndx].paramValue; switch(gFXInfo[ndx].effect) { case CMD_VOLUMESLIDE: case CMD_TONEPORTAVOL: case CMD_VIBRATOVOL: case CMD_GLOBALVOLSLIDE: case CMD_CHANNELVOLSLIDE: case CMD_PANNINGSLIDE: if (sndFile.GetType() & MOD_TYPE_S3MITMPT) { if (pos < 15) param = 15 - pos; else if (pos < 29) param = (29 - pos) | 0xF0; else if (pos == 29) param = 0; else if (pos <= 44) param = ((pos - 29) << 4) | 0x0F; else if (pos <= 59) param = (pos - 44) << 4; } else { if (pos < 15) param = 15 - pos; else param = (pos - 15) << 4; } break; case CMD_PANNING8: if(sndFile.GetType() == MOD_TYPE_S3M) param = (pos <= 0x80) ? pos : 0xA4; break; } return param; } bool EffectInfo::GetEffectNameEx(CString &pszName, const ModCommand &m, uint32 param, CHANNELINDEX chn) const { CString s; const TCHAR *continueOrIgnore; auto ndx = GetIndexFromEffect(m.command, static_cast(param)); if(ndx < 0 || static_cast(ndx) >= std::size(gFXInfo) || !gFXInfo[ndx].name) return false; pszName = CString{gFXInfo[ndx].name} + _T(": "); // for effects that don't have effect memory in MOD format. if(sndFile.GetType() == MOD_TYPE_MOD) continueOrIgnore = _T("ignore"); else continueOrIgnore = _T("continue"); const TCHAR *plusChar = _T("+"), *minusChar = _T("-"); switch(gFXInfo[ndx].effect) { case CMD_ARPEGGIO: if(sndFile.GetType() == MOD_TYPE_XM) // XM also ignores this! continueOrIgnore = _T("ignore"); if(param) s.Format(_T("note+%d note+%d"), param >> 4, param & 0x0F); else s = continueOrIgnore; break; case CMD_PORTAMENTOUP: case CMD_PORTAMENTODOWN: if(param) { TCHAR sign = (gFXInfo[ndx].effect == CMD_PORTAMENTOUP) ? _T('+') : _T('-'); if((sndFile.GetType() & MOD_TYPE_S3MITMPT) && ((param & 0xF0) == 0xF0)) s.Format(_T("fine %c%d"), sign, (param & 0x0F)); else if((sndFile.GetType() & MOD_TYPE_S3MITMPT) && ((param & 0xF0) == 0xE0)) s.Format(_T("extra fine %c%d"), sign, (param & 0x0F)); else s.Format(_T("%c%d"), sign, param); } else { s = continueOrIgnore; } break; case CMD_TONEPORTAMENTO: if (param) s.Format(_T("speed %d"), param); else s = _T("continue"); break; case CMD_VIBRATO: case CMD_TREMOLO: case CMD_PANBRELLO: case CMD_FINEVIBRATO: if (param) s.Format(_T("speed=%d depth=%d"), param >> 4, param & 0x0F); else s = _T("continue"); break; case CMD_SPEED: s.Format(_T("%d ticks/row"), param); break; case CMD_TEMPO: if (param == 0) s = _T("continue"); else if (param < 0x10) s.Format(_T("-%d bpm (slower)"), param & 0x0F); else if (param < 0x20) s.Format(_T("+%d bpm (faster)"), param & 0x0F); else s.Format(_T("%d bpm"), param); break; case CMD_PANNING8: if(sndFile.GetType() == MOD_TYPE_S3M && param == 0xA4) s = _T("Surround"); else s.Format(_T("%d"), param); break; case CMD_RETRIG: switch(param >> 4) { case 0: if(sndFile.GetType() & MOD_TYPE_XM) s = _T("continue"); else s = _T("vol *1"); break; case 1: s = _T("vol -1"); break; case 2: s = _T("vol -2"); break; case 3: s = _T("vol -4"); break; case 4: s = _T("vol -8"); break; case 5: s = _T("vol -16"); break; case 6: s = _T("vol *0.66"); break; case 7: s = _T("vol *0.5"); break; case 8: s = _T("vol *1"); break; case 9: s = _T("vol +1"); break; case 10: s = _T("vol +2"); break; case 11: s = _T("vol +4"); break; case 12: s = _T("vol +8"); break; case 13: s = _T("vol +16"); break; case 14: s = _T("vol *1.5"); break; case 15: s = _T("vol *2"); break; } s.AppendFormat(_T(" speed %d"), param & 0x0F); break; case CMD_VOLUMESLIDE: if(sndFile.GetType() == MOD_TYPE_MOD && !param) { s = continueOrIgnore; break; } [[fallthrough]]; case CMD_TONEPORTAVOL: case CMD_VIBRATOVOL: case CMD_GLOBALVOLSLIDE: case CMD_CHANNELVOLSLIDE: case CMD_PANNINGSLIDE: if(gFXInfo[ndx].effect == CMD_PANNINGSLIDE) { if(sndFile.GetType() == MOD_TYPE_XM) { plusChar = _T("-> "); minusChar = _T("<- "); } else { plusChar = _T("<- "); minusChar = _T("-> "); } } if (!param) { s.Format(_T("continue")); } else if ((sndFile.GetType() & MOD_TYPE_S3MITMPT) && ((param & 0x0F) == 0x0F) && (param & 0xF0)) { s.Format(_T("fine %s%d"), plusChar, param >> 4); } else if ((sndFile.GetType() & MOD_TYPE_S3MITMPT) && ((param & 0xF0) == 0xF0) && (param & 0x0F)) { s.Format(_T("fine %s%d"), minusChar, param & 0x0F); } else if ((param & 0x0F) != param && (param & 0xF0) != param) // both nibbles are set. { s = _T("undefined"); } else if (param & 0x0F) { s.Format(_T("%s%d"), minusChar, param & 0x0F); } else { s.Format(_T("%s%d"), plusChar, param >> 4); } break; case CMD_PATTERNBREAK: pszName.Format(_T("Break to row %u"), param); break; case CMD_POSITIONJUMP: pszName.Format(_T("Jump to position %u"), param); break; case CMD_OFFSET: if (param) pszName.Format(_T("Set Offset to %s"), mpt::cfmt::dec(3, ',', param).GetString()); else s = _T("continue"); break; case CMD_CHANNELVOLUME: case CMD_GLOBALVOLUME: { ModCommand::PARAM minVal = 0, maxVal = 128; GetEffectInfo(ndx, nullptr, false, &minVal, &maxVal); if((sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_S3M)) && param > maxVal) s = _T("undefined"); else s.Format(_T("%u"), std::min(static_cast(param), static_cast(maxVal))); } break; case CMD_TREMOR: if(param) { uint8 ontime = (uint8)(param >> 4), offtime = (uint8)(param & 0x0F); if(sndFile.m_SongFlags[SONG_ITOLDEFFECTS] || (sndFile.GetType() & MOD_TYPE_XM)) { ontime++; offtime++; } else { if(ontime == 0) ontime = 1; if(offtime == 0) offtime = 1; } s.Format(_T("ontime %u, offtime %u"), ontime, offtime); } else { s = _T("continue"); } break; case CMD_SETENVPOSITION: s.Format(_T("Tick %u"), param); break; case CMD_MIDI: case CMD_SMOOTHMIDI: if (param < 0x80) { if(chn != CHANNELINDEX_INVALID) { const uint8 macroIndex = sndFile.m_PlayState.Chn[chn].nActiveMacro; const PLUGINDEX plugin = sndFile.GetBestPlugin(sndFile.m_PlayState, chn, PrioritiseChannel, EvenIfMuted) - 1; IMixPlugin *pPlugin = (plugin < MAX_MIXPLUGINS ? sndFile.m_MixPlugins[plugin].pMixPlugin : nullptr); pszName.Format(_T("SFx MIDI Macro z=%d (SF%X: %s)"), param, macroIndex, sndFile.m_MidiCfg.GetParameteredMacroName(macroIndex, pPlugin).GetString()); } else { pszName.Format(_T("SFx MIDI Macro z=%02X (%d)"), param, param); } } else { pszName.Format(_T("Fixed Macro Z%02X"), param); } break; case CMD_DELAYCUT: pszName.Format(_T("Note delay: %d, cut after %d ticks"), (param >> 4), (param & 0x0F)); break; case CMD_FINETUNE: case CMD_FINETUNE_SMOOTH: { int8 pwd = 1; const TCHAR *unit = _T(" cents"); if(m.instr > 0 && m.instr <= sndFile.GetNumInstruments() && sndFile.Instruments[m.instr] != nullptr) pwd = sndFile.Instruments[m.instr]->midiPWD; else if(chn != CHANNELINDEX_INVALID && sndFile.m_PlayState.Chn[chn].pModInstrument != nullptr) pwd = sndFile.m_PlayState.Chn[chn].pModInstrument->midiPWD; else if(sndFile.GetNumInstruments()) unit = _T(""); pszName = MPT_CFORMAT("Finetune{}: {}{}{}")( CString(gFXInfo[ndx].effect == CMD_FINETUNE ? _T("") : _T(" (Smooth)")), CString(param >= 0x8000 ? _T("+") : _T("")), mpt::cfmt::val((static_cast(param) - 0x8000) * pwd / 327.68), CString(unit)); } break; default: if (gFXInfo[ndx].paramMask == 0xF0) { // Sound control names if (((gFXInfo[ndx].effect == CMD_XFINEPORTAUPDOWN) || (gFXInfo[ndx].effect == CMD_S3MCMDEX)) && ((gFXInfo[ndx].paramValue & 0xF0) == 0x90) && ((param & 0xF0) == 0x90)) { switch(param & 0x0F) { case 0x00: s = _T("90: Surround Off"); break; case 0x01: s = _T("91: Surround On"); break; case 0x08: s = _T("98: Reverb Off"); break; case 0x09: s = _T("99: Reverb On"); break; case 0x0A: s = _T("9A: Center surround"); break; case 0x0B: s = _T("9B: Quad surround"); break; case 0x0C: s = _T("9C: Global filters"); break; case 0x0D: s = _T("9D: Local filters"); break; case 0x0E: s = _T("9E: Play Forward"); break; case 0x0F: s = _T("9F: Play Backward"); break; default: s.Format(_T("%02X: undefined"), param); } } else if (((gFXInfo[ndx].effect == CMD_XFINEPORTAUPDOWN) || (gFXInfo[ndx].effect == CMD_S3MCMDEX)) && ((gFXInfo[ndx].paramValue & 0xF0) == 0x70) && ((param & 0xF0) == 0x70)) { switch(param & 0x0F) { case 0x00: s = _T("70: Past note cut"); break; case 0x01: s = _T("71: Past note off"); break; case 0x02: s = _T("72: Past note fade"); break; case 0x03: s = _T("73: NNA note cut"); break; case 0x04: s = _T("74: NNA continue"); break; case 0x05: s = _T("75: NNA note off"); break; case 0x06: s = _T("76: NNA note fade"); break; case 0x07: s = _T("77: Volume Env Off"); break; case 0x08: s = _T("78: Volume Env On"); break; case 0x09: s = _T("79: Pan Env Off"); break; case 0x0A: s = _T("7A: Pan Env On"); break; case 0x0B: s = _T("7B: Pitch Env Off"); break; case 0x0C: s = _T("7C: Pitch Env On"); break; case 0x0D: if(sndFile.GetType() == MOD_TYPE_MPT) { s = _T("7D: Force Pitch Env"); break; } [[fallthrough]]; case 0x0E: if(sndFile.GetType() == MOD_TYPE_MPT) { s = _T("7E: Force Filter Env"); break; } [[fallthrough]]; default: s.Format(_T("%02X: undefined"), param); break; } } else { s.Format(_T("%d"), param & 0x0F); if(gFXInfo[ndx].effect == CMD_S3MCMDEX) { switch(param & 0xF0) { case 0x10: // glissando control if((param & 0x0F) == 0) s = _T("smooth"); else s = _T("semitones"); break; case 0x20: // set finetune s.Format(_T("%dHz"), S3MFineTuneTable[param & 0x0F]); break; case 0x30: // vibrato waveform case 0x40: // tremolo waveform case 0x50: // panbrello waveform if(((param & 0x0F) > 0x03) && sndFile.m_playBehaviour[kITVibratoTremoloPanbrello]) { s = _T("ignore"); break; } switch(param & 0x0F) { case 0x00: s = _T("sine wave"); break; case 0x01: s = _T("ramp down"); break; case 0x02: s = _T("square wave"); break; case 0x03: s = _T("random"); break; case 0x04: s = _T("sine wave (cont.)"); break; case 0x05: s = _T("ramp down (cont.)"); break; case 0x06: s = _T("square wave (cont.)"); break; case 0x07: s = _T("random (cont.)"); break; default: s = _T("ignore"); break; } break; case 0x60: // fine pattern delay (ticks) s += _T(" ticks"); break; case 0xA0: // high offset s.Format(_T("+ %u samples"), (param & 0x0F) * 0x10000); break; case 0xB0: // pattern loop if((param & 0x0F) == 0x00) s = _T("loop start"); else s += _T(" times"); break; case 0xC0: // note cut case 0xD0: // note delay //IT compatibility 22. SD0 == SD1, SC0 == SC1 if(((param & 0x0F) == 1) || ((param & 0x0F) == 0 && (sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)))) s = _T("1 tick"); else s += _T(" ticks"); break; case 0xE0: // pattern delay (rows) s += _T(" rows"); break; case 0xF0: // macro s = sndFile.m_MidiCfg.GetParameteredMacroName(param & 0x0F); break; default: break; } } if(gFXInfo[ndx].effect == CMD_MODCMDEX) { switch(param & 0xF0) { case 0x00: // Filter if(param & 1) s = _T("LED Filter Off"); else s = _T("LED Filter On"); break; case 0x30: // glissando control if((param & 0x0F) == 0) s = _T("smooth"); else s = _T("semitones"); break; case 0x40: // vibrato waveform case 0x70: // tremolo waveform switch(param & 0x0F) { case 0x00: case 0x08: s = _T("sine wave"); break; case 0x01: case 0x09: s = _T("ramp down"); break; case 0x02: case 0x0A: s = _T("square wave"); break; case 0x03: case 0x0B: s = _T("square wave"); break; case 0x04: case 0x0C: s = _T("sine wave (cont.)"); break; case 0x05: case 0x0D: s = _T("ramp down (cont.)"); break; case 0x06: case 0x0E: s = _T("square wave (cont.)"); break; case 0x07: case 0x0F: s = _T("square wave (cont.)"); break; } break; case 0x50: // set finetune { int8 nFinetune = (param & 0x0F); if(sndFile.GetType() & MOD_TYPE_XM) { // XM finetune nFinetune = (nFinetune - 8) * 16; } else { // MOD finetune if(nFinetune > 7) nFinetune -= 16; } s.Format(_T("%d"), nFinetune); } break; case 0x60: // pattern loop if((param & 0x0F) == 0x00) s = _T("loop start"); else s += _T(" times"); break; case 0x90: // retrigger s.Format(_T("speed %d"), param & 0x0F); break; case 0xC0: // note cut case 0xD0: // note delay s += _T(" ticks"); break; case 0xE0: // pattern delay (rows) s += _T(" rows"); break; case 0xF0: if(sndFile.GetType() == MOD_TYPE_MOD) { // invert loop if((param & 0x0F) == 0) s = _T("Stop"); else s.Format(_T("Speed %d"), param & 0x0F); } else { // macro s = sndFile.m_MidiCfg.GetParameteredMacroName(param & 0x0F); } break; default: break; } } } } else { s.Format(_T("%u"), param); } } pszName += s; return true; } //////////////////////////////////////////////////////////////////////////////////////// // Volume column effects description struct MPTVolCmdInfo { VolumeCommand volCmd; // VOLCMD_XXXX FlagSet supportedFormats; // MOD_TYPE_XXX combo const TCHAR *name; // e.g. "Set Volume" }; 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")}, }; static_assert(mpt::array_size::size == (MAX_VOLCMDS - 1)); UINT EffectInfo::GetNumVolCmds() const { return static_cast(std::size(gVolCmdInfo)); } LONG EffectInfo::GetIndexFromVolCmd(ModCommand::VOLCMD volcmd) const { for (UINT i = 0; i < std::size(gVolCmdInfo); i++) { if (gVolCmdInfo[i].volCmd == volcmd) return i; } return -1; } VolumeCommand EffectInfo::GetVolCmdFromIndex(UINT ndx) const { return (ndx < std::size(gVolCmdInfo)) ? gVolCmdInfo[ndx].volCmd : VOLCMD_NONE; } bool EffectInfo::GetVolCmdInfo(UINT ndx, CString *s, ModCommand::VOL *prangeMin, ModCommand::VOL *prangeMax) const { if (s) s->Empty(); if (prangeMin) *prangeMin = 0; if (prangeMax) *prangeMax = 0; if (ndx >= std::size(gVolCmdInfo)) return false; if (s) { s->Format(_T("%c: %s"), sndFile.GetModSpecifications().GetVolEffectLetter(GetVolCmdFromIndex(ndx)), gVolCmdInfo[ndx].name); } if ((prangeMin) && (prangeMax)) { switch(gVolCmdInfo[ndx].volCmd) { case VOLCMD_VOLUME: case VOLCMD_PANNING: *prangeMax = 64; break; default: *prangeMax = (sndFile.GetType() & MOD_TYPE_XM) ? 15 : 9; } } return (sndFile.GetType() & gVolCmdInfo[ndx].supportedFormats); } bool EffectInfo::GetVolCmdParamInfo(const ModCommand &m, CString *s) const { if(s == nullptr) return false; s->Empty(); switch(m.volcmd) { case VOLCMD_VOLSLIDEUP: case VOLCMD_VOLSLIDEDOWN: case VOLCMD_FINEVOLUP: case VOLCMD_FINEVOLDOWN: if(m.vol > 0 || sndFile.GetType() == MOD_TYPE_XM) { s->Format(_T("%c%u"), (m.volcmd == VOLCMD_VOLSLIDEUP || m.volcmd == VOLCMD_FINEVOLUP) ? _T('+') : _T('-'), m.vol); } else { *s = _T("continue"); } break; case VOLCMD_PORTAUP: case VOLCMD_PORTADOWN: case VOLCMD_TONEPORTAMENTO: if(m.vol > 0) { ModCommand::PARAM param = m.vol << 2; ModCommand::COMMAND cmd = CMD_PORTAMENTOUP; if(m.volcmd == VOLCMD_PORTADOWN) { cmd = CMD_PORTAMENTODOWN; } else if(m.volcmd == VOLCMD_TONEPORTAMENTO) { cmd = CMD_TONEPORTAMENTO; if(sndFile.GetType() != MOD_TYPE_XM) param = ImpulseTrackerPortaVolCmd[m.vol & 0x0F]; else param = m.vol << 4; } s->Format(_T("%u (%c%02X)"), m.vol, sndFile.GetModSpecifications().GetEffectLetter(cmd), param); } else { *s = _T("continue"); } break; case VOLCMD_OFFSET: if(m.vol) { SAMPLEINDEX smp = m.instr; if(smp > 0 && smp <= sndFile.GetNumInstruments() && m.IsNote() && sndFile.Instruments[smp] != nullptr) { smp = sndFile.Instruments[smp]->Keyboard[m.note - NOTE_MIN]; } s->Format(_T("Cue %u: "), m.vol); if(smp > 0 && smp <= sndFile.GetNumSamples() && m.vol > 0 && m.vol <= std::size(sndFile.GetSample(smp).cues)) { auto cue = sndFile.GetSample(smp).cues[m.vol - 1]; if(cue < sndFile.GetSample(smp).nLength) s->Append(mpt::cfmt::dec(3, _T(','), sndFile.GetSample(smp).cues[m.vol - 1])); else s->Append(_T("unused")); } else s->Append(_T("unknown")); } else { *s = _T("continue"); } break; case VOLCMD_PLAYCONTROL: if(m.vol == 0) *s = _T("Pause Playback"); else if(m.vol == 1) *s = _T("Continue Playback"); break; default: s->Format(_T("%u"), m.vol); break; } return true; } OPENMPT_NAMESPACE_END