aboutsummaryrefslogtreecommitdiff
path: root/Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp')
-rw-r--r--Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp1279
1 files changed, 1279 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp
new file mode 100644
index 00000000..5f5f98b2
--- /dev/null
+++ b/Src/external_dependencies/openmpt-trunk/soundlib/modcommand.cpp
@@ -0,0 +1,1279 @@
+/*
+ * modcommand.cpp
+ * --------------
+ * Purpose: Various functions for writing effects to patterns, converting ModCommands, etc.
+ * Notes : (currently none)
+ * Authors: OpenMPT Devs
+ * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
+ */
+
+
+#include "stdafx.h"
+#include "Sndfile.h"
+#include "mod_specifications.h"
+#include "Tables.h"
+
+
+OPENMPT_NAMESPACE_BEGIN
+
+
+const EffectType effectTypes[] =
+{
+ EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_VOLUME, EFFECT_TYPE_VOLUME,
+ EFFECT_TYPE_VOLUME, EFFECT_TYPE_PANNING, EFFECT_TYPE_NORMAL, EFFECT_TYPE_VOLUME,
+ EFFECT_TYPE_GLOBAL, EFFECT_TYPE_VOLUME, EFFECT_TYPE_GLOBAL, EFFECT_TYPE_NORMAL,
+ EFFECT_TYPE_GLOBAL, EFFECT_TYPE_GLOBAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
+ EFFECT_TYPE_NORMAL, EFFECT_TYPE_VOLUME, EFFECT_TYPE_VOLUME, EFFECT_TYPE_GLOBAL,
+ EFFECT_TYPE_GLOBAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PANNING,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_PANNING, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
+ EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
+ EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
+};
+
+static_assert(std::size(effectTypes) == MAX_EFFECTS);
+
+
+const EffectType volumeEffectTypes[] =
+{
+ EFFECT_TYPE_NORMAL, EFFECT_TYPE_VOLUME, EFFECT_TYPE_PANNING, EFFECT_TYPE_VOLUME,
+ EFFECT_TYPE_VOLUME, EFFECT_TYPE_VOLUME, EFFECT_TYPE_VOLUME, EFFECT_TYPE_PITCH,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_PANNING, EFFECT_TYPE_PANNING, EFFECT_TYPE_PITCH,
+ EFFECT_TYPE_PITCH, EFFECT_TYPE_PITCH, EFFECT_TYPE_NORMAL, EFFECT_TYPE_NORMAL,
+};
+
+static_assert(std::size(volumeEffectTypes) == MAX_VOLCMDS);
+
+
+EffectType ModCommand::GetEffectType(COMMAND cmd)
+{
+ if(cmd < std::size(effectTypes))
+ return effectTypes[cmd];
+ else
+ return EFFECT_TYPE_NORMAL;
+}
+
+
+EffectType ModCommand::GetVolumeEffectType(VOLCMD volcmd)
+{
+ if(volcmd < std::size(volumeEffectTypes))
+ return volumeEffectTypes[volcmd];
+ else
+ return EFFECT_TYPE_NORMAL;
+}
+
+
+// Convert an Exx command (MOD) to Sxx command (S3M)
+void ModCommand::ExtendedMODtoS3MEffect()
+{
+ if(command != CMD_MODCMDEX)
+ return;
+
+ command = CMD_S3MCMDEX;
+ switch(param & 0xF0)
+ {
+ case 0x00: command = CMD_NONE; break; // No filter control
+ case 0x10: command = CMD_PORTAMENTOUP; param |= 0xF0; break;
+ case 0x20: command = CMD_PORTAMENTODOWN; param |= 0xF0; break;
+ case 0x30: param = (param & 0x0F) | 0x10; break;
+ case 0x40: param = (param & 0x03) | 0x30; break;
+ case 0x50: param = (param & 0x0F) | 0x20; break;
+ case 0x60: param = (param & 0x0F) | 0xB0; break;
+ case 0x70: param = (param & 0x03) | 0x40; break;
+ case 0x90: command = CMD_RETRIG; param = (param & 0x0F); break;
+ case 0xA0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param = (param << 4) | 0x0F; } else command = CMD_NONE; break;
+ case 0xB0: if(param & 0x0F) { command = CMD_VOLUMESLIDE; param = 0xF0 | static_cast<PARAM>(std::min(param & 0x0F, 0x0E)); } else command = CMD_NONE; break;
+ case 0xC0: if(param == 0xC0) { command = CMD_NONE; note = NOTE_NOTECUT; } break; // this does different things in IT and ST3
+ case 0xD0: if(param == 0xD0) { command = CMD_NONE; } break; // ditto
+ // rest are the same or handled elsewhere
+ }
+}
+
+
+// Convert an Sxx command (S3M) to Exx command (MOD)
+void ModCommand::ExtendedS3MtoMODEffect()
+{
+ if(command != CMD_S3MCMDEX)
+ return;
+
+ command = CMD_MODCMDEX;
+ switch(param & 0xF0)
+ {
+ case 0x10: param = (param & 0x0F) | 0x30; break;
+ case 0x20: param = (param & 0x0F) | 0x50; break;
+ case 0x30: param = (param & 0x0F) | 0x40; break;
+ case 0x40: param = (param & 0x0F) | 0x70; break;
+ case 0x50: command = CMD_XFINEPORTAUPDOWN; break; // map to unused X5x
+ case 0x60: command = CMD_XFINEPORTAUPDOWN; break; // map to unused X6x
+ case 0x80: command = CMD_PANNING8; param = (param & 0x0F) * 0x11; break; // FT2 does actually not support E8x
+ case 0x90: command = CMD_XFINEPORTAUPDOWN; break; // map to unused X9x
+ case 0xA0: command = CMD_XFINEPORTAUPDOWN; break; // map to unused XAx
+ case 0xB0: param = (param & 0x0F) | 0x60; break;
+ case 0x70: command = CMD_NONE; break; // No NNA / envelope control in MOD/XM format
+ // rest are the same or handled elsewhere
+ }
+}
+
+
+// Convert a mod command from one format to another.
+void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &sndFile)
+{
+ if(fromType == toType)
+ {
+ return;
+ }
+
+ if(fromType == MOD_TYPE_MTM)
+ {
+ // Special MTM fixups.
+ // Retrigger with param 0
+ if(command == CMD_MODCMDEX && param == 0x90)
+ {
+ command = CMD_NONE;
+ } else if(command == CMD_VIBRATO)
+ {
+ // Vibrato is approximately half as deep compared to MOD/S3M.
+ uint8 speed = (param & 0xF0);
+ uint8 depth = (param & 0x0F) >> 1;
+ param = speed | depth;
+ }
+ // Apart from these special fixups, do a regular conversion from MOD.
+ fromType = MOD_TYPE_MOD;
+ }
+ if(command == CMD_DIGIREVERSESAMPLE && toType != MOD_TYPE_DIGI)
+ {
+ command = CMD_S3MCMDEX;
+ param = 0x9F;
+ }
+
+ // helper variables
+ const bool oldTypeIsMOD = (fromType == MOD_TYPE_MOD), oldTypeIsXM = (fromType == MOD_TYPE_XM),
+ oldTypeIsS3M = (fromType == MOD_TYPE_S3M), oldTypeIsIT = (fromType == MOD_TYPE_IT),
+ oldTypeIsMPT = (fromType == MOD_TYPE_MPT), oldTypeIsMOD_XM = (oldTypeIsMOD || oldTypeIsXM),
+ oldTypeIsS3M_IT_MPT = (oldTypeIsS3M || oldTypeIsIT || oldTypeIsMPT),
+ oldTypeIsIT_MPT = (oldTypeIsIT || oldTypeIsMPT);
+
+ const bool newTypeIsMOD = (toType == MOD_TYPE_MOD), newTypeIsXM = (toType == MOD_TYPE_XM),
+ newTypeIsS3M = (toType == MOD_TYPE_S3M), newTypeIsIT = (toType == MOD_TYPE_IT),
+ newTypeIsMPT = (toType == MOD_TYPE_MPT), newTypeIsMOD_XM = (newTypeIsMOD || newTypeIsXM),
+ newTypeIsS3M_IT_MPT = (newTypeIsS3M || newTypeIsIT || newTypeIsMPT),
+ newTypeIsIT_MPT = (newTypeIsIT || newTypeIsMPT);
+
+ const CModSpecifications &newSpecs = CSoundFile::GetModSpecifications(toType);
+
+ //////////////////////////
+ // Convert 8-bit Panning
+ if(command == CMD_PANNING8)
+ {
+ if(newTypeIsS3M)
+ {
+ param = (param + 1) >> 1;
+ } else if(oldTypeIsS3M)
+ {
+ if(param == 0xA4)
+ {
+ // surround remap
+ command = static_cast<COMMAND>((toType & (MOD_TYPE_IT | MOD_TYPE_MPT)) ? CMD_S3MCMDEX : CMD_XFINEPORTAUPDOWN);
+ param = 0x91;
+ } else
+ {
+ param = mpt::saturate_cast<PARAM>(param * 2u);
+ }
+ }
+ } // End if(command == CMD_PANNING8)
+
+ // Re-map \xx to Zxx if the new format only knows the latter command.
+ if(command == CMD_SMOOTHMIDI && !newSpecs.HasCommand(CMD_SMOOTHMIDI) && newSpecs.HasCommand(CMD_MIDI))
+ {
+ command = CMD_MIDI;
+ }
+
+ ///////////////////////////////////////////////////////////////////////////////////////
+ // MPTM to anything: Convert param control, extended envelope control, note delay+cut
+ if(oldTypeIsMPT)
+ {
+ if(IsPcNote())
+ {
+ COMMAND newCmd = static_cast<COMMAND>(note == NOTE_PC ? CMD_MIDI : CMD_SMOOTHMIDI);
+ if(!newSpecs.HasCommand(newCmd))
+ {
+ newCmd = CMD_MIDI; // assuming that this was CMD_SMOOTHMIDI
+ if(!newSpecs.HasCommand(newCmd))
+ {
+ newCmd = CMD_NONE;
+ }
+ }
+
+ param = static_cast<PARAM>(std::min(static_cast<uint16>(maxColumnValue), GetValueEffectCol()) * 0x7F / maxColumnValue);
+ command = newCmd; // might be removed later
+ volcmd = VOLCMD_NONE;
+ note = NOTE_NONE;
+ instr = 0;
+ }
+
+ if((command == CMD_S3MCMDEX) && ((param & 0xF0) == 0x70) && ((param & 0x0F) > 0x0C))
+ {
+ // Extended pitch envelope control commands
+ param = 0x7C;
+ } else if(command == CMD_DELAYCUT)
+ {
+ command = CMD_S3MCMDEX; // When converting to MOD/XM, this will be converted to CMD_MODCMDEX later
+ param = 0xD0 | (param >> 4); // Preserve delay nibble
+ } else if(command == CMD_FINETUNE || command == CMD_FINETUNE_SMOOTH)
+ {
+ // Convert finetune from +/-128th of a semitone to (extra-)fine portamento (assumes linear slides, plus we're missing the actual pitch wheel depth of the instrument)
+ if(param < 0x80)
+ {
+ command = CMD_PORTAMENTODOWN;
+ param = 0x80 - param;
+ } else if(param > 0x80)
+ {
+ command = CMD_PORTAMENTOUP;
+ param -= 0x80;
+ }
+ if(param <= 30)
+ param = 0xE0 | ((param + 1u) / 2u);
+ else
+ param = 0xF0 | std::min(static_cast<PARAM>((param + 7u) / 8u), PARAM(15));
+ }
+ } // End if(oldTypeIsMPT)
+
+ /////////////////////////////////////////
+ // Convert MOD / XM to S3M / IT / MPTM
+ if(oldTypeIsMOD_XM && newTypeIsS3M_IT_MPT)
+ {
+ switch(command)
+ {
+ case CMD_ARPEGGIO:
+ if(!param) command = CMD_NONE; // 000 does nothing in MOD/XM
+ break;
+
+ case CMD_MODCMDEX:
+ ExtendedMODtoS3MEffect();
+ break;
+
+ case CMD_VOLUME:
+ // Effect column volume command overrides the volume column in XM.
+ if(volcmd == VOLCMD_NONE || volcmd == VOLCMD_VOLUME)
+ {
+ volcmd = VOLCMD_VOLUME;
+ vol = param;
+ if(vol > 64) vol = 64;
+ command = CMD_NONE;
+ param = 0;
+ } else if(volcmd == VOLCMD_PANNING)
+ {
+ std::swap(vol, param);
+ volcmd = VOLCMD_VOLUME;
+ if(vol > 64) vol = 64;
+ command = CMD_S3MCMDEX;
+ param = 0x80 | (param / 4); // XM volcol panning is actually 4-Bit, so we can use 4-Bit panning here.
+ }
+ break;
+
+ case CMD_PORTAMENTOUP:
+ if(param > 0xDF) param = 0xDF;
+ break;
+
+ case CMD_PORTAMENTODOWN:
+ if(param > 0xDF) param = 0xDF;
+ break;
+
+ case CMD_XFINEPORTAUPDOWN:
+ switch(param & 0xF0)
+ {
+ case 0x10: command = CMD_PORTAMENTOUP; param = (param & 0x0F) | 0xE0; break;
+ case 0x20: command = CMD_PORTAMENTODOWN; param = (param & 0x0F) | 0xE0; break;
+ case 0x50:
+ case 0x60:
+ case 0x70:
+ case 0x90:
+ case 0xA0:
+ command = CMD_S3MCMDEX;
+ // Surround remap (this is the "official" command)
+ if(toType & MOD_TYPE_S3M && param == 0x91)
+ {
+ command = CMD_PANNING8;
+ param = 0xA4;
+ }
+ break;
+ }
+ break;
+
+ case CMD_KEYOFF:
+ if(note == NOTE_NONE)
+ {
+ note = newTypeIsS3M ? NOTE_NOTECUT : NOTE_KEYOFF;
+ command = CMD_S3MCMDEX;
+ if(param == 0)
+ instr = 0;
+ param = 0xD0 | (param & 0x0F);
+ }
+ break;
+
+ case CMD_PANNINGSLIDE:
+ // swap L/R, convert to fine slide
+ if(param & 0xF0)
+ {
+ param = 0xF0 | std::min(PARAM(0x0E), static_cast<PARAM>(param >> 4));
+ } else
+ {
+ param = 0x0F | (std::min(PARAM(0x0E), static_cast<PARAM>(param & 0x0F)) << 4);
+ }
+ break;
+
+ default:
+ break;
+ }
+ } // End if(oldTypeIsMOD_XM && newTypeIsS3M_IT_MPT)
+
+
+ /////////////////////////////////////////
+ // Convert S3M / IT / MPTM to MOD / XM
+ else if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM)
+ {
+ if(note == NOTE_NOTECUT)
+ {
+ // convert note cut to C00 if possible or volume command otherwise (MOD/XM has no real way of cutting notes that cannot be "undone" by volume commands)
+ note = NOTE_NONE;
+ if(command == CMD_NONE || !newTypeIsXM)
+ {
+ command = CMD_VOLUME;
+ param = 0;
+ } else
+ {
+ volcmd = VOLCMD_VOLUME;
+ vol = 0;
+ }
+ } else if(note == NOTE_FADE)
+ {
+ // convert note fade to note off
+ note = NOTE_KEYOFF;
+ }
+
+ switch(command)
+ {
+ case CMD_S3MCMDEX:
+ ExtendedS3MtoMODEffect();
+ break;
+
+ case CMD_TONEPORTAVOL: // Can't do fine slides and portamento/vibrato at the same time :(
+ case CMD_VIBRATOVOL: // ditto
+ if(volcmd == VOLCMD_NONE && (((param & 0xF0) && ((param & 0x0F) == 0x0F)) || ((param & 0x0F) && ((param & 0xF0) == 0xF0))))
+ {
+ // Try to salvage portamento/vibrato
+ if(command == CMD_TONEPORTAVOL)
+ volcmd = VOLCMD_TONEPORTAMENTO;
+ else if(command == CMD_VIBRATOVOL)
+ volcmd = VOLCMD_VIBRATODEPTH;
+ vol = 0;
+ }
+
+ [[fallthrough]];
+ case CMD_VOLUMESLIDE:
+ if((param & 0xF0) && ((param & 0x0F) == 0x0F))
+ {
+ command = CMD_MODCMDEX;
+ param = (param >> 4) | 0xA0;
+ } else if((param & 0x0F) && ((param & 0xF0) == 0xF0))
+ {
+ command = CMD_MODCMDEX;
+ param = (param & 0x0F) | 0xB0;
+ }
+ break;
+
+ case CMD_PORTAMENTOUP:
+ if(param >= 0xF0)
+ {
+ command = CMD_MODCMDEX;
+ param = (param & 0x0F) | 0x10;
+ } else if(param >= 0xE0)
+ {
+ if(newTypeIsXM)
+ {
+ command = CMD_XFINEPORTAUPDOWN;
+ param = 0x10 | (param & 0x0F);
+ } else
+ {
+ command = CMD_MODCMDEX;
+ param = (((param & 0x0F) + 3) >> 2) | 0x10;
+ }
+ } else
+ {
+ command = CMD_PORTAMENTOUP;
+ }
+ break;
+
+ case CMD_PORTAMENTODOWN:
+ if(param >= 0xF0)
+ {
+ command = CMD_MODCMDEX;
+ param = (param & 0x0F) | 0x20;
+ } else if(param >= 0xE0)
+ {
+ if(newTypeIsXM)
+ {
+ command = CMD_XFINEPORTAUPDOWN;
+ param = 0x20 | (param & 0x0F);
+ } else
+ {
+ command = CMD_MODCMDEX;
+ param = (((param & 0x0F) + 3) >> 2) | 0x20;
+ }
+ } else
+ {
+ command = CMD_PORTAMENTODOWN;
+ }
+ break;
+
+ case CMD_TEMPO:
+ if(param < 0x20) command = CMD_NONE; // no tempo slides
+ break;
+
+ case CMD_PANNINGSLIDE:
+ // swap L/R, convert fine slides to normal slides
+ if((param & 0x0F) == 0x0F && (param & 0xF0))
+ {
+ param = (param >> 4);
+ } else if((param & 0xF0) == 0xF0 && (param & 0x0F))
+ {
+ param = (param & 0x0F) << 4;
+ } else if(param & 0x0F)
+ {
+ param = 0xF0;
+ } else if(param & 0xF0)
+ {
+ param = 0x0F;
+ } else
+ {
+ param = 0;
+ }
+ break;
+
+ case CMD_RETRIG:
+ // Retrig: Q0y doesn't change volume in IT/S3M, but R0y in XM takes the last x parameter
+ if(param != 0 && (param & 0xF0) == 0)
+ {
+ param |= 0x80;
+ }
+ break;
+
+ default:
+ break;
+ }
+ } // End if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM)
+
+
+ ///////////////////////
+ // Convert IT to S3M
+ else if(oldTypeIsIT_MPT && newTypeIsS3M)
+ {
+ if(note == NOTE_KEYOFF || note == NOTE_FADE)
+ note = NOTE_NOTECUT;
+
+ switch(command)
+ {
+ case CMD_S3MCMDEX:
+ switch(param & 0xF0)
+ {
+ case 0x70: command = CMD_NONE; break; // No NNA / envelope control in S3M format
+ case 0x90:
+ if(param == 0x91)
+ {
+ // surround remap (this is the "official" command)
+ command = CMD_PANNING8;
+ param = 0xA4;
+ } else if(param == 0x90)
+ {
+ command = CMD_PANNING8;
+ param = 0x40;
+ }
+ break;
+ }
+ break;
+
+ case CMD_GLOBALVOLUME:
+ param = (std::min(PARAM(0x80), param) + 1) / 2u;
+ break;
+
+ default:
+ break;
+ }
+ } // End if(oldTypeIsIT_MPT && newTypeIsS3M)
+
+ //////////////////////
+ // Convert IT to XM
+ if(oldTypeIsIT_MPT && newTypeIsXM)
+ {
+ switch(command)
+ {
+ case CMD_VIBRATO:
+ // With linear slides, strength is roughly doubled.
+ param = (param & 0xF0) | (((param & 0x0F) + 1) / 2u);
+ break;
+ case CMD_GLOBALVOLUME:
+ param = (std::min(PARAM(0x80), param) + 1) / 2u;
+ break;
+ }
+ } // End if(oldTypeIsIT_MPT && newTypeIsXM)
+
+ //////////////////////
+ // Convert XM to IT
+ if(oldTypeIsXM && newTypeIsIT_MPT)
+ {
+ switch(command)
+ {
+ case CMD_VIBRATO:
+ // With linear slides, strength is roughly halved.
+ param = (param & 0xF0) | std::min(static_cast<PARAM>((param & 0x0F) * 2u), PARAM(15));
+ break;
+ case CMD_GLOBALVOLUME:
+ param = std::min(PARAM(0x40), param) * 2u;
+ break;
+ }
+ } // End if(oldTypeIsIT_MPT && newTypeIsXM)
+
+ ///////////////////////////////////
+ // MOD / XM Speed/Tempo limits
+ if(newTypeIsMOD_XM)
+ {
+ switch(command)
+ {
+ case CMD_SPEED:
+ param = std::min(param, PARAM(0x1F));
+ break;
+ break;
+ case CMD_TEMPO:
+ param = std::max(param, PARAM(0x20));
+ break;
+ }
+ }
+
+ ///////////////////////////////////////////////////////////////////////
+ // Convert MOD to anything - adjust effect memory, remove Invert Loop
+ if(oldTypeIsMOD)
+ {
+ switch(command)
+ {
+ case CMD_TONEPORTAVOL: // lacks memory -> 500 is the same as 300
+ if(param == 0x00)
+ command = CMD_TONEPORTAMENTO;
+ break;
+
+ case CMD_VIBRATOVOL: // lacks memory -> 600 is the same as 400
+ if(param == 0x00)
+ command = CMD_VIBRATO;
+ break;
+
+ case CMD_PORTAMENTOUP: // lacks memory -> remove
+ case CMD_PORTAMENTODOWN:
+ case CMD_VOLUMESLIDE:
+ if(param == 0x00)
+ command = CMD_NONE;
+ break;
+
+ case CMD_MODCMDEX: // This would turn into "Set Active Macro", so let's better remove it
+ case CMD_S3MCMDEX:
+ if((param & 0xF0) == 0xF0)
+ command = CMD_NONE;
+ break;
+ }
+ } // End if(oldTypeIsMOD && newTypeIsXM)
+
+ /////////////////////////////////////////////////////////////////////
+ // Convert anything to MOD - remove volume column, remove Set Macro
+ if(newTypeIsMOD)
+ {
+ // convert note off events
+ if(IsSpecialNote())
+ {
+ note = NOTE_NONE;
+ // no effect present, so just convert note off to volume 0
+ if(command == CMD_NONE)
+ {
+ command = CMD_VOLUME;
+ param = 0;
+ // EDx effect present, so convert it to ECx
+ } else if((command == CMD_MODCMDEX) && ((param & 0xF0) == 0xD0))
+ {
+ param = 0xC0 | (param & 0x0F);
+ }
+ }
+
+ if(command != CMD_NONE) switch(command)
+ {
+ case CMD_RETRIG: // MOD only has E9x
+ command = CMD_MODCMDEX;
+ param = 0x90 | (param & 0x0F);
+ break;
+
+ case CMD_MODCMDEX: // This would turn into "Invert Loop", so let's better remove it
+ if((param & 0xF0) == 0xF0) command = CMD_NONE;
+ break;
+ }
+
+ if(command == CMD_NONE) switch(volcmd)
+ {
+ case VOLCMD_VOLUME:
+ command = CMD_VOLUME;
+ param = vol;
+ break;
+
+ case VOLCMD_PANNING:
+ command = CMD_PANNING8;
+ param = vol < 64 ? vol << 2 : 255;
+ break;
+
+ case VOLCMD_VOLSLIDEDOWN:
+ command = CMD_VOLUMESLIDE;
+ param = vol;
+ break;
+
+ case VOLCMD_VOLSLIDEUP:
+ command = CMD_VOLUMESLIDE;
+ param = vol << 4;
+ break;
+
+ case VOLCMD_FINEVOLDOWN:
+ command = CMD_MODCMDEX;
+ param = 0xB0 | vol;
+ break;
+
+ case VOLCMD_FINEVOLUP:
+ command = CMD_MODCMDEX;
+ param = 0xA0 | vol;
+ break;
+
+ case VOLCMD_PORTADOWN:
+ command = CMD_PORTAMENTODOWN;
+ param = vol << 2;
+ break;
+
+ case VOLCMD_PORTAUP:
+ command = CMD_PORTAMENTOUP;
+ param = vol << 2;
+ break;
+
+ case VOLCMD_TONEPORTAMENTO:
+ command = CMD_TONEPORTAMENTO;
+ param = vol << 2;
+ break;
+
+ case VOLCMD_VIBRATODEPTH:
+ command = CMD_VIBRATO;
+ param = vol;
+ break;
+
+ case VOLCMD_VIBRATOSPEED:
+ command = CMD_VIBRATO;
+ param = vol << 4;
+ break;
+ }
+ volcmd = VOLCMD_NONE;
+ } // End if(newTypeIsMOD)
+
+ ///////////////////////////////////////////////////
+ // Convert anything to S3M - adjust volume column
+ if(newTypeIsS3M)
+ {
+ if(command == CMD_NONE) switch(volcmd)
+ {
+ case VOLCMD_VOLSLIDEDOWN:
+ command = CMD_VOLUMESLIDE;
+ param = vol;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_VOLSLIDEUP:
+ command = CMD_VOLUMESLIDE;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_FINEVOLDOWN:
+ command = CMD_VOLUMESLIDE;
+ param = 0xF0 | vol;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_FINEVOLUP:
+ command = CMD_VOLUMESLIDE;
+ param = (vol << 4) | 0x0F;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PORTADOWN:
+ command = CMD_PORTAMENTODOWN;
+ param = vol << 2;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PORTAUP:
+ command = CMD_PORTAMENTOUP;
+ param = vol << 2;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_TONEPORTAMENTO:
+ command = CMD_TONEPORTAMENTO;
+ param = vol << 2;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_VIBRATODEPTH:
+ command = CMD_VIBRATO;
+ param = vol;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_VIBRATOSPEED:
+ command = CMD_VIBRATO;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PANSLIDELEFT:
+ command = CMD_PANNINGSLIDE;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PANSLIDERIGHT:
+ command = CMD_PANNINGSLIDE;
+ param = vol;
+ volcmd = VOLCMD_NONE;
+ break;
+ }
+ } // End if(newTypeIsS3M)
+
+ ////////////////////////////////////////////////////////////////////////
+ // Convert anything to XM - adjust volume column, breaking EDx command
+ if(newTypeIsXM)
+ {
+ // remove EDx if no note is next to it, or it will retrigger the note in FT2 mode
+ if(command == CMD_MODCMDEX && (param & 0xF0) == 0xD0 && note == NOTE_NONE)
+ {
+ command = CMD_NONE;
+ param = 0;
+ }
+
+ if(IsSpecialNote())
+ {
+ // Instrument numbers next to Note Off reset instrument settings
+ instr = 0;
+
+ if(command == CMD_MODCMDEX && (param & 0xF0) == 0xD0)
+ {
+ // Note Off + Note Delay does nothing when using envelopes.
+ note = NOTE_NONE;
+ command = CMD_KEYOFF;
+ param &= 0x0F;
+ }
+ }
+
+ // Convert some commands which behave differently or don't exist
+ if(command == CMD_NONE) switch(volcmd)
+ {
+ case VOLCMD_PORTADOWN:
+ command = CMD_PORTAMENTODOWN;
+ param = vol << 2;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PORTAUP:
+ command = CMD_PORTAMENTOUP;
+ param = vol << 2;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_TONEPORTAMENTO:
+ command = CMD_TONEPORTAMENTO;
+ param = ImpulseTrackerPortaVolCmd[vol & 0x0F];
+ volcmd = VOLCMD_NONE;
+ break;
+ }
+ } // End if(newTypeIsXM)
+
+ ///////////////////////////////////////////////////
+ // Convert anything to IT - adjust volume column
+ if(newTypeIsIT_MPT)
+ {
+ // Convert some commands which behave differently or don't exist
+ if(!oldTypeIsIT_MPT && command == CMD_NONE) switch(volcmd)
+ {
+ case VOLCMD_PANSLIDELEFT:
+ command = CMD_PANNINGSLIDE;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_PANSLIDERIGHT:
+ command = CMD_PANNINGSLIDE;
+ param = vol;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_VIBRATOSPEED:
+ command = CMD_VIBRATO;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+
+ case VOLCMD_TONEPORTAMENTO:
+ command = CMD_TONEPORTAMENTO;
+ param = vol << 4;
+ volcmd = VOLCMD_NONE;
+ break;
+ }
+
+ switch(volcmd)
+ {
+ case VOLCMD_VOLSLIDEDOWN:
+ case VOLCMD_VOLSLIDEUP:
+ case VOLCMD_FINEVOLDOWN:
+ case VOLCMD_FINEVOLUP:
+ case VOLCMD_PORTADOWN:
+ case VOLCMD_PORTAUP:
+ case VOLCMD_TONEPORTAMENTO:
+ case VOLCMD_VIBRATODEPTH:
+ // OpenMPT-specific commands
+ case VOLCMD_OFFSET:
+ vol = std::min(vol, VOL(9));
+ break;
+ }
+ } // End if(newTypeIsIT_MPT)
+
+ // Fix volume column offset for formats that don't have it.
+ if(volcmd == VOLCMD_OFFSET && !newSpecs.HasVolCommand(VOLCMD_OFFSET) && (command == CMD_NONE || command == CMD_OFFSET || !newSpecs.HasCommand(command)))
+ {
+ const ModCommand::PARAM oldOffset = (command == CMD_OFFSET) ? param : 0;
+ command = CMD_OFFSET;
+ volcmd = VOLCMD_NONE;
+ SAMPLEINDEX smp = instr;
+ if(smp > 0 && smp <= sndFile.GetNumInstruments() && IsNote() && sndFile.Instruments[smp] != nullptr)
+ smp = sndFile.Instruments[smp]->Keyboard[note - NOTE_MIN];
+
+ if(smp > 0 && smp <= sndFile.GetNumSamples() && vol <= std::size(ModSample().cues))
+ {
+ const ModSample &sample = sndFile.GetSample(smp);
+ if(vol == 0)
+ param = mpt::saturate_cast<ModCommand::PARAM>(Util::muldivr_unsigned(sample.nLength, oldOffset, 65536u));
+ else
+ param = mpt::saturate_cast<ModCommand::PARAM>((sample.cues[vol - 1] + (oldOffset * 256u) + 128u) / 256u);
+ } else
+ {
+ param = vol << 3;
+ }
+ }
+
+ if((command == CMD_REVERSEOFFSET || command == CMD_OFFSETPERCENTAGE) && !newSpecs.HasCommand(command))
+ {
+ command = CMD_OFFSET;
+ }
+
+ if(!newSpecs.HasNote(note))
+ note = NOTE_NONE;
+
+ // ensure the commands really exist in this format
+ if(!newSpecs.HasCommand(command))
+ command = CMD_NONE;
+ if(!newSpecs.HasVolCommand(volcmd))
+ volcmd = VOLCMD_NONE;
+
+}
+
+
+bool ModCommand::IsContinousCommand(const CSoundFile &sndFile) const
+{
+ switch(command)
+ {
+ case CMD_ARPEGGIO:
+ case CMD_TONEPORTAMENTO:
+ case CMD_VIBRATO:
+ case CMD_TREMOLO:
+ case CMD_RETRIG:
+ case CMD_TREMOR:
+ case CMD_FINEVIBRATO:
+ case CMD_PANBRELLO:
+ case CMD_SMOOTHMIDI:
+ case CMD_NOTESLIDEUP:
+ case CMD_NOTESLIDEDOWN:
+ case CMD_NOTESLIDEUPRETRIG:
+ case CMD_NOTESLIDEDOWNRETRIG:
+ return true;
+ case CMD_PORTAMENTOUP:
+ case CMD_PORTAMENTODOWN:
+ if(!param && sndFile.GetType() == MOD_TYPE_MOD)
+ return false;
+ if(sndFile.GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_MT2 | MOD_TYPE_MED | MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_STP | MOD_TYPE_DTM))
+ return true;
+ if(param >= 0xF0)
+ return false;
+ if(param >= 0xE0 && sndFile.GetType() != MOD_TYPE_DBM)
+ return false;
+ return true;
+ case CMD_VOLUMESLIDE:
+ case CMD_TONEPORTAVOL:
+ case CMD_VIBRATOVOL:
+ case CMD_GLOBALVOLSLIDE:
+ case CMD_CHANNELVOLSLIDE:
+ case CMD_PANNINGSLIDE:
+ if(!param && sndFile.GetType() == MOD_TYPE_MOD)
+ return false;
+ if(sndFile.GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_AMF0 | MOD_TYPE_MED | MOD_TYPE_DIGI))
+ return true;
+ if((param & 0xF0) == 0xF0 && (param & 0x0F))
+ return false;
+ if((param & 0x0F) == 0x0F && (param & 0xF0))
+ return false;
+ return true;
+ case CMD_TEMPO:
+ return (param < 0x20);
+ default:
+ return false;
+ }
+}
+
+
+bool ModCommand::IsContinousVolColCommand() const
+{
+ switch(volcmd)
+ {
+ case VOLCMD_VOLSLIDEUP:
+ case VOLCMD_VOLSLIDEDOWN:
+ case VOLCMD_VIBRATOSPEED:
+ case VOLCMD_VIBRATODEPTH:
+ case VOLCMD_PANSLIDELEFT:
+ case VOLCMD_PANSLIDERIGHT:
+ case VOLCMD_TONEPORTAMENTO:
+ case VOLCMD_PORTAUP:
+ case VOLCMD_PORTADOWN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+bool ModCommand::IsSlideUpDownCommand() const
+{
+ switch(command)
+ {
+ case CMD_VOLUMESLIDE:
+ case CMD_TONEPORTAVOL:
+ case CMD_VIBRATOVOL:
+ case CMD_GLOBALVOLSLIDE:
+ case CMD_CHANNELVOLSLIDE:
+ case CMD_PANNINGSLIDE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+
+bool ModCommand::IsGlobalCommand(COMMAND command, PARAM param)
+{
+ switch(command)
+ {
+ case CMD_POSITIONJUMP:
+ case CMD_PATTERNBREAK:
+ case CMD_SPEED:
+ case CMD_TEMPO:
+ case CMD_GLOBALVOLUME:
+ case CMD_GLOBALVOLSLIDE:
+ case CMD_MIDI:
+ case CMD_SMOOTHMIDI:
+ case CMD_DBMECHO:
+ return true;
+ case CMD_MODCMDEX:
+ switch(param & 0xF0)
+ {
+ case 0x00: // LED Filter
+ case 0x60: // Pattern Loop
+ case 0xE0: // Row Delay
+ return true;
+ default:
+ return false;
+ }
+ case CMD_XFINEPORTAUPDOWN:
+ case CMD_S3MCMDEX:
+ switch(param & 0xF0)
+ {
+ case 0x60: // Tick Delay
+ case 0x90: // Sound Control
+ case 0xB0: // Pattern Loop
+ case 0xE0: // Row Delay
+ return true;
+ default:
+ return false;
+ }
+
+ default:
+ return false;
+ }
+}
+
+// "Importance" of every FX command. Table is used for importing from formats with multiple effect colums
+// and is approximately the same as in SchismTracker.
+size_t ModCommand::GetEffectWeight(COMMAND cmd)
+{
+ // Effect weights, sorted from lowest to highest weight.
+ static constexpr COMMAND weights[] =
+ {
+ CMD_NONE,
+ CMD_DUMMY,
+ CMD_XPARAM,
+ CMD_SETENVPOSITION,
+ CMD_KEYOFF,
+ CMD_TREMOLO,
+ CMD_FINEVIBRATO,
+ CMD_VIBRATO,
+ CMD_XFINEPORTAUPDOWN,
+ CMD_FINETUNE,
+ CMD_FINETUNE_SMOOTH,
+ CMD_PANBRELLO,
+ CMD_S3MCMDEX,
+ CMD_MODCMDEX,
+ CMD_DELAYCUT,
+ CMD_MIDI,
+ CMD_SMOOTHMIDI,
+ CMD_PANNINGSLIDE,
+ CMD_PANNING8,
+ CMD_NOTESLIDEUPRETRIG,
+ CMD_NOTESLIDEUP,
+ CMD_NOTESLIDEDOWNRETRIG,
+ CMD_NOTESLIDEDOWN,
+ CMD_PORTAMENTOUP,
+ CMD_PORTAMENTODOWN,
+ CMD_VOLUMESLIDE,
+ CMD_VIBRATOVOL,
+ CMD_VOLUME,
+ CMD_DIGIREVERSESAMPLE,
+ CMD_REVERSEOFFSET,
+ CMD_OFFSETPERCENTAGE,
+ CMD_OFFSET,
+ CMD_TREMOR,
+ CMD_RETRIG,
+ CMD_ARPEGGIO,
+ CMD_TONEPORTAMENTO,
+ CMD_TONEPORTAVOL,
+ CMD_DBMECHO,
+ CMD_GLOBALVOLSLIDE,
+ CMD_CHANNELVOLUME,
+ CMD_GLOBALVOLSLIDE,
+ CMD_GLOBALVOLUME,
+ CMD_TEMPO,
+ CMD_SPEED,
+ CMD_POSITIONJUMP,
+ CMD_PATTERNBREAK,
+ };
+ static_assert(std::size(weights) == MAX_EFFECTS);
+
+ for(size_t i = 0; i < std::size(weights); i++)
+ {
+ if(weights[i] == cmd)
+ {
+ return i;
+ }
+ }
+ // Invalid / unknown command.
+ return 0;
+}
+
+
+// Try to convert a fx column command (&effect) into a volume column command.
+// Returns true if successful.
+// Some commands can only be converted by losing some precision.
+// If moving the command into the volume column is more important than accuracy, use force = true.
+// (Code translated from SchismTracker and mainly supposed to be used with loaders ported from this tracker)
+bool ModCommand::ConvertVolEffect(uint8 &effect, uint8 &param, bool force)
+{
+ switch(effect)
+ {
+ case CMD_NONE:
+ effect = VOLCMD_NONE;
+ return true;
+ case CMD_VOLUME:
+ effect = VOLCMD_VOLUME;
+ param = std::min(param, PARAM(64));
+ break;
+ case CMD_PORTAMENTOUP:
+ // if not force, reject when dividing causes loss of data in LSB, or if the final value is too
+ // large to fit. (volume column Ex/Fx are four times stronger than effect column)
+ if(!force && ((param & 3) || param >= 0xE0))
+ return false;
+ param /= 4;
+ effect = VOLCMD_PORTAUP;
+ break;
+ case CMD_PORTAMENTODOWN:
+ if(!force && ((param & 3) || param >= 0xE0))
+ return false;
+ param /= 4;
+ effect = VOLCMD_PORTADOWN;
+ break;
+ case CMD_TONEPORTAMENTO:
+ if(param >= 0xF0)
+ {
+ // hack for people who can't type F twice :)
+ effect = VOLCMD_TONEPORTAMENTO;
+ param = 9;
+ return true;
+ }
+ for(uint8 n = 0; n < 10; n++)
+ {
+ if(force
+ ? (param <= ImpulseTrackerPortaVolCmd[n])
+ : (param == ImpulseTrackerPortaVolCmd[n]))
+ {
+ effect = VOLCMD_TONEPORTAMENTO;
+ param = n;
+ return true;
+ }
+ }
+ return false;
+ case CMD_VIBRATO:
+ if(force)
+ param = std::min(static_cast<PARAM>(param & 0x0F), PARAM(9));
+ else if((param & 0x0F) > 9 || (param & 0xF0) != 0)
+ return false;
+ param &= 0x0F;
+ effect = VOLCMD_VIBRATODEPTH;
+ break;
+ case CMD_FINEVIBRATO:
+ if(force)
+ param = 0;
+ else if(param)
+ return false;
+ effect = VOLCMD_VIBRATODEPTH;
+ break;
+ case CMD_PANNING8:
+ if(param == 255)
+ param = 64;
+ else
+ param /= 4;
+ effect = VOLCMD_PANNING;
+ break;
+ case CMD_VOLUMESLIDE:
+ if(param == 0)
+ return false;
+ if((param & 0xF) == 0) // Dx0 / Cx
+ {
+ param >>= 4;
+ effect = VOLCMD_VOLSLIDEUP;
+ } else if((param & 0xF0) == 0) // D0x / Dx
+ {
+ effect = VOLCMD_VOLSLIDEDOWN;
+ } else if((param & 0xF) == 0xF) // DxF / Ax
+ {
+ param >>= 4;
+ effect = VOLCMD_FINEVOLUP;
+ } else if((param & 0xF0) == 0xF0) // DFx / Bx
+ {
+ param &= 0xF;
+ effect = VOLCMD_FINEVOLDOWN;
+ } else // ???
+ {
+ return false;
+ }
+ break;
+ case CMD_S3MCMDEX:
+ switch (param >> 4)
+ {
+ case 8:
+ effect = VOLCMD_PANNING;
+ param = ((param & 0xF) << 2) + 2;
+ return true;
+ case 0: case 1: case 2: case 0xF:
+ if(force)
+ {
+ effect = param = 0;
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ return false;
+ default:
+ return false;
+ }
+ return true;
+}
+
+// Try to combine two commands into one. Returns true on success and the combined command is placed in eff1 / param1.
+bool ModCommand::CombineEffects(uint8 &eff1, uint8 &param1, uint8 &eff2, uint8 &param2)
+{
+ if(eff1 == CMD_VOLUMESLIDE && (eff2 == CMD_VIBRATO || eff2 == CMD_TONEPORTAVOL) && param2 == 0)
+ {
+ // Merge commands
+ if(eff2 == CMD_VIBRATO)
+ {
+ eff1 = CMD_VIBRATOVOL;
+ } else
+ {
+ eff1 = CMD_TONEPORTAVOL;
+ }
+ eff2 = CMD_NONE;
+ return true;
+ } else if(eff2 == CMD_VOLUMESLIDE && (eff1 == CMD_VIBRATO || eff1 == CMD_TONEPORTAVOL) && param1 == 0)
+ {
+ // Merge commands
+ if(eff1 == CMD_VIBRATO)
+ {
+ eff1 = CMD_VIBRATOVOL;
+ } else
+ {
+ eff1 = CMD_TONEPORTAVOL;
+ }
+ param1 = param2;
+ eff2 = CMD_NONE;
+ return true;
+ } else if(eff1 == CMD_OFFSET && eff2 == CMD_S3MCMDEX && param2 == 0x9F)
+ {
+ // Reverse offset
+ eff1 = CMD_REVERSEOFFSET;
+ eff2 = CMD_NONE;
+ return true;
+ } else if(eff1 == CMD_S3MCMDEX && param1 == 0x9F && eff2 == CMD_OFFSET)
+ {
+ // Reverse offset
+ eff1 = CMD_REVERSEOFFSET;
+ param1 = param2;
+ eff2 = CMD_NONE;
+ return true;
+ } else
+ {
+ return false;
+ }
+}
+
+
+std::pair<EffectCommand, ModCommand::PARAM> ModCommand::TwoRegularCommandsToMPT(uint8 &effect1, uint8 &param1, uint8 &effect2, uint8 &param2)
+{
+ for(uint8 n = 0; n < 4; n++)
+ {
+ if(ModCommand::ConvertVolEffect(effect1, param1, (n > 1)))
+ {
+ return {CMD_NONE, ModCommand::PARAM(0)};
+ }
+ std::swap(effect1, effect2);
+ std::swap(param1, param2);
+ }
+
+ // Can only keep one command :(
+ if(GetEffectWeight(static_cast<COMMAND>(effect1)) > GetEffectWeight(static_cast<COMMAND>(effect2)))
+ {
+ std::swap(effect1, effect2);
+ std::swap(param1, param2);
+ }
+ std::pair<EffectCommand, PARAM> lostCommand = {static_cast<EffectCommand>(effect1), param1};
+ effect1 = VOLCMD_NONE;
+ param1 = 0;
+ return lostCommand;
+}
+
+
+OPENMPT_NAMESPACE_END