676 lines
20 KiB
C++
676 lines
20 KiB
C++
/*
|
|
* ModConvert.cpp
|
|
* --------------
|
|
* Purpose: Converting between various module formats.
|
|
* Notes : Incomplete list of MPTm-only features and extensions in the old formats:
|
|
* Features only available for MPTm:
|
|
* - User definable tunings.
|
|
* - Extended pattern range
|
|
* - Extended sequence
|
|
* - Multiple sequences ("songs")
|
|
* - Pattern-specific time signatures
|
|
* - Pattern effects :xy, S7D, S7E
|
|
* - Long instrument envelopes
|
|
* - Envelope release node (this was previously also usable in the IT format, but is now deprecated in that format)
|
|
* - Fractional tempo
|
|
* - Song-specific resampling
|
|
* - Alternative tempo modes (only usable in legacy XM / IT files)
|
|
*
|
|
* Extended features in IT/XM/S3M (not all listed below are available in all of those formats):
|
|
* - Plugins
|
|
* - Extended ranges for
|
|
* - Sample count
|
|
* - Instrument count
|
|
* - Pattern count
|
|
* - Sequence size
|
|
* - Row count
|
|
* - Channel count
|
|
* - Tempo limits
|
|
* - Extended sample/instrument properties.
|
|
* - MIDI mapping directives
|
|
* - Version info
|
|
* - Channel names
|
|
* - Pattern names
|
|
* - For more info, see e.g. SaveExtendedSongProperties(), SaveExtendedInstrumentProperties()
|
|
* Authors: OpenMPT Devs
|
|
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
|
|
*/
|
|
|
|
|
|
#include "stdafx.h"
|
|
#include "Moddoc.h"
|
|
#include "Mainfrm.h"
|
|
#include "InputHandler.h"
|
|
#include "../tracklib/SampleEdit.h"
|
|
#include "../soundlib/modsmp_ctrl.h"
|
|
#include "../soundlib/mod_specifications.h"
|
|
#include "ModConvert.h"
|
|
|
|
|
|
OPENMPT_NAMESPACE_BEGIN
|
|
|
|
|
|
// Trim envelopes and remove release nodes.
|
|
static void UpdateEnvelopes(InstrumentEnvelope &mptEnv, const CModSpecifications &specs, std::bitset<wNumWarnings> &warnings)
|
|
{
|
|
// shorten instrument envelope if necessary (for mod conversion)
|
|
const uint8 envMax = specs.envelopePointsMax;
|
|
|
|
#define TRIMENV(envLen) if(envLen >= envMax) { envLen = envMax - 1; warnings.set(wTrimmedEnvelopes); }
|
|
|
|
if(mptEnv.size() > envMax)
|
|
{
|
|
mptEnv.resize(envMax);
|
|
warnings.set(wTrimmedEnvelopes);
|
|
}
|
|
TRIMENV(mptEnv.nLoopStart);
|
|
TRIMENV(mptEnv.nLoopEnd);
|
|
TRIMENV(mptEnv.nSustainStart);
|
|
TRIMENV(mptEnv.nSustainEnd);
|
|
if(mptEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET)
|
|
{
|
|
if(specs.hasReleaseNode)
|
|
{
|
|
TRIMENV(mptEnv.nReleaseNode);
|
|
} else
|
|
{
|
|
mptEnv.nReleaseNode = ENV_RELEASE_NODE_UNSET;
|
|
warnings.set(wReleaseNode);
|
|
}
|
|
}
|
|
|
|
#undef TRIMENV
|
|
}
|
|
|
|
|
|
bool CModDoc::ChangeModType(MODTYPE nNewType)
|
|
{
|
|
std::bitset<wNumWarnings> warnings;
|
|
warnings.reset();
|
|
PATTERNINDEX nResizedPatterns = 0;
|
|
|
|
const MODTYPE nOldType = m_SndFile.GetType();
|
|
|
|
if(nNewType == nOldType)
|
|
return true;
|
|
|
|
const bool oldTypeIsXM = (nOldType == MOD_TYPE_XM),
|
|
oldTypeIsS3M = (nOldType == MOD_TYPE_S3M), oldTypeIsIT = (nOldType == MOD_TYPE_IT),
|
|
oldTypeIsMPT = (nOldType == MOD_TYPE_MPT),
|
|
oldTypeIsS3M_IT_MPT = (oldTypeIsS3M || oldTypeIsIT || oldTypeIsMPT),
|
|
oldTypeIsIT_MPT = (oldTypeIsIT || oldTypeIsMPT);
|
|
|
|
const bool newTypeIsMOD = (nNewType == MOD_TYPE_MOD), newTypeIsXM = (nNewType == MOD_TYPE_XM),
|
|
newTypeIsS3M = (nNewType == MOD_TYPE_S3M), newTypeIsIT = (nNewType == MOD_TYPE_IT),
|
|
newTypeIsMPT = (nNewType == MOD_TYPE_MPT), newTypeIsMOD_XM = (newTypeIsMOD || newTypeIsXM),
|
|
newTypeIsIT_MPT = (newTypeIsIT || newTypeIsMPT);
|
|
|
|
const CModSpecifications &specs = m_SndFile.GetModSpecifications(nNewType);
|
|
|
|
// Check if conversion to 64 rows is necessary
|
|
for(const auto &pat : m_SndFile.Patterns)
|
|
{
|
|
if(pat.IsValid() && pat.GetNumRows() != 64)
|
|
nResizedPatterns++;
|
|
}
|
|
|
|
if((m_SndFile.GetNumInstruments() || nResizedPatterns) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
|
|
{
|
|
if(Reporting::Confirm(
|
|
"This operation will convert all instruments to samples,\n"
|
|
"and resize all patterns to 64 rows.\n"
|
|
"Do you want to continue?", "Warning") != cnfYes) return false;
|
|
BeginWaitCursor();
|
|
CriticalSection cs;
|
|
|
|
// Converting instruments to samples
|
|
if(m_SndFile.GetNumInstruments())
|
|
{
|
|
ConvertInstrumentsToSamples();
|
|
warnings.set(wInstrumentsToSamples);
|
|
}
|
|
|
|
// Resizing all patterns to 64 rows
|
|
for(auto &pat : m_SndFile.Patterns) if(pat.IsValid() && pat.GetNumRows() != 64)
|
|
{
|
|
ROWINDEX origRows = pat.GetNumRows();
|
|
pat.Resize(64);
|
|
|
|
if(origRows < 64)
|
|
{
|
|
// Try to save short patterns by inserting a pattern break.
|
|
pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(origRows - 1).RetryNextRow());
|
|
}
|
|
|
|
warnings.set(wResizedPatterns);
|
|
}
|
|
|
|
// Removing all instrument headers from channels
|
|
for(auto &chn : m_SndFile.m_PlayState.Chn)
|
|
{
|
|
chn.pModInstrument = nullptr;
|
|
}
|
|
|
|
for(INSTRUMENTINDEX nIns = 0; nIns <= m_SndFile.GetNumInstruments(); nIns++)
|
|
{
|
|
delete m_SndFile.Instruments[nIns];
|
|
m_SndFile.Instruments[nIns] = nullptr;
|
|
}
|
|
m_SndFile.m_nInstruments = 0;
|
|
|
|
EndWaitCursor();
|
|
} //End if (((m_SndFile.m_nInstruments) || (b64)) && (nNewType & (MOD_TYPE_MOD|MOD_TYPE_S3M)))
|
|
BeginWaitCursor();
|
|
|
|
|
|
/////////////////////////////
|
|
// Converting pattern data
|
|
|
|
// When converting to MOD, get the new sample transpose setting right here so that we can compensate notes in the pattern.
|
|
if(newTypeIsMOD && !oldTypeIsXM)
|
|
{
|
|
for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
|
|
{
|
|
m_SndFile.GetSample(smp).FrequencyToTranspose();
|
|
}
|
|
}
|
|
|
|
bool onlyAmigaNotes = true;
|
|
for(auto &pat : m_SndFile.Patterns) if(pat.IsValid())
|
|
{
|
|
// This is used for -> MOD/XM conversion
|
|
std::vector<std::array<ModCommand::PARAM, MAX_EFFECTS>> effMemory(GetNumChannels());
|
|
std::vector<ModCommand::VOL> volMemory(GetNumChannels(), 0);
|
|
std::vector<ModCommand::INSTR> instrMemory(GetNumChannels(), 0);
|
|
|
|
bool addBreak = false; // When converting to XM, avoid the E60 bug.
|
|
CHANNELINDEX chn = 0;
|
|
ROWINDEX row = 0;
|
|
|
|
for(auto m = pat.begin(); m != pat.end(); m++, chn++)
|
|
{
|
|
if(chn >= GetNumChannels())
|
|
{
|
|
chn = 0;
|
|
row++;
|
|
}
|
|
|
|
ModCommand::INSTR instr = m->instr;
|
|
if(m->instr) instrMemory[chn] = instr;
|
|
else instr = instrMemory[chn];
|
|
|
|
// Deal with volume column slide memory (it's not shared with the effect column)
|
|
if(oldTypeIsIT_MPT && (newTypeIsMOD_XM || newTypeIsS3M))
|
|
{
|
|
switch(m->volcmd)
|
|
{
|
|
case VOLCMD_VOLSLIDEUP:
|
|
case VOLCMD_VOLSLIDEDOWN:
|
|
case VOLCMD_FINEVOLUP:
|
|
case VOLCMD_FINEVOLDOWN:
|
|
if(m->vol == 0)
|
|
m->vol = volMemory[chn];
|
|
else
|
|
volMemory[chn] = m->vol;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Deal with MOD/XM commands without effect memory
|
|
if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM)
|
|
{
|
|
switch(m->command)
|
|
{
|
|
// No effect memory in XM / MOD
|
|
case CMD_ARPEGGIO:
|
|
case CMD_S3MCMDEX:
|
|
case CMD_MODCMDEX:
|
|
|
|
// These have effect memory in XM, but it is spread over several commands (for fine and extra-fine slides), so the easiest way to fix this is to just always use the previous value.
|
|
case CMD_PORTAMENTOUP:
|
|
case CMD_PORTAMENTODOWN:
|
|
case CMD_VOLUMESLIDE:
|
|
if(m->param == 0)
|
|
m->param = effMemory[chn][m->command];
|
|
else
|
|
effMemory[chn][m->command] = m->param;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Adjust effect memory for MOD files
|
|
if(newTypeIsMOD)
|
|
{
|
|
switch(m->command)
|
|
{
|
|
case CMD_PORTAMENTOUP:
|
|
case CMD_PORTAMENTODOWN:
|
|
case CMD_TONEPORTAVOL:
|
|
case CMD_VIBRATOVOL:
|
|
case CMD_VOLUMESLIDE:
|
|
// ProTracker doesn't have effect memory for these commands, so let's try to fix them
|
|
if(m->param == 0)
|
|
m->param = effMemory[chn][m->command];
|
|
else
|
|
effMemory[chn][m->command] = m->param;
|
|
break;
|
|
|
|
}
|
|
|
|
// Compensate for loss of transpose information
|
|
if(m->IsNote() && instr && instr <= GetNumSamples())
|
|
{
|
|
const int newNote = m->note + m_SndFile.GetSample(instr).RelativeTone;
|
|
m->note = static_cast<ModCommand::NOTE>(Clamp(newNote, specs.noteMin, specs.noteMax));
|
|
}
|
|
if(!m->IsAmigaNote())
|
|
{
|
|
onlyAmigaNotes = false;
|
|
}
|
|
}
|
|
|
|
m->Convert(nOldType, nNewType, m_SndFile);
|
|
|
|
// When converting to XM, avoid the E60 bug.
|
|
if(newTypeIsXM)
|
|
{
|
|
switch(m->command)
|
|
{
|
|
case CMD_MODCMDEX:
|
|
if(m->param == 0x60 && row > 0)
|
|
{
|
|
addBreak = true;
|
|
}
|
|
break;
|
|
case CMD_POSITIONJUMP:
|
|
case CMD_PATTERNBREAK:
|
|
addBreak = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fix Row Delay commands when converting between MOD/XM and S3M/IT.
|
|
// FT2 only considers the rightmost command, ST3/IT only the leftmost...
|
|
if((nOldType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) && (nNewType & (MOD_TYPE_MOD | MOD_TYPE_XM))
|
|
&& m->command == CMD_MODCMDEX && (m->param & 0xF0) == 0xE0)
|
|
{
|
|
if(oldTypeIsIT_MPT || m->param != 0xE0)
|
|
{
|
|
// If the leftmost row delay command is SE0, ST3 ignores it, IT doesn't.
|
|
|
|
// Delete all commands right of the first command
|
|
auto p = m + 1;
|
|
for(CHANNELINDEX c = chn + 1; c < m_SndFile.GetNumChannels(); c++, p++)
|
|
{
|
|
if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
|
|
{
|
|
p->command = CMD_NONE;
|
|
}
|
|
}
|
|
}
|
|
} else if((nOldType & (MOD_TYPE_MOD | MOD_TYPE_XM)) && (nNewType & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT))
|
|
&& m->command == CMD_S3MCMDEX && (m->param & 0xF0) == 0xE0)
|
|
{
|
|
// Delete all commands left of the last command
|
|
auto p = m - 1;
|
|
for(CHANNELINDEX c = 0; c < chn; c++, p--)
|
|
{
|
|
if(p->command == CMD_S3MCMDEX && (p->param & 0xF0) == 0xE0)
|
|
{
|
|
p->command = CMD_NONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
if(addBreak)
|
|
{
|
|
pat.WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(pat.GetNumRows() - 1));
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////
|
|
// Converting instrument / sample / etc. data
|
|
|
|
|
|
// Do some sample conversion
|
|
const bool newTypeHasPingPongLoops = !(newTypeIsMOD || newTypeIsS3M);
|
|
for(SAMPLEINDEX smp = 1; smp <= m_SndFile.GetNumSamples(); smp++)
|
|
{
|
|
ModSample &sample = m_SndFile.GetSample(smp);
|
|
GetSampleUndo().PrepareUndo(smp, sundo_none, "Song Conversion");
|
|
|
|
// Too many samples? Only 31 samples allowed in MOD format...
|
|
if(newTypeIsMOD && smp > 31 && sample.nLength > 0)
|
|
{
|
|
warnings.set(wMOD31Samples);
|
|
}
|
|
|
|
// No auto-vibrato in MOD/S3M
|
|
if((newTypeIsMOD || newTypeIsS3M) && (sample.nVibDepth | sample.nVibRate | sample.nVibSweep) != 0)
|
|
{
|
|
warnings.set(wSampleAutoVibrato);
|
|
}
|
|
|
|
// No sustain loops for MOD/S3M/XM
|
|
bool ignoreLoopConversion = false;
|
|
if(newTypeIsMOD_XM || newTypeIsS3M)
|
|
{
|
|
// Sustain loops - convert to normal loops
|
|
if(sample.uFlags[CHN_SUSTAINLOOP])
|
|
{
|
|
warnings.set(wSampleSustainLoops);
|
|
// Prepare conversion to regular loop
|
|
if(!newTypeHasPingPongLoops)
|
|
{
|
|
ignoreLoopConversion = true;
|
|
if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, true))
|
|
warnings.set(wSampleBidiLoops);
|
|
}
|
|
}
|
|
}
|
|
|
|
// No ping-pong loops in MOD/S3M
|
|
if(!ignoreLoopConversion && !newTypeHasPingPongLoops && sample.HasPingPongLoop())
|
|
{
|
|
if(!SampleEdit::ConvertPingPongLoop(sample, m_SndFile, false))
|
|
warnings.set(wSampleBidiLoops);
|
|
}
|
|
|
|
if(newTypeIsMOD && sample.RelativeTone != 0)
|
|
{
|
|
warnings.set(wMODSampleFrequency);
|
|
}
|
|
|
|
if(!CSoundFile::SupportsOPL(nNewType) && sample.uFlags[CHN_ADLIB])
|
|
{
|
|
warnings.set(wAdlibInstruments);
|
|
}
|
|
|
|
sample.Convert(nOldType, nNewType);
|
|
}
|
|
|
|
for(INSTRUMENTINDEX ins = 1; ins <= m_SndFile.GetNumInstruments(); ins++)
|
|
{
|
|
ModInstrument *pIns = m_SndFile.Instruments[ins];
|
|
if(pIns == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Convert IT/MPT to XM (fix instruments)
|
|
if(newTypeIsXM)
|
|
{
|
|
for(size_t i = 0; i < std::size(pIns->NoteMap); i++)
|
|
{
|
|
if (pIns->NoteMap[i] && pIns->NoteMap[i] != (i + 1))
|
|
{
|
|
warnings.set(wBrokenNoteMap);
|
|
break;
|
|
}
|
|
}
|
|
// Convert sustain loops to sustain "points"
|
|
if(pIns->VolEnv.nSustainStart != pIns->VolEnv.nSustainEnd)
|
|
{
|
|
warnings.set(wInstrumentSustainLoops);
|
|
}
|
|
if(pIns->PanEnv.nSustainStart != pIns->PanEnv.nSustainEnd)
|
|
{
|
|
warnings.set(wInstrumentSustainLoops);
|
|
}
|
|
}
|
|
|
|
|
|
// Convert MPT to anything - remove instrument tunings, Pitch/Tempo Lock, filter variation
|
|
if(oldTypeIsMPT)
|
|
{
|
|
if(pIns->pTuning != nullptr)
|
|
{
|
|
warnings.set(wInstrumentTuning);
|
|
}
|
|
if(pIns->pitchToTempoLock.GetRaw() != 0)
|
|
{
|
|
warnings.set(wPitchToTempoLock);
|
|
}
|
|
if((pIns->nCutSwing | pIns->nResSwing) != 0)
|
|
{
|
|
warnings.set(wFilterVariation);
|
|
}
|
|
}
|
|
|
|
pIns->Convert(nOldType, nNewType);
|
|
}
|
|
|
|
if(newTypeIsMOD)
|
|
{
|
|
// Not supported in MOD format
|
|
auto firstPat = std::find_if(m_SndFile.Order().cbegin(), m_SndFile.Order().cend(), [this](PATTERNINDEX pat) { return m_SndFile.Patterns.IsValidPat(pat); });
|
|
bool firstPatValid = firstPat != m_SndFile.Order().cend();
|
|
bool lossy = false;
|
|
|
|
if(m_SndFile.m_nDefaultSpeed != 6)
|
|
{
|
|
if(firstPatValid)
|
|
{
|
|
m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_SPEED, ModCommand::PARAM(m_SndFile.m_nDefaultSpeed)).RetryNextRow());
|
|
}
|
|
m_SndFile.m_nDefaultSpeed = 6;
|
|
lossy = true;
|
|
}
|
|
if(m_SndFile.m_nDefaultTempo != TEMPO(125, 0))
|
|
{
|
|
if(firstPatValid)
|
|
{
|
|
m_SndFile.Patterns[*firstPat].WriteEffect(EffectWriter(CMD_TEMPO, ModCommand::PARAM(m_SndFile.m_nDefaultTempo.GetInt())).RetryNextRow());
|
|
}
|
|
m_SndFile.m_nDefaultTempo.Set(125);
|
|
lossy = true;
|
|
}
|
|
if(m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME || m_SndFile.m_nSamplePreAmp != 48 || m_SndFile.m_nVSTiVolume != 48)
|
|
{
|
|
m_SndFile.m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME;
|
|
m_SndFile.m_nSamplePreAmp = 48;
|
|
m_SndFile.m_nVSTiVolume = 48;
|
|
lossy = true;
|
|
}
|
|
if(lossy)
|
|
{
|
|
warnings.set(wMODGlobalVars);
|
|
}
|
|
}
|
|
|
|
// Is the "restart position" value allowed in this format?
|
|
for(SEQUENCEINDEX seq = 0; seq < m_SndFile.Order.GetNumSequences(); seq++)
|
|
{
|
|
if(m_SndFile.Order(seq).GetRestartPos() > 0 && !specs.hasRestartPos)
|
|
{
|
|
// Try to fix it by placing a pattern jump command in the pattern.
|
|
if(!m_SndFile.Order.RestartPosToPattern(seq))
|
|
{
|
|
// Couldn't fix it! :(
|
|
warnings.set(wRestartPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fix channel settings (pan/vol)
|
|
for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++)
|
|
{
|
|
if(newTypeIsMOD_XM || newTypeIsS3M)
|
|
{
|
|
if(m_SndFile.ChnSettings[nChn].nVolume != 64 || m_SndFile.ChnSettings[nChn].dwFlags[CHN_SURROUND])
|
|
{
|
|
m_SndFile.ChnSettings[nChn].nVolume = 64;
|
|
m_SndFile.ChnSettings[nChn].dwFlags.reset(CHN_SURROUND);
|
|
warnings.set(wChannelVolSurround);
|
|
}
|
|
}
|
|
if(newTypeIsXM)
|
|
{
|
|
if(m_SndFile.ChnSettings[nChn].nPan != 128)
|
|
{
|
|
m_SndFile.ChnSettings[nChn].nPan = 128;
|
|
warnings.set(wChannelPanning);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for patterns with custom time signatures (fixing will be applied in the pattern container)
|
|
if(!specs.hasPatternSignatures)
|
|
{
|
|
for(const auto &pat: m_SndFile.Patterns)
|
|
{
|
|
if(pat.GetOverrideSignature())
|
|
{
|
|
warnings.set(wPatternSignatures);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check whether the new format supports embedding the edit history in the file.
|
|
if(oldTypeIsIT_MPT && !newTypeIsIT_MPT && GetSoundFile().GetFileHistory().size() > 0)
|
|
{
|
|
warnings.set(wEditHistory);
|
|
}
|
|
|
|
if((nOldType & MOD_TYPE_XM) && m_SndFile.m_playBehaviour[kFT2VolumeRamping])
|
|
{
|
|
warnings.set(wVolRamp);
|
|
}
|
|
|
|
CriticalSection cs;
|
|
m_SndFile.ChangeModTypeTo(nNewType);
|
|
|
|
// In case we need to update IT bidi loop handling pre-computation or loops got changed...
|
|
m_SndFile.PrecomputeSampleLoops(false);
|
|
|
|
// Song flags
|
|
if(!(specs.songFlags & SONG_LINEARSLIDES) && m_SndFile.m_SongFlags[SONG_LINEARSLIDES])
|
|
{
|
|
warnings.set(wLinearSlides);
|
|
}
|
|
if(oldTypeIsXM && newTypeIsIT_MPT)
|
|
{
|
|
m_SndFile.m_SongFlags.set(SONG_ITCOMPATGXX);
|
|
} else if(newTypeIsMOD && GetNumChannels() == 4 && onlyAmigaNotes)
|
|
{
|
|
m_SndFile.m_SongFlags.set(SONG_ISAMIGA);
|
|
m_SndFile.InitAmigaResampler();
|
|
}
|
|
m_SndFile.m_SongFlags &= (specs.songFlags | SONG_PLAY_FLAGS);
|
|
|
|
// Adjust mix levels
|
|
if(newTypeIsMOD || newTypeIsS3M)
|
|
{
|
|
m_SndFile.SetMixLevels(MixLevels::Compatible);
|
|
}
|
|
if(oldTypeIsMPT && m_SndFile.GetMixLevels() != MixLevels::Compatible && m_SndFile.GetMixLevels() != MixLevels::CompatibleFT2)
|
|
{
|
|
warnings.set(wMixmode);
|
|
}
|
|
|
|
if(!specs.hasFractionalTempo && m_SndFile.m_nDefaultTempo.GetFract() != 0)
|
|
{
|
|
m_SndFile.m_nDefaultTempo.Set(m_SndFile.m_nDefaultTempo.GetInt(), 0);
|
|
warnings.set(wFractionalTempo);
|
|
}
|
|
|
|
ChangeFileExtension(nNewType);
|
|
|
|
// Check mod specifications
|
|
Limit(m_SndFile.m_nDefaultTempo, specs.GetTempoMin(), specs.GetTempoMax());
|
|
Limit(m_SndFile.m_nDefaultSpeed, specs.speedMin, specs.speedMax);
|
|
|
|
for(INSTRUMENTINDEX i = 1; i <= m_SndFile.GetNumInstruments(); i++) if(m_SndFile.Instruments[i] != nullptr)
|
|
{
|
|
UpdateEnvelopes(m_SndFile.Instruments[i]->VolEnv, specs, warnings);
|
|
UpdateEnvelopes(m_SndFile.Instruments[i]->PanEnv, specs, warnings);
|
|
UpdateEnvelopes(m_SndFile.Instruments[i]->PitchEnv, specs, warnings);
|
|
}
|
|
|
|
// XM requires instruments, so we create them right away.
|
|
if(newTypeIsXM && GetNumInstruments() == 0)
|
|
{
|
|
ConvertSamplesToInstruments();
|
|
}
|
|
|
|
// XM has no global volume
|
|
if(newTypeIsXM && m_SndFile.m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME)
|
|
{
|
|
if(!GlobalVolumeToPattern())
|
|
{
|
|
warnings.set(wGlobalVolumeNotSupported);
|
|
}
|
|
}
|
|
|
|
// Resampling is only saved in MPTM
|
|
if(!newTypeIsMPT && m_SndFile.m_nResampling != SRCMODE_DEFAULT)
|
|
{
|
|
warnings.set(wResamplingMode);
|
|
m_SndFile.m_nResampling = SRCMODE_DEFAULT;
|
|
}
|
|
|
|
cs.Leave();
|
|
|
|
if(warnings[wResizedPatterns])
|
|
{
|
|
AddToLog(LogInformation, MPT_UFORMAT("{} patterns have been resized to 64 rows")(nResizedPatterns));
|
|
}
|
|
static constexpr struct
|
|
{
|
|
ConversionWarning warning;
|
|
const char *mesage;
|
|
} messages[] =
|
|
{
|
|
// Pattern warnings
|
|
{ wRestartPos, "Restart position is not supported by the new format." },
|
|
{ wPatternSignatures, "Pattern-specific time signatures are not supported by the new format." },
|
|
{ wChannelVolSurround, "Channel volume and surround are not supported by the new format." },
|
|
{ wChannelPanning, "Channel panning is not supported by the new format." },
|
|
// Sample warnings
|
|
{ wSampleBidiLoops, "Sample bidi loops are not supported by the new format." },
|
|
{ wSampleSustainLoops, "New format doesn't support sample sustain loops." },
|
|
{ wSampleAutoVibrato, "New format doesn't support sample autovibrato." },
|
|
{ wMODSampleFrequency, "Sample C-5 frequencies will be lost." },
|
|
{ wMOD31Samples, "Samples above 31 will be lost when saving as MOD. Consider rearranging samples if there are unused slots available." },
|
|
{ wAdlibInstruments, "OPL instruments are not supported by this format." },
|
|
// Instrument warnings
|
|
{ wInstrumentsToSamples, "All instruments have been converted to samples." },
|
|
{ wTrimmedEnvelopes, "Instrument envelopes have been shortened." },
|
|
{ wInstrumentSustainLoops, "Sustain loops were converted to sustain points." },
|
|
{ wInstrumentTuning, "Instrument tunings will be lost." },
|
|
{ wPitchToTempoLock, "Pitch / Tempo Lock instrument property is not supported by the new format." },
|
|
{ wBrokenNoteMap, "Instrument Note Mapping is not supported by the new format." },
|
|
{ wReleaseNode, "Instrument envelope release nodes are not supported by the new format." },
|
|
{ wFilterVariation, "Random filter variation is not supported by the new format." },
|
|
// General warnings
|
|
{ wMODGlobalVars, "Default speed, tempo and global volume will be lost." },
|
|
{ wLinearSlides, "Linear Frequency Slides not supported by the new format." },
|
|
{ wEditHistory, "Edit history will not be saved in the new format." },
|
|
{ wMixmode, "Consider setting the mix levels to \"Compatible\" in the song properties when working with legacy formats." },
|
|
{ wVolRamp, "Fasttracker 2 compatible super soft volume ramping gets lost when converting XM files to another type." },
|
|
{ wGlobalVolumeNotSupported, "Default global volume is not supported by the new format." },
|
|
{ wResamplingMode, "Song-specific resampling mode is not supported by the new format." },
|
|
{ wFractionalTempo, "Fractional tempo is not supported by the new format." },
|
|
};
|
|
for(const auto &msg : messages)
|
|
{
|
|
if(warnings[msg.warning])
|
|
AddToLog(LogInformation, mpt::ToUnicode(mpt::Charset::UTF8, msg.mesage));
|
|
}
|
|
|
|
SetModified();
|
|
GetPatternUndo().ClearUndo();
|
|
UpdateAllViews(nullptr, GeneralHint().General().ModType());
|
|
EndWaitCursor();
|
|
|
|
// Update effect key commands
|
|
CMainFrame::GetInputHandler()->SetEffectLetters(m_SndFile.GetModSpecifications());
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
OPENMPT_NAMESPACE_END
|