1280 lines
31 KiB
C++
1280 lines
31 KiB
C++
/*
|
|
* 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 ¶m, 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 ¶m1, uint8 &eff2, uint8 ¶m2)
|
|
{
|
|
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 ¶m1, uint8 &effect2, uint8 ¶m2)
|
|
{
|
|
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
|