Bundle libOpenMPT as a dynamic framework, which should be safe once again, now that there is only one version to bundle. Also, now it is using the versions of libvorbisfile and libmpg123 that are bundled with the player, instead of compiling minimp3 and stbvorbis. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
1279 lines
31 KiB
C++
1279 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
|